/** * @author Rider Linden * @brief LLSettingsPicker class header file including related functions * * $LicenseInfo:firstyear=2018&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2018, 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" #include "llsettingspicker.h" #include "llcombobox.h" #include "llfiltereditor.h" #include "llfolderviewmodel.h" #include "llinventory.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" #include "llinventorymodelbackgroundfetch.h" #include "llinventoryobserver.h" #include "llinventorypanel.h" #include "llsettingsvo.h" #include "lldraghandle.h" #include "llviewercontrol.h" #include "llagent.h" //========================================================================= namespace { const std::string FLOATER_DEFINITION_XML("floater_settings_picker.xml"); const std::string FLT_INVENTORY_SEARCH("flt_inventory_search"); const std::string CMB_TRACK_SELECTION("track_selection"); const std::string PNL_INVENTORY("pnl_inventory"); const std::string PNL_COMBO("pnl_combo"); const std::string BTN_SELECT("btn_select"); const std::string BTN_CANCEL("btn_cancel"); const F32 CONTEXT_CONE_IN_ALPHA(0.0f); const F32 CONTEXT_CONE_OUT_ALPHA(1.0f); const F32 CONTEXT_FADE_TIME(0.08f); // strings in xml const std::string STR_TITLE_PREFIX = "pick title"; const std::string STR_TITLE_TRACK = "pick_track"; const std::string STR_TITLE_SETTINGS = "pick_settings"; const std::string STR_TRACK_WATER = "track_water"; const std::string STR_TRACK_GROUND = "track_ground"; const std::string STR_TRACK_SKY = "track_sky"; } //========================================================================= LLFloaterSettingsPicker::LLFloaterSettingsPicker(LLView * owner, LLUUID initial_item_id, const LLSD ¶ms): LLFloater(params), mOwnerHandle(), mActive(true), mContextConeOpacity(0.0f), mSettingItemID(initial_item_id), mTrackWater(true), mImmediateFilterPermMask(PERM_NONE) { mOwnerHandle = owner->getHandle(); buildFromFile(FLOATER_DEFINITION_XML); setCanMinimize(FALSE); } LLFloaterSettingsPicker::~LLFloaterSettingsPicker() { } //------------------------------------------------------------------------- BOOL LLFloaterSettingsPicker::postBuild() { if (!LLFloater::postBuild()) return FALSE; std::string prefix = getString(STR_TITLE_PREFIX); std::string label = getString(STR_TITLE_SETTINGS); setTitle(prefix + " " + label); mFilterEdit = getChild(FLT_INVENTORY_SEARCH); mFilterEdit->setCommitCallback([this](LLUICtrl*, const LLSD& param){ onFilterEdit(param.asString()); }); mInventoryPanel = getChild(PNL_INVENTORY); if (mInventoryPanel) { U32 filter_types = 0x0; filter_types |= 0x1 << LLInventoryType::IT_SETTINGS; mInventoryPanel->setFilterTypes(filter_types); mInventoryPanel->setFilterPermMask(mImmediateFilterPermMask); mInventoryPanel->setSelectCallback([this](const LLFloaterSettingsPicker::itemlist_t &items, bool useraction){ onSelectionChange(items, useraction); }); mInventoryPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); mInventoryPanel->setSuppressOpenItemAction(true); // Disable auto selecting first filtered item because it takes away // selection from the item set by LLTextureCtrl owning this floater. mInventoryPanel->getRootFolder()->setAutoSelectOverride(TRUE); // don't put keyboard focus on selected item, because the selection callback // will assume that this was user input if (!mSettingItemID.isNull()) { //todo: this is bad idea mInventoryPanel->setSelection(mSettingItemID, TAKE_FOCUS_NO); } } mNoCopySettingsSelected = FALSE; childSetAction(BTN_CANCEL, [this](LLUICtrl*, const LLSD& param){ onButtonCancel(); }); childSetAction(BTN_SELECT, [this](LLUICtrl*, const LLSD& param){ onButtonSelect(); }); // update permission filter once UI is fully initialized mSavedFolderState.setApply(FALSE); return TRUE; } void LLFloaterSettingsPicker::onClose(bool app_quitting) { if (app_quitting) return; mCloseSignal(); LLView *owner = mOwnerHandle.get(); if (owner) { owner->setFocus(TRUE); } } void LLFloaterSettingsPicker::setValue(const LLSD& value) { mSettingItemID = value.asUUID(); } LLSD LLFloaterSettingsPicker::getValue() const { return LLSD(mSettingItemID); } void LLFloaterSettingsPicker::setSettingsFilter(LLSettingsType::type_e type) { U64 filter = 0xFFFFFFFFFFFFFFFF; if (type != LLSettingsType::ST_NONE) { filter = static_cast(0x1) << static_cast(type); } bool day_cycle = (type != LLSettingsType::ST_WATER) && (type != LLSettingsType::ST_SKY); getChild(PNL_COMBO)->setVisible(day_cycle); std::string prefix = getString(STR_TITLE_PREFIX); std::string label; if (day_cycle) { label = getString(STR_TITLE_TRACK); } else { label = getString(STR_TITLE_SETTINGS); } setTitle(prefix + " " + label); mInventoryPanel->setFilterSettingsTypes(filter); } void LLFloaterSettingsPicker::draw() { LLView *owner = mOwnerHandle.get(); if (owner) { // draw cone of context pointing back to texture swatch LLRect owner_rect; owner->localRectToOtherView(owner->getLocalRect(), &owner_rect, this); LLRect local_rect = getLocalRect(); if (gFocusMgr.childHasKeyboardFocus(this) && owner->isInVisibleChain() && mContextConeOpacity > 0.001f) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLGLEnable(GL_CULL_FACE); gGL.begin(LLRender::QUADS); { gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity); gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity); gGL.vertex2i(local_rect.mRight, local_rect.mTop); gGL.vertex2i(local_rect.mLeft, local_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity); gGL.vertex2i(local_rect.mLeft, local_rect.mTop); gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity); gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity); gGL.vertex2i(local_rect.mRight, local_rect.mBottom); gGL.vertex2i(local_rect.mRight, local_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity); gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_OUT_ALPHA * mContextConeOpacity); gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); gGL.vertex2i(local_rect.mRight, local_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, CONTEXT_CONE_IN_ALPHA * mContextConeOpacity); gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); } gGL.end(); } } if (gFocusMgr.childHasMouseCapture(getDragHandle())) { mContextConeOpacity = lerp(mContextConeOpacity, gSavedSettings.getF32("PickerContextOpacity"), LLSmoothInterpolation::getInterpolant(CONTEXT_FADE_TIME)); } else { mContextConeOpacity = lerp(mContextConeOpacity, 0.f, LLSmoothInterpolation::getInterpolant(CONTEXT_FADE_TIME)); } LLFloater::draw(); } //========================================================================= void LLFloaterSettingsPicker::onFilterEdit(const std::string& search_string) { std::string upper_case_search_string = search_string; LLStringUtil::toUpper(upper_case_search_string); if (upper_case_search_string.empty()) { if (mInventoryPanel->getFilterSubString().empty()) { // current filter and new filter empty, do nothing return; } mSavedFolderState.setApply(TRUE); mInventoryPanel->getRootFolder()->applyFunctorRecursively(mSavedFolderState); // add folder with current item to list of previously opened folders LLOpenFoldersWithSelection opener; mInventoryPanel->getRootFolder()->applyFunctorRecursively(opener); mInventoryPanel->getRootFolder()->scrollToShowSelection(); } else if (mInventoryPanel->getFilterSubString().empty()) { // first letter in search term, save existing folder open state if (!mInventoryPanel->getFilter().isNotDefault()) { mSavedFolderState.setApply(FALSE); mInventoryPanel->getRootFolder()->applyFunctorRecursively(mSavedFolderState); } } mInventoryPanel->setFilterSubString(search_string); } void LLFloaterSettingsPicker::onSelectionChange(const LLFloaterSettingsPicker::itemlist_t &items, bool user_action) { bool track_picker_enabled = false; LLUUID asset_id; if (items.size()) { LLFolderViewItem* first_item = items.front(); mNoCopySettingsSelected = false; if (first_item) { LLItemBridge *bridge_model = dynamic_cast(first_item->getViewModelItem()); if (bridge_model && bridge_model->getItem()) { if (!bridge_model->isItemCopyable()) { mNoCopySettingsSelected = true; } setSettingsItemId(bridge_model->getItem()->getUUID(), false); asset_id = bridge_model->getItem()->getAssetUUID(); mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? if (user_action) { mChangeIDSignal(mSettingItemID); } if (bridge_model->getSettingsType() == LLSettingsType::ST_DAYCYCLE && !mNoCopySettingsSelected) { track_picker_enabled = true; } } } } getChild(CMB_TRACK_SELECTION)->setEnabled(track_picker_enabled && mSettingAssetID == asset_id); if (track_picker_enabled && asset_id.notNull() && mSettingAssetID != asset_id) { LLUUID item_id = mSettingItemID; LLHandle handle = getHandle(); LLSettingsVOBase::getSettingsAsset(asset_id, [item_id, handle](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { LLFloaterSettingsPicker::onAssetLoadedCb(handle, item_id, asset_id, settings, status); }); } } void LLFloaterSettingsPicker::onAssetLoadedCb(LLHandle handle, LLUUID item_id, LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status) { if (handle.isDead() || status) { return; } LLFloaterSettingsPicker *picker = static_cast(handle.get()); if (picker->mSettingItemID != item_id) { return; } picker->onAssetLoaded(asset_id, settings); } void LLFloaterSettingsPicker::onAssetLoaded(LLUUID asset_id, LLSettingsBase::ptr_t settings) { LLComboBox* track_selection = getChild(CMB_TRACK_SELECTION); track_selection->clear(); track_selection->removeall(); LLSettingsDay::ptr_t pday = std::dynamic_pointer_cast(settings); if (mTrackWater) { track_selection->add(getString(STR_TRACK_WATER), LLSD::Integer(LLSettingsDay::TRACK_WATER), ADD_TOP, true); } else { // track 1 always present track_selection->add(getString(STR_TRACK_GROUND), LLSD::Integer(LLSettingsDay::TRACK_GROUND_LEVEL), ADD_TOP, true); LLUIString formatted_label = getString(STR_TRACK_SKY); for (int i = 2; i < LLSettingsDay::TRACK_MAX; i++) { if (!pday->isTrackEmpty(i)) { formatted_label.setArg("[NUM]", llformat("%d", i)); track_selection->add(formatted_label.getString(), LLSD::Integer(i), ADD_TOP, true); } } } mSettingAssetID = asset_id; track_selection->setEnabled(true); } void LLFloaterSettingsPicker::onButtonCancel() { closeFloater(); } void LLFloaterSettingsPicker::onButtonSelect() { if (mCommitSignal) { LLSD res; res["ItemId"] = mSettingItemID; res["Track"] = getChild(CMB_TRACK_SELECTION)->getValue(); (*mCommitSignal)(this, res); } closeFloater(); } BOOL LLFloaterSettingsPicker::handleDoubleClick(S32 x, S32 y, MASK mask) { BOOL result = FALSE; if (mSettingItemID.notNull() && mInventoryPanel) { S32 inventory_x = x - mInventoryPanel->getRect().mLeft; S32 inventory_y = y - mInventoryPanel->getRect().mBottom; if (mInventoryPanel->parentPointInView(inventory_x, inventory_y)) { // make sure item (not folder) is selected LLFolderViewItem* item_viewp = mInventoryPanel->getItemByID(mSettingItemID); if (item_viewp && item_viewp->getIsCurSelection()) { LLRect target_rect; item_viewp->localRectToOtherView(item_viewp->getLocalRect(), &target_rect, this); if (target_rect.pointInRect(x, y)) { // Quick-apply if (mCommitSignal) { LLSD res; res["ItemId"] = mSettingItemID; res["Track"] = getChild(CMB_TRACK_SELECTION)->getValue(); (*mCommitSignal)(this, res); } closeFloater(); } } // hit inside panel on free place or (de)unselected item, double click should do nothing result = TRUE; } } if (!result) { result = LLFloater::handleDoubleClick(x, y, mask); } return result; } BOOL LLFloaterSettingsPicker::handleKeyHere(KEY key, MASK mask) { if ((key == KEY_RETURN) && (mask == MASK_NONE)) { LLFolderViewItem* item_viewp = mInventoryPanel->getItemByID(mSettingItemID); if (item_viewp && item_viewp->getIsCurSelection()) { // Quick-apply if (mCommitSignal) { LLSD res; res["ItemId"] = mSettingItemID; res["Track"] = getChild(CMB_TRACK_SELECTION)->getValue(); (*mCommitSignal)(this, res); } closeFloater(); return TRUE; } } return LLFloater::handleKeyHere(key, mask); } //========================================================================= void LLFloaterSettingsPicker::setActive(bool active) { mActive = active; } void LLFloaterSettingsPicker::setSettingsItemId(const LLUUID &settings_id, bool set_selection) { if (mSettingItemID != settings_id && mActive) { mNoCopySettingsSelected = false; mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? mSettingItemID = settings_id; if (mSettingItemID.isNull()) { mInventoryPanel->getRootFolder()->clearSelection(); } else { LLInventoryItem* itemp = gInventory.getItem(settings_id); if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) { mNoCopySettingsSelected = true; } } if (set_selection) { mInventoryPanel->setSelection(settings_id, TAKE_FOCUS_NO); } } } LLInventoryItem* LLFloaterSettingsPicker::findItem(const LLUUID& asset_id, bool copyable_only, bool ignore_library) { LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; LLAssetIDMatches asset_id_matches(asset_id); gInventory.collectDescendentsIf(LLUUID::null, cats, items, LLInventoryModel::INCLUDE_TRASH, asset_id_matches); if (items.size()) { // search for copyable version first for (S32 i = 0; i < items.size(); i++) { LLInventoryItem* itemp = items[i]; LLPermissions item_permissions = itemp->getPermissions(); if (item_permissions.allowCopyBy(gAgent.getID(), gAgent.getGroupID())) { if(!ignore_library || !gInventory.isObjectDescendentOf(itemp->getUUID(),gInventory.getLibraryRootFolderID())) { return itemp; } } } // otherwise just return first instance, unless copyable requested if (copyable_only) { return nullptr; } else { if(!ignore_library || !gInventory.isObjectDescendentOf(items[0]->getUUID(),gInventory.getLibraryRootFolderID())) { return items[0]; } } } return nullptr; }