/** * @file lloutfitgallery.cpp * @author Pavlo Kryvych * @brief Visual gallery of agent's outfits for My Appearance side panel * * $LicenseInfo:firstyear=2015&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2015, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" // must be first include #include "lloutfitgallery.h" // llcommon #include "llcommonutils.h" #include "llfilesystem.h" #include "llaccordionctrltab.h" #include "llappearancemgr.h" #include "llerror.h" #include "llfilepicker.h" #include "llfloaterperms.h" #include "llfloaterreg.h" #include "llfloatersimplesnapshot.h" #include "llimagedimensionsinfo.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" #include "lllocalbitmaps.h" #include "llnotificationsutil.h" #include "llpaneloutfitsinventory.h" #include "lltabcontainer.h" #include "lltexturectrl.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llviewermenufile.h" #include "llviewertexturelist.h" #include "llwearableitemslist.h" static LLPanelInjector t_outfit_gallery("outfit_gallery"); // The maximum resolution at which to load the outfit photo. If the given // texture has a higher resolution, tell the texture streaming system to // only load the resolution needed. An in-world object may request to load // the texture at a higher resolution, but that won't affect textures // loaded with LLViewerTexture::FETCHED_TEXTURE. (see // LLOutfitGalleryItem::setImageAssetId and also // LLViewerTexture::LOD_TEXTURE) #define MAX_OUTFIT_PHOTO_LOAD_WIDTH 256 #define MAX_OUTFIT_PHOTO_LOAD_HEIGHT 256 const S32 GALLERY_ITEMS_PER_ROW_MIN = 2; LLOutfitGallery::LLOutfitGallery(const LLOutfitGallery::Params& p) : LLOutfitListBase(), mOutfitsObserver(NULL), mScrollPanel(NULL), mGalleryPanel(NULL), mLastRowPanel(NULL), mGalleryCreated(false), mRowCount(0), mItemsAddedCount(0), mOutfitLinkPending(NULL), mOutfitRenamePending(NULL), mSnapshotFolderID(NULL), mRowPanelHeight(p.row_panel_height), mVerticalGap(p.vertical_gap), mHorizontalGap(p.horizontal_gap), mItemWidth(p.item_width), mItemHeight(p.item_height), mItemHorizontalGap(p.item_horizontal_gap), mItemsInRow(p.items_in_row), mRowPanWidthFactor(p.row_panel_width_factor), mGalleryWidthFactor(p.gallery_width_factor), mTextureSelected(NULL) { updateGalleryWidth(); } LLOutfitGallery::Params::Params() : row_panel_height("row_panel_height", 180), vertical_gap("vertical_gap", 10), horizontal_gap("horizontal_gap", 10), item_width("item_width", 150), item_height("item_height", 175), item_horizontal_gap("item_horizontal_gap", 16), items_in_row("items_in_row", GALLERY_ITEMS_PER_ROW_MIN), row_panel_width_factor("row_panel_width_factor", 166), gallery_width_factor("gallery_width_factor", 163) { addSynonym(row_panel_height, "row_height"); } const LLOutfitGallery::Params& LLOutfitGallery::getDefaultParams() { return LLUICtrlFactory::getDefaultParams(); } bool LLOutfitGallery::postBuild() { bool rv = LLOutfitListBase::postBuild(); mScrollPanel = getChild("gallery_scroll_panel"); mMessageTextBox = getChild("no_outfits_txt"); mOutfitGalleryMenu = new LLOutfitGalleryContextMenu(this); return rv; } void LLOutfitGallery::onOpen(const LLSD& info) { LLOutfitListBase::onOpen(info); if (!mGalleryCreated) { uuid_vec_t cats; getCurrentCategories(cats); int n = static_cast(cats.size()); buildGalleryPanel(n); mScrollPanel->addChild(mGalleryPanel); for (int i = 0; i < n; i++) { addToGallery(getItem(cats[i])); } reArrangeRows(); mGalleryCreated = true; } } void LLOutfitGallery::draw() { LLPanel::draw(); if (mGalleryCreated) { updateRowsIfNeeded(); } } bool LLOutfitGallery::handleKeyHere(KEY key, MASK mask) { bool handled = false; switch (key) { case KEY_RETURN: // Open selected items if enter key hit on the inventory panel if (mask == MASK_NONE && mSelectedOutfitUUID.notNull()) { // Or should it wearSelectedOutfit? getSelectedItem()->openOutfitsContent(); } handled = true; break; case KEY_DELETE: #if LL_DARWIN case KEY_BACKSPACE: #endif // Delete selected items if delete or backspace key hit on the inventory panel // Note: on Mac laptop keyboards, backspace and delete are one and the same if (mSelectedOutfitUUID.notNull()) { onRemoveOutfit(mSelectedOutfitUUID); } handled = true; break; case KEY_F2: LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID); handled = true; break; case KEY_PAGE_UP: if (mScrollPanel) { mScrollPanel->pageUp(30); } handled = true; break; case KEY_PAGE_DOWN: if (mScrollPanel) { mScrollPanel->pageDown(30); } handled = true; break; case KEY_HOME: if (mScrollPanel) { mScrollPanel->goToTop(); } handled = true; break; case KEY_END: if (mScrollPanel) { mScrollPanel->goToBottom(); } handled = true; break; case KEY_LEFT: moveLeft(); handled = true; break; case KEY_RIGHT: moveRight(); handled = true; break; case KEY_UP: moveUp(); handled = true; break; case KEY_DOWN: moveDown(); handled = true; break; default: break; } if (handled) { mOutfitGalleryMenu->hide(); } return handled; } void LLOutfitGallery::moveUp() { if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) { LLOutfitGalleryItem* item = getSelectedItem(); if (item) { S32 n = mItemIndexMap[item]; n -= mItemsInRow; if (n >= 0) { item = mIndexToItemMap[n]; LLUUID item_id = item->getUUID(); ChangeOutfitSelection(nullptr, item_id); item->setFocus(true); scrollToShowItem(mSelectedOutfitUUID); } } } } void LLOutfitGallery::moveDown() { if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) { LLOutfitGalleryItem* item = getSelectedItem(); if (item) { S32 n = mItemIndexMap[item]; n += mItemsInRow; if (n < mItemsAddedCount) { item = mIndexToItemMap[n]; LLUUID item_id = item->getUUID(); ChangeOutfitSelection(nullptr, item_id); item->setFocus(true); scrollToShowItem(mSelectedOutfitUUID); } } } } void LLOutfitGallery::moveLeft() { if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) { LLOutfitGalleryItem* item = getSelectedItem(); if (item) { // Might be better to get item from panel S32 n = mItemIndexMap[item]; n--; if (n < 0) { n = mItemsAddedCount - 1; } item = mIndexToItemMap[n]; LLUUID item_id = item->getUUID(); ChangeOutfitSelection(nullptr, item_id); item->setFocus(true); scrollToShowItem(mSelectedOutfitUUID); } } } void LLOutfitGallery::moveRight() { if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) { LLOutfitGalleryItem* item = getSelectedItem(); if (item) { S32 n = mItemIndexMap[item]; n++; if (n == mItemsAddedCount) { n = 0; } item = mIndexToItemMap[n]; LLUUID item_id = item->getUUID(); ChangeOutfitSelection(nullptr, item_id); item->setFocus(true); scrollToShowItem(mSelectedOutfitUUID); } } } void LLOutfitGallery::onFocusLost() { LLOutfitListBase::onFocusLost(); if (mSelectedOutfitUUID.notNull()) { LLOutfitGalleryItem* item = getSelectedItem(); if (item) { item->setSelected(false); } } } void LLOutfitGallery::onFocusReceived() { LLOutfitListBase::onFocusReceived(); if (mSelectedOutfitUUID.notNull()) { LLOutfitGalleryItem* item = getSelectedItem(); if (item) { item->setSelected(true); } } } void LLOutfitGallery::onRemoveOutfit(const LLUUID& outfit_cat_id) { LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(onOutfitsRemovalConfirmation, _1, _2, outfit_cat_id)); } void LLOutfitGallery::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (option != 0) return; // canceled if (outfit_cat_id.notNull()) { gInventory.removeCategory(outfit_cat_id); } } void LLOutfitGallery::scrollToShowItem(const LLUUID& item_id) { LLOutfitGalleryItem* item = getItem(item_id); if (item) { const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect(); LLRect item_rect; item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel); LLRect overlap_rect(item_rect); overlap_rect.intersectWith(visible_content_rect); //Scroll when the selected item is outside the visible area if (overlap_rect.getHeight() + 5 < item->getRect().getHeight()) { LLRect content_rect = mScrollPanel->getContentWindowRect(); LLRect constraint_rect; constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); LLRect item_doc_rect; item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel); mScrollPanel->scrollToShowRect(item_doc_rect, constraint_rect); } } } void LLOutfitGallery::updateRowsIfNeeded() { if(((getRect().getWidth() - mRowPanelWidth) > mItemWidth) && mRowCount > 1) { reArrangeRows(1); } else if((mRowPanelWidth > (getRect().getWidth() + mItemHorizontalGap)) && mItemsInRow > GALLERY_ITEMS_PER_ROW_MIN) { reArrangeRows(-1); } } bool compareGalleryItem(LLOutfitGalleryItem* item1, LLOutfitGalleryItem* item2) { static LLCachedControl outfit_gallery_sort_by_name(gSavedSettings, "OutfitGallerySortByName"); if(outfit_gallery_sort_by_name || ((item1->isDefaultImage() && item2->isDefaultImage()) || (!item1->isDefaultImage() && !item2->isDefaultImage()))) { std::string name1 = item1->getItemName(); std::string name2 = item2->getItemName(); return (LLStringUtil::compareDict(name1, name2) < 0); } else { return item2->isDefaultImage(); } } void LLOutfitGallery::reArrangeRows(S32 row_diff) { std::vector buf_items = mItems; for (std::vector::const_reverse_iterator it = buf_items.rbegin(); it != buf_items.rend(); ++it) { removeFromGalleryLast(*it); } for (std::vector::const_reverse_iterator it = mHiddenItems.rbegin(); it != mHiddenItems.rend(); ++it) { buf_items.push_back(*it); } mHiddenItems.clear(); mItemsInRow += row_diff; updateGalleryWidth(); std::sort(buf_items.begin(), buf_items.end(), compareGalleryItem); std::string cur_filter = getFilterSubString(); LLStringUtil::toUpper(cur_filter); for (std::vector::const_iterator it = buf_items.begin(); it != buf_items.end(); ++it) { std::string outfit_name = (*it)->getItemName(); LLStringUtil::toUpper(outfit_name); bool hidden = (std::string::npos == outfit_name.find(cur_filter)); (*it)->setHidden(hidden); addToGallery(*it); } updateMessageVisibility(); } void LLOutfitGallery::updateGalleryWidth() { mRowPanelWidth = mRowPanWidthFactor * mItemsInRow - mItemHorizontalGap; mGalleryWidth = mGalleryWidthFactor * mItemsInRow - mItemHorizontalGap; } LLPanel* LLOutfitGallery::addLastRow() { mRowCount++; int row = 0; int vgap = mVerticalGap * row; LLPanel* result = buildRowPanel(0, row * mRowPanelHeight + vgap); mGalleryPanel->addChild(result); return result; } void LLOutfitGallery::moveRowUp(int row) { moveRow(row, mRowCount - 1 - row + 1); } void LLOutfitGallery::moveRowDown(int row) { moveRow(row, mRowCount - 1 - row - 1); } void LLOutfitGallery::moveRow(int row, int pos) { int vgap = mVerticalGap * pos; moveRowPanel(mRowPanels[row], 0, pos * mRowPanelHeight + vgap); } void LLOutfitGallery::removeLastRow() { mRowCount--; mGalleryPanel->removeChild(mLastRowPanel); mUnusedRowPanels.push_back(mLastRowPanel); mRowPanels.pop_back(); if (mRowPanels.size() > 0) { // Just removed last row mLastRowPanel = mRowPanels.back(); } else { mLastRowPanel = NULL; } } LLPanel* LLOutfitGallery::addToRow(LLPanel* row_stack, LLOutfitGalleryItem* item, int pos, int hgap) { LLPanel* lpanel = buildItemPanel(pos * mItemWidth + hgap); lpanel->addChild(item); row_stack->addChild(lpanel); mItemPanels.push_back(lpanel); return lpanel; } void LLOutfitGallery::addToGallery(LLOutfitGalleryItem* item) { if (!item) { return; } if(item->isHidden()) { mHiddenItems.push_back(item); return; } mItemIndexMap[item] = mItemsAddedCount; mIndexToItemMap[mItemsAddedCount] = item; mItemsAddedCount++; int n = mItemsAddedCount; int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; int n_prev = n - 1; int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; bool add_row = row_count != row_count_prev; int pos = 0; if (add_row) { for (int i = 0; i < row_count_prev; i++) { moveRowUp(i); } mLastRowPanel = addLastRow(); mRowPanels.push_back(mLastRowPanel); } pos = (n - 1) % mItemsInRow; mItems.push_back(item); addToRow(mLastRowPanel, item, pos, mHorizontalGap * pos); reshapeGalleryPanel(row_count); } void LLOutfitGallery::removeFromGalleryLast(LLOutfitGalleryItem* item) { if(item->isHidden()) { mHiddenItems.pop_back(); return; } int n_prev = mItemsAddedCount; int n = mItemsAddedCount - 1; int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; mItemsAddedCount--; mIndexToItemMap.erase(mItemsAddedCount); bool remove_row = row_count != row_count_prev; removeFromLastRow(mItems[mItemsAddedCount]); mItems.pop_back(); if (remove_row) { for (int i = 0; i < row_count_prev - 1; i++) { moveRowDown(i); } removeLastRow(); } reshapeGalleryPanel(row_count); } void LLOutfitGallery::removeFromGalleryMiddle(LLOutfitGalleryItem* item) { if(item->isHidden()) { mHiddenItems.erase(std::remove(mHiddenItems.begin(), mHiddenItems.end(), item), mHiddenItems.end()); return; } int n = mItemIndexMap[item]; mItemIndexMap.erase(item); mIndexToItemMap.erase(n); std::vector saved; for (int i = mItemsAddedCount - 1; i > n; i--) { saved.push_back(mItems[i]); removeFromGalleryLast(mItems[i]); } removeFromGalleryLast(mItems[n]); auto saved_count = saved.size(); for (size_t i = 0; i < saved_count; i++) { addToGallery(saved.back()); saved.pop_back(); } } void LLOutfitGallery::removeFromLastRow(LLOutfitGalleryItem* item) { mItemPanels.back()->removeChild(item); mLastRowPanel->removeChild(mItemPanels.back()); mUnusedItemPanels.push_back(mItemPanels.back()); mItemPanels.pop_back(); } LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID outfit_id) { LLOutfitGalleryItem::Params giparams; LLOutfitGalleryItem* gitem = LLUICtrlFactory::create(giparams); gitem->reshape(mItemWidth, mItemHeight); gitem->setVisible(true); gitem->setFollowsLeft(); gitem->setFollowsTop(); gitem->setOutfitName(name); gitem->setUUID(outfit_id); gitem->setGallery(this); return gitem; } LLOutfitGalleryItem* LLOutfitGallery::getSelectedItem() const { return getItem(mSelectedOutfitUUID); } LLOutfitGalleryItem* LLOutfitGallery::getItem(const LLUUID& id) const { auto it = mOutfitMap.find(id); if (it != mOutfitMap.end()) { return it->second; } return nullptr; } void LLOutfitGallery::buildGalleryPanel(int row_count) { LLPanel::Params params; mGalleryPanel = LLUICtrlFactory::create(params); reshapeGalleryPanel(row_count); } void LLOutfitGallery::reshapeGalleryPanel(int row_count) { int bottom = 0; int left = 0; int height = row_count * (mRowPanelHeight + mVerticalGap); LLRect rect = LLRect(left, bottom + height, left + mGalleryWidth, bottom); mGalleryPanel->setRect(rect); mGalleryPanel->reshape(mGalleryWidth, height); mGalleryPanel->setVisible(true); mGalleryPanel->setFollowsLeft(); mGalleryPanel->setFollowsTop(); } LLPanel* LLOutfitGallery::buildItemPanel(int left) { LLPanel::Params lpparams; int top = 0; LLPanel* lpanel = NULL; if(mUnusedItemPanels.empty()) { lpanel = LLUICtrlFactory::create(lpparams); } else { lpanel = mUnusedItemPanels.back(); mUnusedItemPanels.pop_back(); } LLRect rect = LLRect(left, top + mItemHeight, left + mItemWidth + mItemHorizontalGap, top); lpanel->setRect(rect); lpanel->reshape(mItemWidth + mItemHorizontalGap, mItemHeight); lpanel->setVisible(true); lpanel->setFollowsLeft(); lpanel->setFollowsTop(); return lpanel; } LLPanel* LLOutfitGallery::buildRowPanel(int left, int bottom) { LLPanel::Params sparams; LLPanel* stack = NULL; if(mUnusedRowPanels.empty()) { stack = LLUICtrlFactory::create(sparams); } else { stack = mUnusedRowPanels.back(); mUnusedRowPanels.pop_back(); } moveRowPanel(stack, left, bottom); return stack; } void LLOutfitGallery::moveRowPanel(LLPanel* stack, int left, int bottom) { LLRect rect = LLRect(left, bottom + mRowPanelHeight, left + mRowPanelWidth, bottom); stack->setRect(rect); stack->reshape(mRowPanelWidth, mRowPanelHeight); stack->setVisible(true); stack->setFollowsLeft(); stack->setFollowsTop(); } LLOutfitGallery::~LLOutfitGallery() { delete mOutfitGalleryMenu; if (gInventory.containsObserver(mOutfitsObserver)) { gInventory.removeObserver(mOutfitsObserver); } delete mOutfitsObserver; while (!mUnusedRowPanels.empty()) { LLPanel* panelp = mUnusedRowPanels.back(); mUnusedRowPanels.pop_back(); panelp->die(); } while (!mUnusedItemPanels.empty()) { LLPanel* panelp = mUnusedItemPanels.back(); mUnusedItemPanels.pop_back(); panelp->die(); } } // virtual void LLOutfitGallery::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string) { reArrangeRows(); } void LLOutfitGallery::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) { auto base_it = mOutfitMap.find(base_id); if (base_it != mOutfitMap.end()) { base_it->second->setOutfitWorn(true); } auto prev_it = mOutfitMap.find(prev_id); if (prev_it != mOutfitMap.end()) { prev_it->second->setOutfitWorn(false); } } void LLOutfitGallery::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) { } void LLOutfitGallery::getCurrentCategories(uuid_vec_t& vcur) { for (outfit_map_t::const_iterator iter = mOutfitMap.begin(); iter != mOutfitMap.end(); iter++) { if ((*iter).second != NULL) { vcur.push_back((*iter).first); } } } 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); mOutfitMap.insert(LLOutfitGallery::outfit_map_value_t(cat_id, item)); item->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this, _1, _2, _3, cat_id)); LLWearableItemsList* list = NULL; item->setFocusReceivedCallback(boost::bind(&LLOutfitListBase::ChangeOutfitSelection, this, list, cat_id)); if (mGalleryCreated) { addToGallery(item); } LLViewerInventoryCategory* outfit_category = gInventory.getCategory(cat_id); if (!outfit_category) return; if (mOutfitsObserver == NULL) { mOutfitsObserver = new LLInventoryCategoriesObserver(); gInventory.addObserver(mOutfitsObserver); } // Start observing changes in "My Outfits" category. mOutfitsObserver->addCategory(cat_id, boost::bind(&LLOutfitGallery::refreshOutfit, this, cat_id), true); outfit_category->fetch(); refreshOutfit(cat_id); } void LLOutfitGallery::updateRemovedCategory(LLUUID cat_id) { outfit_map_t::iterator outfits_iter = mOutfitMap.find(cat_id); if (outfits_iter != mOutfitMap.end()) { // 0. Remove category from observer. mOutfitsObserver->removeCategory(cat_id); //const LLUUID& outfit_id = outfits_iter->first; LLOutfitGalleryItem* item = outfits_iter->second; // An outfit is removed from the list. Do the following: // 2. Remove the outfit from selection. deselectOutfit(cat_id); // 3. Remove category UUID to accordion tab mapping. mOutfitMap.erase(outfits_iter); // 4. Remove outfit from gallery. removeFromGalleryMiddle(item); // kill removed item if (item != NULL) { item->die(); } } } void LLOutfitGallery::updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name) { outfit_map_t::iterator outfit_iter = mOutfitMap.find(cat->getUUID()); if (outfit_iter != mOutfitMap.end()) { // Update name of outfit in gallery LLOutfitGalleryItem* item = outfit_iter->second; if (item) { item->setOutfitName(name); } } } void LLOutfitGallery::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) { if (mOutfitMenu && cat_id.notNull()) { uuid_vec_t selected_uuids; selected_uuids.push_back(cat_id); mOutfitGalleryMenu->show(ctrl, selected_uuids, x, y); } } void LLOutfitGallery::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) { if (mSelectedOutfitUUID == category_id) return; auto selected_it = mOutfitMap.find(mSelectedOutfitUUID); if (selected_it != mOutfitMap.end()) { selected_it->second->setSelected(false); } auto category_it = mOutfitMap.find(category_id); if (category_it != mOutfitMap.end()) { category_it->second->setSelected(true); } // mSelectedOutfitUUID will be set in LLOutfitListBase::ChangeOutfitSelection } void LLOutfitGallery::wearSelectedOutfit() { LLAppearanceMgr::instance().replaceCurrentOutfit(getSelectedOutfitUUID()); } bool LLOutfitGallery::hasItemSelected() { return false; } bool LLOutfitGallery::canWearSelected() { return false; } bool LLOutfitGallery::hasDefaultImage(const LLUUID& outfit_cat_id) { auto outfit_it = mOutfitMap.find(outfit_cat_id); if (outfit_it != mOutfitMap.end()) { return outfit_it->second->isDefaultImage(); } return false; } void LLOutfitGallery::updateMessageVisibility() { if (mItems.empty()) { mMessageTextBox->setVisible(true); mScrollPanel->setVisible(false); std::string message = getString(getFilterSubString().empty() ? "no_outfits_msg" : "no_matched_outfits_msg"); mMessageTextBox->setValue(message); } else { mScrollPanel->setVisible(true); mMessageTextBox->setVisible(false); } } LLOutfitListGearMenuBase* LLOutfitGallery::createGearMenu() { return new LLOutfitGalleryGearMenu(this); } static LLDefaultChildRegistry::Register r("outfit_gallery_item"); LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) : LLPanel(p), mGallery(nullptr), mTexturep(nullptr), mSelected(false), mWorn(false), mDefaultImage(true), mOutfitName(""), mUUID(LLUUID()) { buildFromFile("panel_outfit_gallery_item.xml"); } LLOutfitGalleryItem::~LLOutfitGalleryItem() { } bool LLOutfitGalleryItem::postBuild() { mPreviewIcon = getChild("preview_outfit"); setDefaultImage(); mOutfitNameText = getChild("outfit_name"); mOutfitWornText = getChild("outfit_worn_text"); mTextBgPanel = getChild("text_bg_panel"); setOutfitWorn(false); mHidden = false; return true; } void LLOutfitGalleryItem::draw() { LLPanel::draw(); // Draw border static LLUIColor selected_color = LLUIColorTable::instance().getColor("OutfitGalleryItemSelected", LLColor4::white); static LLUIColor unselected_color = LLUIColorTable::instance().getColor("OutfitGalleryItemUnselected", LLColor4::white); const LLColor4& border_color = mSelected ? selected_color : unselected_color; LLRect border = mPreviewIcon->getRect(); border.mRight = border.mRight + 1; gl_rect_2d(border, border_color, false); if (mTexturep) { LLRect interior = border; interior.stretch(-1); // Pump the priority const F32 stats = (F32)llmin(interior.getWidth() * interior.getHeight(), MAX_OUTFIT_PHOTO_LOAD_WIDTH * MAX_OUTFIT_PHOTO_LOAD_HEIGHT); mTexturep->addTextureStats(stats); // If the floater is focused, don't apply its alpha to the texture (STORM-677). const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); gl_draw_scaled_image(interior.mLeft - 1, interior.mBottom, interior.getWidth(), interior.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); } } void LLOutfitGalleryItem::setOutfitName(std::string name) { mOutfitNameText->setText(name); mOutfitNameText->setToolTip(name); mOutfitName = name; } 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->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); mOutfitNameText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); mOutfitWornText->setValue(value ? worn_string : ""); mOutfitNameText->setText(mOutfitName); // refresh LLTextViewModel to pick up font changes } void LLOutfitGalleryItem::setSelected(bool value) { mSelected = value; mTextBgPanel->setBackgroundVisible(value); setOutfitWorn(mWorn); } bool LLOutfitGalleryItem::handleMouseDown(S32 x, S32 y, MASK mask) { setFocus(true); return LLUICtrl::handleMouseDown(x, y, mask); } bool LLOutfitGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask) { setFocus(true); return LLUICtrl::handleRightMouseDown(x, y, mask); } bool LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) { return openOutfitsContent() || LLPanel::handleDoubleClick(x, y, mask); } bool LLOutfitGalleryItem::handleKeyHere(KEY key, MASK mask) { if (!mGallery) { return false; } bool handled = false; switch (key) { case KEY_LEFT: mGallery->moveLeft(); handled = true; break; case KEY_RIGHT: mGallery->moveRight(); handled = true; break; case KEY_UP: mGallery->moveUp(); handled = true; break; case KEY_DOWN: mGallery->moveDown(); handled = true; break; default: break; } return handled; } void LLOutfitGalleryItem::onFocusLost() { setSelected(false); LLPanel::onFocusLost(); } void LLOutfitGalleryItem::onFocusReceived() { setSelected(true); LLPanel::onFocusReceived(); } bool LLOutfitGalleryItem::openOutfitsContent() { LLTabContainer* appearence_tabs = LLPanelOutfitsInventory::findInstance()->getChild("appearance_tabs"); if (appearence_tabs && mUUID.notNull()) { appearence_tabs->selectTabByName("outfitslist_tab"); LLPanel* panel = appearence_tabs->getCurrentPanel(); if (panel) { LLAccordionCtrl* accordion = panel->getChild("outfits_accordion"); LLOutfitsList* outfit_list = dynamic_cast(panel); if (accordion != NULL && outfit_list != NULL) { outfit_list->setSelectedOutfitByUUID(mUUID); LLAccordionCtrlTab* tab = accordion->getSelectedTab(); if (tab) { tab->showAndFocusHeader(); return true; } } } } return false; } bool LLOutfitGalleryItem::setImageAssetId(LLUUID image_asset_id) { LLPointer texture = LLViewerTextureManager::getFetchedTexture(image_asset_id, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_NONE, LLViewerTexture::FETCHED_TEXTURE); if (texture) { mImageAssetId = image_asset_id; mTexturep = texture; // *TODO: There was previously logic which attempted to toggle // visibility of the preview icon based on certain conditions, // however these conditions either did not make sense or were not // applicable due to texture streaming. Maybe we should only hide // the preview icon if the texture has at least one mip loaded. mPreviewIcon->setVisible(false); mDefaultImage = false; return true; } return false; } LLUUID LLOutfitGalleryItem::getImageAssetId() { return mImageAssetId; } void LLOutfitGalleryItem::setDefaultImage() { mTexturep = NULL; mImageAssetId.setNull(); mPreviewIcon->setVisible(true); mDefaultImage = true; } LLContextMenu* LLOutfitGalleryContextMenu::createMenu() { LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; LLUUID selected_id = mUUIDs.front(); registrar.add("Outfit.WearReplace", boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); registrar.add("Outfit.WearAdd", boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); registrar.add("Outfit.TakeOff", boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); registrar.add("Outfit.Edit", boost::bind(editOutfit)); registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id)); registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2)); registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, 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)); return createFromFile("menu_gallery_outfit_tab.xml"); } void LLOutfitGalleryContextMenu::onCreate(const LLSD& data) { LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString()); if (type == LLWearableType::WT_NONE) { LL_WARNS() << "Invalid wearable type" << LL_ENDL; return; } LLAgentWearables::createWearable(type, true); } bool LLOutfitGalleryContextMenu::onEnable(LLSD::String param) { return LLOutfitContextMenu::onEnable(param); } bool LLOutfitGalleryContextMenu::onVisible(LLSD::String param) { return LLOutfitContextMenu::onVisible(param); } LLOutfitGalleryGearMenu::LLOutfitGalleryGearMenu(LLOutfitListBase* olist) : LLOutfitListGearMenuBase(olist) { } void LLOutfitGalleryGearMenu::onUpdateItemsVisibility() { if (!mMenu) return; bool have_selection = getSelectedOutfitID().notNull(); mMenu->setItemVisible("expand", false); mMenu->setItemVisible("collapse", false); mMenu->setItemVisible("thumbnail", have_selection); mMenu->setItemVisible("sepatator3", true); mMenu->setItemVisible("sort_folders_by_name", true); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); } void LLOutfitGalleryGearMenu::onChangeSortOrder() { bool sort_by_name = !gSavedSettings.getBOOL("OutfitGallerySortByName"); gSavedSettings.setBOOL("OutfitGallerySortByName", sort_by_name); LLOutfitGallery* gallery = dynamic_cast(mOutfitList); if (gallery) { gallery->reArrangeRows(); } } bool LLOutfitGalleryGearMenu::hasDefaultImage() { LLOutfitGallery* gallery = dynamic_cast(mOutfitList); LLUUID selected_outfit_id = getSelectedOutfitID(); if (gallery && selected_outfit_id.notNull()) { return gallery->hasDefaultImage(selected_outfit_id); } return true; } void LLOutfitGallery::onTextureSelectionChanged(LLInventoryItem* itemp) { } void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) { LLViewerInventoryCategory* category = gInventory.getCategory(category_id); if (category) { bool photo_loaded = false; LLUUID asset_id = category->getThumbnailUUID(); if (asset_id.isNull()) { LLInventoryModel::cat_array_t sub_cat_array; LLInventoryModel::item_array_t outfit_item_array; // Collect all sub-categories of a given category. gInventory.collectDescendents( category->getUUID(), sub_cat_array, outfit_item_array, LLInventoryModel::EXCLUDE_TRASH); for (LLViewerInventoryItem* outfit_item : outfit_item_array) { LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); LLUUID asset_id, inv_id; std::string item_name; if (linked_item != NULL) { if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) { asset_id = linked_item->getAssetUUID(); inv_id = linked_item->getUUID(); item_name = linked_item->getName(); } } else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) { asset_id = outfit_item->getAssetUUID(); inv_id = outfit_item->getUUID(); item_name = outfit_item->getName(); } if (category->getThumbnailUUID().notNull()) { asset_id = category->getThumbnailUUID(); } if (asset_id.notNull()) { LLOutfitGalleryItem* item = getItem(category_id); if (item) { photo_loaded |= item->setImageAssetId(asset_id); } else { photo_loaded = true; } // Rename links if (!mOutfitRenamePending.isNull() && mOutfitRenamePending.asString() == item_name) { LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(mOutfitRenamePending); LLStringUtil::format_map_t photo_string_args; photo_string_args["OUTFIT_NAME"] = outfit_cat->getName(); std::string new_name = getString("outfit_photo_string", photo_string_args); LLSD updates; updates["name"] = new_name; update_inventory_item(inv_id, updates, NULL); mOutfitRenamePending.setNull(); LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance"); if (appearance_floater) { appearance_floater->setFocus(true); } } if (item_name == LLAppearanceMgr::sExpectedTextureName) { // Images with "appropriate" name take priority break; } } if (!photo_loaded) { LLOutfitGalleryItem* item = getItem(category_id); if (item) { item->setDefaultImage(); } } } } else { LLOutfitGalleryItem* item = getItem(category_id); if (item) { item->setImageAssetId(asset_id); } } } if (mGalleryCreated && !LLApp::isExiting()) { reArrangeRows(); } } LLUUID LLOutfitGallery::getPhotoAssetId(const LLUUID& outfit_id) { outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); if (outfit_it != mOutfitMap.end()) { return outfit_it->second->getImageAssetId(); } return LLUUID(); } LLUUID LLOutfitGallery::getDefaultPhoto() { return LLUUID(); }