diff options
68 files changed, 2202 insertions, 277 deletions
diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp index 48cb55e3e3..14abf76eb9 100644 --- a/indra/llinventory/llinventory.cpp +++ b/indra/llinventory/llinventory.cpp @@ -60,6 +60,7 @@ static const std::string INV_LINKED_ID_LABEL("linked_id"); static const std::string INV_SALE_INFO_LABEL("sale_info"); static const std::string INV_FLAGS_LABEL("flags"); static const std::string INV_CREATION_DATE_LABEL("created_at"); +static const std::string INV_TOGGLED_LABEL("toggled"); // key used by agent-inventory-service static const std::string INV_ASSET_TYPE_LABEL_WS("type_default"); @@ -261,9 +262,18 @@ bool LLInventoryObject::importLegacyStream(std::istream& input_stream) { setThumbnailUUID(LLUUID::null); } + if (metadata.has("favorite")) { - setFavorite(metadata["favorite"].asBoolean()); + const LLSD& favorite = metadata["favorite"]; + if (favorite.has("toggled")) + { + setFavorite(favorite["toggled"].asBoolean()); + } + else + { + setFavorite(false); + } } else { @@ -757,9 +767,18 @@ bool LLInventoryItem::importLegacyStream(std::istream& input_stream) { setThumbnailUUID(LLUUID::null); } + if (metadata.has("favorite")) { - setFavorite(metadata["favorite"].asBoolean()); + const LLSD& favorite = metadata["favorite"]; + if (favorite.has("toggled")) + { + setFavorite(favorite["toggled"].asBoolean()); + } + else + { + setFavorite(false); + } } else { @@ -927,7 +946,7 @@ void LLInventoryItem::asLLSD( LLSD& sd ) const if (mFavorite) { - sd[INV_FAVORITE_LABEL] = mFavorite; + sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); } U32 mask = mPermissions.getMaskBase(); @@ -972,6 +991,7 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new) // TODO - figure out if this should be moved into the noclobber fields above mThumbnailUUID.setNull(); + mFavorite = false; // iterate as map to avoid making unnecessary temp copies of everything LLSD::map_const_iterator i, end; @@ -1019,7 +1039,12 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new) if (i->first == INV_FAVORITE_LABEL) { - mFavorite = i->second.asBoolean(); + const LLSD& favorite_map = i->second; + const std::string w = INV_TOGGLED_LABEL; + if (favorite_map.has(w)) + { + mFavorite = favorite_map[w].asBoolean(); + } continue; } @@ -1220,7 +1245,7 @@ LLSD LLInventoryCategory::asLLSD() const if (mFavorite) { - sd[INV_FAVORITE_LABEL] = mFavorite; + sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); } return sd; @@ -1234,13 +1259,15 @@ LLSD LLInventoryCategory::asAISCreateCatLLSD() const S8 type = static_cast<S8>(mPreferredType); sd[INV_ASSET_TYPE_LABEL_WS] = type; sd[INV_NAME_LABEL] = mName; + if (mThumbnailUUID.notNull()) { sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); } + if (mFavorite) { - sd[INV_FAVORITE_LABEL] = mFavorite; + sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); } return sd; @@ -1290,10 +1317,16 @@ bool LLInventoryCategory::fromLLSD(const LLSD& sd) mThumbnailUUID = sd[w]; } } + mFavorite = false; w = INV_FAVORITE_LABEL; if (sd.has(w)) { - mFavorite = sd[w].asBoolean(); + const LLSD& favorite_map = sd[w]; + w = INV_TOGGLED_LABEL; + if (favorite_map.has(w)) + { + mFavorite = favorite_map[w].asBoolean(); + } } w = INV_ASSET_TYPE_LABEL; if (sd.has(w)) @@ -1417,9 +1450,18 @@ bool LLInventoryCategory::importLegacyStream(std::istream& input_stream) { setThumbnailUUID(LLUUID::null); } + if (metadata.has("favorite")) { - setFavorite(metadata["favorite"].asBoolean()); + const LLSD& favorite = metadata["favorite"]; + if (favorite.has("toggled")) + { + setFavorite(favorite["toggled"].asBoolean()); + } + else + { + setFavorite(false); + } } else { @@ -1474,7 +1516,7 @@ LLSD LLInventoryCategory::exportLLSD() const } if (mFavorite) { - cat_data[INV_FAVORITE_LABEL] = mFavorite; + cat_data[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); } return cat_data; @@ -1510,7 +1552,13 @@ bool LLInventoryCategory::importLLSD(const LLSD& cat_data) } if (cat_data.has(INV_FAVORITE_LABEL)) { - setFavorite(cat_data[INV_FAVORITE_LABEL].asBoolean()); + bool favorite = false; + const LLSD& favorite_data = cat_data[INV_FAVORITE_LABEL]; + if (favorite_data.has(INV_TOGGLED_LABEL)) + { + favorite = favorite_data[INV_TOGGLED_LABEL].asBoolean(); + } + setFavorite(favorite); } if (cat_data.has(INV_NAME_LABEL)) { diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h index 3fdcf9f7f2..bb0b8ce04f 100644 --- a/indra/llui/llaccordionctrltab.h +++ b/indra/llui/llaccordionctrltab.h @@ -140,7 +140,7 @@ public: S32 notify(const LLSD& info); bool notifyChildren(const LLSD& info); - void draw(); + virtual void draw(); void storeOpenCloseState(); void restoreOpenCloseState(); diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index b8c833f4fd..8a69bd010e 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -1342,9 +1342,17 @@ bool LLFlatListViewEx::getForceShowingUnmatchedItems() const return mForceShowingUnmatchedItems; } -void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show) +void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show, bool notify_parent) +{ + if (mForceShowingUnmatchedItems != show) { mForceShowingUnmatchedItems = show; + if (!mFilterSubString.empty()) + { + updateNoItemsMessage(mFilterSubString); + filterItems(false, true); + } + } } void LLFlatListViewEx::setFilterSubString(const std::string& filter_str, bool notify_parent) @@ -1412,6 +1420,7 @@ void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) if (visibility_changed && notify_parent) { + rearrangeItems(); notifyParentItemsRectChanged(); } } diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 112c330a15..32fef6c11a 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -480,7 +480,11 @@ public: bool getForceShowingUnmatchedItems() const; - void setForceShowingUnmatchedItems(bool show); + /** + * Sets filtered out items to stay visible. Can result in rect changes, + * so can notify_parent if rect changes + */ + void setForceShowingUnmatchedItems(bool show, bool notify_parent); /** * Sets up new filter string and filters the list. diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 18bde344a0..21898366e7 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -57,6 +57,7 @@ LLUIColor LLFolderViewItem::sFilterBGColor; LLUIColor LLFolderViewItem::sFilterTextColor; LLUIColor LLFolderViewItem::sSuffixColor; LLUIColor LLFolderViewItem::sSearchStatusColor; +LLUIColor LLFolderViewItem::sFavoriteColor; S32 LLFolderViewItem::sTopPad = 0; LLUIImagePtr LLFolderViewItem::sFolderArrowImg; LLUIImagePtr LLFolderViewItem::sSelectionImg; @@ -113,6 +114,7 @@ void LLFolderViewItem::initClass() sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); + sFavoriteColor = LLUIColorTable::instance().getColor("InventoryFavoriteColor", DEFAULT_WHITE); } //static @@ -129,6 +131,8 @@ void LLFolderViewItem::cleanupClass() LLFolderViewItem::Params::Params() : root(), listener(), + favorite_image("favorite_image"), + favorite_content_image("favorite_content_image"), folder_arrow_image("folder_arrow_image"), folder_indentation("folder_indentation"), selection_image("selection_image"), @@ -155,6 +159,8 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) : LLView(p), mLabelWidth(0), mLabelWidthDirty(false), + mIsFavorite(false), + mHasFavorites(false), mSuffixNeedsRefresh(false), mLabelPaddingRight(DEFAULT_LABEL_PADDING_RIGHT), mParentFolder( NULL ), @@ -193,6 +199,10 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) { mViewModelItem->setFolderViewItem(this); } + if (mViewModelItem) + { + mViewModelItem->setFolderViewItem(this); + } } // Destroys the object @@ -211,6 +221,7 @@ bool LLFolderViewItem::postBuild() // getDisplayName() is expensive (due to internal getLabelSuffix() and name building) // it also sets search strings so it requires a filter reset mLabel = utf8str_to_wstring(vmi->getDisplayName()); + mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); setToolTip(vmi->getName()); // Dirty the filter flag of the model from the view (CHUI-849) @@ -325,6 +336,7 @@ void LLFolderViewItem::refresh() mLabel = utf8str_to_wstring(vmi.getDisplayName()); mLabelFontBuffer.reset(); + mIsFavorite = vmi.isFavorite() && !vmi.isItemInTrash(); setToolTip(vmi.getName()); // icons are slightly expensive to get, can be optimized // see LLInventoryIcon::getIcon() @@ -359,6 +371,8 @@ void LLFolderViewItem::refreshSuffix() mIconOpen = vmi->getIconOpen(); mIconOverlay = vmi->getIconOverlay(); + mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); + if (mRoot->useLabelSuffix()) { // Very Expensive! @@ -771,6 +785,35 @@ void LLFolderViewItem::drawOpenFolderArrow() } } +void LLFolderViewItem::drawFavoriteIcon(const Params& default_params, const LLUIColor& fg_color) +{ + static LLUICachedControl<bool> draw_star("InventoryFavoritesUseStar", true); + if (!draw_star) + { + return; + } + + LLUIImage* favorite_image = NULL; + if (mIsFavorite) + { + favorite_image = default_params.favorite_image; + } + else if (mHasFavorites && !isOpen()) + { + favorite_image = default_params.favorite_content_image; + } + + if (favorite_image) + { + const S32 PAD = 3; + const S32 image_size = 14; + + gl_draw_scaled_image( + getRect().getWidth() - image_size - PAD, getRect().getHeight() - mItemHeight + PAD, + image_size, image_size, favorite_image->getImage(), fg_color); + } +} + /*virtual*/ bool LLFolderViewItem::isHighlightAllowed() { return mIsSelected; @@ -919,6 +962,8 @@ void LLFolderViewItem::draw() const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); const bool filled = show_context || (getRoot() ? getRoot()->getParentPanel()->hasFocus() : false); // If we have keyboard focus, draw selection filled + const Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>(); + const LLFontGL* font = getLabelFont(); S32 line_height = font->getLineHeight(); @@ -928,6 +973,7 @@ void LLFolderViewItem::draw() { drawOpenFolderArrow(); } + drawFavoriteIcon(default_params, sFgColor); drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); @@ -999,7 +1045,20 @@ void LLFolderViewItem::draw() } } - LLColor4 color = (mIsSelected && filled) ? mFontHighlightColor : mFontColor; + static LLUICachedControl<bool> highlight_color("InventoryFavoritesColorText", true); + LLColor4 color; + if (mIsSelected && filled) + { + color = mFontHighlightColor; + } + else if (mIsFavorite && highlight_color) + { + color = sFavoriteColor; + } + else + { + color = mFontColor; + } if (isFadeItem()) { @@ -1093,7 +1152,8 @@ LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): mIsFolderComplete(false), // folder might have children that are not loaded yet. mAreChildrenInited(false), // folder might have children that are not built yet. mLastArrangeGeneration( -1 ), - mLastCalculatedWidth(0) + mLastCalculatedWidth(0), + mFavoritesDirtyFlags(0) { } @@ -1119,6 +1179,11 @@ LLFolderViewFolder::~LLFolderViewFolder( void ) // The LLView base class takes care of object destruction. make sure that we // don't have mouse or keyboard focus gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + + if (mFavoritesDirtyFlags) + { + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, this); + } } // addToFolder() returns true if it succeeds. false otherwise @@ -1762,6 +1827,128 @@ bool LLFolderViewFolder::isMovable() return true; } +void LLFolderViewFolder::updateHasFavorites(bool new_childs_value) +{ + if (mFavoritesDirtyFlags == 0) + { + gIdleCallbacks.addFunction(&LLFolderViewFolder::onIdleUpdateFavorites, this); + } + if (new_childs_value) + { + mFavoritesDirtyFlags |= FAVORITE_ADDED; + } + else + { + mFavoritesDirtyFlags |= FAVORITE_REMOVED; + } +} + +void LLFolderViewFolder::onIdleUpdateFavorites(void* data) +{ + LLFolderViewFolder* self = reinterpret_cast<LLFolderViewFolder*>(data); + if (self->mFavoritesDirtyFlags == 0) + { + LL_WARNS() << "Called onIdleUpdateFavorites without dirty flags set" << LL_ENDL; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + return; + } + + if (self->getViewModelItem()->isItemInTrash()) + { + // do not display favorite-stars in trash + self->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + return; + } + + if (self->mFavoritesDirtyFlags == FAVORITE_ADDED) + { + if (!self->mHasFavorites) + { + // propagate up, exclude root + LLFolderViewFolder* parent = self; + while (parent + && (!parent->hasFavorites() || parent->mFavoritesDirtyFlags) + && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + parent->setHasFavorites(true); + if (parent->mFavoritesDirtyFlags) + { + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, parent); + parent->mFavoritesDirtyFlags = 0; + } + parent = parent->getParentFolder(); + } + } + } + else if (self->mFavoritesDirtyFlags > FAVORITE_ADDED) + { + // full check + LLFolderViewFolder* parent = self; + while (parent && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + bool has_favorites = false; + for (items_t::iterator iter = parent->mItems.begin(); + iter != parent->mItems.end();) + { + items_t::iterator iit = iter++; + if ((*iit)->isFavorite()) + { + has_favorites = true; + break; + } + } + + for (folders_t::iterator iter = parent->mFolders.begin(); + iter != parent->mFolders.end() && !has_favorites;) + { + folders_t::iterator fit = iter++; + if ((*fit)->isFavorite() || (*fit)->hasFavorites()) + { + has_favorites = true; + break; + } + } + + if (!has_favorites) + { + if (parent->hasFavorites()) + { + parent->setHasFavorites(false); + } + else + { + // Nothing changed + break; + } + } + else + { + // propagate up, exclude root + while (parent + && (!parent->hasFavorites() || parent->mFavoritesDirtyFlags) + && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + parent->setHasFavorites(true); + if (parent->mFavoritesDirtyFlags) + { + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, parent); + parent->mFavoritesDirtyFlags = 0; + } + parent = parent->getParentFolder(); + } + break; + } + if (parent->mFavoritesDirtyFlags) + { + parent->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, parent); + } + parent = parent->getParentFolder(); + } + } +} + bool LLFolderViewFolder::isRemovable() { diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 234d0dc7f9..5f979231e1 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -50,7 +50,9 @@ class LLFolderViewItem : public LLView public: struct Params : public LLInitParam::Block<Params, LLView::Params> { - Optional<LLUIImage*> folder_arrow_image, + Optional<LLUIImage*> favorite_image, + favorite_content_image, + folder_arrow_image, selection_image; Mandatory<LLFolderView*> root; Mandatory<LLFolderViewModelItem*> listener; @@ -93,6 +95,8 @@ protected: LLWString mLabel; S32 mLabelWidth; bool mLabelWidthDirty; + bool mIsFavorite; + bool mHasFavorites; S32 mLabelPaddingRight; LLFolderViewFolder* mParentFolder; LLPointer<LLFolderViewModelItem> mViewModelItem; @@ -145,6 +149,8 @@ protected: static LLUIColor sFilterTextColor; static LLUIColor sSuffixColor; static LLUIColor sSearchStatusColor; + static LLUIColor sFavoriteColor; + // this is an internal method used for adding items to folders. A // no-op at this level, but reimplemented in derived classes. @@ -208,6 +214,8 @@ public: // Returns true is this object and all of its children can be moved virtual bool isMovable(); + bool isFavorite() const { return mIsFavorite; } + // destroys this item recursively virtual void destroyView(); @@ -298,6 +306,7 @@ public: // virtual void handleDropped(); virtual void draw(); void drawOpenFolderArrow(); + void drawFavoriteIcon(const Params& default_params, const LLUIColor& fg_color); void drawHighlight(bool showContent, bool hasKeyboardFocus, const LLUIColor& selectColor, const LLUIColor& flashColor, const LLUIColor& outlineColor, const LLUIColor& mouseOverColor); void drawLabel(const LLFontGL* font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x); virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, @@ -400,6 +409,18 @@ public: // Returns true is this object and all of its children can be moved virtual bool isMovable(); + bool isFavorite() const { return mIsFavorite; } + bool hasFavorites() const { return mHasFavorites; } + void setHasFavorites(bool val) { mHasFavorites = val; } + void updateHasFavorites(bool new_childs_value); +private: + static void onIdleUpdateFavorites(void* data); + + constexpr static S32 FAVORITE_ADDED = 1; + constexpr static S32 FAVORITE_REMOVED = 2; + S32 mFavoritesDirtyFlags { 0 }; +public: + // destroys this folder, and all children virtual void destroyView(); void destroyRoot(); diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index 9372818ca5..24bac4509b 100644 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -162,6 +162,7 @@ public: virtual void navigateToFolder(bool new_window = false, bool change_mode = false) = 0; + virtual bool isFavorite() const = 0; virtual bool isItemWearable() const { return false; } virtual bool isItemRenameable() const = 0; @@ -171,6 +172,7 @@ public: virtual void move( LLFolderViewModelItem* parent_listener ) = 0; virtual bool isItemRemovable( bool check_worn = true ) const = 0; // Can be destroyed + virtual bool isItemInTrash(void) const = 0; virtual bool removeItem() = 0; virtual void removeBatch(std::vector<LLFolderViewModelItem*>& batch) = 0; @@ -183,6 +185,9 @@ public: virtual void pasteFromClipboard() = 0; virtual void pasteLinkFromClipboard() = 0; + virtual bool isAgentInventory() const = 0; + virtual bool isAgentInventoryRoot() const = 0; + virtual void buildContextMenu(LLMenuGL& menu, U32 flags) = 0; virtual bool potentiallyVisible() = 0; // is the item definitely visible or we haven't made up our minds yet? diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 4cc22ef23a..923659e32c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -14347,6 +14347,28 @@ <key>Value</key> <integer>0</integer> </map> + <key>OutfitListSortOrder</key> + <map> + <key>Comment</key> + <string>How outfit list in Avatar's floater is sorted. 0 - by name 1 - favorites to top</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>S32</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>OutfitListFilterFullList</key> + <map> + <key>Comment</key> + <string>How outfit list in Avatar's floater is sorted. 0 - by name 1 - favorites to top</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>OutfitOperationsTimeout</key> <map> <key>Comment</key> @@ -16220,6 +16242,39 @@ <key>Value</key> <integer>0</integer> </map> + <key>InventoryFavoritesUseStar</key> + <map> + <key>Comment</key> + <string>Show star near favorited items in inventory</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>InventoryFavoritesColorText</key> + <map> + <key>Comment</key> + <string>render favorite items using InventoryFavoriteText as color</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>InventoryAddAttachmentBehavior</key> + <map> + <key>Comment</key> + <string>Defines behavior when hitting return on an inventory item</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>StatsReportMaxDuration</key> <map> <key>Comment</key> diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 9e6aff41cf..bc816d3bb3 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2030,7 +2030,7 @@ bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id) } // Moved from LLWearableList::ContextMenu for wider utility. -bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids) const +bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids, bool warn_on_type_mismarch) const { // TODO: investigate wearables may not be loaded at this point EXT-8231 @@ -2060,7 +2060,10 @@ bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids) const } else { + if (warn_on_type_mismarch) + { LL_WARNS() << "Unexpected wearable type" << LL_ENDL; + } return false; } } diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 9e624f593f..78971cd3eb 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -104,7 +104,7 @@ public: bool getCanReplaceCOF(const LLUUID& outfit_cat_id); // Can we add all referenced items to the avatar? - bool canAddWearables(const uuid_vec_t& item_ids) const; + bool canAddWearables(const uuid_vec_t& item_ids, bool warn_on_type_mismarch = true) const; // Copy all items in a category. void shallowCopyCategoryContents(const LLUUID& src_id, const LLUUID& dst_id, diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index c1e48c63a9..d5486b9f4a 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -79,6 +79,9 @@ public: virtual LLPointer<LLUIImage> getOpenIcon() const { return getIcon(); } virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } virtual std::string getLabelSuffix() const { return LLStringUtil::null; } + virtual bool isFavorite() const { return false; } + virtual bool isAgentInventory() const { return false; } + virtual bool isAgentInventoryRoot() const { return false; } virtual bool isItemRenameable() const { return true; } virtual bool renameItem(const std::string& new_name) { mName = new_name; mNeedsRefresh = true; return true; } virtual bool isItemMovable( void ) const { return false; } diff --git a/indra/newview/llfloaterinventorysettings.cpp b/indra/newview/llfloaterinventorysettings.cpp index e5ee69f240..3232b4f8b2 100644 --- a/indra/newview/llfloaterinventorysettings.cpp +++ b/indra/newview/llfloaterinventorysettings.cpp @@ -28,9 +28,13 @@ #include "llfloaterinventorysettings.h" +#include "llcolorswatch.h" + LLFloaterInventorySettings::LLFloaterInventorySettings(const LLSD& key) : LLFloater(key) { + mCommitCallbackRegistrar.add("ScriptPref.applyUIColor", { boost::bind(&LLFloaterInventorySettings::applyUIColor, this, _1, _2) }); + mCommitCallbackRegistrar.add("ScriptPref.getUIColor", { boost::bind(&LLFloaterInventorySettings::getUIColor, this, _1, _2) }); } LLFloaterInventorySettings::~LLFloaterInventorySettings() @@ -38,7 +42,25 @@ LLFloaterInventorySettings::~LLFloaterInventorySettings() bool LLFloaterInventorySettings::postBuild() { + getChild<LLUICtrl>("favorites_color")->setCommitCallback(boost::bind(&LLFloaterInventorySettings::updateColorSwatch, this)); getChild<LLButton>("ok_btn")->setCommitCallback(boost::bind(&LLFloater::closeFloater, this, false)); return true; } +void LLFloaterInventorySettings::updateColorSwatch() +{ + bool val = getChild<LLUICtrl>("favorites_color")->getEnabled(); + getChild<LLUICtrl>("favorites_color")->setEnabled(val); +} + +void LLFloaterInventorySettings::applyUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLUIColorTable::instance().setColor(param.asString(), LLColor4(ctrl->getValue())); +} + +void LLFloaterInventorySettings::getUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLColorSwatchCtrl* color_swatch = (LLColorSwatchCtrl*)ctrl; + color_swatch->setOriginal(LLUIColorTable::instance().getColor(param.asString())); +} + diff --git a/indra/newview/llfloaterinventorysettings.h b/indra/newview/llfloaterinventorysettings.h index 3fe3a001b9..c27d5d2e1b 100644 --- a/indra/newview/llfloaterinventorysettings.h +++ b/indra/newview/llfloaterinventorysettings.h @@ -40,6 +40,11 @@ public: private: LLFloaterInventorySettings(const LLSD& key); ~LLFloaterInventorySettings(); + + void updateColorSwatch(); + + void applyUIColor(LLUICtrl* ctrl, const LLSD& param); + void getUIColor(LLUICtrl* ctrl, const LLSD& param); }; #endif diff --git a/indra/newview/llfolderviewmodelinventory.cpp b/indra/newview/llfolderviewmodelinventory.cpp index c668d414d3..58132299de 100644 --- a/indra/newview/llfolderviewmodelinventory.cpp +++ b/indra/newview/llfolderviewmodelinventory.cpp @@ -68,9 +68,10 @@ void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder ) if (!folder->areChildrenInited() || !needsSort(folder->getViewModelItem())) return; - LLFolderViewModelItemInventory* modelp = static_cast<LLFolderViewModelItemInventory*>(folder->getViewModelItem()); - if (modelp->getUUID().isNull()) return; + LLFolderViewModelItemInventory* sort_modelp = static_cast<LLFolderViewModelItemInventory*>(folder->getViewModelItem()); + if (sort_modelp->getUUID().isNull()) return; + bool has_favorites = false; for (std::list<LLFolderViewFolder*>::iterator it = folder->getFoldersBegin(), end_it = folder->getFoldersEnd(); it != end_it; ++it) @@ -79,11 +80,14 @@ void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder ) LLFolderViewFolder* child_folderp = *it; sort(child_folderp); + LLFolderViewModelItemInventory* modelp = static_cast<LLFolderViewModelItemInventory*>(child_folderp->getViewModelItem()); + has_favorites |= child_folderp->isFavorite() || child_folderp->hasFavorites(); + if (child_folderp->getFoldersCount() > 0) { - time_t most_recent_folder_time = - static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getFoldersBegin())->getViewModelItem())->getCreationDate(); - LLFolderViewModelItemInventory* modelp = static_cast<LLFolderViewModelItemInventory*>(child_folderp->getViewModelItem()); + LLFolderViewModelItemInventory* folderp = static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getFoldersBegin())->getViewModelItem()); + time_t most_recent_folder_time = folderp->getCreationDate(); + if (most_recent_folder_time > modelp->getCreationDate()) { modelp->setCreationDate(most_recent_folder_time); @@ -91,16 +95,26 @@ void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder ) } if (child_folderp->getItemsCount() > 0) { - time_t most_recent_item_time = - static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getItemsBegin())->getViewModelItem())->getCreationDate(); + LLFolderViewModelItemInventory* itemp = static_cast<LLFolderViewModelItemInventory*>((*child_folderp->getItemsBegin())->getViewModelItem()); + time_t most_recent_item_time = itemp->getCreationDate(); - LLFolderViewModelItemInventory* modelp = static_cast<LLFolderViewModelItemInventory*>(child_folderp->getViewModelItem()); if (most_recent_item_time > modelp->getCreationDate()) { modelp->setCreationDate(most_recent_item_time); } } } + for (std::list<LLFolderViewItem*>::const_iterator it = folder->getItemsBegin(), end_it = folder->getItemsEnd(); + it != end_it && !has_favorites; + ++it) + { + LLFolderViewItem* child_itemp = *it; + has_favorites |= child_itemp->isFavorite(); + } + if (has_favorites) + { + folder->updateHasFavorites(true); + } base_t::sort(folder); } @@ -234,7 +248,7 @@ bool LLFolderViewModelItemInventory::filterChildItem( LLFolderViewModelItem* ite return continue_filtering; } -bool LLFolderViewModelItemInventory::filter(LLFolderViewFilter& filter) +bool LLFolderViewModelItemInventory::filter( LLFolderViewFilter& filter) { const S32 filter_generation = filter.getCurrentGeneration(); const S32 must_pass_generation = filter.getFirstRequiredGeneration(); diff --git a/indra/newview/llfolderviewmodelinventory.h b/indra/newview/llfolderviewmodelinventory.h index 48b4ee5fd9..04b0b6e8f4 100644 --- a/indra/newview/llfolderviewmodelinventory.h +++ b/indra/newview/llfolderviewmodelinventory.h @@ -48,6 +48,7 @@ public: virtual bool isItemInTrash( void) const { return false; } // TODO: make into pure virtual. virtual bool isItemInOutfits() const { return false; } virtual bool isAgentInventory() const { return false; } + virtual bool isAgentInventoryRoot() const { return false; } virtual bool isUpToDate() const = 0; virtual void addChild(LLFolderViewModelItem* child); virtual bool hasChildren() const = 0; diff --git a/indra/newview/llgltffolderitem.h b/indra/newview/llgltffolderitem.h index 89d90c81cc..a748e2fb6a 100644 --- a/indra/newview/llgltffolderitem.h +++ b/indra/newview/llgltffolderitem.h @@ -71,6 +71,7 @@ public: void navigateToFolder(bool new_window = false, bool change_mode = false) override {} + bool isFavorite() const override { return false; } bool isItemWearable() const override { return false; } bool isItemRenameable() const override { return false; } @@ -80,6 +81,7 @@ public: void move(LLFolderViewModelItem* parent_listener) override {} bool isItemRemovable(bool check_worn = true) const override { return false; } + bool isItemInTrash(void) const override { return false; } bool removeItem() override { return false; } void removeBatch(std::vector<LLFolderViewModelItem*>& batch) override {} @@ -92,6 +94,9 @@ public: void pasteFromClipboard() override {} void pasteLinkFromClipboard() override {} + bool isAgentInventory() const override { return false; } + bool isAgentInventoryRoot() const override { return false; } + void buildContextMenu(LLMenuGL& menu, U32 flags) override {}; bool potentiallyVisible() override { return true; }; // is the item definitely visible or we haven't made up our minds yet? diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 58667aba66..6382569fd5 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -830,6 +830,8 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, { const LLInventoryObject *obj = getInventoryObject(); bool single_folder_root = (mRoot == NULL); + bool is_cof = isCOFFolder(); + bool is_inbox = isInboxFolder(); if (obj) { @@ -844,7 +846,8 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, disabled_items.push_back(std::string("Copy")); } - if (isAgentInventory() && !single_folder_root && !isMarketplaceListingsFolder()) + bool is_agent_inventory = isAgentInventory(); + if (is_agent_inventory && !single_folder_root && !is_cof && !is_inbox && !isMarketplaceListingsFolder()) { items.push_back(std::string("New folder from selected")); items.push_back(std::string("Subfolder Separator")); @@ -857,6 +860,17 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, } } + if (isFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else if (is_agent_inventory + && gInventory.getRootFolderID() != mUUID + && !gInventory.isObjectDescendentOf(mUUID, gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH))) + { + items.push_back(std::string("Add to Favorites")); + } + if (obj->getIsLinkType()) { items.push_back(std::string("Find Original")); @@ -869,6 +883,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, if (!isItemMovable() || !canMenuCut()) { disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("New folder from selected")); } } else @@ -878,7 +893,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, items.push_back(std::string("Find Links")); } - if (!isInboxFolder() && !single_folder_root) + if (!is_inbox && !single_folder_root) { items.push_back(std::string("Rename")); if (!isItemRenameable() || ((flags & FIRST_SELECTED_ITEM) == 0)) @@ -918,6 +933,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, if (!isItemMovable() || !canMenuCut()) { disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("New folder from selected")); } if (canListOnMarketplace() && !isMarketplaceListingsFolder() && !isInboxFolder()) @@ -940,7 +956,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, } // Don't allow items to be pasted directly into the COF or the inbox - if (!isCOFFolder() && !isInboxFolder()) + if (!is_cof && !is_inbox) { items.push_back(std::string("Paste")); } @@ -1334,6 +1350,13 @@ bool LLInvFVBridge::isAgentInventory() const return model->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()); } +bool LLInvFVBridge::isAgentInventoryRoot() const +{ + const LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + return gInventory.getRootFolderID() == mUUID; +} + bool LLInvFVBridge::isCOFFolder() const { return LLAppearanceMgr::instance().getIsInCOF(mUUID); @@ -2281,7 +2304,7 @@ const LLUUID& LLItemBridge::getThumbnailUUID() const return LLUUID::null; } -bool LLItemBridge::getIsFavorite() const +bool LLItemBridge::isFavorite() const { LLViewerInventoryItem* item = NULL; LLInventoryModel* model = getInventoryModel(); @@ -2441,7 +2464,7 @@ const LLUUID& LLFolderBridge::getThumbnailUUID() const return LLUUID::null; } -bool LLFolderBridge::getIsFavorite() const +bool LLFolderBridge::isFavorite() const { LLViewerInventoryCategory* cat = getCategory(); if (cat) @@ -4447,6 +4470,15 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items items.push_back(std::string("Rename")); items.push_back(std::string("thumbnail")); + if (cat->getIsFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else + { + items.push_back(std::string("Add to Favorites")); + } + addDeleteContextMenuOptions(items, disabled_items); // EXT-4030: disallow deletion of currently worn outfit const LLViewerInventoryItem *base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); @@ -8079,7 +8111,8 @@ void LLObjectBridgeAction::attachOrDetach() } else { - LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. + static LLCachedControl<bool> inventory_linking(gSavedSettings, "InventoryAddAttachmentBehavior", false); + LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, inventory_linking()); // Don't replace if adding. } } @@ -8270,6 +8303,7 @@ void LLRecentItemsFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) buildContextMenuOptions(flags, items, disabled_items); items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end()); + items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end()); hide_context_entries(menu, items, disabled_items); } @@ -8304,6 +8338,51 @@ LLInvFVBridge* LLRecentInventoryBridgeBuilder::createBridge( return new_listener; } +/************************************************************************/ +/* Favorites Inventory Panel related classes */ +/************************************************************************/ +void LLFavoritesFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + // todo: consider things that should be disabled + menuentry_vec_t disabled_items, items; + buildContextMenuOptions(flags, items, disabled_items); + + items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end()); + items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end()); + + hide_context_entries(menu, items, disabled_items); +} + +LLInvFVBridge* LLFavoritesInventoryBridgeBuilder::createBridge( + LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags /*= 0x00*/) const +{ + LLInvFVBridge* new_listener = NULL; + if (asset_type == LLAssetType::AT_CATEGORY + && actual_asset_type != LLAssetType::AT_LINK_FOLDER) + { + new_listener = new LLFavoritesFolderBridge(inv_type, inventory, root, uuid); + } + else + { + new_listener = LLInventoryFolderViewModelBuilder::createBridge(asset_type, + actual_asset_type, + inv_type, + inventory, + view_model, + root, + uuid, + flags); + } + return new_listener; +} + LLFolderViewGroupedItemBridge::LLFolderViewGroupedItemBridge() { } @@ -8314,7 +8393,7 @@ void LLFolderViewGroupedItemBridge::groupFilterContextMenu(folder_view_item_dequ menuentry_vec_t disabled_items; if (get_selection_item_uuids(selected_items, ids)) { - if (!LLAppearanceMgr::instance().canAddWearables(ids) && canWearSelected(ids)) + if (!LLAppearanceMgr::instance().canAddWearables(ids, false) && canWearSelected(ids)) { disabled_items.push_back(std::string("Wearable And Object Wear")); disabled_items.push_back(std::string("Wearable Add")); diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index da61a2ac55..a19b5f8e59 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -86,7 +86,7 @@ public: //-------------------------------------------------------------------- virtual const LLUUID& getUUID() const { return mUUID; } virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null; } - virtual bool getIsFavorite() const { return false; } + virtual bool isFavorite() const { return false; } virtual void clearDisplayName() { mDisplayName.clear(); } virtual void restoreItem() {} virtual void restoreToWorld() {} @@ -176,6 +176,7 @@ protected: bool isLinkedObjectMissing() const; // Is this a linked obj whose baseobj is not in inventory? bool isAgentInventory() const; // false if lost or in the inventory library + bool isAgentInventoryRoot() const; // true if worn by agent bool isCOFFolder() const; // true if COF or descendant of bool isInboxFolder() const; // true if COF or descendant of marketplace inbox @@ -260,7 +261,7 @@ public: LLViewerInventoryItem* getItem() const; virtual const LLUUID& getThumbnailUUID() const; - virtual bool getIsFavorite() const; + virtual bool isFavorite() const; protected: bool confirmRemoveItem(const LLSD& notification, const LLSD& response); @@ -303,7 +304,7 @@ public: virtual std::string getLabelSuffix() const; virtual LLFontGL::StyleFlags getLabelStyle() const; virtual const LLUUID& getThumbnailUUID() const; - virtual bool getIsFavorite() const; + virtual bool isFavorite() const; void setShowDescendantsCount(bool show_count) {mShowDescendantsCount = show_count;} @@ -757,6 +758,45 @@ public: }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Favorites Inventory Panel related classes +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// Overridden version of the Inventory-Folder-View-Bridge for Folders +class LLFavoritesFolderBridge : public LLFolderBridge +{ + friend class LLInvFVBridgeAction; +public: + // Creates context menu for Folders related to Recent Inventory Panel. + // Uses base logic and than removes from visible items "New..." menu items. + LLFavoritesFolderBridge(LLInventoryType::EType type, + LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLFolderBridge(inventory, root, uuid) + { + mInvType = type; + } + /*virtual*/ void buildContextMenu(LLMenuGL& menu, U32 flags); +}; + +// Bridge builder to create Inventory-Folder-View-Bridge for Recent Inventory Panel +class LLFavoritesInventoryBridgeBuilder : public LLInventoryFolderViewModelBuilder +{ +public: + LLFavoritesInventoryBridgeBuilder() {} + // Overrides FolderBridge for Recent Inventory Panel. + // It use base functionality for bridges other than FolderBridge. + virtual LLInvFVBridge* createBridge(LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags = 0x00) const; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Marketplace Inventory Panel related classes //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 1a3c9c711a..25a834d318 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -160,6 +160,7 @@ bool LLInventoryFilter::check(const LLFolderViewModelItem* item) passed = passed && checkAgainstCreator(listener); passed = passed && checkAgainstSearchVisibility(listener); + passed = passed && checkAgainstFilterFavorites(listener->getUUID()); passed = passed && checkAgainstFilterThumbnails(listener->getUUID()); return passed; @@ -222,6 +223,19 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const return false; } + const LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (cat && cat->getIsFavorite()) + { + if (mFilterOps.mFilterFavorites == FILTER_ONLY_FAVORITES) + { + return true; + } + if (mFilterOps.mFilterFavorites == FILTER_EXCLUDE_FAVORITES) + { + return false; + } + } + // Marketplace folder filtering const U32 filterTypes = mFilterOps.mFilterTypes; const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | @@ -274,6 +288,16 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const } } + if (filterTypes & FILTERTYPE_NO_TRASH_ITEMS) + { + const LLUUID trash_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + // If not a descendant of the marketplace listings root, then the nesting depth is -1 by definition + if (gInventory.isObjectDescendentOf(folder_id, trash_uuid)) + { + return false; + } + } + // show folder links LLViewerInventoryItem* item = gInventory.getItem(folder_id); if (item && item->getActualType() == LLAssetType::AT_LINK_FOLDER) @@ -618,10 +642,12 @@ bool LLInventoryFilter::checkAgainstFilterFavorites(const LLUUID& object_id) con if (!object) return true; const bool is_favorite = object->getIsFavorite(); + if (is_favorite && (mFilterOps.mFilterFavorites == FILTER_EXCLUDE_FAVORITES)) return false; if (!is_favorite && (mFilterOps.mFilterFavorites == FILTER_ONLY_FAVORITES)) return false; + return true; } @@ -963,6 +989,11 @@ void LLInventoryFilter::toggleSearchVisibilityLibrary() } } +void LLInventoryFilter::setFilterNoTrashFolder() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_NO_TRASH_ITEMS; +} + void LLInventoryFilter::setFilterNoMarketplaceFolder() { mFilterOps.mFilterTypes |= FILTERTYPE_NO_MARKETPLACE_ITEMS; diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index 937864bbfa..c0164e04e4 100644 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -61,6 +61,7 @@ public: FILTERTYPE_NO_MARKETPLACE_ITEMS = 0x1 << 10, // pass iff folder is not under the marketplace FILTERTYPE_WORN = 0x1 << 11, // pass if item is worn FILTERTYPE_SETTINGS = 0x1 << 12, // pass if the item is a settings object + FILTERTYPE_NO_TRASH_ITEMS = 0x1 << 13, // pass iff folder is not under the marketplace }; enum EFilterDateDirection @@ -244,6 +245,7 @@ public: void setFilterMarketplaceInactiveFolders(); void setFilterMarketplaceUnassociatedFolders(); void setFilterMarketplaceListingFolders(bool select_only_listing_folders); + void setFilterNoTrashFolder(); void setFilterNoMarketplaceFolder(); void setFilterThumbnails(U64 filter_thumbnails); void setFilterFavorites(U64 filter_favorites); diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index fdf77ab8df..879c562af7 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -2418,6 +2418,102 @@ void ungroup_folder_items(const LLUUID& folder_id) gInventory.notifyObservers(); } +class LLUpdateFavorite : public LLInventoryCallback +{ +public: + LLUpdateFavorite(const LLUUID& inv_item_id) + : mInvItemID(inv_item_id) + {} + /* virtual */ void fire(const LLUUID& inv_item_id) override + { + gInventory.addChangedMask(LLInventoryObserver::UPDATE_FAVORITE, mInvItemID); + gInventory.notifyObservers(); + } +private: + LLUUID mInvItemID; +}; + +void set_favorite(const LLUUID& obj_id, bool favorite) +{ + LLInventoryObject* obj = gInventory.getObject(obj_id); + if (obj->getIsFavorite() != favorite) + { + LLSD val; + if (favorite) + { + val = true; + } // else leave undefined to remove unneeded metadata field + + LLSD updates; + if (favorite) + { + updates["favorite"] = LLSD().with("toggled", true); + } + else + { + updates["favorite"] = LLSD(); + } + + LLPointer<LLInventoryCallback> cb = new LLUpdateFavorite(obj_id); + + LLViewerInventoryCategory* view_folder = dynamic_cast<LLViewerInventoryCategory*>(obj); + if (view_folder) + { + update_inventory_category(obj_id, updates, cb); + } + LLViewerInventoryItem* view_item = dynamic_cast<LLViewerInventoryItem*>(obj); + if (view_item) + { + update_inventory_item(obj_id, updates, cb); + } + } +} + +void toggle_favorite(const LLUUID& obj_id) +{ + LLInventoryObject* obj = gInventory.getObject(obj_id); + if (!obj) + { + return; + } + + LLSD updates; + if (!obj->getIsFavorite()) + { + updates["favorite"] = LLSD().with("toggled", true); + } + else + { + updates["favorite"] = LLSD(); + } + + LLPointer<LLInventoryCallback> cb = new LLUpdateFavorite(obj_id); + + LLViewerInventoryCategory* view_folder = dynamic_cast<LLViewerInventoryCategory*>(obj); + if (view_folder) + { + update_inventory_category(obj_id, updates, cb); + } + LLViewerInventoryItem* view_item = dynamic_cast<LLViewerInventoryItem*>(obj); + if (view_item) + { + update_inventory_item(obj_id, updates, cb); + } +} + +void toggle_linked_favorite(const LLUUID& obj_id) +{ + LLViewerInventoryItem* item = gInventory.getItem(obj_id); + if (!item) + { + LL_WARNS() << "Invalid item" << LL_ENDL; + return; + } + + LLUUID linked_id = item->getLinkedUUID(); + toggle_favorite(linked_id); +} + std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id) { if (model) @@ -2697,6 +2793,20 @@ bool LLIsTypeWithPermissions::operator()(LLInventoryCategory* cat, LLInventoryIt return false; } +bool LLFavoritesCollector::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if (item && item->getIsFavorite()) + { + return true; + } + if (cat && cat->getIsFavorite()) + { + return true; + } + return false; +} + bool LLBuddyCollector::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { @@ -3481,6 +3591,20 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root LLFloaterReg::showInstance("change_item_thumbnail", data); } } + else if ("add_to_favorites" == action) + { + for (const LLUUID& id : ids) + { + set_favorite(id, true); + } + } + else if ("remove_from_favorites" == action) + { + for (const LLUUID& id : ids) + { + set_favorite(id, false); + } + } else { std::set<LLFolderViewItem*>::iterator set_iter; diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index cd6f319f66..9005dc9b06 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -116,6 +116,9 @@ bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_ca std::string get_localized_folder_name(LLUUID cat_uuid); void new_folder_window(const LLUUID& folder_id); void ungroup_folder_items(const LLUUID& folder_id); +void set_favorite(const LLUUID& obj_id, bool favorite); +void toggle_favorite(const LLUUID& obj_id); +void toggle_linked_favorite(const LLUUID& obj_id); std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id); std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id); std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id); @@ -328,6 +331,21 @@ protected: }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFavoritesCollector +// +// Simple class that collects calling cards that are not null, and not +// the agent. Duplicates are possible. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFavoritesCollector : public LLInventoryCollectFunctor +{ +public: + LLFavoritesCollector() {} + virtual ~LLFavoritesCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLBuddyCollector // // Simple class that collects calling cards that are not null, and not diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp index 03bafa48bd..45c5246aa5 100644 --- a/indra/newview/llinventorygallery.cpp +++ b/indra/newview/llinventorygallery.cpp @@ -634,7 +634,7 @@ void LLInventoryGallery::removeFromLastRow(LLInventoryGalleryItem* item) mItemPanels.pop_back(); } -LLInventoryGalleryItem* LLInventoryGallery::buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn) +LLInventoryGalleryItem* LLInventoryGallery::buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn, bool is_favorite) { LLInventoryGalleryItem::Params giparams; giparams.visible = true; @@ -645,6 +645,7 @@ LLInventoryGalleryItem* LLInventoryGallery::buildGalleryItem(std::string name, L gitem->setUUID(item_id); gitem->setGallery(this); gitem->setType(type, inventory_type, flags, is_link); + gitem->setFavorite(is_favorite); gitem->setLoadImmediately(mLoadThumbnailsImmediately); gitem->setThumbnail(thumbnail_id); gitem->setWorn(is_worn); @@ -938,7 +939,17 @@ bool LLInventoryGallery::updateAddedItem(LLUUID item_id) bool res = false; - LLInventoryGalleryItem* item = buildGalleryItem(name, item_id, obj->getType(), thumbnail_id, inventory_type, misc_flags, obj->getCreationDate(), obj->getIsLinkType(), is_worn); + LLInventoryGalleryItem* item = buildGalleryItem( + name, + item_id, + obj->getType(), + thumbnail_id, + inventory_type, + misc_flags, + obj->getCreationDate(), + obj->getIsLinkType(), + is_worn, + obj->getIsFavorite()); mItemMap.insert(LLInventoryGallery::gallery_item_map_t::value_type(item_id, item)); if (mGalleryCreated) { @@ -975,7 +986,7 @@ void LLInventoryGallery::updateRemovedItem(LLUUID item_id) mItemBuildQuery.erase(item_id); } -void LLInventoryGallery::updateChangedItemName(LLUUID item_id, std::string name) +void LLInventoryGallery::updateChangedItemData(LLUUID item_id, std::string name, bool is_favorite) { gallery_item_map_t::iterator iter = mItemMap.find(item_id); if (iter != mItemMap.end()) @@ -984,6 +995,7 @@ void LLInventoryGallery::updateChangedItemName(LLUUID item_id, std::string name) if (item) { item->setItemName(name); + item->setFavorite(is_favorite); } } } @@ -2357,7 +2369,7 @@ void LLInventoryGallery::refreshList(const LLUUID& category_id) return; } - updateChangedItemName(*items_iter, obj->getName()); + updateChangedItemData(*items_iter, obj->getName(), obj->getIsFavorite()); mNeedsArrange = true; } @@ -2873,6 +2885,14 @@ void LLInventoryGalleryItem::setType(LLAssetType::EType type, LLInventoryType::E getChild<LLIconCtrl>("link_overlay")->setVisible(is_link); } +void LLInventoryGalleryItem::setFavorite(bool is_favorite) +{ + getChild<LLIconCtrl>("fav_icon")->setVisible(is_favorite); + static const LLUIColor text_color = LLUIColorTable::instance().getColor("LabelTextColor", LLColor4::white); + static const LLUIColor favorite_color = LLUIColorTable::instance().getColor("InventoryFavoriteColor", LLColor4::white); + mNameText->setReadOnlyColor(is_favorite ? favorite_color : text_color); +} + void LLInventoryGalleryItem::setThumbnail(LLUUID id) { mDefaultImage = id.isNull(); diff --git a/indra/newview/llinventorygallery.h b/indra/newview/llinventorygallery.h index 59d08d19ed..7f53f9998d 100644 --- a/indra/newview/llinventorygallery.h +++ b/indra/newview/llinventorygallery.h @@ -102,7 +102,7 @@ public: void getCurrentCategories(uuid_vec_t& vcur); bool updateAddedItem(LLUUID item_id); // returns true if added item is visible void updateRemovedItem(LLUUID item_id); - void updateChangedItemName(LLUUID item_id, std::string name); + void updateChangedItemData(LLUUID item_id, std::string name, bool is_favorite); void updateItemThumbnail(LLUUID item_id); void updateWornItem(LLUUID item_id, bool is_worn); @@ -227,7 +227,7 @@ private: bool updateRowsIfNeeded(); void updateGalleryWidth(); - LLInventoryGalleryItem* buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn); + LLInventoryGalleryItem* buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn, bool is_favorite); LLInventoryGalleryItem* getItem(const LLUUID& id) const; void buildGalleryPanel(int row_count); @@ -343,6 +343,7 @@ public: void setHidden(bool hidden) {mHidden = hidden;} void setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link); + void setFavorite(bool is_favorite); LLAssetType::EType getAssetType() { return mType; } void setThumbnail(LLUUID id); void setGallery(LLInventoryGallery* gallery) { mGallery = gallery; } diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index 340ecfcbbc..018d2e9521 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -269,6 +269,20 @@ void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata) LLAppearanceMgr::instance().takeOffOutfit(cat->getLinkedUUID()); } } + else if ("add_to_favorites" == action) + { + for (const LLUUID& id : mUUIDs) + { + set_favorite(id, true); + } + } + else if ("remove_from_favorites" == action) + { + for (const LLUUID& id : mUUIDs) + { + set_favorite(id, false); + } + } else if ("take_off" == action || "detach" == action) { for (LLUUID& selected_id : mUUIDs) @@ -795,6 +809,18 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men items.push_back(std::string("New Outfit")); } + if (!is_trash && !is_in_trash && gInventory.getRootFolderID() != selected_id) + { + if (obj->getIsFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else + { + items.push_back(std::string("Add to Favorites")); + } + } + items.push_back(std::string("Subfolder Separator")); if (!is_system_folder && !isRootFolder()) { @@ -840,6 +866,17 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men if(is_agent_inventory) { items.push_back(std::string("Cut")); + if (!is_in_trash) + { + if (obj->getIsFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else + { + items.push_back(std::string("Add to Favorites")); + } + } if (!is_link || !is_cof || !get_is_item_worn(selected_id)) { items.push_back(std::string("Delete")); @@ -1025,6 +1062,15 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men disabled_items.push_back(std::string("Marketplace Move")); } } + + if (obj->getIsFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else if (is_agent_inventory) + { + items.push_back(std::string("Add to Favorites")); + } } hide_context_entries(*menu, items, disabled_items); diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index ac791e224e..ac22be9d5a 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -749,6 +749,13 @@ void LLInventoryCategoriesObserver::changed(U32 mask) cat_changed = true; } + bool is_favorite = category->getIsFavorite(); + if (cat_data.mIsFavorite != is_favorite) + { + cat_data.mIsFavorite = is_favorite; + cat_changed = true; + } + // If anything has changed above, fire the callback. if (cat_changed) cat_data.mCallback(); @@ -766,6 +773,7 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; S32 current_num_known_descendents = LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN; bool can_be_added = true; + bool favorite = false; LLUUID thumbnail_id; LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); @@ -779,6 +787,7 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t // to a category have been made. version = category->getVersion(); thumbnail_id = category->getThumbnailUUID(); + favorite = category->getIsFavorite(); LLInventoryModel::cat_array_t* cats; LLInventoryModel::item_array_t* items; @@ -804,11 +813,11 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t if(init_name_hash) { digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents,item_name_hash))); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, favorite, cb, version, current_num_known_descendents,item_name_hash))); } else { - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents))); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, favorite, cb, version, current_num_known_descendents))); } } @@ -821,25 +830,37 @@ void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id) } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents) + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, + S32 version, + S32 num_descendents) : mCatID(cat_id) , mCallback(cb) , mVersion(version) , mDescendentsCount(num_descendents) , mThumbnailId(thumbnail_id) + , mIsFavorite(is_favorite) , mIsNameHashInitialized(false) { } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash) + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, S32 version, + S32 num_descendents, + const digest_t& name_hash) : mCatID(cat_id) , mCallback(cb) , mVersion(version) , mDescendentsCount(num_descendents) , mThumbnailId(thumbnail_id) + , mIsFavorite(is_favorite) , mIsNameHashInitialized(true) , mItemNameHash(name_hash) { diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h index 950b02d3cf..12d6c44521 100644 --- a/indra/newview/llinventoryobserver.h +++ b/indra/newview/llinventoryobserver.h @@ -60,6 +60,7 @@ public: CREATE = 512, // With ADD, item has just been created. // unfortunately a particular message is still associated with some unique semantics. UPDATE_CREATE = 1024, // With ADD, item added via UpdateCreateInventoryItem + UPDATE_FAVORITE = 2048, // With ADD, item added via UpdateCreateInventoryItem ALL = 0xffffffff }; LLInventoryObserver(); @@ -276,12 +277,26 @@ protected: typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" struct LLCategoryData { - LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents); - LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash); + LLCategoryData( + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, + S32 version, + S32 num_descendents); + LLCategoryData( + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, + S32 version, + S32 num_descendents, + const digest_t& name_hash); callback_t mCallback; S32 mVersion; S32 mDescendentsCount; digest_t mItemNameHash; + bool mIsFavorite; bool mIsNameHashInitialized; LLUUID mCatID; LLUUID mThumbnailId; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index b8833dcd05..cd36dc1ffb 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -55,11 +55,13 @@ #include "llviewerfoldertype.h" #include "llvoavatarself.h" +class LLInventoryFavoritesItemsPanel; class LLInventoryRecentItemsPanel; class LLAssetFilteredInventoryPanel; static LLDefaultChildRegistry::Register<LLInventoryPanel> r("inventory_panel"); static LLDefaultChildRegistry::Register<LLInventoryRecentItemsPanel> t_recent_inventory_panel("recent_inventory_panel"); +static LLDefaultChildRegistry::Register<LLInventoryFavoritesItemsPanel> t_favorites_inventory_panel("favorites_inventory_panel"); static LLDefaultChildRegistry::Register<LLAssetFilteredInventoryPanel> t_asset_filtered_inv_panel("asset_filtered_inv_panel"); const std::string LLInventoryPanel::DEFAULT_SORT_ORDER = std::string("InventorySortOrder"); @@ -622,6 +624,18 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve } } + if (mask & LLInventoryObserver::UPDATE_FAVORITE) + { + if (view_item) + { + LLFolderViewFolder* parent = view_item->getParentFolder(); + if (parent) + { + parent->updateHasFavorites(view_item->isFavorite()); + } + } + } + // We don't typically care which of these masks the item is actually flagged with, since the masks // may not be accurate (e.g. in the main inventory panel, I move an item from My Inventory into // Landmarks; this is a STRUCTURE change for that panel but is an ADD change for the Landmarks @@ -650,6 +664,15 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve setSelection(item_id, false); } updateFolderLabel(model_item->getParentUUID()); + if (model_item->getIsFavorite()) + { + LLFolderViewFolder* new_parent = (LLFolderViewFolder*)getItemByID(model_item->getParentUUID()); + if (new_parent) + { + new_parent->updateHasFavorites(true); + } + } + } ////////////////////////////// @@ -700,6 +723,12 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve { old_parent_vmi->dirtyDescendantsFilter(); } + + if (view_item->isFavorite()) + { + old_parent->updateHasFavorites(false); // favorite was removed + new_parent->updateHasFavorites(true); // favorite was added + } } } } @@ -724,6 +753,10 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve { updateFolderLabel(viewmodel_folder->getUUID()); } + if (view_item->isFavorite()) + { + parent->updateHasFavorites(false); // favorite was removed + } } } } @@ -842,7 +875,23 @@ void LLInventoryPanel::idle(void* user_data) bool in_visible_chain = panel->isInVisibleChain(); - if (!panel->mBuildViewsQueue.empty()) + if (!panel->mBuildRootQueue.empty()) + { + const F64 max_time = in_visible_chain ? 0.006f : 0.001f; // 6 ms + F64 curent_time = LLTimer::getTotalSeconds(); + panel->mBuildViewsEndTime = curent_time + max_time; + + while (curent_time < panel->mBuildViewsEndTime + && !panel->mBuildRootQueue.empty()) + { + LLUUID item_id = panel->mBuildRootQueue.back(); + panel->mBuildRootQueue.pop_back(); + panel->findAndInitRootContent(item_id); + + curent_time = LLTimer::getTotalSeconds(); + } + } + else if (!panel->mBuildViewsQueue.empty()) { const F64 max_time = in_visible_chain ? 0.006f : 0.001f; // 6 ms F64 curent_time = LLTimer::getTotalSeconds(); @@ -937,7 +986,7 @@ void LLInventoryPanel::initializeViews(F64 max_time) buildNewViews(gInventory.getLibraryRootFolderID()); // Library } - if (mBuildViewsQueue.empty()) + if (mBuildViewsQueue.empty() && mBuildRootQueue.empty()) { mViewsInitialized = VIEWS_INITIALIZED; } @@ -2204,6 +2253,240 @@ LLInventoryRecentItemsPanel::LLInventoryRecentItemsPanel( const Params& params) mInvFVBridgeBuilder = &RECENT_ITEMS_BUILDER; } +/************************************************************************/ +/* Favorites Inventory Panel related class */ +/************************************************************************/ +static const LLFavoritesInventoryBridgeBuilder FAVORITES_BUILDER; +class LLInventoryFavoritesItemsPanel : public LLInventoryPanel +{ +public: + struct Params : public LLInitParam::Block<Params, LLInventoryPanel::Params> + {}; + + void initFromParams(const Params& p) + { + LLInventoryPanel::initFromParams(p); + // turn off trash + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() | (1ULL << LLFolderType::FT_TRASH)); + getFilter().setFilterNoTrashFolder(); + // turn off marketplace for favorites + getFilter().setFilterNoMarketplaceFolder(); + } + + void removeItemID(const LLUUID& id) override; + +protected: + LLInventoryFavoritesItemsPanel(const Params&); + friend class LLUICtrlFactory; + + void findAndInitRootContent(const LLUUID& folder_id) override; + + bool removeFavorite(const LLUUID& id, const LLInventoryObject* model_item); + void itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item) override; + + std::set<LLUUID> mRootContentIDs; +}; + +LLInventoryFavoritesItemsPanel::LLInventoryFavoritesItemsPanel(const Params& params) + : LLInventoryPanel(params) +{ + // replace bridge builder to have necessary View bridges. + mInvFVBridgeBuilder = &FAVORITES_BUILDER; +} + +void LLInventoryFavoritesItemsPanel::removeItemID(const LLUUID& id) +{ + std::set<LLUUID>::iterator found = mRootContentIDs.find(id); + if (found != mRootContentIDs.end()) + { + mRootContentIDs.erase(found); + // check content for favorites + mBuildRootQueue.emplace_back(id); + } + + LLInventoryPanel::removeItemID(id); +} + +void LLInventoryFavoritesItemsPanel::findAndInitRootContent(const LLUUID& id) +{ + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildRootQueue.emplace_back(id); + return; + } + LLViewerInventoryCategory::cat_array_t* categories; + LLViewerInventoryItem::item_array_t* items; + mInventory->lockDirectDescendentArrays(id, categories, items); + + if (categories) + { + size_t count = categories->size(); + for (size_t i = 0; i < count; ++i) + { + LLViewerInventoryCategory* cat = categories->at(i); + if (cat->getPreferredType() == LLFolderType::FT_TRASH) + { + continue; + } + else if (cat->getIsFavorite()) + { + LLFolderViewItem* folder_view_item = getItemByID(cat->getUUID()); + if (!folder_view_item) + { + const LLUUID& parent_id = cat->getParentUUID(); + mRootContentIDs.emplace(cat->getUUID()); + + buildViewsTree(cat->getUUID(), parent_id, cat, folder_view_item, mFolderRoot.get(), BUILD_TIMELIMIT); + } + } + else + { + findAndInitRootContent(cat->getUUID()); + } + } + } + + if (items) + { + size_t count = items->size(); + for (size_t i = 0; i < count; ++i) + { + LLViewerInventoryItem* item = items->at(i); + const LLUUID item_id = item->getUUID(); + if (item->getIsFavorite() && typedViewsFilter(item_id, item)) + { + LLFolderViewItem* folder_view_item = getItemByID(id); + if (!folder_view_item) + { + const LLUUID& parent_id = item->getParentUUID(); + mRootContentIDs.emplace(item_id); + + buildViewsTree(item_id, parent_id, item, folder_view_item, mFolderRoot.get(), BUILD_TIMELIMIT); + } + } + } + } +} + +bool LLInventoryFavoritesItemsPanel::removeFavorite(const LLUUID& id, const LLInventoryObject* model_item) +{ + std::set<LLUUID>::iterator found = mRootContentIDs.find(id); + if (found == mRootContentIDs.end()) + { + return false; + } + + mRootContentIDs.erase(found); + + // This item is in root's content, remove item's UI. + LLFolderViewItem* view_item = getItemByID(id); + if (view_item) + { + LLFolderViewFolder* parent = view_item->getParentFolder(); + LLFolderViewModelItemInventory* viewmodel_item = static_cast<LLFolderViewModelItemInventory*>(view_item->getViewModelItem()); + if (viewmodel_item) + { + removeItemID(viewmodel_item->getUUID()); + } + view_item->destroyView(); + if (parent) + { + parent->getViewModelItem()->dirtyDescendantsFilter(); + LLFolderViewModelItemInventory* viewmodel_folder = static_cast<LLFolderViewModelItemInventory*>(parent->getViewModelItem()); + if (viewmodel_folder) + { + updateFolderLabel(viewmodel_folder->getUUID()); + } + if (view_item->isFavorite()) + { + parent->updateHasFavorites(false); // favorite was removed + } + } + } + + return true; +} + +void LLInventoryFavoritesItemsPanel::itemChanged(const LLUUID& id, U32 mask, const LLInventoryObject* model_item) +{ + if (!model_item && !getItemByID(id)) + { + // remove operation, but item is not in panel already + return; + } + + bool handled = false; + + if (mask & (LLInventoryObserver::UPDATE_FAVORITE | + LLInventoryObserver::STRUCTURE | + LLInventoryObserver::ADD | + LLInventoryObserver::REMOVE)) + { + if (model_item && model_item->getIsFavorite()) + { + LLFolderViewItem* view_item = getItemByID(id); + if (!view_item) + { + const LLViewerInventoryCategory* cat = dynamic_cast<const LLViewerInventoryCategory*>(model_item); + if (cat) + { + // New favorite folder + if (cat->getPreferredType() != LLFolderType::FT_TRASH) + { + // If any descendants were in the list, remove them + LLFavoritesCollector is_favorite; + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendentsIf(id, cat_array, item_array, FALSE, is_favorite); + for (LLInventoryModel::cat_array_t::const_iterator it = cat_array.begin(); it != cat_array.end(); ++it) + { + removeFavorite((*it)->getUUID(), *it); + } + for (LLInventoryModel::item_array_t::const_iterator it = item_array.begin(); it != item_array.end(); ++it) + { + removeFavorite((*it)->getUUID(), *it); + } + + LLFolderViewItem* folder_view_item = getItemByID(cat->getUUID()); + if (!folder_view_item) + { + const LLUUID& parent_id = cat->getParentUUID(); + mRootContentIDs.emplace(cat->getUUID()); + + buildViewsTree(cat->getUUID(), parent_id, cat, folder_view_item, mFolderRoot.get(), BUILD_ONE_FOLDER); + } + } + } + else + { + // New favorite item + if (model_item->getIsFavorite() && typedViewsFilter(id, model_item)) + { + const LLUUID& parent_id = model_item->getParentUUID(); + mRootContentIDs.emplace(id); + + buildViewsTree(id, parent_id, model_item, NULL, mFolderRoot.get(), BUILD_ONE_FOLDER); + } + } + handled = true; + } + } + else + { + handled = removeFavorite(id, model_item); + } + } + + if (!handled) + { + LLInventoryPanel::itemChanged(id, mask, model_item); + } +} +/************************************************************************/ +/* LLInventorySingleFolderPanel */ +/************************************************************************/ + static LLDefaultChildRegistry::Register<LLInventorySingleFolderPanel> t_single_folder_inventory_panel("single_folder_inventory_panel"); LLInventorySingleFolderPanel::LLInventorySingleFolderPanel(const Params& params) diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index 56909c8d98..8de17f8b91 100644 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -251,7 +251,7 @@ public: bool reset_filter = false); static void setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id); void addItemID(const LLUUID& id, LLFolderViewItem* itemp); - void removeItemID(const LLUUID& id); + virtual void removeItemID(const LLUUID& id); LLFolderViewItem* getItemByID(const LLUUID& id); LLFolderViewFolder* getFolderByID(const LLUUID& id); void setSelectionByID(const LLUUID& obj_id, bool take_keyboard_focus); @@ -334,6 +334,7 @@ public: protected: // Builds the UI. Call this once the inventory is usable. void initializeViews(F64 max_time); + virtual void findAndInitRootContent(const LLUUID& root_id) {}; // Specific inventory colors static bool sColorSetInitialized; @@ -371,7 +372,7 @@ protected: virtual LLFolderViewItem* createFolderViewItem(LLInvFVBridge * bridge); boost::function<void(const std::deque<LLFolderViewItem*>& items, bool user_action)> mSelectionCallback; -private: +protected: // buildViewsTree does not include some checks and is meant // for recursive use, use buildNewViews() for first call LLFolderViewItem* buildViewsTree(const LLUUID& id, @@ -394,6 +395,7 @@ private: EViewsInitializationState mViewsInitialized; // Whether views have been generated F64 mBuildViewsEndTime; // Stop building views past this timestamp std::deque<LLUUID> mBuildViewsQueue; + std::deque<LLUUID> mBuildRootQueue; }; diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index dba294cef4..d6a4333e11 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -620,7 +620,7 @@ void LLOutfitGallery::removeFromLastRow(LLOutfitGalleryItem* item) mItemPanels.pop_back(); } -LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID outfit_id) +LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID outfit_id, bool is_favorite) { LLOutfitGalleryItem::Params giparams; LLOutfitGalleryItem* gitem = LLUICtrlFactory::create<LLOutfitGalleryItem>(giparams); @@ -629,6 +629,7 @@ LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID gitem->setFollowsLeft(); gitem->setFollowsTop(); gitem->setOutfitName(name); + gitem->setOutfitFavorite(is_favorite); gitem->setUUID(outfit_id); gitem->setGallery(this); return gitem; @@ -786,8 +787,7 @@ void LLOutfitGallery::updateAddedCategory(LLUUID cat_id) LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); if (!cat) return; - std::string name = cat->getName(); - LLOutfitGalleryItem* item = buildGalleryItem(name, cat_id); + LLOutfitGalleryItem* item = buildGalleryItem(cat->getName(), cat_id, cat->getIsFavorite()); mOutfitMap.insert(LLOutfitGallery::outfit_map_value_t(cat_id, item)); item->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this, _1, _2, _3, cat_id)); @@ -856,6 +856,7 @@ void LLOutfitGallery::updateChangedCategoryName(LLViewerInventoryCategory *cat, if (item) { item->setOutfitName(name); + item->setOutfitFavorite(cat->getIsFavorite()); } } } @@ -936,6 +937,10 @@ LLOutfitListGearMenuBase* LLOutfitGallery::createGearMenu() static LLDefaultChildRegistry::Register<LLOutfitGalleryItem> r("outfit_gallery_item"); +bool LLOutfitGalleryItem::sColorSetInitialized = false; +LLUIColor LLOutfitGalleryItem::sDefaultTextColor; +LLUIColor LLOutfitGalleryItem::sDefaultFavoriteColor; + LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) : LLPanel(p), mGallery(nullptr), @@ -947,6 +952,12 @@ LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) mUUID(LLUUID()) { buildFromFile("panel_outfit_gallery_item.xml"); + if (!sColorSetInitialized) + { + sDefaultTextColor = LLUIColorTable::instance().getColor("White", LLColor4::white); + sDefaultFavoriteColor = LLUIColorTable::instance().getColor("InventoryFavoriteColor", LLColor4::white); + sColorSetInitialized = true; + } } LLOutfitGalleryItem::~LLOutfitGalleryItem() @@ -1003,6 +1014,17 @@ void LLOutfitGalleryItem::draw() } } + if(mFavorite) + { + const S32 HPAD = 3; + const S32 VPAD = 6; // includes padding for text and for the image + const S32 image_size = 14; + static LLPointer<LLUIImage> fav_img = LLRender2D::getInstance()->getUIImage("Inv_Favorite_Star_Full"); + + gl_draw_scaled_image( + border.getWidth() - image_size - HPAD, image_size + VPAD + mOutfitNameText->getRect().getHeight(), + image_size, image_size, fav_img->getImage(), UI_VERTEX_COLOR % alpha); + } } void LLOutfitGalleryItem::setOutfitName(std::string name) @@ -1012,14 +1034,19 @@ void LLOutfitGalleryItem::setOutfitName(std::string name) mOutfitName = name; } +void LLOutfitGalleryItem::setOutfitFavorite(bool is_favorite) +{ + mFavorite = is_favorite; + mOutfitNameText->setReadOnlyColor(mFavorite ? sDefaultFavoriteColor.get() : sDefaultTextColor.get()); +} + void LLOutfitGalleryItem::setOutfitWorn(bool value) { mWorn = value; LLStringUtil::format_map_t worn_string_args; std::string worn_string = getString("worn_string", worn_string_args); - LLUIColor text_color = LLUIColorTable::instance().getColor("White", LLColor4::white); - mOutfitWornText->setReadOnlyColor(text_color); - mOutfitNameText->setReadOnlyColor(text_color); + mOutfitWornText->setReadOnlyColor(sDefaultTextColor.get()); + mOutfitNameText->setReadOnlyColor(mFavorite ? sDefaultFavoriteColor.get() : sDefaultTextColor.get()); mOutfitWornText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); mOutfitNameText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); mOutfitWornText->setValue(value ? worn_string : ""); @@ -1172,6 +1199,7 @@ LLContextMenu* LLOutfitGalleryContextMenu::createMenu() registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, this, selected_id)); + registrar.add("Outfit.Favorite", boost::bind(&LLOutfitGalleryContextMenu::onFavorite, this, selected_id)); registrar.add("Outfit.Save", boost::bind(&LLOutfitGalleryContextMenu::onSave, this, selected_id)); enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitGalleryContextMenu::onEnable, this, _2)); enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitGalleryContextMenu::onVisible, this, _2)); @@ -1213,6 +1241,10 @@ void LLOutfitGalleryGearMenu::onUpdateItemsVisibility() mMenu->setItemVisible("expand", false); mMenu->setItemVisible("collapse", false); mMenu->setItemVisible("thumbnail", have_selection); + mMenu->setItemVisible("inventory_settings", false); + mMenu->setItemVisible("inv_settings_separator", false); + mMenu->setItemVisible("sort_order_separator", true); + mMenu->setItemVisible("sort_order_by_image", true); mMenu->setItemVisible("sepatator3", true); mMenu->setItemVisible("sort_folders_by_name", true); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); @@ -1345,6 +1377,11 @@ void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) } } +LLToggleableMenu* LLOutfitGallery::getSortMenu() +{ + return nullptr; +} + LLUUID LLOutfitGallery::getPhotoAssetId(const LLUUID& outfit_id) { outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h index fa441ff209..c65eb4bd19 100644 --- a/indra/newview/lloutfitgallery.h +++ b/indra/newview/lloutfitgallery.h @@ -102,10 +102,12 @@ public: /*virtual*/ bool getHasExpandableFolders() { return false; } + /*virtual*/ void onChangeSortOrder(const LLSD& userdata) {}; void updateMessageVisibility(); bool hasDefaultImage(const LLUUID& outfit_cat_id); void refreshOutfit(const LLUUID& category_id); + virtual LLToggleableMenu* getSortMenu(); protected: /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); @@ -134,7 +136,7 @@ private: void updateRowsIfNeeded(); void updateGalleryWidth(); - LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id); + LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id, bool is_favorite); LLOutfitGalleryItem* getSelectedItem() const; LLOutfitGalleryItem* getItem(const LLUUID& id) const; @@ -243,6 +245,7 @@ public: bool setImageAssetId(LLUUID asset_id); LLUUID getImageAssetId(); void setOutfitName(std::string name); + void setOutfitFavorite(bool is_favorite); void setOutfitWorn(bool value); void setSelected(bool value); void setUUID(const LLUUID &outfit_id) {mUUID = outfit_id;} @@ -268,7 +271,12 @@ private: bool mDefaultImage; bool mImageUpdatePending; bool mHidden; + bool mFavorite; std::string mOutfitName; + + static bool sColorSetInitialized; + static LLUIColor sDefaultTextColor; + static LLUIColor sDefaultFavoriteColor; }; #endif // LL_LLOUTFITGALLERYCTRL_H diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 0233a22c2a..397fabd5d3 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -35,6 +35,7 @@ #include "llaccordionctrltab.h" #include "llagentwearables.h" #include "llappearancemgr.h" +#include "llappviewer.h" #include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llinspecttexture.h" @@ -45,6 +46,7 @@ #include "lloutfitobserver.h" #include "lltoggleablemenu.h" #include "lltransutil.h" +#include "llviewercontrol.h" #include "llviewermenu.h" #include "llvoavatar.h" #include "llvoavatarself.h" @@ -53,14 +55,24 @@ static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y); static const LLOutfitTabNameComparator OUTFIT_TAB_NAME_COMPARATOR; +static const LLOutfitTabFavComparator OUTFIT_TAB_FAV_COMPARATOR; /*virtual*/ bool LLOutfitTabNameComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const { - std::string name1 = tab1->getTitle(); - std::string name2 = tab2->getTitle(); + return (LLStringUtil::compareDict(tab1->getTitle(), tab2->getTitle()) < 0); +} + +bool LLOutfitTabFavComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const +{ + LLOutfitAccordionCtrlTab* taba = (LLOutfitAccordionCtrlTab*)tab1; + LLOutfitAccordionCtrlTab* tabb = (LLOutfitAccordionCtrlTab*)tab2; + if (taba->getFavorite() != tabb->getFavorite()) + { + return taba->getFavorite(); + } - return (LLStringUtil::compareDict(name1, name2) < 0); + return (LLStringUtil::compareDict(tab1->getTitle(), tab2->getTitle()) < 0); } struct outfit_accordion_tab_params : public LLInitParam::Block<outfit_accordion_tab_params, LLOutfitAccordionCtrlTab::Params> @@ -80,6 +92,9 @@ const outfit_accordion_tab_params& get_accordion_tab_params() { initialized = true; + LLOutfitAccordionCtrlTab::sFavoriteIcon = LLUI::getUIImage("Inv_Favorite_Star_Full"); + LLOutfitAccordionCtrlTab::sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255)); + LLXMLNodePtr xmlNode; if (LLUICtrlFactory::getLayeredXMLNode("outfit_accordion_tab.xml", xmlNode)) { @@ -103,11 +118,20 @@ LLOutfitsList::LLOutfitsList() , mAccordion(NULL) , mListCommands(NULL) , mItemSelected(false) + , mSortMenu(nullptr) { + LLControlVariable* ctrl = gSavedSettings.getControl("InventoryFavoritesColorText"); + if (ctrl) + { + mSavedSettingInvFavColor = ctrl->getSignal()->connect(boost::bind(&LLOutfitsList::handleInvFavColorChange, this)); + } } LLOutfitsList::~LLOutfitsList() { + delete mSortMenu; + mSavedSettingInvFavColor.disconnect(); + mGearMenuConnection.disconnect(); } bool LLOutfitsList::postBuild() @@ -115,9 +139,25 @@ bool LLOutfitsList::postBuild() mAccordion = getChild<LLAccordionCtrl>("outfits_accordion"); mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR); + initComparator(); + return LLOutfitListBase::postBuild(); } +void LLOutfitsList::initComparator() +{ + S32 mode = gSavedSettings.getS32("OutfitListSortOrder"); + if (mode == 0) + { + mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR); + } + else + { + mAccordion->setComparator(&OUTFIT_TAB_FAV_COMPARATOR); + } + sortOutfits(); +} + //virtual void LLOutfitsList::onOpen(const LLSD& info) { @@ -154,6 +194,7 @@ void LLOutfitsList::updateAddedCategory(LLUUID cat_id) tab->setName(name); tab->setTitle(name); + tab->setFavorite(cat->getIsFavorite()); // *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated. tab->setDisplayChildren(false); @@ -183,8 +224,9 @@ void LLOutfitsList::updateAddedCategory(LLUUID cat_id) // Setting callback to reset items selection inside outfit on accordion collapsing and expanding (EXT-7875) tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::resetItemSelection, this, list, cat_id)); - // force showing list items that don't match current filter(EXT-7158) - list->setForceShowingUnmatchedItems(true); + // Depending on settings, force showing list items that don't match current filter(EXT-7158) + LLCachedControl<bool> list_filter(gSavedSettings, "OutfitListFilterFullList"); + list->setForceShowingUnmatchedItems(list_filter(), false); // Setting list commit callback to monitor currently selected wearable item. list->setCommitCallback(boost::bind(&LLOutfitsList::onListSelectionChange, this, _1)); @@ -249,13 +291,11 @@ void LLOutfitsList::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) { if (mOutfitsMap[prev_id]) { - mOutfitsMap[prev_id]->setTitleFontStyle("NORMAL"); - mOutfitsMap[prev_id]->setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor")); + ((LLOutfitAccordionCtrlTab*)mOutfitsMap[prev_id])->setOutfitSelected(false); } if (mOutfitsMap[base_id]) { - mOutfitsMap[base_id]->setTitleFontStyle("BOLD"); - mOutfitsMap[base_id]->setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor")); + ((LLOutfitAccordionCtrlTab*)mOutfitsMap[base_id])->setOutfitSelected(true); } } @@ -313,6 +353,11 @@ void LLOutfitsList::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) } } +void LLOutfitListBase::onAction(const LLSD& userdata) +{ + performAction(userdata.asString()); +} + // virtual bool LLOutfitListBase::isActionEnabled(const LLSD& userdata) { @@ -425,11 +470,12 @@ void LLOutfitsList::updateChangedCategoryName(LLViewerInventoryCategory *cat, st if (outfits_iter != mOutfitsMap.end()) { // Update tab name with the new category name. - LLAccordionCtrlTab* tab = outfits_iter->second; + LLOutfitAccordionCtrlTab* tab = (LLOutfitAccordionCtrlTab*) outfits_iter->second; if (tab) { tab->setName(name); tab->setTitle(name); + tab->setFavorite(cat->getIsFavorite()); } } } @@ -738,6 +784,76 @@ void LLOutfitsList::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUI } } + +void LLOutfitsList::handleInvFavColorChange() +{ + for (outfits_map_t::iterator iter = mOutfitsMap.begin(); + iter != mOutfitsMap.end(); + ++iter) + { + if (!iter->second) continue; + LLOutfitAccordionCtrlTab* tab = (LLOutfitAccordionCtrlTab*)iter->second; + + // refresh font color + tab->setFavorite(tab->getFavorite()); + } +} + +void LLOutfitsList::onChangeSortOrder(const LLSD& userdata) +{ + std::string sort_data = userdata.asString(); + if (sort_data == "favorites_to_top") + { + // at the moment this is a toggle + S32 val = gSavedSettings.getS32("OutfitListSortOrder"); + gSavedSettings.setS32("OutfitListSortOrder", (val ? 0 : 1)); + + initComparator(); + } + else if (sort_data == "show_entire_outfit") + { + bool new_val = !gSavedSettings.getBOOL("OutfitListFilterFullList"); + gSavedSettings.setBOOL("OutfitListFilterFullList", new_val); + + if (!getFilterSubString().empty()) + { + for (outfits_map_t::value_type& outfit : mOutfitsMap) + { + LLAccordionCtrlTab* tab = outfit.second; + const LLUUID& category_id = outfit.first; + if (!tab) continue; + + LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(tab->getAccordionView()); + if (list) + { + list->setForceRefresh(true); + list->setForceShowingUnmatchedItems(new_val, tab->getDisplayChildren()); + } + applyFilterToTab(category_id, tab, getFilterSubString()); + } + mAccordion->arrange(); + } + } +} + +LLToggleableMenu* LLOutfitsList::getSortMenu() +{ + if (!mSortMenu) + { + mSortMenu = new LLOutfitListSortMenu(this); + } + return mSortMenu->getMenu(); +} + +void LLOutfitsList::updateMenuItemsVisibility() +{ + if (mSortMenu) + { + mSortMenu->updateItemsVisibility(); + } + LLOutfitListBase::updateMenuItemsVisibility(); +} + LLOutfitListGearMenuBase* LLOutfitsList::createGearMenu() { return new LLOutfitListGearMenu(this); @@ -755,10 +871,10 @@ bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y) LLOutfitListBase::LLOutfitListBase() : LLPanelAppearanceTab() , mIsInitialized(false) + , mGearMenu(nullptr) { mCategoriesObserver = new LLInventoryCategoriesObserver(); mOutfitMenu = new LLOutfitContextMenu(this); - //mGearMenu = createGearMenu(); } LLOutfitListBase::~LLOutfitListBase() @@ -821,6 +937,10 @@ void LLOutfitListBase::observerCallback(const LLUUID& category_id) void LLOutfitListBase::refreshList(const LLUUID& category_id) { + if (LLAppViewer::instance()->quitRequested()) + { + return; + } bool wasNull = mRefreshListState.CategoryUUID.isNull(); mRefreshListState.CategoryUUID.setNull(); @@ -879,8 +999,18 @@ void LLOutfitListBase::onIdle(void* userdata) void LLOutfitListBase::onIdleRefreshList() { + if (LLAppViewer::instance()->quitRequested()) + { + mRefreshListState.CategoryUUID.setNull(); + gIdleCallbacks.deleteFunction(onIdle, this); + return; + } if (mRefreshListState.CategoryUUID.isNull()) + { + LL_WARNS() << "Called onIdleRefreshList without id" << LL_ENDL; + gIdleCallbacks.deleteFunction(onIdle, this); return; + } const F64 MAX_TIME = 0.05f; F64 curent_time = LLTimer::getTotalSeconds(); @@ -1041,6 +1171,20 @@ void LLOutfitListBase::expandAllFolders() onExpandAllFolders(); } +void LLOutfitListBase::updateMenuItemsVisibility() +{ + mGearMenu->updateItemsVisibility(); +} + +LLToggleableMenu* LLOutfitListBase::getGearMenu() +{ + if (!mGearMenu) + { + mGearMenu = createGearMenu(); + } + return mGearMenu->getMenu(); +}; + void LLOutfitListBase::deselectOutfit(const LLUUID& category_id) { // Reset selection if the outfit is selected. @@ -1067,6 +1211,7 @@ LLContextMenu* LLOutfitContextMenu::createMenu() registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitContextMenu::onThumbnail, this, selected_id)); + registrar.add("Outfit.Favorite", boost::bind(&LLOutfitContextMenu::onFavorite, this, selected_id)); registrar.add("Outfit.Save", boost::bind(&LLOutfitContextMenu::onSave, this, selected_id)); enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitContextMenu::onEnable, this, _2)); @@ -1117,6 +1262,16 @@ bool LLOutfitContextMenu::onVisible(LLSD::String param) { return LLAppearanceMgr::instance().getCanRemoveOutfit(outfit_cat_id); } + else if ("favorites_add" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_cat_id); + return cat && !cat->getIsFavorite(); + } + else if ("favorites_remove" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_cat_id); + return cat && cat->getIsFavorite(); + } return true; } @@ -1141,6 +1296,14 @@ void LLOutfitContextMenu::onThumbnail(const LLUUID &outfit_cat_id) } } +void LLOutfitContextMenu::onFavorite(const LLUUID& outfit_cat_id) +{ + if (outfit_cat_id.notNull()) + { + toggle_favorite(outfit_cat_id); + } +} + void LLOutfitContextMenu::onSave(const LLUUID &outfit_cat_id) { if (outfit_cat_id.notNull()) @@ -1171,14 +1334,13 @@ LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist) registrar.add("Gear.Rename", boost::bind(&LLOutfitListGearMenuBase::onRename, this)); registrar.add("Gear.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); registrar.add("Gear.Create", boost::bind(&LLOutfitListGearMenuBase::onCreate, this, _2)); - registrar.add("Gear.Collapse", boost::bind(&LLOutfitListBase::onCollapseAllFolders, mOutfitList)); - registrar.add("Gear.Expand", boost::bind(&LLOutfitListBase::onExpandAllFolders, mOutfitList)); registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this)); registrar.add("Gear.Save", boost::bind(&LLOutfitListGearMenuBase::onSave, this)); registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this)); - registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); + registrar.add("Gear.Favorite", boost::bind(&LLOutfitListGearMenuBase::onFavorite, this)); + registrar.add("Gear.SortByImage", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2)); enable_registrar.add("Gear.OnVisible", boost::bind(&LLOutfitListGearMenuBase::onVisible, this, _2)); @@ -1301,6 +1463,10 @@ bool LLOutfitListGearMenuBase::onEnable(LLSD::String param) { return LLAppearanceMgr::instance().getCanReplaceCOF(mOutfitList->getSelectedOutfitUUID()); } + if ("sort_by_image" == param) + { + return !gSavedSettings.getBOOL("OutfitGallerySortByName"); + } return mOutfitList->isActionEnabled(param); } @@ -1312,6 +1478,16 @@ bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) { return false; } + else if ("favorites_add" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id); + return cat && !cat->getIsFavorite(); + } + else if ("favorites_remove" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id); + return cat && cat->getIsFavorite(); + } return true; } @@ -1323,6 +1499,12 @@ void LLOutfitListGearMenuBase::onThumbnail() LLFloaterReg::showInstance("change_item_thumbnail", data); } +void LLOutfitListGearMenuBase::onFavorite() +{ + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + toggle_favorite(selected_outfit_id); +} + void LLOutfitListGearMenuBase::onChangeSortOrder() { @@ -1341,11 +1523,82 @@ void LLOutfitListGearMenu::onUpdateItemsVisibility() mMenu->setItemVisible("expand", true); mMenu->setItemVisible("collapse", true); mMenu->setItemVisible("thumbnail", getSelectedOutfitID().notNull()); + mMenu->setItemVisible("favorite", getSelectedOutfitID().notNull()); + mMenu->setItemVisible("inventory_settings", true); + mMenu->setItemVisible("inv_settings_separator", true); + mMenu->setItemVisible("sort_order_separator", false); + mMenu->setItemVisible("sort_order_by_image", false); mMenu->setItemVisible("sepatator3", false); mMenu->setItemVisible("sort_folders_by_name", false); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); } +//////////////////// LLOutfitListSortMenu //////////////////// + +LLOutfitListSortMenu::LLOutfitListSortMenu(LLOutfitListBase* parent_panel) + : mPanelHandle(parent_panel->getHandle()) +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("Sort.Collapse", { boost::bind(&LLOutfitListBase::onCollapseAllFolders, parent_panel) }); + registrar.add("Sort.Expand", { boost::bind(&LLOutfitListBase::onExpandAllFolders, parent_panel) }); + registrar.add("Sort.OnSort", { boost::bind(&LLOutfitListBase::onChangeSortOrder, parent_panel, _2) }); + enable_registrar.add("Sort.OnEnable", boost::bind(&LLOutfitListSortMenu::onEnable, this, _2)); + + mMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>( + "menu_outfit_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + llassert(mMenu); +} + + +LLToggleableMenu* LLOutfitListSortMenu::getMenu() +{ + return mMenu; +} + +void LLOutfitListSortMenu::updateItemsVisibility() +{ + onUpdateItemsVisibility(); +} + +void LLOutfitListSortMenu::onUpdateItemsVisibility() +{ + if (!mMenu) return; + mMenu->setItemVisible("expand", true); + mMenu->setItemVisible("collapse", true); + mMenu->setItemVisible("sort_favorites_to_top", true); + mMenu->setItemVisible("show_entire_outfit_in_search", true); +} + +bool LLOutfitListSortMenu::onEnable(LLSD::String param) +{ + if ("favorites_to_top" == param) + { + LLCachedControl<S32> sort_order(gSavedSettings, "OutfitListSortOrder", 0); + return sort_order == 1; + } + else if ("show_entire_outfit" == param) + { + LLCachedControl<bool> filter_mode(gSavedSettings, "OutfitListFilterFullList", 0); + return !filter_mode; + } + + return true; +} + + +//////////////////// LLOutfitAccordionCtrlTab //////////////////// + +LLUIImage* LLOutfitAccordionCtrlTab::sFavoriteIcon; +LLUIColor LLOutfitAccordionCtrlTab::sFgColor; + +void LLOutfitAccordionCtrlTab::draw() +{ + LLAccordionCtrlTab::draw(); + drawFavoriteIcon(); +} + bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) { if (y >= getLocalRect().getHeight() - getHeaderHeight()) @@ -1366,4 +1619,61 @@ bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) return LLAccordionCtrlTab::handleToolTip(x, y, mask); } + +void LLOutfitAccordionCtrlTab::setFavorite(bool is_favorite) +{ + mIsFavorite = is_favorite; + static LLUICachedControl<bool> highlight_color("InventoryFavoritesColorText", true); + if (!mIsSelected && mIsFavorite && highlight_color()) + { + setTitleColor(LLUIColorTable::instance().getColor("InventoryFavoriteColor")); + } + else + { + setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor")); + } +} + +void LLOutfitAccordionCtrlTab::setOutfitSelected(bool val) +{ + mIsSelected = val; + if (val) + { + setTitleFontStyle("BOLD"); + setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor")); + } + else + { + setTitleFontStyle("NORMAL"); + static LLUICachedControl<bool> highlight_color("InventoryFavoritesColorText", true); + if (mIsFavorite && highlight_color()) + { + setTitleColor(LLUIColorTable::instance().getColor("InventoryFavoriteColor")); + } + else + { + setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor")); + } + } +} + +void LLOutfitAccordionCtrlTab::drawFavoriteIcon() +{ + if (!mIsFavorite) + { + return; + } + static LLUICachedControl<bool> draw_star("InventoryFavoritesUseStar", true); + if (!draw_star) + { + return; + } + + const S32 PAD = 2; + const S32 image_size = 18; + + gl_draw_scaled_image( + getRect().getWidth() - image_size - PAD, getRect().getHeight() - image_size - PAD, + image_size, image_size, sFavoriteIcon->getImage(), sFgColor); +} // EOF diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h index f581b419d9..8c926a85f6 100644 --- a/indra/newview/lloutfitslist.h +++ b/indra/newview/lloutfitslist.h @@ -41,6 +41,7 @@ class LLAccordionCtrlTab; class LLInventoryCategoriesObserver; class LLOutfitListGearMenuBase; +class LLOutfitListSortMenuBase; class LLWearableItemsList; class LLListContextMenu; @@ -61,6 +62,17 @@ public: /*virtual*/ bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const; }; +class LLOutfitTabFavComparator : public LLAccordionCtrl::LLTabComparator +{ + LOG_CLASS(LLOutfitTabFavComparator); + +public: + LLOutfitTabFavComparator() {}; + virtual ~LLOutfitTabFavComparator() {}; + + /*virtual*/ bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const; +}; + class LLOutfitListBase : public LLPanelAppearanceTab { public: @@ -92,6 +104,7 @@ public: boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); void outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); + void onAction(const LLSD& userdata); virtual bool isActionEnabled(const LLSD& userdata); virtual void performAction(std::string action); virtual bool hasItemSelected() = 0; @@ -109,6 +122,12 @@ public: virtual bool getHasExpandableFolders() = 0; + virtual void onChangeSortOrder(const LLSD& userdata) = 0; + + virtual void updateMenuItemsVisibility(); + virtual LLToggleableMenu* getGearMenu(); + virtual bool getTrashMenuVisible() { return true; }; + protected: void observerCallback(const LLUUID& category_id); virtual LLOutfitListGearMenuBase* createGearMenu() = 0; @@ -139,6 +158,7 @@ protected: selection_change_signal_t mSelectionChangeSignal; LLListContextMenu* mOutfitMenu; LLOutfitListGearMenuBase* mGearMenu; + boost::signals2::connection mGearMenuConnection; }; ////////////////////////////////////////////////////////////////////////// @@ -155,7 +175,6 @@ protected: /* virtual */ LLContextMenu* createMenu(); bool onEnable(LLSD::String param); - bool onVisible(LLSD::String param); static void editOutfit(); @@ -163,6 +182,7 @@ protected: static void renameOutfit(const LLUUID& outfit_cat_id); void onThumbnail(const LLUUID &outfit_cat_id); + void onFavorite(const LLUUID& outfit_cat_id); void onSave(const LLUUID &outfit_cat_id); private: @@ -182,6 +202,7 @@ public: protected: virtual void onUpdateItemsVisibility(); virtual void onThumbnail(); + virtual void onFavorite(); virtual void onChangeSortOrder(); const LLUUID& getSelectedOutfitID(); @@ -202,6 +223,23 @@ private: bool onVisible(LLSD::String param); }; +class LLOutfitListSortMenu +{ +public: + LLOutfitListSortMenu(LLOutfitListBase* parent_panel); + + LLToggleableMenu* getMenu(); + void updateItemsVisibility(); + +private: + void onUpdateItemsVisibility(); + bool onEnable(LLSD::String param); + + LLToggleableMenu* mMenu; + LLHandle<LLPanel> mPanelHandle; +}; + + class LLOutfitListGearMenu : public LLOutfitListGearMenuBase { public: @@ -221,8 +259,16 @@ public: Params() : cat_id("cat_id") {} }; + virtual void draw(); virtual bool handleToolTip(S32 x, S32 y, MASK mask); + void setFavorite(bool is_favorite); + bool getFavorite() const { return mIsFavorite; } + void setOutfitSelected(bool val); + + static LLUIImage* sFavoriteIcon; + static LLUIColor sFgColor; + protected: LLOutfitAccordionCtrlTab(const LLOutfitAccordionCtrlTab::Params &p) : LLAccordionCtrlTab(p), @@ -230,7 +276,11 @@ public: {} friend class LLUICtrlFactory; + void drawFavoriteIcon(); + LLUUID mFolderID; + bool mIsFavorite = false; + bool mIsSelected = false; }; /** * @class LLOutfitsList @@ -249,6 +299,7 @@ public: virtual ~LLOutfitsList(); /*virtual*/ bool postBuild(); + void initComparator(); /*virtual*/ void onOpen(const LLSD& info); @@ -287,6 +338,10 @@ public: /*virtual*/ bool getHasExpandableFolders() { return true; } + /*virtual*/ void onChangeSortOrder(const LLSD& userdata); + virtual LLToggleableMenu* getSortMenu(); + void updateMenuItemsVisibility(); + protected: LLOutfitListGearMenuBase* createGearMenu(); @@ -357,6 +412,8 @@ private: static void onOutfitRename(const LLSD& notification, const LLSD& response); + void handleInvFavColorChange(); + //LLInventoryCategoriesObserver* mCategoriesObserver; LLAccordionCtrl* mAccordion; @@ -374,13 +431,15 @@ private: // Used to monitor COF changes for updating items worn state. See EXT-8636. uuid_vec_t mCOFLinkedItems; - //LLOutfitListGearMenu* mGearMenu; + LLOutfitListSortMenu* mSortMenu; //bool mIsInitialized; /** * True if there is a selection inside currently selected outfit */ bool mItemSelected; + + boost::signals2::connection mSavedSettingInvFavColor; }; #endif //LL_LLOUTFITSLIST_H diff --git a/indra/newview/llpanelappearancetab.h b/indra/newview/llpanelappearancetab.h index e4d16582de..e088c3e6f0 100644 --- a/indra/newview/llpanelappearancetab.h +++ b/indra/newview/llpanelappearancetab.h @@ -29,6 +29,8 @@ #include "llpanel.h" +class LLToggleableMenu; + class LLPanelAppearanceTab : public LLPanel { public: @@ -47,6 +49,11 @@ public: const std::string& getFilterSubString() { return mFilterSubString; } + virtual void updateMenuItemsVisibility() = 0; + virtual LLToggleableMenu* getGearMenu() = 0; + virtual LLToggleableMenu* getSortMenu() = 0; + virtual bool getTrashMenuVisible() = 0; + protected: /** diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index 48f32a05ba..36a8069010 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -68,6 +68,7 @@ const std::string FILTERS_FILENAME("filters.xml"); const std::string ALL_ITEMS("All Items"); const std::string RECENT_ITEMS("Recent Items"); const std::string WORN_ITEMS("Worn Items"); +const std::string FAVORITES("Favorites"); static LLPanelInjector<LLPanelMainInventory> t_inventory("panel_main_inventory"); @@ -216,6 +217,17 @@ bool LLPanelMainInventory::postBuild() worn_filter.markDefault(); mWornItemsPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, mWornItemsPanel, _1, _2)); } + + LLInventoryPanel* favorites_panel = getChild<LLInventoryPanel>(FAVORITES); + if (favorites_panel) + { + favorites_panel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::DEFAULT_SORT_ORDER)); + LLInventoryFilter& favorites_filter = favorites_panel->getFilter(); + favorites_filter.setEmptyLookupMessage("InventoryNoMatchingFavorites"); + favorites_filter.markDefault(); + favorites_panel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, favorites_panel, _1, _2)); + } + mSearchTypeCombo = getChild<LLComboBox>("search_type"); if(mSearchTypeCombo) { diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index dcd7fdf30d..417308abe5 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -129,6 +129,7 @@ public: virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} virtual bool isItemRenameable() const; virtual bool renameItem(const std::string& new_name); + virtual bool isFavorite() const { return false; } virtual bool isItemMovable() const; virtual bool isItemRemovable(bool check_worn = true) const; virtual bool removeItem(); diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 60017db51d..73ade2d902 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -404,7 +404,9 @@ LLPanelOutfitEdit::LLPanelOutfitEdit() mWearableListManager(NULL), mPlusBtn(NULL), mWearablesGearMenuBtn(NULL), - mGearMenuBtn(NULL) + mGearMenuBtn(NULL), + mStatus(NULL), + mCurrentOutfitName(NULL) { mSavedFolderState = new LLSaveFolderState(); mSavedFolderState->setApply(false); diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 47c02793a3..e2e2cf1a61 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -33,6 +33,7 @@ #include "llfloatersidepanelcontainer.h" #include "llinventoryfunctions.h" #include "llinventorymodelbackgroundfetch.h" +#include "llmenubutton.h" #include "llnotificationsutil.h" #include "lloutfitgallery.h" #include "lloutfitobserver.h" @@ -53,12 +54,17 @@ static const std::string SAVE_BTN("save_btn"); static LLPanelInjector<LLPanelOutfitsInventory> t_inventory("panel_outfits_inventory"); -LLPanelOutfitsInventory::LLPanelOutfitsInventory() : - mMyOutfitsPanel(NULL), - mCurrentOutfitPanel(NULL), - mActivePanel(NULL), - mAppearanceTabs(NULL), - mInitialized(false) +LLPanelOutfitsInventory::LLPanelOutfitsInventory() + : mMyOutfitsPanel(nullptr) + , mCurrentOutfitPanel(nullptr) + , mActivePanel(nullptr) + , mAppearanceTabs(nullptr) + , mInitialized(false) + , mGearMenu(nullptr) + , mSortMenu(nullptr) + , mTrashBtn(nullptr) + , mSortMenuPanel(nullptr) + , mTrashMenuPanel(nullptr) { gAgentWearables.addLoadedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoaded, this)); gAgentWearables.addLoadingStartedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoading, this)); @@ -75,6 +81,9 @@ LLPanelOutfitsInventory::~LLPanelOutfitsInventory() { gSavedSettings.setS32("LastAppearanceTab", mAppearanceTabs->getCurrentPanelIndex()); } + mGearMenuConnection.disconnect(); + mSortMenuConnection.disconnect(); + mTrashMenuConnection.disconnect(); } // virtual @@ -258,6 +267,22 @@ void LLPanelOutfitsInventory::initListCommandsHandlers() mOutfitGalleryPanel->childSetAction("trash_btn", boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); } +void LLPanelOutfitsInventory::setMenuButtons(LLMenuButton* gear_menu, LLMenuButton* sort_menu, LLButton* trash_btn, LLPanel* sort_menu_panel, LLPanel* trash_menu_panel) +{ + mGearMenu = gear_menu; + mSortMenu = sort_menu; + mTrashBtn = trash_btn; + mSortMenuPanel = sort_menu_panel; + mTrashMenuPanel = trash_menu_panel; + + mGearMenuConnection.disconnect(); + mSortMenuConnection.disconnect(); + mTrashMenuConnection.disconnect(); + mGearMenuConnection = mGearMenu->setMouseDownCallback(boost::bind(&LLPanelOutfitsInventory::onGearMouseDown, this)); + mSortMenuConnection = mSortMenu->setMouseDownCallback(boost::bind(&LLPanelOutfitsInventory::onGearMouseDown, this)); + mTrashMenuConnection = mTrashBtn->setClickedCallback(boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); +} + void LLPanelOutfitsInventory::updateListCommands() { bool trash_enabled = isActionEnabled("delete"); @@ -284,6 +309,14 @@ void LLPanelOutfitsInventory::onTrashButtonClick() } } +void LLPanelOutfitsInventory::onGearMouseDown() +{ + if (mActivePanel) + { + mActivePanel->updateMenuItemsVisibility(); + } +} + bool LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) { return mActivePanel && mActivePanel->isActionEnabled(userdata); @@ -320,6 +353,28 @@ void LLPanelOutfitsInventory::onTabChange() mActivePanel->checkFilterSubString(); mActivePanel->onOpen(LLSD()); + if (mGearMenu) + { + mGearMenu->setMenu(mActivePanel->getGearMenu(), LLMenuButton::MP_BOTTOM_LEFT); + } + if (mSortMenu && mSortMenuPanel) + { + LLToggleableMenu* menu = mActivePanel->getSortMenu(); + if (menu) + { + mSortMenu->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); + mSortMenuPanel->setVisible(true); + } + else + { + mSortMenuPanel->setVisible(false); + } + } + if (mTrashMenuPanel) + { + mTrashMenuPanel->setVisible(mActivePanel->getTrashMenuVisible()); + } + updateVerbs(); } diff --git a/indra/newview/llpaneloutfitsinventory.h b/indra/newview/llpaneloutfitsinventory.h index e046681e95..59cfcc75ba 100644 --- a/indra/newview/llpaneloutfitsinventory.h +++ b/indra/newview/llpaneloutfitsinventory.h @@ -30,11 +30,13 @@ #include "llpanel.h" +class LLButton; class LLOutfitGallery; class LLOutfitsList; class LLOutfitListGearMenuBase; class LLPanelAppearanceTab; class LLPanelWearing; +class LLMenuButton; class LLMenuGL; class LLSidepanelAppearance; class LLTabContainer; @@ -63,6 +65,13 @@ public: bool isCOFPanelActive() const; + void setMenuButtons( + LLMenuButton* gear_menu, + LLMenuButton* sort_menu, + LLButton* trash_btn, + LLPanel* sort_menu_panel, + LLPanel* trash_menu_panel); + protected: void updateVerbs(); @@ -94,6 +103,7 @@ protected: void onWearButtonClick(); void showGearMenu(); void onTrashButtonClick(); + void onGearMouseDown(); bool isActionEnabled(const LLSD& userdata); void setWearablesLoading(bool val); void onWearablesLoaded(); @@ -106,6 +116,16 @@ private: ////////////////////////////////////////////////////////////////////////////////// bool mInitialized; + + // not owned items + LLMenuButton* mGearMenu; + LLMenuButton* mSortMenu; + LLButton* mTrashBtn; + LLPanel* mSortMenuPanel; + LLPanel* mTrashMenuPanel; + boost::signals2::connection mGearMenuConnection; + boost::signals2::connection mSortMenuConnection; + boost::signals2::connection mTrashMenuConnection; }; #endif //LL_LLPANELOUTFITSINVENTORY_H diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index 1056f73d58..5646c8b80c 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -113,6 +113,7 @@ protected: boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); registrar.add("Wearing.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); + registrar.add("Wearing.Favorite", boost::bind(toggle_linked_favorite, mUUIDs.front())); LLContextMenu* menu = createFromFile("menu_wearing_tab.xml"); updateMenuItemsVisibility(menu); @@ -125,6 +126,8 @@ protected: bool bp_selected = false; // true if body parts selected bool clothes_selected = false; bool attachments_selected = false; + bool can_favorite = false; + bool can_unfavorite = false; // See what types of wearables are selected. for (uuid_vec_t::const_iterator it = mUUIDs.begin(); it != mUUIDs.end(); ++it) @@ -137,6 +140,9 @@ protected: continue; } + LLUUID linked_id = item->getLinkedUUID(); + LLViewerInventoryItem* linked_item = gInventory.getItem(linked_id); + LLAssetType::EType type = item->getType(); if (type == LLAssetType::AT_CLOTHING) { @@ -150,6 +156,8 @@ protected: { attachments_selected = true; } + can_favorite |= !linked_item->getIsFavorite(); + can_unfavorite |= linked_item->getIsFavorite(); } // Enable/disable some menu items depending on the selection. @@ -162,10 +170,12 @@ protected: menu->setItemEnabled("touch_attach", 1 == mUUIDs.size() && enable_attachment_touch(mUUIDs.front())); menu->setItemVisible("edit_item", show_edit); menu->setItemEnabled("edit_item", 1 == mUUIDs.size() && get_is_item_editable(mUUIDs.front())); - menu->setItemVisible("take_off", allow_take_off); - menu->setItemVisible("detach", allow_detach); + menu->setItemVisible("take_off", allow_take_off); + menu->setItemVisible("detach", allow_detach); menu->setItemVisible("edit_outfit_separator", show_touch | show_edit | allow_take_off || allow_detach); - menu->setItemVisible("show_original", mUUIDs.size() == 1); + menu->setItemVisible("show_original", mUUIDs.size() == 1); + menu->setItemVisible("favorites_add", can_favorite); + menu->setItemVisible("favorites_remove", can_unfavorite); } }; @@ -232,6 +242,10 @@ LLPanelWearing::~LLPanelWearing() { mAttachmentsChangedConnection.disconnect(); } + if (mGearMenuConnection.connected()) + { + mGearMenuConnection.disconnect(); + } } bool LLPanelWearing::postBuild() @@ -560,6 +574,16 @@ void LLPanelWearing::onRemoveAttachment() } } +LLToggleableMenu* LLPanelWearing::getGearMenu() +{ + return mGearMenu->getMenu(); +} + +LLToggleableMenu* LLPanelWearing::getSortMenu() +{ + return NULL; +} + void LLPanelWearing::onRemoveItem() { if (mWearablesTab->isExpanded()) diff --git a/indra/newview/llpanelwearing.h b/indra/newview/llpanelwearing.h index ea0787d0ef..aa80a3fc21 100644 --- a/indra/newview/llpanelwearing.h +++ b/indra/newview/llpanelwearing.h @@ -84,6 +84,11 @@ public: void onEditAttachment(); void onRemoveAttachment(); + void updateMenuItemsVisibility() {}; + LLToggleableMenu* getGearMenu(); + LLToggleableMenu* getSortMenu(); + bool getTrashMenuVisible() { return false; } + private: void onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); void onTempAttachmentsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); @@ -93,6 +98,7 @@ private: LLWearableItemsList* mCOFItemsList; LLScrollListCtrl* mTempItemsList; LLWearingGearMenu* mGearMenu; + boost::signals2::connection mGearMenuConnection; LLListContextMenu* mContextMenu; LLListContextMenu* mAttachmentsMenu; diff --git a/indra/newview/llsidepanelappearance.cpp b/indra/newview/llsidepanelappearance.cpp index c618483fc4..1d1b31d2a6 100644 --- a/indra/newview/llsidepanelappearance.cpp +++ b/indra/newview/llsidepanelappearance.cpp @@ -40,6 +40,7 @@ #include "llfloaterworldmap.h" #include "llfolderviewmodel.h" #include "llloadingindicator.h" +#include "llmenubutton.h" #include "lloutfitobserver.h" #include "llpaneleditwearable.h" #include "llpaneloutfitsinventory.h" @@ -145,6 +146,14 @@ bool LLSidepanelAppearance::postBuild() setWearablesLoading(gAgentWearables.isCOFChangeInProgress()); + + LLMenuButton* menu_gear_btn = getChild<LLMenuButton>("options_gear_btn"); + LLMenuButton* menu_sort_btn = getChild<LLMenuButton>("sorting_menu_btn"); + LLButton* menu_trash_btn = getChild<LLButton>("trash_btn"); + LLPanel* menu_sort_btn_panel = getChild<LLPanel>("options_sort_btn_panel"); + LLPanel* menu_trash_btn_panel = getChild<LLPanel>("trash_btn_panel"); + mPanelOutfitsInventory->setMenuButtons(menu_gear_btn, menu_sort_btn, menu_trash_btn, menu_sort_btn_panel, menu_trash_btn_panel); + return true; } diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 4c9910fd0a..418071f6aa 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -1439,7 +1439,8 @@ void update_inventory_category( if(obj) { if (LLFolderType::lookupIsProtectedType(obj->getPreferredType()) - && (updates.size() != 1 || !updates.has("thumbnail"))) + && (updates.size() != 1 + || !(updates.has("thumbnail") || updates.has("favorite")))) { LLNotificationsUtil::add("CannotModifyProtectedCategories"); return; diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index 217d46d1d2..451befcd1c 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -933,6 +933,7 @@ LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() // Register handlers for attachments. registrar.add("Attachment.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); + registrar.add("Attachment.Favorite", boost::bind(toggle_linked_favorite, selected_id)); registrar.add("Attachment.Touch", boost::bind(handle_attachment_touch, selected_id)); registrar.add("Attachment.Profile", boost::bind(show_item_profile, selected_id)); registrar.add("Object.Attach", boost::bind(LLViewerAttachMenu::attachObjects, ids, _2)); @@ -964,6 +965,7 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu U32 n_links = 0; // number of links among the selected items U32 n_editable = 0; // number of editable items among the selected ones U32 n_touchable = 0; // number of touchable items among the selected ones + U32 n_favorites = 0; // number of favorite items among the selected ones bool can_be_worn = true; @@ -987,6 +989,11 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu const bool is_editable = get_is_item_editable(id); const bool is_touchable = enable_attachment_touch(id); const bool is_already_worn = gAgentWearables.selfHasWearable(wearable_type); + + LLUUID linked_id = item->getLinkedUUID(); + LLViewerInventoryItem* linked_item = gInventory.getItem(linked_id); + const bool is_favorite = linked_item->getIsFavorite(); + if (is_worn) { ++n_worn; @@ -1007,10 +1014,14 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu { ++n_already_worn; } + if (is_favorite) + { + ++n_favorites; + } if (can_be_worn) { - can_be_worn = get_can_item_be_worn(item->getLinkedUUID()); + can_be_worn = get_can_item_be_worn(linked_id); } } // for @@ -1032,6 +1043,8 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu setMenuItemEnabled(menu, "create_new", LLAppearanceMgr::instance().canAddWearables(ids)); setMenuItemVisible(menu, "show_original", !standalone); setMenuItemEnabled(menu, "show_original", n_items == 1 && n_links == n_items); + setMenuItemVisible(menu, "favorites_add", n_favorites < n_items); + setMenuItemVisible(menu, "favorites_remove", n_favorites > 0); setMenuItemVisible(menu, "take_off", mask == MASK_CLOTHING && n_worn == n_items); setMenuItemVisible(menu, "detach", mask == MASK_ATTACHMENT && n_worn == n_items); setMenuItemVisible(menu, "take_off_or_detach", mask == (MASK_ATTACHMENT|MASK_CLOTHING)); diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index 92f63a1820..60d9a5da23 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -454,6 +454,9 @@ name="InventoryBackgroundColor" reference="DkGray2" /> <color + name="InventoryFavoriteColor" + reference="Yellow" /> + <color name="InventoryFocusOutlineColor" reference="White_25" /> <color diff --git a/indra/newview/skins/default/textures/icons/Icon_Pointer.png b/indra/newview/skins/default/textures/icons/Icon_Pointer.png Binary files differnew file mode 100644 index 0000000000..021942a8aa --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Icon_Pointer.png diff --git a/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Content.png b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Content.png Binary files differnew file mode 100644 index 0000000000..b71b202234 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Content.png diff --git a/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Full.png b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Full.png Binary files differnew file mode 100644 index 0000000000..7d55fb5cfe --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Full.png diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 97468c3b2e..d650e7e791 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -306,6 +306,8 @@ with the same filename but different name <texture name="Inv_CallingCard" file_name="icons/Inv_CallingCard.png" preload="false" /> <texture name="Inv_Clothing" file_name="icons/Inv_Clothing.png" preload="false" /> <texture name="Inv_Eye" file_name="icons/Inv_Eye.png" preload="false" /> + <texture name="Inv_Favorite_Star_Content" file_name="icons/Inv_Favorite_Star_Content.png" preload="false" /> + <texture name="Inv_Favorite_Star_Full" file_name="icons/Inv_Favorite_Star_Full.png" preload="false" /> <texture name="Inv_FolderClosed" file_name="icons/Inv_FolderClosed.png" preload="false" /> <texture name="Inv_FolderOpen" file_name="icons/Inv_FolderOpen.png" preload="false" /> <texture name="Inv_Gesture" file_name="icons/Inv_Gesture.png" preload="false" /> @@ -907,4 +909,5 @@ with the same filename but different name <texture name="Single_Folder_Up" file_name="icons/single_folder_up.png" preload="true"/> <texture name="Icon_Color_Palette" file_name="icons/Icon_Color_Palette.png" preload="false"/> <texture name="Icon_Font_Size" file_name="icons/Icon_Font_Size.png" preload="false"/> + <texture name="Icon_Pointer" file_name="icons/Icon_Pointer.png" preload="false"/> </textures> diff --git a/indra/newview/skins/default/xui/en/floater_inventory_settings.xml b/indra/newview/skins/default/xui/en/floater_inventory_settings.xml index 156bba6c27..e027d7556f 100644 --- a/indra/newview/skins/default/xui/en/floater_inventory_settings.xml +++ b/indra/newview/skins/default/xui/en/floater_inventory_settings.xml @@ -4,8 +4,8 @@ can_minimize="true" can_resize="false" save_rect="true" - height="370" - width="370" + height="460" + width="460" name="inventory_settings" title="INVENTORY SETTINGS"> <icon @@ -16,7 +16,7 @@ left="18" mouse_opaque="true" name="multi_folder_icon" - top="25" + top="20" width="18" /> <text type="string" @@ -40,10 +40,10 @@ font="SansSerifMedium" left="60" width="300" - height="70" + height="66" name="multi_double_click_setting"> <radio_item - height="20" + height="18" label="Expands & collapses folder" label_text.text_color="White" follows="left|top" @@ -51,24 +51,24 @@ name="0" width="200"/> <radio_item - height="20" + height="18" follows="left|top" label="Opens a new window" label_text.text_color="White" layout="topleft" left_delta="0" name="1" - top_pad ="5" + top_pad ="3" width="200" /> <radio_item - height="20" + height="18" follows="left|top" label="Switches view" label_text.text_color="White" layout="topleft" left_delta="0" name="2" - top_pad ="5" + top_pad ="3" width="200" /> </radio_group> <icon @@ -79,7 +79,7 @@ left="18" mouse_opaque="true" name="single_folder_icon" - top_pad="30" + top_pad="19" width="18" /> <text type="string" @@ -103,10 +103,10 @@ font="SansSerifMedium" left="60" width="300" - height="45" + height="44" name="single_double_click_setting"> <radio_item - height="20" + height="18" label="Stays in current window" label_text.text_color="White" follows="left|top" @@ -114,27 +114,37 @@ name="false" width="200"/> <radio_item - height="20" + height="18" follows="left|top" label="Opens a new window" label_text.text_color="White" layout="topleft" left_delta="0" name="true" - top_pad ="5" + top_pad ="3" width="200" /> </radio_group> + <icon + follows="top|left" + height="15" + image_name="Icon_Pointer" + layout="topleft" + left="20" + mouse_opaque="true" + name="single_folder_icon" + top_pad="20" + width="15" /> <text type="string" length="1" follows="left|top|right" height="13" layout="topleft" - left="48" + left_pad="13" name="find_original_txt" font="SansSerifMedium" text_color="White" - top_pad="30" + top_delta="1" width="300"> Clicking on "Show in inventory" or "Find original" </text> @@ -146,10 +156,10 @@ font="SansSerifMedium" left="60" width="300" - height="45" + height="44" name="find_original_settings"> <radio_item - height="20" + height="18" label="Shows item in main inventory window" label_text.text_color="White" follows="left|top" @@ -157,23 +167,123 @@ name="false" width="200"/> <radio_item - height="20" + height="18" follows="left|top" label="Opens a new single-folder window" label_text.text_color="White" layout="topleft" left_delta="0" name="true" - top_pad ="5" + top_pad ="3" width="200" /> </radio_group> - <button - height="20" - label="OK" - layout="topleft" - left="140" - bottom="-20" - name="ok_btn" - label_color="White" - width="90" /> + <icon + follows="top|left" + height="18" + image_name="Inv_Favorite_Star_Full" + layout="topleft" + left="18" + mouse_opaque="true" + name="favorites_icon" + top_pad="19" + width="18" /> + <text + type="string" + length="1" + layout="topleft" + follows="left|top|right" + height="13" + left_pad="12" + top_delta="2" + name="favorites_txt" + font="SansSerifMedium" + text_color="White" + width="300"> + Favorites + </text> + <check_box + control_name="InventoryFavoritesUseStar" + layout="topleft" + follows="left|top" + top_pad="8" + font="SansSerifMedium" + left="60" + width="300" + height="18" + label="Star" + name="favorite_star" + label_text.text_color="White" + initial_value="false"/> + <check_box + control_name="InventoryFavoritesColorText" + follows="left|top" + top_pad="5" + layout="topleft" + font="SansSerifMedium" + left="60" + width="150" + height="18" + label="Colored text" + name="favorites_color" + label_text.text_color="White" + initial_value="false"/> + <color_swatch + can_apply_immediately="true" + follows="left|top" + layout="topleft" + height="24" + label_height="0" + left_pad="40" + name="favorites_swatch" + top_delta="-6" + width="44" > + <color_swatch.init_callback + function="ScriptPref.getUIColor" + parameter="InventoryFavoriteColor" /> + <color_swatch.commit_callback + function="ScriptPref.applyUIColor" + parameter="InventoryFavoriteColor" /> + </color_swatch> + <icon + follows="top|left" + height="18" + image_name="Inv_Object" + layout="topleft" + left="18" + mouse_opaque="true" + name="obj_icon" + top_pad="19" + width="18" /> + <text + type="string" + length="1" + follows="left|top|right" + height="13" + layout="topleft" + left_pad="12" + top_delta="2" + name="single_folder_txt" + font="SansSerifMedium" + text_color="White" + width="300"> + Pressing return on an avatar attachment + </text> + <combo_box + control_name="InventoryAddAttachmentBehavior" + layout="topleft" + follows="left|top" + top_pad="8" + height="24" + left="60" + name="attach_combo" + width="300"> + <combo_box.item + label="Adds attachment (recommended)" + name="0" + value="0"/> + <combo_box.item + label="Wear (removes attachment at that point)" + name="1" + value="1"/> + </combo_box> </floater> diff --git a/indra/newview/skins/default/xui/en/floater_preview_trash.xml b/indra/newview/skins/default/xui/en/floater_preview_trash.xml index f1c87c8c5a..ebb5cd9251 100644 --- a/indra/newview/skins/default/xui/en/floater_preview_trash.xml +++ b/indra/newview/skins/default/xui/en/floater_preview_trash.xml @@ -29,6 +29,8 @@ bevel_style="none" scroll.reserve_scroll_corner="false"> <folder folder_arrow_image="Folder_Arrow" + favorite_image="Inv_Favorite_Star_Full" + favorite_content_image="Inv_Favorite_Star_Content" folder_indentation="8" item_height="20" item_top_pad="4" diff --git a/indra/newview/skins/default/xui/en/menu_gallery_inventory.xml b/indra/newview/skins/default/xui/en/menu_gallery_inventory.xml index 16907d3577..d9919661f3 100644 --- a/indra/newview/skins/default/xui/en/menu_gallery_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_gallery_inventory.xml @@ -126,6 +126,22 @@ parameter="thumbnail" /> </menu_item_call> <menu_item_call + label="Add to favorites" + layout="topleft" + name="favorites_add"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="favorite" /> + </menu_item_call> + <menu_item_call + label="Remove from favorites" + layout="topleft" + name="favorites_remove"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="favorite" /> + </menu_item_call> + <menu_item_call label="Copy Asset UUID" layout="topleft" name="Copy Asset UUID"> @@ -670,6 +686,22 @@ function="Inventory.DoToSelected" parameter="ungroup_folder_items" /> </menu_item_call> + <menu_item_call + label="Add to Favorites" + layout="topleft" + name="Add to Favorites"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="add_to_favorites" /> + </menu_item_call> + <menu_item_call + label="Remove from Favorites" + layout="topleft" + name="Remove from Favorites"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="remove_from_favorites" /> + </menu_item_call> <menu label="Use as default for" layout="topleft" diff --git a/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml b/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml index 99cee83f4e..fb68193006 100755 --- a/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml +++ b/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml @@ -50,6 +50,26 @@ function="Outfit.Thumbnail" /> </menu_item_call> <menu_item_call + label="Add to favorites" + layout="topleft" + name="favorites_add"> + <on_visible + function="Outfit.OnVisible" + parameter="favorites_add" /> + <on_click + function="Outfit.Favorite" /> + </menu_item_call> + <menu_item_call + label="Remove from favorites" + layout="topleft" + name="favorites_remove"> + <on_visible + function="Outfit.OnVisible" + parameter="favorites_remove" /> + <on_click + function="Outfit.Favorite" /> + </menu_item_call> + <menu_item_call label="Edit outfit" layout="topleft" name="edit"> diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml index d8aac71dd5..ed2b8ea7bd 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -951,6 +951,22 @@ function="Inventory.DoToSelected" parameter="ungroup_folder_items" /> </menu_item_call> + <menu_item_call + label="Add to Favorites" + layout="topleft" + name="Add to Favorites"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="add_to_favorites" /> + </menu_item_call> + <menu_item_call + label="Remove from Favorites" + layout="topleft" + name="Remove from Favorites"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="remove_from_favorites" /> + </menu_item_call> <menu label="Use as default for" layout="topleft" diff --git a/indra/newview/skins/default/xui/en/menu_outfit_gear.xml b/indra/newview/skins/default/xui/en/menu_outfit_gear.xml index 8f36c7a00a..bd0c49309d 100644 --- a/indra/newview/skins/default/xui/en/menu_outfit_gear.xml +++ b/indra/newview/skins/default/xui/en/menu_outfit_gear.xml @@ -50,6 +50,26 @@ function="Gear.Thumbnail" /> </menu_item_call> <menu_item_call + label="Add to favorite outfits" + layout="topleft" + name="favorites_add"> + <on_visible + function="Gear.OnVisible" + parameter="favorites_add" /> + <on_click + function="Gear.Favorite" /> + </menu_item_call> + <menu_item_call + label="Remove from favorite outfits" + layout="topleft" + name="favorites_remove"> + <on_visible + function="Gear.OnVisible" + parameter="favorites_remove" /> + <on_click + function="Gear.Favorite" /> + </menu_item_call> + <menu_item_call label="Rename outfit" layout="topleft" name="rename"> @@ -89,34 +109,18 @@ function="Gear.OnVisible" parameter="delete" /> </menu_item_call> - <menu_item_separator> - <on_visible - function="Gear.OnVisible"/> - </menu_item_separator> + <menu_item_separator + name="sort_order_separator"/> <menu_item_check - label="Sort folders always by name" + label="Show outfits with images first" layout="topleft" - name="sort_folders_by_name"> + name="sort_order_by_image"> <on_click - function="Gear.SortByName" /> + function="Gear.SortByImage" /> <on_check - function="CheckControl" - parameter="OutfitGallerySortByName" /> + function="Gear.OnEnable" + parameter="sort_by_image" /> </menu_item_check> - <menu_item_call - label="Expand all folders" - layout="topleft" - name="expand"> - <on_click - function="Gear.Expand" /> - </menu_item_call> - <menu_item_call - label="Collapse all folders" - layout="topleft" - name="collapse"> - <on_click - function="Gear.Collapse" /> - </menu_item_call> <menu_item_separator/> <!-- copied (with minor modifications) from menu_inventory_add.xml --> <!-- *TODO: generate dynamically? --> @@ -277,4 +281,19 @@ </menu_item_call> </menu> <!-- copied from menu_inventory_add.xml --> + + <menu_item_separator + name="inv_settings_separator"/> + + <menu_item_check + label="Inventory settings..." + layout="topleft" + name="inventory_settings"> + <menu_item_check.on_check + function="Floater.Visible" + parameter="inventory_settings" /> + <menu_item_check.on_click + function="Floater.Toggle" + parameter="inventory_settings" /> + </menu_item_check> </toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/menu_outfit_sort.xml b/indra/newview/skins/default/xui/en/menu_outfit_sort.xml new file mode 100644 index 0000000000..0a4d1ea877 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_outfit_sort.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<toggleable_menu + layout="topleft" + visible="false" + name="Sort Outfit"> + <menu_item_call + label="Expand all folders" + layout="topleft" + name="expand"> + <on_click + function="Sort.Expand" /> + </menu_item_call> + <menu_item_call + label="Collapse all folders" + layout="topleft" + visible="true" + name="collapse"> + <on_click + function="Sort.Collapse" /> + </menu_item_call> + + <menu_item_separator/> + + <menu_item_check + label="Sort favorites to top" + layout="topleft" + name="sort_favorites_to_top"> + <on_click + function="Sort.OnSort" + parameter="favorites_to_top" /> + <on_check + function="Sort.OnEnable" + parameter="favorites_to_top" /> + </menu_item_check> + + <menu_item_separator/> + + <menu_item_check + label="Show entire outfit in search" + layout="topleft" + name="show_entire_outfit_in_search"> + <on_click + function="Sort.OnSort" + parameter="show_entire_outfit" /> + <on_check + function="Sort.OnEnable" + parameter="show_entire_outfit" /> + </menu_item_check> +</toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/menu_outfit_tab.xml b/indra/newview/skins/default/xui/en/menu_outfit_tab.xml index c6805edd63..0d45e7c95c 100644 --- a/indra/newview/skins/default/xui/en/menu_outfit_tab.xml +++ b/indra/newview/skins/default/xui/en/menu_outfit_tab.xml @@ -50,6 +50,26 @@ function="Outfit.Thumbnail" /> </menu_item_call> <menu_item_call + label="Add to favorites" + layout="topleft" + name="favorites_add"> + <on_visible + function="Outfit.OnVisible" + parameter="favorites_add" /> + <on_click + function="Outfit.Favorite" /> + </menu_item_call> + <menu_item_call + label="Remove from favorites" + layout="topleft" + name="favorites_remove"> + <on_visible + function="Outfit.OnVisible" + parameter="favorites_remove" /> + <on_click + function="Outfit.Favorite" /> + </menu_item_call> + <menu_item_call label="Edit outfit" layout="topleft" name="edit"> diff --git a/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml b/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml index ee77ef23f0..63d37edf38 100644 --- a/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml +++ b/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml @@ -80,6 +80,20 @@ function="Attachment.Profile" /> </menu_item_call> <menu_item_call + label="Add to favorites" + layout="topleft" + name="favorites_add"> + <on_click + function="Attachment.Favorite" /> + </menu_item_call> + <menu_item_call + label="Remove from favorites" + layout="topleft" + name="favorites_remove"> + <on_click + function="Attachment.Favorite" /> + </menu_item_call> + <menu_item_call label="Show Original" layout="topleft" visible="false" diff --git a/indra/newview/skins/default/xui/en/menu_wearing_tab.xml b/indra/newview/skins/default/xui/en/menu_wearing_tab.xml index 321e8a0831..9a752a1643 100644 --- a/indra/newview/skins/default/xui/en/menu_wearing_tab.xml +++ b/indra/newview/skins/default/xui/en/menu_wearing_tab.xml @@ -31,6 +31,20 @@ function="Wearing.Detach" parameter="detach"/> </menu_item_call> + <menu_item_call + label="Add to favorites" + layout="topleft" + name="favorites_add"> + <on_click + function="Wearing.Favorite" /> + </menu_item_call> + <menu_item_call + label="Remove from favorites" + layout="topleft" + name="favorites_remove"> + <on_click + function="Wearing.Favorite" /> + </menu_item_call> <menu_item_separator layout="topleft" name="edit_outfit_separator" /> diff --git a/indra/newview/skins/default/xui/en/panel_inventory_gallery_item.xml b/indra/newview/skins/default/xui/en/panel_inventory_gallery_item.xml index 73cb9b080f..f5906c17fd 100644 --- a/indra/newview/skins/default/xui/en/panel_inventory_gallery_item.xml +++ b/indra/newview/skins/default/xui/en/panel_inventory_gallery_item.xml @@ -43,6 +43,16 @@ follows="left|top" visible="false" image_name="Inv_Link"/> + <icon + layout="topleft" + follows="left|top" + name="fav_icon" + left="110" + top_pad="-14" + height="14" + width="14" + visible="true" + image_name="Inv_Favorite_Star_Full"/> <panel background_visible="false" background_opaque="true" diff --git a/indra/newview/skins/default/xui/en/panel_main_inventory.xml b/indra/newview/skins/default/xui/en/panel_main_inventory.xml index 498dab1ef3..6e1e6facbe 100644 --- a/indra/newview/skins/default/xui/en/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/en/panel_main_inventory.xml @@ -269,6 +269,22 @@ scroll.reserve_scroll_corner="false"> <folder double_click_override="true"/> </inventory_panel> + <favorites_inventory_panel + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + follows="all" + label="FAVORITES" + help_topic="recent_inventory_tab" + layout="topleft" + name="Favorites" + show_item_link_overlays="true" + preinitialize_views="false" + scroll.reserve_scroll_corner="false"> + <folder double_click_override="true"/> + </favorites_inventory_panel> </tab_container> </panel> <panel diff --git a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml index 6bd491f7a3..96624e7aa2 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml @@ -35,7 +35,7 @@ </text> <scroll_container follows="all" - height="400" + height="429" width="312" layout="topleft" left="4" @@ -44,49 +44,5 @@ name="gallery_scroll_panel" opaque="false" top_pad="0"> - </scroll_container> - <panel - background_visible="true" - follows="bottom|left|right" - height="28" - layout="topleft" - left="4" - top_pad="0" - visible="true" - name="bottom_panel" - width="312"> - <menu_button - follows="bottom|left" - tool_tip="Show additional options" - height="25" - image_hover_unselected="Toolbar_Left_Over" - image_overlay="OptionsMenu_Off" - image_selected="Toolbar_Left_Selected" - image_unselected="Toolbar_Left_Off" - layout="topleft" - left="0" - name="options_gear_btn" - top="1" - width="31" /> - <icon - follows="bottom|left|right" - height="25" - image_name="Toolbar_Middle_Off" - layout="topleft" - left_pad="1" - name="dummy_icon" - width="243"/> - <button - follows="bottom|right" - height="25" - image_hover_unselected="Toolbar_Right_Over" - image_overlay="TrashItem_Off" - image_selected="Toolbar_Right_Selected" - image_unselected="Toolbar_Right_Off" - layout="topleft" - left_pad="1" - name="trash_btn" - tool_tip="Delete selected outfit" - width="31"/> - </panel> + </scroll_container> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_outfits_list.xml b/indra/newview/skins/default/xui/en/panel_outfits_list.xml index 9281a21fbf..b38e2b2b50 100644 --- a/indra/newview/skins/default/xui/en/panel_outfits_list.xml +++ b/indra/newview/skins/default/xui/en/panel_outfits_list.xml @@ -16,7 +16,7 @@ bg_opaque_color="DkGray2" follows="all" - height="400" + height="428" layout="topleft" left="3" name="outfits_accordion" @@ -30,48 +30,4 @@ name="no_outfits_msg" value="You don't have any outfits yet. Try [secondlife:///app/search/all/ Search]"/> </accordion> - <panel - background_visible="true" - follows="bottom|left|right" - height="28" - layout="topleft" - left="4" - top_pad="0" - visible="true" - name="bottom_panel" - width="312"> - <menu_button - follows="bottom|left" - tool_tip="Show additional options" - height="25" - image_hover_unselected="Toolbar_Left_Over" - image_overlay="OptionsMenu_Off" - image_selected="Toolbar_Left_Selected" - image_unselected="Toolbar_Left_Off" - layout="topleft" - left="0" - name="options_gear_btn" - top="1" - width="31" /> - <icon - follows="bottom|left|right" - height="25" - image_name="Toolbar_Middle_Off" - layout="topleft" - left_pad="1" - name="dummy_icon" - width="243"/> - <button - follows="bottom|right" - height="25" - image_hover_unselected="Toolbar_Right_Over" - image_overlay="TrashItem_Off" - image_selected="Toolbar_Right_Selected" - image_unselected="Toolbar_Right_Off" - layout="topleft" - left_pad="1" - name="trash_btn" - tool_tip="Delete selected outfit" - width="31"/> - </panel> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml b/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml index a486c03ac7..ceefe9f6fb 100644 --- a/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml +++ b/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml @@ -16,7 +16,7 @@ <accordion fit_parent="true" follows="all" - height="400" + height="429" layout="topleft" left="3" single_expansion="true" @@ -62,35 +62,4 @@ </scroll_list> </accordion_tab> </accordion> - <panel - background_visible="true" - follows="bottom|left|right" - height="28" - layout="topleft" - left="4" - name="bottom_panel" - top_pad="0" - width="312"> - <menu_button - follows="bottom|left" - height="25" - image_hover_unselected="Toolbar_Left_Over" - image_overlay="OptionsMenu_Off" - image_selected="Toolbar_Left_Selected" - image_unselected="Toolbar_Left_Off" - layout="topleft" - left="0" - name="options_gear_btn" - tool_tip="Show additional options" - top="1" - width="31" /> - <icon - follows="bottom|left|right" - height="25" - image_name="Toolbar_Right_Off" - layout="topleft" - left_pad="1" - name="dummy_icon" - width="274" /> - </panel> </panel> diff --git a/indra/newview/skins/default/xui/en/sidepanel_appearance.xml b/indra/newview/skins/default/xui/en/sidepanel_appearance.xml index c898b0989e..a16b0b58da 100644 --- a/indra/newview/skins/default/xui/en/sidepanel_appearance.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_appearance.xml @@ -64,7 +64,7 @@ width="333"> font="SansSerifSmall" text_color="EmphasisColor" width="300" - height="10" + height="13" follows="top|left|right" layout="topleft" left="35" @@ -94,7 +94,7 @@ width="333"> image_overlay="Edit_Wrench" label="" layout="topleft" - left="265" + right="-3" name="edit_outfit_btn" tool_tip="Edit this outfit" top="3" @@ -108,17 +108,100 @@ width="333"> top="6" width="24" /> </panel> - <filter_editor - height="23" - follows="left|top|right" - layout="topleft" - left="10" - label="Filter Outfits" - max_length_chars="300" - name="Filter" - search_button_visible="true" - top_pad="10" - width="307" /> + <layout_stack + animate="false" + border_size="0" + follows="left|top|right" + height="27" + layout="topleft" + orientation="horizontal" + top_pad="6" + left="0" + name="top_menu_panel" + width="320"> + <layout_panel + auto_resize="true" + layout="topleft" + name="filter_panel" + width="193"> + <filter_editor + text_pad_left="10" + follows="left|top|right" + font="SansSerifSmall" + height="23" + layout="topleft" + left="10" + label="Filter Outfits" + max_length_chars="300" + name="Filter" + search_button_visible="true" + tab_group="1" + top="3" + width="181" /> + </layout_panel> + <layout_panel + auto_resize="false" + height="25" + layout="topleft" + name="options_gear_btn_panel" + width="32"> + <menu_button + follows="bottom|left" + tool_tip="Show additional options" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="OptionsMenu_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + menu_position="bottomleft" + layout="topleft" + left="0" + name="options_gear_btn" + top="0" + width="31" /> + </layout_panel> + <layout_panel + auto_resize="false" + height="25" + layout="topleft" + name="options_sort_btn_panel" + width="32"> + <menu_button + follows="bottom|left" + tool_tip="Show sorting options" + height="25" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Conv_toolbar_sort" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + menu_position="bottomleft" + layout="topleft" + left="0" + name="sorting_menu_btn" + top="0" + width="31" /> + </layout_panel> + <layout_panel + auto_resize="false" + height="25" + layout="topleft" + name="trash_btn_panel" + width="31"> + <dnd_button + follows="bottom|left" + height="25" + image_hover_unselected="Toolbar_Right_Over" + image_overlay="TrashItem_Off" + image_selected="Toolbar_Right_Selected" + image_unselected="Toolbar_Right_Off" + left="0" + layout="topleft" + name="trash_btn" + tool_tip="Delete selected outfit" + top="0" + width="31"/> + </layout_panel> + </layout_stack> <panel class="panel_outfits_inventory" filename="panel_outfits_inventory.xml" @@ -129,7 +212,7 @@ width="333"> visible="false" left="0" tab_group="1" - top_pad="6" + top_pad="4" follows="all" /> <panel class="panel_outfit_edit" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 3ec4b7205b..a0ee52cfd0 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2324,6 +2324,7 @@ For AI Character: Get the closest navigable point to the point provided. <!-- inventory --> <string name="InventoryNoMatchingItems">Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search].</string> <string name="InventoryNoMatchingRecentItems">Didn't find what you're looking for? Try [secondlife:///app/inventory/filters Show filters].</string> + <string name="InventoryNoMatchingFavorites">You haven't marked any items as favorites.</string> <string name="PlacesNoMatchingItems">To add a place to your landmarks, click the star to the right of the location name.</string> <string name="FavoritesNoMatchingItems">To add a place to your favorites, click the star to the right of the location name, then save the landmark to "Favorites bar".</string> <string name="MarketplaceNoListing">You have no listings yet.</string> diff --git a/indra/newview/skins/default/xui/en/widgets/folder_view_item.xml b/indra/newview/skins/default/xui/en/widgets/folder_view_item.xml index b598bbccd8..50c5285e04 100644 --- a/indra/newview/skins/default/xui/en/widgets/folder_view_item.xml +++ b/indra/newview/skins/default/xui/en/widgets/folder_view_item.xml @@ -1,6 +1,8 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <folder_view_item folder_arrow_image="Folder_Arrow" + favorite_image="Inv_Favorite_Star_Full" + favorite_content_image="Inv_Favorite_Star_Content" folder_indentation="8" item_height="20" item_top_pad="4" diff --git a/indra/newview/skins/default/xui/en/widgets/inbox_folder_view_folder.xml b/indra/newview/skins/default/xui/en/widgets/inbox_folder_view_folder.xml index 27ec6ded81..865c145022 100644 --- a/indra/newview/skins/default/xui/en/widgets/inbox_folder_view_folder.xml +++ b/indra/newview/skins/default/xui/en/widgets/inbox_folder_view_folder.xml @@ -1,6 +1,8 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <inbox_folder_view_folder folder_arrow_image="Folder_Arrow" + favorite_image="Inv_Favorite_Star_Full" + favorite_content_image="Inv_Favorite_Star_Content" folder_indentation="8" item_height="20" item_top_pad="4" |