phoenix-firestorm/indra/newview/lloutfitgallery.cpp

1508 lines
44 KiB
C++

/**
* @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<LLOutfitGallery> 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),
mSortMenu(nullptr)
{
updateGalleryWidth();
LLControlVariable* ctrl = gSavedSettings.getControl("InventoryFavoritesColorText");
if (ctrl)
{
mSavedSettingInvFavColor = ctrl->getSignal()->connect(boost::bind(&LLOutfitGallery::handleInvFavColorChange, this));
}
}
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<LLOutfitGallery>();
}
bool LLOutfitGallery::postBuild()
{
bool rv = LLOutfitListBase::postBuild();
mScrollPanel = getChild<LLScrollContainer>("gallery_scroll_panel");
mMessageTextBox = getChild<LLTextBox>("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<int>(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<S32> sort_by_name(gSavedSettings, "OutfitGallerySortOrder", 0);
switch (sort_by_name())
{
case 2:
// Sort by favorites - favorite items first, then alphabetically
if (item1->isFavorite() != item2->isFavorite())
{
return item1->isFavorite();
}
break;
case 1:
// Sort by images - items with non-default images first, then alphabetically
if (item1->isDefaultImage() != item2->isDefaultImage())
{
return item2->isDefaultImage();
}
break;
default:
// Sort alphabetically only
break;
}
// Final comparison is always alphabetical by name
std::string name1 = item1->getItemName();
std::string name2 = item2->getItemName();
return (LLStringUtil::compareDict(name1, name2) < 0);
}
void LLOutfitGallery::reArrangeRows(S32 row_diff)
{
std::vector<LLOutfitGalleryItem*> buf_items = mItems;
for (std::vector<LLOutfitGalleryItem*>::const_reverse_iterator it = buf_items.rbegin(); it != buf_items.rend(); ++it)
{
removeFromGalleryLast(*it);
}
for (std::vector<LLOutfitGalleryItem*>::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<LLOutfitGalleryItem*>::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;
}
void LLOutfitGallery::handleInvFavColorChange()
{
for (outfit_map_t::iterator iter = mOutfitMap.begin();
iter != mOutfitMap.end();
++iter)
{
if (!iter->second) continue;
LLOutfitGalleryItem* item = (LLOutfitGalleryItem*)iter->second;
// refresh font color
item->setOutfitFavorite(item->isFavorite());
}
}
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<LLOutfitGalleryItem*> 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, bool is_favorite)
{
LLOutfitGalleryItem::Params giparams;
LLOutfitGalleryItem* gitem = LLUICtrlFactory::create<LLOutfitGalleryItem>(giparams);
gitem->reshape(mItemWidth, mItemHeight);
gitem->setVisible(true);
gitem->setFollowsLeft();
gitem->setFollowsTop();
gitem->setOutfitName(name);
gitem->setOutfitFavorite(is_favorite);
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<LLPanel>(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<LLPanel>(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<LLPanel>(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;
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));
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);
item->setOutfitFavorite(cat->getIsFavorite());
}
}
}
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<LLOutfitGalleryItem> r("outfit_gallery_item");
bool LLOutfitGalleryItem::sColorSetInitialized = false;
LLUIColor LLOutfitGalleryItem::sDefaultTextColor;
LLUIColor LLOutfitGalleryItem::sDefaultFavoriteColor;
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");
if (!sColorSetInitialized)
{
sDefaultTextColor = LLUIColorTable::instance().getColor("White", LLColor4::white);
sDefaultFavoriteColor = LLUIColorTable::instance().getColor("InventoryFavoriteColor", LLColor4::white);
sColorSetInitialized = true;
}
}
LLOutfitGalleryItem::~LLOutfitGalleryItem()
{
}
bool LLOutfitGalleryItem::postBuild()
{
mPreviewIcon = getChild<LLIconCtrl>("preview_outfit");
setDefaultImage();
mOutfitNameText = getChild<LLTextBox>("outfit_name");
mOutfitWornText = getChild<LLTextBox>("outfit_worn_text");
mTextBgPanel = getChild<LLPanel>("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);
}
static LLUICachedControl<bool> draw_star("InventoryFavoritesUseStar", true);
if(mFavorite && draw_star())
{
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");
const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency();
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)
{
mOutfitNameText->setText(name);
mOutfitNameText->setToolTip(name);
mOutfitName = name;
}
void LLOutfitGalleryItem::setOutfitFavorite(bool is_favorite)
{
mFavorite = is_favorite;
static LLCachedControl<bool> use_color(gSavedSettings, "InventoryFavoritesColorText");
mOutfitNameText->setReadOnlyColor((mFavorite && use_color()) ? sDefaultFavoriteColor : sDefaultTextColor);
}
void LLOutfitGalleryItem::setOutfitWorn(bool value)
{
mWorn = value;
LLStringUtil::format_map_t worn_string_args;
std::string worn_string = getString("worn_string", worn_string_args);
mOutfitWornText->setReadOnlyColor(sDefaultTextColor.get());
mOutfitNameText->setReadOnlyColor(sDefaultTextColor.get());
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
static LLCachedControl<bool> use_color(gSavedSettings, "InventoryFavoritesColorText");
mOutfitNameText->setReadOnlyColor((mFavorite && use_color()) ? sDefaultFavoriteColor : sDefaultTextColor);
}
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<LLTabContainer>("appearance_tabs");
if (appearence_tabs && mUUID.notNull())
{
appearence_tabs->selectTabByName("outfitslist_tab");
LLPanel* panel = appearence_tabs->getCurrentPanel();
if (panel)
{
LLAccordionCtrl* accordion = panel->getChild<LLAccordionCtrl>("outfits_accordion");
LLOutfitsList* outfit_list = dynamic_cast<LLOutfitsList*>(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<LLViewerFetchedTexture> 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.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));
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("thumbnail", have_selection);
mMenu->setItemVisible("sort_folders_by_name", true);
LLOutfitListGearMenuBase::onUpdateItemsVisibility();
}
bool LLOutfitGalleryGearMenu::hasDefaultImage()
{
LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(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();
}
}
LLToggleableMenu* LLOutfitGallery::getSortMenu()
{
if (!mSortMenu)
{
mSortMenu = new LLOutfitGallerySortMenu(this);
}
return mSortMenu->getMenu();
}
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();
}
//////////////////// LLOutfitGallerySortMenu ////////////////////
LLOutfitGallerySortMenu::LLOutfitGallerySortMenu(LLOutfitListBase* parent_panel)
: mPanelHandle(parent_panel->getHandle())
{
LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
registrar.add("Sort.OnSort", boost::bind(&LLOutfitGallerySortMenu::onSort, this, _2));
enable_registrar.add("Sort.OnEnable", boost::bind(&LLOutfitGallerySortMenu::onEnable, this, _2));
mMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>(
"menu_outfit_gallery_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
llassert(mMenu);
}
LLToggleableMenu* LLOutfitGallerySortMenu::getMenu()
{
return mMenu;
}
void LLOutfitGallerySortMenu::updateItemsVisibility()
{
onUpdateItemsVisibility();
}
void LLOutfitGallerySortMenu::onUpdateItemsVisibility()
{
if (!mMenu) return;
}
bool LLOutfitGallerySortMenu::onEnable(LLSD::String param)
{
static LLCachedControl<S32> sort_order(gSavedSettings, "OutfitGallerySortOrder", 0);
if ("favorites_to_top" == param)
{
return sort_order == 2;
}
else if ("images_to_top" == param)
{
return sort_order == 1;
}
else if ("by_name" == param)
{
return sort_order == 0;
}
return false;
}
void LLOutfitGallerySortMenu::onSort(LLSD::String param)
{
S32 sort_order = gSavedSettings.getS32("OutfitGallerySortOrder");
S32 new_sort_order = 0;
if ("favorites_to_top" == param)
{
new_sort_order = 2;
}
else if ("images_to_top" == param)
{
new_sort_order = 1;
}
else if ("by_name" == param)
{
new_sort_order = 0;
}
if (sort_order == new_sort_order)
{
new_sort_order = sort_order ? 0 : 1;
}
gSavedSettings.setS32("OutfitGallerySortOrder", new_sort_order);
LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mPanelHandle.get());
if (gallery)
{
gallery->reArrangeRows();
}
}