Up to compile phase

at 20fd3e98b4
This commit is contained in:
Inusaito Sayori
2014-12-05 12:19:30 -05:00
committed by Lirusaito
parent 0fca6ee425
commit 167d336274
54 changed files with 5566 additions and 462 deletions

View File

@@ -103,6 +103,9 @@ LLFolderDictionary::LLFolderDictionary()
addEntry(LLFolderType::FT_OUTBOX, new FolderEntry("outbox", TRUE));
addEntry(LLFolderType::FT_BASIC_ROOT, new FolderEntry("basic_rt", TRUE));
addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new FolderEntry("merchant", FALSE));
addEntry(LLFolderType::FT_MARKETPLACE_STOCK, new FolderEntry("stock", FALSE));
addEntry(LLFolderType::FT_MARKETPLACE_VERSION, new FolderEntry("version", FALSE));
addEntry(LLFolderType::FT_SUITCASE, new FolderEntry("suitcase", TRUE));
addEntry(LLFolderType::FT_NONE, new FolderEntry("-1", FALSE));

View File

@@ -94,6 +94,9 @@ 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_SUITCASE = 100,
FT_COUNT,

View File

@@ -231,6 +231,7 @@ set(viewer_SOURCE_FILES
llfloaterlandholdings.cpp
llfloaterlandmark.cpp
llfloatermap.cpp
llfloatermarketplacelistings.cpp
llfloatermediafilter.cpp
llfloatermediasettings.cpp
llfloatermemleak.cpp
@@ -764,6 +765,7 @@ set(viewer_HEADER_FILES
llfloaterlandholdings.h
llfloaterlandmark.h
llfloatermap.h
llfloatermarketplacelistings.h
llfloatermediafilter.h
llfloatermediasettings.h
llfloatermemleak.h

View File

@@ -5419,6 +5419,17 @@ This should be as low as possible, but too low may break functionality</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>DebugHideEmptySystemFolders</key>
<map>
<key>Comment</key>
<string>Hide empty system folders when on</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>DebugInventoryFilters</key>
<map>
<key>Comment</key>
@@ -9400,12 +9411,12 @@ This should be as low as possible, but too low may break functionality</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>InventoryDisplayOutbox</key>
<key>InventoryOutboxDisplayBoth</key>
<map>
<key>Comment</key>
<string>Override merchant inventory outbox display</string>
<string>Show the legacy Merchant Outbox UI as well as the Marketplace Listings UI</string>
<key>Persist</key>
<integer>0</integer>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -9422,6 +9433,17 @@ This should be as low as possible, but too low may break functionality</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>InventoryOutboxMakeVisible</key>
<map>
<key>Comment</key>
<string>Enable making the Merchant Outbox and Inbox visible in the inventory for debug purposes.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>InventoryOutboxMaxFolderCount</key>
<map>
<key>Comment</key>
@@ -9431,7 +9453,7 @@ This should be as low as possible, but too low may break functionality</string>
<key>Type</key>
<string>U32</string>
<key>Value</key>
<integer>21</integer>
<integer>20</integer>
</map>
<key>InventoryOutboxMaxFolderDepth</key>
<map>
@@ -10307,6 +10329,17 @@ This should be as low as possible, but too low may break functionality</string>
<key>Value</key>
<string/>
</map>
<key>MarketplaceListingsLogging</key>
<map>
<key>Comment</key>
<string>Enable debug output associated with the Marketplace Listings (SLM) API.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>MaxDragDistance</key>
<map>
<key>Comment</key>

View File

@@ -1761,6 +1761,17 @@ Changing this setting only affects new text.</string>
<key>IsCOA</key>
<integer>1</integer>
</map>
<key>ToolbarVisibleMarketplaceListings</key>
<map>
<key>Comment</key>
<string>Whether or not the button for marketplace listings is on the toolbar</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<boolean>0</boolean>
</map>
<key>ToolbarVisibleMeanEvents</key>
<map>
<key>Comment</key>

View File

@@ -0,0 +1,782 @@
/**
* @file llfloatermarketplacelistings.cpp
* @brief Implementation of the marketplace listings floater and panels
* @author merov@lindenlab.com
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llfloatermarketplacelistings.h"
//#include "llfloaterreg.h"
#include "llfiltereditor.h"
#include "llfolderview.h"
#include "llinventorybridge.h"
#include "llinventorymodelbackgroundfetch.h"
#include "llinventoryobserver.h"
#include "llinventoryfunctions.h"
#include "llmarketplacefunctions.h"
#include "llnotificationhandler.h"
#include "llnotificationmanager.h"
#include "llnotificationsutil.h"
//#include "llsidepaneliteminfo.h"
#include "lltextbox.h"
#include "lltrans.h"
///----------------------------------------------------------------------------
/// LLPanelMarketplaceListings
///----------------------------------------------------------------------------
static LLPanelInjector<LLPanelMarketplaceListings> t_panel_status("llpanelmarketplacelistings"); // Liru TODO: build this instead.
LLPanelMarketplaceListings::LLPanelMarketplaceListings()
: mRootFolder(NULL)
, mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME)
, mFilterType(LLInventoryFilter::FILTERTYPE_NONE)
{
mCommitCallbackRegistrar.add("Marketplace.ViewSort.Action", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemClicked, this, _2));
mEnableCallbackRegistrar.add("Marketplace.ViewSort.CheckItem", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemCheck, this, _2));
}
BOOL LLPanelMarketplaceListings::postBuild()
{
childSetAction("add_btn", boost::bind(&LLPanelMarketplaceListings::onAddButtonClicked, this));
childSetAction("audit_btn", boost::bind(&LLPanelMarketplaceListings::onAuditButtonClicked, this));
mFilterEditor = getChild<LLFilterEditor>("filter_editor");
mFilterEditor->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onFilterEdit, this, _2));
mAuditBtn = getChild<LLButton>("audit_btn");
mAuditBtn->setEnabled(FALSE);
return LLPanel::postBuild();
}
void LLPanelMarketplaceListings::buildAllPanels()
{
// Build the All panel first
LLInventoryPanel* panel_all_items;
panel_all_items = buildInventoryPanel("All Items", "panel_marketplace_listings_inventory.xml");
panel_all_items->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems");
panel_all_items->getFilter().markDefault();
// Build the other panels
LLInventoryPanel* panel;
panel = buildInventoryPanel("Active Items", "panel_marketplace_listings_listed.xml");
panel->getFilter().setFilterMarketplaceActiveFolders();
panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems");
panel->getFilter().markDefault();
panel = buildInventoryPanel("Inactive Items", "panel_marketplace_listings_unlisted.xml");
panel->getFilter().setFilterMarketplaceInactiveFolders();
panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems");
panel->getFilter().markDefault();
panel = buildInventoryPanel("Unassociated Items", "panel_marketplace_listings_unassociated.xml");
panel->getFilter().setFilterMarketplaceUnassociatedFolders();
panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems");
panel->getFilter().markDefault();
// Set the tab panel
LLTabContainer* tabs_panel = getChild<LLTabContainer>("marketplace_filter_tabs");
tabs_panel->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onTabChange, this));
tabs_panel->selectTabPanel(panel_all_items); // All panel selected by default
mRootFolder = panel_all_items->getRootFolder(); // Keep the root of the all panel
}
LLInventoryPanel* LLPanelMarketplaceListings::buildInventoryPanel(const std::string& childname, const std::string& filename)
{
LLTabContainer* tabs_panel = getChild<LLTabContainer>("marketplace_filter_tabs");
LLInventoryPanel* panel = getChild<LLInventoryPanel>(childname);
if (panel)
{
tabs_panel->removeTabPanel(panel);
delete panel;
}
panel = LLUICtrlFactory::createFromFile<LLInventoryPanel>(filename, tabs_panel, LLInventoryPanel::child_registry_t::instance());
llassert(panel != NULL);
// Set sort order and callbacks
panel = getChild<LLInventoryPanel>(childname);
panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME);
panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2));
return panel;
}
void LLPanelMarketplaceListings::onFilterEdit(const std::string& search_string)
{
// Find active panel
LLInventoryPanel* panel = (LLInventoryPanel*)getChild<LLTabContainer>("marketplace_filter_tabs")->getCurrentPanel();
if (panel)
{
// Save filter string (needed when switching tabs)
mFilterSubString = search_string;
// Set filter string on active panel
panel->setFilterSubString(mFilterSubString);
}
}
void LLPanelMarketplaceListings::draw()
{
if (LLMarketplaceData::instance().checkDirtyCount())
{
update_all_marketplace_count();
}
// Get the audit button enabled only after the whole inventory is fetched
if (!mAuditBtn->getEnabled())
{
mAuditBtn->setEnabled(LLInventoryModelBackgroundFetch::instance().isEverythingFetched());
}
LLPanel::draw();
}
void LLPanelMarketplaceListings::onSelectionChange(LLInventoryPanel *panel, const std::deque<LLFolderViewItem*>& items, BOOL user_action)
{
panel->onSelectionChange(items, user_action);
}
void LLPanelMarketplaceListings::onTabChange()
{
// Find active panel
LLInventoryPanel* panel = (LLInventoryPanel*)getChild<LLTabContainer>("marketplace_filter_tabs")->getCurrentPanel();
if (panel)
{
// If the panel doesn't allow drop on root, it doesn't allow the creation of new folder on root either
LLButton* add_btn = getChild<LLButton>("add_btn");
add_btn->setEnabled(panel->getAllowDropOnRoot());
// Set filter string on active panel
panel->setFilterSubString(mFilterSubString);
}
}
void LLPanelMarketplaceListings::onAddButtonClicked()
{
// Find active panel
LLInventoryPanel* panel = (LLInventoryPanel*)getChild<LLTabContainer>("marketplace_filter_tabs")->getCurrentPanel();
if (panel)
{
LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false);
llassert(marketplacelistings_id.notNull());
LLFolderType::EType preferred_type = LLFolderType::lookup("category");
LLUUID category = gInventory.createNewCategory(marketplacelistings_id, preferred_type, LLStringUtil::null);
gInventory.notifyObservers();
panel->setSelectionByID(category, TRUE);
panel->getRootFolder()->setNeedsAutoRename(TRUE);
}
}
void LLPanelMarketplaceListings::onAuditButtonClicked()
{
LLSD data(LLSD::emptyMap());
LLFloaterReg::showInstance("marketplace_validation", data);
}
void LLPanelMarketplaceListings::onViewSortMenuItemClicked(const LLSD& userdata)
{
std::string chosen_item = userdata.asString();
// Sort options
if (chosen_item == "sort_by_stock_amount")
{
mSortOrder = (mSortOrder == LLInventoryFilter::SO_FOLDERS_BY_NAME ? LLInventoryFilter::SO_FOLDERS_BY_WEIGHT : LLInventoryFilter::SO_FOLDERS_BY_NAME);
// Set each panel with that sort order
LLTabContainer* tabs_panel = getChild<LLTabContainer>("marketplace_filter_tabs");
LLInventoryPanel* panel = (LLInventoryPanel*)tabs_panel->getPanelByName("All Items");
panel->setSortOrder(mSortOrder);
panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Active Items");
panel->setSortOrder(mSortOrder);
panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Inactive Items");
panel->setSortOrder(mSortOrder);
panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Unassociated Items");
panel->setSortOrder(mSortOrder);
}
}
bool LLPanelMarketplaceListings::onViewSortMenuItemCheck(const LLSD& userdata)
{
std::string chosen_item = userdata.asString();
if (chosen_item == "sort_by_stock_amount")
return mSortOrder == LLInventoryFilter::SO_FOLDERS_BY_WEIGHT;
return false;
}
///----------------------------------------------------------------------------
/// LLMarketplaceListingsAddedObserver helper class
///----------------------------------------------------------------------------
class LLMarketplaceListingsAddedObserver : public LLInventoryCategoryAddedObserver
{
public:
LLMarketplaceListingsAddedObserver(LLFloaterMarketplaceListings * marketplace_listings_floater)
: LLInventoryCategoryAddedObserver()
, mMarketplaceListingsFloater(marketplace_listings_floater)
{
}
void done()
{
for (cat_vec_t::iterator it = mAddedCategories.begin(); it != mAddedCategories.end(); ++it)
{
LLViewerInventoryCategory* added_category = *it;
LLFolderType::EType added_category_type = added_category->getPreferredType();
if (added_category_type == LLFolderType::FT_MARKETPLACE_LISTINGS)
{
mMarketplaceListingsFloater->initializeMarketPlace();
}
}
}
private:
LLFloaterMarketplaceListings * mMarketplaceListingsFloater;
};
///----------------------------------------------------------------------------
/// LLFloaterMarketplaceListings
///----------------------------------------------------------------------------
LLFloaterMarketplaceListings::LLFloaterMarketplaceListings(const LLSD& key)
: LLFloater(key)
, mCategoriesObserver(NULL)
, mCategoryAddedObserver(NULL)
, mRootFolderId(LLUUID::null)
, mInventoryStatus(NULL)
, mInventoryInitializationInProgress(NULL)
, mInventoryPlaceholder(NULL)
, mInventoryText(NULL)
, mInventoryTitle(NULL)
, mPanelListings(NULL)
, mFirstViewListings(true)
{
//buildFromFile("floater_marketplace_listings.xml");
LLUICtrlFactory::instance().buildFloater(this, "floater_marketplace_listings.xml");
}
LLFloaterMarketplaceListings::~LLFloaterMarketplaceListings()
{
if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver))
{
gInventory.removeObserver(mCategoriesObserver);
}
delete mCategoriesObserver;
if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver))
{
gInventory.removeObserver(mCategoryAddedObserver);
}
delete mCategoryAddedObserver;
}
BOOL LLFloaterMarketplaceListings::postBuild()
{
mInventoryStatus = getChild<LLTextBox>("marketplace_status");
mInventoryInitializationInProgress = getChild<LLView>("initialization_progress_indicator");
mInventoryPlaceholder = getChild<LLView>("marketplace_listings_inventory_placeholder_panel");
mInventoryText = mInventoryPlaceholder->getChild<LLTextBox>("marketplace_listings_inventory_placeholder_text");
mInventoryTitle = mInventoryPlaceholder->getChild<LLTextBox>("marketplace_listings_inventory_placeholder_title");
mPanelListings = static_cast<LLPanelMarketplaceListings*>(getChild<LLUICtrl>("panel_marketplace_listing"));
LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLFloaterMarketplaceListings::onFocusReceived, this));
// Observe category creation to catch marketplace listings creation (moot if already existing)
mCategoryAddedObserver = new LLMarketplaceListingsAddedObserver(this);
gInventory.addObserver(mCategoryAddedObserver);
// Debug : fetch aggressively so we can create test data right onOpen()
fetchContents();
return TRUE;
}
void LLFloaterMarketplaceListings::onClose(bool app_quitting)
{
}
bool canAccessMarketplace()
{
return (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT) || gSavedSettings.getBOOL("InventoryOutboxDisplayBoth");
}
void LLFloaterMarketplaceListings::onOpen(const LLSD& key)
{
if (!canAccessMarketplace())
{
close();
return;
}
//
// Initialize the Market Place or go update the marketplace listings
//
if (LLMarketplaceData::instance().getSLMStatus() <= MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE)
{
initializeMarketPlace();
}
else
{
updateView();
}
}
void LLFloaterMarketplaceListings::onFocusReceived()
{
updateView();
}
void LLFloaterMarketplaceListings::fetchContents()
{
if (mRootFolderId.notNull())
{
LLInventoryModelBackgroundFetch::instance().start(mRootFolderId);
// Get all the SLM Listings
LLMarketplaceData::instance().getSLMListings();
}
}
void LLFloaterMarketplaceListings::setup()
{
if (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_MERCHANT)
{
// If we are *not* a merchant or we have no market place connection established yet, do nothing
return;
}
// We are a merchant. Get the Marketplace listings folder, create it if needs be.
LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, true);
if (marketplacelistings_id.isNull())
{
// We should never get there unless the inventory fails badly
LL_ERRS("SLM") << "Inventory problem: failure to create the marketplace listings folder for a merchant!" << LL_ENDL;
return;
}
// No longer need to observe new category creation
if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver))
{
gInventory.removeObserver(mCategoryAddedObserver);
delete mCategoryAddedObserver;
mCategoryAddedObserver = NULL;
}
llassert(!mCategoryAddedObserver);
if (marketplacelistings_id == mRootFolderId)
{
LL_WARNS("SLM") << "Inventory warning: Marketplace listings folder already set" << LL_ENDL;
return;
}
mRootFolderId = marketplacelistings_id;
// 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);
// Now that we do have a non NULL root, we can build the inventory panels
mPanelListings->buildAllPanels();
// Create observer for marketplace listings modifications
if (!mCategoriesObserver && mRootFolderId.notNull())
{
mCategoriesObserver = new LLInventoryCategoriesObserver();
llassert(mCategoriesObserver);
gInventory.addObserver(mCategoriesObserver);
mCategoriesObserver->addCategory(mRootFolderId, boost::bind(&LLFloaterMarketplaceListings::onChanged, this));
}
// Get the content of the marketplace listings folder
fetchContents();
}
void LLFloaterMarketplaceListings::initializeMarketPlace()
{
LLMarketplaceData::instance().initializeSLM(boost::bind(&LLFloaterMarketplaceListings::updateView, this));
}
S32 LLFloaterMarketplaceListings::getFolderCount()
{
if (mPanelListings && mRootFolderId.notNull())
{
LLInventoryModel::cat_array_t * cats;
LLInventoryModel::item_array_t * items;
gInventory.getDirectDescendentsOf(mRootFolderId, cats, items);
return (cats->size() + items->size());
}
else
{
return 0;
}
}
void LLFloaterMarketplaceListings::setStatusString(const std::string& statusString)
{
mInventoryStatus->setText(statusString);
}
void LLFloaterMarketplaceListings::updateView()
{
U32 mkt_status = LLMarketplaceData::instance().getSLMStatus();
// Get or create the root folder if we are a merchant and it hasn't been done already
if (mRootFolderId.isNull() && (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT))
{
setup();
}
// Update the bottom initializing status and progress dial
if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING)
{
setStatusString(getString("MarketplaceListingsInitializing"));
mInventoryInitializationInProgress->setVisible(true);
}
else
{
setStatusString("");
mInventoryInitializationInProgress->setVisible(false);
}
// Update the middle portion : tabs or messages
if (getFolderCount() > 0)
{
if (mFirstViewListings)
{
// We need to rebuild the tabs cleanly the first time we make them visible
// setup() does it if the root is nixed first
mRootFolderId.setNull();
setup();
mFirstViewListings = false;
}
mPanelListings->setVisible(TRUE);
mInventoryPlaceholder->setVisible(FALSE);
}
else
{
mPanelListings->setVisible(FALSE);
mInventoryPlaceholder->setVisible(TRUE);
std::string text;
std::string title;
std::string tooltip;
const LLSD& subs = getMarketplaceStringSubstitutions();
// Update the top message or flip to the tabs and folders view
// *TODO : check those messages and create better appropriate ones in strings.xml
if (mRootFolderId.notNull())
{
// "Marketplace listings is empty!" message strings
text = LLTrans::getString("InventoryMarketplaceListingsNoItems", subs);
title = LLTrans::getString("InventoryMarketplaceListingsNoItemsTitle");
tooltip = LLTrans::getString("InventoryMarketplaceListingsNoItemsTooltip");
}
else if (mkt_status <= MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING)
{
// "Initializing!" message strings
text = LLTrans::getString("InventoryOutboxInitializing", subs);
title = LLTrans::getString("InventoryOutboxInitializingTitle");
tooltip = LLTrans::getString("InventoryOutboxInitializingTooltip");
}
else if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT)
{
// "Not a merchant!" message strings
text = LLTrans::getString("InventoryOutboxNotMerchant", subs);
title = LLTrans::getString("InventoryOutboxNotMerchantTitle");
tooltip = LLTrans::getString("InventoryOutboxNotMerchantTooltip");
}
else
{
// "Errors!" message strings
text = LLTrans::getString("InventoryOutboxError", subs);
title = LLTrans::getString("InventoryOutboxErrorTitle");
tooltip = LLTrans::getString("InventoryOutboxErrorTooltip");
}
mInventoryText->setValue(text);
mInventoryTitle->setValue(title);
mInventoryPlaceholder->getParent()->setToolTip(tooltip);
}
}
bool LLFloaterMarketplaceListings::isAccepted(EAcceptance accept)
{
return (accept >= ACCEPT_YES_COPY_SINGLE);
}
BOOL LLFloaterMarketplaceListings::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
EDragAndDropType cargo_type,
void* cargo_data,
EAcceptance* accept,
std::string& tooltip_msg)
{
// If there's no panel to accept drops or no existing marketplace listings folder, we refuse all drop
if (!mPanelListings || mRootFolderId.isNull())
{
return FALSE;
}
// Pass to the children
LLView * handled_view = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
BOOL handled = (handled_view != NULL);
// If no one handled it or it was not accepted, we try to accept it at the floater level as if it was dropped on the
// marketplace listings root folder
if (!handled || !isAccepted(*accept))
{
if (!mPanelListings->getVisible() && mRootFolderId.notNull())
{
LLFolderView* root_folder = mPanelListings->getRootFolder();
handled = root_folder->handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
}
}
return handled;
}
BOOL LLFloaterMarketplaceListings::handleHover(S32 x, S32 y, MASK mask)
{
return LLFloater::handleHover(x, y, mask);
}
void LLFloaterMarketplaceListings::onMouseLeave(S32 x, S32 y, MASK mask)
{
LLFloater::onMouseLeave(x, y, mask);
}
void LLFloaterMarketplaceListings::onChanged()
{
LLViewerInventoryCategory* category = gInventory.getCategory(mRootFolderId);
if (mRootFolderId.notNull() && category)
{
updateView();
}
else
{
// Invalidate the marketplace listings data
mRootFolderId.setNull();
}
}
//-----------------------------------------------------------------------------
// LLFloaterAssociateListing
//-----------------------------------------------------------------------------
LLFloaterAssociateListing::LLFloaterAssociateListing(const LLSD& key)
: LLFloater(key)
, mUUID()
{
//buildFromFile("floater_associate_listing.xml");
LLUICtrlFactory::instance().buildFloater(this, "floater_associate_listing.xml");
}
LLFloaterAssociateListing::~LLFloaterAssociateListing()
{
gFocusMgr.releaseFocusIfNeeded( this );
}
BOOL LLFloaterAssociateListing::postBuild()
{
getChild<LLButton>("OK")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::apply, this, TRUE));
getChild<LLButton>("Cancel")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::cancel, this));
getChild<LLLineEditor>("listing_id")->setPrevalidate(&LLTextValidate::validateNonNegativeS32);
center();
return LLFloater::postBuild();
}
BOOL LLFloaterAssociateListing::handleKeyHere(KEY key, MASK mask)
{
if (key == KEY_RETURN && mask == MASK_NONE)
{
apply();
return TRUE;
}
else if (key == KEY_ESCAPE && mask == MASK_NONE)
{
cancel();
return TRUE;
}
return LLFloater::handleKeyHere(key, mask);
}
// static
void LLFloaterAssociateListing::show(LLFloater* floater, const LLSD& folder_id)
{
if (!floater) return;
floater->mUUID = folder_id.asUUID();
floater->open();
}
// Callback for apply if DAMA required...
void LLFloaterAssociateListing::callback_apply(const LLSD& notification, const LLSD& response)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
if (option == 0) // YES
{
apply(FALSE);
}
}
void LLFloaterAssociateListing::apply(BOOL user_confirm)
{
if (mUUID.notNull())
{
S32 id = (S32)getChild<LLUICtrl>("listing_id")->getValue().asInteger();
if (id > 0)
{
// Check if the id exists in the merchant SLM DB: note that this record might exist in the LLMarketplaceData
// structure even if unseen in the UI, for instance, if its listing_uuid doesn't exist in the merchant inventory
LLUUID listing_uuid = LLMarketplaceData::instance().getListingFolder(id);
if (listing_uuid.notNull() && user_confirm && LLMarketplaceData::instance().getActivationState(listing_uuid))
{
// Look for user confirmation before unlisting
LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLFloaterAssociateListing::callback_apply, this, _1, _2));
return;
}
// Associate the id with the user chosen folder
LLMarketplaceData::instance().associateListing(mUUID,listing_uuid,id);
// Update the folder widgets now that the action is launched
update_marketplace_category(listing_uuid);
update_marketplace_category(mUUID);
}
else
{
LLNotificationsUtil::add("AlertMerchantListingInvalidID");
}
}
closeFloater();
}
void LLFloaterAssociateListing::cancel()
{
closeFloater();
}
//-----------------------------------------------------------------------------
// LLFloaterMarketplaceValidation
//-----------------------------------------------------------------------------
LLFloaterMarketplaceValidation::LLFloaterMarketplaceValidation(const LLSD& key)
: LLFloater(key),
mEditor(NULL)
{
//buildFromFile("floater_marketplace_validation.xml");
LLUICtrlFactory::instance().buildFloater(this, "floater_marketplace_validation.xml");
}
BOOL LLFloaterMarketplaceValidation::postBuild()
{
childSetAction("OK", onOK, this);
// This widget displays the validation messages
mEditor = getChild<LLTextEditor>("validation_text");
mEditor->setEnabled(FALSE);
mEditor->setFocus(TRUE);
mEditor->setValue(LLSD());
return TRUE;
}
LLFloaterMarketplaceValidation::~LLFloaterMarketplaceValidation()
{
}
// virtual
void LLFloaterMarketplaceValidation::draw()
{
// draw children
LLFloater::draw();
}
void LLFloaterMarketplaceValidation::onOpen(const LLSD& key)
{
// Clear the text panel
mEditor->setValue(LLSD());
// Validates the marketplace
LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false);
if (marketplacelistings_id.notNull())
{
LLViewerInventoryCategory* cat = gInventory.getCategory(marketplacelistings_id);
validate_marketplacelistings(cat, boost::bind(&LLFloaterMarketplaceValidation::appendMessage, this, _1, _2), false);
}
}
// static
void LLFloaterMarketplaceValidation::onOK( void* userdata )
{
// destroys this object
LLFloaterMarketplaceValidation* self = (LLFloaterMarketplaceValidation*) userdata;
self->closeFloater();
}
void LLFloaterMarketplaceValidation::appendMessage(std::string& message, LLError::ELevel log_level)
{
if (mEditor)
{
// Errors are printed in bold, other messages in normal font
LLStyle::Params style;
LLFontDescriptor new_desc(mEditor->getFont()->getFontDesc());
new_desc.setStyle(log_level == LLError::LEVEL_ERROR ? LLFontGL::BOLD : LLFontGL::NORMAL);
LLFontGL* new_font = LLFontGL::getFont(new_desc);
style.font = new_font;
mEditor->appendText(message, true, style);
}
}
//-----------------------------------------------------------------------------
// LLFloaterItemProperties
//-----------------------------------------------------------------------------
LLFloaterItemProperties::LLFloaterItemProperties(const LLSD& key)
: LLFloater(key)
{
}
LLFloaterItemProperties::~LLFloaterItemProperties()
{
}
BOOL LLFloaterItemProperties::postBuild()
{
// On the standalone properties floater, we have no need for a back button...
LLPanel* panel = getChild<LLPanel>("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
LLPanel* panel = getChild<LLPanel>("item_panel");
panel->setItemID(key["id"].asUUID());
}

View File

@@ -0,0 +1,204 @@
/**
* @file llfloatermarketplacelistings.h
* @brief Implementation of the marketplace listings floater and panels
* @author merov@lindenlab.com
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLFLOATERMARKETPLACELISTINGS_H
#define LL_LLFLOATERMARKETPLACELISTINGS_H
#include "llfloater.h"
#include "llinventoryfilter.h"
#include "llinventorypanel.h"
#include "llnotificationptr.h"
#include "llmodaldialog.h"
#include "lltexteditor.h"
class LLInventoryCategoriesObserver;
class LLInventoryCategoryAddedObserver;
class LLTextBox;
class LLView;
class LLFilterEditor;
class LLFloaterMarketplaceListings;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLPanelMarketplaceListings
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class LLPanelMarketplaceListings : public LLPanel
{
public:
LLPanelMarketplaceListings();
BOOL postBuild();
void draw();
LLFolderView* getRootFolder() { return mRootFolder; }
void buildAllPanels();
private:
LLInventoryPanel* buildInventoryPanel(const std::string& childname, const std::string& filename);
// UI callbacks
void onViewSortMenuItemClicked(const LLSD& userdata);
bool onViewSortMenuItemCheck(const LLSD& userdata);
void onAddButtonClicked();
void onAuditButtonClicked();
void onSelectionChange(LLInventoryPanel *panel, const std::deque<LLFolderViewItem*>& items, BOOL user_action);
void onTabChange();
void onFilterEdit(const std::string& search_string);
LLFolderView* mRootFolder;
LLButton* mAuditBtn;
LLFilterEditor* mFilterEditor;
std::string mFilterSubString;
LLInventoryFilter::ESortOrderType mSortOrder;
LLInventoryFilter::EFilterType mFilterType;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLFloaterMarketplaceListings
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class LLFloaterMarketplaceListings : public LLFloater, public LLFloaterSingleton<LLFloaterMArketplaceListings>
{
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 setStatusString(const std::string& statusString);
void onClose(bool app_quitting);
void onOpen(const LLSD& key);
void onFocusReceived();
void onChanged();
bool isAccepted(EAcceptance accept);
void updateView();
private:
S32 getFolderCount();
LLInventoryCategoriesObserver * mCategoriesObserver;
LLInventoryCategoryAddedObserver * mCategoryAddedObserver;
LLTextBox * mInventoryStatus;
LLView * mInventoryInitializationInProgress;
LLView * mInventoryPlaceholder;
LLTextBox * mInventoryText;
LLTextBox * mInventoryTitle;
LLUUID mRootFolderId;
LLPanelMarketplaceListings * mPanelListings;
bool mFirstViewListings;
};
//-----------------------------------------------------------------------------
// LLFloaterAssociateListing
//-----------------------------------------------------------------------------
class LLFloaterAssociateListing : public LLFloater, public LLUISingleton<LLFloaterAssociateListing, LLFloaterAssociatedListing>
{
friend class LLFloaterReg;
public:
virtual BOOL postBuild();
virtual BOOL handleKeyHere(KEY key, MASK mask);
static bool visible(LLFloaterAssociateListing* floater, const LLSD& key) { floater && !floater->isMinimized() && floater->isInVisibleChain(); }
static void show(LLFloaterAssociateListing* floater, const LLSD& folder_id);
static void hide(LLFloaterAssociateListing* floater, const LLSD& key) { if (floater) floater->close(); }
private:
LLFloaterAssociateListing(const LLSD& key);
virtual ~LLFloaterAssociateListing();
// UI Callbacks
void apply(BOOL user_confirm = TRUE);
void cancel();
void callback_apply(const LLSD& notification, const LLSD& response);
LLUUID mUUID;
};
//-----------------------------------------------------------------------------
// LLFloaterMarketplaceValidation
//-----------------------------------------------------------------------------
// Note: For the moment, we just display the validation text. Eventually, we should
// get the validation triggered on the server and display the html report.
// *TODO : morph into an html/text window using the pattern in llfloatertos
class LLFloaterMarketplaceValidation : public LLFloater
{
public:
LLFloaterMarketplaceValidation(const LLSD& key);
virtual ~LLFloaterMarketplaceValidation();
virtual BOOL postBuild();
virtual void draw();
virtual void onOpen(const LLSD& key);
void appendMessage(std::string& message, LLError::ELevel log_level);
static void onOK( void* userdata );
private:
LLTextEditor* mEditor;
};
//-----------------------------------------------------------------------------
// LLFloaterItemProperties
//-----------------------------------------------------------------------------
class LLFloaterItemProperties : public LLFloater
{
public:
LLFloaterItemProperties(const LLSD& key);
virtual ~LLFloaterItemProperties();
BOOL postBuild();
virtual void onOpen(const LLSD& key);
private:
};
#endif // LL_LLFLOATERMARKETPLACELISTINGS_H

View File

@@ -161,6 +161,12 @@ BOOL LLFloaterOutbox::postBuild()
mCategoryAddedObserver = new LLOutboxAddedObserver(this);
gInventory.addObserver(mCategoryAddedObserver);
// Setup callbacks for importer
LLMarketplaceInventoryImporter& importer = LLMarketplaceInventoryImporter::instance();
importer.setInitializationErrorCallback(boost::bind(&LLFloaterOutbox::initializationReportError, this, _1, _2));
importer.setStatusChangedCallback(boost::bind(&LLFloaterOutbox::importStatusChanged, this, _1));
importer.setStatusReportCallback(boost::bind(&LLFloaterOutbox::importReportResults, this, _1, _2));
return TRUE;
}
@@ -299,12 +305,8 @@ void LLFloaterOutbox::initializeMarketPlace()
// Initialize the marketplace import API
//
LLMarketplaceInventoryImporter& importer = LLMarketplaceInventoryImporter::instance();
if (!importer.isInitialized())
{
importer.setInitializationErrorCallback(boost::bind(&LLFloaterOutbox::initializationReportError, this, _1, _2));
importer.setStatusChangedCallback(boost::bind(&LLFloaterOutbox::importStatusChanged, this, _1));
importer.setStatusReportCallback(boost::bind(&LLFloaterOutbox::importReportResults, this, _1, _2));
importer.initialize();
}
}

View File

@@ -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"

View File

@@ -64,6 +64,9 @@ public:
virtual void previewItem( void ) = 0;
virtual void selectItem(void) = 0;
virtual void showProperties(void) = 0;
virtual BOOL isItemWearable() const { return FALSE; }
virtual BOOL isItemRenameable() const = 0;
virtual BOOL renameItem(const std::string& new_name) = 0;
virtual void nameOrDescriptionChanged(void) const { } // Singu note: Currently only used by LLWearableBridge.

View File

@@ -587,7 +587,8 @@ void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
void LLFolderViewItem::openItem( void )
{
if( mListener )
if (!mListener) return;
if (mAllowWear || mListener->isItemWearable())
{
mListener->openItem();
}
@@ -1124,7 +1125,7 @@ LLFolderViewFolder::LLFolderViewFolder( const std::string& name, LLUIImagePtr ic
LLUIImagePtr icon_open,
LLUIImagePtr icon_link,
LLFolderView* root,
LLFolderViewEventListener* listener ):
LLFolderViewEventListener* listener):
LLFolderViewItem( name, icon, icon_open, icon_link, 0, root, listener ), // 0 = no create time
mIsOpen(FALSE),
mExpanderHighlighted(FALSE),
@@ -1938,6 +1939,7 @@ void LLFolderViewFolder::destroyView()
while (!mFolders.empty())
{
LLFolderViewFolder *folderp = mFolders.back();
mFolders.pop_back();
folderp->destroyView(); // removes entry from mFolders
}
@@ -2411,9 +2413,16 @@ BOOL LLFolderViewFolder::handleDragAndDropToThisFolder(MASK mask,
EAcceptance* accept,
std::string& tooltip_msg)
{
if (!mAllowDrop)
{
*accept = ACCEPT_NO;
tooltip_msg = LLTrans::getString("TooltipOutboxCannotDropOnRoot");
return TRUE;
}
BOOL accepted = mListener && mListener->dragOrDrop(mask, drop, cargo_type, cargo_data);
if (accepted)
if (accepted)
{
mDragAndDropTarget = TRUE;
*accept = ACCEPT_YES_MULTI;
@@ -2797,6 +2806,7 @@ bool LLInventorySort::updateSort(U32 order)
mByDate = (order & LLInventoryFilter::SO_DATE);
mSystemToTop = (order & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP);
mFoldersByName = (order & LLInventoryFilter::SO_FOLDERS_BY_NAME);
mFoldersByWeight = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_WEIGHT);
return true;
}
return false;
@@ -2834,9 +2844,7 @@ bool LLInventorySort::operator()(const LLFolderViewItem* const& a, const LLFolde
// 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())
{
@@ -2868,6 +2876,31 @@ bool LLInventorySort::operator()(const LLFolderViewItem* const& a, const LLFolde
return (compare < 0);
}
}
else if (mFoldersByWeight)
{
S32 weight_a = compute_stock_count(a->getUUID());
S32 weight_b = compute_stock_count(b->getUUID());
if (weight_a == weight_b)
{
// Equal weight -> use alphabetical order
return (LLStringUtil::compareDict(a->getDisplayName(), b->getDisplayName()) < 0);
}
else if (weight_a == -1)
{
// No weight -> move a at the end of the list
return false;
}
else if (weight_b == -1)
{
// No weight -> move b at the end of the list
return true;
}
else
{
// Lighter is first (sorted in increasing order of weight)
return (weight_a < weight_b);
}
}
else
{
// BUG: This is very very slow. The getCreationDate() is log n in number

View File

@@ -61,7 +61,8 @@ public:
: mSortOrder(0),
mByDate(false),
mSystemToTop(false),
mFoldersByName(false) { }
mFoldersByName(false),
mFoldersByWeight(false) { }
// Returns true if order has changed
bool updateSort(U32 order);
@@ -74,6 +75,7 @@ private:
bool mByDate;
bool mSystemToTop;
bool mFoldersByName;
bool mFoldersByWeight;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -139,6 +141,8 @@ protected:
BOOL mIsLoading;
LLTimer mTimeSinceRequestStart;
bool mShowLoadStatus;
bool mAllowWear;
bool mAllowDrop;
std::string mSearchable;
U32 mSearchType;
@@ -240,6 +244,9 @@ public:
void setShowLoadStatus(bool status) { mShowLoadStatus = status; }
void setAllowWear(bool allow) { mAllowWear = allow; }
void setAllowDrop(bool allow) { mAllowDrop = allow; }
// Call through to the viewed object and return true if it can be
// removed. Returns true if it's removed.
//virtual BOOL removeRecursively(BOOL single_item);

View File

@@ -62,35 +62,95 @@ using namespace LLOldEvents;
namespace LLInventoryAction
{
bool doToSelected(LLFolderView* folder, std::string action);
void callback_doToSelected(const LLSD& notification, const LLSD& response, LLFolderView* folder, const std::string& action);
bool doToSelected(LLFolderView* root, std::string action, BOOL user_confirm = TRUE);
void buildMarketplaceFolders(LLFolderView* root);
void updateMarketplaceFolders();
std::list<LLUUID> sMarketplaceFolders; // Marketplace folders that will need update once the action is completed
}
typedef LLMemberListener<LLPanelObjectInventory> object_inventory_listener_t;
typedef LLMemberListener<LLPanelMainInventory> inventory_listener_t;
typedef LLMemberListener<LLInventoryPanel> inventory_panel_listener_t;
bool LLInventoryAction::doToSelected(LLFolderView* folder, std::string action)
std::list<LLUUID> LLInventoryAction::sMarketplaceFolders;
// Callback for doToSelected if DAMA required...
void LLInventoryAction::callback_doToSelected(const LLSD& notification, const LLSD& response, LLFolderView* folder, const std::string& action)
{
if (!folder)
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
if (option == 0) // YES
{
doToSelected(folder, action, FALSE);
}
}
bool LLInventoryAction::doToSelected(LLFolderView* root, std::string action, BOOL user_confirm)
{
if (!root)
return true;
std::set<LLUUID> selected_items = root->getSelectionList();
// Prompt the user and check for authorization for some marketplace active listing edits
if (user_confirm && (("delete" == action) || ("cut" == action) || ("rename" == action) || ("properties" == action) || ("task_properties" == action) || ("open" == action)))
{
std::set<LLUUID>::iterator set_iter = std::find_if(selected_items.begin(), selected_items.end(), boost::bind(&depth_nesting_in_marketplace, _1) >= 0);
if (set_iter != selected_items.end())
{
if ("open" == action)
{
if (get_can_item_be_worn(*set_iter))
{
// Wearing an object from any listing, active or not, is verbotten
LLNotificationsUtil::add("AlertMerchantListingCannotWear");
return;
}
// Note: we do not prompt for change when opening items (e.g. textures or note cards) on the marketplace...
}
else if (LLMarketplaceData::instance().isInActiveFolder(*set_iter) ||
LLMarketplaceData::instance().isListedAndActive(*set_iter))
{
// If item is in active listing, further confirmation is required
if ((("cut" == action) || ("delete" == action)) && (LLMarketplaceData::instance().isListed(*set_iter) || LLMarketplaceData::instance().isVersionFolder(*set_iter)))
{
// Cut or delete of the active version folder or listing folder itself will unlist the listing so ask that question specifically
LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action));
return;
}
// Any other case will simply modify but not unlist a listing
LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action));
return;
}
}
}
// Keep track of the marketplace folders that will need update of their status/name after the operation is performed
buildMarketplaceFolders(root);
LLInventoryModel* model = &gInventory;
if ("rename" == action)
{
folder->startRenamingSelectedItem();
root->startRenamingSelectedItem();
// Update the marketplace listings that have been affected by the operation
updateMarketplaceFolders();
return true;
}
else if ("delete" == action)
if ("delete" == action)
{
folder->removeSelectedItems();
root->removeSelectedItems();
// Update the marketplace listings that have been affected by the operation
updateMarketplaceFolders();
return true;
}
else if ("copy" == action || "cut" == action)
if ("copy" == action || "cut" == action)
{
LLInventoryClipboard::instance().reset();
}
std::set<LLUUID> selected_items = folder->getSelectionList();
LLMultiFloater* multi_floaterp = NULL;
if (("task_open" == action || "open" == action) && selected_items.size() > 1)
@@ -118,7 +178,7 @@ bool LLInventoryAction::doToSelected(LLFolderView* folder, std::string action)
for (set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter)
{
LLFolderViewItem* folder_item = folder->getItemByID(*set_iter);
LLFolderViewItem* folder_item = root->getItemByID(*set_iter);
if(!folder_item) continue;
LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getListener();
if(!bridge) continue;
@@ -126,6 +186,9 @@ bool LLInventoryAction::doToSelected(LLFolderView* folder, std::string action)
bridge->performAction(model, action);
}
// Update the marketplace listings that have been affected by the operation
updateMarketplaceFolders();
LLFloater::setFloaterHost(NULL);
if (multi_floaterp)
{
@@ -135,7 +198,79 @@ bool LLInventoryAction::doToSelected(LLFolderView* folder, std::string action)
return true;
}
struct LLNewWindow : public inventory_listener_t
void LLInventoryAction::buildMarketplaceFolders(LLFolderView* root)
{
// Make a list of all marketplace folders containing the elements in the selected list
// as well as the elements themselves.
// Once those elements are updated (cut, delete in particular but potentially any action), their
// containing folder will need to be updated as well as their initially containing folder. For
// instance, moving a stock folder from a listed folder to another will require an update of the
// target listing *and* the original listing. So we need to keep track of both.
sMarketplaceFolders.clear();
const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false);
std::set<LLUUID> selected_items = root->getSelectionList();
for (std::set<LLUUID>::const_iterator set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter)
{
const LLInventoryObject* obj(gInventory.getObject(*set_iter));
if (gInventory.isObjectDescendentOf(obj->getParentUUID(), marketplacelistings_id))
{
sMarketplaceFolders.push_back(obj->getParentUUID());
sMarketplaceFolders.push_back(obj->getUUID());
}
}
// Suppress dupes in the list so we wo't update listings twice
sMarketplaceFolders.sort();
sMarketplaceFolders.unique();
}
void LLInventoryAction::updateMarketplaceFolders()
{
while (!sMarketplaceFolders.empty())
{
update_marketplace_category(sMarketplaceFolders.back());
sMarketplaceFolders.pop_back();
}
}
class LLDoToSelectedPanel : public object_inventory_listener_t
{
bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
{
LLPanelObjectInventory *panel = mPtr;
LLFolderView* folder = panel->getRootFolder();
if(!folder) return true;
return LLInventoryAction::doToSelected(folder, userdata.asString());
}
};
class LLDoToSelectedFloater : public inventory_listener_t
{
bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
{
LLInventoryPanel *panel = mPtr->getPanel();
LLFolderView* folder = panel->getRootFolder();
if(!folder) return true;
return LLInventoryAction::doToSelected(folder, userdata.asString());
}
};
class LLDoToSelected : public inventory_panel_listener_t
{
bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
{
LLInventoryPanel *panel = mPtr;
LLFolderView* folder = panel->getRootFolder();
if(!folder) return true;
return LLInventoryAction::doToSelected(folder, userdata.asString());
}
};
class LLNewWindow : public inventory_listener_t
{
bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
{

File diff suppressed because it is too large Load Diff

View File

@@ -141,6 +141,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);
@@ -154,6 +157,7 @@ protected:
BOOL isCOFFolder() const; // true if COF or descendant of
BOOL isInboxFolder() const; // true if COF or descendant of marketplace inbox
BOOL isOutboxFolderDirectParent() const;
BOOL isMarketplaceListingsFolder() const; // true if descendant of Marketplace listings folder
const LLUUID getOutboxFolder() const;
virtual BOOL isItemPermissive() const;
@@ -167,7 +171,10 @@ protected:
BOOL restamp);
void removeBatchNoCheck(std::vector<LLFolderViewEventListener*>& batch);
public:
BOOL isOutboxFolder() const; // true if COF or descendant of marketplace outbox
BOOL callback_cutToClipboard(const LLSD& notification, const LLSD& response);
BOOL perform_cutToClipboard();
protected:
LLHandle<LLInventoryPanel> mInventoryPanel;
LLFolderView* mRoot;
@@ -178,6 +185,7 @@ protected:
void purgeItem(LLInventoryModel *model, const LLUUID &uuid);
virtual void buildDisplayName() const {}
void removeObject(LLInventoryModel* model, const LLUUID& uuid);
};
class AIFilePicker;
@@ -247,8 +255,10 @@ public:
mWearables(FALSE)
{}
BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop);
BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop);
BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop, BOOL user_confirm = TRUE);
BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop, BOOL user_confirm = TRUE);
void callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item);
void callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_item);
virtual void buildDisplayName() const;
@@ -266,6 +276,9 @@ public:
static LLUIImagePtr getIcon(LLFolderType::EType preferred_type);
virtual std::string getLabelSuffix() const;
virtual LLFontGL::StyleFlags getLabelStyle() const;
virtual BOOL renameItem(const std::string& new_name);
virtual BOOL removeItem();
@@ -334,9 +347,14 @@ public:
static void staticFolderOptionsMenu();
private:
void callback_pasteFromClipboard(const LLSD& notification, const LLSD& response);
void perform_pasteFromClipboard();
void gatherMessage(std::string& message, LLError::ELevel log_level);
LLUIImagePtr getFolderIcon(BOOL is_open) const;
bool mCallingCards;
bool mWearables;
std::string mMessage;
LLRootHandle<LLFolderBridge> mHandle;
};
@@ -422,6 +440,7 @@ public:
const LLUUID& uuid) :
LLItemBridge(inventory, root, uuid) {}
virtual void openItem();
virtual void buildContextMenu(LLMenuGL& menu, U32 flags);
};
class LLGestureBridge : public LLItemBridge
@@ -468,7 +487,7 @@ public:
virtual LLUIImagePtr getIcon() const;
virtual void performAction(LLInventoryModel* model, std::string action);
virtual void openItem();
virtual BOOL isItemWearable() const { return TRUE; }
virtual BOOL isItemWearable() const { return TRUE; }
virtual std::string getLabelSuffix() const;
virtual void buildContextMenu(LLMenuGL& menu, U32 flags);
virtual BOOL renameItem(const std::string& new_name);
@@ -502,7 +521,7 @@ public:
virtual LLUIImagePtr getIcon() const;
virtual void performAction(LLInventoryModel* model, std::string action);
virtual void openItem();
virtual BOOL isItemWearable() const { return TRUE; }
virtual BOOL isItemWearable() const { return TRUE; }
virtual void buildContextMenu(LLMenuGL& menu, U32 flags);
virtual std::string getLabelSuffix() const;
virtual BOOL renameItem(const std::string& new_name);

View File

@@ -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"
@@ -153,6 +155,40 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const
{
LLInventoryModelBackgroundFetch::instance().start(folder_id);
}
// Marketplace folder filtering
const U32 filterTypes = mFilterOps.mFilterTypes;
const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | FILTERTYPE_MARKETPLACE_UNASSOCIATED;
if (filterTypes & marketplace_filter)
{
S32 depth = depth_nesting_in_marketplace(folder_id);
if (depth > 0)
{
LLUUID listing_uuid = nested_parent_id(folder_id, depth);
if (filterTypes & FILTERTYPE_MARKETPLACE_ACTIVE)
{
if (!LLMarketplaceData::instance().getActivationState(listing_uuid))
{
return false;
}
}
else if (filterTypes & FILTERTYPE_MARKETPLACE_INACTIVE)
{
if (!LLMarketplaceData::instance().isListed(listing_uuid) || LLMarketplaceData::instance().getActivationState(listing_uuid))
{
return false;
}
}
else if (filterTypes & FILTER_MARKETPLACE_UNASSOCIATED)
{
if (LLMarketplaceData::instance().isListed(listing_uuid))
{
return false;
}
}
}
}
if (mFilterOps.mFilterTypes & FILTERTYPE_CATEGORY)
{
// Can only filter categories for items in your inventory
@@ -443,6 +479,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 == object_id)

View File

@@ -58,7 +58,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 empty
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
@@ -73,7 +76,8 @@ public:
SO_NAME = 0, // Sort inventory by name
SO_DATE = 0x1, // Sort inventory by date
SO_FOLDERS_BY_NAME = 0x1 << 1, // Force folder sort by name
SO_SYSTEM_FOLDERS_TO_TOP = 0x1 << 2 // Force system folders to be on top
SO_SYSTEM_FOLDERS_TO_TOP = 0x1 << 2, // Force system folders to be on top
SO_FOLDERS_BY_WEIGHT = 0x1 << 3, // Force folder sort by weight, usually, amount of some elements in their descendents
};
struct FilterOps
@@ -111,6 +115,9 @@ struct FilterOps
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);

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,11 @@ BOOL get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id)
void show_item_profile(const LLUUID& item_uuid);
// Nudge the listing categories in the inventory to signal that their marketplace status changed
void update_marketplace_category(const LLUUID& cat_id, bool perform_consistency_enforcement = true);
// Nudge all listing categories to signal that their marketplace status changed
void update_all_marketplace_count();
void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name);
void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id = LLUUID::null);
@@ -67,9 +72,19 @@ void append_path_short(const LLUUID& id, std::string& path);
void copy_item_to_outbox(LLInventoryItem* inv_item, LLUUID dest_folder, const LLUUID& top_level_folder, S32 operation_id);
void move_item_within_outbox(LLInventoryItem* inv_item, LLUUID dest_folder, S32 operation_id);
void copy_folder_to_outbox(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, const LLUUID& top_level_folder, S32 operation_id);
typedef boost::function<void(std::string& validation_message, LLError::ELevel log_level)> validation_callback_t;
bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size = 1, bool from_paste = false);
bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size = 1, bool check_items = true, bool from_paste = false);
bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy = false);
bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy = false);
bool validate_marketplacelistings(LLInventoryCategory* inv_cat, validation_callback_t cb = NULL, bool fix_hierarchy = true);
S32 depth_nesting_in_marketplace(LLUUID cur_uuid);
LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth);
S32 computer_stock_count(LLUUID cat_uuid);
/** Miscellaneous global functions
** **
*******************************************************************************/

View File

@@ -1132,7 +1132,8 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32
LLPointer<LLViewerInventoryCategory> old_cat = getCategory(cat->getUUID());
if(old_cat)
{
// We already have an old category, modify it's values
// We already have an old category, modify its values
U32 mask = LLInventoryObserver::NONE;
LLUUID old_parent_id = old_cat->getParentUUID();
LLUUID new_parent_id = cat->getParentUUID();
if(old_parent_id != new_parent_id)
@@ -1156,6 +1157,12 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32
{
mask |= LLInventoryObserver::LABEL;
}
// 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 |= LLInventoryObeserver::LABEL;
}
old_cat->copyViewerCategory(cat);
addChangedMask(mask, cat->getUUID());
}
@@ -1509,6 +1516,11 @@ void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, boo
LLPointer<LLViewerInventoryCategory> cat = (LLViewerInventoryCategory*)((LLInventoryObject*)obj);
vector_replace_with_last(*cat_list, cat);
}
// Note : We need to tell the inventory observers that those things are going to be deleted *before* the tree is cleared or they won't know what to delete (in views and view models)
addChangedMask(LLInventoryObserver::REMOVE, id);
gInventory.notifyObservers();
item_list = getUnlockedItemArray(id);
if(item_list)
{
@@ -1654,9 +1666,10 @@ void LLInventoryModel::addChangedMask(U32 mask, const LLUUID& referent)
}
mModifyMask |= mask;
if (referent.notNull())
if (referent.notNull() && (mChangedItemIDs.find(referent) == mChangedItemIDs.end()))
{
mChangedItemIDs.insert(referent);
update_marketplace_category(referent, false);
if (mask & LLInventoryObserver::ADD)
{

View File

@@ -155,6 +155,18 @@ LLInventoryPanel::LLInventoryPanel(const std::string& name,
setBackgroundOpaque(TRUE);
}
LLUUID getStartFolder(const std::string& start_folder)
{
if ("LIBRARY" == start_folder)
return gInventory.getLibraryRootFolderID();
const LLFolderType::EType preferred_type = LLViewerFolderType::lookupTypeFromNewCategoryName(start_folder);
return (preferred_type != LLFolderType::FT_NONE)
? gInventory.findCategoryUUIDForType(preferred_type, false, false)
: gInventory.getCategory(static_cast<LLUUID>(start_folder)) ? static_cast<LLUUID>(mStartFolder) // Singu Note: if start folder is an id of a folder, use it
: LLUUID::null;
}
void LLInventoryPanel::buildFolderView()
{
// Determine the root folder in case specified, and
@@ -162,21 +174,7 @@ void LLInventoryPanel::buildFolderView()
//std::string start_folder_name(params.start_folder());
const LLFolderType::EType preferred_type = LLViewerFolderType::lookupTypeFromNewCategoryName(mStartFolder);
LLUUID root_id;
if ("LIBRARY" == mStartFolder)
{
root_id = gInventory.getLibraryRootFolderID();
}
else
{
root_id = (preferred_type != LLFolderType::FT_NONE)
? gInventory.findCategoryUUIDForType(preferred_type, false)
: gInventory.getCategory(static_cast<LLUUID>(mStartFolder)) ? static_cast<LLUUID>(mStartFolder) // Singu Note: if start folder is an id of a folder, use it
: LLUUID::null;
}
LLUUID root_id = getStartFolder(mStartFolder);
if ((root_id == LLUUID::null) && !mStartFolder.empty())
{
@@ -235,7 +233,26 @@ BOOL LLInventoryPanel::postBuild()
{
setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER));
}
getFilter()->setFilterEmptySystemFolders();
// hide inbox
if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible"))
{
//getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); // Singu Nope!
getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_OUTBOX));
}
// hide marketplace listing box, unless we want to show it (note: hacky as show_root_folder is only used for marketplace...)
// Singu Note: Even more hacky! String compare for merchant StartFolder to create the same effect /*&& !mParams.show_root_folder*/
if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && mStartFolder != "merchant")
{
getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS));
}
// set the filter for the empty folder if the debug setting is on
if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders"))
{
getFilter()->setFilterEmptySystemFolders();
}
return LLPanel::postBuild();
}
@@ -671,7 +688,21 @@ void LLInventoryPanel::onIdle(void *userdata)
const LLUUID& LLInventoryPanel::getRootFolderID() const
{
return mFolderRoot.get()->getListener()->getUUID();
LLUUID root_id;
if (mFolderRoot.get() && mFolderRoot.get()->getListener())
{
root_id = mFolderRoot.get()->getListener()->getUUID();
}
else
{
root_id = getStartFolder(mStartFolder);
if (root_id.isNull())
{
llwarns << "Could not find folder of type " << preferred_type << LL_ENDL;
root_id.generateNewID();
}
}
return root_id;
}
void LLInventoryPanel::initializeViews()
@@ -732,7 +763,7 @@ LLFolderView * LLInventoryPanel::createFolderView(LLInvFVBridge * bridge, bool u
return ret;
}
LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge)
LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge* bridge)
{
return new LLFolderViewFolder(
bridge->getDisplayName(),
@@ -758,71 +789,98 @@ LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge
LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id)
{
LLInventoryObject const* objectp = gInventory.getObject(id);
LLUUID root_id = mFolderRoot.get()->getListener()->getUUID();
LLFolderViewFolder* parent_folder = NULL;
LLFolderViewItem* itemp = NULL;
if (!objectp)
{
return NULL;
}
LLFolderViewItem* folder_view_item = mFolderRoot.get()->getItemByID(id);
const LLUUID& parent_id = objectp->getParentUUID();
LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)mFolderRoot.get()->getItemByID(parent_id);
// Force the creation of an extra root level folder item if required by the inventory panel (default is "false")
bool allow_drop = true;
bool allow_wear = true; // Singu TODO: Remove this when we have params here.
bool create_root = false;
if (mStartFolder == "merchant") // Singu Note: Only done for "merchant"
{
LLUUID root_id = getRootFolderID();
if (root_id == id)
{
// We insert an extra level that's seen by the UI but has no influence on the model
parent_folder = dynamic_cast<LLFolderViewFolder*>(folder_view_item);
folder_view_item = NULL;
allow_drop = false; // merchant floaters upstream say false in xui
allow_wear = false; // merchant floaters upstream say false in xui
create_root = true;
}
}
else if (mStartFolder == "outbox")
{
allow_wear = false; // outbox floater upstream says false in xui
}
if (!folder_view_item && parent_folder)
{
if (objectp->getType() <= LLAssetType::AT_NONE ||
objectp->getType() >= LLAssetType::AT_COUNT)
{
llwarns << "LLInventoryPanel::buildNewViews called with invalid objectp->mType : "
<< ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID()
<< llendl;
return NULL;
}
if (id == root_id)
{
parent_folder = mFolderRoot.get();
}
else if (objectp)
{
const LLUUID &parent_id = objectp->getParentUUID();
parent_folder = (LLFolderViewFolder*)mFolderRoot.get()->getItemByID(parent_id);
if (parent_folder)
{
if (objectp->getType() <= LLAssetType::AT_NONE ||
objectp->getType() >= LLAssetType::AT_COUNT)
{
LL_WARNS() << "LLInventoryPanel::buildNewViews called with invalid objectp->mType : "
<< ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID()
<< LL_ENDL;
return NULL;
}
if ((objectp->getType() == LLAssetType::AT_CATEGORY) &&
(objectp->getActualType() != LLAssetType::AT_LINK_FOLDER))
if ((objectp->getType() == LLAssetType::AT_CATEGORY) &&
(objectp->getActualType() != LLAssetType::AT_LINK_FOLDER))
{
LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(objectp->getType(),
objectp->getType(),
LLInventoryType::IT_CATEGORY,
this,
mFolderRoot.get(),
objectp->getUUID());
if (new_listener)
{
LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(objectp->getType(),
objectp->getType(),
LLInventoryType::IT_CATEGORY,
this,
mFolderRoot.get(),
objectp->getUUID());
if (new_listener)
{
LLFolderViewFolder* folderp = createFolderViewFolder(new_listener);
if (folderp)
{
folderp->setItemSortOrder(mFolderRoot.get()->getSortOrder());
}
itemp = folderp;
}
}
else
{
// Build new view for item.
LLInventoryItem* item = (LLInventoryItem*)objectp;
LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(item->getType(),
item->getActualType(),
item->getInventoryType(),
this,
mFolderRoot.get(),
item->getUUID(),
item->getFlags());
if (new_listener)
{
itemp = createFolderViewItem(new_listener);
}
}
if (itemp)
{
itemp->addToFolder(parent_folder, mFolderRoot.get());
}
LLFolderViewFolder* folderp = createFolderViewFolder(new_listener);
if (folderp)
{
folderp->setItemSortOrder(mFolderRoot.get()->getSortOrder());
folderp->setAllowDrop(allow_drop);
folderp->setAllowWear(allow_wear);
}
folder_view_item = folderp;
}
}
else
{
// Build new view for item.
LLInventoryItem* item = (LLInventoryItem*)objectp;
LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(item->getType(),
item->getActualType(),
item->getInventoryType(),
this,
mFolderRoot.get(),
item->getUUID(),
item->getFlags());
if (new_listener)
{
folder_view_item = createFolderViewItem(new_listener);
}
}
if (folder_view_item)
{
llassert(parent_folder != NULL);
folder_view_item->addToFolder(parent_folder, mFolderRoot.get());
// In the case of the root folder been shown, open that folder by default once the widget is created
if (create_root)
{
folder_view_item->setOpen(TRUE);
}
}
}
@@ -860,7 +918,7 @@ LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id)
mInventory->unlockDirectDescendentArrays(id);
}
return itemp;
return folder_view_item;
}
// bit of a hack to make sure the inventory is open.
@@ -965,7 +1023,7 @@ BOOL LLInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
// If folder view is empty the (x, y) point won't be in its rect
// so the handler must be called explicitly.
// but only if was not handled before. See EXT-6746.
if (!handled && !mFolderRoot.get()->hasVisibleChildren())
if (!handled && /*mParams.allow_drop_on_root*/mStartFolder != "merchant" && !mFolderRoot.get()->hasVisibleChildren())
{
handled = mFolderRoot.get()->handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
}

View File

@@ -128,6 +128,7 @@ public:
void modelChanged(U32 mask);
LLFolderView* getRootFolder() { return mFolderRoot.get(); }
LLScrollContainer* getScrollableContainer() { return mScroller; }
bool getAllowDropOnRoot() const { return mStartFolder != "merchant"; }
void onSelectionChange(const std::deque<LLFolderViewItem*> &items, BOOL user_action);

File diff suppressed because it is too large Load Diff

View File

@@ -73,6 +73,8 @@ namespace MarketplaceStatusCodes
MARKET_PLACE_CONNECTION_FAILURE = 2,
MARKET_PLACE_MERCHANT = 3,
MARKET_PLACE_NOT_MERCHANT = 4,
MARKET_PLACE_NOT_MIGRATED_MERCHANT = 5,
MARKET_PLACE_MIGRATED_MERCHANT = 6
};
}
@@ -114,6 +116,150 @@ private:
};
// Classes handling the data coming from and going to the Marketplace SLM Server DB:
// * implement the Marketplace API
// * cache the current Marketplace data (tuples)
// * provide methods to get Marketplace data on any inventory item
// * set Marketplace data
// * signal Marketplace updates to inventory
namespace SLMErrorCodes
{
enum eCode
{
SLM_SUCCESS = 200,
SLM_RECORD_CREATED = 201,
SLM_MALFORMED_PAYLOAD = 400,
SLM_NOT_FOUND = 404,
};
}
class LLMarketplaceData;
class LLInventoryObserver;
// A Marketplace item is known by its tuple
class LLMarketplaceTuple
{
public:
friend class LLMarketplaceData;
LLMarketplaceTuple();
LLMarketplaceTuple(const LLUUID& folder_id);
LLMarketplaceTuple(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed = false);
private:
// Representation of a marketplace item in the Marketplace DB (well, what we know of it...)
LLUUID mListingFolderId;
S32 mListingId;
LLUUID mVersionFolderId;
bool mIsActive;
S32 mCountOnHand;
std::string mEditURL;
};
// Notes:
// * The mListingFolderId is used as a key to this map. It could therefore be taken off the LLMarketplaceTuple object themselves.
// * The SLM DB however uses the mListingId as its primary key and it shows in its API. In the viewer though, the mListingFolderId is what we use to grab an inventory record.
typedef std::map<LLUUID, LLMarketplaceTuple> marketplace_items_list_t;
// Session cache of all Marketplace tuples
// Notes:
// * There's one and only one possible set of Marketplace dataset per agent and per session thus making it an LLSingleton
// * Some of those records might correspond to folders that do not exist in the inventory anymore. We do not clear them out though. They just won't show up in the UI.
class LLSLMGetMerchantResponder;
class LLSLMGetListingsResponder;
class LLSLMCreateListingsResponder;
class LLSLMGetListingResponder;
class LLSLMUpdateListingsResponder;
class LLSLMAssociateListingsResponder;
class LLSLMDeleteListingsResponder;
class LLMarketplaceData
: public LLSingleton<LLMarketplaceData>
{
public:
friend class LLSLMGetMerchantResponder;
friend class LLSLMGetListingsResponder;
friend class LLSLMCreateListingsResponder;
friend class LLSLMGetListingResponder;
friend class LLSLMUpdateListingsResponder;
friend class LLSLMAssociateListingsResponder;
friend class LLSLMDeleteListingsResponder;
LLMarketplaceData();
virtual ~LLMarketplaceData();
// Public SLM API : Initialization and status
typedef boost::signals2::signal<void()> status_updated_signal_t;
void initializeSLM(const status_updated_signal_t::slot_type& cb);
U32 getSLMStatus() const { return mMarketPlaceStatus; }
void getSLMListings();
bool isEmpty() const { return (mMarketplaceItems.size() == 0); }
// High level create/delete/set Marketplace data: each method returns true if the function succeeds, false if error
bool createListing(const LLUUID& folder_id);
bool activateListing(const LLUUID& folder_id, bool activate);
bool clearListing(const LLUUID& folder_id);
bool setVersionFolder(const LLUUID& folder_id, const LLUUID& version_id);
bool associateListing(const LLUUID& folder_id, const LLUUID& source_folder_id, S32 listing_id);
bool updateCountOnHand(const LLUUID& folder_id);
bool getListing(const LLUUID& folder_id);
bool getListing(S32 listing_id);
bool deleteListing(S32 listing_id, bool update = true);
// Probe the Marketplace data set to identify folders
bool isListed(const LLUUID& folder_id); // returns true if folder_id is a Listing folder
bool isListedAndActive(const LLUUID& folder_id); // returns true if folder_id is an active (listed) Listing folder
bool isVersionFolder(const LLUUID& folder_id); // returns true if folder_id is a Version folder
bool isInActiveFolder(const LLUUID& obj_id); // returns true if the obj_id is buried in an active version folder
LLUUID getActiveFolder(const LLUUID& obj_id); // returns the UUID of the active version folder obj_id is in
bool isUpdating(const LLUUID& folder_id); // returns true if we're waiting from SLM incoming data for folder_id
// Access MArketplace data set : each method returns a default value if the argument can't be found
bool getActivationState(const LLUUID& folder_id);
S32 getListingID(const LLUUID& folder_id);
LLUUID getVersionFolder(const LLUUID& folder_id);
std::string getListingURL(const LLUUID& folder_id);
LLUUID getListingFolder(S32 listing_id);
S32 getCountOnHand(const LLUUID& folder_id);
// Used to flag if stock count values for Marketplace have to updated
bool checkDirtyCount() { if (mDirtyCount) { mDirtyCount = false; return true; } else { return false; } }
void setDirtyCount() { mDirtyCount = true; }
void setUpdating(const LLUUID& folder_id, bool isUpdating);
private:
// Modify Marketplace data set : each method returns true if the function succeeds, false if error
// Used internally only by SLM Responders when data are received from the SLM Server
bool addListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, bool update = true);
bool deleteListing(const LLUUID& folder_id, bool update = true);
bool setListingID(const LLUUID& folder_id, S32 listing_id, bool update = true);
bool setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id, bool update = true);
bool setActivationState(const LLUUID& folder_id, bool activate, bool update = true);
bool setListingURL(const LLUUID& folder_id, const std::string& edit_url, bool update = true);
bool setCountOnHand(const LLUUID& folder_id, S32 count, bool update = true);
// Private SLM API : package data and get/post/put requests to the SLM Server through the SLM API
void setSLMStatus(U32 status);
void createSLMListing(const LLUUID& folder_id);
void getSLMListing(S32 listing_id);
void updateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, S32 count);
void associateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id);
void deleteSLMListing(S32 listing_id);
std::string getSLMConnectURL(const std::string& route);
// Handling Marketplace connection and inventory connection
U32 mMarketPlaceStatus
status_updated_signal_t* mStatusUpdatedSignal;
LLInventoryObserver* mInventoryObserver;
bool mDirtyCount; // If true, stock count value need to be updated at the next check
// Update data
std::set<LLUUID> mPendingUpdateSet;
// The cache of SLM data (at last...)
marketplace_items_list_t mMarketplaceItems;
};
#endif // LL_LLMARKETPLACEFUNCTIONS_H

View File

@@ -76,6 +76,7 @@
#include "llfloaterland.h"
#include "llfloaterlandholdings.h"
#include "llfloatermap.h"
#include "llfloatermarketplacelistings.h"
#include "llfloatermediafilter.h"
#include "llfloatermemleak.h"
#include "llfloatermessagelog.h"
@@ -230,6 +231,7 @@ struct MenuFloaterDict : public LLSingleton<MenuFloaterDict>
registerFloater<LLFloaterJoystick> ("joystick");
registerFloater<LLFloaterMediaFilter> ("media filter");
registerFloater<LLFloaterMap> ("mini map");
registerFloater<LLFloaterMarketplaceListings> ("marketplace_listings");
registerFloater<LLFloaterMove> ("movement controls");
registerFloater<LLFloaterMute> ("mute list");
registerFloater<LLFloaterNotificationConsole> ("notifications console");

View File

@@ -2339,6 +2339,14 @@ bool idle_startup()
}
display_startup();
// Init SLM Marketplace connection so we know which UI should be used for the user as a merchant
// Note: Eventually, all merchant will be migrated to the new SLM system and there will be no reason to show the old UI at all.
// *TODO : Suppress the Merchant Outbox UI completely
check_merchant_status();
display_startup();
// We're successfully logged in.
gSavedSettings.setBOOL("FirstLoginThisInstall", FALSE);

View File

@@ -802,6 +802,7 @@ void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, BOOL drop,
if ( !handled )
{
// *TODO: Suppress the "outbox" case once "marketplace" is used everywhere for everyone
// Disallow drag and drop to 3D from the outbox
const LLUUID outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false);
if (outbox_id.notNull())
@@ -816,7 +817,21 @@ void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, BOOL drop,
}
}
}
// Disallow drag and drop to 3D from the marketplace
const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false);
if (marketplacelistings_id.notNull())
{
for (S32 item_index = 0; item_index < (S32)mCargoIDs.size(); item_index++)
{
if (gInventory.isObjectDescendentOf(mCargoIDs[item_index], outbox_id))
{
*acceptance = ACCEPT_NO;
mToolTipMsg = LLTrans::getString("TooltipOutboxDragToWorld");
return;
}
}
}
dragOrDrop3D( x, y, mask, drop, acceptance );
}
}

View File

@@ -131,12 +131,16 @@ LLViewerFolderDictionary::LLViewerFolderDictionary()
addEntry(LLFolderType::FT_OUTFIT, new ViewerFolderEntry("New Outfit", "inv_folder_outfit.tga", "inv_folder_outfit.tga", TRUE, false));
addEntry(LLFolderType::FT_MY_OUTFITS, new ViewerFolderEntry("My Outfits", "inv_folder_outfit.tga", "inv_folder_outfit.tga", TRUE, false));
addEntry(LLFolderType::FT_MESH, new ViewerFolderEntry("Meshes", "inv_folder_mesh.tga", "inv_folder_mesh.tga", FALSE, false));
bool boxes_invisible = !gSavedSettings.getBOOL("InventoryOutboxMakeVisible");
addEntry(LLFolderType::FT_INBOX, new ViewerFolderEntry("Received Items", "inv_folder_inbox.tga", "inv_folder_inbox.tga", FALSE, false));
addEntry(LLFolderType::FT_OUTBOX, new ViewerFolderEntry("Merchant Outbox", "inv_folder_outbox.tga", "inv_folder_outbox.tga", FALSE, false));
addEntry(LLFolderType::FT_OUTBOX, new ViewerFolderEntry("Merchant Outbox", "inv_folder_outbox.tga", "inv_folder_outbox.tga", FALSE, boxes_invisible));
addEntry(LLFolderType::FT_BASIC_ROOT, new ViewerFolderEntry("Basic Root", "inv_folder_plain_open.tga", "inv_folder_plain_closed.tga", FALSE, false));
addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new ViewerFolderEntry("Marketplace Listings", "inv_folder_plain_open.tga", "inv_folder_plain_closed.tga", 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_SUITCASE, new ViewerFolderEntry("My Suitcase", "inv_folder_plain_open.tga", "inv_folder_plain_closed.tga", FALSE, false));
addEntry(LLFolderType::FT_NONE, new ViewerFolderEntry("New Folder", "inv_folder_plain_open.tga", "inv_folder_plain_closed.tga", FALSE, false, "default"));

View File

@@ -809,6 +809,31 @@ bool LLViewerInventoryCategory::exportFileLocal(LLFILE* fp) const
return true;
}
bool LLViewerInventoryCategory::acceptItem(LLInventoryItem* inv_item)
{
bool accept = true;
// Only stock folders have limitation on which item they will accept
if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)
{
// If the item is copyable (i.e. non stock) do not accept the drop in a stock folder
if (inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()))
{
accept = false;
}
else
{
LLInventoryModel::cat_array_t* cat_array;
LLInventoryModel::item_array_t* item_array;
gInventory.getDirectDescendentsOf(getUUID(),cat_array,item_array);
// Destination stock folder must be empty OR types of incoming and existing items must be identical and have the same permissions
accept = (!item_array->size() ||
((item_array->at(0)->getInventoryType() == inv_item->getInventoryType()) &&
(item_array->at(0)->getPermissions().getMaskNextOwner() == inv_item->getPermissions().getMaskNextOwner())));
}
}
return accept;
}
void LLViewerInventoryCategory::determineFolderType()
{
/* Do NOT uncomment this code. This is for future 2.1 support of ensembles.

View File

@@ -232,6 +232,9 @@ public:
virtual void unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0);
virtual BOOL unpackMessage(const LLSD& category);
// returns true if the category object will accept the incoming item
bool acceptItem(LLInventoryItem* inv_item);
private:
friend class LLInventoryModel;
void localizeName(); // intended to be called from the LLInventoryModel

View File

@@ -94,6 +94,7 @@
#include "llhudeffecttrail.h"
#include "llhudmanager.h"
#include "llinventoryfunctions.h"
#include "llmarketplacefunctions.h"
#include "llmimetypes.h"
#include "llmenuoptionpathfindingrebakenavmesh.h"
#include "llmutelist.h"
@@ -103,6 +104,7 @@
#include "llselectmgr.h"
#include "llstatusbar.h"
#include "lltextureview.h"
#include "lltoolbar.h"
#include "lltoolcomp.h"
#include "lltoolgrab.h"
#include "lltoolmgr.h"
@@ -585,6 +587,55 @@ void rebuild_context_menus()
gAgentAvatarp->buildContextMenus();
}
void set_merchant_SLM_menu()
{
// DD-170 : SLM Alpha and Beta program : for the moment, we always show the SLM menu and
// tools so that all merchants can try out the UI, even if not migrated.
// *TODO : Keep SLM UI hidden for non migrated merchant in released viewer
//if (LLMarketplaceData::instance().getSLMStatus() == MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT)
//{
// Merchant not migrated: show only the old Merchant Outbox menu
// gMenuHolder->getChild<LLView>("MerchantOutbox")->setVisible(TRUE);
//}
//else
//{
// All other cases (new merchant, not merchant, migrated merchant): show the new Marketplace Listings menu and enable the tool
gMenuHolder->getChild<LLView>("MarketplaceListings")->setVisible(TRUE);
gToolBar->getChild<LLView>("marketplace_listings_btn")->setEnabled(false);
//}
}
void set_merchant_outbox_menu(U32 status, const LLSD& content)
{
// If the merchant is fully migrated, the API is disabled (503) and we won't show the old menu item.
// In all other cases, we show it.
if (status != MarketplaceErrorCodes::IMPORT_SERVER_API_DISABLED)
{
gMenuHolder->getChild<LLView>("MerchantOutbox")->setVisible(TRUE);
}
}
void check_merchant_status()
{
if (!gSavedSettings.getBOOL("InventoryOutboxDisplayBoth"))
{
// Hide both merchant related menu items
gMenuHolder->getChild<LLView>("MerchantOutbox")->setVisible(FALSE);
gMenuHolder->getChild<LLView>("MarketplaceListings")->setVisible(FALSE);
// Also disable the toolbar button for Marketplace Listings
gToolBar->getChild<LLView>("marketplace_listings_btn")->setEnabled(false);
// Launch an SLM test connection to get the merchant status
LLMarketplaceData::instance().initializeSLM(boost::bind(&set_merchant_SLM_menu));
// Launch a Merchant Outbox test connection to get the migration status
LLMarketplaceInventoryImporter::instance().setStatusReportCallback(boost::bind(&set_merchant_outbox_menu, _1, _2));
LLMarketplaceInventoryImporter::instance().initialize();
}
}
void init_menus()
{
S32 top = gViewerWindow->getRootView()->getRect().getHeight();
@@ -8815,6 +8866,16 @@ class LLWorldEnableEnvSettings : public view_listener_t
}
};
bool canAccessMarketplace();
class LLMarketplaceEnabled : public LLMemberListener<LLView>
{
bool handleEvent(LLPointer<LLOldEvents::LLEvent>, const LLSD& userdata)
{
gMenuHolder->findControl(userdata["control"].asString())->setValue(canAccessMarketplace());
return true;
}
};
class SinguCloseAllDialogs : public view_listener_t
{
bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
@@ -9378,6 +9439,7 @@ void initialize_menus()
addMenu(new LLWorldVisibleDestinations(), "World.VisibleDestinations");
(new LLWorldEnvSettings())->registerListener(gMenuHolder, "World.EnvSettings");
(new LLWorldEnableEnvSettings())->registerListener(gMenuHolder, "World.EnableEnvSettings");
addMenu(new LLMarketplaceEnabled, "Marketplace.Enabled");
// Tools menu

View File

@@ -89,6 +89,7 @@ BOOL enable_god_full(void* user_data);
BOOL enable_god_liaison(void* user_data);
BOOL enable_god_basic(void* user_data);
void set_underclothes_menu_options();
void check_merchant_status();
void exchange_callingcard(const LLUUID& dest_id);

View File

@@ -76,6 +76,7 @@
#include "llinventorybridge.h"
#include "llinventorymodel.h"
#include "llinventorypanel.h"
#include "llmarketplacefunctions.h"
#include "llmutelist.h"
#include "llnotify.h"
#include "llnotifications.h"
@@ -6709,7 +6710,7 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem)
LL_WARNS() << "attempt_standard_notification: Attempted to read notification parameter data into LLSD but failed:" << llsdRaw << LL_ENDL;
}
}
if (
(notificationID == "RegionEntryAccessBlocked") ||
(notificationID == "LandClaimAccessBlocked") ||
@@ -6761,6 +6762,25 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem)
LLUI::sAudioCallback(LLUUID(gSavedSettings.getString("UISndRestart")));
return true; // Floater is enough.
}
else
// Special Marketplace update notification
if (notificationID == "SLM_UPDATE_FOLDER")
{
std::string state = llsdBlock["state"].asString();
if (state == "deleted")
{
// Perform the deletion viewer side, no alert shown in this case
LLMarketplaceData::instance().deleteListing(llsdBlock["listing_id"].asInteger());
return true;
}
else
{
// In general, no message will be displayed, all we want is to get the listing updated in the marketplace floater
// If getListing() fails though, the message of the alert will be shown by the caller of attempt_standard_notification()
return LLMarketplaceData::instance().getListing(llsdBlock["listing_id"].asInteger());
}
}
LLNotificationsUtil::add(notificationID, llsdBlock);
return true;

View File

@@ -1894,6 +1894,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
capabilityNames.append("CreateInventoryCategory");
capabilityNames.append("CustomMenuAction");
capabilityNames.append("DispatchRegionInfo");
capabilityNames.append("DirectDelivery");
capabilityNames.append("EnvironmentSettings");
capabilityNames.append("EstateChangeInfo");
capabilityNames.append("EventQueueGet");

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -297,6 +297,11 @@ with the same filename but different name
<texture name="inv_folder_sound.tga"/>
<texture name="inv_folder_texture.tga"/>
<texture name="inv_folder_trash.tga" preload="true"/>
<texture name="Inv_StockFolderClosed" file_name="Inv_StockFolderClosed.png" preload="false" />
<texture name="Inv_StockFolderOpen" file_name="icons/Inv_StockFolderOpen.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="inv_item_animation.tga"/>
<texture name="inv_item_callingcard_offline.tga"/>
<texture name="inv_item_callingcard_online.tga"/>

View File

@@ -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>

View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<floater
title="MARKETPLACE LISTINGS"
name="floater_marketplace_listings"
help_topic="floater_marketplace_listings"
positioning="cascading"
width="333"
height="445"
min_width="200"
min_height="300"
can_close="true"
can_resize="true"
save_rect="true"
save_visibility="false"
reuse_instance="true">
<string name="MarketplaceListingsInitializing">Initializing...</string>
<panel
name="marketplace_listings_panel"
follows="all"
layout="topleft"
left="0"
top="0"
height="440"
width="333">
<panel
follows="all"
left="10"
height="440"
width="313"
top="0"
bg_opaque_color="InventoryBackgroundColor">
<panel
name="marketplace_listings_inventory_placeholder_panel"
follows="all"
layout="topleft"
top="0"
left="0"
width="313"
height="440"
bg_opaque_color="InventoryBackgroundColor">
<text
name="marketplace_listings_inventory_placeholder_title"
type="string"
follows="top|left|right"
layout="topleft"
top="10"
left="0"
width="313"
height="25"
wrap="true"
halign="center"
font="SansSerifBold">
Loading...
</text>
<text
name="marketplace_listings_inventory_placeholder_text"
type="string"
follows="top|left|right"
layout="topleft"
top="35"
left="0"
width="313"
height="130"
wrap="true"
halign="left" />
</panel>
<panel
name="panel_marketplace_listing"
filename="panel_marketplace_listings.xml"
class="llpanelmarketplacelistings"
top="0"
follows="all"/>
</panel>
<panel
name="marketplace_panel_status"
follows="bottom|left|right"
layout="topleft"
left="10"
width="313"
height="20">
<text
name="marketplace_status"
type="string"
follows="bottom|left|right"
layout="topleft"
top="0"
left="5"
width="150"
height="20"
wrap="true"
halign="left"
valign="center"
font="SansSerif"/>
</panel>
<layout_stack name="initialization_progress_indicator" orientation="vertical" left="0" height="440" top="0" width="333" follows="all" visible="false">
<layout_panel />
<layout_panel height="24" auto_resize="false">
<layout_stack orientation="horizontal" left="0" height="24" top="0" width="333" follows="all">
<layout_panel width="0" />
<layout_panel width="24" auto_resize="false">
<loading_indicator
height="24"
layout="topleft"
left="0"
top="0"
width="24" />
</layout_panel>
<layout_panel width="0" />
</layout_stack>
</layout_panel>
<layout_panel />
</layout_stack>
</panel>
</floater>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<floater
title="Audit Marketplace Listings"
name="floater_marketplace_validation"
help_topic="floater_marketplace_validation"
layout="topleft"
positioning="cascading"
legacy_header_height="18"
width="600"
height="500"
min_width="400"
min_height="200"
can_close="true"
can_resize="true"
can_minimize="true"
save_rect="true"
reuse_instance="true"
save_visibility="false">
<button
name="OK"
label="OK"
label_selected="OK"
layout="topleft"
follows="right|bottom"
top="465"
width="100"
height="20"
left="484"/>
<text_editor
name="validation_text"
type="string"
font="SansSerif"
length="1"
max_length="65536"
layout="topleft"
follows="all"
bg_readonly_color="TextBgReadOnlyColor"
text_readonly_color="TextFgReadOnlyColor"
top="25"
bottom="455"
left="20"
right="-20"
word_wrap="true">
MARKETPLACE_VALIDATION_TEXT
</text_editor>
</floater>

View File

@@ -1,6 +1,52 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<menu bottom="100" color="MenuDefaultBgColor" drop_shadow="true" height="101" left="100"
mouse_opaque="false" name="Popup" opaque="true" width="128">
<menu_item_call
label="Create listing"
name="Marketplace Create Listing">
<on_click function="Inventory.DoToSelected" userdata="marketplace_create_listing"/>
</menu_item_call>
<menu_item_call
label="Associate listing"
name="Marketplace Associate Listing">
<on_click function="Inventory.DoToSelected" userdata="marketplace_associate_listing"/>
</menu_item_call>
<menu_item_call
label="Unassociate listing"
name="Marketplace Disassociate Listing">
<on_click function="Inventory.DoToSelected" userdata="marketplace_disassociate_listing"/>
</menu_item_call>
<menu_item_call
label="Get (Refresh) listing"
name="Marketplace Get Listing">
<on_click function="Inventory.DoToSelected" userdata="marketplace_get_listing"/>
</menu_item_call>
<menu_item_call
label="Edit listing"
name="Marketplace Edit Listing">
<on_click function="Inventory.DoToSelected" userdata="marketplace_edit_listing"/>
</menu_item_call>
<menu_item_call
label="List"
name="Marketplace List">
<on_click function="Inventory.DoToSelected" userdata="marketplace_list"/>
</menu_item_call>
<menu_item_call
label="Unlist"
name="Marketplace Unlist">
<on_click function="Inventory.DoToSelected" userdata="marketplace_unlist"/>
</menu_item_call>
<menu_item_call
label="Activate"
name="Marketplace Activate">
<on_click function="Inventory.DoToSelected" userdata="marketplace_activate"/>
</menu_item_call>
<menu_item_call
label="Deactivate"
name="Marketplace Deactivate">
<on_click function="Inventory.DoToSelected" userdata="marketplace_deactivate"/>
</menu_item_call>
<menu_item_separator name="Marketplace Listings Separator"/>
<menu_item_call bottom_delta="-18" height="18" label="Buy" left="0" mouse_opaque="true"
name="Task Buy" width="128">
<on_click filter="" function="Inventory.DoToSelected" userdata="task_buy" />
@@ -350,6 +396,7 @@
name="Merchant Copy" width="128">
<on_click filter="" function="Inventory.DoToSelected" userdata="copy_to_outbox" />
</menu_item_call>
<!-- Singu TODO: Remove Marketplace Send when removing Outbox -->
<menu_item_call bottom_delta="-18" height="18" label="Send to Marketplace" left="0" mouse_opaque="true"
name="Marketplace Send" width="128">
<on_click filter="" function="Inventory.DoToSelected" userdata="send_to_marketplace" />

View File

@@ -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>

View File

@@ -605,6 +605,12 @@
<on_click function="PromptShowURL" name="ManageMyAccount_url"
userdata="WebLaunchJoinNow,http://secondlife.com/account/" />
</menu_item_call>
<menu_item_check
label="Marketplace listing..."
name="MarketplaceListings">
<on_click function="ShowFloater" userdata="marketplace_listings" />
<on_check function="FloaterVisible" userdata="marketplace_listings" />
</menu_item_check>
<menu_item_check
label="Merchant Outbox..."
name="MerchantOutbox">

View File

@@ -436,6 +436,121 @@ There is no marketplace on the region you've moved to.
yestext="OK"/>
</notification>
<notification
icon="OutboxStatus_Error"
name="MerchantPasteFailed"
type="outbox">
Copy or move to Marketplace Listings failed with error :
&apos;[ERROR_CODE]&apos;
<usetemplate
name="okbutton"
yestext="OK"/>
</notification>
<notification
icon="OutboxStatus_Error"
name="MerchantTransactionFailed"
type="outbox">
The transaction with the Marketplace failed with the following error :
Reason : &apos;[ERROR_REASON]&apos;
[ERROR_DESCRIPTION]
<usetemplate
name="okbutton"
yestext="OK"/>
</notification>
<notification
icon="OutboxStatus_Error"
name="MerchantListingFailed"
type="outbox">
Listing to Marketplace failed with error :
&apos;[ERROR_CODE]&apos;
<usetemplate
name="okbutton"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="ConfirmMerchantActiveChange"
type="alertmodal">
This action will change the active content of this listing. Do you want to continue?
<tag>confirm</tag>
<usetemplate
ignoretext="Confirm before I change an active listing on the marketplace"
name="okcancelignore"
notext="Cancel"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="ConfirmMerchantUnlist"
type="alertmodal">
This action will unlist this listing. Do you want to continue?
<tag>confirm</tag>
<usetemplate
ignoretext="Confirm before I unlist an active listing on the marketplace"
name="okcancelignore"
notext="Cancel"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="ConfirmMerchantClearVersion"
type="alertmodal">
This action will deactivate the version folder of this listing. Do you want to continue?
<tag>confirm</tag>
<usetemplate
ignoretext="Confirm before I deactivate the version folder of a listing on the marketplace"
name="okcancelignore"
notext="Cancel"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="AlertMerchantAssociateNeedsVersion"
type="alertmodal">
Before you can activate this listing or edit it on the Marketplace web site, you will need to activate a version folder.
<tag>confirm</tag>
<usetemplate
ignoretext="Alert about version folder requirements when I associate a listing with an existing listing id"
name="okignore"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="AlertMerchantListingNotUpdated"
type="alertmodal">
This listing could not be updated.
[URL] Click to edit it on the Marketplace.
<usetemplate
name="okbutton"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="AlertMerchantListingCannotWear"
type="alertmodal">
You cannot wear clothes or body parts that are in the Marketplace Listings folder.
<tag>fail</tag>
</notification>
<notification
icon="alertmodal.tga"
name="AlertMerchantListingInvalidID"
type="alertmodal">
Invalid listing ID.
<tag>fail</tag>
</notification>
<notification
icon="alertmodal.tga"
name="CompileQueueSaveText"
@@ -4205,6 +4320,13 @@ Are you sure you want to change the Estate Covenant?
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="SLM_UPDATE_FOLDER"
type="alertmodal">
[MESSAGE]
</notification>
<notification
icon="alertmodal.tga"
name="RegionEntryAccessBlocked_AdultsOnlyContent"

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<panel
label="Marketplace"
name="Marketplace Panel"
follows="all"
layout="topleft"
width="308"
height="440">
<panel
name="tool_panel"
follows="left|top|right"
layout="topleft"
height="30"
width="308"
top="0"
left="0">
<menu_button
name="sort_btn"
tool_tip="View/sort options"
layout="topleft"
follows="top|left"
width="31"
height="25"
left="2"
menu_filename="menu_marketplace_view.xml"
image_hover_unselected="Toolbar_Middle_Over"
image_overlay="Conv_toolbar_sort"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
menu_position="bottomleft"/>
<button
name="add_btn"
tool_tip="Create a new listing folder"
layout="topleft"
follows="top|left"
width="31"
height="25"
left_pad="2"
image_hover_unselected="Toolbar_Middle_Over"
image_overlay="Conv_toolbar_plus"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"/>
<button
name="audit_btn"
label="Check for Errors"
tool_tip="Check your marketplace listings"
layout="topleft"
follows="top|right"
width="97"
height="26"
left_pad="143"/>
</panel>
<panel
name="tab_container_panel"
follows="all"
layout="topleft"
default_tab_group="1"
width="308"
height="400">
<filter_editor
text_pad_left="10"
follows="left|top|right"
height="23"
label="Filter Marketplace Listings"
layout="topleft"
left="0"
max_length_chars="300"
name="filter_editor"
top="0"
width="308" />
<tab_container
name="marketplace_filter_tabs"
follows="all"
layout="topleft"
top="30"
left="0"
top_pad="0"
width="308"
height="370"
halign="center"
tab_height="30"
tab_group="1"
tab_position="top"
tab_min_width="50">
</tab_container>
</panel>
</panel>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<inventory_panel
label="ALL"
name="All Items"
help_topic="marketplace_tab"
layout="topleft"
follows="all"
width="308"
height="370"
top="16"
left="0"
start_folder.name="Marketplace listings"
show_empty_message="true"
show_root_folder="true"
start_folder.type="merchant"
tool_tip="Drag and drop items here to list them"
bg_opaque_color="DkGray2"
bg_alpha_color="DkGray2"
background_visible="true"
border="false"
bevel_style="none"
show_item_link_overlays="true">
<item allow_wear="false"/>
</inventory_panel>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<inventory_panel
label="LISTED"
name="Active Items"
help_topic="marketplace_tab"
layout="topleft"
follows="all"
width="308"
height="370"
left_delta="0"
start_folder.name="Marketplace listings"
show_empty_message="true"
show_root_folder="true"
allow_drop_on_root="false"
start_folder.type="merchant"
bg_opaque_color="DkGray2"
bg_alpha_color="DkGray2"
background_visible="true"
border="false"
bevel_style="none"
show_item_link_overlays="true">
<item allow_wear="false"/>
</inventory_panel>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<inventory_panel
label="UNASSOCIATED"
name="Unassociated Items"
help_topic="marketplace_tab"
layout="topleft"
follows="all"
width="308"
height="370"
left_delta="0"
start_folder.name="Marketplace listings"
show_empty_message="true"
show_root_folder="true"
start_folder.type="merchant"
bg_opaque_color="DkGray2"
bg_alpha_color="DkGray2"
background_visible="true"
border="false"
bevel_style="none"
show_item_link_overlays="true">
<item allow_wear="false"/>
</inventory_panel>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<inventory_panel
label="UNLISTED"
name="Inactive Items"
help_topic="marketplace_tab"
layout="topleft"
follows="all"
width="308"
height="370"
left_delta="0"
start_folder.name="Marketplace listings"
show_empty_message="true"
show_root_folder="true"
allow_drop_on_root="false"
start_folder.type="merchant"
bg_opaque_color="DkGray2"
bg_alpha_color="DkGray2"
background_visible="true"
border="false"
bevel_style="none"
show_item_link_overlays="true">
<item allow_wear="false"/>
</inventory_panel>

View File

@@ -412,6 +412,11 @@
<button.commit_callback function="ShowFloater" parameter="outbox"/>
</button>
</layout_panel>
<layout_panel name="panelmarketplacelistings" height="24" width="50" user_resize="false" visibility_control="ToolbarVisibleMarketplaceListings">
<button bottom="0" height="24" label="Market Listings" image_overlay="icn_toolbar_marketplace_listings.tga" image_overlay_alignment="left" image_selected="toolbar_btn_selected.tga" image_unselected="toolbar_btn_enabled.tga" image_disabled="toolbar_btn_disabled.tga" scale_image="true" name="marketplace_listings_btn" width="50" follows="left|right">
<button.commit_callback function="ShowFloater" parameter="marketplace_listings"/>
</button>
</layout_panel>
<layout_panel name="panelpreferences" height="24" width="50" user_resize="false" visibility_control="ToolbarVisiblePreferences">
<button bottom="0" height="24" label="Preferences" name="preferences_btn" tool_tip="Ctrl-P" image_overlay="icn_toolbar_preferences.tga" image_overlay_alignment="left" image_selected="toolbar_btn_selected.tga" image_unselected="toolbar_btn_enabled.tga" image_disabled="toolbar_btn_disabled.tga" scale_image="true" width="50" follows="left|right">
<button.commit_callback function="ShowFloater" parameter="preferences"/>

View File

@@ -109,14 +109,19 @@ Make sure you entered the correct Login URI. An example of a Login URI is: \"htt
<string name="TooltipLand">Land:</string>
<string name="TooltipMustSingleDrop">Only a single item can be dragged here</string>
<string name="TooltipOutboxDragToWorld">You can not rez items in your merchant outbox</string>
<string name="TooltipOutboxNoTransfer">One or more of these objects cannot be sold or transferred.</string>
<string name="TooltipOutboxNotInInventory">Your merchant outbox can only accept items directly from your inventory</string>
<string name="TooltipOutboxWorn">You can not put items you are wearing into your merchant outbox</string>
<string name="TooltipOutboxCallingCard">You can not put calling cards into your merchant outbox</string>
<string name="TooltipOutboxFolderLevels">Depth of nested folders exceeds 3</string>
<string name="TooltipOutboxTooManyFolders">Subfolder count in top-level folder exceeds 20</string>
<string name="TooltipOutboxTooManyObjects">Item count in top-level folder exceeds 200</string>
<string name="TooltipOutboxDragToWorld">You can not rez items on the marketplace</string>
<string name="TooltipOutboxNoTransfer">One or more of these objects cannot be sold or transferred</string>
<string name="TooltipOutboxNotInInventory">You can only put items from your inventory on the marketplace</string>
<string name="TooltipOutboxWorn">You can't put items you are wearing on the marketplace</string>
<string name="TooltipOutboxLinked">You can't put linked items or folders on the marketplace</string>
<string name="TooltipOutboxCallingCard">You can't put calling cards on the marketplace</string>
<string name="TooltipOutboxFolderLevels">Depth of nested folders exceeds [AMOUNT]</string>
<string name="TooltipOutboxTooManyFolders">Subfolder count exceeds [AMOUNT]</string>
<string name="TooltipOutboxTooManyObjects">Item count exceeds [AMOUNT]</string>
<string name="TooltipOutboxDragActive">You can't move a listed listing</string>
<string name="TooltipOutboxCannotDropOnRoot">You can't drop items on filtered tabs root</string>
<string name="TooltipOutboxCannotMoveRoot">You can't move the marketplace listings root folder</string>
<string name="TooltipOutboxMixedStock">All items in a stock folder must have the same type and permission</string>
<string name="TooltipDragOntoOwnChild">You can't move a folder into its child</string>
<string name="TooltipDragOntoSelf">You can't move a folder into itself</string>
@@ -3173,6 +3178,7 @@ Where tag = tag string to match. Removes bot's matching the tag.
<!-- inventory -->
<string name="InventoryNoMatchingItems">No matching items found in inventory.</string>
<string name="MarketplaceNoMatchingItems">No items found. Check the spelling of your search string and try again.</string>
<string name="InventoryNoTexture">You do not have a copy of this texture in your inventory</string>
<string name="MarketplaceURL">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/</string>
<string name="MarketplaceURL_CreateStore">http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3</string>
@@ -3207,6 +3213,40 @@ The
</string>
<string name="InventoryOutboxErrorSubs">Marketplace store</string>
<string name="InventoryOutboxError2">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 Validation Log"></string>
<string name="Marketplace Validation Warning Stock">stock folder must be contained by a version folder</string>
<string name="Marketplace Validation Warning Stock">stock folder must be contained by a version folder</string>
<string name="Marketplace Validation Error Subfolder In Stock">: Error: stock folder cannot contain subfolders</string>
<string name="Marketplace Validation Warning Empty">: Warning: folder doesn't contain any items</string>
<string name="Marketplace Validation Warning Create Stock">: Warning: creating stock folder</string>
<string name="Marketplace Validation Warning Create Version">: Warning: creating version folder</string>
<string name="Marketplace Validation Warning Move">: Warning : moving items</string>
<string name="Marketplace Validation Warning Delete">: Warning: folder content transfered to stock folder, removing empty folder</string>
<string name="Marketplace Validation Error Stock Item">: Error: no-copy items must be contained by a stock folder</string>
<string name="Marketplace Validation Warning Unwrapped Item">: Warning: items must be contained by a version folder</string>
<string name="Marketplace Validation Error">: Error: </string>
<string name="Marketplace Validation Warning">: Warning: </string>
<string name="Marketplace Validation Error Empty Version">: Warning: version folder must contain at least 1 item</string>
<string name="Marketplace Validation Error Empty Stock">: Warning: stock folder must contain at least 1 item</string>
<string name="Marketplace Error None">No errors</string>
<string name="Marketplace Error Prefix">Error: </string>
<string name="Marketplace Error Not Merchant">Before sending items to the Marketplace you will need to set yourself up as a merchant (free of charge).</string>
<string name="Marketplace Error Not Accepted">Cannot move item in that folder.</string>
<string name="Marketplace Error Unsellable Item">This item cannot be sold on the Marketplace.</string>
<string name="MarketplaceNoID">no Mkt ID</string>
<string name="MarketplaceLive">listed</string>
<string name="MarketplaceActive">active</string>
<string name="MarketplaceMax">max</string>
<string name="MarketplaceStock">stock</string>
<string name="MarketplaceNoStock">out of stock</string>
<string name="MarketplaceUpdating">updating...</string>
<!-- use value="" because they have preceding spaces -->
<string name="no_transfer" value=" (no transfer)" />