Changes: Adds LLUICtrlFactory::getBuiltFloater() to return LLFloater* when a floater has been built. In llviewermenu.cpp's LLShowFloater::handleEvent(): Return early if the floater_name is empty, otherwise if no anticipated floater is found, build one, unless one of the same name is already built, then bring it to focus. Documentation: In order to add simple codeless floaters, a general understanding of our XMLs is necessary... A simple codeless floater can be a few things, perhaps not limited to: media browsers, settings controls, and holders for inventory_panels. So how do you make them conform to the codeless standard? For the purpose of these examples, my floater file will always be floater_name.xml Let's start with a shortened xml menu entry: <menu_item_call label="Name!" name="name"> <on_click function="ShowFloater" userdata="floater_name.xml"/> </menu_item_call> Alright, that wasn't so hard, just remember the key in this is ShowFloater is the function and userdata is the filename. The next part is the actual floater, which isn't so different from making a complex floater.. There are two ways to do this, depending upon desired behavior: The first behavior is to have only one of the floater open at a time, the following short xml exemplifies this: <floater name="floater_name.xml" title="Name!"/> Having a floater with this behavior will cause the menu entry to bring it into focus. The other behavior is to allow more than one of the floater open at a time: <floater name="name" title="Name!"/> In this case, the menu entry will spawn a new version of the floater with every click, regardless of previous ones existing. Granted, these short <floater/>'s probably aren't but examples and will not likely stand alone.
616 lines
16 KiB
C++
616 lines
16 KiB
C++
/**
|
|
* @file lluictrlfactory.cpp
|
|
* @brief Factory class for creating UI controls
|
|
*
|
|
* $LicenseInfo:firstyear=2003&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2003-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "lluictrlfactory.h"
|
|
|
|
#include <fstream>
|
|
#include <boost/tokenizer.hpp>
|
|
|
|
// other library includes
|
|
#include "llcontrol.h"
|
|
#include "lldir.h"
|
|
#include "v4color.h"
|
|
|
|
// this library includes
|
|
#include "llbutton.h"
|
|
#include "llcheckboxctrl.h"
|
|
//#include "llcolorswatch.h"
|
|
#include "llcombobox.h"
|
|
#include "llcontrol.h"
|
|
#include "lldir.h"
|
|
#include "llevent.h"
|
|
#include "llfloater.h"
|
|
#include "lliconctrl.h"
|
|
#include "lllineeditor.h"
|
|
#include "llmenugl.h"
|
|
#include "llradiogroup.h"
|
|
#include "llscrollcontainer.h"
|
|
#include "llscrollingpanellist.h"
|
|
#include "llscrolllistctrl.h"
|
|
#include "llslider.h"
|
|
#include "llsliderctrl.h"
|
|
#include "llmultislider.h"
|
|
#include "llmultisliderctrl.h"
|
|
#include "llspinctrl.h"
|
|
#include "lltabcontainer.h"
|
|
#include "lltextbox.h"
|
|
#include "lltexteditor.h"
|
|
#include "llui.h"
|
|
#include "lluiimage.h"
|
|
#include "llviewborder.h"
|
|
|
|
const char XML_HEADER[] = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n";
|
|
|
|
const S32 HPAD = 4;
|
|
const S32 VPAD = 4;
|
|
const S32 FLOATER_H_MARGIN = 15;
|
|
const S32 MIN_WIDGET_HEIGHT = 10;
|
|
|
|
std::vector<std::string> LLUICtrlFactory::sXUIPaths;
|
|
|
|
// UI Ctrl class for padding
|
|
class LLUICtrlLocate : public LLUICtrl
|
|
{
|
|
public:
|
|
LLUICtrlLocate() : LLUICtrl(std::string("locate"), LLRect(0,0,0,0), FALSE, NULL, NULL) { setTabStop(FALSE); }
|
|
virtual void draw() { }
|
|
|
|
virtual LLXMLNodePtr getXML(bool save_children = true) const
|
|
{
|
|
LLXMLNodePtr node = LLUICtrl::getXML();
|
|
|
|
node->setName(LL_UI_CTRL_LOCATE_TAG);
|
|
|
|
return node;
|
|
}
|
|
|
|
static LLView *fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
|
|
{
|
|
std::string name("pad");
|
|
node->getAttributeString("name", name);
|
|
|
|
LLUICtrlLocate *new_ctrl = new LLUICtrlLocate();
|
|
new_ctrl->setName(name);
|
|
new_ctrl->initFromXML(node, parent);
|
|
return new_ctrl;
|
|
}
|
|
};
|
|
|
|
static LLRegisterWidget<LLUICtrlLocate> r1("locate");
|
|
static LLRegisterWidget<LLUICtrlLocate> r2("pad");
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLUICtrlFactory()
|
|
//-----------------------------------------------------------------------------
|
|
LLUICtrlFactory::LLUICtrlFactory()
|
|
: mDummyPanel(NULL)
|
|
{
|
|
setupPaths();
|
|
}
|
|
|
|
LLUICtrlFactory::~LLUICtrlFactory()
|
|
{
|
|
delete mDummyPanel;
|
|
mDummyPanel = NULL;
|
|
}
|
|
|
|
void LLUICtrlFactory::setupPaths()
|
|
{
|
|
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "paths.xml");
|
|
|
|
LLXMLNodePtr root;
|
|
BOOL success = LLXMLNode::parseFile(filename, root, NULL);
|
|
sXUIPaths.clear();
|
|
|
|
if (success)
|
|
{
|
|
LLXMLNodePtr path;
|
|
|
|
for (path = root->getFirstChild(); path.notNull(); path = path->getNextSibling())
|
|
{
|
|
LLUIString path_val_ui(path->getValue());
|
|
std::string language = LLUI::getLanguage();
|
|
path_val_ui.setArg("[LANGUAGE]", language);
|
|
|
|
if (std::find(sXUIPaths.begin(), sXUIPaths.end(), path_val_ui.getString()) == sXUIPaths.end())
|
|
{
|
|
sXUIPaths.push_back(path_val_ui.getString());
|
|
}
|
|
}
|
|
}
|
|
else // parsing failed
|
|
{
|
|
std::string slash = gDirUtilp->getDirDelimiter();
|
|
std::string dir = "xui" + slash + "en-us";
|
|
llwarns << "XUI::config file unable to open: " << filename << llendl;
|
|
sXUIPaths.push_back(dir);
|
|
}
|
|
}
|
|
|
|
// static
|
|
const std::vector<std::string>& LLUICtrlFactory::getXUIPaths()
|
|
{
|
|
return sXUIPaths;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getLayeredXMLNode()
|
|
//-----------------------------------------------------------------------------
|
|
bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root)
|
|
{
|
|
std::string full_filename = gDirUtilp->findSkinnedFilename(sXUIPaths.front(), xui_filename);
|
|
if (full_filename.empty())
|
|
{
|
|
// try filename as passed in since sometimes we load an xml file from a user-supplied path
|
|
if (gDirUtilp->fileExists(xui_filename))
|
|
{
|
|
full_filename = xui_filename;
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Couldn't find UI description file: " << sXUIPaths.front() + gDirUtilp->getDirDelimiter() + xui_filename << llendl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!LLXMLNode::parseFile(full_filename, root, NULL))
|
|
{
|
|
llwarns << "Problem reading UI description file: " << full_filename << llendl;
|
|
return false;
|
|
}
|
|
|
|
LLXMLNodePtr updateRoot;
|
|
|
|
std::vector<std::string>::const_iterator itor;
|
|
|
|
for (itor = sXUIPaths.begin(), ++itor; itor != sXUIPaths.end(); ++itor)
|
|
{
|
|
std::string nodeName;
|
|
std::string updateName;
|
|
|
|
std::string layer_filename = gDirUtilp->findSkinnedFilename((*itor), xui_filename);
|
|
if(layer_filename.empty())
|
|
{
|
|
// no localized version of this file, that's ok, keep looking
|
|
continue;
|
|
}
|
|
|
|
if (!LLXMLNode::parseFile(layer_filename, updateRoot, NULL))
|
|
{
|
|
llwarns << "Problem reading localized UI description file: " << (*itor) + gDirUtilp->getDirDelimiter() + xui_filename << llendl;
|
|
return false;
|
|
}
|
|
|
|
updateRoot->getAttributeString("name", updateName);
|
|
root->getAttributeString("name", nodeName);
|
|
|
|
if (updateName == nodeName)
|
|
{
|
|
LLXMLNode::updateNode(root, updateRoot);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LLUICtrlFactory::getLayeredXMLNodeFromBuffer(const std::string &buffer, LLXMLNodePtr& root)
|
|
{
|
|
if (!LLXMLNode::parseBuffer((U8*)buffer.data(), buffer.size(), root, 0)) {
|
|
llwarns << "Error reading UI description from buffer." << llendl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// buildFloater()
|
|
//-----------------------------------------------------------------------------
|
|
void LLUICtrlFactory::buildFloater(LLFloater* floaterp, const std::string& filename,
|
|
const LLCallbackMap::map_t* factory_map, BOOL open) /* Flawfinder: ignore */
|
|
{
|
|
LLXMLNodePtr root;
|
|
|
|
if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
|
|
{
|
|
return;
|
|
}
|
|
|
|
buildFloaterInternal(floaterp, root, filename, factory_map, open);
|
|
}
|
|
|
|
void LLUICtrlFactory::buildFloaterFromBuffer(LLFloater *floaterp, const std::string &buffer,
|
|
const LLCallbackMap::map_t *factory_map, BOOL open) /* Flawfinder: ignore */
|
|
{
|
|
LLXMLNodePtr root;
|
|
|
|
if (!LLUICtrlFactory::getLayeredXMLNodeFromBuffer(buffer, root))
|
|
return;
|
|
|
|
buildFloaterInternal(floaterp, root, "(buffer)", factory_map, open);
|
|
}
|
|
|
|
void LLUICtrlFactory::buildFloaterInternal(LLFloater *floaterp, LLXMLNodePtr &root, const std::string &filename,
|
|
const LLCallbackMap::map_t *factory_map, BOOL open) /* Flawfinder: ignore */
|
|
{
|
|
// root must be called floater
|
|
if( !(root->hasName("floater") || root->hasName("multi_floater") ) )
|
|
{
|
|
llwarns << "Root node should be named floater in: " << filename << llendl;
|
|
return;
|
|
}
|
|
|
|
if (factory_map)
|
|
{
|
|
mFactoryStack.push_front(factory_map);
|
|
}
|
|
|
|
floaterp->initFloaterXML(root, NULL, this, open); /* Flawfinder: ignore */
|
|
|
|
if (LLUI::sShowXUINames)
|
|
{
|
|
floaterp->setToolTip(filename);
|
|
}
|
|
|
|
if (factory_map)
|
|
{
|
|
mFactoryStack.pop_front();
|
|
}
|
|
|
|
LLHandle<LLFloater> handle = floaterp->getHandle();
|
|
mBuiltFloaters[handle] = filename;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getBuiltFloater()
|
|
//-----------------------------------------------------------------------------
|
|
LLFloater* LLUICtrlFactory::getBuiltFloater(const std::string name) const
|
|
{
|
|
for (built_floater_t::const_iterator i = mBuiltFloaters.begin(); i != mBuiltFloaters.end(); ++i)
|
|
{
|
|
LLFloater* floater = i->first.get();
|
|
if (floater && floater->getName() == name)
|
|
return floater;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// saveToXML()
|
|
//-----------------------------------------------------------------------------
|
|
S32 LLUICtrlFactory::saveToXML(LLView* viewp, const std::string& filename)
|
|
{
|
|
llofstream out(filename);
|
|
if (!out.good())
|
|
{
|
|
llwarns << "Unable to open " << filename << " for output." << llendl;
|
|
return 1;
|
|
}
|
|
|
|
out << XML_HEADER;
|
|
|
|
LLXMLNodePtr xml_node = viewp->getXML();
|
|
|
|
xml_node->writeToOstream(out);
|
|
|
|
out.close();
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// buildPanel()
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLUICtrlFactory::buildPanel(LLPanel* panelp, const std::string& filename,
|
|
const LLCallbackMap::map_t* factory_map)
|
|
{
|
|
LLXMLNodePtr root;
|
|
|
|
if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return buildPanelInternal(panelp, root, filename, factory_map);
|
|
}
|
|
|
|
BOOL LLUICtrlFactory::buildPanelFromBuffer(LLPanel *panelp, const std::string &buffer,
|
|
const LLCallbackMap::map_t* factory_map)
|
|
{
|
|
LLXMLNodePtr root;
|
|
|
|
if (!LLUICtrlFactory::getLayeredXMLNodeFromBuffer(buffer, root))
|
|
return FALSE;
|
|
|
|
return buildPanelInternal(panelp, root, "(buffer)", factory_map);
|
|
}
|
|
|
|
BOOL LLUICtrlFactory::buildPanelInternal(LLPanel* panelp, LLXMLNodePtr &root, const std::string &filename,
|
|
const LLCallbackMap::map_t* factory_map)
|
|
{
|
|
BOOL didPost = FALSE;
|
|
|
|
// root must be called panel
|
|
if( !root->hasName("panel" ) )
|
|
{
|
|
llwarns << "Root node should be named panel in : " << filename << llendl;
|
|
return didPost;
|
|
}
|
|
|
|
if (factory_map)
|
|
{
|
|
mFactoryStack.push_front(factory_map);
|
|
}
|
|
|
|
didPost = panelp->initPanelXML(root, NULL, this);
|
|
|
|
if (LLUI::sShowXUINames)
|
|
{
|
|
panelp->setToolTip(filename);
|
|
}
|
|
|
|
LLHandle<LLPanel> handle = panelp->getHandle();
|
|
mBuiltPanels[handle] = filename;
|
|
|
|
if (factory_map)
|
|
{
|
|
mFactoryStack.pop_front();
|
|
}
|
|
|
|
return didPost;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// buildMenu()
|
|
//-----------------------------------------------------------------------------
|
|
LLMenuGL *LLUICtrlFactory::buildMenu(const std::string &filename, LLView* parentp)
|
|
{
|
|
// TomY TODO: Break this function into buildMenu and buildMenuBar
|
|
LLXMLNodePtr root;
|
|
LLMenuGL* menu;
|
|
|
|
if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// root must be called panel
|
|
if( !root->hasName( "menu_bar" ) && !root->hasName( "menu" ))
|
|
{
|
|
llwarns << "Root node should be named menu bar or menu in : " << filename << llendl;
|
|
return NULL;
|
|
}
|
|
|
|
if (root->hasName("menu"))
|
|
{
|
|
menu = (LLMenuGL*)LLMenuGL::fromXML(root, parentp, this);
|
|
}
|
|
else
|
|
{
|
|
menu = (LLMenuGL*)LLMenuBarGL::fromXML(root, parentp, this);
|
|
}
|
|
|
|
if (LLUI::sShowXUINames)
|
|
{
|
|
menu->setToolTip(filename);
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// buildMenu()
|
|
//-----------------------------------------------------------------------------
|
|
LLPieMenu *LLUICtrlFactory::buildPieMenu(const std::string &filename, LLView* parentp)
|
|
{
|
|
LLXMLNodePtr root;
|
|
|
|
if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// root must be called panel
|
|
if( !root->hasName( LL_PIE_MENU_TAG ))
|
|
{
|
|
llwarns << "Root node should be named " << LL_PIE_MENU_TAG << " in : " << filename << llendl;
|
|
return NULL;
|
|
}
|
|
|
|
std::string name("menu");
|
|
root->getAttributeString("name", name);
|
|
|
|
LLPieMenu *menu = new LLPieMenu(name);
|
|
parentp->addChild(menu);
|
|
menu->initXML(root, parentp, this);
|
|
|
|
if (LLUI::sShowXUINames)
|
|
{
|
|
menu->setToolTip(filename);
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// rebuild()
|
|
//-----------------------------------------------------------------------------
|
|
void LLUICtrlFactory::rebuild()
|
|
{
|
|
built_panel_t built_panels = mBuiltPanels;
|
|
mBuiltPanels.clear();
|
|
built_panel_t::iterator built_panel_it;
|
|
for (built_panel_it = built_panels.begin();
|
|
built_panel_it != built_panels.end();
|
|
++built_panel_it)
|
|
{
|
|
std::string filename = built_panel_it->second;
|
|
LLPanel* panelp = built_panel_it->first.get();
|
|
if (!panelp)
|
|
{
|
|
continue;
|
|
}
|
|
llinfos << "Rebuilding UI panel " << panelp->getName()
|
|
<< " from " << filename
|
|
<< llendl;
|
|
BOOL visible = panelp->getVisible();
|
|
panelp->setVisible(FALSE);
|
|
panelp->setFocus(FALSE);
|
|
panelp->deleteAllChildren();
|
|
|
|
buildPanel(panelp, filename.c_str(), &panelp->getFactoryMap());
|
|
panelp->setVisible(visible);
|
|
}
|
|
|
|
built_floater_t::iterator built_floater_it;
|
|
for (built_floater_it = mBuiltFloaters.begin();
|
|
built_floater_it != mBuiltFloaters.end();
|
|
++built_floater_it)
|
|
{
|
|
LLFloater* floaterp = built_floater_it->first.get();
|
|
if (!floaterp)
|
|
{
|
|
continue;
|
|
}
|
|
std::string filename = built_floater_it->second;
|
|
llinfos << "Rebuilding UI floater " << floaterp->getName()
|
|
<< " from " << filename
|
|
<< llendl;
|
|
BOOL visible = floaterp->getVisible();
|
|
floaterp->setVisible(FALSE);
|
|
floaterp->setFocus(FALSE);
|
|
floaterp->deleteAllChildren();
|
|
|
|
gFloaterView->removeChild(floaterp);
|
|
buildFloater(floaterp, filename, &floaterp->getFactoryMap());
|
|
floaterp->setVisible(visible);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
LLView *LLUICtrlFactory::createCtrlWidget(LLPanel *parent, LLXMLNodePtr node)
|
|
{
|
|
std::string ctrl_type = node->getName()->mString;
|
|
LLStringUtil::toLower(ctrl_type);
|
|
|
|
LLWidgetClassRegistry::factory_func_t func = LLWidgetClassRegistry::getInstance()->getCreatorFunc(ctrl_type);
|
|
|
|
if (func == NULL)
|
|
{
|
|
llwarns << "Unknown control type " << ctrl_type << llendl;
|
|
return NULL;
|
|
}
|
|
|
|
if (parent == NULL)
|
|
{
|
|
if (mDummyPanel == NULL)
|
|
{
|
|
mDummyPanel = new LLPanel;
|
|
}
|
|
parent = mDummyPanel;
|
|
}
|
|
LLView *ctrl = func(node, parent, this);
|
|
|
|
return ctrl;
|
|
}
|
|
|
|
LLView* LLUICtrlFactory::createWidget(LLPanel *parent, LLXMLNodePtr node)
|
|
{
|
|
LLView* view = createCtrlWidget(parent, node);
|
|
|
|
S32 tab_group = parent->getLastTabGroup();
|
|
node->getAttributeS32("tab_group", tab_group);
|
|
|
|
if (view)
|
|
{
|
|
parent->addChild(view, tab_group);
|
|
}
|
|
|
|
return view;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// createFactoryPanel()
|
|
//-----------------------------------------------------------------------------
|
|
LLPanel* LLUICtrlFactory::createFactoryPanel(const std::string& name)
|
|
{
|
|
std::deque<const LLCallbackMap::map_t*>::iterator itor;
|
|
for (itor = mFactoryStack.begin(); itor != mFactoryStack.end(); ++itor)
|
|
{
|
|
const LLCallbackMap::map_t* factory_map = *itor;
|
|
|
|
// Look up this panel's name in the map.
|
|
LLCallbackMap::map_const_iter_t iter = factory_map->find( name );
|
|
if (iter != factory_map->end())
|
|
{
|
|
// Use the factory to create the panel, instead of using a default LLPanel.
|
|
LLPanel *ret = (LLPanel*) iter->second.mCallback( iter->second.mData );
|
|
return ret;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//static
|
|
BOOL LLUICtrlFactory::getAttributeColor(LLXMLNodePtr node, const std::string& name, LLColor4& color)
|
|
{
|
|
std::string colorstring;
|
|
BOOL res = node->getAttributeString(name.c_str(), colorstring);
|
|
if (res && LLUI::sColorsGroup)
|
|
{
|
|
if (LLUI::sColorsGroup->controlExists(colorstring))
|
|
{
|
|
color.setVec(LLUI::sColorsGroup->getColor(colorstring));
|
|
}
|
|
else
|
|
{
|
|
res = FALSE;
|
|
}
|
|
}
|
|
if (!res)
|
|
{
|
|
res = LLColor4::parseColor(colorstring, &color);
|
|
}
|
|
if (!res)
|
|
{
|
|
res = node->getAttributeColor(name.c_str(), color);
|
|
}
|
|
return res;
|
|
}
|
|
|