diff options
40 files changed, 2901 insertions, 80 deletions
diff --git a/indra/llcommon/llfoldertype.cpp b/indra/llcommon/llfoldertype.cpp index 9c38349cf7..965e4d9734 100755 --- a/indra/llcommon/llfoldertype.cpp +++ b/indra/llcommon/llfoldertype.cpp @@ -96,6 +96,10 @@ LLFolderDictionary::LLFolderDictionary() addEntry(LLFolderType::FT_OUTBOX, new FolderEntry("outbox", FALSE)); 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/llcommon/llfoldertype.h b/indra/llcommon/llfoldertype.h index a0c847914f..515bb05a3f 100644 --- a/indra/llcommon/llfoldertype.h +++ b/indra/llcommon/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/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 0bf0152b30..fdc8bfbf04 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -245,6 +245,7 @@ set(viewer_SOURCE_FILES llfloaterlagmeter.cpp llfloaterland.cpp llfloaterlandholdings.cpp + llfloatermarketplacelistings.cpp llfloatermap.cpp llfloatermediasettings.cpp llfloatermemleak.cpp @@ -835,6 +836,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 60c942094a..fd74166980 100755 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -118,6 +118,16 @@ tooltip_ref="Command_Marketplace_Tooltip" execute_function="Avatar.OpenMarketplace" /> + <command name="marketplacelistings" + available_in_toybox="true" + icon="Command_Marketplace_Icon" + label_ref="Command_MarketplaceListings_Label" + tooltip_ref="Command_MarketplaceListings_Tooltip" + execute_function="Floater.ToggleOrBringToFront" + execute_parameters="marketplace_listings" + 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/toolbars.xml b/indra/newview/app_settings/toolbars.xml index 86f9912815..69cae1b945 100755 --- a/indra/newview/app_settings/toolbars.xml +++ b/indra/newview/app_settings/toolbars.xml @@ -22,5 +22,6 @@ <command name="voice"/> <command name="minimap"/> <command name="snapshot"/> + <command name="marketplacelistings"/> </left_toolbar> </toolbars> diff --git a/indra/newview/llfloatermarketplacelistings.cpp b/indra/newview/llfloatermarketplacelistings.cpp new file mode 100755 index 0000000000..76cca065c8 --- /dev/null +++ b/indra/newview/llfloatermarketplacelistings.cpp @@ -0,0 +1,671 @@ +/** + * @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 "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() +: mAllPanel(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() +{ + mAllPanel = getChild<LLInventoryPanel>("All Items"); + childSetAction("add_btn", boost::bind(&LLPanelMarketplaceListings::onAddButtonClicked, this)); + childSetAction("audit_btn", boost::bind(&LLPanelMarketplaceListings::onAuditButtonClicked, this)); + + // Set the sort order newest to oldest + LLInventoryPanel* panel = getChild<LLInventoryPanel>("All Items"); + panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME); + panel->getFilter().markDefault(); + panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2)); + + // Set filters on the 3 prefiltered panels + panel = getChild<LLInventoryPanel>("Active Items"); + panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME); + panel->getFilter().setFilterMarketplaceActiveFolders(); + panel->getFilter().markDefault(); + panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2)); + panel = getChild<LLInventoryPanel>("Inactive Items"); + panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME); + panel->getFilter().setFilterMarketplaceInactiveFolders(); + panel->getFilter().markDefault(); + panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2)); + panel = getChild<LLInventoryPanel>("Unassociated Items"); + panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME); + panel->getFilter().setFilterMarketplaceUnassociatedFolders(); + panel->getFilter().markDefault(); + panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2)); + + return LLPanel::postBuild(); +} + +void LLPanelMarketplaceListings::draw() +{ + LLPanel::draw(); +} + +void LLPanelMarketplaceListings::onSelectionChange(LLInventoryPanel *panel, const std::deque<LLFolderViewItem*>& items, BOOL user_action) +{ + panel->onSelectionChange(items, user_action); +} + +void LLPanelMarketplaceListings::onAddButtonClicked() +{ + 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(); + mAllPanel->setSelectionByID(category, 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); + mAllPanel->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) +{ +} + +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); + + // Merov : Debug : fetch aggressively so we can create test data right onOpen() + llinfos << "Merov : postBuild, do fetchContent() ahead of time" << llendl; + 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 (LLMarketplaceInventoryImporter::getInstance()->getMarketPlaceStatus() == MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) + { + initializeMarketPlace(); + } + else + { + setup(); + } + + // + // Update the floater view + // + updateView(); + + // + // Trigger fetch of the contents + // + fetchContents(); +} + +void LLFloaterMarketplaceListings::onFocusReceived() +{ + fetchContents(); +} + +void LLFloaterMarketplaceListings::fetchContents() +{ + if (mRootFolderId.notNull()) + { + LLInventoryModelBackgroundFetch::instance().start(mRootFolderId); + } +} + +void LLFloaterMarketplaceListings::setup() +{ + if (LLMarketplaceInventoryImporter::getInstance()->getMarketPlaceStatus() != 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 + llinfos << "Merov : Inventory problem: failure to create the marketplace listings folder for a merchant!" << llendl; + llerrs << "Inventory problem: failure to create the marketplace listings folder for a merchant!" << llendl; + 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(marketplacelistings_id, LLFolderType::FT_MARKETPLACE_LISTINGS); + + if (marketplacelistings_id == mRootFolderId) + { + llinfos << "Merov : Inventory warning: Marketplace listings folder already set" << llendl; + llwarns << "Inventory warning: Marketplace listings folder already set" << llendl; + return; + } + mRootFolderId = marketplacelistings_id; + + // No longer need to observe new category creation + if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) + { + gInventory.removeObserver(mCategoryAddedObserver); + delete mCategoryAddedObserver; + mCategoryAddedObserver = NULL; + } + llassert(!mCategoryAddedObserver); + + // Create observer for marketplace listings modifications : clear the old one and create a new one + if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver)) + { + gInventory.removeObserver(mCategoriesObserver); + delete mCategoriesObserver; + } + mCategoriesObserver = new LLInventoryCategoriesObserver(); + gInventory.addObserver(mCategoriesObserver); + mCategoriesObserver->addCategory(mRootFolderId, boost::bind(&LLFloaterMarketplaceListings::onChanged, this)); + llassert(mCategoriesObserver); + + // Get the content of the marketplace listings folder + fetchContents(); +} + +void LLFloaterMarketplaceListings::initializeMarketPlace() +{ + // + // Initialize the marketplace import API + // + LLMarketplaceInventoryImporter& importer = LLMarketplaceInventoryImporter::instance(); + + if (!importer.isInitialized()) + { + importer.setInitializationErrorCallback(boost::bind(&LLFloaterMarketplaceListings::initializationReportError, this, _1, _2)); + importer.setStatusChangedCallback(boost::bind(&LLFloaterMarketplaceListings::importStatusChanged, this, _1)); + importer.setStatusReportCallback(boost::bind(&LLFloaterMarketplaceListings::importReportResults, this, _1, _2)); + importer.initialize(); + } +} + +S32 LLFloaterMarketplaceListings::getFolderCount() +{ + if (mPanelListings && mRootFolderId.notNull()) + { + LLInventoryModel::cat_array_t * cats; + LLInventoryModel::item_array_t * items; + gInventory.getDirectDescendentsOf(mRootFolderId, cats, items); + + return (cats->count() + items->count()); + } + else + { + return 0; + } +} + +void LLFloaterMarketplaceListings::setStatusString(const std::string& statusString) +{ + mInventoryStatus->setText(statusString); +} + +void LLFloaterMarketplaceListings::updateView() +{ + if (getFolderCount() > 0) + { + 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(); + U32 mkt_status = LLMarketplaceInventoryImporter::getInstance()->getMarketPlaceStatus(); + + // *TODO : check those messages and create better appropriate ones in strings.xml + if (mRootFolderId.notNull()) + { + // Does the marketplace listings folder needs recreation? + if (!mPanelListings || !gInventory.getCategory(mRootFolderId)) + { + setup(); + } + // "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()) + { + 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) + { + fetchContents(); + updateView(); + } + else + { + // Invalidate the marketplace listings data + mRootFolderId.setNull(); + } +} + +void LLFloaterMarketplaceListings::initializationReportError(U32 status, const LLSD& content) +{ + updateView(); +} + +void LLFloaterMarketplaceListings::importStatusChanged(bool inProgress) +{ + if (mRootFolderId.isNull() && (LLMarketplaceInventoryImporter::getInstance()->getMarketPlaceStatus() == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT)) + { + setup(); + } + + if (inProgress) + { + setStatusString(getString("MarketplaceListingsInitializing")); + mInventoryInitializationInProgress->setVisible(true); + } + else + { + setStatusString(""); + mInventoryInitializationInProgress->setVisible(false); + } + + updateView(); +} + +void LLFloaterMarketplaceListings::importReportResults(U32 status, const LLSD& content) +{ + updateView(); +} + +//----------------------------------------------------------------------------- +// 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)); + getChild<LLButton>("Cancel")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::cancel, this)); + 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; +} + +void LLFloaterAssociateListing::apply() +{ + if (mUUID.notNull()) + { + S32 id = (S32)getChild<LLUICtrl>("listing_id")->getValue().asInteger(); + if (id > 0) + { + LLMarketplaceData::instance().associateListing(mUUID,id); + } + } + 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, true); + llassert(marketplacelistings_id.notNull()); + LLViewerInventoryCategory* cat = gInventory.getCategory(marketplacelistings_id); + validate_marketplacelistings(cat,boost::bind(&LLFloaterMarketplaceValidation::appendMessage, this, _1)); +} + +// static +void LLFloaterMarketplaceValidation::onOK( void* userdata ) +{ + // destroys this object + LLFloaterMarketplaceValidation* self = (LLFloaterMarketplaceValidation*) userdata; + self->closeFloater(); +} + +void LLFloaterMarketplaceValidation::appendMessage(std::string& message) +{ + if (mEditor) + { + mEditor->appendText(message, true); + } +} + +//----------------------------------------------------------------------------- +// 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..cb900c903c --- /dev/null +++ b/indra/newview/llfloatermarketplacelistings.h @@ -0,0 +1,190 @@ +/** + * @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 LLPanelMarketplaceListings +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLPanelMarketplaceListings : public LLPanel +{ +public: + LLPanelMarketplaceListings(); + BOOL postBuild(); + void draw(); + LLFolderView* getRootFolder() { return mAllPanel->getRootFolder(); } // *TODO : Suppress and get DnD in here instead... + +private: + // 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); + + LLInventoryPanel* mAllPanel; + 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 setup(); + void fetchContents(); + + void importReportResults(U32 status, const LLSD& content); + void importStatusChanged(bool inProgress); + void initializationReportError(U32 status, const LLSD& content); + 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; +}; + +//----------------------------------------------------------------------------- +// 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(); + void cancel(); + + 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); + 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 de96f75602..f5ebd5cf51 100755 --- a/indra/newview/llfloateroutbox.cpp +++ b/indra/newview/llfloateroutbox.cpp @@ -617,3 +617,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 aac3a41b9e..c31b40b179 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" @@ -269,7 +270,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()) { @@ -301,6 +302,35 @@ 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 != -1) || (weight_b != -1)) + { + llinfos << "Merov : sort by weight, a = " << a->getName() << ", " << weight_a << ", b = " << b->getName() << ", " << weight_b << llendl; + } + 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 9dcfdfa185..b6d2c8502b 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 44943d8722..e6ecd4e96e 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" @@ -288,7 +290,15 @@ BOOL LLInvFVBridge::copyToClipboard() const // *TODO: make sure this does the right thing void LLInvFVBridge::showProperties() { - show_item_profile(mUUID); + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, true); + if (gInventory.isObjectDescendentOf(mUUID, marketplacelistings_id)) + { + LLFloaterReg::showInstance("item_properties", LLSD().with("id",mUUID)); + } + else + { + show_item_profile(mUUID); + } // Disable old properties floater; this is replaced by the sidepanel. /* @@ -650,7 +660,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")); } @@ -688,7 +698,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")); @@ -845,6 +855,81 @@ void LLInvFVBridge::addOutboxContextMenuOptions(U32 flags, #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().isListed(mUUID)) + { + disabled_items.push_back(std::string("Marketplace Create Listing")); + disabled_items.push_back(std::string("Marketplace Associate Listing")); + if (LLMarketplaceData::instance().getVersionFolderID(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 (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().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); + if (!LLMarketplaceData::instance().isListed(listing_folder_id)) + { + 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 { @@ -959,6 +1044,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(); @@ -1174,6 +1271,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; @@ -1394,7 +1507,7 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) else if ("cut" == action) { cutToClipboard(); - gInventory.removeObject(mUUID); + removeObject(model, mUUID); return; } else if ("copy" == action) @@ -1454,6 +1567,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) @@ -1931,6 +2049,67 @@ void LLFolderBridge::buildDisplayName() const } } +std::string LLFolderBridge::getLabelSuffix() const +{ + 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) + ")"; + } + } + 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() { @@ -2017,6 +2196,11 @@ BOOL LLFolderBridge::isItemRemovable() const return FALSE; } } + + if (isMarketplaceListingsFolder() && LLMarketplaceData::instance().getActivationState(mUUID)) + { + return FALSE; + } return TRUE; } @@ -2252,10 +2436,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(); @@ -2435,6 +2623,12 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, } } } + if (is_movable && move_is_into_marketplacelistings) + { + // One cannot move a folder into a stock folder + is_movable = (getPreferredType() != LLFolderType::FT_MARKETPLACE_STOCK); + // *TODO : Merov : Add case if (nesting depth source + depth destination) > marketplace limit -> FALSE + } if (is_movable) { @@ -2537,6 +2731,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))) @@ -2552,11 +2750,17 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, mUUID, move_is_into_trash); } + if (move_is_from_marketplacelistings) + { + update_marketplace_category(from_folder_uuid); + // Clear the folder from the marketplace in case it was a listing folder (moot if not listed) + LLMarketplaceData::instance().deleteListing(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; @@ -2568,7 +2772,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; @@ -2942,7 +3146,7 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) else if ("cut" == action) { cutToClipboard(); - gInventory.removeObject(mUUID); + removeObject(model, mUUID); return; } else if ("copy" == action) @@ -2970,6 +3174,61 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) restoreItem(); return; } + else if ("marketplace_list" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 1) + { + LLMarketplaceData::instance().setActivation(mUUID,true); + } + return; + } + else if ("marketplace_activate" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 2) + { + LLInventoryCategory* category = gInventory.getCategory(mUUID); + LLMarketplaceData::instance().setVersionFolderID(category->getParentUUID(), mUUID); + } + return; + } + else if ("marketplace_unlist" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 1) + { + LLMarketplaceData::instance().setActivation(mUUID,false); + } + return; + } + else if ("marketplace_deactivate" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 2) + { + LLInventoryCategory* category = gInventory.getCategory(mUUID); + LLMarketplaceData::instance().setVersionFolderID(category->getParentUUID(), LLUUID::null); + } + return; + } + else if ("marketplace_create_listing" == action) + { + LLMarketplaceData::instance().addListing(mUUID); + return; + } + else if ("marketplace_disassociate_listing" == action) + { + LLMarketplaceData::instance().deleteListing(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); + LLUrlAction::openURL(url); + return; + } #ifndef LL_RELEASE_FOR_DOWNLOAD else if ("delete_system_folder" == action) { @@ -3068,10 +3327,15 @@ LLUIImagePtr LLFolderBridge::getIcon() const { LLFolderType::EType preferred_type = LLFolderType::FT_NONE; LLViewerInventoryCategory* cat = getCategory(); - if(cat) + if (cat) { preferred_type = cat->getPreferredType(); } + if ((preferred_type == LLFolderType::FT_NONE) && (depth_nesting_in_marketplace(mUUID) == 2)) + { + // We override the type when in the marketplace listings folder and only for version folder + preferred_type = LLFolderType::FT_MARKETPLACE_VERSION; + } return getIcon(preferred_type); } @@ -3083,7 +3347,13 @@ LLUIImagePtr LLFolderBridge::getIcon(LLFolderType::EType preferred_type) LLUIImagePtr LLFolderBridge::getIconOpen() const { - return LLUI::getUIImage(LLViewerFolderType::lookupIconName(getPreferredType(), TRUE)); + LLFolderType::EType preferred_type = getPreferredType(); + if ((preferred_type == LLFolderType::FT_NONE) && (depth_nesting_in_marketplace(mUUID) == 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, TRUE)); } @@ -3180,10 +3450,12 @@ void LLFolderBridge::pasteFromClipboard() { 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 BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); const BOOL move_is_into_outfit = (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); LLDynamicArray<LLUUID> objects; LLClipboard::instance().pasteFromClipboard(objects); @@ -3255,21 +3527,37 @@ 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 was a listing folder (moot if not listed) + LLMarketplaceData::instance().deleteListing(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) + { + move_item_to_marketplacelistings(viitem, parent_id); + } + else + { + //changeItemParent() implicity calls dirtyFilter + changeItemParent(model, viitem, parent_id, FALSE); + } + } + } } else { @@ -3280,22 +3568,41 @@ 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) + { + move_item_to_marketplacelistings(viitem, parent_id, true); + } + 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); } @@ -3308,12 +3615,14 @@ 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 BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); const BOOL move_is_into_outfit = (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; @@ -3412,6 +3721,10 @@ 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(trash_id == mUUID) { // This is the trash. @@ -3441,12 +3754,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")); + } } #if SUPPORT_ENSEMBLES // Changing folder types is an unfinished unsupported feature @@ -3490,9 +3805,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; } @@ -3504,7 +3819,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()) @@ -3533,8 +3848,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) @@ -3551,6 +3866,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); @@ -3570,11 +3891,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)) { @@ -3983,6 +4299,8 @@ 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 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); @@ -3990,6 +4308,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; @@ -4086,6 +4406,10 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, } } } + else if (move_is_into_marketplacelistings) + { + accept = (getCategory() && getCategory()->acceptItem(inv_item)); + } LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); @@ -4126,7 +4450,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); } @@ -4144,6 +4468,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) @@ -4155,6 +4481,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 @@ -4171,8 +4503,13 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, mUUID, move_is_into_trash); } + + if (move_is_from_marketplacelistings) + { + update_marketplace_category(from_folder_uuid); + } - // + // //-------------------------------------------------------------------------------- } } @@ -4221,7 +4558,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; @@ -4259,7 +4596,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; @@ -4293,7 +4630,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; @@ -4466,6 +4803,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")); @@ -4533,6 +4876,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()) @@ -4591,6 +4940,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()) @@ -4841,6 +5196,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")); @@ -5108,6 +5469,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")); @@ -5162,6 +5529,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()) @@ -5441,6 +5814,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")); @@ -5663,6 +6042,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); @@ -5963,6 +6348,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 bc875e8f37..72b92b6911 100755 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -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 isOutboxFolder() const; // true if COF or descendant of marketplace outbox BOOL isOutboxFolderDirectParent() const; + BOOL isMarketplaceListingsFolder() const; // true if descendant of Marketplace listings folder const LLUUID getOutboxFolder() const; virtual BOOL isItemPermissive() const; @@ -183,6 +187,7 @@ protected: mutable std::string mSearchableName; void purgeItem(LLInventoryModel *model, const LLUUID &uuid); + void removeObject(LLInventoryModel *model, const LLUUID &uuid); virtual void buildDisplayName() const {} }; @@ -274,7 +279,8 @@ public: virtual LLUIImagePtr getIcon() const; virtual LLUIImagePtr getIconOpen() const; virtual LLUIImagePtr getIconOverlay() const; - + virtual std::string getLabelSuffix() const; + virtual LLFontGL::StyleFlags getLabelStyle() const; static LLUIImagePtr getIcon(LLFolderType::EType preferred_type); virtual BOOL renameItem(const std::string& new_name); diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 15463e0d33..cf5e87c717 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" @@ -131,6 +133,35 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const LLInventoryModelBackgroundFetch::instance().start(folder_id); } + // Marketplace folder filtering + S32 depth = depth_nesting_in_marketplace(folder_id); + if (depth > 0) + { + const U32 filterTypes = mFilterOps.mFilterTypes; + 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; + } + } + } + // Always check against the clipboard const BOOL passed_clipboard = checkAgainstClipboard(folder_id); @@ -250,7 +281,7 @@ bool LLInventoryFilter::checkAgainstFilterType(const LLFolderViewModelItemInvent } } } - + return TRUE; } @@ -475,6 +506,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 ce516af0b9..113596b0eb 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 EFilterLink @@ -67,7 +70,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 @@ -160,6 +164,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 f1a4889f5a..10a5ac4bc7 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" @@ -90,6 +91,14 @@ BOOL LLInventoryState::sWearNewClothing = FALSE; LLUUID LLInventoryState::sWearNewClothingTransactionID; +// 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(); +} + // Generates a string containing the path to the item specified by // item_id. void append_path(const LLUUID& id, std::string& path) @@ -111,6 +120,80 @@ 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); + gInventory.notifyObservers(); + + // 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& cat_id) +{ + // When changing the marketplace status of a folder, 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 a folder 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. + + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + // No marketplace -> likely called too early... or + // Not a descendent of the marketplace listings root -> likely called in error then... + if (marketplace_listings_uuid.isNull() || !gInventory.isObjectDescendentOf(cat_id, marketplace_listings_uuid)) + { + // In those cases, just do the regular category update + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + gInventory.updateCategory(cat); + gInventory.notifyObservers(); + return; + } + + // Grab marketplace listing data for this folder + S32 depth = depth_nesting_in_marketplace(cat_id); + LLUUID listing_uuid = nested_parent_id(cat_id, depth); + + // Verify marketplace data consistency for this listing + if (LLMarketplaceData::instance().isListed(listing_uuid)) + { + LLUUID version_folder_uuid = LLMarketplaceData::instance().getVersionFolderID(listing_uuid); + if (version_folder_uuid.notNull() && !gInventory.isObjectDescendentOf(version_folder_uuid, listing_uuid)) + { + // *TODO : Confirm with Producer that this is what we want to happen in that case! + llinfos << "Merov : Unlist as the version folder is not under the listing folder anymore!!" << llendl; + LLMarketplaceData::instance().setVersionFolderID(listing_uuid, LLUUID::null); + LLMarketplaceData::instance().setActivation(listing_uuid, false); + } + if (!gInventory.isObjectDescendentOf(listing_uuid, marketplace_listings_uuid)) + { + // *TODO : Confirm with Producer that this is what we want to happen in that case! + llinfos << "Merov : Disassociate as the listing folder is not under the marketplace folder anymore!!" << llendl; + LLMarketplaceData::instance().deleteListing(listing_uuid); + } + } + + // Update all descendents starting from the listing root + update_marketplace_folder_hierarchy(listing_uuid); + + return; +} + void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name) { LLViewerInventoryCategory* cat; @@ -153,13 +236,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 @@ -511,14 +595,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; @@ -534,8 +621,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(); @@ -606,7 +692,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(), @@ -636,7 +722,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; @@ -677,6 +763,479 @@ 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) +{ + LLInventoryObject* cur_object = gInventory.getObject(cur_uuid); + cur_uuid = (depth < 1 ? LLUUID::null : 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 + // Note: we *always* give a stock count for stock folders, it's useful even if the listing is unassociated + return cat->getDescendentCount(); + } + + // 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().getVersionFolderID(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; +} + +void move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy) +{ + // Get the marketplace listings, exit with error if none + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + if (marketplace_listings_uuid.isNull()) + { + llinfos << "Merov : Marketplace error : There is no marketplace listings folder -> move aborted!" << llendl; + return; + } + + // 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 + 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. + if (viewer_inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID(), gAgent.getGroupID())) + { + // When moving an isolated item directly under the marketplace listings root, we create a new folder with that name + if (dest_folder == marketplace_listings_uuid) + { + dest_folder = create_folder_for_item(inv_item, dest_folder); + } + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); + + // Verify we can have this item in that destination category + if (!dest_cat->acceptItem(viewer_inv_item)) + { + llinfos << "Merov : Marketplace error : Cannot move item in that stock folder -> move aborted!" << llendl; + return; + } + + // When moving a no copy item into a first level listing folder, we create a stock folder for it + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && (dest_cat->getParentUUID() == marketplace_listings_uuid)) + { + dest_folder = create_folder_for_item(inv_item, dest_folder); + } + + // 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, false); + } + + // Validate the destination : note that this will run the validation code only on one listing folder at most... + validate_marketplacelistings(dest_cat); + + // Update the modified folders + update_marketplace_category(src_folder); + update_marketplace_category(dest_folder); + } + else + { + // *TODO : signal an error to the user (UI for this TBD) + llinfos << "Merov : Marketplace error : User doesn't have the correct permission to put this item on sale -> move aborted!" << llendl; + } + } +} + +void 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. + if (has_correct_permissions_for_sale(inv_cat)) + { + // 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) + { + llinfos << "Merov : Marketplace error : Cannot move folder in stock folder -> move aborted!" << llendl; + return; + } + + // 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); + } +} + +// Returns true if all items within the argument folder are fit for sale, false otherwise +bool has_correct_permissions_for_sale(LLInventoryCategory* cat) +{ + 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; + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) item; + LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); + LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); + // Linked items and folders cannot be put for sale + if (linked_category || linked_item) + { + llinfos << "Merov : linked items in this folder -> not allowed to sell!" << llendl; + return false; + } + // 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. + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID(), gAgent.getGroupID())) + { + llinfos << "Merov : wrong permissions on items in this folder -> not allowed to sell!" << llendl; + 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)) + { + 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 +// The only thing that's done is to move and sort folders containing no-copy items to stock folders +// *TODO : Add the rest of the SLM/AIS business logic (limit of nesting depth, stock folder consistency, overall limit on listings, etc...) +void validate_marketplacelistings(LLInventoryCategory* cat, validation_callback_t cb) +{ + // Special case a stock folder depth issue + LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (cat); + const LLFolderType::EType folder_type = cat->getPreferredType(); + S32 depth = depth_nesting_in_marketplace(cat->getUUID()); + if (depth == 1) + { + std::string message = "Validating listing : " + cat->getName(); + llinfos << "Merov : Validation log : " << message << llendl; + if (cb) + { + cb(message); + } + } + if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth <= 2)) + { + // Nest the stock folder one level deeper in a normal folder and restart from there + //LLUUID parent_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + LLUUID parent_uuid = cat->getParentUUID(); + LLUUID folder_uuid = gInventory.createNewCategory(parent_uuid, LLFolderType::FT_NONE, cat->getName()); + std::string message = " Warning : creating wrapping folder for stock folder : " + cat->getName(); + llinfos << "Merov : Validation warning : " << message << llendl; + if (cb) + { + cb(message); + } + LLInventoryCategory* new_cat = gInventory.getCategory(folder_uuid); + gInventory.changeCategoryParent(viewer_cat, folder_uuid, false); + validate_marketplacelistings(new_cat, cb); + return; + } + + 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 + 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; + LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); + LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); + // Skip items that shouldn't be there to start with, raise an error message for those + if (linked_category || linked_item) + { + std::string message = " Error : linked item are not allowed in listings : " + viewer_inv_item->getName(); + llinfos << "Merov : Validation error : " << message << llendl; + if (cb) + { + cb(message); + } + continue; + } + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID(), gAgent.getGroupID())) + { + std::string message = " Error : item with incorrect permissions in listing : " + viewer_inv_item->getName(); + llinfos << "Merov : Validation error : " << message << llendl; + if (cb) + { + cb(message); + } + 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) -> all OK + if (count == 0) + { + std::string message = " Log : folder validates: doesn't contain any item"; + llinfos << "Merov : Validation log : " << message << llendl; + if (cb) + { + cb(message); + } + } + // If we have one kind only, in the correct folder type at the right depth -> all OK + else if ((count == 1) && (((type == LLInventoryType::IT_COUNT) && (depth > 1)) || ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2)))) + { + // Done with that folder! + std::string message = " Log : folder validates: all items of compatible types"; + llinfos << "Merov : Validation log : " << message << llendl; + if (cb) + { + cb(message); + } + } + else + { + // 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 + std::string message = " Warning : creating stock folder : " + viewer_cat->getName(); + llinfos << "Merov : Validation warning : " << message << llendl; + if (cb) + { + cb(message); + } + LLUUID parent_uuid = (depth > 2 ? viewer_cat->getParentUUID() : viewer_cat->getUUID()); + LLFolderType::EType new_folder_type = (i == LLInventoryType::IT_COUNT ? LLFolderType::FT_NONE : LLFolderType::FT_MARKETPLACE_STOCK); + LLUUID folder_uuid = gInventory.createNewCategory(parent_uuid, new_folder_type, viewer_cat->getName()); + // Move each item to the new folder + while (!items_vector[i].empty()) + { + LLViewerInventoryItem* viewer_inv_item = items_vector[i].back(); + std::string message = " Warning : moving item : " + viewer_inv_item->getName(); + llinfos << "Merov : Validation warning : " << message << llendl; + if (cb) + { + cb(message); + } + gInventory.changeItemParent(viewer_inv_item, folder_uuid, false); + items_vector[i].pop_back(); + } + update_marketplace_category(folder_uuid); + } + } + // Clean up + if (viewer_cat->getDescendentCount() == 0) + { + // Remove the current folder if it ends up empty + llinfos << "Merov : Validation warning : " << llendl; + std::string message = " Warning : folder content completely moved to stock folder -> removing empty folder"; + llinfos << "Merov : Validation warning : " << message << llendl; + if (cb) + { + cb(message); + } + gInventory.removeCategory(cat->getUUID()); + gInventory.notifyObservers(); + return; + } + else + { + // Update the current folder + update_marketplace_category(cat->getUUID()); + } + } + + // Recursion : Perform the same validation on each nested folder + 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; + validate_marketplacelistings(category, cb); + } +} + +///---------------------------------------------------------------------------- /// LLInventoryCollectFunctor implementations ///---------------------------------------------------------------------------- diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index f1066a4dc9..a31bc9dbdd 100755 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -58,6 +58,9 @@ 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(); +// Just nudge the category in the global inventory to signal that its marketplace status changed +void update_marketplace_category(const LLUUID& cat_id); + 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 +70,18 @@ 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)> validation_callback_t; + +void move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy = false); +void move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy = false); +bool has_correct_permissions_for_sale(LLInventoryCategory* cat); +void validate_marketplacelistings(LLInventoryCategory* inv_cat, validation_callback_t cb = NULL); +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 ** ** *******************************************************************************/ diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index ed7fd3cd34..c0aedd3881 100755 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1013,7 +1013,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) { - if(cat->getUUID().isNull()) + if(!cat || cat->getUUID().isNull()) { return; } @@ -1027,7 +1027,7 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat) 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(); @@ -1052,7 +1052,13 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat) { 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 @@ -2160,12 +2166,12 @@ void LLInventoryModel::buildParentChildMap() // implement it, we would need a set or map of uuid pairs // which would be (folder_id, new_parent_id) to be sent up // to the server. - llinfos << "Lost categroy: " << cat->getUUID() << " - " + llinfos << "Lost category: " << cat->getUUID() << " - " << cat->getName() << llendl; ++lost; // plop it into the lost & found. LLFolderType::EType pref = cat->getPreferredType(); - if(LLFolderType::FT_NONE == pref) + if ((LLFolderType::FT_NONE == pref) || (LLFolderType::FT_MARKETPLACE_STOCK == pref)) { cat->setParent(findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); } diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 81ee7ac07e..b0163b5996 100755 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -288,6 +288,7 @@ void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) { getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_OUTBOX)); + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); } // set the filter for the empty folder if the debug setting is on @@ -567,7 +568,8 @@ void LLInventoryPanel::modelChanged(U32 mask) else if (!model_item && view_item && viewmodel_item) { // Remove the item's UI. - removeItemID(viewmodel_item->getUUID()); + const LLUUID& idp = viewmodel_item->getUUID(); + removeItemID(idp); view_item->destroyView(); } } @@ -1489,5 +1491,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/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index 05c9d76810..40417bf77b 100755 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -30,12 +30,14 @@ #include "llagent.h" #include "llhttpclient.h" +#include "llinventoryfunctions.h" #include "llsdserialize.h" #include "lltimer.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llviewermedia.h" #include "llviewernetwork.h" +#include "llviewerregion.h" // @@ -425,6 +427,18 @@ void LLMarketplaceInventoryImporter::initialize() return; } + // Test DirectDelivery cap + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("DirectDelivery"); + llinfos << "Merov : Test DirectDelivery cap : url = " << url << llendl; + } + else + { + llinfos << "Merov : Test DirectDelivery cap : no region accessible" << llendl; + } + if (!LLMarketplaceImport::hasSessionCookie()) { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; @@ -528,3 +542,240 @@ void LLMarketplaceInventoryImporter::updateImport() } } +// +// Direct Delivery : Marketplace tuples and data +// + +// Tuple == Item +LLMarketplaceTuple::LLMarketplaceTuple() : + mListingFolderId(), + mListingId(0), + mVersionFolderId(), + mIsActive(false) +{ +} + +LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id) : + mListingFolderId(folder_id), + mListingId(0), + mVersionFolderId(), + mIsActive(false) +{ +} + +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) +{ +} + + +// Data map +LLMarketplaceData::LLMarketplaceData() +{ + mTestCurrentMarketplaceID = 1234567; +} + +// Creation / Deletion +bool LLMarketplaceData::addListing(const LLUUID& folder_id) +{ + if (isListed(folder_id)) + { + // Listing already exists -> exit with error + return false; + } + mMarketplaceItems[folder_id] = LLMarketplaceTuple(folder_id); + + // *TODO : Create the listing on SLM and get the ID (blocking?) + // For the moment, we use that wonky test ID generator... + S32 listing_id = LLMarketplaceData::instance().getTestMarketplaceID(); + + setListingID(folder_id,listing_id); + update_marketplace_category(folder_id); + return true; +} + +bool LLMarketplaceData::associateListing(const LLUUID& folder_id, S32 listing_id) +{ + if (isListed(folder_id)) + { + // Listing already exists -> exit with error + return false; + } + mMarketplaceItems[folder_id] = LLMarketplaceTuple(folder_id); + + // Check that the listing ID is not already associated to some other record + LLUUID old_listing = 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) + deleteListing(old_listing); + } + + setListingID(folder_id,listing_id); + update_marketplace_category(folder_id); + return true; +} + +bool LLMarketplaceData::deleteListing(const LLUUID& folder_id) +{ + if (!isListed(folder_id)) + { + // Listing doesn't exist -> exit with error + return false; + } + mMarketplaceItems.erase(folder_id); + update_marketplace_category(folder_id); + return true; +} + +// 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); +} + +LLUUID LLMarketplaceData::getVersionFolderID(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; +} + +bool LLMarketplaceData::isListed(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it != mMarketplaceItems.end()); +} + +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; +} + +std::string LLMarketplaceData::getListingURL(const LLUUID& folder_id) +{ + // Get the listing id (i.e. go up the hierarchy to find the listing folder + // URL format will be something like : https://marketplace.secondlife.com/p/listing/<listing_id> + std::string marketplace_url = getMarketplaceURL("MarketplaceURL"); + + 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) + { + marketplace_url += llformat("p/listing/%d",listing_id); + } + return marketplace_url; +} + +// Modifiers +bool LLMarketplaceData::setListingID(const LLUUID& folder_id, S32 listing_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + else + { + (it->second).mListingId = listing_id; + update_marketplace_category(folder_id); + return true; + } +} + +bool LLMarketplaceData::setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + else + { + LLUUID old_version_id = (it->second).mVersionFolderId; + if (old_version_id != version_id) + { + (it->second).mVersionFolderId = version_id; + update_marketplace_category(old_version_id); + update_marketplace_category(version_id); + } + return true; + } +} + +bool LLMarketplaceData::setActivation(const LLUUID& folder_id, bool activate) +{ + // Listing folder case + if (isListed(folder_id)) + { + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + (it->second).mIsActive = activate; + update_marketplace_category((it->second).mListingFolderId); + return true; + } + // 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) + { + (it->second).mIsActive = activate; + update_marketplace_category((it->second).mListingFolderId); + return true; + } + it++; + } + return false; +} + + diff --git a/indra/newview/llmarketplacefunctions.h b/indra/newview/llmarketplacefunctions.h index abe60890a3..a587419323 100755 --- a/indra/newview/llmarketplacefunctions.h +++ b/indra/newview/llmarketplacefunctions.h @@ -109,6 +109,74 @@ private: }; +// Classes handling the data coming from and going to the Marketplace DB: +// * implement the Marketplace API (TBD) +// * cache the current Marketplace data (tuples) +// * provide methods to get Marketplace data on any inventory item +// * set Marketplace data +// * signal Marketplace updates to inventory +class LLMarketplaceData; + +// 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; +}; +// Note: The listing folder UUID is used as a key to this map. It could therefore be taken off the LLMarketplaceTuple objects themselves +typedef std::map<LLUUID, LLMarketplaceTuple> marketplace_items_list_t; + +// Session cache of Marketplace tuples +// Note: There's one and only one possible set of Marketplace dataset per agent and per session +class LLMarketplaceData + : public LLSingleton<LLMarketplaceData> +{ +public: + LLMarketplaceData(); + + bool isEmpty() { return (mMarketplaceItems.size() == 0); } + + // Probe the Marketplace data set to identify folders + bool isListed(const LLUUID& folder_id); // returns true if folder_id is a Listing folder + bool isVersionFolder(const LLUUID& folder_id); // returns true if folder_id is a Version folder + + // Create/Delete Marketplace data set : each method returns true if the function succeeds, false if error + bool addListing(const LLUUID& folder_id); + bool associateListing(const LLUUID& folder_id, S32 listing_id); + bool deleteListing(const LLUUID& folder_id); + + // Access Marketplace data set : each method returns a default value if the folder_id can't be found + bool getActivationState(const LLUUID& folder_id); + S32 getListingID(const LLUUID& folder_id); + LLUUID getVersionFolderID(const LLUUID& folder_id); + std::string getListingURL(const LLUUID& folder_id); + LLUUID getListingFolder(S32 listing_id); + + // Modify Marketplace data set : each method returns true if the function succeeds, false if error + bool setListingID(const LLUUID& folder_id, S32 listing_id); + bool setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id); + bool setActivation(const LLUUID& folder_id, bool activate); + + // Merov : Test method while waiting for SLM API + S32 getTestMarketplaceID() { return mTestCurrentMarketplaceID++; } + +private: + marketplace_items_list_t mMarketplaceItems; + // Merov : This is for test only, waiting for SLM API + S32 mTestCurrentMarketplaceID; +}; + #endif // LL_LLMARKETPLACEFUNCTIONS_H diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index a8eeddb798..3df0467e45 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -79,6 +79,7 @@ #include "llfloaterland.h" #include "llfloaterlandholdings.h" #include "llfloatermap.h" +#include "llfloatermarketplacelistings.h" #include "llfloatermemleak.h" #include "llfloaternamedesc.h" #include "llfloaternotificationsconsole.h" @@ -177,6 +178,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("fast_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>); @@ -225,6 +227,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(); @@ -243,6 +246,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 991f6b40e6..1e9d0791c0 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")); #if SUPPORT_ENSEMBLES diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index fff9821e86..4ca569e78c 100755 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -816,6 +816,29 @@ 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 + accept = (!item_array->count() || (item_array->get(0)->getInventoryType() == inv_item->getInventoryType())); + } + } + 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 ab19a12014..6b33ef0672 100755 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -225,6 +225,9 @@ public: bool importFileLocal(LLFILE* fp); void determineFolderType(); void changeType(LLFolderType::EType new_folder_type); + + // 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/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index a271690349..7a57badf79 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1593,6 +1593,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 94c187e21a..9c81271308 100755 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -300,6 +300,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" /> @@ -311,6 +313,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/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..f5630aeecb --- /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="420" + 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="420" + 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..165540cfa1 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_marketplace_validation.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + title="MARKETPLACE VALIDATION LOG" + 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 + name="tos_heading" + type="string" + font="SansSerif" + length="1" + layout="topleft" + follows="left|top" + width="552" + height="20" + left="20" + top="20"> + The Marketplace Validation reported the following: + </text> + <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="45" + 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 6fa45d7d66..b621d53a6c 100755 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -4,6 +4,73 @@ 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="Disassociate 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="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" 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 7e8d2aaf9a..2c432c2c8c 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -181,6 +181,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/panel_marketplace_listings.xml b/indra/newview/skins/default/xui/en/panel_marketplace_listings.xml new file mode 100755 index 0000000000..0ebb7a5f48 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_marketplace_listings.xml @@ -0,0 +1,161 @@ +<?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" + tool_tip="Validate your marketplace listings" + layout="topleft" + follows="top|left" + width="31" + height="25" + left_pad="2" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Info" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off"/> + </panel> + <panel + name="tab_container_panel" + follows="all" + layout="topleft" + default_tab_group="1" + width="308" + height="400"> + <tab_container + name="marketplace_filter_tabs" + follows="all" + layout="topleft" + top="0" + left="0" + top_pad="0" + width="308" + height="400" + halign="center" + tab_height="30" + tab_group="1" + tab_position="top" + tab_min_width="50"> + <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="false" + show_load_status="false" + start_folder.type="merchant" + tool_tip="Drag and drop items here to sell them" + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + show_item_link_overlays="true"> + </inventory_panel> + <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="false" + show_load_status="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"> + </inventory_panel> + <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="false" + show_load_status="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"> + </inventory_panel> + <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="false" + show_load_status="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"> + </inventory_panel> + </tab_container> + </panel> +</panel>
\ No newline at end of file diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 3252ed2b62..ab7df75216 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2255,6 +2255,11 @@ We are accessing your account on the [[MARKETPLACE_CREATE_STORE_URL] Marketplace <string name="InventoryOutboxError"> The [[MARKETPLACE_CREATE_STORE_URL] Marketplace store] is returning errors. </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 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> @@ -2266,6 +2271,13 @@ The [[MARKETPLACE_CREATE_STORE_URL] Marketplace store] is returning errors. <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="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="Open landmarks">Open landmarks</string> <!-- use value="" because they have preceding spaces --> @@ -3911,6 +3923,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">Sell on 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> @@ -3939,6 +3952,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> |