diff options
Diffstat (limited to 'indra')
72 files changed, 5713 insertions, 545 deletions
diff --git a/indra/cmake/JsonCpp.cmake b/indra/cmake/JsonCpp.cmake index 0aab2d6634..d6d05af315 100755 --- a/indra/cmake/JsonCpp.cmake +++ b/indra/cmake/JsonCpp.cmake @@ -14,7 +14,7 @@ else (USESYSTEMLIBS) debug json_vc100debug_libmt.lib optimized json_vc100_libmt) elseif (DARWIN) - set(JSONCPP_LIBRARIES libjson_linux-gcc-4.0.1_libmt.a) + set(JSONCPP_LIBRARIES libjson_darwin_libmt.a) elseif (LINUX) set(JSONCPP_LIBRARIES libjson_linux-gcc-4.1.3_libmt.a) endif (WINDOWS) diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index 23bf6da1f9..86aca77de8 100755 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -96,6 +96,10 @@ LLFolderDictionary::LLFolderDictionary() addEntry(LLFolderType::FT_OUTBOX, new FolderEntry("outbox", TRUE)); addEntry(LLFolderType::FT_BASIC_ROOT, new FolderEntry("basic_rt", TRUE)); + + addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new FolderEntry("merchant", FALSE)); + addEntry(LLFolderType::FT_MARKETPLACE_STOCK, new FolderEntry("stock", FALSE)); + addEntry(LLFolderType::FT_MARKETPLACE_VERSION, new FolderEntry("version", FALSE)); addEntry(LLFolderType::FT_NONE, new FolderEntry("-1", FALSE)); }; diff --git a/indra/llinventory/llfoldertype.h b/indra/llinventory/llfoldertype.h index a0c847914f..515bb05a3f 100755..100644 --- a/indra/llinventory/llfoldertype.h +++ b/indra/llinventory/llfoldertype.h @@ -87,6 +87,10 @@ public: FT_BASIC_ROOT = 52, + FT_MARKETPLACE_LISTINGS = 53, + FT_MARKETPLACE_STOCK = 54, + FT_MARKETPLACE_VERSION = 55, // Note: We actually *never* create folders with that type. This is used for icon override only. + FT_COUNT, FT_NONE = -1 diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 200116337d..a8c3b86192 100755 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -572,6 +572,17 @@ void LLHTTPClient::patch( request(url, HTTP_PATCH, new LLSDInjector(body), responder, timeout, headers); } +void LLHTTPClient::putRaw( + const std::string& url, + const U8* data, + S32 size, + ResponderPtr responder, + const LLSD& headers, + const F32 timeout) +{ + request(url, HTTP_PUT, new RawInjector(data, size), responder, timeout, headers); +} + void LLHTTPClient::post( const std::string& url, const LLSD& body, diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index b18258fd7b..fd48b4a743 100755 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -79,6 +79,14 @@ public: ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); + static void putRaw( + const std::string& url, + const U8* data, + S32 size, + ResponderPtr responder, + const LLSD& headers = LLSD(), + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); + static void patch( const std::string& url, diff --git a/indra/llui/llcommandmanager.cpp b/indra/llui/llcommandmanager.cpp index 74ef8dd0c3..3e159365e5 100755 --- a/indra/llui/llcommandmanager.cpp +++ b/indra/llui/llcommandmanager.cpp @@ -131,6 +131,25 @@ LLCommand * LLCommandManager::getCommand(const LLCommandId& commandId) return command_match; } +LLCommand * LLCommandManager::getCommand(const std::string& name) +{ + LLCommand * command_match = NULL; + + CommandVector::const_iterator it = mCommands.begin(); + + while (it != mCommands.end()) + { + if ((*it)->name() == name) + { + command_match = *it; + break; + } + it++; + } + + return command_match; +} + void LLCommandManager::addCommand(LLCommand * command) { LLCommandId command_id = command->id(); diff --git a/indra/llui/llcommandmanager.h b/indra/llui/llcommandmanager.h index ff5a8a3257..f2f2145953 100755 --- a/indra/llui/llcommandmanager.h +++ b/indra/llui/llcommandmanager.h @@ -190,6 +190,7 @@ public: U32 commandCount() const; LLCommand * getCommand(U32 commandIndex); LLCommand * getCommand(const LLCommandId& commandId); + LLCommand * getCommand(const std::string& name); static bool load(); diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index 474b545f00..72422cf427 100755 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -947,7 +947,6 @@ void LLFolderView::cut() if (listener) { listener->cutToClipboard(); - listener->removeItem(); } } diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 6dd6f94d02..f2bb71c53b 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -104,7 +104,8 @@ LLFolderViewItem::Params::Params() item_height("item_height"), item_top_pad("item_top_pad"), creation_date(), - allow_open("allow_open", true), + allow_wear("allow_wear", true), + allow_drop("allow_drop", true), font_color("font_color"), font_highlight_color("font_highlight_color"), left_pad("left_pad", 0), @@ -137,7 +138,8 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mRoot(p.root), mViewModelItem(p.listener), mIsMouseOverTitle(false), - mAllowOpen(p.allow_open), + mAllowWear(p.allow_wear), + mAllowDrop(p.allow_drop), mFontColor(p.font_color), mFontHighlightColor(p.font_highlight_color), mLeftPad(p.left_pad), @@ -471,7 +473,7 @@ void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags) void LLFolderViewItem::openItem( void ) { - if (mAllowOpen) + if (mAllowWear || !getViewModelItem()->isItemWearable()) { getViewModelItem()->openItem(); } @@ -1494,12 +1496,14 @@ void LLFolderViewFolder::destroyView() while (!mItems.empty()) { LLFolderViewItem *itemp = mItems.back(); + mItems.pop_back(); itemp->destroyView(); // LLFolderViewItem::destroyView() removes entry from mItems } while (!mFolders.empty()) { LLFolderViewFolder *folderp = mFolders.back(); + mFolders.pop_back(); folderp->destroyView(); // LLFolderVievFolder::destroyView() removes entry from mFolders } @@ -1797,9 +1801,16 @@ BOOL LLFolderViewFolder::handleDragAndDropToThisFolder(MASK mask, EAcceptance* accept, std::string& tooltip_msg) { + if (!mAllowDrop) + { + *accept = ACCEPT_NO; + tooltip_msg = LLTrans::getString("TooltipOutboxCannotDropOnRoot"); + return TRUE; + } + BOOL accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg); - - if (accepted) + + if (accepted) { mDragAndDropTarget = TRUE; *accept = ACCEPT_YES_MULTI; diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 0cd20a0f2d..4c2bb09cc5 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -59,7 +59,8 @@ public: item_top_pad; Optional<time_t> creation_date; - Optional<bool> allow_open; + Optional<bool> allow_wear; + Optional<bool> allow_drop; Optional<LLUIColor> font_color; Optional<LLUIColor> font_highlight_color; @@ -117,7 +118,8 @@ protected: mIsCurSelection, mDragAndDropTarget, mIsMouseOverTitle, - mAllowOpen, + mAllowWear, + mAllowDrop, mSelectPending; LLUIColor mFontColor; diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index f6550eae42..d60e36183b 100755 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -156,6 +156,8 @@ public: virtual void openItem( void ) = 0; virtual void closeItem( void ) = 0; virtual void selectItem(void) = 0; + + virtual BOOL isItemWearable() const { return FALSE; } virtual BOOL isItemRenameable() const = 0; virtual BOOL renameItem(const std::string& new_name) = 0; @@ -169,7 +171,7 @@ public: virtual BOOL isItemCopyable() const = 0; virtual BOOL copyToClipboard() const = 0; - virtual BOOL cutToClipboard() const = 0; + virtual BOOL cutToClipboard() = 0; virtual BOOL isClipboardPasteable() const = 0; virtual void pasteFromClipboard() = 0; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index e8f4144e70..98a7cf8ec3 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -255,6 +255,7 @@ set(viewer_SOURCE_FILES llfloaterlagmeter.cpp llfloaterland.cpp llfloaterlandholdings.cpp + llfloatermarketplacelistings.cpp llfloatermap.cpp llfloatermediasettings.cpp llfloatermemleak.cpp @@ -864,6 +865,7 @@ set(viewer_HEADER_FILES llfloaterland.h llfloaterlandholdings.h llfloatermap.h + llfloatermarketplacelistings.h llfloatermediasettings.h llfloatermemleak.h llfloatermodelpreview.h diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index 7b329e2092..2cd6638042 100755 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -118,6 +118,18 @@ tooltip_ref="Command_Marketplace_Tooltip" execute_function="Avatar.OpenMarketplace" /> + <command name="marketplacelistings" + available_in_toybox="true" + icon="Command_MktListings_Icon" + label_ref="Command_MarketplaceListings_Label" + tooltip_ref="Command_MarketplaceListings_Tooltip" + execute_function="Marketplace.Toggle" + execute_parameters="marketplace" + is_enabled_function="Marketplace.Enabled" + is_enabled_parameters="marketplace" + is_running_function="Floater.IsOpen" + is_running_parameters="marketplace_listings" + /> <command name="minimap" available_in_toybox="true" icon="Command_MiniMap_Icon" diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 94d3c8a59f..4a106c08e4 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4654,17 +4654,6 @@ <key>Value</key> <integer>0</integer> </map> - <key>InventoryDisplayOutbox</key> - <map> - <key>Comment</key> - <string>Override merchant inventory outbox display</string> - <key>Persist</key> - <integer>0</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> <key>InventoryInboxToggleState</key> <map> <key>Comment</key> @@ -4687,6 +4676,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>InventoryOutboxDisplayBoth</key> + <map> + <key>Comment</key> + <string>Show the legacy Merchant Outbox UI as well as the Marketplace Listings UI</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>InventoryOutboxLogging</key> <map> <key>Comment</key> @@ -4718,7 +4718,7 @@ <key>Type</key> <string>U32</string> <key>Value</key> - <integer>21</integer> + <integer>20</integer> </map> <key>InventoryOutboxMaxFolderDepth</key> <map> @@ -5601,6 +5601,17 @@ <key>Value</key> <string /> </map> + <key>MarketplaceListingsLogging</key> + <map> + <key>Comment</key> + <string>Enable debug output associated with the Marketplace Listings (SLM) API.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>MarketplaceURL</key> <map> <key>Comment</key> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 5cb03cd2ae..af7b07c26e 100755 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -3875,6 +3875,12 @@ void LLAgent::handleTeleportFinished() LLNotificationsUtil::add("PreferredMaturityChanged", args); mIsMaturityRatingChangingDuringTeleport = false; } + + // Init SLM Marketplace connection so we know which UI should be used for the user as a merchant + // Note: Eventually, all merchant will be migrated to the new SLM system and there will be no reason to show the old UI at all. + // Note: Some regions will not support the SLM cap for a while so we need to do that check for each teleport. + // *TODO : Suppress that line from here once the whole grid migrated to SLM and move it to idle_startup() (llstartup.cpp) + check_merchant_status(); } void LLAgent::handleTeleportFailed() diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 56e1a26709..3d69cd6f5b 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -86,7 +86,7 @@ public: virtual void move( LLFolderViewModelItem* parent_listener ) { } virtual BOOL isItemCopyable() const { return FALSE; } virtual BOOL copyToClipboard() const { return FALSE; } - virtual BOOL cutToClipboard() const { return FALSE; } + virtual BOOL cutToClipboard() { return FALSE; } virtual BOOL isClipboardPasteable() const { return FALSE; } virtual void pasteFromClipboard() { } virtual void pasteLinkFromClipboard() { } diff --git a/indra/newview/llfloatermarketplacelistings.cpp b/indra/newview/llfloatermarketplacelistings.cpp new file mode 100755 index 0000000000..00fd87fece --- /dev/null +++ b/indra/newview/llfloatermarketplacelistings.cpp @@ -0,0 +1,774 @@ +/** + * @file llfloatermarketplacelistings.cpp + * @brief Implementation of the marketplace listings floater and panels + * @author merov@lindenlab.com + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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 "llfloatermarketplacelistings.h" + +#include "llfloaterreg.h" +#include "llfiltereditor.h" +#include "llfolderview.h" +#include "llinventorybridge.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "llinventoryfunctions.h" +#include "llmarketplacefunctions.h" +#include "llnotificationhandler.h" +#include "llnotificationmanager.h" +#include "llnotificationsutil.h" +#include "llsidepaneliteminfo.h" +#include "lltextbox.h" +#include "lltrans.h" + +///---------------------------------------------------------------------------- +/// LLPanelMarketplaceListings +///---------------------------------------------------------------------------- + +static LLPanelInjector<LLPanelMarketplaceListings> t_panel_status("llpanelmarketplacelistings"); + +LLPanelMarketplaceListings::LLPanelMarketplaceListings() +: mRootFolder(NULL) +, mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME) +, mFilterType(LLInventoryFilter::FILTERTYPE_NONE) +{ + mCommitCallbackRegistrar.add("Marketplace.ViewSort.Action", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemClicked, this, _2)); + mEnableCallbackRegistrar.add("Marketplace.ViewSort.CheckItem", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemCheck, this, _2)); +} + +BOOL LLPanelMarketplaceListings::postBuild() +{ + childSetAction("add_btn", boost::bind(&LLPanelMarketplaceListings::onAddButtonClicked, this)); + childSetAction("audit_btn", boost::bind(&LLPanelMarketplaceListings::onAuditButtonClicked, this)); + + mFilterEditor = getChild<LLFilterEditor>("filter_editor"); + mFilterEditor->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onFilterEdit, this, _2)); + + mAuditBtn = getChild<LLButton>("audit_btn"); + mAuditBtn->setEnabled(FALSE); + + return LLPanel::postBuild(); +} + +void LLPanelMarketplaceListings::buildAllPanels() +{ + // Build the All panel first + LLInventoryPanel* panel_all_items; + panel_all_items = buildInventoryPanel("All Items", "panel_marketplace_listings_inventory.xml"); + panel_all_items->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel_all_items->getFilter().markDefault(); + + // Build the other panels + LLInventoryPanel* panel; + panel = buildInventoryPanel("Active Items", "panel_marketplace_listings_listed.xml"); + panel->getFilter().setFilterMarketplaceActiveFolders(); + panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel->getFilter().markDefault(); + panel = buildInventoryPanel("Inactive Items", "panel_marketplace_listings_unlisted.xml"); + panel->getFilter().setFilterMarketplaceInactiveFolders(); + panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel->getFilter().markDefault(); + panel = buildInventoryPanel("Unassociated Items", "panel_marketplace_listings_unassociated.xml"); + panel->getFilter().setFilterMarketplaceUnassociatedFolders(); + panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel->getFilter().markDefault(); + + // Set the tab panel + LLTabContainer* tabs_panel = getChild<LLTabContainer>("marketplace_filter_tabs"); + tabs_panel->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onTabChange, this)); + tabs_panel->selectTabPanel(panel_all_items); // All panel selected by default + mRootFolder = panel_all_items->getRootFolder(); // Keep the root of the all panel +} + +LLInventoryPanel* LLPanelMarketplaceListings::buildInventoryPanel(const std::string& childname, const std::string& filename) +{ + LLTabContainer* tabs_panel = getChild<LLTabContainer>("marketplace_filter_tabs"); + LLInventoryPanel* panel = LLUICtrlFactory::createFromFile<LLInventoryPanel>(filename, tabs_panel, LLInventoryPanel::child_registry_t::instance()); + llassert(panel != NULL); + + // Set sort order and callbacks + panel = getChild<LLInventoryPanel>(childname); + panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME); + panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2)); + + return panel; +} + +void LLPanelMarketplaceListings::onFilterEdit(const std::string& search_string) +{ + // Find active panel + LLInventoryPanel* panel = (LLInventoryPanel*)getChild<LLTabContainer>("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + // Save filter string (needed when switching tabs) + mFilterSubString = search_string; + // Set filter string on active panel + panel->setFilterSubString(mFilterSubString); + } +} + +void LLPanelMarketplaceListings::draw() +{ + if (LLMarketplaceData::instance().checkDirtyCount()) + { + update_all_marketplace_count(); + } + + // Get the audit button enabled only after the whole inventory is fetched + if (!mAuditBtn->getEnabled()) + { + mAuditBtn->setEnabled(LLInventoryModelBackgroundFetch::instance().isEverythingFetched()); + } + + LLPanel::draw(); +} + +void LLPanelMarketplaceListings::onSelectionChange(LLInventoryPanel *panel, const std::deque<LLFolderViewItem*>& items, BOOL user_action) +{ + panel->onSelectionChange(items, user_action); +} + +void LLPanelMarketplaceListings::onTabChange() +{ + // Find active panel + LLInventoryPanel* panel = (LLInventoryPanel*)getChild<LLTabContainer>("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + // If the panel doesn't allow drop on root, it doesn't allow the creation of new folder on root either + LLButton* add_btn = getChild<LLButton>("add_btn"); + add_btn->setEnabled(panel->getAllowDropOnRoot()); + + // Set filter string on active panel + panel->setFilterSubString(mFilterSubString); + } +} + +void LLPanelMarketplaceListings::onAddButtonClicked() +{ + // Find active panel + LLInventoryPanel* panel = (LLInventoryPanel*)getChild<LLTabContainer>("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + llassert(marketplacelistings_id.notNull()); + LLFolderType::EType preferred_type = LLFolderType::lookup("category"); + LLUUID category = gInventory.createNewCategory(marketplacelistings_id, preferred_type, LLStringUtil::null); + gInventory.notifyObservers(); + panel->setSelectionByID(category, TRUE); + panel->getRootFolder()->setNeedsAutoRename(TRUE); + } +} + +void LLPanelMarketplaceListings::onAuditButtonClicked() +{ + LLSD data(LLSD::emptyMap()); + LLFloaterReg::showInstance("marketplace_validation", data); +} + +void LLPanelMarketplaceListings::onViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + // Sort options + if (chosen_item == "sort_by_stock_amount") + { + mSortOrder = (mSortOrder == LLInventoryFilter::SO_FOLDERS_BY_NAME ? LLInventoryFilter::SO_FOLDERS_BY_WEIGHT : LLInventoryFilter::SO_FOLDERS_BY_NAME); + // Set each panel with that sort order + LLTabContainer* tabs_panel = getChild<LLTabContainer>("marketplace_filter_tabs"); + LLInventoryPanel* panel = (LLInventoryPanel*)tabs_panel->getPanelByName("All Items"); + panel->setSortOrder(mSortOrder); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Active Items"); + panel->setSortOrder(mSortOrder); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Inactive Items"); + panel->setSortOrder(mSortOrder); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Unassociated Items"); + panel->setSortOrder(mSortOrder); + } +} + +bool LLPanelMarketplaceListings::onViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "sort_by_stock_amount") + return mSortOrder == LLInventoryFilter::SO_FOLDERS_BY_WEIGHT; + return false; +} + +///---------------------------------------------------------------------------- +/// LLMarketplaceListingsAddedObserver helper class +///---------------------------------------------------------------------------- + +class LLMarketplaceListingsAddedObserver : public LLInventoryCategoryAddedObserver +{ +public: + LLMarketplaceListingsAddedObserver(LLFloaterMarketplaceListings * marketplace_listings_floater) + : LLInventoryCategoryAddedObserver() + , mMarketplaceListingsFloater(marketplace_listings_floater) + { + } + + void done() + { + for (cat_vec_t::iterator it = mAddedCategories.begin(); it != mAddedCategories.end(); ++it) + { + LLViewerInventoryCategory* added_category = *it; + + LLFolderType::EType added_category_type = added_category->getPreferredType(); + + if (added_category_type == LLFolderType::FT_MARKETPLACE_LISTINGS) + { + mMarketplaceListingsFloater->initializeMarketPlace(); + } + } + } + +private: + LLFloaterMarketplaceListings * mMarketplaceListingsFloater; +}; + +///---------------------------------------------------------------------------- +/// LLFloaterMarketplaceListings +///---------------------------------------------------------------------------- + +LLFloaterMarketplaceListings::LLFloaterMarketplaceListings(const LLSD& key) +: LLFloater(key) +, mCategoriesObserver(NULL) +, mCategoryAddedObserver(NULL) +, mRootFolderId(LLUUID::null) +, mInventoryStatus(NULL) +, mInventoryInitializationInProgress(NULL) +, mInventoryPlaceholder(NULL) +, mInventoryText(NULL) +, mInventoryTitle(NULL) +, mPanelListings(NULL) +, mPanelListingsSet(false) +{ +} + +LLFloaterMarketplaceListings::~LLFloaterMarketplaceListings() +{ + if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver)) + { + gInventory.removeObserver(mCategoriesObserver); + } + delete mCategoriesObserver; + + if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) + { + gInventory.removeObserver(mCategoryAddedObserver); + } + delete mCategoryAddedObserver; +} + +BOOL LLFloaterMarketplaceListings::postBuild() +{ + mInventoryStatus = getChild<LLTextBox>("marketplace_status"); + mInventoryInitializationInProgress = getChild<LLView>("initialization_progress_indicator"); + mInventoryPlaceholder = getChild<LLView>("marketplace_listings_inventory_placeholder_panel"); + mInventoryText = mInventoryPlaceholder->getChild<LLTextBox>("marketplace_listings_inventory_placeholder_text"); + mInventoryTitle = mInventoryPlaceholder->getChild<LLTextBox>("marketplace_listings_inventory_placeholder_title"); + + mPanelListings = static_cast<LLPanelMarketplaceListings*>(getChild<LLUICtrl>("panel_marketplace_listing")); + + LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLFloaterMarketplaceListings::onFocusReceived, this)); + + // Observe category creation to catch marketplace listings creation (moot if already existing) + mCategoryAddedObserver = new LLMarketplaceListingsAddedObserver(this); + gInventory.addObserver(mCategoryAddedObserver); + + // Debug : fetch aggressively so we can create test data right onOpen() + fetchContents(); + + return TRUE; +} + +void LLFloaterMarketplaceListings::onClose(bool app_quitting) +{ +} + +void LLFloaterMarketplaceListings::onOpen(const LLSD& key) +{ + // + // Initialize the Market Place or go update the marketplace listings + // + if (LLMarketplaceData::instance().getSLMStatus() <= MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE) + { + initializeMarketPlace(); + } + else + { + updateView(); + } +} + +void LLFloaterMarketplaceListings::onFocusReceived() +{ + updateView(); +} + +void LLFloaterMarketplaceListings::fetchContents() +{ + if (mRootFolderId.notNull()) + { + LLInventoryModelBackgroundFetch::instance().start(mRootFolderId); + // Get all the SLM Listings + LLMarketplaceData::instance().getSLMListings(); + } +} + +void LLFloaterMarketplaceListings::setRootFolder() +{ + if (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) + { + // If we are *not* a merchant or we have no market place connection established yet, do nothing + return; + } + + // We are a merchant. Get the Marketplace listings folder, create it if needs be. + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, true); + if (marketplacelistings_id.isNull()) + { + // We should never get there unless the inventory fails badly + LL_ERRS("SLM") << "Inventory problem: failure to create the marketplace listings folder for a merchant!" << LL_ENDL; + return; + } + + // No longer need to observe new category creation + if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) + { + gInventory.removeObserver(mCategoryAddedObserver); + delete mCategoryAddedObserver; + mCategoryAddedObserver = NULL; + } + llassert(!mCategoryAddedObserver); + + if (marketplacelistings_id == mRootFolderId) + { + LL_WARNS("SLM") << "Inventory warning: Marketplace listings folder already set" << LL_ENDL; + return; + } + + mRootFolderId = marketplacelistings_id; +} + +void LLFloaterMarketplaceListings::setPanels() +{ + if (mRootFolderId.isNull()) + { + return; + } + + // Consolidate Marketplace listings + // We shouldn't have to do that but with a client/server system relying on a "well known folder" convention, + // things get messy and conventions get broken down eventually + gInventory.consolidateForType(mRootFolderId, LLFolderType::FT_MARKETPLACE_LISTINGS); + + // Now that we do have a non NULL root, we can build the inventory panels + mPanelListings->buildAllPanels(); + + // Create observer for marketplace listings modifications + if (!mCategoriesObserver) + { + mCategoriesObserver = new LLInventoryCategoriesObserver(); + llassert(mCategoriesObserver); + gInventory.addObserver(mCategoriesObserver); + mCategoriesObserver->addCategory(mRootFolderId, boost::bind(&LLFloaterMarketplaceListings::onChanged, this)); + } + + // Get the content of the marketplace listings folder + fetchContents(); + + // Flag that this is done + mPanelListingsSet = true; +} + +void LLFloaterMarketplaceListings::initializeMarketPlace() +{ + LLMarketplaceData::instance().initializeSLM(boost::bind(&LLFloaterMarketplaceListings::updateView, this)); +} + +S32 LLFloaterMarketplaceListings::getFolderCount() +{ + if (mPanelListings && mRootFolderId.notNull()) + { + LLInventoryModel::cat_array_t * cats; + LLInventoryModel::item_array_t * items; + gInventory.getDirectDescendentsOf(mRootFolderId, cats, items); + + return (cats->size() + items->size()); + } + else + { + return 0; + } +} + +void LLFloaterMarketplaceListings::setStatusString(const std::string& statusString) +{ + mInventoryStatus->setText(statusString); +} + +void LLFloaterMarketplaceListings::updateView() +{ + U32 mkt_status = LLMarketplaceData::instance().getSLMStatus(); + + // Get or create the root folder if we are a merchant and it hasn't been done already + if (mRootFolderId.isNull() && (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT)) + { + setRootFolder(); + } + + // Update the bottom initializing status and progress dial + if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING) + { + setStatusString(getString("MarketplaceListingsInitializing")); + mInventoryInitializationInProgress->setVisible(true); + } + else + { + setStatusString(""); + mInventoryInitializationInProgress->setVisible(false); + } + + // Update the middle portion : tabs or messages + if (getFolderCount() > 0) + { + if (!mPanelListingsSet) + { + // We need to rebuild the tabs cleanly the first time we make them visible + setPanels(); + } + mPanelListings->setVisible(TRUE); + mInventoryPlaceholder->setVisible(FALSE); + } + else + { + mPanelListings->setVisible(FALSE); + mInventoryPlaceholder->setVisible(TRUE); + + std::string text; + std::string title; + std::string tooltip; + + const LLSD& subs = getMarketplaceStringSubstitutions(); + + // Update the top message or flip to the tabs and folders view + // *TODO : check those messages and create better appropriate ones in strings.xml + if (mRootFolderId.notNull()) + { + // "Marketplace listings is empty!" message strings + text = LLTrans::getString("InventoryMarketplaceListingsNoItems", subs); + title = LLTrans::getString("InventoryMarketplaceListingsNoItemsTitle"); + tooltip = LLTrans::getString("InventoryMarketplaceListingsNoItemsTooltip"); + } + else if (mkt_status <= MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING) + { + // "Initializing!" message strings + text = LLTrans::getString("InventoryOutboxInitializing", subs); + title = LLTrans::getString("InventoryOutboxInitializingTitle"); + tooltip = LLTrans::getString("InventoryOutboxInitializingTooltip"); + } + else if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT) + { + // "Not a merchant!" message strings + text = LLTrans::getString("InventoryOutboxNotMerchant", subs); + title = LLTrans::getString("InventoryOutboxNotMerchantTitle"); + tooltip = LLTrans::getString("InventoryOutboxNotMerchantTooltip"); + } + else + { + // "Errors!" message strings + text = LLTrans::getString("InventoryOutboxError", subs); + title = LLTrans::getString("InventoryOutboxErrorTitle"); + tooltip = LLTrans::getString("InventoryOutboxErrorTooltip"); + } + + mInventoryText->setValue(text); + mInventoryTitle->setValue(title); + mInventoryPlaceholder->getParent()->setToolTip(tooltip); + } +} + +bool LLFloaterMarketplaceListings::isAccepted(EAcceptance accept) +{ + return (accept >= ACCEPT_YES_COPY_SINGLE); +} + +BOOL LLFloaterMarketplaceListings::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // If there's no panel to accept drops or no existing marketplace listings folder, we refuse all drop + if (!mPanelListings || mRootFolderId.isNull()) + { + return FALSE; + } + + // Pass to the children + LLView * handled_view = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + BOOL handled = (handled_view != NULL); + + // If no one handled it or it was not accepted, we try to accept it at the floater level as if it was dropped on the + // marketplace listings root folder + if (!handled || !isAccepted(*accept)) + { + if (!mPanelListings->getVisible() && mRootFolderId.notNull()) + { + if (!mPanelListingsSet) + { + setPanels(); + } + LLFolderView* root_folder = mPanelListings->getRootFolder(); + handled = root_folder->handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + } + + return handled; +} + +BOOL LLFloaterMarketplaceListings::handleHover(S32 x, S32 y, MASK mask) +{ + return LLFloater::handleHover(x, y, mask); +} + +void LLFloaterMarketplaceListings::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLFloater::onMouseLeave(x, y, mask); +} + +void LLFloaterMarketplaceListings::onChanged() +{ + LLViewerInventoryCategory* category = gInventory.getCategory(mRootFolderId); + if (mRootFolderId.notNull() && category) + { + updateView(); + } + else + { + // Invalidate the marketplace listings data + mRootFolderId.setNull(); + } +} + +//----------------------------------------------------------------------------- +// LLFloaterAssociateListing +//----------------------------------------------------------------------------- + +LLFloaterAssociateListing::LLFloaterAssociateListing(const LLSD& key) +: LLFloater(key) +, mUUID() +{ +} + +LLFloaterAssociateListing::~LLFloaterAssociateListing() +{ + gFocusMgr.releaseFocusIfNeeded( this ); +} + +BOOL LLFloaterAssociateListing::postBuild() +{ + getChild<LLButton>("OK")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::apply, this, TRUE)); + getChild<LLButton>("Cancel")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::cancel, this)); + getChild<LLLineEditor>("listing_id")->setPrevalidate(&LLTextValidate::validateNonNegativeS32); + center(); + + return LLFloater::postBuild(); +} + +BOOL LLFloaterAssociateListing::handleKeyHere(KEY key, MASK mask) +{ + if (key == KEY_RETURN && mask == MASK_NONE) + { + apply(); + return TRUE; + } + else if (key == KEY_ESCAPE && mask == MASK_NONE) + { + cancel(); + return TRUE; + } + + return LLFloater::handleKeyHere(key, mask); +} + +// static +LLFloaterAssociateListing* LLFloaterAssociateListing::show(const LLUUID& folder_id) +{ + LLFloaterAssociateListing* floater = LLFloaterReg::showTypedInstance<LLFloaterAssociateListing>("associate_listing"); + + floater->mUUID = folder_id; + + return floater; +} + +// Callback for apply if DAMA required... +void LLFloaterAssociateListing::callback_apply(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + apply(FALSE); + } +} + +void LLFloaterAssociateListing::apply(BOOL user_confirm) +{ + if (mUUID.notNull()) + { + S32 id = (S32)getChild<LLUICtrl>("listing_id")->getValue().asInteger(); + if (id > 0) + { + // Check if the id exists in the merchant SLM DB: note that this record might exist in the LLMarketplaceData + // structure even if unseen in the UI, for instance, if its listing_uuid doesn't exist in the merchant inventory + LLUUID listing_uuid = LLMarketplaceData::instance().getListingFolder(id); + if (listing_uuid.notNull() && user_confirm && LLMarketplaceData::instance().getActivationState(listing_uuid)) + { + // Look for user confirmation before unlisting + LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLFloaterAssociateListing::callback_apply, this, _1, _2)); + return; + } + // Associate the id with the user chosen folder + LLMarketplaceData::instance().associateListing(mUUID,listing_uuid,id); + // Update the folder widgets now that the action is launched + update_marketplace_category(listing_uuid); + update_marketplace_category(mUUID); + } + else + { + LLNotificationsUtil::add("AlertMerchantListingInvalidID"); + } + } + closeFloater(); +} + +void LLFloaterAssociateListing::cancel() +{ + closeFloater(); +} + +//----------------------------------------------------------------------------- +// LLFloaterMarketplaceValidation +//----------------------------------------------------------------------------- + +LLFloaterMarketplaceValidation::LLFloaterMarketplaceValidation(const LLSD& key) +: LLFloater(key), +mEditor(NULL) +{ +} + +BOOL LLFloaterMarketplaceValidation::postBuild() +{ + childSetAction("OK", onOK, this); + + // This widget displays the validation messages + mEditor = getChild<LLTextEditor>("validation_text"); + mEditor->setEnabled(FALSE); + mEditor->setFocus(TRUE); + mEditor->setValue(LLSD()); + + return TRUE; +} + +LLFloaterMarketplaceValidation::~LLFloaterMarketplaceValidation() +{ +} + +// virtual +void LLFloaterMarketplaceValidation::draw() +{ + // draw children + LLFloater::draw(); +} + +void LLFloaterMarketplaceValidation::onOpen(const LLSD& key) +{ + // Clear the text panel + mEditor->setValue(LLSD()); + + // Validates the marketplace + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + if (marketplacelistings_id.notNull()) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(marketplacelistings_id); + validate_marketplacelistings(cat, boost::bind(&LLFloaterMarketplaceValidation::appendMessage, this, _1, _2), false); + } +} + +// static +void LLFloaterMarketplaceValidation::onOK( void* userdata ) +{ + // destroys this object + LLFloaterMarketplaceValidation* self = (LLFloaterMarketplaceValidation*) userdata; + self->closeFloater(); +} + +void LLFloaterMarketplaceValidation::appendMessage(std::string& message, LLError::ELevel log_level) +{ + if (mEditor) + { + // Errors are printed in bold, other messages in normal font + LLStyle::Params style; + LLFontDescriptor new_desc(mEditor->getFont()->getFontDesc()); + new_desc.setStyle(log_level == LLError::LEVEL_ERROR ? LLFontGL::BOLD : LLFontGL::NORMAL); + LLFontGL* new_font = LLFontGL::getFont(new_desc); + style.font = new_font; + mEditor->appendText(message, true, style); + } +} + +//----------------------------------------------------------------------------- +// LLFloaterItemProperties +//----------------------------------------------------------------------------- + +LLFloaterItemProperties::LLFloaterItemProperties(const LLSD& key) +: LLFloater(key) +{ +} + +LLFloaterItemProperties::~LLFloaterItemProperties() +{ +} + +BOOL LLFloaterItemProperties::postBuild() +{ + // On the standalone properties floater, we have no need for a back button... + LLSidepanelItemInfo* panel = getChild<LLSidepanelItemInfo>("item_panel"); + LLButton* back_btn = panel->getChild<LLButton>("back_btn"); + back_btn->setVisible(FALSE); + + return LLFloater::postBuild(); +} + +void LLFloaterItemProperties::onOpen(const LLSD& key) +{ + // Tell the panel which item it needs to visualize + LLSidepanelItemInfo* panel = getChild<LLSidepanelItemInfo>("item_panel"); + panel->setItemID(key["id"].asUUID()); +} + diff --git a/indra/newview/llfloatermarketplacelistings.h b/indra/newview/llfloatermarketplacelistings.h new file mode 100755 index 0000000000..8a9731dafc --- /dev/null +++ b/indra/newview/llfloatermarketplacelistings.h @@ -0,0 +1,202 @@ +/** + * @file llfloatermarketplacelistings.h + * @brief Implementation of the marketplace listings floater and panels + * @author merov@lindenlab.com + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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 + * ABILITY 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$ + */ + +#ifndef LL_LLFLOATERMARKETPLACELISTINGS_H +#define LL_LLFLOATERMARKETPLACELISTINGS_H + +#include "llfloater.h" +#include "llinventoryfilter.h" +#include "llinventorypanel.h" +#include "llnotificationptr.h" +#include "llmodaldialog.h" +#include "lltexteditor.h" + +class LLInventoryCategoriesObserver; +class LLInventoryCategoryAddedObserver; +class LLTextBox; +class LLView; +class LLFilterEditor; + +class LLFloaterMarketplaceListings; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLPanelMarketplaceListings +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLPanelMarketplaceListings : public LLPanel +{ +public: + LLPanelMarketplaceListings(); + BOOL postBuild(); + void draw(); + LLFolderView* getRootFolder() { return mRootFolder; } + + void buildAllPanels(); + +private: + LLInventoryPanel* buildInventoryPanel(const std::string& childname, const std::string& filename); + + // UI callbacks + void onViewSortMenuItemClicked(const LLSD& userdata); + bool onViewSortMenuItemCheck(const LLSD& userdata); + void onAddButtonClicked(); + void onAuditButtonClicked(); + void onSelectionChange(LLInventoryPanel *panel, const std::deque<LLFolderViewItem*>& items, BOOL user_action); + void onTabChange(); + void onFilterEdit(const std::string& search_string); + + LLFolderView* mRootFolder; + LLButton* mAuditBtn; + LLFilterEditor* mFilterEditor; + std::string mFilterSubString; + LLInventoryFilter::ESortOrderType mSortOrder; + LLInventoryFilter::EFilterType mFilterType; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterMarketplaceListings +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFloaterMarketplaceListings : public LLFloater +{ +public: + LLFloaterMarketplaceListings(const LLSD& key); + ~LLFloaterMarketplaceListings(); + + void initializeMarketPlace(); + + // virtuals + BOOL postBuild(); + BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + void showNotification(const LLNotificationPtr& notification); + + BOOL handleHover(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + +protected: + void setRootFolder(); + void setPanels(); + void fetchContents(); + + void setStatusString(const std::string& statusString); + + void onClose(bool app_quitting); + void onOpen(const LLSD& key); + void onFocusReceived(); + void onChanged(); + + bool isAccepted(EAcceptance accept); + + void updateView(); + +private: + S32 getFolderCount(); + + LLInventoryCategoriesObserver * mCategoriesObserver; + LLInventoryCategoryAddedObserver * mCategoryAddedObserver; + + LLTextBox * mInventoryStatus; + LLView * mInventoryInitializationInProgress; + LLView * mInventoryPlaceholder; + LLTextBox * mInventoryText; + LLTextBox * mInventoryTitle; + + LLUUID mRootFolderId; + LLPanelMarketplaceListings * mPanelListings; + bool mPanelListingsSet; +}; + +//----------------------------------------------------------------------------- +// LLFloaterAssociateListing +//----------------------------------------------------------------------------- +class LLFloaterAssociateListing : public LLFloater +{ + friend class LLFloaterReg; +public: + virtual BOOL postBuild(); + virtual BOOL handleKeyHere(KEY key, MASK mask); + + static LLFloaterAssociateListing* show(const LLUUID& folder_id); + +private: + LLFloaterAssociateListing(const LLSD& key); + virtual ~LLFloaterAssociateListing(); + + // UI Callbacks + void apply(BOOL user_confirm = TRUE); + void cancel(); + void callback_apply(const LLSD& notification, const LLSD& response); + + LLUUID mUUID; +}; + +//----------------------------------------------------------------------------- +// LLFloaterMarketplaceValidation +//----------------------------------------------------------------------------- +// Note: For the moment, we just display the validation text. Eventually, we should +// get the validation triggered on the server and display the html report. +// *TODO : morph into an html/text window using the pattern in llfloatertos + +class LLFloaterMarketplaceValidation : public LLFloater +{ +public: + LLFloaterMarketplaceValidation(const LLSD& key); + virtual ~LLFloaterMarketplaceValidation(); + + virtual BOOL postBuild(); + virtual void draw(); + virtual void onOpen(const LLSD& key); + + void appendMessage(std::string& message, LLError::ELevel log_level); + static void onOK( void* userdata ); + +private: + LLTextEditor* mEditor; +}; + +//----------------------------------------------------------------------------- +// LLFloaterItemProperties +//----------------------------------------------------------------------------- + +class LLFloaterItemProperties : public LLFloater +{ +public: + LLFloaterItemProperties(const LLSD& key); + virtual ~LLFloaterItemProperties(); + + BOOL postBuild(); + virtual void onOpen(const LLSD& key); + +private: +}; + +#endif // LL_LLFLOATERMARKETPLACELISTINGS_H diff --git a/indra/newview/llfloateroutbox.cpp b/indra/newview/llfloateroutbox.cpp index e5efca1102..b7b1634a5f 100755 --- a/indra/newview/llfloateroutbox.cpp +++ b/indra/newview/llfloateroutbox.cpp @@ -160,6 +160,12 @@ BOOL LLFloaterOutbox::postBuild() mCategoryAddedObserver = new LLOutboxAddedObserver(this); gInventory.addObserver(mCategoryAddedObserver); + // Setup callbacks for importer + LLMarketplaceInventoryImporter& importer = LLMarketplaceInventoryImporter::instance(); + importer.setInitializationErrorCallback(boost::bind(&LLFloaterOutbox::initializationReportError, this, _1, _2)); + importer.setStatusChangedCallback(boost::bind(&LLFloaterOutbox::importStatusChanged, this, _1)); + importer.setStatusReportCallback(boost::bind(&LLFloaterOutbox::importReportResults, this, _1, _2)); + return TRUE; } @@ -298,12 +304,8 @@ void LLFloaterOutbox::initializeMarketPlace() // Initialize the marketplace import API // LLMarketplaceInventoryImporter& importer = LLMarketplaceInventoryImporter::instance(); - if (!importer.isInitialized()) { - importer.setInitializationErrorCallback(boost::bind(&LLFloaterOutbox::initializationReportError, this, _1, _2)); - importer.setStatusChangedCallback(boost::bind(&LLFloaterOutbox::importStatusChanged, this, _1)); - importer.setStatusReportCallback(boost::bind(&LLFloaterOutbox::importReportResults, this, _1, _2)); importer.initialize(); } } @@ -516,7 +518,7 @@ void LLFloaterOutbox::onImportButtonClicked() { mOutboxInventoryPanel.get()->clearSelection(); } - + mImportBusy = LLMarketplaceInventoryImporter::instance().triggerImport(); } @@ -617,3 +619,5 @@ void LLFloaterOutbox::showNotification(const LLNotificationPtr& notification) notification_handler->processNotification(notification); } + + diff --git a/indra/newview/llfloateroutbox.h b/indra/newview/llfloateroutbox.h index 40519c8fd2..2cf69fc3cc 100755 --- a/indra/newview/llfloateroutbox.h +++ b/indra/newview/llfloateroutbox.h @@ -1,7 +1,6 @@ /** * @file llfloateroutbox.h - * @brief LLFloaterOutbox - * class definition + * @brief Implementation of the merchant outbox window * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code @@ -30,6 +29,7 @@ #include "llfloater.h" #include "llfoldertype.h" +#include "llinventoryfilter.h" #include "llnotificationptr.h" diff --git a/indra/newview/llfolderviewmodelinventory.cpp b/indra/newview/llfolderviewmodelinventory.cpp index 3271a40ea0..8c360befc3 100755 --- a/indra/newview/llfolderviewmodelinventory.cpp +++ b/indra/newview/llfolderviewmodelinventory.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llfolderviewmodelinventory.h" #include "llinventorymodelbackgroundfetch.h" +#include "llinventoryfunctions.h" #include "llinventorypanel.h" #include "lltooldraganddrop.h" #include "llfavoritesbar.h" @@ -277,7 +278,7 @@ bool LLInventorySort::operator()(const LLFolderViewModelItemInventory* const& a, // We sort by name if we aren't sorting by date // OR if these are folders and we are sorting folders by name. - bool by_name = (!mByDate || (mFoldersByName && (a->getSortGroup() != SG_ITEM))); + bool by_name = ((!mByDate || (mFoldersByName && (a->getSortGroup() != SG_ITEM))) && !mFoldersByWeight); if (a->getSortGroup() != b->getSortGroup()) { @@ -309,6 +310,31 @@ bool LLInventorySort::operator()(const LLFolderViewModelItemInventory* const& a, return (compare < 0); } } + else if (mFoldersByWeight) + { + S32 weight_a = compute_stock_count(a->getUUID()); + S32 weight_b = compute_stock_count(b->getUUID()); + if (weight_a == weight_b) + { + // Equal weight -> use alphabetical order + return (LLStringUtil::compareDict(a->getDisplayName(), b->getDisplayName()) < 0); + } + else if (weight_a == -1) + { + // No weight -> move a at the end of the list + return false; + } + else if (weight_b == -1) + { + // No weight -> move b at the end of the list + return true; + } + else + { + // Lighter is first (sorted in increasing order of weight) + return (weight_a < weight_b); + } + } else { time_t first_create = a->getCreationDate(); diff --git a/indra/newview/llfolderviewmodelinventory.h b/indra/newview/llfolderviewmodelinventory.h index 8772185ad0..0516fc3b4b 100755 --- a/indra/newview/llfolderviewmodelinventory.h +++ b/indra/newview/llfolderviewmodelinventory.h @@ -89,6 +89,7 @@ public: mByDate = (mSortOrder & LLInventoryFilter::SO_DATE); mSystemToTop = (mSortOrder & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP); mFoldersByName = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME); + mFoldersByWeight = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_WEIGHT); } bool operator()(const LLFolderViewModelItemInventory* const& a, const LLFolderViewModelItemInventory* const& b) const; @@ -97,6 +98,7 @@ private: bool mByDate; bool mSystemToTop; bool mFoldersByName; + bool mFoldersByWeight; }; class LLFolderViewModelInventory diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 1910656066..62612c1450 100755 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -40,6 +40,7 @@ #include "llfavoritesbar.h" // management of favorites folder #include "llfloateropenobject.h" #include "llfloaterreg.h" +#include "llfloatermarketplacelistings.h" #include "llfloatersidepanelcontainer.h" #include "llfloaterworldmap.h" #include "llfolderview.h" @@ -65,6 +66,7 @@ #include "llsidepanelappearance.h" #include "lltooldraganddrop.h" #include "lltrans.h" +#include "llurlaction.h" #include "llviewerassettype.h" #include "llviewerfoldertype.h" #include "llviewermenu.h" @@ -79,11 +81,6 @@ void copy_slurl_to_clipboard_callback_inv(const std::string& slurl); -// Marketplace outbox current disabled -#define ENABLE_MERCHANT_OUTBOX_CONTEXT_MENU 1 -#define ENABLE_MERCHANT_SEND_TO_MARKETPLACE_CONTEXT_MENU 0 -#define BLOCK_WORN_ITEMS_IN_OUTBOX 1 - typedef std::pair<LLUUID, LLUUID> two_uuids_t; typedef std::list<two_uuids_t> two_uuids_list_t; @@ -269,13 +266,49 @@ BOOL LLInvFVBridge::isLibraryItem() const /** * @brief Adds this item into clipboard storage */ -BOOL LLInvFVBridge::cutToClipboard() const +BOOL LLInvFVBridge::cutToClipboard() +{ + const LLInventoryObject* obj = gInventory.getObject(mUUID); + if (obj && isItemMovable() && isItemRemovable()) + { + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const BOOL cut_from_marketplacelistings = gInventory.isObjectDescendentOf(mUUID, marketplacelistings_id); + + if (cut_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(mUUID) || + LLMarketplaceData::instance().isListedAndActive(mUUID))) + { + // Prompt the user if cutting from a marketplace active listing + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLInvFVBridge::callback_cutToClipboard, this, _1, _2)); + } + else + { + // Otherwise just perform the cut + return perform_cutToClipboard(); + } + } + return FALSE; +} + +// Callback for cutToClipboard if DAMA required... +BOOL LLInvFVBridge::callback_cutToClipboard(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + return perform_cutToClipboard(); + } + return FALSE; +} + +BOOL LLInvFVBridge::perform_cutToClipboard() { const LLInventoryObject* obj = gInventory.getObject(mUUID); if (obj && isItemMovable() && isItemRemovable()) { LLClipboard::instance().setCutMode(true); - return LLClipboard::instance().addToClipboard(mUUID); + BOOL added_to_clipboard = LLClipboard::instance().addToClipboard(mUUID); + removeObject(&gInventory, mUUID); // Always perform the remove even if the object couldn't make it to the clipboard + return added_to_clipboard; } return FALSE; } @@ -290,15 +323,22 @@ BOOL LLInvFVBridge::copyToClipboard() const return FALSE; } -// *TODO: make sure this does the right thing void LLInvFVBridge::showProperties() { - show_item_profile(mUUID); - - // Disable old properties floater; this is replaced by the sidepanel. - /* - LLFloaterReg::showInstance("properties", mUUID); - */ + if (isMarketplaceListingsFolder()) + { + LLFloaterReg::showInstance("item_properties", LLSD().with("id",mUUID),TRUE); + // Force it to show on top as this floater has a tendency to hide when confirmation dialog shows up + LLFloater* floater_properties = LLFloaterReg::findInstance("item_properties", LLSD().with("id",mUUID)); + if (floater_properties) + { + floater_properties->setVisibleAndFrontmost(); + } + } + else + { + show_item_profile(mUUID); + } } void LLInvFVBridge::removeBatch(std::vector<LLFolderViewModelItem*>& batch) @@ -655,7 +695,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, if (!isInboxFolder()) { items.push_back(std::string("Rename")); - if (!isItemRenameable() || (flags & FIRST_SELECTED_ITEM) == 0) + if (!isItemRenameable() || ((flags & FIRST_SELECTED_ITEM) == 0)) { disabled_items.push_back(std::string("Rename")); } @@ -693,7 +733,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, disabled_items.push_back(std::string("Cut")); } - if (canListOnMarketplace()) + if (canListOnMarketplace() && !isMarketplaceListingsFolder()) { items.push_back(std::string("Marketplace Separator")); @@ -835,21 +875,113 @@ void LLInvFVBridge::addOutboxContextMenuOptions(U32 flags, { disabled_items.push_back(std::string("Rename")); } - -#if ENABLE_MERCHANT_SEND_TO_MARKETPLACE_CONTEXT_MENU - if (isOutboxFolderDirectParent()) - { - items.push_back(std::string("Marketplace Separator")); - items.push_back(std::string("Marketplace Send")); - - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Marketplace Send")); - } - } -#endif // ENABLE_MERCHANT_SEND_TO_MARKETPLACE_CONTEXT_MENU } +void LLInvFVBridge::addMarketplaceContextMenuOptions(U32 flags, + menuentry_vec_t &items, + menuentry_vec_t &disabled_items) +{ + S32 depth = depth_nesting_in_marketplace(mUUID); + if (depth == 1) + { + // Options available at the Listing Folder level + items.push_back(std::string("Marketplace Create Listing")); + items.push_back(std::string("Marketplace Associate Listing")); + items.push_back(std::string("Marketplace Disassociate Listing")); + items.push_back(std::string("Marketplace List")); + items.push_back(std::string("Marketplace Unlist")); + if (LLMarketplaceData::instance().isUpdating(mUUID)) + { + // During SLM update, disable all marketplace related options + disabled_items.push_back(std::string("Marketplace Create Listing")); + disabled_items.push_back(std::string("Marketplace Associate Listing")); + disabled_items.push_back(std::string("Marketplace Disassociate Listing")); + disabled_items.push_back(std::string("Marketplace List")); + disabled_items.push_back(std::string("Marketplace Unlist")); + } + else + { + if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) + { + items.push_back(std::string("Marketplace Get Listing")); + } + if (LLMarketplaceData::instance().isListed(mUUID)) + { + disabled_items.push_back(std::string("Marketplace Create Listing")); + disabled_items.push_back(std::string("Marketplace Associate Listing")); + if (LLMarketplaceData::instance().getVersionFolder(mUUID).isNull()) + { + disabled_items.push_back(std::string("Marketplace List")); + disabled_items.push_back(std::string("Marketplace Unlist")); + } + else + { + if (LLMarketplaceData::instance().getActivationState(mUUID)) + { + disabled_items.push_back(std::string("Marketplace List")); + } + else + { + disabled_items.push_back(std::string("Marketplace Unlist")); + } + } + } + else + { + disabled_items.push_back(std::string("Marketplace Disassociate Listing")); + disabled_items.push_back(std::string("Marketplace List")); + disabled_items.push_back(std::string("Marketplace Unlist")); + if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) + { + disabled_items.push_back(std::string("Marketplace Get Listing")); + } + } + } + } + if (depth == 2) + { + // Options available at the Version Folder levels and only for folders + LLInventoryCategory* cat = gInventory.getCategory(mUUID); + if (cat && LLMarketplaceData::instance().isListed(cat->getParentUUID())) + { + items.push_back(std::string("Marketplace Activate")); + items.push_back(std::string("Marketplace Deactivate")); + if (LLMarketplaceData::instance().isUpdating(mUUID)) + { + // During SLM update, disable all marketplace related options + disabled_items.push_back(std::string("Marketplace Activate")); + disabled_items.push_back(std::string("Marketplace Deactivate")); + } + else + { + if (LLMarketplaceData::instance().isVersionFolder(mUUID)) + { + disabled_items.push_back(std::string("Marketplace Activate")); + if (LLMarketplaceData::instance().getActivationState(mUUID)) + { + disabled_items.push_back(std::string("Marketplace Deactivate")); + } + } + else + { + disabled_items.push_back(std::string("Marketplace Deactivate")); + } + } + } + } + // Options available at all levels on items and categories + items.push_back(std::string("Marketplace Edit Listing")); + LLUUID listing_folder_id = nested_parent_id(mUUID,depth); + LLUUID version_folder_id = LLMarketplaceData::instance().getVersionFolder(listing_folder_id); + if (!LLMarketplaceData::instance().isListed(listing_folder_id) || version_folder_id.isNull()) + { + disabled_items.push_back(std::string("Marketplace Edit Listing")); + } + // Separator + items.push_back(std::string("Marketplace Listings Separator")); +} + + // *TODO: remove this BOOL LLInvFVBridge::startDrag(EDragAndDropType* type, LLUUID* id) const { @@ -964,6 +1096,18 @@ BOOL LLInvFVBridge::isInboxFolder() const return gInventory.isObjectDescendentOf(mUUID, inbox_id); } +BOOL LLInvFVBridge::isMarketplaceListingsFolder() const +{ + const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + + if (folder_id.isNull()) + { + return FALSE; + } + + return gInventory.isObjectDescendentOf(mUUID, folder_id); +} + BOOL LLInvFVBridge::isOutboxFolder() const { const LLUUID outbox_id = getOutboxFolder(); @@ -1172,6 +1316,22 @@ void LLInvFVBridge::purgeItem(LLInventoryModel *model, const LLUUID &uuid) } } +void LLInvFVBridge::removeObject(LLInventoryModel *model, const LLUUID &uuid) +{ + // Keep track of the parent + LLInventoryItem* itemp = model->getItem(uuid); + LLUUID parent_id = (itemp ? itemp->getParentUUID() : LLUUID::null); + // Remove the object + model->removeObject(uuid); + // Get the parent updated + if (parent_id.notNull()) + { + LLViewerInventoryCategory* parent_cat = model->getCategory(parent_id); + model->updateCategory(parent_cat); + model->notifyObservers(); + } +} + bool LLInvFVBridge::canShare() const { bool can_share = false; @@ -1202,8 +1362,6 @@ bool LLInvFVBridge::canShare() const bool LLInvFVBridge::canListOnMarketplace() const { -#if ENABLE_MERCHANT_OUTBOX_CONTEXT_MENU - LLInventoryModel * model = getInventoryModel(); const LLViewerInventoryCategory * cat = model->getCategory(mUUID); @@ -1242,16 +1400,10 @@ bool LLInvFVBridge::canListOnMarketplace() const } return true; - -#else - return false; -#endif } bool LLInvFVBridge::canListOnMarketplaceNow() const { -#if ENABLE_MERCHANT_OUTBOX_CONTEXT_MENU - bool can_list = true; // Do not allow listing while import is in progress @@ -1297,10 +1449,6 @@ bool LLInvFVBridge::canListOnMarketplaceNow() const } return can_list; - -#else - return false; -#endif } LLToolDragAndDrop::ESource LLInvFVBridge::getDragSource() const @@ -1392,7 +1540,6 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) else if ("cut" == action) { cutToClipboard(); - gInventory.removeObject(mUUID); return; } else if ("copy" == action) @@ -1452,6 +1599,11 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) { doActionOnCurSelectedLandmark(boost::bind(&LLItemBridge::doShowOnMap, this, _1)); } + else if ("marketplace_edit_listing" == action) + { + std::string url = LLMarketplaceData::instance().getListingURL(mUUID); + LLUrlAction::openURL(url); + } } void LLItemBridge::doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb) @@ -1917,6 +2069,79 @@ void LLFolderBridge::buildDisplayName() const } } +std::string LLFolderBridge::getLabelSuffix() const +{ + static LLCachedControl<F32> folder_loading_message_delay(gSavedSettings, "FolderLoadingMessageWaitTime", 0.5f); + + if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= folder_loading_message_delay()) + { + return llformat(" ( %s ) ", LLTrans::getString("LoadingData").c_str()); + } + + if (isMarketplaceListingsFolder()) + { + std::string suffix = ""; + // Listing folder case + if (LLMarketplaceData::instance().isListed(getUUID())) + { + suffix = llformat("%d",LLMarketplaceData::instance().getListingID(getUUID())); + if (suffix.empty()) + { + suffix = LLTrans::getString("MarketplaceNoID"); + } + suffix = " (" + suffix + ")"; + if (LLMarketplaceData::instance().getActivationState(getUUID())) + { + suffix += " (" + LLTrans::getString("MarketplaceLive") + ")"; + } + } + // Version folder case + else if (LLMarketplaceData::instance().isVersionFolder(getUUID())) + { + suffix += " (" + LLTrans::getString("MarketplaceActive") + ")"; + } + // Add stock amount + S32 stock_count = compute_stock_count(getUUID()); + if (stock_count == 0) + { + suffix += " (" + LLTrans::getString("MarketplaceNoStock") + ")"; + } + else if (stock_count != -1) + { + if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + suffix += " (" + LLTrans::getString("MarketplaceStock") + "=" + llformat("%d", stock_count) + ")"; + } + else + { + suffix += " (" + LLTrans::getString("MarketplaceMax") + "=" + llformat("%d", stock_count) + ")"; + } + } + // Add updating suffix + if (LLMarketplaceData::instance().isUpdating(getUUID())) + { + suffix += " (" + LLTrans::getString("MarketplaceUpdating") + ")"; + } + return LLInvFVBridge::getLabelSuffix() + suffix; + } + else + { + return LLInvFVBridge::getLabelSuffix(); + } +} + +LLFontGL::StyleFlags LLFolderBridge::getLabelStyle() const +{ + if (isMarketplaceListingsFolder() && LLMarketplaceData::instance().getActivationState(getUUID())) + { + return LLFontGL::BOLD; + } + else + { + return LLFontGL::NORMAL; + } +} + void LLFolderBridge::update() { @@ -1973,6 +2198,11 @@ BOOL LLFolderBridge::isItemRemovable() const return FALSE; } } + + if (isMarketplaceListingsFolder() && LLMarketplaceData::instance().getActivationState(mUUID)) + { + return FALSE; + } return TRUE; } @@ -2108,88 +2338,11 @@ BOOL LLFolderBridge::isClipboardPasteableAsLink() const } -static BOOL can_move_to_outbox(LLInventoryItem* inv_item, std::string& tooltip_msg) -{ - // Collapse links directly to items/folders - LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; - LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); - if (linked_item != NULL) - { - inv_item = linked_item; - } - - bool allow_transfer = inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); - if (!allow_transfer) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNoTransfer"); - return false; - } - -#if BLOCK_WORN_ITEMS_IN_OUTBOX - bool worn = get_is_item_worn(inv_item->getUUID()); - if (worn) - { - tooltip_msg = LLTrans::getString("TooltipOutboxWorn"); - return false; - } -#endif - - bool calling_card = (LLAssetType::AT_CALLINGCARD == inv_item->getType()); - if (calling_card) - { - tooltip_msg = LLTrans::getString("TooltipOutboxCallingCard"); - return false; - } - - return true; -} - - -int get_folder_levels(LLInventoryCategory* inv_cat) -{ - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cats, items); - - int max_child_levels = 0; - - for (S32 i=0; i < cats->size(); ++i) - { - LLInventoryCategory* category = cats->at(i); - max_child_levels = llmax(max_child_levels, get_folder_levels(category)); - } - - return 1 + max_child_levels; -} - -int get_folder_path_length(const LLUUID& ancestor_id, const LLUUID& descendant_id) -{ - int depth = 0; - - if (ancestor_id == descendant_id) return depth; - - const LLInventoryCategory* category = gInventory.getCategory(descendant_id); - - while(category) - { - LLUUID parent_id = category->getParentUUID(); - - if (parent_id.isNull()) break; - - depth++; - - if (parent_id == ancestor_id) return depth; - - category = gInventory.getCategory(parent_id); - } - - LL_WARNS() << "get_folder_path_length() couldn't trace a path from the descendant to the ancestor" << LL_ENDL; - return -1; -} BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, BOOL drop, - std::string& tooltip_msg) + std::string& tooltip_msg, + BOOL user_confirm) { LLInventoryModel* model = getInventoryModel(); @@ -2208,10 +2361,14 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, const LLUUID &cat_id = inv_cat->getUUID(); const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); const LLUUID &outbox_id = model->findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID from_folder_uuid = inv_cat->getParentUUID(); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); const BOOL move_is_into_outbox = model->isObjectDescendentOf(mUUID, outbox_id); const BOOL move_is_from_outbox = model->isObjectDescendentOf(cat_id, outbox_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(cat_id, marketplacelistings_id); // check to make sure source is agent inventory, and is represented there. LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); @@ -2239,6 +2396,18 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, BOOL is_movable = TRUE; + if (is_movable && (marketplacelistings_id == cat_id)) + { + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipOutboxCannotMoveRoot"); + } + if (is_movable && move_is_from_marketplacelistings && LLMarketplaceData::instance().getActivationState(cat_id)) + { + // If the incoming folder is listed and active (and is therefore either the listing or the version folder), + // then moving is *not* allowed + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipOutboxDragActive"); + } if (is_movable && (mUUID == cat_id)) { is_movable = FALSE; @@ -2264,6 +2433,12 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, is_movable = FALSE; // tooltip? } + if (is_movable && (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)) + { + // One cannot move a folder into a stock folder + is_movable = FALSE; + // tooltip? + } LLInventoryModel::cat_array_t descendent_categories; LLInventoryModel::item_array_t descendent_items; @@ -2331,93 +2506,13 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, } } } - if (is_movable && move_is_into_outbox) + + if (is_movable && (move_is_into_outbox || move_is_into_marketplacelistings)) { - const int nested_folder_levels = get_folder_path_length(outbox_id, mUUID) + get_folder_levels(inv_cat); - - if (nested_folder_levels > gSavedSettings.getU32("InventoryOutboxMaxFolderDepth")) - { - tooltip_msg = LLTrans::getString("TooltipOutboxFolderLevels"); - is_movable = FALSE; - } - else - { - int dragged_folder_count = descendent_categories.size(); - int existing_item_count = 0; - int existing_folder_count = 0; - - const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(outbox_id, mUUID); - - if (master_folder != NULL) - { - if (model->isObjectDescendentOf(cat_id, master_folder->getUUID())) - { - // Don't use count because we're already inside the same category anyway - dragged_folder_count = 0; - } - else - { - existing_folder_count = 1; // Include the master folder in the count! - - // If we're in the drop operation as opposed to the drag without drop, we are doing a - // single category at a time so don't block based on the total amount of cargo data items - if (drop) - { - dragged_folder_count += 1; - } - else - { - // NOTE: The cargo id's count is a total of categories AND items but we err on the side of - // prevention rather than letting too many folders into the hierarchy of the outbox, - // when we're dragging the item to a new parent - dragged_folder_count += LLToolDragAndDrop::instance().getCargoCount(); - } - } - - // Tally the total number of categories and items inside the master folder - - LLInventoryModel::cat_array_t existing_categories; - LLInventoryModel::item_array_t existing_items; - - model->collectDescendents(master_folder->getUUID(), existing_categories, existing_items, FALSE); - - existing_folder_count += existing_categories.size(); - existing_item_count += existing_items.size(); - } - else - { - // Assume a single category is being dragged to the outbox since we evaluate one at a time - // when not putting them under a parent item. - dragged_folder_count += 1; - } - - const int nested_folder_count = existing_folder_count + dragged_folder_count; - const int nested_item_count = existing_item_count + descendent_items.size(); - - if (nested_folder_count > gSavedSettings.getU32("InventoryOutboxMaxFolderCount")) - { - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyFolders"); - is_movable = FALSE; - } - else if (nested_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) - { - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects"); - is_movable = FALSE; - } - - if (is_movable == TRUE) - { - for (S32 i=0; i < descendent_items.size(); ++i) - { - LLInventoryItem* item = descendent_items[i]; - if (!can_move_to_outbox(item, tooltip_msg)) - { - is_movable = FALSE; - break; - } - } - } - } + const LLViewerInventoryCategory * master_folder = (move_is_into_outbox ? model->getFirstDescendantOf(outbox_id, mUUID) : model->getFirstDescendantOf(marketplacelistings_id, mUUID)); + LLViewerInventoryCategory * dest_folder = getCategory(); + S32 bundle_size = (drop ? 1 : LLToolDragAndDrop::instance().getCargoCount()); + is_movable = can_move_folder_to_marketplace(master_folder, dest_folder, inv_cat, tooltip_msg, bundle_size); } if (is_movable) @@ -2460,6 +2555,37 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, if (accept && drop) { + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + if (move_is_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(cat_id) || + LLMarketplaceData::instance().isListedAndActive(cat_id))) + { + if (LLMarketplaceData::instance().isListed(cat_id) || LLMarketplaceData::instance().isVersionFolder(cat_id)) + { + // Move the active version folder or listing folder itself outside marketplace listings will unlist the listing so ask that question specifically + LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + } + else + { + // Any other case will simply modify but not unlist an active listed listing + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + } + return true; + } + if (move_is_from_marketplacelistings && LLMarketplaceData::instance().isVersionFolder(cat_id)) + { + // Moving the version folder from its location will deactivate it. Ask confirmation. + LLNotificationsUtil::add("ConfirmMerchantClearVersion", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + return true; + } + if (move_is_into_marketplacelistings && LLMarketplaceData::instance().isInActiveFolder(mUUID)) + { + // Moving something in an active listed listing will modify it. Ask confirmation. + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + return true; + } + } // Look for any gestures and deactivate them if (move_is_into_trash) { @@ -2485,6 +2611,10 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, { copy_folder_to_outbox(inv_cat, mUUID, cat_id, LLToolDragAndDrop::getOperationId()); } + else if (move_is_into_marketplacelistings) + { + move_folder_to_marketplacelistings(inv_cat, mUUID); + } else { if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX, false))) @@ -2500,11 +2630,31 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, mUUID, move_is_into_trash); } + if (move_is_from_marketplacelistings) + { + // If we move from an active (listed) listing, checks that it's still valid, if not, unlist + LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(version_folder_id); + if (!validate_marketplacelistings(cat,NULL)) + { + LLMarketplaceData::instance().activateListing(version_folder_id,false); + } + } + // Update the listing we moved from anyway + update_marketplace_category(from_folder_uuid); + // Clear the folder from the marketplace in case it is a listing folder + if (LLMarketplaceData::instance().isListed(cat_id)) + { + LLMarketplaceData::instance().clearListing(cat_id); + } + } } } else if (LLToolDragAndDrop::SOURCE_WORLD == source) { - if (move_is_into_outbox) + if (move_is_into_outbox || move_is_into_marketplacelistings) { tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); accept = FALSE; @@ -2516,7 +2666,7 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, } else if (LLToolDragAndDrop::SOURCE_LIBRARY == source) { - if (move_is_into_outbox) + if (move_is_into_outbox || move_is_into_marketplacelistings) { tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); accept = FALSE; @@ -2886,7 +3036,6 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) else if ("cut" == action) { cutToClipboard(); - gInventory.removeObject(mUUID); return; } else if ("copy" == action) @@ -2914,6 +3063,82 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) restoreItem(); return; } + else if ("marketplace_list" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 1) + { + LLUUID version_folder_id = LLMarketplaceData::instance().getVersionFolder(mUUID); + LLViewerInventoryCategory* cat = gInventory.getCategory(version_folder_id); + mMessage = ""; + if (!validate_marketplacelistings(cat,boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2))) + { + LLSD subs; + subs["[ERROR_CODE]"] = mMessage; + LLNotificationsUtil::add("MerchantListingFailed", subs); + } + else + { + LLMarketplaceData::instance().activateListing(mUUID,true); + } + } + return; + } + else if ("marketplace_activate" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 2) + { + LLInventoryCategory* category = gInventory.getCategory(mUUID); + LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), mUUID); + } + return; + } + else if ("marketplace_unlist" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 1) + { + LLMarketplaceData::instance().activateListing(mUUID,false); + } + return; + } + else if ("marketplace_deactivate" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 2) + { + LLInventoryCategory* category = gInventory.getCategory(mUUID); + LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), LLUUID::null); + } + return; + } + else if ("marketplace_create_listing" == action) + { + LLMarketplaceData::instance().createListing(mUUID); + return; + } + else if ("marketplace_disassociate_listing" == action) + { + LLMarketplaceData::instance().clearListing(mUUID); + return; + } + else if ("marketplace_get_listing" == action) + { + // This is used only to exercise the SLM API but won't be shown to end users + LLMarketplaceData::instance().getListing(mUUID); + return; + } + else if ("marketplace_associate_listing" == action) + { + LLFloaterAssociateListing::show(mUUID); + return; + } + else if ("marketplace_edit_listing" == action) + { + std::string url = LLMarketplaceData::instance().getListingURL(mUUID); + if (!url.empty()) + { + LLUrlAction::openURL(url); + } + return; + } #ifndef LL_RELEASE_FOR_DOWNLOAD else if ("delete_system_folder" == action) { @@ -2930,17 +3155,24 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) const LLUUID outbox_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); copy_folder_to_outbox(cat, outbox_id, cat->getUUID(), LLToolDragAndDrop::getOperationId()); } -#if ENABLE_MERCHANT_SEND_TO_MARKETPLACE_CONTEXT_MENU - else if (isMarketplaceSendAction(action)) - { - LL_INFOS() << "Send to marketplace action!" << LL_ENDL; +} - LLInventoryCategory * cat = gInventory.getCategory(mUUID); - if (!cat) return; - - send_to_marketplace(cat); - } -#endif // ENABLE_MERCHANT_SEND_TO_MARKETPLACE_CONTEXT_MENU +void LLFolderBridge::gatherMessage(std::string& message, LLError::ELevel log_level) +{ + if (log_level >= LLError::LEVEL_ERROR) + { + if (!mMessage.empty()) + { + // Currently, we do not gather all messages as it creates very long alerts + // Users can get to the whole list of errors on a listing using the "Check for Errors" audit button + //mMessage += "\n"; + return; + } + // Take the leading spaces out... + std::string::size_type start = message.find_first_not_of(" "); + // Append the message + mMessage += message.substr(start, message.length() - start); + } } void LLFolderBridge::openItem() @@ -3010,25 +3242,30 @@ LLFolderType::EType LLFolderBridge::getPreferredType() const // Icons for folders are based on the preferred type LLUIImagePtr LLFolderBridge::getIcon() const { - LLFolderType::EType preferred_type = LLFolderType::FT_NONE; - LLViewerInventoryCategory* cat = getCategory(); - if(cat) - { - preferred_type = cat->getPreferredType(); - } - return getIcon(preferred_type); + return getFolderIcon(FALSE); } -// static -LLUIImagePtr LLFolderBridge::getIcon(LLFolderType::EType preferred_type) +LLUIImagePtr LLFolderBridge::getIconOpen() const { - return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, FALSE)); + return getFolderIcon(TRUE); } -LLUIImagePtr LLFolderBridge::getIconOpen() const +LLUIImagePtr LLFolderBridge::getFolderIcon(BOOL is_open) const { - return LLUI::getUIImage(LLViewerFolderType::lookupIconName(getPreferredType(), TRUE)); + LLFolderType::EType preferred_type = getPreferredType(); + S32 depth = depth_nesting_in_marketplace(mUUID); + if ((preferred_type == LLFolderType::FT_NONE) && (depth == 2)) + { + // We override the type when in the marketplace listings folder and only for version folder + preferred_type = LLFolderType::FT_MARKETPLACE_VERSION; + } + return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, is_open)); +} +// static : use by LLLinkFolderBridge to get the closed type icons +LLUIImagePtr LLFolderBridge::getIcon(LLFolderType::EType preferred_type) +{ + return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, FALSE)); } LLUIImagePtr LLFolderBridge::getIconOverlay() const @@ -3040,14 +3277,6 @@ LLUIImagePtr LLFolderBridge::getIconOverlay() const return NULL; } -std::string LLFolderBridge::getLabelSuffix() const -{ - static LLCachedControl<F32> folder_loading_message_delay(gSavedSettings, "FolderLoadingMessageWaitTime", 0.5f); - return mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= folder_loading_message_delay() - ? llformat(" ( %s ) ", LLTrans::getString("LoadingData").c_str()) - : LLStringUtil::null; -} - BOOL LLFolderBridge::renameItem(const std::string& new_name) { @@ -3131,10 +3360,42 @@ void LLFolderBridge::updateHierarchyCreationDate(time_t date) void LLFolderBridge::pasteFromClipboard() { LLInventoryModel* model = getInventoryModel(); - if(model && isClipboardPasteable()) + if (model && isClipboardPasteable()) { - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const BOOL paste_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + + if (paste_into_marketplacelistings && !LLMarketplaceData::instance().isListed(mUUID) && LLMarketplaceData::instance().isInActiveFolder(mUUID)) + { + // Prompt the user if pasting in a marketplace active version listing (note that pasting right under the listing folder root doesn't need a prompt) + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_pasteFromClipboard, this, _1, _2)); + } + else + { + // Otherwise just do the paste + perform_pasteFromClipboard(); + } + } +} + +// Callback for pasteFromClipboard if DAMA required... +void LLFolderBridge::callback_pasteFromClipboard(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + perform_pasteFromClipboard(); + } +} + +void LLFolderBridge::perform_pasteFromClipboard() +{ + LLInventoryModel* model = getInventoryModel(); + if (model && isClipboardPasteable()) + { + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); const LLUUID &outbox_id = model->findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE, false); const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); @@ -3142,58 +3403,68 @@ void LLFolderBridge::pasteFromClipboard() const BOOL move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); const BOOL move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); const BOOL move_is_into_outbox = model->isObjectDescendentOf(mUUID, outbox_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); const BOOL move_is_into_favorites = (mUUID == favorites_id); std::vector<LLUUID> objects; LLClipboard::instance().pasteFromClipboard(objects); - - if (move_is_into_outbox) - { - LLFolderViewItem * outbox_itemp = mInventoryPanel.get()->getItemByID(mUUID); - - if (outbox_itemp) - { - LLToolDragAndDrop::instance().setCargoCount(objects.size()); - - BOOL can_list = TRUE; - - for (std::vector<LLUUID>::const_iterator iter = objects.begin(); - (iter != objects.end()) && (can_list == TRUE); - ++iter) - { - const LLUUID& item_id = (*iter); - LLInventoryItem *item = model->getItem(item_id); - - if (item) - { - MASK mask = 0x0; - BOOL drop = FALSE; - EDragAndDropType cargo_type = LLViewerAssetType::lookupDragAndDropType(item->getActualType()); - void * cargo_data = (void *) item; - std::string tooltip_msg; - - can_list = outbox_itemp->getViewModelItem()->dragOrDrop(mask, drop, cargo_type, cargo_data, tooltip_msg); - } - } - - LLToolDragAndDrop::instance().resetCargoCount(); - - if (can_list == FALSE) - { - // Notify user of failure somehow -- play error sound? modal dialog? - return; - } + + LLViewerInventoryCategory * dest_folder = getCategory(); + if (move_is_into_outbox || move_is_into_marketplacelistings) + { + std::string error_msg; + const LLViewerInventoryCategory * master_folder = (move_is_into_outbox ? model->getFirstDescendantOf(outbox_id, mUUID) : model->getFirstDescendantOf(marketplacelistings_id, mUUID)); + for (std::vector<LLUUID>::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) + { + const LLUUID& item_id = (*iter); + LLInventoryItem *item = model->getItem(item_id); + LLInventoryCategory *cat = model->getCategory(item_id); + + if (item && !can_move_item_to_marketplace(master_folder, dest_folder, item, error_msg, objects.size(), true)) + { + break; + } + if (cat && !can_move_folder_to_marketplace(master_folder, dest_folder, cat, error_msg, objects.size(), true, true)) + { + break; + } } - } - + if (!error_msg.empty()) + { + LLSD subs; + subs["[ERROR_CODE]"] = error_msg; + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + } + else + { + // Check that all items can be moved into that folder : for the moment, only stock folder mismatch is checked + for (std::vector<LLUUID>::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) + { + const LLUUID& item_id = (*iter); + LLInventoryItem *item = model->getItem(item_id); + LLInventoryCategory *cat = model->getCategory(item_id); + + if ((item && !dest_folder->acceptItem(item)) || (cat && (dest_folder->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK))) + { + std::string error_msg = LLTrans::getString("TooltipOutboxMixedStock"); + LLSD subs; + subs["[ERROR_CODE]"] = error_msg; + LLNotificationsUtil::add("StockPasteFailed", subs); + return; + } + } + } + const LLUUID parent_id(mUUID); - + for (std::vector<LLUUID>::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) { const LLUUID& item_id = (*iter); - + LLInventoryItem *item = model->getItem(item_id); LLInventoryObject *obj = model->getObject(item_id); if (obj) @@ -3221,21 +3492,44 @@ void LLFolderBridge::pasteFromClipboard() LLViewerInventoryCategory* vicat = (LLViewerInventoryCategory *) model->getCategory(item_id); llassert(vicat); if (vicat) - { - //changeCategoryParent() implicity calls dirtyFilter - changeCategoryParent(model, vicat, parent_id, FALSE); + { + // Clear the cut folder from the marketplace if it is a listing folder + if (LLMarketplaceData::instance().isListed(item_id)) + { + LLMarketplaceData::instance().clearListing(item_id); + } + if (move_is_into_marketplacelistings) + { + move_folder_to_marketplacelistings(vicat, parent_id); + } + else + { + //changeCategoryParent() implicity calls dirtyFilter + changeCategoryParent(model, vicat, parent_id, FALSE); + } } } else - { - LLViewerInventoryItem* viitem = dynamic_cast<LLViewerInventoryItem*>(item); - llassert(viitem); - if (viitem) - { - //changeItemParent() implicity calls dirtyFilter - changeItemParent(model, viitem, parent_id, FALSE); - } - } + { + LLViewerInventoryItem* viitem = dynamic_cast<LLViewerInventoryItem*>(item); + llassert(viitem); + if (viitem) + { + if (move_is_into_marketplacelistings) + { + if (!move_item_to_marketplacelistings(viitem, parent_id)) + { + // Stop pasting into the marketplace as soon as we get an error + break; + } + } + else + { + //changeItemParent() implicity calls dirtyFilter + changeItemParent(model, viitem, parent_id, FALSE); + } + } + } } else { @@ -3246,22 +3540,45 @@ void LLFolderBridge::pasteFromClipboard() llassert(vicat); if (vicat) { - copy_inventory_category(model, vicat, parent_id); + if (move_is_into_marketplacelistings) + { + move_folder_to_marketplacelistings(vicat, parent_id, true); + } + else + { + copy_inventory_category(model, vicat, parent_id); + } } } - else - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - parent_id, - std::string(), - LLPointer<LLInventoryCallback>(NULL)); - } - } - } - } + else + { + LLViewerInventoryItem* viitem = dynamic_cast<LLViewerInventoryItem*>(item); + llassert(viitem); + if (viitem) + { + if (move_is_into_marketplacelistings) + { + if (!move_item_to_marketplacelistings(viitem, parent_id, true)) + { + // Stop pasting into the marketplace as soon as we get an error + break; + } + } + else + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + parent_id, + std::string(), + LLPointer<LLInventoryCallback>(NULL)); + } + } + } + } + } + } // Change mode to paste for next paste LLClipboard::instance().setCutMode(false); } @@ -3274,14 +3591,16 @@ void LLFolderBridge::pasteLinkFromClipboard() { const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); const LLUUID &outbox_id = model->findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); const BOOL move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); const BOOL move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); const BOOL move_is_into_outbox = model->isObjectDescendentOf(mUUID, outbox_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); - if (move_is_into_outbox) + if (move_is_into_outbox || move_is_into_marketplacelistings) { // Notify user of failure somehow -- play error sound? modal dialog? return; @@ -3341,9 +3660,10 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items LLInventoryModel* model = getInventoryModel(); llassert(model != NULL); - const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - const LLUUID lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - const LLUUID favorites = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + const LLUUID &favorites = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &marketplace_listings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); if (lost_and_found_id == mUUID) { @@ -3361,6 +3681,35 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items { disabled_items.push_back(std::string("New Folder")); } + if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + if (LLMarketplaceData::instance().isUpdating(mUUID)) + { + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("Rename")); + disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("Copy")); + disabled_items.push_back(std::string("Paste")); + disabled_items.push_back(std::string("Delete")); + } + } + if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("New Script")); + disabled_items.push_back(std::string("New Note")); + disabled_items.push_back(std::string("New Gesture")); + disabled_items.push_back(std::string("New Clothes")); + disabled_items.push_back(std::string("New Body Parts")); + } + if (marketplace_listings_id == mUUID) + { + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("Rename")); + disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("Delete")); + } if(trash_id == mUUID) { // This is the trash. @@ -3390,12 +3739,14 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items { items.push_back(std::string("New Folder")); } - - items.push_back(std::string("New Script")); - items.push_back(std::string("New Note")); - items.push_back(std::string("New Gesture")); - items.push_back(std::string("New Clothes")); - items.push_back(std::string("New Body Parts")); + if (!isMarketplaceListingsFolder()) + { + items.push_back(std::string("New Script")); + items.push_back(std::string("New Note")); + items.push_back(std::string("New Gesture")); + items.push_back(std::string("New Clothes")); + items.push_back(std::string("New Body Parts")); + } } getClipboardEntries(false, items, disabled_items, flags); } @@ -3429,9 +3780,9 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items LLIsType is_object( LLAssetType::AT_OBJECT ); LLIsType is_gesture( LLAssetType::AT_GESTURE ); - if (checkFolderForContentsOfType(model, is_wearable) || - checkFolderForContentsOfType(model, is_object) || - checkFolderForContentsOfType(model, is_gesture) ) + if (checkFolderForContentsOfType(model, is_wearable) || + checkFolderForContentsOfType(model, is_object) || + checkFolderForContentsOfType(model, is_gesture) ) { mWearables=TRUE; } @@ -3443,7 +3794,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items disabled_items.push_back(std::string("Delete System Folder")); } - if (!isOutboxFolder()) + if (!isOutboxFolder() && !isMarketplaceListingsFolder()) { items.push_back(std::string("Share")); if (!canShare()) @@ -3453,7 +3804,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items } // Add menu items that are dependent on the contents of the folder. LLViewerInventoryCategory* category = (LLViewerInventoryCategory *) model->getCategory(mUUID); - if (category) + if (category && (marketplace_listings_id != mUUID)) { uuid_vec_t folders; folders.push_back(category->getUUID()); @@ -3472,8 +3823,8 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items { // it's all on its way - add an observer, and the inventory will call done for us when everything is here. gInventory.addObserver(fetch); - } -} + } + } } void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items) @@ -3490,6 +3841,12 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& if (isItemInTrash()) return; if (!isAgentInventory()) return; if (isOutboxFolder()) return; + + if (!isItemRemovable()) + { + disabled_items.push_back(std::string("Delete")); + } + if (isMarketplaceListingsFolder()) return; LLFolderType::EType type = category->getPreferredType(); const bool is_system_folder = LLFolderType::lookupIsProtectedType(type); @@ -3509,11 +3866,6 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& } } - if (!isItemRemovable()) - { - disabled_items.push_back(std::string("Delete")); - } - #ifndef LL_RELEASE_FOR_DOWNLOAD if (LLFolderType::lookupIsProtectedType(type)) { @@ -3922,12 +4274,35 @@ void LLFolderBridge::dropToOutfit(LLInventoryItem* inv_item, BOOL move_is_into_c } } +// Callback for drop item if DAMA required... +void LLFolderBridge::callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::string tooltip_msg; + dragItemIntoFolder(inv_item, TRUE, tooltip_msg, FALSE); + } +} + +// Callback for drop category if DAMA required... +void LLFolderBridge::callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::string tooltip_msg; + dragCategoryIntoFolder(inv_category, TRUE, tooltip_msg, FALSE); + } +} + // This is used both for testing whether an item can be dropped // into the folder, as well as performing the actual drop, depending // if drop == TRUE. BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop, - std::string& tooltip_msg) + std::string& tooltip_msg, + BOOL user_confirm) { LLInventoryModel* model = getInventoryModel(); @@ -3945,7 +4320,9 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE, false); const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK, false); const LLUUID &outbox_id = model->findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + const LLUUID from_folder_uuid = inv_item->getParentUUID(); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); const BOOL move_is_into_favorites = (mUUID == favorites_id); @@ -3954,6 +4331,8 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, const BOOL move_is_into_landmarks = (mUUID == landmarks_id) || model->isObjectDescendentOf(mUUID, landmarks_id); const BOOL move_is_into_outbox = model->isObjectDescendentOf(mUUID, outbox_id); const BOOL move_is_from_outbox = model->isObjectDescendentOf(inv_item->getUUID(), outbox_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(inv_item->getUUID(), marketplacelistings_id); LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); BOOL accept = FALSE; @@ -4011,7 +4390,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, accept = TRUE; - if (!is_movable) + if (!is_movable) { accept = FALSE; } @@ -4027,34 +4406,20 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, { accept = can_move_to_landmarks(inv_item); } - else if (move_is_into_outbox) + else if (move_is_into_outbox || move_is_into_marketplacelistings) { - accept = can_move_to_outbox(inv_item, tooltip_msg); - - if (accept) - { - const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(outbox_id, mUUID); - - int existing_item_count = LLToolDragAndDrop::instance().getCargoCount(); - - if (master_folder != NULL) - { - LLInventoryModel::cat_array_t existing_categories; - LLInventoryModel::item_array_t existing_items; - - gInventory.collectDescendents(master_folder->getUUID(), existing_categories, existing_items, FALSE); - - existing_item_count += existing_items.size(); - } - - if (existing_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) - { - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects"); - accept = FALSE; - } - } + const LLViewerInventoryCategory * master_folder = (move_is_into_outbox ? model->getFirstDescendantOf(outbox_id, mUUID) : model->getFirstDescendantOf(marketplacelistings_id, mUUID)); + LLViewerInventoryCategory * dest_folder = getCategory(); + accept = can_move_item_to_marketplace(master_folder, dest_folder, inv_item, tooltip_msg, LLToolDragAndDrop::instance().getCargoCount()); } + // Check that the folder can accept this item based on folder/item type compatibility (e.g. stock folder compatibility) + if (accept) + { + LLViewerInventoryCategory * dest_folder = getCategory(); + accept = dest_folder->acceptItem(inv_item); + } + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); // Check whether the item being dragged from active inventory panel @@ -4082,6 +4447,17 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, { active_panel->unSelectAll(); } + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + if ((move_is_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(inv_item->getUUID()) + || LLMarketplaceData::instance().isListedAndActive(inv_item->getUUID()))) || + (move_is_into_marketplacelistings && LLMarketplaceData::instance().isInActiveFolder(mUUID))) + { + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropItemIntoFolder, this, _1, _2, inv_item)); + return true; + } + } //-------------------------------------------------------------------------------- // Destination folder logic @@ -4094,7 +4470,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, LLFolderViewItem* itemp = destination_panel->getRootFolder()->getDraggingOverItem(); if (itemp) { - LLUUID srcItemId = inv_item->getUUID(); + LLUUID srcItemId = inv_item->getUUID(); LLUUID destItemId = static_cast<LLFolderViewModelItemInventory*>(itemp->getViewModelItem())->getUUID(); LLFavoritesOrderStorage::instance().rearrangeFavoriteLandmarks(srcItemId, destItemId); } @@ -4112,6 +4488,8 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, { dropToOutfit(inv_item, move_is_into_current_outfit); } + // MERCHANT OUTBOX folder + // Move the item else if (move_is_into_outbox) { if (move_is_from_outbox) @@ -4123,6 +4501,12 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, copy_item_to_outbox(inv_item, mUUID, LLUUID::null, LLToolDragAndDrop::getOperationId()); } } + // MARKETPLACE LISTINGS folder + // Move the item + else if (move_is_into_marketplacelistings) + { + move_item_to_marketplacelistings(inv_item, mUUID); + } // NORMAL or TRASH folder // (move the item, restamp if into trash) else @@ -4139,8 +4523,24 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, mUUID, move_is_into_trash); } - - // + + if (move_is_from_marketplacelistings) + { + // If we move from an active (listed) listing, checks that it's still valid, if not, unlist + LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(version_folder_id); + if (!validate_marketplacelistings(cat,NULL)) + { + LLMarketplaceData::instance().activateListing(version_folder_id,false); + } + } + // Update the listing we moved from anyway + update_marketplace_category(from_folder_uuid); + } + + // //-------------------------------------------------------------------------------- } } @@ -4189,7 +4589,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, { accept = FALSE; } - else if (move_is_into_outbox) + else if (move_is_into_outbox || move_is_into_marketplacelistings) { tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); accept = FALSE; @@ -4227,7 +4627,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, } else if(LLToolDragAndDrop::SOURCE_NOTECARD == source) { - if (move_is_into_outbox) + if (move_is_into_outbox || move_is_into_marketplacelistings) { tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); accept = FALSE; @@ -4261,7 +4661,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, { accept = TRUE; - if (move_is_into_outbox) + if (move_is_into_outbox || move_is_into_marketplacelistings) { tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); accept = FALSE; @@ -4434,6 +4834,12 @@ void LLTextureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { addOutboxContextMenuOptions(flags, items, disabled_items); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { items.push_back(std::string("Share")); @@ -4501,6 +4907,12 @@ void LLSoundBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { addOutboxContextMenuOptions(flags, items, disabled_items); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { if (isItemInTrash()) @@ -4576,6 +4988,12 @@ void LLLandmarkBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { addOutboxContextMenuOptions(flags, items, disabled_items); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { if(isItemInTrash()) @@ -4868,6 +5286,12 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Delete")); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { items.push_back(std::string("Share")); @@ -5000,6 +5424,25 @@ void LLNotecardBridge::openItem() } } +void LLNotecardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLNotecardBridge::buildContextMenu()" << LL_ENDL; + + if (isMarketplaceListingsFolder()) + { + menuentry_vec_t items; + menuentry_vec_t disabled_items; + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + hide_context_entries(menu, items, disabled_items); + } + else + { + LLItemBridge::buildContextMenu(menu, flags); + } +} + // +=================================================+ // | LLGestureBridge | // +=================================================+ @@ -5135,6 +5578,12 @@ void LLGestureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Delete")); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { items.push_back(std::string("Share")); @@ -5189,6 +5638,12 @@ void LLAnimationBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Delete")); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { if(isItemInTrash()) @@ -5472,6 +5927,12 @@ void LLObjectBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Delete")); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { items.push_back(std::string("Share")); @@ -5694,6 +6155,12 @@ void LLWearableBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Delete")); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { // FWIW, it looks like SUPPRESS_OPEN_ITEM is not set anywhere BOOL can_open = ((flags & SUPPRESS_OPEN_ITEM) != SUPPRESS_OPEN_ITEM); @@ -5994,6 +6461,12 @@ void LLMeshBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { addOutboxContextMenuOptions(flags, items, disabled_items); } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } else { items.push_back(std::string("Properties")); diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index e8d5db4437..2757f5c884 100755 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -114,7 +114,7 @@ public: virtual void move(LLFolderViewModelItem* new_parent_bridge) {} virtual BOOL isItemCopyable() const { return FALSE; } virtual BOOL copyToClipboard() const; - virtual BOOL cutToClipboard() const; + virtual BOOL cutToClipboard(); virtual BOOL isClipboardPasteable() const; virtual BOOL isClipboardPasteableAsLink() const; virtual void pasteFromClipboard() {} @@ -146,6 +146,9 @@ protected: virtual void addOutboxContextMenuOptions(U32 flags, menuentry_vec_t &items, menuentry_vec_t &disabled_items); + virtual void addMarketplaceContextMenuOptions(U32 flags, + menuentry_vec_t &items, + menuentry_vec_t &disabled_items); protected: LLInvFVBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid); @@ -160,6 +163,7 @@ protected: BOOL isInboxFolder() const; // true if COF or descendant of marketplace inbox BOOL isOutboxFolderDirectParent() const; + BOOL isMarketplaceListingsFolder() const; // true if descendant of Marketplace listings folder const LLUUID getOutboxFolder() const; virtual BOOL isItemPermissive() const; @@ -172,6 +176,9 @@ protected: const LLUUID& new_parent, BOOL restamp); void removeBatchNoCheck(std::vector<LLFolderViewModelItem*>& batch); + + BOOL callback_cutToClipboard(const LLSD& notification, const LLSD& response); + BOOL perform_cutToClipboard(); public: BOOL isOutboxFolder() const; // true if COF or descendant of marketplace outbox @@ -187,6 +194,7 @@ protected: mutable std::string mSearchableName; void purgeItem(LLInventoryModel *model, const LLUUID &uuid); + void removeObject(LLInventoryModel *model, const LLUUID &uuid); virtual void buildDisplayName() const {} }; @@ -262,8 +270,10 @@ public: mIsLoading(false) {} - BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg); - BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop, std::string& tooltip_msg); + BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm = TRUE); + BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop, std::string& tooltip_msg, BOOL user_confirm = TRUE); + void callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item); + void callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category); virtual void buildDisplayName() const; @@ -278,10 +288,9 @@ public: virtual LLUIImagePtr getIcon() const; virtual LLUIImagePtr getIconOpen() const; virtual LLUIImagePtr getIconOverlay() const; - static LLUIImagePtr getIcon(LLFolderType::EType preferred_type); - virtual std::string getLabelSuffix() const; + virtual LLFontGL::StyleFlags getLabelStyle() const; virtual BOOL renameItem(const std::string& new_name); @@ -354,11 +363,16 @@ public: static void staticFolderOptionsMenu(); private: + void callback_pasteFromClipboard(const LLSD& notification, const LLSD& response); + void perform_pasteFromClipboard(); + void gatherMessage(std::string& message, LLError::ELevel log_level); + LLUIImagePtr getFolderIcon(BOOL is_open) const; bool mCallingCards; bool mWearables; bool mIsLoading; LLTimer mTimeSinceRequestStart; + std::string mMessage; LLRootHandle<LLFolderBridge> mHandle; }; @@ -439,6 +453,7 @@ public: const LLUUID& uuid) : LLItemBridge(inventory, root, uuid) {} virtual void openItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); }; class LLGestureBridge : public LLItemBridge @@ -482,6 +497,7 @@ public: virtual LLUIImagePtr getIcon() const; virtual void performAction(LLInventoryModel* model, std::string action); virtual void openItem(); + virtual BOOL isItemWearable() const { return TRUE; } virtual std::string getLabelSuffix() const; virtual void buildContextMenu(LLMenuGL& menu, U32 flags); virtual BOOL renameItem(const std::string& new_name); @@ -514,6 +530,7 @@ public: virtual LLUIImagePtr getIcon() const; virtual void performAction(LLInventoryModel* model, std::string action); virtual void openItem(); + virtual BOOL isItemWearable() const { return TRUE; } virtual void buildContextMenu(LLMenuGL& menu, U32 flags); virtual std::string getLabelSuffix() const; virtual BOOL renameItem(const std::string& new_name); diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index dc8b15a5bf..8fcfbd646f 100755 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -33,6 +33,8 @@ #include "llfolderviewitem.h" #include "llinventorymodel.h" #include "llinventorymodelbackgroundfetch.h" +#include "llinventoryfunctions.h" +#include "llmarketplacefunctions.h" #include "llviewercontrol.h" #include "llfolderview.h" #include "llinventorybridge.h" @@ -136,12 +138,45 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const } // when applying a filter, matching folders get their contents downloaded first - if (mFilterSubString.size() + if (isNotDefault() && !gInventory.isCategoryComplete(folder_id)) { LLInventoryModelBackgroundFetch::instance().start(folder_id); } + // Marketplace folder filtering + const U32 filterTypes = mFilterOps.mFilterTypes; + const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | FILTERTYPE_MARKETPLACE_UNASSOCIATED; + if (filterTypes & marketplace_filter) + { + S32 depth = depth_nesting_in_marketplace(folder_id); + if (depth > 0) + { + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + if (filterTypes & FILTERTYPE_MARKETPLACE_ACTIVE) + { + if (!LLMarketplaceData::instance().getActivationState(listing_uuid)) + { + return false; + } + } + else if (filterTypes & FILTERTYPE_MARKETPLACE_INACTIVE) + { + if (!LLMarketplaceData::instance().isListed(listing_uuid) || LLMarketplaceData::instance().getActivationState(listing_uuid)) + { + return false; + } + } + else if (filterTypes & FILTERTYPE_MARKETPLACE_UNASSOCIATED) + { + if (LLMarketplaceData::instance().isListed(listing_uuid)) + { + return false; + } + } + } + } + // show folder links LLViewerInventoryItem* item = gInventory.getItem(folder_id); if (item && item->getActualType() == LLAssetType::AT_LINK_FOLDER) @@ -501,6 +536,21 @@ void LLInventoryFilter::setFilterEmptySystemFolders() mFilterOps.mFilterTypes |= FILTERTYPE_EMPTYFOLDERS; } +void LLInventoryFilter::setFilterMarketplaceActiveFolders() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_ACTIVE; +} + +void LLInventoryFilter::setFilterMarketplaceInactiveFolders() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_INACTIVE; +} + +void LLInventoryFilter::setFilterMarketplaceUnassociatedFolders() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_UNASSOCIATED; +} + void LLInventoryFilter::setFilterUUID(const LLUUID& object_id) { if (mFilterOps.mFilterUUID == LLUUID::null) diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index 5e18ad4d20..c270b90b09 100755 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -52,7 +52,10 @@ public: FILTERTYPE_UUID = 0x1 << 2, // find the object with UUID and any links to it FILTERTYPE_DATE = 0x1 << 3, // search by date range FILTERTYPE_WEARABLE = 0x1 << 4, // search by wearable type - FILTERTYPE_EMPTYFOLDERS = 0x1 << 5 // pass if folder is not a system folder to be hidden if + FILTERTYPE_EMPTYFOLDERS = 0x1 << 5, // pass if folder is not a system folder to be hidden if empty + FILTERTYPE_MARKETPLACE_ACTIVE = 0x1 << 6, // pass if folder is a marketplace active folder + FILTERTYPE_MARKETPLACE_INACTIVE = 0x1 << 7, // pass if folder is a marketplace inactive folder + FILTERTYPE_MARKETPLACE_UNASSOCIATED = 0x1 << 8 // pass if folder is a marketplace non associated (no market ID) folder }; enum EFilterDateDirection @@ -73,7 +76,8 @@ public: SO_NAME = 0, // Sort inventory by name SO_DATE = 0x1, // Sort inventory by date SO_FOLDERS_BY_NAME = 0x1 << 1, // Force folder sort by name - SO_SYSTEM_FOLDERS_TO_TOP = 0x1 << 2 // Force system folders to be on top + SO_SYSTEM_FOLDERS_TO_TOP = 0x1 << 2,// Force system folders to be on top + SO_FOLDERS_BY_WEIGHT = 0x1 << 3, // Force folder sort by weight, usually, amount of some elements in their descendents }; struct FilterOps @@ -170,6 +174,9 @@ public: void setFilterUUID(const LLUUID &object_id); void setFilterWearableTypes(U64 types); void setFilterEmptySystemFolders(); + void setFilterMarketplaceActiveFolders(); + void setFilterMarketplaceInactiveFolders(); + void setFilterMarketplaceUnassociatedFolders(); void updateFilterTypes(U64 types, U64& current_types); void setFilterSubString(const std::string& string); diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 1abc09bf3b..ddcaf9d2bd 100755 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -59,6 +59,7 @@ #include "llinventorypanel.h" #include "lllineeditor.h" #include "llmarketplacenotifications.h" +#include "llmarketplacefunctions.h" #include "llmenugl.h" #include "llnotificationsutil.h" #include "llpanelmaininventory.h" @@ -89,6 +90,45 @@ BOOL LLInventoryState::sWearNewClothing = FALSE; LLUUID LLInventoryState::sWearNewClothingTransactionID; +std::list<LLUUID> LLInventoryAction::sMarketplaceFolders; + +// Helper function : callback to update a folder after inventory action happened in the background +void update_folder_cb(const LLUUID& dest_folder) +{ + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); + gInventory.updateCategory(dest_cat); + gInventory.notifyObservers(); +} + +// Helper function : Count only the copyable items, i.e. skip the stock items (which are no copy) +S32 count_copyable_items(LLInventoryModel::item_array_t& items) +{ + S32 count = 0; + for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) + { + LLViewerInventoryItem* item = *it; + if (item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + count++; + } + } + return count; +} + +// Helper function : Count the number of stock folders +S32 count_stock_folders(LLInventoryModel::cat_array_t& categories) +{ + S32 count = 0; + for (LLInventoryModel::cat_array_t::const_iterator it = categories.begin(); it != categories.end(); ++it) + { + LLInventoryCategory* cat = *it; + if (cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + count++; + } + } + return count; +} // Generates a string containing the path to the item specified by // item_id. @@ -111,6 +151,134 @@ void append_path(const LLUUID& id, std::string& path) path.append(temp); } +void update_marketplace_folder_hierarchy(const LLUUID cat_id) +{ + // When changing the marketplace status of a folder, the only thing that needs to happen is + // for all observers of the folder to, possibly, change the display label of the folder + // so that's the only thing we change on the update mask. + gInventory.addChangedMask(LLInventoryObserver::LABEL, cat_id); + + // Update all descendent folders down + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); + + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + update_marketplace_folder_hierarchy(category->getUUID()); + } + return; +} + +void update_marketplace_category(const LLUUID& cur_uuid, bool perform_consistency_enforcement) +{ + // When changing the marketplace status of an item, we usually have to change the status of all + // folders in the same listing. This is because the display of each folder is affected by the + // overall status of the whole listing. + // Consequently, the only way to correctly update an item anywhere in the marketplace is to + // update the whole listing from its listing root. + // This is not as bad as it seems as we only update folders, not items, and the folder nesting depth + // is limited to 4. + // We also take care of degenerated cases so we don't update all folders in the inventory by mistake. + + if (cur_uuid.isNull()) + { + return; + } + + // Grab marketplace listing data for this item + S32 depth = depth_nesting_in_marketplace(cur_uuid); + if (depth > 0) + { + // Retrieve the listing uuid this object is in + LLUUID listing_uuid = nested_parent_id(cur_uuid, depth); + + // Verify marketplace data consistency for this listing + if (perform_consistency_enforcement && LLMarketplaceData::instance().isListed(listing_uuid)) + { + LLUUID version_folder_uuid = LLMarketplaceData::instance().getVersionFolder(listing_uuid); + S32 version_depth = depth_nesting_in_marketplace(version_folder_uuid); + if (version_folder_uuid.notNull() && (!gInventory.isObjectDescendentOf(version_folder_uuid, listing_uuid) || (version_depth != 2))) + { + LL_INFOS("SLM") << "Unlist and clear version folder as the version folder is not at the right place anymore!!" << LL_ENDL; + LLMarketplaceData::instance().setVersionFolder(listing_uuid, LLUUID::null); + } + } + + // Check if the count on hand needs to be updated on SLM + if (!LLMarketplaceData::instance().isUpdating(listing_uuid) && + (compute_stock_count(listing_uuid) != LLMarketplaceData::instance().getCountOnHand(listing_uuid))) + { + LLMarketplaceData::instance().updateCountOnHand(listing_uuid); + } + // Update all descendents starting from the listing root + update_marketplace_folder_hierarchy(listing_uuid); + } + else if (depth == 0) + { + // If this is the marketplace listings root itself, update all descendents + if (gInventory.getCategory(cur_uuid)) + { + update_marketplace_folder_hierarchy(cur_uuid); + } + } + else + { + // If the folder is outside the marketplace listings root, clear its SLM data if needs be + if (perform_consistency_enforcement && LLMarketplaceData::instance().isListed(cur_uuid)) + { + LL_INFOS("SLM") << "Disassociate as the listing folder is not under the marketplace folder anymore!!" << LL_ENDL; + LLMarketplaceData::instance().clearListing(cur_uuid); + } + // Update all descendents if this is a category + if (gInventory.getCategory(cur_uuid)) + { + update_marketplace_folder_hierarchy(cur_uuid); + } + } + + return; +} + +// Iterate through the marketplace and flag for label change all categories that countain a stock folder (i.e. stock folders and embedding folders up the hierarchy) +void update_all_marketplace_count(const LLUUID& cat_id) +{ + // Get all descendent folders down + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); + + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + if (category->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + // Listing containing stock folders needs to be updated but not others + // Note: we take advantage of the fact that stock folder *do not* contain sub folders to avoid a recursive call here + update_marketplace_category(category->getUUID()); + } + else + { + // Explore the contained folders recursively + update_all_marketplace_count(category->getUUID()); + } + } +} + +void update_all_marketplace_count() +{ + // Get the marketplace root and launch the recursive exploration + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + if (!marketplace_listings_uuid.isNull()) + { + update_all_marketplace_count(marketplace_listings_uuid); + } + return; +} + void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name) { LLViewerInventoryCategory* cat; @@ -150,13 +318,14 @@ void copy_inventory_category(LLInventoryModel* model, for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) { LLInventoryItem* item = *iter; + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, new_cat_uuid)); copy_inventory_item( gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(), new_cat_uuid, std::string(), - LLPointer<LLInventoryCallback>(NULL)); + cb); } // Copy all the folders @@ -508,14 +677,17 @@ void open_outbox() LLFloaterReg::showInstance("outbox"); } -LLUUID create_folder_in_outbox_for_item(LLInventoryItem* item, const LLUUID& destFolderId, S32 operation_id) +// Create a new folder in destFolderId with the same name as the item name and return the uuid of the new folder +// Note: this is used locally in various situation where we need to wrap an item into a special folder +LLUUID create_folder_for_item(LLInventoryItem* item, const LLUUID& destFolderId) { llassert(item); llassert(destFolderId.notNull()); LLUUID created_folder_id = gInventory.createNewCategory(destFolderId, LLFolderType::FT_NONE, item->getName()); gInventory.notifyObservers(); - + + // *TODO : Create different notifications for the various cases LLNotificationsUtil::add("OutboxFolderCreated"); return created_folder_id; @@ -531,8 +703,7 @@ void move_to_outbox_cb_action(const LLSD& payload) // when moving item directly into outbox create folder with that name if (dest_folder_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false)) { - S32 operation_id = payload["operation_id"].asInteger(); - dest_folder_id = create_folder_in_outbox_for_item(viitem, dest_folder_id, operation_id); + dest_folder_id = create_folder_for_item(viitem, dest_folder_id); } LLUUID parent = viitem->getParentUUID(); @@ -603,7 +774,7 @@ void copy_item_to_outbox(LLInventoryItem* inv_item, LLUUID dest_folder, const LL // when moving item directly into outbox create folder with that name if (dest_folder == gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false)) { - dest_folder = create_folder_in_outbox_for_item(inv_item, dest_folder, operation_id); + dest_folder = create_folder_for_item(inv_item, dest_folder); } copy_inventory_item(gAgent.getID(), @@ -633,7 +804,7 @@ void move_item_within_outbox(LLInventoryItem* inv_item, LLUUID dest_folder, S32 // when moving item directly into outbox create folder with that name if (dest_folder == gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false)) { - dest_folder = create_folder_in_outbox_for_item(inv_item, dest_folder, operation_id); + dest_folder = create_folder_for_item(inv_item, dest_folder); } LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; @@ -674,6 +845,916 @@ void copy_folder_to_outbox(LLInventoryCategory* inv_cat, const LLUUID& dest_fold } ///---------------------------------------------------------------------------- +// Marketplace functions +// +// Handles Copy and Move to or within the Marketplace listings folder. +// Handles creation of stock folders, nesting of listings and version folders, +// permission checking and listings validation. +///---------------------------------------------------------------------------- + +S32 depth_nesting_in_marketplace(LLUUID cur_uuid) +{ + // Get the marketplace listings root, exit with -1 (i.e. not under the marketplace listings root) if none + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + if (marketplace_listings_uuid.isNull()) + { + return -1; + } + // If not a descendent of the marketplace listings root, then the nesting depth is -1 by definition + if (!gInventory.isObjectDescendentOf(cur_uuid, marketplace_listings_uuid)) + { + return -1; + } + + // Iterate through the parents till we hit the marketplace listings root + // Note that the marketplace listings root itself will return 0 + S32 depth = 0; + LLInventoryObject* cur_object = gInventory.getObject(cur_uuid); + while (cur_uuid != marketplace_listings_uuid) + { + depth++; + cur_uuid = cur_object->getParentUUID(); + cur_object = gInventory.getCategory(cur_uuid); + } + return depth; +} + +// Returns the UUID of the marketplace listing this object is in +LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth) +{ + if (depth < 1) + { + // For objects outside the marketplace listings root (or root itself), we return a NULL UUID + return LLUUID::null; + } + else if (depth == 1) + { + // Just under the root, we return the passed UUID itself if it's a folder, NULL otherwise (not a listing) + LLViewerInventoryCategory* cat = gInventory.getCategory(cur_uuid); + return (cat ? cur_uuid : LLUUID::null); + } + + // depth > 1 + LLInventoryObject* cur_object = gInventory.getObject(cur_uuid); + while (depth > 1) + { + depth--; + cur_uuid = cur_object->getParentUUID(); + cur_object = gInventory.getCategory(cur_uuid); + } + return cur_uuid; +} + +S32 compute_stock_count(LLUUID cat_uuid) +{ + // Handle the case of the folder being a stock folder immediately + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_uuid); + if (!cat) + { + // Not a category so no stock count to speak of + return -1; + } + if (cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + // Note: stock folders are *not* supposed to have nested subfolders so we stop recursion here but we count only items (subfolders will be ignored) + // Note: we *always* give a stock count for stock folders, it's useful even if the listing is unassociated + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_uuid,cat_array,item_array); + return item_array->size(); + } + + // Grab marketplace data for this folder + S32 depth = depth_nesting_in_marketplace(cat_uuid); + LLUUID listing_uuid = nested_parent_id(cat_uuid, depth); + if (!LLMarketplaceData::instance().isListed(listing_uuid)) + { + // If not listed, the notion of stock is meaningless so it won't be computed for any level + return -1; + } + + LLUUID version_folder_uuid = LLMarketplaceData::instance().getVersionFolder(listing_uuid); + // Handle the case of the first 2 levels : listing and version folders + if (depth == 1) + { + if (version_folder_uuid.notNull()) + { + // If there is a version folder, the stock value for the listing is the version folder stock + return compute_stock_count(version_folder_uuid); + } + else + { + // If there's no version folder associated, the notion of stock count has no meaning + return -1; + } + } + else if (depth == 2) + { + if (version_folder_uuid.notNull() && (version_folder_uuid != cat_uuid)) + { + // If there is a version folder but we're not it, our stock count is meaningless + return -1; + } + } + + // In all other cases, the stock count is the min of stock folders count found in the descendents + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_uuid,cat_array,item_array); + + // "-1" denotes a folder that doesn't countain any stock folders in its descendents + S32 curr_count = -1; + + // Note: marketplace listings have a maximum depth nesting of 4 + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + S32 count = compute_stock_count(category->getUUID()); + if ((curr_count == -1) || ((count != -1) && (count < curr_count))) + { + curr_count = count; + } + } + + return curr_count; +} + +// local helper +bool can_move_to_marketplace(LLInventoryItem* inv_item, std::string& tooltip_msg, bool resolve_links) +{ + // Collapse links directly to items/folders + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; + LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); + LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); + + // Linked items and folders cannot be put for sale + if (linked_category || linked_item) + { + tooltip_msg = LLTrans::getString("TooltipOutboxLinked"); + return false; + } + + // A category is always considered as passing... + if (linked_category != NULL) + { + return true; + } + + // Take the linked item if necessary + if (linked_item != NULL) + { + inv_item = linked_item; + } + + // Check that the agent has transfer permission on the item: this is required as a resident cannot + // put on sale items she cannot transfer. Proceed with move if we have permission. + bool allow_transfer = inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); + if (!allow_transfer) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNoTransfer"); + return false; + } + + // Check worn/not worn status: worn items cannot be put on the marketplace + bool worn = get_is_item_worn(inv_item->getUUID()); + if (worn) + { + tooltip_msg = LLTrans::getString("TooltipOutboxWorn"); + return false; + } + + // Check library status: library items cannot be put on the marketplace + if (!gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.getRootFolderID())) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + return false; + } + + // Check type: for the moment, calling cards cannot be put on the marketplace + bool calling_card = (LLAssetType::AT_CALLINGCARD == inv_item->getType()); + if (calling_card) + { + tooltip_msg = LLTrans::getString("TooltipOutboxCallingCard"); + return false; + } + + return true; +} + +// local helper +// Returns the max tree length (in folder nodes) down from the argument folder +int get_folder_levels(LLInventoryCategory* inv_cat) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cats, items); + + int max_child_levels = 0; + + for (S32 i=0; i < cats->size(); ++i) + { + LLInventoryCategory* category = cats->at(i); + max_child_levels = llmax(max_child_levels, get_folder_levels(category)); + } + + return 1 + max_child_levels; +} + +// local helper +// Returns the distance (in folder nodes) between the ancestor and its descendant. Returns -1 if not related. +int get_folder_path_length(const LLUUID& ancestor_id, const LLUUID& descendant_id) +{ + int depth = 0; + + if (ancestor_id == descendant_id) return depth; + + const LLInventoryCategory* category = gInventory.getCategory(descendant_id); + + while (category) + { + LLUUID parent_id = category->getParentUUID(); + + if (parent_id.isNull()) break; + + depth++; + + if (parent_id == ancestor_id) return depth; + + category = gInventory.getCategory(parent_id); + } + + LL_WARNS("SLM") << "get_folder_path_length() couldn't trace a path from the descendant to the ancestor" << LL_ENDL; + return -1; +} + +// local helper +// Returns true if all items within the argument folder are fit for sale, false otherwise +bool has_correct_permissions_for_sale(LLInventoryCategory* cat, std::string& error_msg) +{ + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); + + LLInventoryModel::item_array_t item_array_copy = *item_array; + + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + if (!can_move_to_marketplace(item, error_msg, false)) + { + return false; + } + } + + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + if (!has_correct_permissions_for_sale(category, error_msg)) + { + return false; + } + } + return true; +} + +// Returns true if inv_item can be dropped in dest_folder, a folder nested in marketplace listings (or merchant inventory) under the root_folder root +// If returns is false, tooltip_msg contains an error message to display to the user (localized and all). +// bundle_size is the amount of sibling items that are getting moved to the marketplace at the same time. +bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size, bool from_paste) +{ + // Check stock folder type matches item type in marketplace listings or merchant outbox (even if of no use there for the moment) + LLViewerInventoryCategory* view_folder = dynamic_cast<LLViewerInventoryCategory*>(dest_folder); + bool move_in_stock = (view_folder && (view_folder->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)); + bool accept = (view_folder && view_folder->acceptItem(inv_item)); + if (!accept) + { + tooltip_msg = LLTrans::getString("TooltipOutboxMixedStock"); + } + + // Check that the item has the right type and permissions to be sold on the marketplace + if (accept) + { + accept = can_move_to_marketplace(inv_item, tooltip_msg, true); + } + + // Check that the total amount of items won't violate the max limit on the marketplace + if (accept) + { + // If the dest folder is a stock folder, we do not count the incoming items toward the total (stock items are seen as one) + int existing_item_count = (move_in_stock ? 0 : bundle_size); + + // Get the version folder: that's where the counts start from + const LLViewerInventoryCategory * version_folder = ((root_folder && (root_folder != dest_folder)) ? gInventory.getFirstDescendantOf(root_folder->getUUID(), dest_folder->getUUID()) : NULL); + + if (version_folder) + { + if (!from_paste && gInventory.isObjectDescendentOf(inv_item->getUUID(), version_folder->getUUID())) + { + // Clear those counts or they will be counted twice because we're already inside the version category + existing_item_count = 0; + } + + LLInventoryModel::cat_array_t existing_categories; + LLInventoryModel::item_array_t existing_items; + + gInventory.collectDescendents(version_folder->getUUID(), existing_categories, existing_items, FALSE); + + existing_item_count += count_copyable_items(existing_items) + count_stock_folders(existing_categories); + } + + if (existing_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxItemCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects", args); + accept = false; + } + } + + return accept; +} + +// Returns true if inv_cat can be dropped in dest_folder, a folder nested in marketplace listings (or merchant inventory) under the root_folder root +// If returns is false, tooltip_msg contains an error message to display to the user (localized and all). +// bundle_size is the amount of sibling items that are getting moved to the marketplace at the same time. +bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size, bool check_items, bool from_paste) +{ + bool accept = true; + + // Compute the nested folders level we'll add into with that incoming folder + int incoming_folder_depth = get_folder_levels(inv_cat); + // Compute the nested folders level we're inserting ourselves in + // Note: add 1 when inserting under a listing folder as we need to take the root listing folder in the count + int insertion_point_folder_depth = (root_folder ? get_folder_path_length(root_folder->getUUID(), dest_folder->getUUID()) + 1 : 0); + + // Get the version folder: that's where the folders and items counts start from + const LLViewerInventoryCategory * version_folder = (insertion_point_folder_depth >= 2 ? gInventory.getFirstDescendantOf(root_folder->getUUID(), dest_folder->getUUID()) : NULL); + + // Compare the whole with the nested folders depth limit + // Note: substract 2 as we leave root and version folder out of the count threshold + if ((incoming_folder_depth + insertion_point_folder_depth - 2) > (S32)(gSavedSettings.getU32("InventoryOutboxMaxFolderDepth"))) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderDepth"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxFolderLevels", args); + accept = false; + } + + if (accept) + { + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + gInventory.collectDescendents(inv_cat->getUUID(), descendent_categories, descendent_items, FALSE); + + int dragged_folder_count = descendent_categories.size() + bundle_size; // Note: We assume that we're moving a bunch of folders in. That might be wrong... + int dragged_item_count = count_copyable_items(descendent_items) + count_stock_folders(descendent_categories); + int existing_item_count = 0; + int existing_folder_count = 0; + + if (version_folder) + { + if (!from_paste && gInventory.isObjectDescendentOf(inv_cat->getUUID(), version_folder->getUUID())) + { + // Clear those counts or they will be counted twice because we're already inside the version category + dragged_folder_count = 0; + dragged_item_count = 0; + } + + // Tally the total number of categories and items inside the root folder + LLInventoryModel::cat_array_t existing_categories; + LLInventoryModel::item_array_t existing_items; + gInventory.collectDescendents(version_folder->getUUID(), existing_categories, existing_items, FALSE); + + existing_folder_count += existing_categories.size(); + existing_item_count += count_copyable_items(existing_items) + count_stock_folders(existing_categories); + } + + const int total_folder_count = existing_folder_count + dragged_folder_count; + const int total_item_count = existing_item_count + dragged_item_count; + + if (total_folder_count > gSavedSettings.getU32("InventoryOutboxMaxFolderCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyFolders", args); + accept = false; + } + else if (total_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxItemCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects", args); + accept = false; + } + + // Now check that each item in the folder can be moved in the marketplace + if (accept && check_items) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLInventoryItem* item = descendent_items[i]; + if (!can_move_to_marketplace(item, tooltip_msg, false)) + { + accept = false; + break; + } + } + } + } + + return accept; +} + +bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy) +{ + // Get the marketplace listings depth of the destination folder, exit with error if not under marketplace + S32 depth = depth_nesting_in_marketplace(dest_folder); + if (depth < 0) + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Merchant"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + + // We will collapse links into items/folders + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; + LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); + + if (linked_category != NULL) + { + // Move the linked folder directly + return move_folder_to_marketplacelistings(linked_category, dest_folder, copy); + } + else + { + // Grab the linked item if any + LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); + viewer_inv_item = (linked_item != NULL ? linked_item : viewer_inv_item); + + // Check that the agent has transfer permission on the item: this is required as a resident cannot + // put on sale items she cannot transfer. Proceed with move if we have permission. + std::string error_msg; + if (can_move_to_marketplace(inv_item, error_msg, true)) + { + // When moving an isolated item, we might need to create the folder structure to support it + if (depth == 0) + { + // We need a listing folder + dest_folder = gInventory.createNewCategory(dest_folder, LLFolderType::FT_NONE, viewer_inv_item->getName()); + depth++; + } + if (depth == 1) + { + // We need a version folder + dest_folder = gInventory.createNewCategory(dest_folder, LLFolderType::FT_NONE, viewer_inv_item->getName()); + depth++; + } + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && + (dest_cat->getPreferredType() != LLFolderType::FT_MARKETPLACE_STOCK)) + { + // We need a stock folder + dest_folder = gInventory.createNewCategory(dest_folder, LLFolderType::FT_MARKETPLACE_STOCK, viewer_inv_item->getName()); + dest_cat = gInventory.getCategory(dest_folder); + depth++; + } + + // Verify we can have this item in that destination category + if (!dest_cat->acceptItem(viewer_inv_item)) + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + + // Get the parent folder of the moved item : we may have to update it + LLUUID src_folder = viewer_inv_item->getParentUUID(); + + if (copy) + { + // Copy the item + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, dest_folder)); + copy_inventory_item( + gAgent.getID(), + viewer_inv_item->getPermissions().getOwner(), + viewer_inv_item->getUUID(), + dest_folder, + std::string(), + cb); + } + else + { + // Reparent the item + gInventory.changeItemParent(viewer_inv_item, dest_folder, true); + } + + // Update the modified folders + update_marketplace_category(src_folder); + update_marketplace_category(dest_folder); + gInventory.notifyObservers(); + } + else + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + error_msg; + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + } + return true; +} + +bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy) +{ + // Check that we have adequate permission on all items being moved. Proceed if we do. + std::string error_msg; + if (has_correct_permissions_for_sale(inv_cat, error_msg)) + { + // Get the destination folder + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); + + // Check it's not a stock folder + if (dest_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + + // Get the parent folder of the moved item : we may have to update it + LLUUID src_folder = inv_cat->getParentUUID(); + + LLViewerInventoryCategory * viewer_inv_cat = (LLViewerInventoryCategory *) inv_cat; + if (copy) + { + // Copy the folder + copy_inventory_category(&gInventory, viewer_inv_cat, dest_folder); + } + else + { + // Reparent the folder + gInventory.changeCategoryParent(viewer_inv_cat, dest_folder, false); + } + + // Check the destination folder recursively for no copy items and promote the including folders if any + validate_marketplacelistings(dest_cat); + + // Update the modified folders + update_marketplace_category(src_folder); + update_marketplace_category(dest_folder); + gInventory.notifyObservers(); + } + else + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + error_msg; + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + return true; +} + +// Make all relevant business logic checks on the marketplace listings starting with the folder as argument. +// This function does no deletion of listings but a mere audit and raises issues to the user (through the +// optional callback cb). It also returns a boolean, true if things validate, false if issues are raised. +// The only inventory changes that are done is to move and sort folders containing no-copy items to stock folders. +bool sort_alpha(const LLViewerInventoryCategory* cat1, const LLViewerInventoryCategory* cat2) +{ + return cat1->getName().compare(cat2->getName()) < 0; +} + +bool validate_marketplacelistings(LLInventoryCategory* cat, validation_callback_t cb, bool fix_hierarchy) +{ + // Folder is valid unless issue is raised + bool result = true; + + // Get the type and the depth of the folder + LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (cat); + const LLFolderType::EType folder_type = cat->getPreferredType(); + S32 depth = depth_nesting_in_marketplace(cat->getUUID()); + if (depth < 0) + { + // If the folder is not under the marketplace listings root, validation should not be applied + return result; + } + + // Set the indentation for print output (typically, audit button in marketplace folder floater) + std::string indent; + for (int i = 1; i < depth; i++) + { + indent += " "; + } + + // Check out that version folders are marketplace ready + if (depth == 2) + { + std::string message; + // Note: if we fix the hierarchy, we want to check the items individually, hence the last argument here + if (!can_move_folder_to_marketplace(cat, cat, cat, message, 0, fix_hierarchy)) + { + result = false; + if (cb) + { + message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + message; + cb(message,LLError::LEVEL_ERROR); + } + } + } + + // Check out that stock folders are at the right level + if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth <= 2)) + { + if (fix_hierarchy) + { + if (cb) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); + cb(message,LLError::LEVEL_WARN); + } + // Nest the stock folder one level deeper in a normal folder and restart from there + LLUUID parent_uuid = cat->getParentUUID(); + LLUUID folder_uuid = gInventory.createNewCategory(parent_uuid, LLFolderType::FT_NONE, cat->getName()); + LLInventoryCategory* new_cat = gInventory.getCategory(folder_uuid); + gInventory.changeCategoryParent(viewer_cat, folder_uuid, false); + result &= validate_marketplacelistings(new_cat, cb, fix_hierarchy); + return result; + } + else + { + result = false; + if (cb) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); + cb(message,LLError::LEVEL_ERROR); + } + } + } + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); + + // Stock items : sorting and moving the various stock items is complicated as the set of constraints is high + // For each folder, we need to: + // * separate non stock items, stock items per types in different folders + // * have stock items nested at depth 2 at least + // * never ever move the non-stock items + + std::vector<std::vector<LLViewerInventoryItem*> > items_vector; + items_vector.resize(LLInventoryType::IT_COUNT+1); + + // Parse the items and create vectors of items to sort copyable items and stock items of various types + bool has_bad_items = false; + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) item; + + // Test but skip items that shouldn't be there to start with, raise an error message for those + std::string error_msg; + if (!can_move_to_marketplace(item, error_msg, false)) + { + has_bad_items = true; + if (cb && fix_hierarchy) + { + std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; + cb(message,LLError::LEVEL_ERROR); + } + continue; + } + // Update the appropriate vector item for that type + LLInventoryType::EType type = LLInventoryType::IT_COUNT; // Default value for non stock items + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + // Get the item type for stock items + type = viewer_inv_item->getInventoryType(); + } + items_vector[type].push_back(viewer_inv_item); + } + + // How many types of folders? Which type is it if only one? + S32 count = 0; + LLInventoryType::EType type = LLInventoryType::IT_COUNT; + for (S32 i = 0; i <= LLInventoryType::IT_COUNT; i++) + { + if (!items_vector[i].empty()) + { + count++; + type = (LLInventoryType::EType)(i); + } + } + + // If we have no items in there (only folders or empty), analyze a bit further + if ((count == 0) && !has_bad_items) + { + if (cat_array->size() == 0) + { + if (depth == 2) + { + // If this is an empty version folder, warn only (listing won't be delivered by AIS, but only AIS should unlist) + if (cb) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Version"); + cb(message,LLError::LEVEL_WARN); + } + } + else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2)) + { + // If this is a legit but empty stock folder, warn only (listing must stay searchable when out of stock) + if (cb) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Stock"); + cb(message,LLError::LEVEL_WARN); + } + } + else if (cb) + { + // We warn if there's nothing in a regular folder (may be it's an under construction listing) + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning Empty"); + cb(message,LLError::LEVEL_WARN); + } + } + else + { + // Done with that folder : Print out the folder name unless we already found an error here + if (cb && result && (depth >= 1)) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); + cb(message,LLError::LEVEL_INFO); + } + } + } + // If we have a single type of items of the right type in the right place, we're done + else if ((count == 1) && !has_bad_items && (((type == LLInventoryType::IT_COUNT) && (depth > 1)) || ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2) && (cat_array->size() == 0)))) + { + // Done with that folder : Print out the folder name unless we already found an error here + if (cb && result && (depth >= 1)) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); + cb(message,LLError::LEVEL_INFO); + } + } + else + { + if (fix_hierarchy && !has_bad_items) + { + // Create one folder per vector at the right depth and of the right type + for (S32 i = 0; i <= LLInventoryType::IT_COUNT; i++) + { + if (!items_vector[i].empty()) + { + // Create a new folder + LLUUID parent_uuid = (depth > 2 ? viewer_cat->getParentUUID() : viewer_cat->getUUID()); + LLViewerInventoryItem* viewer_inv_item = items_vector[i].back(); + std::string folder_name = (depth > 1 ? viewer_cat->getName() : viewer_inv_item->getName()); + LLFolderType::EType new_folder_type = (i == LLInventoryType::IT_COUNT ? LLFolderType::FT_NONE : LLFolderType::FT_MARKETPLACE_STOCK); + if (cb) + { + std::string message = ""; + if (new_folder_type == LLFolderType::FT_MARKETPLACE_STOCK) + { + message = indent + folder_name + LLTrans::getString("Marketplace Validation Warning Create Stock"); + } + else + { + message = indent + folder_name + LLTrans::getString("Marketplace Validation Warning Create Version"); + } + cb(message,LLError::LEVEL_WARN); + } + LLUUID folder_uuid = gInventory.createNewCategory(parent_uuid, new_folder_type, folder_name); + // Move each item to the new folder + while (!items_vector[i].empty()) + { + LLViewerInventoryItem* viewer_inv_item = items_vector[i].back(); + if (cb) + { + std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Move"); + cb(message,LLError::LEVEL_WARN); + } + gInventory.changeItemParent(viewer_inv_item, folder_uuid, true); + items_vector[i].pop_back(); + } + update_marketplace_category(folder_uuid); + gInventory.notifyObservers(); + } + } + // Stock folder should have no sub folder so reparent those up + if (folder_type == LLFolderType::FT_MARKETPLACE_STOCK) + { + LLUUID parent_uuid = cat->getParentUUID(); + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (*iter); + gInventory.changeCategoryParent(viewer_cat, parent_uuid, false); + // Note : those reparented folders will be recursively visited and validated at the end of this function + } + } + } + else if (cb) + { + // We are not fixing the hierarchy but reporting problems, report everything we can find + // Print the folder name + if (result && (depth >= 1)) + { + if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (count >= 2)) + { + // Report if a stock folder contains a mix of items + result = false; + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Mixed Stock"); + cb(message,LLError::LEVEL_ERROR); + } + else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (cat_array->size() != 0)) + { + // Report if a stock folder contains subfolders + result = false; + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Subfolder In Stock"); + cb(message,LLError::LEVEL_ERROR); + } + else + { + // Simply print the folder name + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); + cb(message,LLError::LEVEL_INFO); + } + } + // Scan each item and report if there's a problem + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) item; + std::string error_msg; + if (!can_move_to_marketplace(item, error_msg, false)) + { + // Report items that shouldn't be there to start with + result = false; + std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; + cb(message,LLError::LEVEL_ERROR); + } + else if ((!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) && (folder_type != LLFolderType::FT_MARKETPLACE_STOCK)) + { + // Report stock items that are misplaced + result = false; + std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error Stock Item"); + cb(message,LLError::LEVEL_ERROR); + } + else if (depth == 1) + { + // Report items not wrapped in version folder + std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Unwrapped Item"); + cb(message,LLError::LEVEL_WARN); + } + } + } + + // Clean up + if (viewer_cat->getDescendentCount() == 0) + { + // Remove the current folder if it ends up empty + if (cb) + { + std::string message = indent + viewer_cat->getName() + LLTrans::getString("Marketplace Validation Warning Delete"); + cb(message,LLError::LEVEL_WARN); + } + gInventory.removeCategory(cat->getUUID()); + gInventory.notifyObservers(); + return result && !has_bad_items; + } + else + { + // Update the current folder + update_marketplace_category(cat->getUUID()); + gInventory.notifyObservers(); + } + } + + // Recursion : Perform the same validation on each nested folder + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + // Sort the folders in alphabetical order first + std::sort(cat_array_copy.begin(), cat_array_copy.end(), sort_alpha); + + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + result &= validate_marketplacelistings(category, cb, fix_hierarchy); + } + + return result && !has_bad_items; +} + +///---------------------------------------------------------------------------- /// LLInventoryCollectFunctor implementations ///---------------------------------------------------------------------------- @@ -1060,18 +2141,79 @@ void LLOpenFoldersWithSelection::doFolder(LLFolderViewFolder* folder) } } -void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root, const std::string& action) +// Callback for doToSelected if DAMA required... +void LLInventoryAction::callback_doToSelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + doToSelected(model, root, action, FALSE); + } +} + +void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root, const std::string& action, BOOL user_confirm) { + std::set<LLFolderViewItem*> selected_items = root->getSelectionList(); + + // Prompt the user and check for authorization for some marketplace active listing edits + if (user_confirm && (("delete" == action) || ("cut" == action) || ("rename" == action) || ("properties" == action) || ("task_properties" == action) || ("open" == action))) + { + std::set<LLFolderViewItem*>::iterator set_iter = selected_items.begin(); + LLFolderViewModelItemInventory * viewModel = NULL; + for (; set_iter != selected_items.end(); ++set_iter) + { + viewModel = dynamic_cast<LLFolderViewModelItemInventory *>((*set_iter)->getViewModelItem()); + if (viewModel && (depth_nesting_in_marketplace(viewModel->getUUID()) >= 0)) + { + break; + } + } + if (set_iter != selected_items.end()) + { + if ("open" == action) + { + if (get_can_item_be_worn(viewModel->getUUID())) + { + // Wearing an object from any listing, active or not, is verbotten + LLNotificationsUtil::add("AlertMerchantListingCannotWear"); + return; + } + // Note: we do not prompt for change when opening items (e.g. textures or note cards) on the marketplace... + } + else if (LLMarketplaceData::instance().isInActiveFolder(viewModel->getUUID()) || + LLMarketplaceData::instance().isListedAndActive(viewModel->getUUID())) + { + // If item is in active listing, further confirmation is required + if ((("cut" == action) || ("delete" == action)) && (LLMarketplaceData::instance().isListed(viewModel->getUUID()) || LLMarketplaceData::instance().isVersionFolder(viewModel->getUUID()))) + { + // Cut or delete of the active version folder or listing folder itself will unlist the listing so ask that question specifically + LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); + return; + } + // Any other case will simply modify but not unlist a listing + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); + return; + } + } + } + + // Keep track of the marketplace folders that will need update of their status/name after the operation is performed + buildMarketplaceFolders(root); + if ("rename" == action) { root->startRenamingSelectedItem(); + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); return; } + if ("delete" == action) { LLSD args; args["QUESTION"] = LLTrans::getString(root->getSelectedCount() > 1 ? "DeleteItems" : "DeleteItem"); LLNotificationsUtil::add("DeleteItems", args, LLSD(), boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root)); + // Note: marketplace listings will be updated in the callback if delete confirmed return; } if (("copy" == action) || ("cut" == action)) @@ -1089,12 +2231,12 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root LLViewerInventoryCategory *cat = model->getCategory(inventory_item->getUUID()); if (!cat) return; cat->changeType(new_folder_type); + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); return; } - std::set<LLFolderViewItem*> selected_items = root->getSelectionList(); - LLMultiPreview* multi_previewp = NULL; LLMultiProperties* multi_propertiesp = NULL; @@ -1125,6 +2267,9 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root bridge->performAction(model, action); } + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); + LLFloater::setFloaterHost(NULL); if (multi_previewp) { @@ -1168,5 +2313,47 @@ void LLInventoryAction::onItemsRemovalConfirmation( const LLSD& notification, co //because once removed from root folder view the item is no longer a selected item removeItemFromDND(root); root->removeSelectedItems(); + + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); } } + +void LLInventoryAction::buildMarketplaceFolders(LLFolderView* root) +{ + // Make a list of all marketplace folders containing the elements in the selected list + // as well as the elements themselves. + // Once those elements are updated (cut, delete in particular but potentially any action), their + // containing folder will need to be updated as well as their initially containing folder. For + // instance, moving a stock folder from a listed folder to another will require an update of the + // target listing *and* the original listing. So we need to keep track of both. + sMarketplaceFolders.clear(); + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + std::set<LLFolderViewItem*> selected_items = root->getSelectionList(); + std::set<LLFolderViewItem*>::iterator set_iter = selected_items.begin(); + LLFolderViewModelItemInventory * viewModel = NULL; + for (; set_iter != selected_items.end(); ++set_iter) + { + viewModel = dynamic_cast<LLFolderViewModelItemInventory *>((*set_iter)->getViewModelItem()); + if (viewModel && gInventory.isObjectDescendentOf(viewModel->getInventoryObject()->getParentUUID(), marketplacelistings_id)) + { + sMarketplaceFolders.push_back(viewModel->getInventoryObject()->getParentUUID()); + sMarketplaceFolders.push_back(viewModel->getInventoryObject()->getUUID()); + } + } + // Suppress dupes in the list so we won't update listings twice + sMarketplaceFolders.sort(); + sMarketplaceFolders.unique(); +} + +void LLInventoryAction::updateMarketplaceFolders() +{ + while (!sMarketplaceFolders.empty()) + { + update_marketplace_category(sMarketplaceFolders.back()); + sMarketplaceFolders.pop_back(); + } +} + + + diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index 6b3861aa79..b2497fb055 100755 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -58,6 +58,11 @@ void show_task_item_profile(const LLUUID& item_uuid, const LLUUID& object_id); void show_item_original(const LLUUID& item_uuid); void reset_inventory_filter(); +// Nudge the listing categories in the inventory to signal that their marketplace status changed +void update_marketplace_category(const LLUUID& cat_id, bool perform_consistency_enforcement = true); +// Nudge all listing categories to signal that their marketplace status changed +void update_all_marketplace_count(); + void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name); void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id = LLUUID::null); @@ -67,9 +72,19 @@ void append_path(const LLUUID& id, std::string& path); void copy_item_to_outbox(LLInventoryItem* inv_item, LLUUID dest_folder, const LLUUID& top_level_folder, S32 operation_id); void move_item_within_outbox(LLInventoryItem* inv_item, LLUUID dest_folder, S32 operation_id); - void copy_folder_to_outbox(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, const LLUUID& top_level_folder, S32 operation_id); +typedef boost::function<void(std::string& validation_message, LLError::ELevel log_level)> validation_callback_t; + +bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size = 1, bool from_paste = false); +bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size = 1, bool check_items = true, bool from_paste = false); +bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy = false); +bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy = false); +bool validate_marketplacelistings(LLInventoryCategory* inv_cat, validation_callback_t cb = NULL, bool fix_hierarchy = true); +S32 depth_nesting_in_marketplace(LLUUID cur_uuid); +LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth); +S32 compute_stock_count(LLUUID cat_uuid); + /** Miscellaneous global functions ** ** *******************************************************************************/ @@ -437,10 +452,16 @@ public: struct LLInventoryAction { - static void doToSelected(class LLInventoryModel* model, class LLFolderView* root, const std::string& action); + static void doToSelected(class LLInventoryModel* model, class LLFolderView* root, const std::string& action, BOOL user_confirm = TRUE); + static void callback_doToSelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action); static void onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLFolderView* root); static void removeItemFromDND(LLFolderView* root); + +private: + static void buildMarketplaceFolders(LLFolderView* root); + static void updateMarketplaceFolders(); + static std::list<LLUUID> sMarketplaceFolders; // Marketplace folders that will need update once the action is completed }; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index f92332dea5..1cfa0a9ebf 100755 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1023,7 +1023,7 @@ LLInventoryModel::item_array_t* LLInventoryModel::getUnlockedItemArray(const LLU // an existing item with the matching id, or it will add the category. void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32 mask) { - if(cat->getUUID().isNull()) + if(!cat || cat->getUUID().isNull()) { return; } @@ -1037,7 +1037,8 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32 LLPointer<LLViewerInventoryCategory> old_cat = getCategory(cat->getUUID()); if(old_cat) { - // We already have an old category, modify it's values + // We already have an old category, modify its values + U32 mask = LLInventoryObserver::NONE; LLUUID old_parent_id = old_cat->getParentUUID(); LLUUID new_parent_id = cat->getParentUUID(); if(old_parent_id != new_parent_id) @@ -1061,7 +1062,13 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32 { mask |= LLInventoryObserver::LABEL; } - old_cat->copyViewerCategory(cat); + // Under marketplace, category labels are quite complex and need extra upate + const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + if (marketplace_id.notNull() && isObjectDescendentOf(cat->getUUID(), marketplace_id)) + { + mask |= LLInventoryObserver::LABEL; + } + old_cat->copyViewerCategory(cat); addChangedMask(mask, cat->getUUID()); } else @@ -1411,6 +1418,11 @@ void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, boo LLPointer<LLViewerInventoryCategory> cat = (LLViewerInventoryCategory*)((LLInventoryObject*)obj); vector_replace_with_last(*cat_list, cat); } + + // Note : We need to tell the inventory observers that those things are going to be deleted *before* the tree is cleared or they won't know what to delete (in views and view models) + addChangedMask(LLInventoryObserver::REMOVE, id); + gInventory.notifyObservers(); + item_list = getUnlockedItemArray(id); if(item_list) { @@ -1558,10 +1570,11 @@ void LLInventoryModel::addChangedMask(U32 mask, const LLUUID& referent) } } - mModifyMask |= mask; - if (referent.notNull()) + mModifyMask |= mask; + if (referent.notNull() && (mChangedItemIDs.find(referent) == mChangedItemIDs.end())) { mChangedItemIDs.insert(referent); + update_marketplace_category(referent, false); if (mask & LLInventoryObserver::ADD) { diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 3546317471..86ff1aed1b 100755 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -191,6 +191,7 @@ LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id ) p.show_empty_message = mShowEmptyMessage; p.show_item_link_overlays = mShowItemLinkOverlays; p.root = NULL; + p.allow_drop = mParams.allow_drop_on_root; p.options_menu = "menu_inventory.xml"; return LLUICtrlFactory::create<LLFolderView>(p); @@ -290,7 +291,12 @@ void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_OUTBOX)); } - + // hide marketplace listing box, unless we want to show it (note: hacky as show_root_folder is only used for marketplace...) + if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && !mParams.show_root_folder) + { + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); + } + // set the filter for the empty folder if the debug setting is on if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders")) { @@ -599,13 +605,13 @@ void LLInventoryPanel::modelChanged(U32 mask) LLUUID LLInventoryPanel::getRootFolderID() { + LLUUID root_id; if (mFolderRoot.get() && mFolderRoot.get()->getViewModelItem()) { - return static_cast<LLFolderViewModelItemInventory*>(mFolderRoot.get()->getViewModelItem())->getUUID(); + root_id = static_cast<LLFolderViewModelItemInventory*>(mFolderRoot.get()->getViewModelItem())->getUUID(); } else { - LLUUID root_id; if (mParams.start_folder.id.isChosen()) { root_id = mParams.start_folder.id; @@ -633,8 +639,8 @@ LLUUID LLInventoryPanel::getRootFolderID() } } } - return root_id; } + return root_id; } // static @@ -756,7 +762,7 @@ void LLInventoryPanel::initializeViews() } -LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge) +LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop) { LLFolderViewFolder::Params params(mParams.folder); @@ -764,6 +770,7 @@ LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * br params.root = mFolderRoot.get(); params.listener = bridge; params.tool_tip = params.name; + params.allow_drop = allow_drop; params.font_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultColor)); params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultHighlightColor)); @@ -792,21 +799,40 @@ LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id) { LLInventoryObject const* objectp = gInventory.getObject(id); - if (!objectp) return NULL; + if (!objectp) + { + return NULL; + } LLFolderViewItem* folder_view_item = getItemByID(id); - const LLUUID &parent_id = objectp->getParentUUID(); + const LLUUID &parent_id = objectp->getParentUUID(); LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)getItemByID(parent_id); + // Force the creation of an extra root level folder item if required by the inventory panel (default is "false") + bool allow_drop = true; + bool create_root = false; + if (mParams.show_root_folder) + { + LLUUID root_id = getRootFolderID(); + if (root_id == id) + { + // We insert an extra level that's seen by the UI but has no influence on the model + parent_folder = dynamic_cast<LLFolderViewFolder*>(folder_view_item); + folder_view_item = NULL; + allow_drop = mParams.allow_drop_on_root; + create_root = true; + } + } + if (!folder_view_item && parent_folder) { if (objectp->getType() <= LLAssetType::AT_NONE || objectp->getType() >= LLAssetType::AT_COUNT) { LL_WARNS() << "LLInventoryPanel::buildNewViews called with invalid objectp->mType : " - << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() - << LL_ENDL; + << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() + << LL_ENDL; return NULL; } @@ -822,7 +848,7 @@ LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id) objectp->getUUID()); if (new_listener) { - folder_view_item = createFolderViewFolder(new_listener); + folder_view_item = createFolderViewFolder(new_listener,allow_drop); } } else @@ -845,11 +871,16 @@ LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id) } if (folder_view_item) - { + { llassert(parent_folder != NULL); folder_view_item->addToFolder(parent_folder); addItemID(id, folder_view_item); - } + // In the case of the root folder been shown, open that folder by default once the widget is created + if (create_root) + { + folder_view_item->setOpen(TRUE); + } + } } // If this is a folder, add the children of the folder and recursively add any @@ -958,7 +989,7 @@ BOOL LLInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, // If folder view is empty the (x, y) point won't be in its rect // so the handler must be called explicitly. // but only if was not handled before. See EXT-6746. - if (!handled && !mFolderRoot.get()->hasVisibleChildren()) + if (!handled && mParams.allow_drop_on_root && !mFolderRoot.get()->hasVisibleChildren()) { handled = mFolderRoot.get()->handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); } @@ -1523,5 +1554,8 @@ namespace LLInitParam declare(LLFolderType::lookup(LLFolderType::FT_INBOX) , LLFolderType::FT_INBOX); declare(LLFolderType::lookup(LLFolderType::FT_OUTBOX) , LLFolderType::FT_OUTBOX); declare(LLFolderType::lookup(LLFolderType::FT_BASIC_ROOT) , LLFolderType::FT_BASIC_ROOT); + declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_LISTINGS) , LLFolderType::FT_MARKETPLACE_LISTINGS); + declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_STOCK), LLFolderType::FT_MARKETPLACE_STOCK); + declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_VERSION), LLFolderType::FT_MARKETPLACE_VERSION); } } diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index a490dfce5d..822c7c2fe7 100755 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -96,6 +96,8 @@ public: Optional<StartFolder> start_folder; Optional<bool> use_label_suffix; Optional<bool> show_empty_message; + Optional<bool> show_root_folder; + Optional<bool> allow_drop_on_root; Optional<LLScrollContainer::Params> scroll; Optional<bool> accepts_drag_and_drop; Optional<LLFolderView::Params> folder_view; @@ -110,7 +112,9 @@ public: filter("filter"), start_folder("start_folder"), use_label_suffix("use_label_suffix", true), - show_empty_message("show_empty_message", true), + show_empty_message("show_empty_message", true), + show_root_folder("show_root_folder", false), + allow_drop_on_root("allow_drop_on_root", true), scroll("scroll"), accepts_drag_and_drop("accepts_drag_and_drop"), folder_view("folder_view"), @@ -184,6 +188,7 @@ public: LLFolderView* getRootFolder() { return mFolderRoot.get(); } LLUUID getRootFolderID(); LLScrollContainer* getScrollableContainer() { return mScroller; } + bool getAllowDropOnRoot() { return mParams.allow_drop_on_root; } void onSelectionChange(const std::deque<LLFolderViewItem*> &items, BOOL user_action); @@ -291,7 +296,7 @@ protected: BOOL getIsHiddenFolderType(LLFolderType::EType folder_type) const; virtual LLFolderView * createFolderRoot(LLUUID root_id ); - virtual LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge); + virtual LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); virtual LLFolderViewItem* createFolderViewItem(LLInvFVBridge * bridge); private: bool mBuildDefaultHierarchy; // default inventory hierarchy should be created in postBuild() diff --git a/indra/newview/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index 4a7a4e268d..c35b251c7e 100755 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -29,14 +29,22 @@ #include "llmarketplacefunctions.h" #include "llagent.h" +#include "llbufferstream.h" #include "llhttpclient.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "llnotificationsutil.h" #include "llsdserialize.h" #include "lltimer.h" #include "lltrans.h" #include "llviewercontrol.h" +#include "llviewerinventory.h" #include "llviewermedia.h" #include "llviewernetwork.h" +#include "llviewerregion.h" +#include "reader.h" // JSON +#include "writer.h" // JSON // // Helpers @@ -93,6 +101,532 @@ LLSD getMarketplaceStringSubstitutions() return marketplace_sub_map; } +/////////////////////////////////////////////////////////////////////////////// +// SLM Responders +void log_SLM_warning(const std::string& request, U32 status, const std::string& reason, const std::string& code, const std::string& description) +{ + LL_WARNS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", reason : " << reason << ", code : " << code << ", description : " << description << LL_ENDL; + // Prompt the user with the warning (so they know why things are failing) + LLSD subs; + subs["[ERROR_REASON]"] = reason; + // We do show long descriptions in the alert (unlikely to be readable). The description string will be in the log though. + subs["[ERROR_DESCRIPTION]"] = (description.length() <= 512 ? description : ""); + LLNotificationsUtil::add("MerchantTransactionFailed", subs); + +} +void log_SLM_infos(const std::string& request, U32 status, const std::string& body) +{ + if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) + { + LL_INFOS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", body or description : " << body << LL_ENDL; + } +} +void log_SLM_infos(const std::string& request, const std::string& url, const std::string& body) +{ + if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) + { + LL_INFOS("SLM") << "SLM API : Sending " << request << ". url : " << url << ", body : " << body << LL_ENDL; + } +} + +// Merov: This is a temporary hack used by dev while secondlife-staging is down... +// *TODO : Suppress that before shipping! +static bool sBypassMerchant = false; + +class LLSLMGetMerchantResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLSLMGetMerchantResponder); +public: + + LLSLMGetMerchantResponder() {} + +protected: + virtual void httpFailure() + { + if (sBypassMerchant) + { + // *TODO : Suppress that before shipping! + log_SLM_infos("Get /merchant", getStatus(), "SLM Connection error bypassed (debug only)"); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_MERCHANT); + } + else if (HTTP_NOT_FOUND == getStatus()) + { + log_SLM_infos("Get /merchant", getStatus(), "User is not a merchant"); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT); + } + else if (HTTP_SERVICE_UNAVAILABLE == getStatus()) + { + log_SLM_infos("Get /merchant", getStatus(), "Merchant is not migrated"); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); + } + else + { + log_SLM_warning("Get /merchant", getStatus(), getReason(), getContent().get("error_code"), getContent().get("error_description")); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE); + } + } + + virtual void httpSuccess() + { + log_SLM_infos("Get /merchant", getStatus(), "User is a merchant"); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_MERCHANT); + } + +}; + +class LLSLMGetListingsResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLSLMGetListingsResponder); +public: + + LLSLMGetListingsResponder(const LLUUID& folder_id) + { + mExpectedFolderId = folder_id; + } + + virtual void completedRaw(const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer) + { + LLMarketplaceData::instance().setUpdating(mExpectedFolderId,false); + + LLBufferStream istr(channels, buffer.get()); + std::stringstream strstrm; + strstrm << istr.rdbuf(); + const std::string body = strstrm.str(); + + if (!isGoodStatus()) + { + log_SLM_warning("Get /listings", getStatus(), getReason(), "", body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(body,root)) + { + log_SLM_warning("Get /listings", getStatus(), "Json parsing failed", reader.getFormatedErrorMessages(), body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Get /listings", getStatus(), body); + + // Extract the info from the Json string + Json::ValueIterator it = root["listings"].begin(); + + while (it != root["listings"].end()) + { + Json::Value listing = *it; + + int listing_id = listing["id"].asInt(); + bool is_listed = listing["is_listed"].asBool(); + std::string edit_url = listing["edit_url"].asString(); + std::string folder_uuid_string = listing["inventory_info"]["listing_folder_id"].asString(); + std::string version_uuid_string = listing["inventory_info"]["version_folder_id"].asString(); + int count = listing["inventory_info"]["count_on_hand"].asInt(); + + LLUUID folder_id(folder_uuid_string); + LLUUID version_id(version_uuid_string); + if (folder_id.notNull()) + { + LLMarketplaceData::instance().deleteListing(folder_id,false); + LLMarketplaceData::instance().addListing(folder_id,listing_id,version_id,is_listed,false); + LLMarketplaceData::instance().setListingURL(folder_id, edit_url,false); + LLMarketplaceData::instance().setCountOnHand(folder_id,count,false); + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + } + it++; + } + + // Update all folders under the root + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + } +private: + LLUUID mExpectedFolderId; +}; + +class LLSLMCreateListingsResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLSLMCreateListingsResponder); +public: + + LLSLMCreateListingsResponder(const LLUUID& folder_id) + { + mExpectedFolderId = folder_id; + } + + virtual void completedRaw(const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer) + { + LLMarketplaceData::instance().setUpdating(mExpectedFolderId,false); + + LLBufferStream istr(channels, buffer.get()); + std::stringstream strstrm; + strstrm << istr.rdbuf(); + const std::string body = strstrm.str(); + + if (!isGoodStatus()) + { + log_SLM_warning("Post /listings", getStatus(), getReason(), "", body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(body,root)) + { + log_SLM_warning("Post /listings", getStatus(), "Json parsing failed", reader.getFormatedErrorMessages(), body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Post /listings", getStatus(), body); + + // Extract the info from the Json string + Json::ValueIterator it = root["listings"].begin(); + + while (it != root["listings"].end()) + { + Json::Value listing = *it; + + int listing_id = listing["id"].asInt(); + bool is_listed = listing["is_listed"].asBool(); + std::string edit_url = listing["edit_url"].asString(); + std::string folder_uuid_string = listing["inventory_info"]["listing_folder_id"].asString(); + std::string version_uuid_string = listing["inventory_info"]["version_folder_id"].asString(); + int count = listing["inventory_info"]["count_on_hand"].asInt(); + + LLUUID folder_id(folder_uuid_string); + LLUUID version_id(version_uuid_string); + LLMarketplaceData::instance().addListing(folder_id,listing_id,version_id,is_listed, false); + LLMarketplaceData::instance().setListingURL(folder_id, edit_url, false); + LLMarketplaceData::instance().setCountOnHand(folder_id,count,false); + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + it++; + } + } +private: + LLUUID mExpectedFolderId; +}; + +class LLSLMGetListingResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLSLMGetListingResponder); +public: + + LLSLMGetListingResponder(const LLUUID& folder_id) + { + mExpectedFolderId = folder_id; + } + + virtual void completedRaw(const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer) + { + LLMarketplaceData::instance().setUpdating(mExpectedFolderId,false); + + LLBufferStream istr(channels, buffer.get()); + std::stringstream strstrm; + strstrm << istr.rdbuf(); + const std::string body = strstrm.str(); + + if (!isGoodStatus()) + { + log_SLM_warning("Get /listing", getStatus(), getReason(), "", body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(body,root)) + { + log_SLM_warning("Get /listing", getStatus(), "Json parsing failed", reader.getFormatedErrorMessages(), body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Get /listing", getStatus(), body); + + // Extract the info from the Json string + Json::ValueIterator it = root["listings"].begin(); + + while (it != root["listings"].end()) + { + Json::Value listing = *it; + + int listing_id = listing["id"].asInt(); + bool is_listed = listing["is_listed"].asBool(); + std::string edit_url = listing["edit_url"].asString(); + std::string folder_uuid_string = listing["inventory_info"]["listing_folder_id"].asString(); + std::string version_uuid_string = listing["inventory_info"]["version_folder_id"].asString(); + int count = listing["inventory_info"]["count_on_hand"].asInt(); + + LLUUID folder_id(folder_uuid_string); + LLUUID version_id(version_uuid_string); + + // Update that listing + LLMarketplaceData::instance().setListingID(folder_id, listing_id, false); + LLMarketplaceData::instance().setVersionFolderID(folder_id, version_id, false); + LLMarketplaceData::instance().setActivationState(folder_id, is_listed, false); + LLMarketplaceData::instance().setListingURL(folder_id, edit_url, false); + LLMarketplaceData::instance().setCountOnHand(folder_id,count,false); + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + + it++; + } + } +private: + LLUUID mExpectedFolderId; +}; + +class LLSLMUpdateListingsResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLSLMUpdateListingsResponder); +public: + + LLSLMUpdateListingsResponder(const LLUUID& folder_id, bool expected_listed_state, const LLUUID& expected_version_id) + { + mExpectedFolderId = folder_id; + mExpectedListedState = expected_listed_state; + mExpectedVersionUUID = expected_version_id; + } + + virtual void completedRaw(const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer) + { + LLMarketplaceData::instance().setUpdating(mExpectedFolderId,false); + + LLBufferStream istr(channels, buffer.get()); + std::stringstream strstrm; + strstrm << istr.rdbuf(); + const std::string body = strstrm.str(); + + if (!isGoodStatus()) + { + log_SLM_warning("Put /listing", getStatus(), getReason(), "", body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(body,root)) + { + log_SLM_warning("Put /listing", getStatus(), "Json parsing failed", reader.getFormatedErrorMessages(), body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Put /listing", getStatus(), body); + + // Extract the info from the Json string + Json::ValueIterator it = root["listings"].begin(); + + while (it != root["listings"].end()) + { + Json::Value listing = *it; + + int listing_id = listing["id"].asInt(); + bool is_listed = listing["is_listed"].asBool(); + std::string edit_url = listing["edit_url"].asString(); + std::string folder_uuid_string = listing["inventory_info"]["listing_folder_id"].asString(); + std::string version_uuid_string = listing["inventory_info"]["version_folder_id"].asString(); + int count = listing["inventory_info"]["count_on_hand"].asInt(); + + LLUUID folder_id(folder_uuid_string); + LLUUID version_id(version_uuid_string); + + // Update that listing + LLMarketplaceData::instance().setListingID(folder_id, listing_id, false); + LLMarketplaceData::instance().setVersionFolderID(folder_id, version_id, false); + LLMarketplaceData::instance().setActivationState(folder_id, is_listed, false); + LLMarketplaceData::instance().setListingURL(folder_id, edit_url, false); + LLMarketplaceData::instance().setCountOnHand(folder_id,count,false); + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + + // Show a notification alert if what we got is not what we expected + // (this actually doesn't result in an error status from the SLM API protocol) + if ((mExpectedListedState != is_listed) || (mExpectedVersionUUID != version_id)) + { + LLSD subs; + subs["[URL]"] = edit_url; + LLNotificationsUtil::add("AlertMerchantListingNotUpdated", subs); + } + + it++; + } + } +private: + LLUUID mExpectedFolderId; + bool mExpectedListedState; + LLUUID mExpectedVersionUUID; +}; + +class LLSLMAssociateListingsResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLSLMAssociateListingsResponder); +public: + + LLSLMAssociateListingsResponder(const LLUUID& folder_id, const LLUUID& source_folder_id) + { + mExpectedFolderId = folder_id; + mSourceFolderId = source_folder_id; + } + + virtual void completedRaw(const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer) + { + LLMarketplaceData::instance().setUpdating(mExpectedFolderId,false); + LLMarketplaceData::instance().setUpdating(mSourceFolderId,false); + + LLBufferStream istr(channels, buffer.get()); + std::stringstream strstrm; + strstrm << istr.rdbuf(); + const std::string body = strstrm.str(); + + if (!isGoodStatus()) + { + log_SLM_warning("Put /associate_inventory", getStatus(), getReason(), "", body); + update_marketplace_category(mExpectedFolderId, false); + update_marketplace_category(mSourceFolderId, false); + gInventory.notifyObservers(); + return; + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(body,root)) + { + log_SLM_warning("Put /associate_inventory", getStatus(), "Json parsing failed", reader.getFormatedErrorMessages(), body); + update_marketplace_category(mExpectedFolderId, false); + update_marketplace_category(mSourceFolderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Put /associate_inventory", getStatus(), body); + + // Extract the info from the Json string + Json::ValueIterator it = root["listings"].begin(); + + while (it != root["listings"].end()) + { + Json::Value listing = *it; + + int listing_id = listing["id"].asInt(); + bool is_listed = listing["is_listed"].asBool(); + std::string edit_url = listing["edit_url"].asString(); + std::string folder_uuid_string = listing["inventory_info"]["listing_folder_id"].asString(); + std::string version_uuid_string = listing["inventory_info"]["version_folder_id"].asString(); + int count = listing["inventory_info"]["count_on_hand"].asInt(); + + LLUUID folder_id(folder_uuid_string); + LLUUID version_id(version_uuid_string); + + // Check that the listing ID is not already associated to some other record + LLUUID old_listing = LLMarketplaceData::instance().getListingFolder(listing_id); + if (old_listing.notNull()) + { + // If it is already used, unlist the old record (we can't have 2 listings with the same listing ID) + LLMarketplaceData::instance().deleteListing(old_listing); + } + + // Add the new association + LLMarketplaceData::instance().addListing(folder_id,listing_id,version_id,is_listed, false); + LLMarketplaceData::instance().setListingURL(folder_id, edit_url, false); + LLMarketplaceData::instance().setCountOnHand(folder_id,count,false); + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + + // Alert with DAMA informing the user that a version folder must be designated + LLNotificationsUtil::add("AlertMerchantAssociateNeedsVersion"); + it++; + } + + // Always update the source folder so its widget updates + update_marketplace_category(mSourceFolderId, false); + } +private: + LLUUID mExpectedFolderId; // This is the folder now associated with the id. + LLUUID mSourceFolderId; // This is the folder initially associated with the id. Can be LLUUI::null +}; + +class LLSLMDeleteListingsResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLSLMDeleteListingsResponder); +public: + + LLSLMDeleteListingsResponder(const LLUUID& folder_id) + { + mExpectedFolderId = folder_id; + } + + virtual void completedRaw(const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer) + { + LLMarketplaceData::instance().setUpdating(mExpectedFolderId,false); + + LLBufferStream istr(channels, buffer.get()); + std::stringstream strstrm; + strstrm << istr.rdbuf(); + const std::string body = strstrm.str(); + + if (!isGoodStatus()) + { + log_SLM_warning("Delete /listing", getStatus(), getReason(), "", body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(body,root)) + { + log_SLM_warning("Delete /listing", getStatus(), "Json parsing failed", reader.getFormatedErrorMessages(), body); + update_marketplace_category(mExpectedFolderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Delete /listing", getStatus(), body); + + // Extract the info from the Json string + Json::ValueIterator it = root["listings"].begin(); + + while (it != root["listings"].end()) + { + Json::Value listing = *it; + + int listing_id = listing["id"].asInt(); + LLUUID folder_id = LLMarketplaceData::instance().getListingFolder(listing_id); + LLMarketplaceData::instance().deleteListing(folder_id); + + it++; + } + } +private: + LLUUID mExpectedFolderId; +}; + +// SLM Responders End +/////////////////////////////////////////////////////////////////////////////// + namespace LLMarketplaceImport { // Basic interface for this namespace @@ -156,7 +690,7 @@ namespace LLMarketplaceImport { if (gSavedSettings.getBOOL("InventoryOutboxLogging")) { - LL_INFOS() << " SLM POST clearing marketplace cookie due to client or server error" << LL_ENDL; + LL_INFOS() << " SLM POST : Clearing marketplace cookie due to client or server error" << LL_ENDL; } sMarketplaceCookie.clear(); } @@ -236,7 +770,7 @@ namespace LLMarketplaceImport S32 getResultStatus() { - return sImportResultStatus; + return sImportResultStatus; } const LLSD& getResults() @@ -427,15 +961,15 @@ void LLMarketplaceInventoryImporter::initialize() return; } - if (!LLMarketplaceImport::hasSessionCookie()) - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; - LLMarketplaceImport::establishMarketplaceSessionCookie(); - } - else - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; - } + if (!LLMarketplaceImport::hasSessionCookie()) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; + LLMarketplaceImport::establishMarketplaceSessionCookie(); + } + else + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; + } } void LLMarketplaceInventoryImporter::reinitializeAndTriggerImport() @@ -479,54 +1013,779 @@ void LLMarketplaceInventoryImporter::updateImport() // If we are no longer in progress if (!mImportInProgress) { - if (mInitialized) - { - // Report results - if (mStatusReportSignal) - { - (*mStatusReportSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); - } - } - else - { - // Look for results success - mInitialized = LLMarketplaceImport::hasSessionCookie(); - - if (mInitialized) - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; - // Follow up with auto trigger of import - if (mAutoTriggerImport) - { - mAutoTriggerImport = false; - mImportInProgress = triggerImport(); - } - } - else - { - U32 status = LLMarketplaceImport::getResultStatus(); - if ((status == MarketplaceErrorCodes::IMPORT_FORBIDDEN) || - (status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR)) - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT; - } - else - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; - } - if (mErrorInitSignal && (mMarketPlaceStatus == MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE)) - { - (*mErrorInitSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); - } - } - } - } + // Look for results success + mInitialized = LLMarketplaceImport::hasSessionCookie(); - // Make sure we trigger the status change with the final state (in case of auto trigger after initialize) - if (mStatusChangedSignal) - { - (*mStatusChangedSignal)(mImportInProgress); + // Report results + if (mStatusReportSignal) + { + (*mStatusReportSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); + } + + if (mInitialized) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; + // Follow up with auto trigger of import + if (mAutoTriggerImport) + { + mAutoTriggerImport = false; + mImportInProgress = triggerImport(); + } + } + else + { + U32 status = LLMarketplaceImport::getResultStatus(); + if ((status == MarketplaceErrorCodes::IMPORT_FORBIDDEN) || + (status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR)) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT; + } + else if (status == MarketplaceErrorCodes::IMPORT_SERVER_API_DISABLED) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT; + } + else + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; + } + if (mErrorInitSignal && (mMarketPlaceStatus == MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE)) + { + (*mErrorInitSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); + } + } } } + + // Make sure we trigger the status change with the final state (in case of auto trigger after initialize) + if (mStatusChangedSignal) + { + (*mStatusChangedSignal)(mImportInProgress); + } +} + +// +// Direct Delivery : Marketplace tuples and data +// +class LLMarketplaceInventoryObserver : public LLInventoryObserver +{ +public: + LLMarketplaceInventoryObserver() {} + virtual ~LLMarketplaceInventoryObserver() {} + virtual void changed(U32 mask); +}; + +void LLMarketplaceInventoryObserver::changed(U32 mask) +{ + // When things are changed in the inventory, this can trigger a host of changes in the marketplace listings folder: + // * stock counts changing : no copy items coming in and out will change the stock count on folders + // * version and listing folders : moving those might invalidate the marketplace data itself + // Since we should cannot raise inventory change while the observer is called (the list will be cleared + // once observers are called) we need to raise a flag in the inventory to signal that things have been dirtied. + + // That's the only changes that really do make sense for marketplace to worry about + if (mask & (LLInventoryObserver::INTERNAL | LLInventoryObserver::STRUCTURE)) + { + const std::set<LLUUID>& changed_items = gInventory.getChangedIDs(); + + std::set<LLUUID>::const_iterator id_it = changed_items.begin(); + std::set<LLUUID>::const_iterator id_end = changed_items.end(); + for (;id_it != id_end; ++id_it) + { + LLInventoryObject* obj = gInventory.getObject(*id_it); + if (obj) + { + if (LLAssetType::AT_CATEGORY == obj->getType()) + { + // If it's a folder known to the marketplace, let's check it's in proper shape + if (LLMarketplaceData::instance().isListed(*id_it) || LLMarketplaceData::instance().isVersionFolder(*id_it)) + { + LLInventoryCategory* cat = (LLInventoryCategory*)(obj); + validate_marketplacelistings(cat); + } + } + else + { + // If it's not a category, it's an item... + LLInventoryItem* item = (LLInventoryItem*)(obj); + // If it's a no copy item, we may need to update the label count of marketplace listings + if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + LLMarketplaceData::instance().setDirtyCount(); + } + } + } + } + } +} + +// Tuple == Item +LLMarketplaceTuple::LLMarketplaceTuple() : + mListingFolderId(), + mListingId(0), + mVersionFolderId(), + mIsActive(false), + mEditURL("") +{ +} + +LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id) : + mListingFolderId(folder_id), + mListingId(0), + mVersionFolderId(), + mIsActive(false), + mEditURL("") +{ +} + +LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed) : + mListingFolderId(folder_id), + mListingId(listing_id), + mVersionFolderId(version_id), + mIsActive(is_listed), + mEditURL("") +{ +} + + +// Data map +LLMarketplaceData::LLMarketplaceData() : + mMarketPlaceStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED), + mStatusUpdatedSignal(NULL), + mDirtyCount(false) +{ + mInventoryObserver = new LLMarketplaceInventoryObserver; + gInventory.addObserver(mInventoryObserver); +} + +LLMarketplaceData::~LLMarketplaceData() +{ + gInventory.removeObserver(mInventoryObserver); +} + +void LLMarketplaceData::initializeSLM(const status_updated_signal_t::slot_type& cb) +{ + if (mStatusUpdatedSignal == NULL) + { + mStatusUpdatedSignal = new status_updated_signal_t(); + } + mStatusUpdatedSignal->connect(cb); + + if (mMarketPlaceStatus != MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) + { + // If already initialized, just confirm the status so the callback gets called + setSLMStatus(mMarketPlaceStatus); + } + else + { + // Initiate SLM connection and set responder + std::string url = getSLMConnectURL("/merchant"); + if (url != "") + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; + log_SLM_infos("LLHTTPClient::get", url, ""); + LLHTTPClient::get(url, new LLSLMGetMerchantResponder(), LLSD()); + } + } +} + +// Get/Post/Put requests to the SLM Server using the SLM API +void LLMarketplaceData::getSLMListings() +{ + LLSD headers = LLSD::emptyMap(); + headers["Accept"] = "application/json"; + headers["Content-Type"] = "application/json"; + + // Send request + std::string url = getSLMConnectURL("/listings"); + log_SLM_infos("LLHTTPClient::get", url, ""); + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + setUpdating(marketplacelistings_id,true); + LLHTTPClient::get(url, new LLSLMGetListingsResponder(marketplacelistings_id), headers); +} + +void LLMarketplaceData::getSLMListing(S32 listing_id) +{ + LLSD headers = LLSD::emptyMap(); + headers["Accept"] = "application/json"; + headers["Content-Type"] = "application/json"; + + // Send request + std::string url = getSLMConnectURL("/listing/") + llformat("%d",listing_id); + log_SLM_infos("LLHTTPClient::get", url, ""); + LLUUID folder_id = LLMarketplaceData::instance().getListingFolder(listing_id); + setUpdating(folder_id,true); + LLHTTPClient::get(url, new LLSLMGetListingResponder(folder_id), headers); +} + +void LLMarketplaceData::createSLMListing(const LLUUID& folder_id) +{ + LLSD headers = LLSD::emptyMap(); + headers["Accept"] = "application/json"; + headers["Content-Type"] = "application/json"; + + LLViewerInventoryCategory* category = gInventory.getCategory(folder_id); + + Json::Value root; + Json::FastWriter writer; + + root["listing"]["name"] = category->getName(); + root["listing"]["inventory_info"]["listing_folder_id"] = category->getUUID().asString(); + + std::string json_str = writer.write(root); + + // postRaw() takes ownership of the buffer and releases it later. + size_t size = json_str.size(); + U8 *data = new U8[size]; + memcpy(data, (U8*)(json_str.c_str()), size); + + // Send request + std::string url = getSLMConnectURL("/listings"); + log_SLM_infos("LLHTTPClient::postRaw", url, json_str); + setUpdating(folder_id,true); + LLHTTPClient::postRaw(url, data, size, new LLSLMCreateListingsResponder(folder_id), headers); +} + +void LLMarketplaceData::updateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, S32 count) +{ + LLSD headers = LLSD::emptyMap(); + headers["Accept"] = "application/json"; + headers["Content-Type"] = "application/json"; + + Json::Value root; + Json::FastWriter writer; + + // Note : we're assuming that sending unchanged info won't break anything server side... + root["listing"]["id"] = listing_id; + root["listing"]["is_listed"] = is_listed; + root["listing"]["inventory_info"]["listing_folder_id"] = folder_id.asString(); + root["listing"]["inventory_info"]["version_folder_id"] = version_id.asString(); + root["listing"]["inventory_info"]["count_on_hand"] = count; + + std::string json_str = writer.write(root); + + // postRaw() takes ownership of the buffer and releases it later. + size_t size = json_str.size(); + U8 *data = new U8[size]; + memcpy(data, (U8*)(json_str.c_str()), size); + + // Send request + std::string url = getSLMConnectURL("/listing/") + llformat("%d",listing_id); + log_SLM_infos("LLHTTPClient::putRaw", url, json_str); + setUpdating(folder_id,true); + LLHTTPClient::putRaw(url, data, size, new LLSLMUpdateListingsResponder(folder_id, is_listed, version_id), headers); +} + +void LLMarketplaceData::associateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& source_folder_id) +{ + LLSD headers = LLSD::emptyMap(); + headers["Accept"] = "application/json"; + headers["Content-Type"] = "application/json"; + + Json::Value root; + Json::FastWriter writer; + + // Note : we're assuming that sending unchanged info won't break anything server side... + root["listing"]["id"] = listing_id; + root["listing"]["is_listed"] = false; + root["listing"]["inventory_info"]["listing_folder_id"] = folder_id.asString(); + root["listing"]["inventory_info"]["version_folder_id"] = LLUUID::null.asString(); + root["listing"]["inventory_info"]["count_on_hand"] = -1; + + std::string json_str = writer.write(root); + + // postRaw() takes ownership of the buffer and releases it later. + size_t size = json_str.size(); + U8 *data = new U8[size]; + memcpy(data, (U8*)(json_str.c_str()), size); + + // Send request + std::string url = getSLMConnectURL("/associate_inventory/") + llformat("%d",listing_id); + log_SLM_infos("LLHTTPClient::putRaw", url, json_str); + setUpdating(folder_id,true); + setUpdating(source_folder_id,true); + LLHTTPClient::putRaw(url, data, size, new LLSLMAssociateListingsResponder(folder_id,source_folder_id), headers); } +void LLMarketplaceData::deleteSLMListing(S32 listing_id) +{ + LLSD headers = LLSD::emptyMap(); + headers["Accept"] = "application/json"; + headers["Content-Type"] = "application/json"; + + // Send request + std::string url = getSLMConnectURL("/listing/") + llformat("%d",listing_id); + log_SLM_infos("LLHTTPClient::del", url, ""); + LLUUID folder_id = LLMarketplaceData::instance().getListingFolder(listing_id); + setUpdating(folder_id,true); + LLHTTPClient::del(url, new LLSLMDeleteListingsResponder(folder_id), headers); +} + +std::string LLMarketplaceData::getSLMConnectURL(const std::string& route) +{ + std::string url(""); + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + // Get DirectDelivery cap + url = regionp->getCapability("DirectDelivery"); + if (url != "") + { + url += route; + } + } + return url; +} + +void LLMarketplaceData::setSLMStatus(U32 status) +{ + mMarketPlaceStatus = status; + if (mStatusUpdatedSignal) + { + (*mStatusUpdatedSignal)(); + } +} + +// Creation / Deletion / Update +// Methods publicly called +bool LLMarketplaceData::createListing(const LLUUID& folder_id) +{ + if (isListed(folder_id)) + { + // Listing already exists -> exit with error + return false; + } + + // Post the listing creation request to SLM + createSLMListing(folder_id); + + return true; +} + +bool LLMarketplaceData::clearListing(const LLUUID& folder_id) +{ + if (folder_id.isNull()) + { + // Folder doesn't exists -> exit with error + return false; + } + + // Folder id can be the root of the listing or not so we need to retrieve the root first + S32 depth = depth_nesting_in_marketplace(folder_id); + LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); + S32 listing_id = getListingID(listing_uuid); + + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + // Update the SLM Server so that this listing is deleted (actually, archived...) + deleteSLMListing(listing_id); + + return true; +} + +bool LLMarketplaceData::getListing(const LLUUID& folder_id) +{ + if (folder_id.isNull()) + { + // Folder doesn't exists -> exit with error + return false; + } + + // Folder id can be the root of the listing or not so we need to retrieve the root first + S32 depth = depth_nesting_in_marketplace(folder_id); + LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); + S32 listing_id = getListingID(listing_uuid); + + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + // Get listing data from SLM + getSLMListing(listing_id); + + return true; +} + +bool LLMarketplaceData::getListing(S32 listing_id) +{ + if (listing_id == 0) + { + return false; + } + + // Get listing data from SLM + getSLMListing(listing_id); + return true; +} + +bool LLMarketplaceData::activateListing(const LLUUID& folder_id, bool activate) +{ + // Folder id can be the root of the listing or not so we need to retrieve the root first + S32 depth = depth_nesting_in_marketplace(folder_id); + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + S32 listing_id = getListingID(listing_uuid); + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + LLUUID version_uuid = getVersionFolder(listing_uuid); + + // Also update the count on hand + S32 count = compute_stock_count(folder_id); + + // Post the listing update request to SLM + updateSLMListing(listing_uuid, listing_id, version_uuid, activate, count); + + return true; +} + +bool LLMarketplaceData::setVersionFolder(const LLUUID& folder_id, const LLUUID& version_id) +{ + // Folder id can be the root of the listing or not so we need to retrieve the root first + S32 depth = depth_nesting_in_marketplace(folder_id); + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + S32 listing_id = getListingID(listing_uuid); + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + // Note: if the version_id is cleared, we need to unlist the listing, otherwise, state unchanged + bool is_listed = (version_id.isNull() ? false : getActivationState(listing_uuid)); + + // Also update the count on hand + S32 count = compute_stock_count(version_id); + + // Post the listing update request to SLM + updateSLMListing(listing_uuid, listing_id, version_id, is_listed, count); + + return true; +} + +bool LLMarketplaceData::updateCountOnHand(const LLUUID& folder_id) +{ + // Folder id can be the root of the listing or not so we need to retrieve the root first + S32 depth = depth_nesting_in_marketplace(folder_id); + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + S32 listing_id = getListingID(listing_uuid); + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + // Get the unchanged values + bool is_listed = getActivationState(listing_uuid); + LLUUID version_uuid = getVersionFolder(listing_uuid); + + // Compute the new count on hand + S32 count = compute_stock_count(folder_id); + + // Post the listing update request to SLM + updateSLMListing(listing_uuid, listing_id, version_uuid, is_listed, count); + + return true; +} + +bool LLMarketplaceData::associateListing(const LLUUID& folder_id, const LLUUID& source_folder_id, S32 listing_id) +{ + if (isListed(folder_id)) + { + // Listing already exists -> exit with error + return false; + } + + // Post the listing update request to SLM + associateSLMListing(folder_id, listing_id, source_folder_id); + + return true; +} + +// Methods privately called or called by SLM responders to perform changes +bool LLMarketplaceData::addListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, bool update) +{ + if (isListed(folder_id)) + { + // Listing already exists -> exit with error + return false; + } + mMarketplaceItems[folder_id] = LLMarketplaceTuple(folder_id, listing_id, version_id, is_listed); + + if (update) + { + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::deleteListing(const LLUUID& folder_id, bool update) +{ + if (!isListed(folder_id)) + { + // Listing doesn't exist -> exit with error + return false; + } + mMarketplaceItems.erase(folder_id); + + if (update) + { + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::deleteListing(S32 listing_id, bool update) +{ + if (listing_id == 0) + { + return false; + } + + LLUUID folder_id = getListingFolder(listing_id); + return deleteListing(folder_id, update); +} + +// Accessors +bool LLMarketplaceData::getActivationState(const LLUUID& folder_id) +{ + // Listing folder case + if (isListed(folder_id)) + { + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it->second).mIsActive; + } + // We need to iterate through the list to check it's not a version folder + marketplace_items_list_t::iterator it = mMarketplaceItems.begin(); + while (it != mMarketplaceItems.end()) + { + if ((it->second).mVersionFolderId == folder_id) + { + return (it->second).mIsActive; + } + it++; + } + return false; +} + +S32 LLMarketplaceData::getListingID(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it == mMarketplaceItems.end() ? 0 : (it->second).mListingId); +} + +S32 LLMarketplaceData::getCountOnHand(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it == mMarketplaceItems.end() ? -1 : (it->second).mCountOnHand); +} + +LLUUID LLMarketplaceData::getVersionFolder(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it == mMarketplaceItems.end() ? LLUUID::null : (it->second).mVersionFolderId); +} + +// Reverse lookup : find the listing folder id from the listing id +LLUUID LLMarketplaceData::getListingFolder(S32 listing_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.begin(); + while (it != mMarketplaceItems.end()) + { + if ((it->second).mListingId == listing_id) + { + return (it->second).mListingFolderId; + } + it++; + } + return LLUUID::null; +} + +std::string LLMarketplaceData::getListingURL(const LLUUID& folder_id) +{ + S32 depth = depth_nesting_in_marketplace(folder_id); + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + + marketplace_items_list_t::iterator it = mMarketplaceItems.find(listing_uuid); + return (it == mMarketplaceItems.end() ? "" : (it->second).mEditURL); +} + +bool LLMarketplaceData::isListed(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it != mMarketplaceItems.end()); +} + +bool LLMarketplaceData::isListedAndActive(const LLUUID& folder_id) +{ + return (isListed(folder_id) && getActivationState(folder_id)); +} + +bool LLMarketplaceData::isVersionFolder(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.begin(); + while (it != mMarketplaceItems.end()) + { + if ((it->second).mVersionFolderId == folder_id) + { + return true; + } + it++; + } + return false; +} + +bool LLMarketplaceData::isInActiveFolder(const LLUUID& obj_id) +{ + S32 depth = depth_nesting_in_marketplace(obj_id); + LLUUID listing_uuid = nested_parent_id(obj_id, depth); + bool active = getActivationState(listing_uuid); + LLUUID version_uuid = getVersionFolder(listing_uuid); + return (active && ((obj_id == version_uuid) || gInventory.isObjectDescendentOf(obj_id, version_uuid))); +} + +LLUUID LLMarketplaceData::getActiveFolder(const LLUUID& obj_id) +{ + S32 depth = depth_nesting_in_marketplace(obj_id); + LLUUID listing_uuid = nested_parent_id(obj_id, depth); + return (getActivationState(listing_uuid) ? getVersionFolder(listing_uuid) : LLUUID::null); +} + +bool LLMarketplaceData::isUpdating(const LLUUID& folder_id) +{ + S32 depth = depth_nesting_in_marketplace(folder_id); + if ((depth <= 0) || (depth > 2)) + { + // Only listing and version folders though are concerned by that status + return false; + } + else + { + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + std::set<LLUUID>::iterator it = mPendingUpdateSet.find(marketplace_listings_uuid); + if (it != mPendingUpdateSet.end()) + { + // If we're waiting for data for the marketplace listings root, we are in the updating process for all + return true; + } + else + { + // Check if the listing folder is waiting or data + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + it = mPendingUpdateSet.find(listing_uuid); + return (it != mPendingUpdateSet.end()); + } + } +} + +void LLMarketplaceData::setUpdating(const LLUUID& folder_id, bool isUpdating) +{ + std::set<LLUUID>::iterator it = mPendingUpdateSet.find(folder_id); + if (it != mPendingUpdateSet.end()) + { + mPendingUpdateSet.erase(it); + } + if (isUpdating) + { + mPendingUpdateSet.insert(folder_id); + } +} + +// Private Modifiers +bool LLMarketplaceData::setListingID(const LLUUID& folder_id, S32 listing_id, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mListingId = listing_id; + + if (update) + { + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::setCountOnHand(const LLUUID& folder_id, S32 count, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mCountOnHand = count; + + return true; +} + +bool LLMarketplaceData::setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + LLUUID old_version_id = (it->second).mVersionFolderId; + if (old_version_id == version_id) + { + return false; + } + + (it->second).mVersionFolderId = version_id; + + if (update) + { + update_marketplace_category(old_version_id, false); + update_marketplace_category(version_id, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::setActivationState(const LLUUID& folder_id, bool activate, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mIsActive = activate; + + if (update) + { + update_marketplace_category((it->second).mListingFolderId, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::setListingURL(const LLUUID& folder_id, const std::string& edit_url, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mEditURL = edit_url; + return true; +} + + + diff --git a/indra/newview/llmarketplacefunctions.h b/indra/newview/llmarketplacefunctions.h index abe60890a3..4aca88cba1 100755 --- a/indra/newview/llmarketplacefunctions.h +++ b/indra/newview/llmarketplacefunctions.h @@ -68,6 +68,8 @@ namespace MarketplaceStatusCodes MARKET_PLACE_CONNECTION_FAILURE = 2, MARKET_PLACE_MERCHANT = 3, MARKET_PLACE_NOT_MERCHANT = 4, + MARKET_PLACE_NOT_MIGRATED_MERCHANT = 5, + MARKET_PLACE_MIGRATED_MERCHANT = 6 }; } @@ -109,6 +111,150 @@ private: }; +// Classes handling the data coming from and going to the Marketplace SLM Server DB: +// * implement the Marketplace API +// * cache the current Marketplace data (tuples) +// * provide methods to get Marketplace data on any inventory item +// * set Marketplace data +// * signal Marketplace updates to inventory +namespace SLMErrorCodes +{ + enum eCode + { + SLM_SUCCESS = 200, + SLM_RECORD_CREATED = 201, + SLM_MALFORMED_PAYLOAD = 400, + SLM_NOT_FOUND = 404, + }; +} + +class LLMarketplaceData; +class LLInventoryObserver; + +// A Marketplace item is known by its tuple +class LLMarketplaceTuple +{ +public: + friend class LLMarketplaceData; + + LLMarketplaceTuple(); + LLMarketplaceTuple(const LLUUID& folder_id); + LLMarketplaceTuple(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed = false); + +private: + // Representation of a marketplace item in the Marketplace DB (well, what we know of it...) + LLUUID mListingFolderId; + S32 mListingId; + LLUUID mVersionFolderId; + bool mIsActive; + S32 mCountOnHand; + std::string mEditURL; +}; +// Notes: +// * The mListingFolderId is used as a key to this map. It could therefore be taken off the LLMarketplaceTuple objects themselves. +// * The SLM DB however uses mListingId as its primary key and it shows in its API. In the viewer though, the mListingFolderId is what we use to grab an inventory record. +typedef std::map<LLUUID, LLMarketplaceTuple> marketplace_items_list_t; + +// Session cache of all Marketplace tuples +// Notes: +// * There's one and only one possible set of Marketplace dataset per agent and per session thus making it an LLSingleton +// * Some of those records might correspond to folders that do not exist in the inventory anymore. We do not clear them out though. They just won't show up in the UI. + +class LLSLMGetMerchantResponder; +class LLSLMGetListingsResponder; +class LLSLMCreateListingsResponder; +class LLSLMGetListingResponder; +class LLSLMUpdateListingsResponder; +class LLSLMAssociateListingsResponder; +class LLSLMDeleteListingsResponder; + +class LLMarketplaceData + : public LLSingleton<LLMarketplaceData> +{ +public: + friend class LLSLMGetMerchantResponder; + friend class LLSLMGetListingsResponder; + friend class LLSLMCreateListingsResponder; + friend class LLSLMGetListingResponder; + friend class LLSLMUpdateListingsResponder; + friend class LLSLMAssociateListingsResponder; + friend class LLSLMDeleteListingsResponder; + + LLMarketplaceData(); + virtual ~LLMarketplaceData(); + + // Public SLM API : Initialization and status + typedef boost::signals2::signal<void ()> status_updated_signal_t; + void initializeSLM(const status_updated_signal_t::slot_type& cb); + U32 getSLMStatus() const { return mMarketPlaceStatus; } + void setSLMStatus(U32 status); + void getSLMListings(); + bool isEmpty() { return (mMarketplaceItems.size() == 0); } + + // High level create/delete/set Marketplace data: each method returns true if the function succeeds, false if error + bool createListing(const LLUUID& folder_id); + bool activateListing(const LLUUID& folder_id, bool activate); + bool clearListing(const LLUUID& folder_id); + bool setVersionFolder(const LLUUID& folder_id, const LLUUID& version_id); + bool associateListing(const LLUUID& folder_id, const LLUUID& source_folder_id, S32 listing_id); + bool updateCountOnHand(const LLUUID& folder_id); + bool getListing(const LLUUID& folder_id); + bool getListing(S32 listing_id); + bool deleteListing(S32 listing_id, bool update = true); + + // Probe the Marketplace data set to identify folders + bool isListed(const LLUUID& folder_id); // returns true if folder_id is a Listing folder + bool isListedAndActive(const LLUUID& folder_id); // returns true if folder_id is an active (listed) Listing folder + bool isVersionFolder(const LLUUID& folder_id); // returns true if folder_id is a Version folder + bool isInActiveFolder(const LLUUID& obj_id); // returns true if the obj_id is buried in an active version folder + LLUUID getActiveFolder(const LLUUID& obj_id); // returns the UUID of the active version folder obj_id is in + bool isUpdating(const LLUUID& folder_id); // returns true if we're waiting from SLM incoming data for folder_id + + // Access Marketplace data set : each method returns a default value if the argument can't be found + bool getActivationState(const LLUUID& folder_id); + S32 getListingID(const LLUUID& folder_id); + LLUUID getVersionFolder(const LLUUID& folder_id); + std::string getListingURL(const LLUUID& folder_id); + LLUUID getListingFolder(S32 listing_id); + S32 getCountOnHand(const LLUUID& folder_id); + + // Used to flag if stock count values for Marketplace have to be updated + bool checkDirtyCount() { if (mDirtyCount) { mDirtyCount = false; return true; } else { return false; } } + void setDirtyCount() { mDirtyCount = true; } + void setUpdating(const LLUUID& folder_id, bool isUpdating); + +private: + // Modify Marketplace data set : each method returns true if the function succeeds, false if error + // Used internally only by SLM Responders when data are received from the SLM Server + bool addListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, bool update = true); + bool deleteListing(const LLUUID& folder_id, bool update = true); + bool setListingID(const LLUUID& folder_id, S32 listing_id, bool update = true); + bool setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id, bool update = true); + bool setActivationState(const LLUUID& folder_id, bool activate, bool update = true); + bool setListingURL(const LLUUID& folder_id, const std::string& edit_url, bool update = true); + bool setCountOnHand(const LLUUID& folder_id, S32 count, bool update = true); + + // Private SLM API : package data and get/post/put requests to the SLM Server through the SLM API + void createSLMListing(const LLUUID& folder_id); + void getSLMListing(S32 listing_id); + void updateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, S32 count); + void associateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id); + void deleteSLMListing(S32 listing_id); + std::string getSLMConnectURL(const std::string& route); + + // Handling Marketplace connection and inventory connection + U32 mMarketPlaceStatus; + status_updated_signal_t* mStatusUpdatedSignal; + LLInventoryObserver* mInventoryObserver; + bool mDirtyCount; // If true, stock count value need to be updated at the next check + + // Update data + std::set<LLUUID> mPendingUpdateSet; + + // The cache of SLM data (at last...) + marketplace_items_list_t mMarketplaceItems; +}; + #endif // LL_LLMARKETPLACEFUNCTIONS_H diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp index f7c2f629ec..c5fda3c136 100755 --- a/indra/newview/llpanelmarketplaceinboxinventory.cpp +++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp @@ -62,7 +62,7 @@ LLInboxInventoryPanel::LLInboxInventoryPanel(const LLInboxInventoryPanel::Params LLInboxInventoryPanel::~LLInboxInventoryPanel() {} -LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge) +LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop) { LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); @@ -74,6 +74,7 @@ LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge params.tool_tip = params.name; params.font_color = item_color; params.font_highlight_color = item_color; + params.allow_drop = allow_drop; return LLUICtrlFactory::create<LLInboxFolderViewFolder>(params); } diff --git a/indra/newview/llpanelmarketplaceinboxinventory.h b/indra/newview/llpanelmarketplaceinboxinventory.h index c05e18c300..66aafe83d1 100755 --- a/indra/newview/llpanelmarketplaceinboxinventory.h +++ b/indra/newview/llpanelmarketplaceinboxinventory.h @@ -46,7 +46,7 @@ public: ~LLInboxInventoryPanel(); // virtual - LLFolderViewFolder * createFolderViewFolder(LLInvFVBridge * bridge); + LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); LLFolderViewItem * createFolderViewItem(LLInvFVBridge * bridge); }; diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 6354b5a02b..a7a0196770 100755 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -130,7 +130,7 @@ public: virtual void move(LLFolderViewModelItem* parent_listener); virtual BOOL isItemCopyable() const; virtual BOOL copyToClipboard() const; - virtual BOOL cutToClipboard() const; + virtual BOOL cutToClipboard(); virtual BOOL isClipboardPasteable() const; virtual void pasteFromClipboard(); virtual void pasteLinkFromClipboard(); @@ -542,7 +542,7 @@ BOOL LLTaskInvFVBridge::copyToClipboard() const return FALSE; } -BOOL LLTaskInvFVBridge::cutToClipboard() const +BOOL LLTaskInvFVBridge::cutToClipboard() { return FALSE; } diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 9da7717b74..cfc654eb98 100755 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -1875,6 +1875,11 @@ bool idle_startup() } display_startup(); + + // *TODO : Uncomment that line once the whole grid migrated to SLM and suppress it from LLAgent::handleTeleportFinished() (llagent.cpp) + //check_merchant_status(); + + display_startup(); if (gSavedSettings.getBOOL("HelpFloaterOpen")) { diff --git a/indra/newview/lltoastalertpanel.cpp b/indra/newview/lltoastalertpanel.cpp index c82894a5cc..5aaba6614e 100755 --- a/indra/newview/lltoastalertpanel.cpp +++ b/indra/newview/lltoastalertpanel.cpp @@ -68,19 +68,19 @@ LLToastAlertPanel::LLToastAlertPanel( LLNotificationPtr notification, bool modal mLabel(notification->getName()), mLineEditor(NULL) { - // EXP-1822 - // save currently focused view, so that return focus to it - // on destroying this toast. - LLView* current_selection = dynamic_cast<LLView*>(gFocusMgr.getKeyboardFocus()); - while(current_selection) - { - if (current_selection->isFocusRoot()) - { - mPreviouslyFocusedView = current_selection->getHandle(); - break; - } - current_selection = current_selection->getParent(); - } + // EXP-1822 + // save currently focused view, so that return focus to it + // on destroying this toast. + LLView* current_selection = dynamic_cast<LLView*>(gFocusMgr.getKeyboardFocus()); + while(current_selection) + { + if (current_selection->isFocusRoot()) + { + mPreviouslyFocusedView = current_selection->getHandle(); + break; + } + current_selection = current_selection->getParent(); + } const LLFontGL* font = LLFontGL::getFontSansSerif(); const S32 LINE_HEIGHT = font->getLineHeight(); @@ -432,7 +432,24 @@ LLToastAlertPanel::~LLToastAlertPanel() // return focus to the previously focused view if the viewer is not exiting if (mPreviouslyFocusedView.get() && !LLApp::isExiting()) { - mPreviouslyFocusedView.get()->setFocus(TRUE); + LLView* current_selection = dynamic_cast<LLView*>(gFocusMgr.getKeyboardFocus()); + while(current_selection) + { + if (current_selection->isFocusRoot()) + { + break; + } + current_selection = current_selection->getParent(); + } + if (current_selection) + { + // If the focus moved to some other view though, move the focus there + current_selection->setFocus(TRUE); + } + else + { + mPreviouslyFocusedView.get()->setFocus(TRUE); + } } } diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index 8561d265de..9172c896cf 100755 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -820,6 +820,7 @@ void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, BOOL drop, if (!handled) { + // *TODO: Suppress the "outbox" case once "marketplace" is used everywhere for everyone // Disallow drag and drop to 3D from the outbox const LLUUID outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); if (outbox_id.notNull()) @@ -834,6 +835,20 @@ void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, BOOL drop, } } } + // Disallow drag and drop to 3D from the marketplace + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + if (marketplacelistings_id.notNull()) + { + for (S32 item_index = 0; item_index < (S32)mCargoIDs.size(); item_index++) + { + if (gInventory.isObjectDescendentOf(mCargoIDs[item_index], marketplacelistings_id)) + { + *acceptance = ACCEPT_NO; + mToolTipMsg = LLTrans::getString("TooltipOutboxDragToWorld"); + return; + } + } + } dragOrDrop3D( x, y, mask, drop, acceptance ); } diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index 175227173f..3ecb4015ce 100755 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -37,6 +37,7 @@ #include "llfloaterinspect.h" #include "lltool.h" #include "llmanipscale.h" +#include "llmarketplacefunctions.h" #include "llselectmgr.h" #include "lltoolbrush.h" #include "lltoolcomp.h" @@ -83,6 +84,8 @@ LLToolMgr::LLToolMgr() LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Active", boost::bind(&LLToolMgr::inEdit, this)); LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Enabled", boost::bind(&LLToolMgr::canEdit, this)); LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Build.Toggle", boost::bind(&LLToolMgr::toggleBuildMode, this, _2)); + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Marketplace.Enabled", boost::bind(&LLToolMgr::canAccessMarketplace, this)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Marketplace.Toggle", boost::bind(&LLToolMgr::toggleMarketplace, this, _2)); gToolNull = new LLTool(LLStringUtil::null); // Does nothing setCurrentTool(gToolNull); @@ -345,6 +348,23 @@ bool LLToolMgr::inBuildMode() return b; } +bool LLToolMgr::canAccessMarketplace() +{ + return (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT) || gSavedSettings.getBOOL("InventoryOutboxDisplayBoth"); +} + +void LLToolMgr::toggleMarketplace(const LLSD& sdname) +{ + const std::string& param = sdname.asString(); + + if ((param != "marketplace") || !canAccessMarketplace()) + { + return; + } + + LLFloaterReg::toggleInstanceOrBringToFront("marketplace_listings"); +} + void LLToolMgr::setTransientTool(LLTool* tool) { if (!tool) diff --git a/indra/newview/lltoolmgr.h b/indra/newview/lltoolmgr.h index e7d1c56c83..a3c1045aac 100755 --- a/indra/newview/lltoolmgr.h +++ b/indra/newview/lltoolmgr.h @@ -54,7 +54,9 @@ public: bool inEdit(); bool canEdit(); + bool canAccessMarketplace(); void toggleBuildMode(const LLSD& sdname); + void toggleMarketplace(const LLSD& sdname); /* Determines if we are in Build mode or not. */ bool inBuildMode(); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index e19fe9ca75..2815a4f16f 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -80,6 +80,7 @@ #include "llfloaterland.h" #include "llfloaterlandholdings.h" #include "llfloatermap.h" +#include "llfloatermarketplacelistings.h" #include "llfloatermediasettings.h" #include "llfloatermemleak.h" #include "llfloatermodelpreview.h" @@ -182,6 +183,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("block_timers", "floater_fast_timers.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFastTimerView>); LLFloaterReg::add("about_land", "floater_about_land.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLand>); LLFloaterReg::add("appearance", "floater_my_appearance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>); + LLFloaterReg::add("associate_listing", "floater_associate_listing.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAssociateListing>); LLFloaterReg::add("auction", "floater_auction.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAuction>); LLFloaterReg::add("avatar", "floater_avatar.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatar>); LLFloaterReg::add("avatar_picker", "floater_avatar_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterAvatarPicker>); @@ -230,6 +232,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("incoming_call", "floater_incoming_call.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLIncomingCallDialog>); LLFloaterReg::add("inventory", "floater_my_inventory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>); LLFloaterReg::add("inspect", "floater_inspect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterInspect>); + LLFloaterReg::add("item_properties", "floater_item_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterItemProperties>); LLInspectAvatarUtil::registerFloater(); LLInspectGroupUtil::registerFloater(); LLInspectObjectUtil::registerFloater(); @@ -248,6 +251,8 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("tex_fetch_debugger", "floater_texture_fetch_debugger.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterTextureFetchDebugger>); } LLFloaterReg::add("media_settings", "floater_media_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMediaSettings>); + LLFloaterReg::add("marketplace_listings", "floater_marketplace_listings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMarketplaceListings>); + LLFloaterReg::add("marketplace_validation", "floater_marketplace_validation.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMarketplaceValidation>); LLFloaterReg::add("message_critical", "floater_critical.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterTOS>); LLFloaterReg::add("message_tos", "floater_tos.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterTOS>); LLFloaterReg::add("moveview", "floater_moveview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMove>); diff --git a/indra/newview/llviewerfoldertype.cpp b/indra/newview/llviewerfoldertype.cpp index 0401de7e69..158cacbc81 100644 --- a/indra/newview/llviewerfoldertype.cpp +++ b/indra/newview/llviewerfoldertype.cpp @@ -140,7 +140,11 @@ LLViewerFolderDictionary::LLViewerFolderDictionary() addEntry(LLFolderType::FT_OUTBOX, new ViewerFolderEntry("Merchant Outbox", "Inv_SysOpen", "Inv_SysClosed", FALSE, boxes_invisible)); addEntry(LLFolderType::FT_BASIC_ROOT, new ViewerFolderEntry("Basic Root", "Inv_SysOpen", "Inv_SysClosed", FALSE, true)); - + + addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new ViewerFolderEntry("Marketplace Listings", "Inv_SysOpen", "Inv_SysClosed", FALSE, boxes_invisible)); + addEntry(LLFolderType::FT_MARKETPLACE_STOCK, new ViewerFolderEntry("New Stock", "Inv_StockFolderOpen", "Inv_StockFolderClosed", FALSE, false, "default")); + addEntry(LLFolderType::FT_MARKETPLACE_VERSION, new ViewerFolderEntry("New Version", "Inv_VersionFolderOpen","Inv_VersionFolderClosed", FALSE, false, "default")); + addEntry(LLFolderType::FT_NONE, new ViewerFolderEntry("New Folder", "Inv_FolderOpen", "Inv_FolderClosed", FALSE, false, "default")); for (U32 type = (U32)LLFolderType::FT_ENSEMBLE_START; type <= (U32)LLFolderType::FT_ENSEMBLE_END; ++type) diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index d6c8ba10f6..dcd986a5c7 100755 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -808,6 +808,31 @@ bool LLViewerInventoryCategory::exportFileLocal(LLFILE* fp) const return true; } +bool LLViewerInventoryCategory::acceptItem(LLInventoryItem* inv_item) +{ + bool accept = true; + // Only stock folders have limitation on which item they will accept + if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + // If the item is copyable (i.e. non stock) do not accept the drop in a stock folder + if (inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + accept = false; + } + else + { + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(getUUID(),cat_array,item_array); + // Destination stock folder must be empty OR types of incoming and existing items must be identical and have the same permissions + accept = (!item_array->size() || + ((item_array->at(0)->getInventoryType() == inv_item->getInventoryType()) && + (item_array->at(0)->getPermissions().getMaskNextOwner() == inv_item->getPermissions().getMaskNextOwner()))); + } + } + return accept; +} + void LLViewerInventoryCategory::determineFolderType() { /* Do NOT uncomment this code. This is for future 2.1 support of ensembles. diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h index ca92565600..4e91e4110a 100755 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -228,6 +228,9 @@ public: void changeType(LLFolderType::EType new_folder_type); virtual void unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); virtual BOOL unpackMessage(const LLSD& category); + + // returns true if the category object will accept the incoming item + bool acceptItem(LLInventoryItem* inv_item); private: friend class LLInventoryModel; diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 3abeba4b43..1de615aa6e 100755 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -88,6 +88,7 @@ #include "llinventoryfunctions.h" #include "llpanellogin.h" #include "llpanelblockedlist.h" +#include "llmarketplacefunctions.h" #include "llmenuoptionpathfindingrebakenavmesh.h" #include "llmoveview.h" #include "llparcel.h" @@ -98,6 +99,7 @@ #include "llspellcheckmenuhandler.h" #include "llstatusbar.h" #include "lltextureview.h" +#include "lltoolbarview.h" #include "lltoolcomp.h" #include "lltoolmgr.h" #include "lltoolpie.h" @@ -386,6 +388,66 @@ void set_underclothes_menu_options() } } +void set_merchant_SLM_menu() +{ + // DD-170 : SLM Alpha and Beta program : for the moment, we always show the SLM menu and + // tools so that all merchants can try out the UI, even if not migrated. + // *TODO : Keep SLM UI hidden for non migrated merchant in released viewer + + //if (LLMarketplaceData::instance().getSLMStatus() == MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT) + //{ + // Merchant not migrated: show only the old Merchant Outbox menu + // gMenuHolder->getChild<LLView>("MerchantOutbox")->setVisible(TRUE); + //} + //else + //{ + // All other cases (new merchant, not merchant, migrated merchant): show the new Marketplace Listings menu and enable the tool + gMenuHolder->getChild<LLView>("MarketplaceListings")->setVisible(TRUE); + LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); + gToolBarView->enableCommand(command->id(), true); + //} +} + +void set_merchant_outbox_menu(U32 status, const LLSD& content) +{ + // If the merchant is fully migrated, the API is disabled (503) and we won't show the old menu item. + // In all other cases, we show it. + if (status != MarketplaceErrorCodes::IMPORT_SERVER_API_DISABLED) + { + gMenuHolder->getChild<LLView>("MerchantOutbox")->setVisible(TRUE); + } +} + +void check_merchant_status() +{ + if (!gSavedSettings.getBOOL("InventoryOutboxDisplayBoth")) + { + // Reset the SLM status: we actually want to check again, that's the point of calling check_merchant_status() + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED); + + // Hide SLM related menu item + gMenuHolder->getChild<LLView>("MarketplaceListings")->setVisible(FALSE); + + // Also disable the toolbar button for Marketplace Listings + LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); + gToolBarView->enableCommand(command->id(), false); + + // Launch an SLM test connection to get the merchant status + LLMarketplaceData::instance().initializeSLM(boost::bind(&set_merchant_SLM_menu)); + + // Do the Merchant Outbox init only once per session + if (LLMarketplaceInventoryImporter::instance().getMarketPlaceStatus() == MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) + { + // Hide merchant outbox related menu item + gMenuHolder->getChild<LLView>("MerchantOutbox")->setVisible(FALSE); + + // Launch a Merchant Outbox test connection to get the migration status + LLMarketplaceInventoryImporter::instance().setStatusReportCallback(boost::bind(&set_merchant_outbox_menu,_1, _2)); + LLMarketplaceInventoryImporter::instance().initialize(); + } + } +} + void init_menus() { // Initialize actions diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h index 7f09fc2d8f..b7bdf00157 100755 --- a/indra/newview/llviewermenu.h +++ b/indra/newview/llviewermenu.h @@ -83,6 +83,7 @@ BOOL enable_god_full(void* user_data); BOOL enable_god_liaison(void* user_data); BOOL enable_god_basic(void* user_data); void set_underclothes_menu_options(); +void check_merchant_status(); void exchange_callingcard(const LLUUID& dest_id); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 44eb4361f1..75a5688827 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -71,6 +71,7 @@ #include "llinventoryobserver.h" #include "llinventorypanel.h" #include "llfloaterimnearbychat.h" +#include "llmarketplacefunctions.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llpanelgrouplandmoney.h" @@ -5882,7 +5883,7 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem) LL_WARNS() << "attempt_standard_notification: Attempted to read notification parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; } } - + if ( (notificationID == "RegionEntryAccessBlocked") || (notificationID == "LandClaimAccessBlocked") || @@ -5963,7 +5964,25 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem) make_ui_sound("UISndRestart"); } - + + // Special Marketplace update notification + if (notificationID == "SLM_UPDATE_FOLDER") + { + std::string state = llsdBlock["state"].asString(); + if (state == "deleted") + { + // Perform the deletion viewer side, no alert shown in this case + LLMarketplaceData::instance().deleteListing(llsdBlock["listing_id"].asInteger()); + return true; + } + else + { + // In general, no message will be displayed, all we want is to get the listing updated in the marketplace floater + // If getListing() fails though, the message of the alert will be shown by the caller of attempt_standard_notification() + return LLMarketplaceData::instance().getListing(llsdBlock["listing_id"].asInteger()); + } + } + LLNotificationsUtil::add(notificationID, llsdBlock); return true; } diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 11cbf3fc24..9ecbcba8e1 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -2685,6 +2685,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("CopyInventoryFromNotecard"); capabilityNames.append("CreateInventoryCategory"); capabilityNames.append("DispatchRegionInfo"); + capabilityNames.append("DirectDelivery"); capabilityNames.append("EnvironmentSettings"); capabilityNames.append("EstateChangeInfo"); capabilityNames.append("EventQueueGet"); diff --git a/indra/newview/skins/default/textures/icons/Inv_StockFolderClosed.png b/indra/newview/skins/default/textures/icons/Inv_StockFolderClosed.png Binary files differnew file mode 100644 index 0000000000..4dc484dc22 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Inv_StockFolderClosed.png diff --git a/indra/newview/skins/default/textures/icons/Inv_StockFolderOpen.png b/indra/newview/skins/default/textures/icons/Inv_StockFolderOpen.png Binary files differnew file mode 100644 index 0000000000..0d140b56a7 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Inv_StockFolderOpen.png diff --git a/indra/newview/skins/default/textures/icons/Inv_VersionFolderClosed.png b/indra/newview/skins/default/textures/icons/Inv_VersionFolderClosed.png Binary files differnew file mode 100644 index 0000000000..e89a4d7f31 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Inv_VersionFolderClosed.png diff --git a/indra/newview/skins/default/textures/icons/Inv_VersionFolderOpen.png b/indra/newview/skins/default/textures/icons/Inv_VersionFolderOpen.png Binary files differnew file mode 100644 index 0000000000..659d7d392f --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Inv_VersionFolderOpen.png diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 1f10d966d5..392eaf85a3 100755 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -140,6 +140,7 @@ with the same filename but different name <texture name="Command_Inventory_Icon" file_name="toolbar_icons/inventory.png" preload="true" /> <texture name="Command_Map_Icon" file_name="toolbar_icons/map.png" preload="true" /> <texture name="Command_Marketplace_Icon" file_name="toolbar_icons/marketplace.png" preload="true" /> + <texture name="Command_MktListings_Icon" file_name="toolbar_icons/mktlistings.png" preload="true" /> <texture name="Command_MiniCart_Icon" file_name="toolbar_icons/mini_cart.png" preload="true" /> <texture name="Command_MiniMap_Icon" file_name="toolbar_icons/mini_map.png" preload="true" /> <texture name="Command_Move_Icon" file_name="toolbar_icons/move.png" preload="true" /> @@ -303,6 +304,8 @@ with the same filename but different name <texture name="Inv_Snapshot" file_name="icons/Inv_Snapshot.png" preload="false" /> <texture name="Inv_Socks" file_name="icons/Inv_Socks.png" preload="false" /> <texture name="Inv_Sound" file_name="icons/Inv_Sound.png" preload="false" /> + <texture name="Inv_StockFolderClosed" file_name="icons/Inv_StockFolderClosed.png" preload="false" /> + <texture name="Inv_StockFolderOpen" file_name="icons/Inv_StockFolderOpen.png" preload="false" /> <texture name="Inv_SysClosed" file_name="icons/Inv_SysClosed.png" preload="false" /> <texture name="Inv_SysOpen" file_name="icons/Inv_SysOpen.png" preload="false" /> <texture name="Inv_Tattoo" file_name="icons/Inv_Tattoo.png" preload="false" /> @@ -314,6 +317,8 @@ with the same filename but different name <texture name="Inv_Undershirt" file_name="icons/Inv_Undershirt.png" preload="false" /> <texture name="Inv_Link" file_name="icons/Inv_Link.png" preload="false" /> <texture name="Inv_Invalid" file_name="icons/Inv_Invalid.png" preload="false" /> + <texture name="Inv_VersionFolderClosed" file_name="icons/Inv_VersionFolderClosed.png" preload="false" /> + <texture name="Inv_VersionFolderOpen" file_name="icons/Inv_VersionFolderOpen.png" preload="false" /> <texture name="Linden_Dollar_Alert" file_name="widgets/Linden_Dollar_Alert.png"/> <texture name="Linden_Dollar_Background" file_name="widgets/Linden_Dollar_Background.png"/> diff --git a/indra/newview/skins/default/textures/toolbar_icons/mktlistings.png b/indra/newview/skins/default/textures/toolbar_icons/mktlistings.png Binary files differnew file mode 100644 index 0000000000..a6f90461d7 --- /dev/null +++ b/indra/newview/skins/default/textures/toolbar_icons/mktlistings.png diff --git a/indra/newview/skins/default/xui/en/floater_associate_listing.xml b/indra/newview/skins/default/xui/en/floater_associate_listing.xml new file mode 100755 index 0000000000..e019ed58dd --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_associate_listing.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + can_minimize="false" + height="110" + layout="topleft" + name="associate listing" + help_topic="associate_listing" + title="ASSOCIATE LISTING" + width="375"> + <text + type="string" + length="1" + follows="top|left" + font="SansSerifLarge" + height="16" + layout="topleft" + left="10" + top="25" + name="message"> + Listing ID: + </text> + <line_editor + type="string" + length="1" + follows="top|right" + font="SansSerif" + height="20" + layout="topleft" + left_delta="0" + name="listing_id" + top_pad="5" + width="350"> + Type ID here + </line_editor> + <button + follows="bottom|left" + height="23" + label="OK" + layout="topleft" + left="155" + name="OK" + top_pad="10" + width="100" /> + <button + follows="bottom|right" + height="23" + label="Cancel" + layout="topleft" + left_pad="5" + name="Cancel" + width="100" /> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_item_properties.xml b/indra/newview/skins/default/xui/en/floater_item_properties.xml new file mode 100755 index 0000000000..0fc54a9c8b --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_item_properties.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="570" + layout="topleft" + name="Item Properties" + help_topic="item+properties" + title="ITEM PROPERTIES" + width="330"> + <panel + follows="all" + layout="topleft" + left="0" + class="sidepanel_item_info" + filename="sidepanel_item_info.xml" + name="item_panel" + top="20" + label="" + height="570" + visible="true" + width="330"> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_marketplace_listings.xml b/indra/newview/skins/default/xui/en/floater_marketplace_listings.xml new file mode 100755 index 0000000000..8f58146a48 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_marketplace_listings.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<floater + title="MARKETPLACE LISTINGS" + name="floater_marketplace_listings" + help_topic="floater_marketplace_listings" + positioning="cascading" + width="333" + height="445" + min_width="200" + min_height="300" + can_close="true" + can_resize="true" + save_rect="true" + save_visibility="false" + reuse_instance="true"> + <string name="MarketplaceListingsInitializing">Initializing...</string> + <panel + name="marketplace_listings_panel" + follows="all" + layout="topleft" + left="0" + top="0" + height="440" + width="333"> + <panel + follows="all" + left="10" + height="440" + width="313" + top="0" + bg_opaque_color="InventoryBackgroundColor"> + <panel + name="marketplace_listings_inventory_placeholder_panel" + follows="all" + layout="topleft" + top="0" + left="0" + width="313" + height="440" + bg_opaque_color="InventoryBackgroundColor"> + <text + name="marketplace_listings_inventory_placeholder_title" + type="string" + follows="top|left|right" + layout="topleft" + top="10" + left="0" + width="313" + height="25" + wrap="true" + halign="center" + font="SansSerifBold"> + Loading... + </text> + <text + name="marketplace_listings_inventory_placeholder_text" + type="string" + follows="top|left|right" + layout="topleft" + top="35" + left="0" + width="313" + height="130" + wrap="true" + halign="left" /> + </panel> + <panel + name="panel_marketplace_listing" + filename="panel_marketplace_listings.xml" + class="llpanelmarketplacelistings" + top="0" + follows="all"/> + </panel> + <panel + name="marketplace_panel_status" + follows="bottom|left|right" + layout="topleft" + left="10" + width="313" + height="20"> + <text + name="marketplace_status" + type="string" + follows="bottom|left|right" + layout="topleft" + top="0" + left="5" + width="150" + height="20" + wrap="true" + halign="left" + valign="center" + font="SansSerif"/> + </panel> + <layout_stack name="initialization_progress_indicator" orientation="vertical" left="0" height="440" top="0" width="333" follows="all" visible="false"> + <layout_panel /> + <layout_panel height="24" auto_resize="false"> + <layout_stack orientation="horizontal" left="0" height="24" top="0" width="333" follows="all"> + <layout_panel width="0" /> + <layout_panel width="24" auto_resize="false"> + <loading_indicator + height="24" + layout="topleft" + left="0" + top="0" + width="24" /> + </layout_panel> + <layout_panel width="0" /> + </layout_stack> + </layout_panel> + <layout_panel /> + </layout_stack> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_marketplace_validation.xml b/indra/newview/skins/default/xui/en/floater_marketplace_validation.xml new file mode 100755 index 0000000000..9035a7c161 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_marketplace_validation.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + title="Audit Marketplace Listings" + name="floater_marketplace_validation" + help_topic="floater_marketplace_validation" + layout="topleft" + positioning="cascading" + legacy_header_height="18" + width="600" + height="500" + min_width="400" + min_height="200" + can_close="true" + can_resize="true" + can_minimize="true" + save_rect="true" + reuse_instance="true" + save_visibility="false"> + <button + name="OK" + label="OK" + label_selected="OK" + layout="topleft" + follows="right|bottom" + top="465" + width="100" + height="20" + left="484"/> + <text_editor + name="validation_text" + type="string" + font="SansSerif" + length="1" + max_length="65536" + layout="topleft" + follows="all" + bg_readonly_color="TextBgReadOnlyColor" + text_readonly_color="TextFgReadOnlyColor" + top="25" + bottom="455" + left="20" + right="-20" + word_wrap="true"> + MARKETPLACE_VALIDATION_TEXT + </text_editor> +</floater> diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml index 7099db63ab..e28a02550f 100755 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -4,6 +4,81 @@ layout="topleft" name="Popup" visible="false"> + <menu_item_call + label="Create listing" + layout="topleft" + name="Marketplace Create Listing"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_create_listing" /> + </menu_item_call> + <menu_item_call + label="Associate listing" + layout="topleft" + name="Marketplace Associate Listing"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_associate_listing" /> + </menu_item_call> + <menu_item_call + label="Unassociate listing" + layout="topleft" + name="Marketplace Disassociate Listing"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_disassociate_listing" /> + </menu_item_call> + <menu_item_call + label="Get (Refresh) listing" + layout="topleft" + name="Marketplace Get Listing"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_get_listing" /> + </menu_item_call> + <menu_item_call + label="Edit listing" + layout="topleft" + name="Marketplace Edit Listing"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_edit_listing" /> + </menu_item_call> + <menu_item_call + label="List" + layout="topleft" + name="Marketplace List"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_list" /> + </menu_item_call> + <menu_item_call + label="Unlist" + layout="topleft" + name="Marketplace Unlist"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_unlist" /> + </menu_item_call> + <menu_item_call + label="Activate" + layout="topleft" + name="Marketplace Activate"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_activate" /> + </menu_item_call> + <menu_item_call + label="Deactivate" + layout="topleft" + name="Marketplace Deactivate"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="marketplace_deactivate" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Marketplace Listings Separator" /> <menu_item_call label="Share" layout="topleft" @@ -718,14 +793,6 @@ parameter="copy_to_outbox" /> </menu_item_call> <menu_item_call - label="Send to Marketplace" - layout="topleft" - name="Marketplace Send"> - <menu_item_call.on_click - function="Inventory.DoToSelected" - parameter="send_to_marketplace" /> - </menu_item_call> - <menu_item_call label="--no options--" layout="topleft" name="--no options--" /> diff --git a/indra/newview/skins/default/xui/en/menu_marketplace_view.xml b/indra/newview/skins/default/xui/en/menu_marketplace_view.xml new file mode 100755 index 0000000000..cff5ec1040 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_marketplace_view.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<toggleable_menu + name="menu_marketplace_sort" + left="0" bottom="0" visible="false" + mouse_opaque="false"> + <menu_item_check + label="Sort by stock amount (low to high)" + name="sort_by_stock_amount"> + <menu_item_check.on_click + function="Marketplace.ViewSort.Action" + parameter="sort_by_stock_amount"/> + <menu_item_check.on_check + function="Marketplace.ViewSort.CheckItem" + parameter="sort_by_stock_amount"/> + </menu_item_check> +</toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 560f81a6fd..08c4151dbb 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -173,6 +173,13 @@ function="Floater.ToggleOrBringToFront" parameter="outbox" /> </menu_item_call> + <menu_item_call + label="Marketplace listings..." + name="MarketplaceListings"> + <menu_item_call.on_click + function="Floater.ToggleOrBringToFront" + parameter="marketplace_listings" /> + </menu_item_call> <menu_item_call label="Account dashboard..." name="Manage My Account"> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index ea1bc66236..5b0bdc4a0d 100755 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -302,9 +302,139 @@ Initialization with the Marketplace failed because of a system or network error. name="okbutton" yestext="OK"/> </notification> + + <notification + icon="OutboxStatus_Error" + name="StockPasteFailed" + type="outbox"> + Copy or move to Stock Folder failed with error : + + '[ERROR_CODE]' + + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="OutboxStatus_Error" + name="MerchantPasteFailed" + type="outbox"> + Copy or move to Marketplace Listings failed with error : + + '[ERROR_CODE]' + + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="OutboxStatus_Error" + name="MerchantTransactionFailed" + type="outbox"> + The transaction with the Marketplace failed with the following error : + + Reason : '[ERROR_REASON]' + [ERROR_DESCRIPTION] + + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + <notification + icon="OutboxStatus_Error" + name="MerchantListingFailed" + type="outbox"> + Listing to Marketplace failed with error : + + '[ERROR_CODE]' + + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="ConfirmMerchantActiveChange" + type="alertmodal"> + This action will change the active content of this listing. Do you want to continue? + <tag>confirm</tag> + <usetemplate + ignoretext="Confirm before I change an active listing on the marketplace" + name="okcancelignore" + notext="Cancel" + yestext="OK"/> + </notification> <notification + icon="alertmodal.tga" + name="ConfirmMerchantUnlist" + type="alertmodal"> + This action will unlist this listing. Do you want to continue? + <tag>confirm</tag> + <usetemplate + ignoretext="Confirm before I unlist an active listing on the marketplace" + name="okcancelignore" + notext="Cancel" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="ConfirmMerchantClearVersion" + type="alertmodal"> + This action will deactivate the version folder of the current listing. Do you want to continue? + <tag>confirm</tag> + <usetemplate + ignoretext="Confirm before I deactivate the version folder of a listing on the marketplace" + name="okcancelignore" + notext="Cancel" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="AlertMerchantAssociateNeedsVersion" + type="alertmodal"> + Before you can activate this listing or edit it on the Marketplace web site, you will need to activate a version folder. + <tag>confirm</tag> + <usetemplate + ignoretext="Alert about version folder requirements when I associate a listing with an existing listing id" + name="okignore" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="AlertMerchantListingNotUpdated" + type="alertmodal"> +This listing could not be updated. +[[URL] Click here] to edit it on the Marketplace. + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="AlertMerchantListingCannotWear" + type="alertmodal"> + You cannot wear clothes or body parts that are in the Marketplace Listings folder. + <tag>fail</tag> + </notification> + + <notification + icon="alertmodal.tga" + name="AlertMerchantListingInvalidID" + type="alertmodal"> + Invalid listing ID. + <tag>fail</tag> + </notification> + + <notification icon="alertmodal.tga" name="CompileQueueSaveText" type="alertmodal"> @@ -4485,6 +4615,13 @@ Are you sure you want to change the Estate Covenant? </notification> <notification + icon="alertmodal.tga" + name="SLM_UPDATE_FOLDER" + type="alertmodal"> + [MESSAGE] + </notification> + + <notification icon="alertmodal.tga" name="RegionEntryAccessBlocked_AdultsOnlyContent" type="alertmodal"> diff --git a/indra/newview/skins/default/xui/en/panel_marketplace_listings.xml b/indra/newview/skins/default/xui/en/panel_marketplace_listings.xml new file mode 100755 index 0000000000..9f319273a8 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_marketplace_listings.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + label="Marketplace" + name="Marketplace Panel" + follows="all" + layout="topleft" + width="308" + height="440"> + <panel + name="tool_panel" + follows="left|top|right" + layout="topleft" + height="30" + width="308" + top="0" + left="0"> + <menu_button + name="sort_btn" + tool_tip="View/sort options" + layout="topleft" + follows="top|left" + width="31" + height="25" + left="2" + menu_filename="menu_marketplace_view.xml" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Conv_toolbar_sort" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + menu_position="bottomleft"/> + <button + name="add_btn" + tool_tip="Create a new listing folder" + layout="topleft" + follows="top|left" + width="31" + height="25" + left_pad="2" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Conv_toolbar_plus" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off"/> + <button + name="audit_btn" + label="Check for Errors" + tool_tip="Check your marketplace listings" + layout="topleft" + follows="top|right" + width="97" + height="26" + left_pad="143"/> + </panel> + <panel + name="tab_container_panel" + follows="all" + layout="topleft" + default_tab_group="1" + width="308" + height="400"> + <filter_editor + text_pad_left="10" + follows="left|top|right" + height="23" + label="Filter Marketplace Listings" + layout="topleft" + left="0" + max_length_chars="300" + name="filter_editor" + top="0" + width="308" /> + <tab_container + name="marketplace_filter_tabs" + follows="all" + layout="topleft" + top="30" + left="0" + top_pad="0" + width="308" + height="370" + halign="center" + tab_height="30" + tab_group="1" + tab_position="top" + tab_min_width="50"> + </tab_container> + </panel> +</panel>
\ No newline at end of file diff --git a/indra/newview/skins/default/xui/en/panel_marketplace_listings_inventory.xml b/indra/newview/skins/default/xui/en/panel_marketplace_listings_inventory.xml new file mode 100755 index 0000000000..67988f3153 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_marketplace_listings_inventory.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<inventory_panel + label="ALL" + name="All Items" + help_topic="marketplace_tab" + layout="topleft" + follows="all" + width="308" + height="370" + top="16" + left="0" + start_folder.name="Marketplace listings" + show_empty_message="true" + show_root_folder="true" + start_folder.type="merchant" + tool_tip="Drag and drop items here to list them" + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + show_item_link_overlays="true"> + <item allow_wear="false"/> +</inventory_panel> diff --git a/indra/newview/skins/default/xui/en/panel_marketplace_listings_listed.xml b/indra/newview/skins/default/xui/en/panel_marketplace_listings_listed.xml new file mode 100755 index 0000000000..181eee70e2 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_marketplace_listings_listed.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<inventory_panel + label="LISTED" + name="Active Items" + help_topic="marketplace_tab" + layout="topleft" + follows="all" + width="308" + height="370" + left_delta="0" + start_folder.name="Marketplace listings" + show_empty_message="true" + show_root_folder="true" + allow_drop_on_root="false" + start_folder.type="merchant" + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + show_item_link_overlays="true"> + <item allow_wear="false"/> +</inventory_panel> diff --git a/indra/newview/skins/default/xui/en/panel_marketplace_listings_unassociated.xml b/indra/newview/skins/default/xui/en/panel_marketplace_listings_unassociated.xml new file mode 100755 index 0000000000..6ea4784c4c --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_marketplace_listings_unassociated.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<inventory_panel + label="UNASSOCIATED" + name="Unassociated Items" + help_topic="marketplace_tab" + layout="topleft" + follows="all" + width="308" + height="370" + left_delta="0" + start_folder.name="Marketplace listings" + show_empty_message="true" + show_root_folder="true" + start_folder.type="merchant" + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + show_item_link_overlays="true"> + <item allow_wear="false"/> +</inventory_panel> diff --git a/indra/newview/skins/default/xui/en/panel_marketplace_listings_unlisted.xml b/indra/newview/skins/default/xui/en/panel_marketplace_listings_unlisted.xml new file mode 100755 index 0000000000..0d19d09918 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_marketplace_listings_unlisted.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<inventory_panel + label="UNLISTED" + name="Inactive Items" + help_topic="marketplace_tab" + layout="topleft" + follows="all" + width="308" + height="370" + left_delta="0" + start_folder.name="Marketplace listings" + show_empty_message="true" + show_root_folder="true" + allow_drop_on_root="false" + start_folder.type="merchant" + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + show_item_link_overlays="true"> + <item allow_wear="false"/> +</inventory_panel> diff --git a/indra/newview/skins/default/xui/en/panel_outbox_inventory.xml b/indra/newview/skins/default/xui/en/panel_outbox_inventory.xml index c80e5b168a..b2d8bb874b 100755 --- a/indra/newview/skins/default/xui/en/panel_outbox_inventory.xml +++ b/indra/newview/skins/default/xui/en/panel_outbox_inventory.xml @@ -3,7 +3,6 @@ name="inventory_outbox" start_folder.name="Outbox" show_empty_message="false" - show_load_status="false" start_folder.type="outbox" follows="all" layout="topleft" top="0" left="0" height="165" width="308" @@ -28,5 +27,5 @@ text_pad_right="4" arrow_size="12" max_folder_item_overlap="2"/> - <item allow_open="false"/> + <item allow_wear="false"/> </inventory_panel> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 945a77c071..0b4bbf0105 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -236,14 +236,19 @@ Please try logging in again in a minute.</string> <string name="TooltipTooManyWearables">You can't wear a folder containing more than [AMOUNT] items. You can change this limit in Advanced > Show Debug Settings > WearFolderLimit.</string> <string name="TooltipPrice" value="L$[AMOUNT]: "/> - <string name="TooltipOutboxDragToWorld">You can not rez items in your merchant outbox</string> - <string name="TooltipOutboxNoTransfer">One or more of these objects cannot be sold or transferred.</string> - <string name="TooltipOutboxNotInInventory">Your merchant outbox can only accept items directly from your inventory</string> - <string name="TooltipOutboxWorn">You can not put items you are wearing into your merchant outbox</string> - <string name="TooltipOutboxCallingCard">You can not put calling cards into your merchant outbox</string> - <string name="TooltipOutboxFolderLevels">Depth of nested folders exceeds 3</string> - <string name="TooltipOutboxTooManyFolders">Subfolder count in top-level folder exceeds 20</string> - <string name="TooltipOutboxTooManyObjects">Item count in top-level folder exceeds 200</string> + <string name="TooltipOutboxDragToWorld">You can't rez items on the marketplace</string> + <string name="TooltipOutboxNoTransfer">One or more of these objects cannot be sold or transferred</string> + <string name="TooltipOutboxNotInInventory">You can only put items from your inventory on the marketplace</string> + <string name="TooltipOutboxWorn">You can't put items you are wearing on the marketplace</string> + <string name="TooltipOutboxLinked">You can't put linked items or folders on the marketplace</string> + <string name="TooltipOutboxCallingCard">You can't put calling cards on the marketplace</string> + <string name="TooltipOutboxFolderLevels">Depth of nested folders exceeds [AMOUNT]</string> + <string name="TooltipOutboxTooManyFolders">Subfolder count exceeds [AMOUNT]</string> + <string name="TooltipOutboxTooManyObjects">Item count exceeds [AMOUNT]</string> + <string name="TooltipOutboxDragActive">You can't move a listed listing</string> + <string name="TooltipOutboxCannotDropOnRoot">You can't drop items on filtered tabs root</string> + <string name="TooltipOutboxCannotMoveRoot">You can't move the marketplace listings root folder</string> + <string name="TooltipOutboxMixedStock">All items in a stock folder must have the same type and permission</string> <string name="TooltipDragOntoOwnChild">You can't move a folder into its child</string> <string name="TooltipDragOntoSelf">You can't move a folder into itself</string> @@ -2256,6 +2261,7 @@ For AI Character: Get the closest navigable point to the point provided. <string name="InventoryNoMatchingItems">Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search].</string> <string name="PlacesNoMatchingItems">Didn't find what you're looking for? Try [secondlife:///app/search/places/[SEARCH_TERM] Search].</string> <string name="FavoritesNoMatchingItems">Drag a landmark here to add it to your favorites.</string> + <string name="MarketplaceNoMatchingItems">No items found. Check the spelling of your search string and try again.</string> <string name="InventoryNoTexture">You do not have a copy of this texture in your inventory</string> <string name="InventoryInboxNoItems">Your Marketplace purchases will appear here. You may then drag them into your inventory to use them.</string> <string name="MarketplaceURL">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/</string> @@ -2281,18 +2287,42 @@ We are accessing your account on the [[MARKETPLACE_CREATE_STORE_URL] Marketplace <string name="InventoryOutboxErrorTitle">Marketplace Errors.</string> <string name="InventoryOutboxErrorTooltip"></string> <string name="InventoryOutboxError"> -The [[MARKETPLACE_CREATE_STORE_URL] Marketplace store] is returning errors. - </string> - +This feature is currently in Beta. Please add your name to this [http://goo.gl/forms/FCQ7UXkakz Google form] if you would like to participate. + </string> + <string name="InventoryMarketplaceListingsNoItemsTitle">Your Marketplace Listings folder is empty.</string> + <string name="InventoryMarketplaceListingsNoItemsTooltip"></string> + <string name="InventoryMarketplaceListingsNoItems"> + Drag folders to this area to list them for sale on the [[MARKETPLACE_DASHBOARD_URL] Marketplace]. + </string> + + <string name="Marketplace Validation Log"></string> + <string name="Marketplace Validation Warning Stock">stock folder must be contained by a version folder</string> + <string name="Marketplace Validation Error Mixed Stock">: Error: all items in a stock folder must be no-copy and of the same type</string> + <string name="Marketplace Validation Error Subfolder In Stock">: Error: stock folder cannot contain subfolders</string> + <string name="Marketplace Validation Warning Empty">: Warning: folder doesn't contain any items</string> + <string name="Marketplace Validation Warning Create Stock">: Warning: creating stock folder</string> + <string name="Marketplace Validation Warning Create Version">: Warning: creating version folder</string> + <string name="Marketplace Validation Warning Move">: Warning : moving items</string> + <string name="Marketplace Validation Warning Delete">: Warning: folder content transfered to stock folder, removing empty folder</string> + <string name="Marketplace Validation Error Stock Item">: Error: no-copy items must be contained by a stock folder</string> + <string name="Marketplace Validation Warning Unwrapped Item">: Warning: items must be contained by a version folder</string> + <string name="Marketplace Validation Error">: Error: </string> + <string name="Marketplace Validation Warning">: Warning: </string> + <string name="Marketplace Validation Error Empty Version">: Warning: version folder must contain at least 1 item</string> + <string name="Marketplace Validation Error Empty Stock">: Warning: stock folder must contain at least 1 item</string> <string name="Marketplace Error None">No errors</string> - <string name="Marketplace Error Not Merchant">Error: Before sending items to the Marketplace you will need to set yourself up as a merchant (free of charge).</string> - <string name="Marketplace Error Empty Folder">Error: This folder has no contents.</string> - <string name="Marketplace Error Unassociated Products">Error: This item failed to upload because your merchant account has too many items unassociated with products. To fix this error, log in to the marketplace website and reduce your unassociated item count.</string> - - <string name="Marketplace Error Object Limit">Error: This item contains too many objects. Fix this error by placing objects together in boxes to reduce the total count to less than 200.</string> - <string name="Marketplace Error Folder Depth">Error: This item contains too many levels of nested folders. Reorganize it to a maximum of 3 levels of nested folders.</string> - <string name="Marketplace Error Unsellable Item">Error: This item can not be sold on the marketplace.</string> - <string name="Marketplace Error Internal Import">Error: There was a problem with this item. Try again later.</string> + <string name="Marketplace Error Prefix">Error: </string> + <string name="Marketplace Error Not Merchant">Before sending items to the Marketplace you will need to set yourself up as a merchant (free of charge).</string> + <string name="Marketplace Error Not Accepted">Cannot move item in that folder.</string> + <string name="Marketplace Error Unsellable Item">This item cannot be sold on the Marketplace.</string> + + <string name="MarketplaceNoID">no Mkt ID</string> + <string name="MarketplaceLive">listed</string> + <string name="MarketplaceActive">active</string> + <string name="MarketplaceMax">max</string> + <string name="MarketplaceStock">stock</string> + <string name="MarketplaceNoStock">out of stock</string> + <string name="MarketplaceUpdating">updating...</string> <string name="Open landmarks">Open landmarks</string> <string name="Unconstrained">Unconstrained</string> @@ -3954,6 +3984,7 @@ Try enclosing path to the editor with double quotes. <string name="Command_Inventory_Label">Inventory</string> <string name="Command_Map_Label">Map</string> <string name="Command_Marketplace_Label">Marketplace</string> + <string name="Command_MarketplaceListings_Label">Marketplace</string> <string name="Command_MiniMap_Label">Mini-map</string> <string name="Command_Move_Label">Walk / run / fly</string> <string name="Command_Outbox_Label">Merchant outbox</string> @@ -3984,6 +4015,7 @@ Try enclosing path to the editor with double quotes. <string name="Command_Inventory_Tooltip">View and use your belongings</string> <string name="Command_Map_Tooltip">Map of the world</string> <string name="Command_Marketplace_Tooltip">Go shopping</string> + <string name="Command_MarketplaceListings_Tooltip">Sell your creation</string> <string name="Command_MiniMap_Tooltip">Show nearby people</string> <string name="Command_Move_Tooltip">Moving your avatar</string> <string name="Command_Outbox_Tooltip">Transfer items to your marketplace for sale</string> diff --git a/indra/newview/skins/default/xui/en/widgets/inbox_inventory_panel.xml b/indra/newview/skins/default/xui/en/widgets/inbox_inventory_panel.xml index d5b10e7f51..92f815d4bf 100755 --- a/indra/newview/skins/default/xui/en/widgets/inbox_inventory_panel.xml +++ b/indra/newview/skins/default/xui/en/widgets/inbox_inventory_panel.xml @@ -1,3 +1,2 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> -<inbox_inventory_panel show_load_status="false" - start_folder.type="inbox"/> +<inbox_inventory_panel start_folder.type="inbox"/> diff --git a/indra/newview/skins/default/xui/en/widgets/inventory_panel.xml b/indra/newview/skins/default/xui/en/widgets/inventory_panel.xml index eaf148c5e4..d5efd723c4 100755 --- a/indra/newview/skins/default/xui/en/widgets/inventory_panel.xml +++ b/indra/newview/skins/default/xui/en/widgets/inventory_panel.xml @@ -3,7 +3,6 @@ bg_opaque_color="InventoryBackgroundColor" background_visible="true" background_opaque="true" - show_load_status="true" accepts_drag_and_drop="true" > <scroll |