diff options
Diffstat (limited to 'indra/newview/llfloateruipreview.cpp')
-rw-r--r-- | indra/newview/llfloateruipreview.cpp | 3434 |
1 files changed, 1717 insertions, 1717 deletions
diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index ba0d5fc2c6..6f526e1905 100644 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -1,1717 +1,1717 @@ -/**
- * @file llfloateruipreview.cpp
- * @brief Tool for previewing and editing floaters, plus localization tool integration
- *
- * $LicenseInfo:firstyear=2008&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$
- */
-
-// Tool for previewing floaters and panels for localization and UI design purposes.
-// See: https://wiki.lindenlab.com/wiki/GUI_Preview_And_Localization_Tools
-// See: https://jira.lindenlab.com/browse/DEV-16869
-
-// *TODO: Translate error messgaes using notifications/alerts.xml
-
-#include "llviewerprecompiledheaders.h" // Precompiled headers
-
-#include "llfloateruipreview.h" // Own header
-
-// Internal utility
-#include "lldiriterator.h"
-#include "lleventtimer.h"
-#include "llexternaleditor.h"
-#include "llrender.h"
-#include "llsdutil.h"
-#include "llxmltree.h"
-#include "llviewerwindow.h"
-
-// XUI
-#include "lluictrlfactory.h"
-#include "llcombobox.h"
-#include "llnotificationsutil.h"
-#include "llresizebar.h"
-#include "llscrolllistitem.h"
-#include "llscrolllistctrl.h"
-#include "llfilepicker.h"
-#include "lldraghandle.h"
-#include "lllayoutstack.h"
-#include "lltooltip.h"
-#include "llviewermenu.h"
-#include "llrngwriter.h"
-#include "llfloater.h" // superclass
-#include "llfloaterreg.h"
-#include "llscrollcontainer.h" // scroll container for overlapping elements
-#include "lllivefile.h" // live file poll/stat/reload
-#include "llviewermenufile.h" // LLFilePickerReplyThread
-
-// Boost (for linux/unix command-line execv)
-#include <boost/tokenizer.hpp>
-#include <boost/shared_ptr.hpp>
-
-// External utility
-#include <string>
-#include <list>
-#include <map>
-
-#if LL_DARWIN
-#include <CoreFoundation/CFURL.h>
-#endif
-
-// Static initialization
-static const S32 PRIMARY_FLOATER = 1;
-static const S32 SECONDARY_FLOATER = 2;
-
-class LLOverlapPanel;
-static LLDefaultChildRegistry::Register<LLOverlapPanel> register_overlap_panel("overlap_panel");
-
-static std::string get_xui_dir()
-{
- std::string delim = gDirUtilp->getDirDelimiter();
- return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim;
-}
-
-// Forward declarations to avoid header dependencies
-class LLColor;
-class LLScrollListCtrl;
-class LLComboBox;
-class LLButton;
-class LLLineEditor;
-class LLXmlTreeNode;
-class LLFloaterUIPreview;
-class LLFadeEventTimer;
-
-class LLLocalizationResetForcer;
-class LLGUIPreviewLiveFile;
-class LLFadeEventTimer;
-class LLPreviewedFloater;
-
-// Implementation of custom overlapping element display panel
-class LLOverlapPanel : public LLPanel
-{
-public:
- struct Params : public LLInitParam::Block<Params, LLPanel::Params>
- {
- Params() {}
- };
- LLOverlapPanel(Params p = Params()) : LLPanel(p),
- mSpacing(10),
- // mClickedElement(NULL),
- mLastClickedElement(NULL)
- {
- mOriginalWidth = getRect().getWidth();
- mOriginalHeight = getRect().getHeight();
- }
- virtual void draw();
-
- typedef std::map<LLView*, std::list<LLView*> > OverlapMap;
- OverlapMap mOverlapMap; // map, of XUI element to a list of XUI elements it overlaps
-
- // LLView *mClickedElement;
- LLView *mLastClickedElement;
- int mOriginalWidth, mOriginalHeight, mSpacing;
-};
-
-
-class LLFloaterUIPreview : public LLFloater
-{
-public:
- // Setup
- LLFloaterUIPreview(const LLSD& key);
- virtual ~LLFloaterUIPreview();
-
- std::string getLocStr(S32 ID); // fetches the localization string based on what is selected in the drop-down menu
- void displayFloater(bool click, S32 ID); // needs to be public so live file can call it when it finds an update
-
- /*virtual*/ bool postBuild();
- /*virtual*/ void onClose(bool app_quitting);
-
- void refreshList(); // refresh list (empty it out and fill it up from scratch)
- void addFloaterEntry(const std::string& path); // add a single file's entry to the list of floaters
-
- static bool containerType(LLView* viewp); // check if the element is a container type and tree traverses need to look at its children
-
-public:
- LLPreviewedFloater* mDisplayedFloater; // the floater which is currently being displayed
- LLPreviewedFloater* mDisplayedFloater_2; // the floater which is currently being displayed
- LLGUIPreviewLiveFile* mLiveFile; // live file for checking for updates to the currently-displayed XML file
- LLOverlapPanel* mOverlapPanel; // custom overlapping elements panel
- // bool mHighlightingDiffs; // bool for whether localization diffs are being highlighted or not
- bool mHighlightingOverlaps; // bool for whether overlapping elements are being highlighted
-
- // typedef std::map<std::string,std::pair<std::list<std::string>,std::list<std::string> > > DiffMap; // this version copies the lists etc., and thus is bad memory-wise
- typedef std::list<std::string> StringList;
- typedef std::shared_ptr<StringList> StringListPtr;
- typedef std::map<std::string, std::pair<StringListPtr,StringListPtr> > DiffMap;
- DiffMap mDiffsMap; // map, of filename to pair of list of changed element paths and list of errors
-
-private:
- LLExternalEditor mExternalEditor;
-
- // XUI elements for this floater
- LLScrollListCtrl* mFileList; // scroll list control for file list
- LLLineEditor* mEditorPathTextBox; // text field for path to editor executable
- LLLineEditor* mEditorArgsTextBox; // text field for arguments to editor executable
- LLLineEditor* mDiffPathTextBox; // text field for path to diff file
- LLButton* mDisplayFloaterBtn; // button to display primary floater
- LLButton* mDisplayFloaterBtn_2; // button to display secondary floater
- LLButton* mEditFloaterBtn; // button to edit floater
- LLButton* mExecutableBrowseButton; // button to browse for executable
- LLButton* mCloseOtherButton; // button to close primary displayed floater
- LLButton* mCloseOtherButton_2; // button to close secondary displayed floater
- LLButton* mDiffBrowseButton; // button to browse for diff file
- LLButton* mToggleHighlightButton; // button to toggle highlight of files/elements with diffs
- LLButton* mToggleOverlapButton; // button to togle overlap panel/highlighting
- LLComboBox* mLanguageSelection; // combo box for primary language selection
- LLComboBox* mLanguageSelection_2; // combo box for secondary language selection
- S32 mLastDisplayedX, mLastDisplayedY; // stored position of last floater so the new one opens up in the same place
- std::string mDelim; // the OS-specific delimiter character (/ or \) (*TODO: this shouldn't be needed, right?)
-
- std::string mSavedEditorPath; // stored editor path so closing this floater doesn't reset it
- std::string mSavedEditorArgs; // stored editor args so closing this floater doesn't reset it
- std::string mSavedDiffPath; // stored diff file path so closing this floater doesn't reset it
-
- // Internal functionality
- static void popupAndPrintWarning(const std::string& warning); // pop up a warning
- std::string getLocalizedDirectory(); // build and return the path to the XUI directory for the currently-selected localization
- void scanDiffFile(LLXmlTreeNode* file_node); // scan a given XML node for diff entries and highlight them in its associated file
- void highlightChangedElements(); // look up the list of elements to highlight and highlight them in the current floater
- void highlightChangedFiles(); // look up the list of changed files to highlight and highlight them in the scroll list
- void findOverlapsInChildren(LLView* parent); // fill the map below with element overlap information
- static bool overlapIgnorable(LLView* viewp); // check it the element can be ignored for overlap/localization purposes
-
- // check if two elements overlap using their rectangles
- // used instead of llrect functions because by adding a few pixels of leeway I can cut down drastically on the number of overlaps
- bool elementOverlap(LLView* view1, LLView* view2);
-
- // Button/drop-down action listeners (self explanatory)
- void onClickDisplayFloater(S32 id);
- void onClickSaveFloater(S32 id);
- void onClickSaveAll(S32 id);
- void onClickEditFloater();
- void onClickBrowseForEditor();
- void getExecutablePath(const std::vector<std::string>& filenames);
- void onClickBrowseForDiffs();
- void getDiffsFilePath(const std::vector<std::string>& filenames);
- void onClickToggleDiffHighlighting();
- void onClickToggleOverlapping();
- void onClickCloseDisplayedFloater(S32 id);
- void onLanguageComboSelect(LLUICtrl* ctrl);
- void onClickExportSchema();
- void onClickShowRectangles(const LLSD& data);
-};
-
-//----------------------------------------------------------------------------
-// Local class declarations
-// Reset object to ensure that when we change the current language setting for preview purposes,
-// it automatically is reset. Constructed on the stack at the start of the method; the reset
-// occurs as it falls out of scope at the end of the method. See llfloateruipreview.cpp for usage.
-class LLLocalizationResetForcer
-{
-public:
- LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID);
- virtual ~LLLocalizationResetForcer();
-
-private:
- std::string mSavedLocalization; // the localization before we change it
-};
-
-// Implementation of live file
-// When a floater is being previewed, any saved changes to its corresponding
-// file cause the previewed floater to be reloaded
-class LLGUIPreviewLiveFile : public LLLiveFile
-{
-public:
- LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent);
- virtual ~LLGUIPreviewLiveFile();
- LLFloaterUIPreview* mParent;
- LLFadeEventTimer* mFadeTimer; // timer for fade-to-yellow-and-back effect to warn that file has been reloaded
- bool mFirstFade; // setting this avoids showing the fade reload warning on first load
- std::string mFileName;
-protected:
- bool loadFile();
-};
-
-// Implementation of graphical fade in/out (on timer) for when XUI files are updated
-class LLFadeEventTimer : public LLEventTimer
-{
-public:
- LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent);
- bool tick();
- LLGUIPreviewLiveFile* mParent;
-private:
- bool mFadingOut; // fades in then out; this is toggled in between
- LLColor4 mOriginalColor; // original color; color is reset to this after fade is coimplete
-};
-
-// Implementation of previewed floater
-// Used to override draw and mouse handler
-class LLPreviewedFloater : public LLFloater
-{
-public:
- LLPreviewedFloater(LLFloaterUIPreview* floater, const Params& params)
- : LLFloater(LLSD(), params),
- mFloaterUIPreview(floater)
- {
- }
-
- virtual void draw();
- bool handleRightMouseDown(S32 x, S32 y, MASK mask);
- bool handleToolTip(S32 x, S32 y, MASK mask);
- bool selectElement(LLView* parent, int x, int y, int depth); // select element to display its overlappers
-
- LLFloaterUIPreview* mFloaterUIPreview;
-
- // draw widget outlines
- static bool sShowRectangles;
-};
-
-bool LLPreviewedFloater::sShowRectangles = false;
-
-//----------------------------------------------------------------------------
-
-// Localization reset forcer -- ensures that when localization is temporarily changed for previewed floater, it is reset
-// Changes are made here
-LLLocalizationResetForcer::LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID)
-{
- mSavedLocalization = LLUI::getInstance()->mSettingGroups["config"]->getString("Language"); // save current localization setting
- LLUI::getInstance()->mSettingGroups["config"]->setString("Language", floater->getLocStr(ID));// hack language to be the one we want to preview floaters in
- // forcibly reset XUI paths with this new language
- gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), floater->getLocStr(ID));
-}
-
-// Actually reset in destructor
-// Changes are reversed here
-LLLocalizationResetForcer::~LLLocalizationResetForcer()
-{
- LLUI::getInstance()->mSettingGroups["config"]->setString("Language", mSavedLocalization); // reset language to what it was before we changed it
- // forcibly reset XUI paths with this new language
- gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), mSavedLocalization);
-}
-
-// Live file constructor
-// Needs full path for LLLiveFile but needs just file name for this code, hence the reduntant arguments; easier than separating later
-LLGUIPreviewLiveFile::LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent)
- : mFileName(name),
- mParent(parent),
- mFirstFade(true),
- mFadeTimer(NULL),
- LLLiveFile(path, 1.0)
-{}
-
-LLGUIPreviewLiveFile::~LLGUIPreviewLiveFile()
-{
- mParent->mLiveFile = NULL;
- if(mFadeTimer)
- {
- mFadeTimer->mParent = NULL;
- // deletes itself; see lltimer.cpp
- }
-}
-
-// Live file load
-bool LLGUIPreviewLiveFile::loadFile()
-{
- mParent->displayFloater(false,1); // redisplay the floater
- if(mFirstFade) // only fade if it wasn't just clicked on; can't use "clicked" bool below because of an oddity with setting LLLiveFile initial state
- {
- mFirstFade = false;
- }
- else
- {
- if(mFadeTimer)
- {
- mFadeTimer->mParent = NULL;
- }
- mFadeTimer = new LLFadeEventTimer(0.05f,this);
- }
- return true;
-}
-
-// Initialize fade event timer
-LLFadeEventTimer::LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent)
- : mParent(parent),
- mFadingOut(true),
- LLEventTimer(refresh)
-{
- mOriginalColor = mParent->mParent->mDisplayedFloater->getBackgroundColor();
-}
-
-// Single tick of fade event timer: increment the color
-bool LLFadeEventTimer::tick()
-{
- float diff = 0.04f;
- if(true == mFadingOut) // set fade for in/out color direction
- {
- diff = -diff;
- }
-
- if(NULL == mParent) // no more need to tick, so suicide
- {
- return true;
- }
-
- // Set up colors
- LLColor4 bg_color = mParent->mParent->mDisplayedFloater->getBackgroundColor();
- LLSD colors = bg_color.getValue();
- LLSD colors_old = colors;
-
- // Tick colors
- colors[0] = colors[0].asReal() - diff; if(colors[0].asReal() < mOriginalColor.getValue()[0].asReal()) { colors[0] = colors_old[0]; }
- colors[1] = colors[1].asReal() - diff; if(colors[1].asReal() < mOriginalColor.getValue()[1].asReal()) { colors[1] = colors_old[1]; }
- colors[2] = colors[2].asReal() + diff; if(colors[2].asReal() > mOriginalColor.getValue()[2].asReal()) { colors[2] = colors_old[2]; }
-
- // Clamp and set colors
- bg_color.setValue(colors);
- bg_color.clamp(); // make sure we didn't exceed [0,1]
- mParent->mParent->mDisplayedFloater->setBackgroundColor(bg_color);
-
- if(bg_color[2] <= 0.0f) // end of fade out, start fading in
- {
- mFadingOut = false;
- }
-
- return false;
-}
-
-// Constructor
-LLFloaterUIPreview::LLFloaterUIPreview(const LLSD& key)
- : LLFloater(key),
- mDisplayedFloater(NULL),
- mDisplayedFloater_2(NULL),
- mLiveFile(NULL),
- // sHighlightingDiffs(false),
- mHighlightingOverlaps(false),
- mLastDisplayedX(0),
- mLastDisplayedY(0)
-{
-}
-
-// Destructor
-LLFloaterUIPreview::~LLFloaterUIPreview()
-{
- // spawned floaters are deleted automatically, so we don't need to delete them here
-
- // save contents of textfields so it can be restored later if the floater is created again this session
- mSavedEditorPath = mEditorPathTextBox->getText();
- mSavedEditorArgs = mEditorArgsTextBox->getText();
- mSavedDiffPath = mDiffPathTextBox->getText();
-
- // delete live file if it exists
- if(mLiveFile)
- {
- delete mLiveFile;
- mLiveFile = NULL;
- }
-}
-
-// Perform post-build setup (defined in superclass)
-bool LLFloaterUIPreview::postBuild()
-{
- LLPanel* main_panel_tmp = getChild<LLPanel>("main_panel"); // get a pointer to the main panel in order to...
- mFileList = main_panel_tmp->getChild<LLScrollListCtrl>("name_list"); // save pointer to file list
- // Double-click opens the floater, for convenience
- mFileList->setDoubleClickCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER));
-
- setDefaultBtn("display_floater");
- // get pointers to buttons and link to callbacks
- mLanguageSelection = main_panel_tmp->getChild<LLComboBox>("language_select_combo");
- mLanguageSelection->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection));
- mLanguageSelection_2 = main_panel_tmp->getChild<LLComboBox>("language_select_combo_2");
- mLanguageSelection_2->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection));
- LLPanel* editor_panel_tmp = main_panel_tmp->getChild<LLPanel>("editor_panel");
- mDisplayFloaterBtn = main_panel_tmp->getChild<LLButton>("display_floater");
- mDisplayFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER));
- mDisplayFloaterBtn_2 = main_panel_tmp->getChild<LLButton>("display_floater_2");
- mDisplayFloaterBtn_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, SECONDARY_FLOATER));
- mToggleOverlapButton = main_panel_tmp->getChild<LLButton>("toggle_overlap_panel");
- mToggleOverlapButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleOverlapping, this));
- mCloseOtherButton = main_panel_tmp->getChild<LLButton>("close_displayed_floater");
- mCloseOtherButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, PRIMARY_FLOATER));
- mCloseOtherButton_2 = main_panel_tmp->getChild<LLButton>("close_displayed_floater_2");
- mCloseOtherButton_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, SECONDARY_FLOATER));
- mEditFloaterBtn = main_panel_tmp->getChild<LLButton>("edit_floater");
- mEditFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickEditFloater, this));
- mExecutableBrowseButton = editor_panel_tmp->getChild<LLButton>("browse_for_executable");
- LLPanel* vlt_panel_tmp = main_panel_tmp->getChild<LLPanel>("vlt_panel");
- mExecutableBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForEditor, this));
- mDiffBrowseButton = vlt_panel_tmp->getChild<LLButton>("browse_for_vlt_diffs");
- mDiffBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForDiffs, this));
- mToggleHighlightButton = vlt_panel_tmp->getChild<LLButton>("toggle_vlt_diff_highlight");
- mToggleHighlightButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleDiffHighlighting, this));
- main_panel_tmp->getChild<LLButton>("save_floater")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveFloater, this, PRIMARY_FLOATER));
- main_panel_tmp->getChild<LLButton>("save_all_floaters")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveAll, this, PRIMARY_FLOATER));
-
- getChild<LLButton>("export_schema")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickExportSchema, this));
- getChild<LLUICtrl>("show_rectangles")->setCommitCallback(
- boost::bind(&LLFloaterUIPreview::onClickShowRectangles, this, _2));
-
- // get pointers to text fields
- mEditorPathTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_path_field");
- mEditorArgsTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_args_field");
- mDiffPathTextBox = vlt_panel_tmp->getChild<LLLineEditor>("vlt_diff_path_field");
-
- // *HACK: restored saved editor path and args to textfields
- mEditorPathTextBox->setText(mSavedEditorPath);
- mEditorArgsTextBox->setText(mSavedEditorArgs);
- mDiffPathTextBox->setText(mSavedDiffPath);
-
- // Set up overlap panel
- mOverlapPanel = getChild<LLOverlapPanel>("overlap_panel");
-
- getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps);
-
- mDelim = gDirUtilp->getDirDelimiter(); // initialize delimiter to dir sep slash
-
- // refresh list of available languages (EN will still be default)
- bool found = true;
- bool found_en_us = false;
- std::string language_directory;
- std::string xui_dir = get_xui_dir(); // directory containing localizations -- don't forget trailing delim
- mLanguageSelection->removeall(); // clear out anything temporarily in list from XML
-
- LLDirIterator iter(xui_dir, "*");
- while(found) // for every directory
- {
- if((found = iter.next(language_directory))) // get next directory
- {
- std::string full_path = gDirUtilp->add(xui_dir, language_directory);
- if(LLFile::isfile(full_path.c_str())) // if it's not a directory, skip it
- {
- continue;
- }
-
- if(strncmp("template",language_directory.c_str(),8) && -1 == language_directory.find(".")) // if it's not the template directory or a hidden directory
- {
- if(!strncmp("en",language_directory.c_str(),5)) // remember if we've seen en, so we can make it default
- {
- found_en_us = true;
- }
- else
- {
- mLanguageSelection->add(std::string(language_directory)); // add it to the language selection dropdown menu
- mLanguageSelection_2->add(std::string(language_directory));
- }
- }
- }
- }
- if(found_en_us)
- {
- mLanguageSelection->add(std::string("en"),ADD_TOP); // make en first item if we found it
- mLanguageSelection_2->add(std::string("en"),ADD_TOP);
- }
- else
- {
- std::string warning = std::string("No EN localization found; check your XUI directories!");
- popupAndPrintWarning(warning);
- }
- mLanguageSelection->selectFirstItem(); // select the first item
- mLanguageSelection_2->selectFirstItem();
-
- refreshList(); // refresh the list of available floaters
-
- return true;
-}
-
-// Callback for language combo box selection: refresh current floater when you change languages
-void LLFloaterUIPreview::onLanguageComboSelect(LLUICtrl* ctrl)
-{
- LLComboBox* caller = dynamic_cast<LLComboBox*>(ctrl);
- if (!caller)
- return;
- if(caller->getName() == std::string("language_select_combo"))
- {
- if(mDisplayedFloater)
- {
- onClickCloseDisplayedFloater(PRIMARY_FLOATER);
- displayFloater(true,1);
- }
- }
- else
- {
- if(mDisplayedFloater_2)
- {
- onClickCloseDisplayedFloater(PRIMARY_FLOATER);
- displayFloater(true,2); // *TODO: make take an arg
- }
- }
-
-}
-
-void LLFloaterUIPreview::onClickExportSchema()
-{
- //NOTE: schema generation not complete
- //gViewerWindow->setCursor(UI_CURSOR_WAIT);
- //std::string template_path = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "xui", "schema");
-
- //typedef LLWidgetTypeRegistry::Registrar::registry_map_t::const_iterator registry_it;
- //registry_it end_it = LLWidgetTypeRegistry::defaultRegistrar().endItems();
- //for(registry_it it = LLWidgetTypeRegistry::defaultRegistrar().beginItems();
- // it != end_it;
- // ++it)
- //{
- // std::string widget_name = it->first;
- // const LLInitParam::BaseBlock& block =
- // (*LLDefaultParamBlockRegistry::instance().getValue(*LLWidgetTypeRegistry::instance().getValue(widget_name)))();
- // LLXMLNodePtr root_nodep = new LLXMLNode();
- // LLRNGWriter().writeRNG(widget_name, root_nodep, block, "http://www.lindenlab.com/xui");
-
- // std::string file_name(template_path + gDirUtilp->getDirDelimiter() + widget_name + ".rng");
-
- // LLFILE* rng_file = LLFile::fopen(file_name.c_str(), "w");
- // {
- // LLXMLNode::writeHeaderToFile(rng_file);
- // const bool use_type_decorations = false;
- // root_nodep->writeToFile(rng_file, std::string(), use_type_decorations);
- // }
- // fclose(rng_file);
- //}
- //gViewerWindow->setCursor(UI_CURSOR_ARROW);
-}
-
-void LLFloaterUIPreview::onClickShowRectangles(const LLSD& data)
-{
- LLPreviewedFloater::sShowRectangles = data.asBoolean();
-}
-
-// Close click handler -- delete my displayed floater if it exists
-void LLFloaterUIPreview::onClose(bool app_quitting)
-{
- if(!app_quitting && mDisplayedFloater)
- {
- onClickCloseDisplayedFloater(PRIMARY_FLOATER);
- onClickCloseDisplayedFloater(SECONDARY_FLOATER);
- delete mDisplayedFloater;
- mDisplayedFloater = NULL;
- delete mDisplayedFloater_2;
- mDisplayedFloater_2 = NULL;
- }
-}
-
-// Error handling (to avoid code repetition)
-// *TODO: this is currently unlocalized. Add to alerts/notifications.xml, someday, maybe.
-void LLFloaterUIPreview::popupAndPrintWarning(const std::string& warning)
-{
- LL_WARNS() << warning << LL_ENDL;
- LLSD args;
- args["MESSAGE"] = warning;
- LLNotificationsUtil::add("GenericAlert", args);
-}
-
-// Get localization string from drop-down menu
-std::string LLFloaterUIPreview::getLocStr(S32 ID)
-{
- if(ID == 1)
- {
- return mLanguageSelection->getSelectedItemLabel(0);
- }
- else
- {
- return mLanguageSelection_2->getSelectedItemLabel(0);
- }
-}
-
-// Get localized directory (build path from data directory to XUI files, substituting localization string in for language)
-std::string LLFloaterUIPreview::getLocalizedDirectory()
-{
- return get_xui_dir() + (getLocStr(1)) + mDelim; // e.g. "C:/Code/guipreview/indra/newview/skins/xui/en/";
-}
-
-// Refresh the list of floaters by doing a directory traverse for XML XUI floater files
-// Could be used to grab any specific language's list of compatible floaters, but currently it's just used to get all of them
-void LLFloaterUIPreview::refreshList()
-{
- // Note: the mask doesn't seem to accept regular expressions, so there need to be two directory searches here
- mFileList->clearRows(); // empty list
- std::string name;
- bool found = true;
-
- LLDirIterator floater_iter(getLocalizedDirectory(), "floater_*.xml");
- while(found) // for every floater file that matches the pattern
- {
- if((found = floater_iter.next(name))) // get next file matching pattern
- {
- addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
- }
- }
- found = true;
-
- LLDirIterator inspect_iter(getLocalizedDirectory(), "inspect_*.xml");
- while(found) // for every inspector file that matches the pattern
- {
- if((found = inspect_iter.next(name))) // get next file matching pattern
- {
- addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
- }
- }
- found = true;
-
- LLDirIterator menu_iter(getLocalizedDirectory(), "menu_*.xml");
- while(found) // for every menu file that matches the pattern
- {
- if((found = menu_iter.next(name))) // get next file matching pattern
- {
- addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
- }
- }
- found = true;
-
- LLDirIterator panel_iter(getLocalizedDirectory(), "panel_*.xml");
- while(found) // for every panel file that matches the pattern
- {
- if((found = panel_iter.next(name))) // get next file matching pattern
- {
- addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
- }
- }
- found = true;
-
- LLDirIterator sidepanel_iter(getLocalizedDirectory(), "sidepanel_*.xml");
- while(found) // for every sidepanel file that matches the pattern
- {
- if((found = sidepanel_iter.next(name))) // get next file matching pattern
- {
- addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
- }
- }
-
- if(!mFileList->isEmpty()) // if there were any matching files, just select the first one (so we don't have to worry about disabling buttons when no entry is selected)
- {
- mFileList->selectFirstItem();
- }
-}
-
-// Add a single entry to the list of available floaters
-// Note: no deduplification (shouldn't be necessary)
-void LLFloaterUIPreview::addFloaterEntry(const std::string& path)
-{
- LLUUID* entry_id = new LLUUID(); // create a new UUID
- entry_id->generate(path);
- const LLUUID& entry_id_ref = *entry_id; // get a reference to the UUID for the LLSD block
-
- // fill LLSD column entry: initialize row/col structure
- LLSD row;
- row["id"] = entry_id_ref;
- LLSD& columns = row["columns"];
-
- // Get name of floater:
- LLXmlTree xml_tree;
- std::string full_path = getLocalizedDirectory() + path; // get full path
- bool success = xml_tree.parseFile(full_path.c_str(), true); // parse xml
- std::string entry_name;
- std::string entry_title;
- if(success)
- {
- // get root (or error handle)
- LLXmlTreeNode* root_floater = xml_tree.getRoot();
- if (!root_floater)
- {
- std::string warning = std::string("No root node found in XUI file: ") + path;
- popupAndPrintWarning(warning);
- return;
- }
-
- // get name
- root_floater->getAttributeString("name",entry_name);
- if(std::string("") == entry_name)
- {
- entry_name = "Error: unable to load " + std::string(path); // set to error state if load fails
- }
-
- // get title
- root_floater->getAttributeString("title",entry_title); // some don't have a title, and some have title = "(unknown)", so just leave it blank if it fails
- }
- else
- {
- std::string warning = std::string("Unable to parse XUI file: ") + path; // error handling
- popupAndPrintWarning(warning);
- if(mLiveFile)
- {
- delete mLiveFile;
- mLiveFile = NULL;
- }
- return;
- }
-
- // Fill floater title column
- columns[0]["column"] = "title_column";
- columns[0]["type"] = "text";
- columns[0]["value"] = entry_title;
-
- // Fill floater path column
- columns[1]["column"] = "file_column";
- columns[1]["type"] = "text";
- columns[1]["value"] = std::string(path);
-
- // Fill floater name column
- columns[2]["column"] = "top_level_node_column";
- columns[2]["type"] = "text";
- columns[2]["value"] = entry_name;
-
- mFileList->addElement(row); // actually add to list
-}
-
-// Respond to button click to display/refresh currently-selected floater
-void LLFloaterUIPreview::onClickDisplayFloater(S32 caller_id)
-{
- displayFloater(true, caller_id);
-}
-
-// Saves the current floater/panel
-void LLFloaterUIPreview::onClickSaveFloater(S32 caller_id)
-{
- displayFloater(true, caller_id);
- popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files");
-}
-
-// Saves all floater/panels
-void LLFloaterUIPreview::onClickSaveAll(S32 caller_id)
-{
- int listSize = mFileList->getItemCount();
-
- for (int index = 0; index < listSize; index++)
- {
- mFileList->selectNthItem(index);
- displayFloater(true, caller_id);
- }
- popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files");
-}
-
-// Actually display the floater
-// Only set up a new live file if this came from a click (at which point there should be no existing live file), rather than from the live file's update itself;
-// otherwise, we get an infinite loop as the live file keeps recreating itself. That means this function is generally called twice.
-void LLFloaterUIPreview::displayFloater(bool click, S32 ID)
-{
- // Convince UI that we're in a different language (the one selected on the drop-down menu)
- LLLocalizationResetForcer reset_forcer(this, ID); // save old language in reset forcer object (to be reset upon destruction when it falls out of scope)
-
- LLPreviewedFloater** floaterp = (ID == 1 ? &(mDisplayedFloater) : &(mDisplayedFloater_2));
- if(ID == 1)
- {
- bool floater_already_open = mDisplayedFloater != NULL;
- if(floater_already_open) // if we are already displaying a floater
- {
- mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft; // save floater's last known position to put the new one there
- mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom;
- delete mDisplayedFloater; // delete it (this closes it too)
- mDisplayedFloater = NULL; // and reset the pointer
- }
- }
- else
- {
- if(mDisplayedFloater_2 != NULL)
- {
- delete mDisplayedFloater_2;
- mDisplayedFloater_2 = NULL;
- }
- }
-
- std::string path = mFileList->getSelectedItemLabel(1); // get the path of the currently-selected floater
- if(std::string("") == path) // if no item is selected
- {
- return; // ignore click (this can only happen with empty list; otherwise an item is always selected)
- }
-
- LLFloater::Params p(LLFloater::getDefaultParams());
- p.min_height=p.header_height;
- p.min_width=10;
-
- *floaterp = new LLPreviewedFloater(this, p);
-
- if(!strncmp(path.c_str(),"floater_",8)
- || !strncmp(path.c_str(), "inspect_", 8)) // if it's a floater
- {
- (*floaterp)->buildFromFile(path); // just build it
- (*floaterp)->openFloater((*floaterp)->getKey());
- (*floaterp)->setCanResize((*floaterp)->isResizable());
- }
- else if (!strncmp(path.c_str(),"menu_",5)) // if it's a menu
- {
- // former 'save' processing excised
- }
- else // if it is a panel...
- {
- (*floaterp)->setCanResize(true);
-
- const LLFloater::Params& floater_params = LLFloater::getDefaultParams();
- S32 floater_header_size = floater_params.header_height;
-
- LLPanel::Params panel_params;
- LLPanel* panel = LLUICtrlFactory::create<LLPanel>(panel_params); // create a new panel
-
- panel->buildFromFile(path); // build it
- panel->setOrigin(2,2); // reset its origin point so it's not offset by -left or other XUI attributes
- (*floaterp)->setTitle(path); // use the file name as its title, since panels have no guaranteed meaningful name attribute
- panel->setUseBoundingRect(true); // enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements)
- panel->updateBoundingRect(); // update bounding rect
- LLRect bounding_rect = panel->getBoundingRect(); // get the bounding rect
- LLRect new_rect = panel->getRect(); // get the panel's rect
- new_rect.unionWith(bounding_rect); // union them to make sure we get the biggest one possible
- LLRect floater_rect = new_rect;
- floater_rect.stretch(4, 4);
- (*floaterp)->reshape(floater_rect.getWidth(), floater_rect.getHeight() + floater_header_size); // reshape floater to match the union rect's dimensions
- panel->reshape(new_rect.getWidth(), new_rect.getHeight()); // reshape panel to match the union rect's dimensions as well (both are needed)
- (*floaterp)->addChild(panel); // add panel as child
- (*floaterp)->openFloater(); // open floater (needed?)
- }
-
- if(ID == 1)
- {
- (*floaterp)->setOrigin(mLastDisplayedX, mLastDisplayedY);
- }
-
- // *HACK: Remove ability to close it; if you close it, its destructor gets called, but we don't know it's null and try to delete it again,
- // resulting in a double free
- (*floaterp)->setCanClose(false);
-
- if(ID == 1)
- {
- mCloseOtherButton->setEnabled(true); // enable my floater's close button
- }
- else
- {
- mCloseOtherButton_2->setEnabled(true);
- }
-
- // Add localization to title so user knows whether it's localized or defaulted to en
- std::string full_path = getLocalizedDirectory() + path;
- std::string floater_lang = "EN";
- llstat dummy;
- if(!LLFile::stat(full_path.c_str(), &dummy)) // if the file does not exist
- {
- floater_lang = getLocStr(ID);
- }
- std::string new_title = (*floaterp)->getTitle() + std::string(" [") + floater_lang +
- (ID == 1 ? " - Primary" : " - Secondary") + std::string("]");
- (*floaterp)->setTitle(new_title);
-
- (*floaterp)->center();
- addDependentFloater(*floaterp);
-
- if(click && ID == 1)
- {
- // set up live file to track it
- if(mLiveFile)
- {
- delete mLiveFile;
- mLiveFile = NULL;
- }
- mLiveFile = new LLGUIPreviewLiveFile(std::string(full_path.c_str()),std::string(path.c_str()),this);
- mLiveFile->checkAndReload();
- mLiveFile->addToEventTimer();
- }
-
- if(ID == 1)
- {
- mToggleOverlapButton->setEnabled(true);
- }
-
- if(LLView::sHighlightingDiffs && click && ID == 1)
- {
- highlightChangedElements();
- }
-
- if(ID == 1)
- {
- mOverlapPanel->mOverlapMap.clear();
- LLView::sPreviewClickedElement = NULL; // stop overlapping elements from drawing
- mOverlapPanel->mLastClickedElement = NULL;
- findOverlapsInChildren((LLView*)mDisplayedFloater);
-
- // highlight and enable them
- if(mHighlightingOverlaps)
- {
- for(LLOverlapPanel::OverlapMap::iterator iter = mOverlapPanel->mOverlapMap.begin(); iter != mOverlapPanel->mOverlapMap.end(); ++iter)
- {
- LLView* viewp = iter->first;
- LLView::sPreviewHighlightedElements.insert(viewp);
- }
- }
- else if(LLView::sHighlightingDiffs)
- {
- highlightChangedElements();
- }
- }
-
- // NOTE: language is reset here automatically when the reset forcer object falls out of scope (see header for details)
-}
-
-// Respond to button click to edit currently-selected floater
-void LLFloaterUIPreview::onClickEditFloater()
-{
- // Determine file to edit.
- std::string file_path;
- {
- std::string file_name = mFileList->getSelectedItemLabel(1); // get the file name of the currently-selected floater
- if (file_name.empty()) // if no item is selected
- {
- LL_WARNS() << "No file selected" << LL_ENDL;
- return; // ignore click
- }
- file_path = getLocalizedDirectory() + file_name;
-
- // stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file)
- llstat dummy;
- if(LLFile::stat(file_path.c_str(), &dummy)) // if the file does not exist
- {
- popupAndPrintWarning("No file for this floater exists in the selected localization. Opening the EN version instead.");
- file_path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default
- }
- }
-
- // Set the editor command.
- std::string cmd_override;
- {
- std::string bin = mEditorPathTextBox->getText();
- if (!bin.empty())
- {
- // surround command with double quotes for the case if the path contains spaces
- if (bin.find("\"") == std::string::npos)
- {
- bin = "\"" + bin + "\"";
- }
-
- std::string args = mEditorArgsTextBox->getText();
- cmd_override = bin + " " + args;
- }
- }
-
- LLExternalEditor::EErrorCode status = mExternalEditor.setCommand("LL_XUI_EDITOR", cmd_override);
- if (status != LLExternalEditor::EC_SUCCESS)
- {
- std::string warning;
-
- if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error.
- {
- warning = getString("ExternalEditorNotSet");
- }
- else
- {
- warning = LLExternalEditor::getErrorMessage(status);
- }
-
- popupAndPrintWarning(warning);
- return;
- }
-
- // Run the editor.
- if (mExternalEditor.run(file_path) != LLExternalEditor::EC_SUCCESS)
- {
- popupAndPrintWarning(LLExternalEditor::getErrorMessage(status));
- return;
- }
-}
-
-// Respond to button click to browse for an executable with which to edit XML files
-void LLFloaterUIPreview::onClickBrowseForEditor()
-{
- // Let the user choose an executable through the file picker dialog box
- LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getExecutablePath, this, _1), LLFilePicker::FFLOAD_EXE, false);
-}
-
-void LLFloaterUIPreview::getExecutablePath(const std::vector<std::string>& filenames)
-{
- // put the selected path into text field
- const std::string chosen_path = filenames[0];
- std::string executable_path = chosen_path;
-#if LL_DARWIN
- // on Mac, if it's an application bundle, figure out the actual path from the Info.plist file
- CFStringRef path_cfstr = CFStringCreateWithCString(kCFAllocatorDefault, chosen_path.c_str(), kCFStringEncodingMacRoman); // get path as a CFStringRef
- CFURLRef path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstr, kCFURLPOSIXPathStyle, true); // turn it into a CFURLRef
- CFBundleRef chosen_bundle = CFBundleCreate(kCFAllocatorDefault, path_url); // get a handle for the bundle
- if(NULL != chosen_bundle)
- {
- CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(chosen_bundle); // get the bundle's dictionary
- if(NULL != bundleInfoDict)
- {
- CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable")); // get the name of the actual executable (e.g. TextEdit or firefox-bin)
- int max_file_length = 256; // (max file name length is 255 in OSX)
- char executable_buf[max_file_length];
- if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman)) // convert CFStringRef to char*
- {
- executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf); // append path to executable directory and then executable name to exec path
- }
- else
- {
- std::string warning = "Unable to get CString from CFString for executable path";
- popupAndPrintWarning(warning);
- }
- }
- else
- {
- std::string warning = "Unable to get bundle info dictionary from application bundle";
- popupAndPrintWarning(warning);
- }
- }
- else
- {
- if(-1 != executable_path.find(".app")) // only warn if this path actually had ".app" in it, i.e. it probably just wasn'nt an app bundle and that's okay
- {
- std::string warning = std::string("Unable to get bundle from path \"") + chosen_path + std::string("\"");
- popupAndPrintWarning(warning);
- }
- }
-
-#endif
- mEditorPathTextBox->setText(std::string(executable_path)); // copy the path to the executable to the textfield for display and later fetching
-}
-
-// Respond to button click to browse for a VLT-generated diffs file
-void LLFloaterUIPreview::onClickBrowseForDiffs()
-{
- // create load dialog box
- LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getDiffsFilePath, this, _1), LLFilePicker::FFLOAD_XML, false);
-}
-
-void LLFloaterUIPreview::getDiffsFilePath(const std::vector<std::string>& filenames)
-{
- // put the selected path into text field
- const std::string chosen_path = filenames[0];
- mDiffPathTextBox->setText(std::string(chosen_path)); // copy the path to the executable to the textfield for display and later fetching
- if(LLView::sHighlightingDiffs) // if we're already highlighting, toggle off and then on so we get the data from the new file
- {
- onClickToggleDiffHighlighting();
- onClickToggleDiffHighlighting();
- }
-}
-
-void LLFloaterUIPreview::onClickToggleDiffHighlighting()
-{
- if(mHighlightingOverlaps)
- {
- onClickToggleOverlapping();
- mToggleOverlapButton->toggleState();
- }
-
- LLView::sPreviewHighlightedElements.clear(); // clear lists first
- mDiffsMap.clear();
- mFileList->clearHighlightedItems();
-
- if(LLView::sHighlightingDiffs) // Turning highlighting off
- {
- LLView::sHighlightingDiffs = !sHighlightingDiffs;
- return;
- }
- else // Turning highlighting on
- {
- // Get the file and make sure it exists
- std::string path_in_textfield = mDiffPathTextBox->getText(); // get file path
- bool error = false;
-
- if(std::string("") == path_in_textfield) // check for blank file
- {
- std::string warning = "Unable to highlight differences because no file was provided; fill in the relevant text field";
- popupAndPrintWarning(warning);
- error = true;
- }
-
- llstat dummy;
- if(LLFile::stat(path_in_textfield.c_str(), &dummy) && !error) // check if the file exists (empty check is reduntant but useful for the informative error message)
- {
- std::string warning = std::string("Unable to highlight differences because an invalid path to a difference file was provided:\"") + path_in_textfield + "\"";
- popupAndPrintWarning(warning);
- error = true;
- }
-
- // Build a list of changed elements as given by the XML
- std::list<std::string> changed_element_names;
- LLXmlTree xml_tree;
- bool success = xml_tree.parseFile(path_in_textfield.c_str(), true);
-
- if(success && !error)
- {
- LLXmlTreeNode* root_floater = xml_tree.getRoot();
- if(!strncmp("XuiDelta",root_floater->getName().c_str(),9))
- {
- for (LLXmlTreeNode* child = root_floater->getFirstChild(); // get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?)
- child != NULL;
- child = root_floater->getNextChild()) // get child for next iteration
- {
- if(!strncmp("file",child->getName().c_str(),5))
- {
- scanDiffFile(child);
- }
- else if(!strncmp("error",child->getName().c_str(),6))
- {
- std::string error_file, error_message;
- child->getAttributeString("filename",error_file);
- child->getAttributeString("message",error_message);
- if(mDiffsMap.find(error_file) != mDiffsMap.end())
- {
- mDiffsMap.insert(std::make_pair(error_file,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList))));
- }
- mDiffsMap[error_file].second->push_back(error_message);
- }
- else
- {
- std::string warning = std::string("Child was neither a file or an error, but rather the following:\"") + std::string(child->getName()) + "\"";
- popupAndPrintWarning(warning);
- error = true;
- break;
- }
- }
- }
- else
- {
- std::string warning = std::string("Root node not named XuiDelta:\"") + path_in_textfield + "\"";
- popupAndPrintWarning(warning);
- error = true;
- }
- }
- else if(!error)
- {
- std::string warning = std::string("Unable to create tree from XML:\"") + path_in_textfield + "\"";
- popupAndPrintWarning(warning);
- error = true;
- }
-
- if(error) // if we encountered an error, reset the button to off
- {
- mToggleHighlightButton->setToggleState(false);
- }
- else // only toggle if we didn't encounter an error
- {
- LLView::sHighlightingDiffs = !sHighlightingDiffs;
- highlightChangedElements(); // *TODO: this is extraneous, right?
- highlightChangedFiles(); // *TODO: this is extraneous, right?
- }
- }
-}
-
-void LLFloaterUIPreview::scanDiffFile(LLXmlTreeNode* file_node)
-{
- // Get file name
- std::string file_name;
- file_node->getAttributeString("name",file_name);
- if(std::string("") == file_name)
- {
- std::string warning = std::string("Empty file name encountered in differences:\"") + file_name + "\"";
- popupAndPrintWarning(warning);
- return;
- }
-
- // Get a list of changed elements
- // Get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?)
- for (LLXmlTreeNode* child = file_node->getFirstChild(); child != NULL; child = file_node->getNextChild())
- {
- if(!strncmp("delta",child->getName().c_str(),6))
- {
- std::string id;
- child->getAttributeString("id",id);
- if(mDiffsMap.find(file_name) == mDiffsMap.end())
- {
- mDiffsMap.insert(std::make_pair(file_name,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList))));
- }
- mDiffsMap[file_name].first->push_back(std::string(id.c_str()));
- }
- else
- {
- std::string warning = std::string("Child of file was not a delta, but rather the following:\"") + std::string(child->getName()) + "\"";
- popupAndPrintWarning(warning);
- return;
- }
- }
-}
-
-void LLFloaterUIPreview::highlightChangedElements()
-{
- if(NULL == mLiveFile)
- {
- return;
- }
-
- // Process differences first (we want their warnings to be shown underneath other warnings)
- StringListPtr changed_element_paths;
- DiffMap::iterator iterExists = mDiffsMap.find(mLiveFile->mFileName);
- if(iterExists != mDiffsMap.end())
- {
- changed_element_paths = mDiffsMap[mLiveFile->mFileName].first; // retrieve list of changed element paths from map
- }
-
- for(std::list<std::string>::iterator iter = changed_element_paths->begin(); iter != changed_element_paths->end(); ++iter) // for every changed element path
- {
- LLView* element = mDisplayedFloater;
- if(!strncmp(iter->c_str(),".",1)) // if it's the root floater itself
- {
- continue;
- }
-
- // Split element hierarchy path on period (*HACK: it's possible that the element name will have a period in it, in which case this won't work. See https://wiki.lindenlab.com/wiki/Viewer_Localization_Tool_Documentation.)
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- boost::char_separator<char> sep(".");
- tokenizer tokens(*iter, sep);
- tokenizer::iterator token_iter;
- bool failed = false;
- for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
- {
- element = element->findChild<LLView>(*token_iter,false); // try to find element: don't recur, and don't create if missing
-
- // if we still didn't find it...
- if(NULL == element)
- {
- LL_INFOS() << "Unable to find element in XuiDelta file named \"" << *iter << "\" in file \"" << mLiveFile->mFileName <<
- "\". The element may no longer exist, the path may be incorrect, or it may not be a non-displayable element (not an LLView) such as a \"string\" type." << LL_ENDL;
- failed = true;
- break;
- }
- }
-
- if(!failed)
- {
- // Now that we have a pointer to the actual element, add it to the list of elements to be highlighted
- std::set<LLView*>::iterator iter2 = std::find(LLView::sPreviewHighlightedElements.begin(), LLView::sPreviewHighlightedElements.end(), element);
- if(iter2 == LLView::sPreviewHighlightedElements.end())
- {
- LLView::sPreviewHighlightedElements.insert(element);
- }
- }
- }
-
- // Process errors second, so their warnings show up on top of other warnings
- StringListPtr error_list;
- if(iterExists != mDiffsMap.end())
- {
- error_list = mDiffsMap[mLiveFile->mFileName].second;
- }
- for(std::list<std::string>::iterator iter = error_list->begin(); iter != error_list->end(); ++iter) // for every changed element path
- {
- std::string warning = std::string("Error listed among differences. Filename: \"") + mLiveFile->mFileName + "\". Message: \"" + *iter + "\"";
- popupAndPrintWarning(warning);
- }
-}
-
-void LLFloaterUIPreview::highlightChangedFiles()
-{
- for(DiffMap::iterator iter = mDiffsMap.begin(); iter != mDiffsMap.end(); ++iter) // for every file listed in diffs
- {
- LLScrollListItem* item = mFileList->getItemByLabel(std::string(iter->first), false, 1);
- if(item)
- {
- item->setHighlighted(true);
- }
- }
-}
-
-// Respond to button click to browse for an executable with which to edit XML files
-void LLFloaterUIPreview::onClickCloseDisplayedFloater(S32 caller_id)
-{
- if(caller_id == PRIMARY_FLOATER)
- {
- mCloseOtherButton->setEnabled(false);
- mToggleOverlapButton->setEnabled(false);
-
- if(mDisplayedFloater)
- {
- mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft;
- mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom;
- delete mDisplayedFloater;
- mDisplayedFloater = NULL;
- }
-
- if(mLiveFile)
- {
- delete mLiveFile;
- mLiveFile = NULL;
- }
-
- if(mToggleOverlapButton->getToggleState())
- {
- mToggleOverlapButton->toggleState();
- onClickToggleOverlapping();
- }
-
- LLView::sPreviewClickedElement = NULL; // stop overlapping elements panel from drawing
- mOverlapPanel->mLastClickedElement = NULL;
- }
- else
- {
- mCloseOtherButton_2->setEnabled(false);
- delete mDisplayedFloater_2;
- mDisplayedFloater_2 = NULL;
- }
-
-}
-
-void append_view_tooltip(LLView* tooltip_view, std::string *tooltip_msg)
-{
- LLRect rect = tooltip_view->getRect();
- LLRect parent_rect = tooltip_view->getParent()->getRect();
- S32 left = rect.mLeft;
- // invert coordinate system for XUI top-left layout
- S32 top = parent_rect.getHeight() - rect.mTop;
- if (!tooltip_msg->empty())
- {
- tooltip_msg->append("\n");
- }
- std::string msg = llformat("%s %d, %d (%d x %d)",
- tooltip_view->getName().c_str(),
- left,
- top,
- rect.getWidth(),
- rect.getHeight() );
- tooltip_msg->append( msg );
-}
-
-bool LLPreviewedFloater::handleToolTip(S32 x, S32 y, MASK mask)
-{
- if (!sShowRectangles)
- {
- return LLFloater::handleToolTip(x, y, mask);
- }
-
- S32 screen_x, screen_y;
- localPointToScreen(x, y, &screen_x, &screen_y);
- std::string tooltip_msg;
- LLView* tooltip_view = this;
- LLView::tree_iterator_t end_it = endTreeDFS();
- for (LLView::tree_iterator_t it = beginTreeDFS(); it != end_it; ++it)
- {
- LLView* viewp = *it;
- LLRect screen_rect;
- viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect);
- if (!(viewp->getVisible()
- && screen_rect.pointInRect(screen_x, screen_y)))
- {
- it.skipDescendants();
- }
- // only report xui names for LLUICtrls, not the various container LLViews
-
- else if (dynamic_cast<LLUICtrl*>(viewp))
- {
- // if we are in a new part of the tree (not a descendent of current tooltip_view)
- // then push the results for tooltip_view and start with a new potential view
- // NOTE: this emulates visiting only the leaf nodes that meet our criteria
-
- if (tooltip_view != this
- && !viewp->hasAncestor(tooltip_view))
- {
- append_view_tooltip(tooltip_view, &tooltip_msg);
- }
- tooltip_view = viewp;
- }
- }
-
- append_view_tooltip(tooltip_view, &tooltip_msg);
-
- LLToolTipMgr::instance().show(LLToolTip::Params()
- .message(tooltip_msg)
- .max_width(400));
- return true;
-}
-
-bool LLPreviewedFloater::handleRightMouseDown(S32 x, S32 y, MASK mask)
-{
- selectElement(this,x,y,0);
- return true;
-}
-
-// *NOTE: In order to hide all of the overlapping elements of the selected element so as to see it in context, here is what you would need to do:
-// -This selectElement call fills the overlap panel as normal. The element which is "selected" here is actually just an intermediate selection step;
-// what you've really selected is a list of elements: the one you clicked on and everything that overlaps it.
-// -The user then selects one of the elements from this list the overlap panel (click handling to the overlap panel would have to be added).
-// This becomes the final selection (as opposed to the intermediate selection that was just made).
-// -Everything else that is currently displayed on the overlap panel should be hidden from view in the previewed floater itself (setVisible(false)).
-// -Subsequent clicks on other elements in the overlap panel (they should still be there) should make other elements the final selection.
-// -On close or on the click of a new button, everything should be shown again and all selection state should be cleared.
-// ~Jacob, 8/08
-bool LLPreviewedFloater::selectElement(LLView* parent, int x, int y, int depth)
-{
- if(getVisible())
- {
- bool handled = false;
- if(LLFloaterUIPreview::containerType(parent))
- {
- for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it)
- {
- LLView* child = *child_it;
- S32 local_x = x - child->getRect().mLeft;
- S32 local_y = y - child->getRect().mBottom;
- if (child->pointInView(local_x, local_y) &&
- child->getVisible() &&
- selectElement(child, x, y, ++depth))
- {
- handled = true;
- break;
- }
- }
- }
-
- if(!handled)
- {
- LLView::sPreviewClickedElement = parent;
- }
- return true;
- }
- else
- {
- return false;
- }
-}
-
-void LLPreviewedFloater::draw()
-{
- if(NULL != mFloaterUIPreview)
- {
- // Set and unset sDrawPreviewHighlights flag so as to avoid using two flags
- if(mFloaterUIPreview->mHighlightingOverlaps)
- {
- LLView::sDrawPreviewHighlights = true;
- }
-
- // If we're looking for truncations, draw debug rects for the displayed
- // floater only.
- bool old_debug_rects = LLView::sDebugRects;
- bool old_show_names = LLView::sDebugRectsShowNames;
- if (sShowRectangles)
- {
- LLView::sDebugRects = true;
- LLView::sDebugRectsShowNames = false;
- }
-
- LLFloater::draw();
-
- LLView::sDebugRects = old_debug_rects;
- LLView::sDebugRectsShowNames = old_show_names;
-
- if(mFloaterUIPreview->mHighlightingOverlaps)
- {
- LLView::sDrawPreviewHighlights = false;
- }
- }
-}
-
-void LLFloaterUIPreview::onClickToggleOverlapping()
-{
- if(LLView::sHighlightingDiffs)
- {
- onClickToggleDiffHighlighting();
- mToggleHighlightButton->toggleState();
- }
- LLView::sPreviewHighlightedElements.clear(); // clear lists first
-
- S32 width, height;
- getResizeLimits(&width, &height); // illegal call of non-static member function
- if(mHighlightingOverlaps)
- {
- mHighlightingOverlaps = !mHighlightingOverlaps;
- // reset list of preview highlighted elements
- setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight - mOverlapPanel->getRect().getWidth(),getRect().mBottom));
- setResizeLimits(width - mOverlapPanel->getRect().getWidth(), height);
- }
- else
- {
- mHighlightingOverlaps = !mHighlightingOverlaps;
- displayFloater(false,1);
- setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight + mOverlapPanel->getRect().getWidth(),getRect().mBottom));
- setResizeLimits(width + mOverlapPanel->getRect().getWidth(), height);
- }
- getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps);
-}
-
-void LLFloaterUIPreview::findOverlapsInChildren(LLView* parent)
-{
- if(parent->getChildCount() == 0 || !containerType(parent)) // if it has no children or isn't a container type, skip it
- {
- return;
- }
-
- // for every child of the parent
- for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it)
- {
- LLView* child = *child_it;
- if(overlapIgnorable(child))
- {
- continue;
- }
-
- // for every sibling
- for(child_list_const_iter_t sibling_it = parent->getChildList()->begin(); sibling_it != parent->getChildList()->end(); ++sibling_it) // for each sibling
- {
- LLView* sibling = *sibling_it;
- if(overlapIgnorable(sibling))
- {
- continue;
- }
-
- // if they overlap... (we don't care if they're visible or enabled -- we want to check those anyway, i.e. hidden tabs that can be later shown)
- if(sibling != child && elementOverlap(child, sibling))
- {
- mOverlapPanel->mOverlapMap[child].push_back(sibling); // add to the map
- }
- }
- findOverlapsInChildren(child); // recur
- }
-}
-
-// *HACK: don't overlap with the drag handle and various other elements
-// This is using dynamic casts because there is no object-oriented way to tell which elements contain localizable text. These are a few that are ignorable.
-// *NOTE: If a list of elements which have localizable content were created, this function should return false if viewp's class is in that list.
-bool LLFloaterUIPreview::overlapIgnorable(LLView* viewp)
-{
- return NULL != dynamic_cast<LLDragHandle*>(viewp) ||
- NULL != dynamic_cast<LLViewBorder*>(viewp) ||
- NULL != dynamic_cast<LLResizeBar*>(viewp);
-}
-
-// *HACK: these are the only two container types as of 8/08, per Richard
-// This is using dynamic casts because there is no object-oriented way to tell which elements are containers.
-bool LLFloaterUIPreview::containerType(LLView* viewp)
-{
- return NULL != dynamic_cast<LLPanel*>(viewp) || NULL != dynamic_cast<LLLayoutStack*>(viewp);
-}
-
-// Check if two llview's rectangles overlap, with some tolerance
-bool LLFloaterUIPreview::elementOverlap(LLView* view1, LLView* view2)
-{
- LLSD rec1 = view1->getRect().getValue();
- LLSD rec2 = view2->getRect().getValue();
- int tolerance = 2;
- return (int)rec1[0] <= (int)rec2[2] - tolerance &&
- (int)rec2[0] <= (int)rec1[2] - tolerance &&
- (int)rec1[3] <= (int)rec2[1] - tolerance &&
- (int)rec2[3] <= (int)rec1[1] - tolerance;
-}
-
-void LLOverlapPanel::draw()
-{
- static const std::string current_selection_text("Current selection: ");
- static const std::string overlapper_text("Overlapper: ");
- LLColor4 text_color = LLColor4::grey;
- gGL.color4fv(text_color.mV);
-
- if(!LLView::sPreviewClickedElement)
- {
- LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5
- LLView::sDrawPreviewHighlights = false;
- LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text, 0, 0, 0, text_color,
- LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW);
- }
- else
- {
- OverlapMap::iterator iterExists = mOverlapMap.find(LLView::sPreviewClickedElement);
- if(iterExists == mOverlapMap.end())
- {
- return;
- }
-
- std::list<LLView*> overlappers = mOverlapMap[LLView::sPreviewClickedElement];
- if(overlappers.size() == 0)
- {
- LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5
- LLView::sDrawPreviewHighlights = false;
- std::string current_selection = std::string(current_selection_text + LLView::sPreviewClickedElement->getName() + " (no elements overlap)");
- S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(current_selection) + 10;
- LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection, 0, 0, 0, text_color,
- LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW);
- // widen panel enough to fit this text
- LLRect rect = getRect();
- setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop));
- return;
- }
-
- // recalculate required with and height; otherwise use cached
- bool need_to_recalculate_bounds = false;
- if(mLastClickedElement == NULL)
- {
- need_to_recalculate_bounds = true;
- }
-
- if(NULL == mLastClickedElement)
- {
- mLastClickedElement = LLView::sPreviewClickedElement;
- }
-
- // recalculate bounds for scroll panel
- if(need_to_recalculate_bounds || LLView::sPreviewClickedElement->getName() != mLastClickedElement->getName())
- {
- // reset panel's rectangle to its default width and height (300x600)
- LLRect panel_rect = getRect();
- setRect(LLRect(panel_rect.mLeft,panel_rect.mTop,panel_rect.mLeft+getRect().getWidth(),panel_rect.mTop-getRect().getHeight()));
-
- LLRect rect;
-
- // change bounds for selected element
- int height_sum = mLastClickedElement->getRect().getHeight() + mSpacing + 80;
- rect = getRect();
- setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > mLastClickedElement->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + mLastClickedElement->getRect().getWidth() + 5, rect.mBottom));
-
- // and widen to accomodate text if that's wider
- std::string display_text = current_selection_text + LLView::sPreviewClickedElement->getName();
- S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10;
- rect = getRect();
- setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop));
-
- std::list<LLView*> overlappers = mOverlapMap[LLView::sPreviewClickedElement];
- for(std::list<LLView*>::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it)
- {
- LLView* viewp = *overlap_it;
- height_sum += viewp->getRect().getHeight() + mSpacing*3;
-
- // widen panel's rectangle to accommodate widest overlapping element of this floater
- rect = getRect();
- setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > viewp->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + viewp->getRect().getWidth() + 5, rect.mBottom));
-
- // and widen to accomodate text if that's wider
- std::string display_text = overlapper_text + viewp->getName();
- S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10;
- rect = getRect();
- setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop));
- }
- // change panel's height to accommodate all element heights plus spacing between them
- rect = getRect();
- setRect(LLRect(rect.mLeft,rect.mTop,rect.mRight,rect.mTop-height_sum));
- }
-
- LLUI::translate(5,getRect().getHeight()-10); // translate to top left
- LLView::sDrawPreviewHighlights = false;
-
- // draw currently-selected element at top of overlappers
- LLUI::translate(0,-mSpacing);
- LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text + LLView::sPreviewClickedElement->getName(), 0, 0, 0, text_color,
- LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW);
- LLUI::translate(0,-mSpacing-LLView::sPreviewClickedElement->getRect().getHeight()); // skip spacing distance + height
- LLView::sPreviewClickedElement->draw();
-
- for(std::list<LLView*>::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it)
- {
- LLView* viewp = *overlap_it;
-
- // draw separating line
- LLUI::translate(0,-mSpacing);
- gl_line_2d(0,0,getRect().getWidth()-10,0,LLColor4(192.0f/255.0f,192.0f/255.0f,192.0f/255.0f));
-
- // draw name
- LLUI::translate(0,-mSpacing);
- LLFontGL::getFontSansSerifSmall()->renderUTF8(overlapper_text + viewp->getName(), 0, 0, 0, text_color,
- LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW);
-
- // draw element
- LLUI::translate(0,-mSpacing-viewp->getRect().getHeight()); // skip spacing distance + height
- viewp->draw();
- }
- mLastClickedElement = LLView::sPreviewClickedElement;
- }
-}
-
-void LLFloaterUIPreviewUtil::registerFloater()
-{
- LLFloaterReg::add("ui_preview", "floater_ui_preview.xml",
- &LLFloaterReg::build<LLFloaterUIPreview>);
-}
+/** + * @file llfloateruipreview.cpp + * @brief Tool for previewing and editing floaters, plus localization tool integration + * + * $LicenseInfo:firstyear=2008&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$ + */ + +// Tool for previewing floaters and panels for localization and UI design purposes. +// See: https://wiki.lindenlab.com/wiki/GUI_Preview_And_Localization_Tools +// See: https://jira.lindenlab.com/browse/DEV-16869 + +// *TODO: Translate error messgaes using notifications/alerts.xml + +#include "llviewerprecompiledheaders.h" // Precompiled headers + +#include "llfloateruipreview.h" // Own header + +// Internal utility +#include "lldiriterator.h" +#include "lleventtimer.h" +#include "llexternaleditor.h" +#include "llrender.h" +#include "llsdutil.h" +#include "llxmltree.h" +#include "llviewerwindow.h" + +// XUI +#include "lluictrlfactory.h" +#include "llcombobox.h" +#include "llnotificationsutil.h" +#include "llresizebar.h" +#include "llscrolllistitem.h" +#include "llscrolllistctrl.h" +#include "llfilepicker.h" +#include "lldraghandle.h" +#include "lllayoutstack.h" +#include "lltooltip.h" +#include "llviewermenu.h" +#include "llrngwriter.h" +#include "llfloater.h" // superclass +#include "llfloaterreg.h" +#include "llscrollcontainer.h" // scroll container for overlapping elements +#include "lllivefile.h" // live file poll/stat/reload +#include "llviewermenufile.h" // LLFilePickerReplyThread + +// Boost (for linux/unix command-line execv) +#include <boost/tokenizer.hpp> +#include <boost/shared_ptr.hpp> + +// External utility +#include <string> +#include <list> +#include <map> + +#if LL_DARWIN +#include <CoreFoundation/CFURL.h> +#endif + +// Static initialization +static const S32 PRIMARY_FLOATER = 1; +static const S32 SECONDARY_FLOATER = 2; + +class LLOverlapPanel; +static LLDefaultChildRegistry::Register<LLOverlapPanel> register_overlap_panel("overlap_panel"); + +static std::string get_xui_dir() +{ + std::string delim = gDirUtilp->getDirDelimiter(); + return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim; +} + +// Forward declarations to avoid header dependencies +class LLColor; +class LLScrollListCtrl; +class LLComboBox; +class LLButton; +class LLLineEditor; +class LLXmlTreeNode; +class LLFloaterUIPreview; +class LLFadeEventTimer; + +class LLLocalizationResetForcer; +class LLGUIPreviewLiveFile; +class LLFadeEventTimer; +class LLPreviewedFloater; + +// Implementation of custom overlapping element display panel +class LLOverlapPanel : public LLPanel +{ +public: + struct Params : public LLInitParam::Block<Params, LLPanel::Params> + { + Params() {} + }; + LLOverlapPanel(Params p = Params()) : LLPanel(p), + mSpacing(10), + // mClickedElement(NULL), + mLastClickedElement(NULL) + { + mOriginalWidth = getRect().getWidth(); + mOriginalHeight = getRect().getHeight(); + } + virtual void draw(); + + typedef std::map<LLView*, std::list<LLView*> > OverlapMap; + OverlapMap mOverlapMap; // map, of XUI element to a list of XUI elements it overlaps + + // LLView *mClickedElement; + LLView *mLastClickedElement; + int mOriginalWidth, mOriginalHeight, mSpacing; +}; + + +class LLFloaterUIPreview : public LLFloater +{ +public: + // Setup + LLFloaterUIPreview(const LLSD& key); + virtual ~LLFloaterUIPreview(); + + std::string getLocStr(S32 ID); // fetches the localization string based on what is selected in the drop-down menu + void displayFloater(bool click, S32 ID); // needs to be public so live file can call it when it finds an update + + /*virtual*/ bool postBuild(); + /*virtual*/ void onClose(bool app_quitting); + + void refreshList(); // refresh list (empty it out and fill it up from scratch) + void addFloaterEntry(const std::string& path); // add a single file's entry to the list of floaters + + static bool containerType(LLView* viewp); // check if the element is a container type and tree traverses need to look at its children + +public: + LLPreviewedFloater* mDisplayedFloater; // the floater which is currently being displayed + LLPreviewedFloater* mDisplayedFloater_2; // the floater which is currently being displayed + LLGUIPreviewLiveFile* mLiveFile; // live file for checking for updates to the currently-displayed XML file + LLOverlapPanel* mOverlapPanel; // custom overlapping elements panel + // bool mHighlightingDiffs; // bool for whether localization diffs are being highlighted or not + bool mHighlightingOverlaps; // bool for whether overlapping elements are being highlighted + + // typedef std::map<std::string,std::pair<std::list<std::string>,std::list<std::string> > > DiffMap; // this version copies the lists etc., and thus is bad memory-wise + typedef std::list<std::string> StringList; + typedef std::shared_ptr<StringList> StringListPtr; + typedef std::map<std::string, std::pair<StringListPtr,StringListPtr> > DiffMap; + DiffMap mDiffsMap; // map, of filename to pair of list of changed element paths and list of errors + +private: + LLExternalEditor mExternalEditor; + + // XUI elements for this floater + LLScrollListCtrl* mFileList; // scroll list control for file list + LLLineEditor* mEditorPathTextBox; // text field for path to editor executable + LLLineEditor* mEditorArgsTextBox; // text field for arguments to editor executable + LLLineEditor* mDiffPathTextBox; // text field for path to diff file + LLButton* mDisplayFloaterBtn; // button to display primary floater + LLButton* mDisplayFloaterBtn_2; // button to display secondary floater + LLButton* mEditFloaterBtn; // button to edit floater + LLButton* mExecutableBrowseButton; // button to browse for executable + LLButton* mCloseOtherButton; // button to close primary displayed floater + LLButton* mCloseOtherButton_2; // button to close secondary displayed floater + LLButton* mDiffBrowseButton; // button to browse for diff file + LLButton* mToggleHighlightButton; // button to toggle highlight of files/elements with diffs + LLButton* mToggleOverlapButton; // button to togle overlap panel/highlighting + LLComboBox* mLanguageSelection; // combo box for primary language selection + LLComboBox* mLanguageSelection_2; // combo box for secondary language selection + S32 mLastDisplayedX, mLastDisplayedY; // stored position of last floater so the new one opens up in the same place + std::string mDelim; // the OS-specific delimiter character (/ or \) (*TODO: this shouldn't be needed, right?) + + std::string mSavedEditorPath; // stored editor path so closing this floater doesn't reset it + std::string mSavedEditorArgs; // stored editor args so closing this floater doesn't reset it + std::string mSavedDiffPath; // stored diff file path so closing this floater doesn't reset it + + // Internal functionality + static void popupAndPrintWarning(const std::string& warning); // pop up a warning + std::string getLocalizedDirectory(); // build and return the path to the XUI directory for the currently-selected localization + void scanDiffFile(LLXmlTreeNode* file_node); // scan a given XML node for diff entries and highlight them in its associated file + void highlightChangedElements(); // look up the list of elements to highlight and highlight them in the current floater + void highlightChangedFiles(); // look up the list of changed files to highlight and highlight them in the scroll list + void findOverlapsInChildren(LLView* parent); // fill the map below with element overlap information + static bool overlapIgnorable(LLView* viewp); // check it the element can be ignored for overlap/localization purposes + + // check if two elements overlap using their rectangles + // used instead of llrect functions because by adding a few pixels of leeway I can cut down drastically on the number of overlaps + bool elementOverlap(LLView* view1, LLView* view2); + + // Button/drop-down action listeners (self explanatory) + void onClickDisplayFloater(S32 id); + void onClickSaveFloater(S32 id); + void onClickSaveAll(S32 id); + void onClickEditFloater(); + void onClickBrowseForEditor(); + void getExecutablePath(const std::vector<std::string>& filenames); + void onClickBrowseForDiffs(); + void getDiffsFilePath(const std::vector<std::string>& filenames); + void onClickToggleDiffHighlighting(); + void onClickToggleOverlapping(); + void onClickCloseDisplayedFloater(S32 id); + void onLanguageComboSelect(LLUICtrl* ctrl); + void onClickExportSchema(); + void onClickShowRectangles(const LLSD& data); +}; + +//---------------------------------------------------------------------------- +// Local class declarations +// Reset object to ensure that when we change the current language setting for preview purposes, +// it automatically is reset. Constructed on the stack at the start of the method; the reset +// occurs as it falls out of scope at the end of the method. See llfloateruipreview.cpp for usage. +class LLLocalizationResetForcer +{ +public: + LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID); + virtual ~LLLocalizationResetForcer(); + +private: + std::string mSavedLocalization; // the localization before we change it +}; + +// Implementation of live file +// When a floater is being previewed, any saved changes to its corresponding +// file cause the previewed floater to be reloaded +class LLGUIPreviewLiveFile : public LLLiveFile +{ +public: + LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent); + virtual ~LLGUIPreviewLiveFile(); + LLFloaterUIPreview* mParent; + LLFadeEventTimer* mFadeTimer; // timer for fade-to-yellow-and-back effect to warn that file has been reloaded + bool mFirstFade; // setting this avoids showing the fade reload warning on first load + std::string mFileName; +protected: + bool loadFile(); +}; + +// Implementation of graphical fade in/out (on timer) for when XUI files are updated +class LLFadeEventTimer : public LLEventTimer +{ +public: + LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent); + bool tick(); + LLGUIPreviewLiveFile* mParent; +private: + bool mFadingOut; // fades in then out; this is toggled in between + LLColor4 mOriginalColor; // original color; color is reset to this after fade is coimplete +}; + +// Implementation of previewed floater +// Used to override draw and mouse handler +class LLPreviewedFloater : public LLFloater +{ +public: + LLPreviewedFloater(LLFloaterUIPreview* floater, const Params& params) + : LLFloater(LLSD(), params), + mFloaterUIPreview(floater) + { + } + + virtual void draw(); + bool handleRightMouseDown(S32 x, S32 y, MASK mask); + bool handleToolTip(S32 x, S32 y, MASK mask); + bool selectElement(LLView* parent, int x, int y, int depth); // select element to display its overlappers + + LLFloaterUIPreview* mFloaterUIPreview; + + // draw widget outlines + static bool sShowRectangles; +}; + +bool LLPreviewedFloater::sShowRectangles = false; + +//---------------------------------------------------------------------------- + +// Localization reset forcer -- ensures that when localization is temporarily changed for previewed floater, it is reset +// Changes are made here +LLLocalizationResetForcer::LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID) +{ + mSavedLocalization = LLUI::getInstance()->mSettingGroups["config"]->getString("Language"); // save current localization setting + LLUI::getInstance()->mSettingGroups["config"]->setString("Language", floater->getLocStr(ID));// hack language to be the one we want to preview floaters in + // forcibly reset XUI paths with this new language + gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), floater->getLocStr(ID)); +} + +// Actually reset in destructor +// Changes are reversed here +LLLocalizationResetForcer::~LLLocalizationResetForcer() +{ + LLUI::getInstance()->mSettingGroups["config"]->setString("Language", mSavedLocalization); // reset language to what it was before we changed it + // forcibly reset XUI paths with this new language + gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), mSavedLocalization); +} + +// Live file constructor +// Needs full path for LLLiveFile but needs just file name for this code, hence the reduntant arguments; easier than separating later +LLGUIPreviewLiveFile::LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent) + : mFileName(name), + mParent(parent), + mFirstFade(true), + mFadeTimer(NULL), + LLLiveFile(path, 1.0) +{} + +LLGUIPreviewLiveFile::~LLGUIPreviewLiveFile() +{ + mParent->mLiveFile = NULL; + if(mFadeTimer) + { + mFadeTimer->mParent = NULL; + // deletes itself; see lltimer.cpp + } +} + +// Live file load +bool LLGUIPreviewLiveFile::loadFile() +{ + mParent->displayFloater(false,1); // redisplay the floater + if(mFirstFade) // only fade if it wasn't just clicked on; can't use "clicked" bool below because of an oddity with setting LLLiveFile initial state + { + mFirstFade = false; + } + else + { + if(mFadeTimer) + { + mFadeTimer->mParent = NULL; + } + mFadeTimer = new LLFadeEventTimer(0.05f,this); + } + return true; +} + +// Initialize fade event timer +LLFadeEventTimer::LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent) + : mParent(parent), + mFadingOut(true), + LLEventTimer(refresh) +{ + mOriginalColor = mParent->mParent->mDisplayedFloater->getBackgroundColor(); +} + +// Single tick of fade event timer: increment the color +bool LLFadeEventTimer::tick() +{ + float diff = 0.04f; + if(true == mFadingOut) // set fade for in/out color direction + { + diff = -diff; + } + + if(NULL == mParent) // no more need to tick, so suicide + { + return true; + } + + // Set up colors + LLColor4 bg_color = mParent->mParent->mDisplayedFloater->getBackgroundColor(); + LLSD colors = bg_color.getValue(); + LLSD colors_old = colors; + + // Tick colors + colors[0] = colors[0].asReal() - diff; if(colors[0].asReal() < mOriginalColor.getValue()[0].asReal()) { colors[0] = colors_old[0]; } + colors[1] = colors[1].asReal() - diff; if(colors[1].asReal() < mOriginalColor.getValue()[1].asReal()) { colors[1] = colors_old[1]; } + colors[2] = colors[2].asReal() + diff; if(colors[2].asReal() > mOriginalColor.getValue()[2].asReal()) { colors[2] = colors_old[2]; } + + // Clamp and set colors + bg_color.setValue(colors); + bg_color.clamp(); // make sure we didn't exceed [0,1] + mParent->mParent->mDisplayedFloater->setBackgroundColor(bg_color); + + if(bg_color[2] <= 0.0f) // end of fade out, start fading in + { + mFadingOut = false; + } + + return false; +} + +// Constructor +LLFloaterUIPreview::LLFloaterUIPreview(const LLSD& key) + : LLFloater(key), + mDisplayedFloater(NULL), + mDisplayedFloater_2(NULL), + mLiveFile(NULL), + // sHighlightingDiffs(false), + mHighlightingOverlaps(false), + mLastDisplayedX(0), + mLastDisplayedY(0) +{ +} + +// Destructor +LLFloaterUIPreview::~LLFloaterUIPreview() +{ + // spawned floaters are deleted automatically, so we don't need to delete them here + + // save contents of textfields so it can be restored later if the floater is created again this session + mSavedEditorPath = mEditorPathTextBox->getText(); + mSavedEditorArgs = mEditorArgsTextBox->getText(); + mSavedDiffPath = mDiffPathTextBox->getText(); + + // delete live file if it exists + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } +} + +// Perform post-build setup (defined in superclass) +bool LLFloaterUIPreview::postBuild() +{ + LLPanel* main_panel_tmp = getChild<LLPanel>("main_panel"); // get a pointer to the main panel in order to... + mFileList = main_panel_tmp->getChild<LLScrollListCtrl>("name_list"); // save pointer to file list + // Double-click opens the floater, for convenience + mFileList->setDoubleClickCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER)); + + setDefaultBtn("display_floater"); + // get pointers to buttons and link to callbacks + mLanguageSelection = main_panel_tmp->getChild<LLComboBox>("language_select_combo"); + mLanguageSelection->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection)); + mLanguageSelection_2 = main_panel_tmp->getChild<LLComboBox>("language_select_combo_2"); + mLanguageSelection_2->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection)); + LLPanel* editor_panel_tmp = main_panel_tmp->getChild<LLPanel>("editor_panel"); + mDisplayFloaterBtn = main_panel_tmp->getChild<LLButton>("display_floater"); + mDisplayFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER)); + mDisplayFloaterBtn_2 = main_panel_tmp->getChild<LLButton>("display_floater_2"); + mDisplayFloaterBtn_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, SECONDARY_FLOATER)); + mToggleOverlapButton = main_panel_tmp->getChild<LLButton>("toggle_overlap_panel"); + mToggleOverlapButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleOverlapping, this)); + mCloseOtherButton = main_panel_tmp->getChild<LLButton>("close_displayed_floater"); + mCloseOtherButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, PRIMARY_FLOATER)); + mCloseOtherButton_2 = main_panel_tmp->getChild<LLButton>("close_displayed_floater_2"); + mCloseOtherButton_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, SECONDARY_FLOATER)); + mEditFloaterBtn = main_panel_tmp->getChild<LLButton>("edit_floater"); + mEditFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickEditFloater, this)); + mExecutableBrowseButton = editor_panel_tmp->getChild<LLButton>("browse_for_executable"); + LLPanel* vlt_panel_tmp = main_panel_tmp->getChild<LLPanel>("vlt_panel"); + mExecutableBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForEditor, this)); + mDiffBrowseButton = vlt_panel_tmp->getChild<LLButton>("browse_for_vlt_diffs"); + mDiffBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForDiffs, this)); + mToggleHighlightButton = vlt_panel_tmp->getChild<LLButton>("toggle_vlt_diff_highlight"); + mToggleHighlightButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleDiffHighlighting, this)); + main_panel_tmp->getChild<LLButton>("save_floater")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveFloater, this, PRIMARY_FLOATER)); + main_panel_tmp->getChild<LLButton>("save_all_floaters")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveAll, this, PRIMARY_FLOATER)); + + getChild<LLButton>("export_schema")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickExportSchema, this)); + getChild<LLUICtrl>("show_rectangles")->setCommitCallback( + boost::bind(&LLFloaterUIPreview::onClickShowRectangles, this, _2)); + + // get pointers to text fields + mEditorPathTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_path_field"); + mEditorArgsTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_args_field"); + mDiffPathTextBox = vlt_panel_tmp->getChild<LLLineEditor>("vlt_diff_path_field"); + + // *HACK: restored saved editor path and args to textfields + mEditorPathTextBox->setText(mSavedEditorPath); + mEditorArgsTextBox->setText(mSavedEditorArgs); + mDiffPathTextBox->setText(mSavedDiffPath); + + // Set up overlap panel + mOverlapPanel = getChild<LLOverlapPanel>("overlap_panel"); + + getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps); + + mDelim = gDirUtilp->getDirDelimiter(); // initialize delimiter to dir sep slash + + // refresh list of available languages (EN will still be default) + bool found = true; + bool found_en_us = false; + std::string language_directory; + std::string xui_dir = get_xui_dir(); // directory containing localizations -- don't forget trailing delim + mLanguageSelection->removeall(); // clear out anything temporarily in list from XML + + LLDirIterator iter(xui_dir, "*"); + while(found) // for every directory + { + if((found = iter.next(language_directory))) // get next directory + { + std::string full_path = gDirUtilp->add(xui_dir, language_directory); + if(LLFile::isfile(full_path.c_str())) // if it's not a directory, skip it + { + continue; + } + + if(strncmp("template",language_directory.c_str(),8) && -1 == language_directory.find(".")) // if it's not the template directory or a hidden directory + { + if(!strncmp("en",language_directory.c_str(),5)) // remember if we've seen en, so we can make it default + { + found_en_us = true; + } + else + { + mLanguageSelection->add(std::string(language_directory)); // add it to the language selection dropdown menu + mLanguageSelection_2->add(std::string(language_directory)); + } + } + } + } + if(found_en_us) + { + mLanguageSelection->add(std::string("en"),ADD_TOP); // make en first item if we found it + mLanguageSelection_2->add(std::string("en"),ADD_TOP); + } + else + { + std::string warning = std::string("No EN localization found; check your XUI directories!"); + popupAndPrintWarning(warning); + } + mLanguageSelection->selectFirstItem(); // select the first item + mLanguageSelection_2->selectFirstItem(); + + refreshList(); // refresh the list of available floaters + + return true; +} + +// Callback for language combo box selection: refresh current floater when you change languages +void LLFloaterUIPreview::onLanguageComboSelect(LLUICtrl* ctrl) +{ + LLComboBox* caller = dynamic_cast<LLComboBox*>(ctrl); + if (!caller) + return; + if(caller->getName() == std::string("language_select_combo")) + { + if(mDisplayedFloater) + { + onClickCloseDisplayedFloater(PRIMARY_FLOATER); + displayFloater(true,1); + } + } + else + { + if(mDisplayedFloater_2) + { + onClickCloseDisplayedFloater(PRIMARY_FLOATER); + displayFloater(true,2); // *TODO: make take an arg + } + } + +} + +void LLFloaterUIPreview::onClickExportSchema() +{ + //NOTE: schema generation not complete + //gViewerWindow->setCursor(UI_CURSOR_WAIT); + //std::string template_path = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "xui", "schema"); + + //typedef LLWidgetTypeRegistry::Registrar::registry_map_t::const_iterator registry_it; + //registry_it end_it = LLWidgetTypeRegistry::defaultRegistrar().endItems(); + //for(registry_it it = LLWidgetTypeRegistry::defaultRegistrar().beginItems(); + // it != end_it; + // ++it) + //{ + // std::string widget_name = it->first; + // const LLInitParam::BaseBlock& block = + // (*LLDefaultParamBlockRegistry::instance().getValue(*LLWidgetTypeRegistry::instance().getValue(widget_name)))(); + // LLXMLNodePtr root_nodep = new LLXMLNode(); + // LLRNGWriter().writeRNG(widget_name, root_nodep, block, "http://www.lindenlab.com/xui"); + + // std::string file_name(template_path + gDirUtilp->getDirDelimiter() + widget_name + ".rng"); + + // LLFILE* rng_file = LLFile::fopen(file_name.c_str(), "w"); + // { + // LLXMLNode::writeHeaderToFile(rng_file); + // const bool use_type_decorations = false; + // root_nodep->writeToFile(rng_file, std::string(), use_type_decorations); + // } + // fclose(rng_file); + //} + //gViewerWindow->setCursor(UI_CURSOR_ARROW); +} + +void LLFloaterUIPreview::onClickShowRectangles(const LLSD& data) +{ + LLPreviewedFloater::sShowRectangles = data.asBoolean(); +} + +// Close click handler -- delete my displayed floater if it exists +void LLFloaterUIPreview::onClose(bool app_quitting) +{ + if(!app_quitting && mDisplayedFloater) + { + onClickCloseDisplayedFloater(PRIMARY_FLOATER); + onClickCloseDisplayedFloater(SECONDARY_FLOATER); + delete mDisplayedFloater; + mDisplayedFloater = NULL; + delete mDisplayedFloater_2; + mDisplayedFloater_2 = NULL; + } +} + +// Error handling (to avoid code repetition) +// *TODO: this is currently unlocalized. Add to alerts/notifications.xml, someday, maybe. +void LLFloaterUIPreview::popupAndPrintWarning(const std::string& warning) +{ + LL_WARNS() << warning << LL_ENDL; + LLSD args; + args["MESSAGE"] = warning; + LLNotificationsUtil::add("GenericAlert", args); +} + +// Get localization string from drop-down menu +std::string LLFloaterUIPreview::getLocStr(S32 ID) +{ + if(ID == 1) + { + return mLanguageSelection->getSelectedItemLabel(0); + } + else + { + return mLanguageSelection_2->getSelectedItemLabel(0); + } +} + +// Get localized directory (build path from data directory to XUI files, substituting localization string in for language) +std::string LLFloaterUIPreview::getLocalizedDirectory() +{ + return get_xui_dir() + (getLocStr(1)) + mDelim; // e.g. "C:/Code/guipreview/indra/newview/skins/xui/en/"; +} + +// Refresh the list of floaters by doing a directory traverse for XML XUI floater files +// Could be used to grab any specific language's list of compatible floaters, but currently it's just used to get all of them +void LLFloaterUIPreview::refreshList() +{ + // Note: the mask doesn't seem to accept regular expressions, so there need to be two directory searches here + mFileList->clearRows(); // empty list + std::string name; + bool found = true; + + LLDirIterator floater_iter(getLocalizedDirectory(), "floater_*.xml"); + while(found) // for every floater file that matches the pattern + { + if((found = floater_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator inspect_iter(getLocalizedDirectory(), "inspect_*.xml"); + while(found) // for every inspector file that matches the pattern + { + if((found = inspect_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator menu_iter(getLocalizedDirectory(), "menu_*.xml"); + while(found) // for every menu file that matches the pattern + { + if((found = menu_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator panel_iter(getLocalizedDirectory(), "panel_*.xml"); + while(found) // for every panel file that matches the pattern + { + if((found = panel_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator sidepanel_iter(getLocalizedDirectory(), "sidepanel_*.xml"); + while(found) // for every sidepanel file that matches the pattern + { + if((found = sidepanel_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + + if(!mFileList->isEmpty()) // if there were any matching files, just select the first one (so we don't have to worry about disabling buttons when no entry is selected) + { + mFileList->selectFirstItem(); + } +} + +// Add a single entry to the list of available floaters +// Note: no deduplification (shouldn't be necessary) +void LLFloaterUIPreview::addFloaterEntry(const std::string& path) +{ + LLUUID* entry_id = new LLUUID(); // create a new UUID + entry_id->generate(path); + const LLUUID& entry_id_ref = *entry_id; // get a reference to the UUID for the LLSD block + + // fill LLSD column entry: initialize row/col structure + LLSD row; + row["id"] = entry_id_ref; + LLSD& columns = row["columns"]; + + // Get name of floater: + LLXmlTree xml_tree; + std::string full_path = getLocalizedDirectory() + path; // get full path + bool success = xml_tree.parseFile(full_path.c_str(), true); // parse xml + std::string entry_name; + std::string entry_title; + if(success) + { + // get root (or error handle) + LLXmlTreeNode* root_floater = xml_tree.getRoot(); + if (!root_floater) + { + std::string warning = std::string("No root node found in XUI file: ") + path; + popupAndPrintWarning(warning); + return; + } + + // get name + root_floater->getAttributeString("name",entry_name); + if(std::string("") == entry_name) + { + entry_name = "Error: unable to load " + std::string(path); // set to error state if load fails + } + + // get title + root_floater->getAttributeString("title",entry_title); // some don't have a title, and some have title = "(unknown)", so just leave it blank if it fails + } + else + { + std::string warning = std::string("Unable to parse XUI file: ") + path; // error handling + popupAndPrintWarning(warning); + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } + return; + } + + // Fill floater title column + columns[0]["column"] = "title_column"; + columns[0]["type"] = "text"; + columns[0]["value"] = entry_title; + + // Fill floater path column + columns[1]["column"] = "file_column"; + columns[1]["type"] = "text"; + columns[1]["value"] = std::string(path); + + // Fill floater name column + columns[2]["column"] = "top_level_node_column"; + columns[2]["type"] = "text"; + columns[2]["value"] = entry_name; + + mFileList->addElement(row); // actually add to list +} + +// Respond to button click to display/refresh currently-selected floater +void LLFloaterUIPreview::onClickDisplayFloater(S32 caller_id) +{ + displayFloater(true, caller_id); +} + +// Saves the current floater/panel +void LLFloaterUIPreview::onClickSaveFloater(S32 caller_id) +{ + displayFloater(true, caller_id); + popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files"); +} + +// Saves all floater/panels +void LLFloaterUIPreview::onClickSaveAll(S32 caller_id) +{ + int listSize = mFileList->getItemCount(); + + for (int index = 0; index < listSize; index++) + { + mFileList->selectNthItem(index); + displayFloater(true, caller_id); + } + popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files"); +} + +// Actually display the floater +// Only set up a new live file if this came from a click (at which point there should be no existing live file), rather than from the live file's update itself; +// otherwise, we get an infinite loop as the live file keeps recreating itself. That means this function is generally called twice. +void LLFloaterUIPreview::displayFloater(bool click, S32 ID) +{ + // Convince UI that we're in a different language (the one selected on the drop-down menu) + LLLocalizationResetForcer reset_forcer(this, ID); // save old language in reset forcer object (to be reset upon destruction when it falls out of scope) + + LLPreviewedFloater** floaterp = (ID == 1 ? &(mDisplayedFloater) : &(mDisplayedFloater_2)); + if(ID == 1) + { + bool floater_already_open = mDisplayedFloater != NULL; + if(floater_already_open) // if we are already displaying a floater + { + mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft; // save floater's last known position to put the new one there + mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom; + delete mDisplayedFloater; // delete it (this closes it too) + mDisplayedFloater = NULL; // and reset the pointer + } + } + else + { + if(mDisplayedFloater_2 != NULL) + { + delete mDisplayedFloater_2; + mDisplayedFloater_2 = NULL; + } + } + + std::string path = mFileList->getSelectedItemLabel(1); // get the path of the currently-selected floater + if(std::string("") == path) // if no item is selected + { + return; // ignore click (this can only happen with empty list; otherwise an item is always selected) + } + + LLFloater::Params p(LLFloater::getDefaultParams()); + p.min_height=p.header_height; + p.min_width=10; + + *floaterp = new LLPreviewedFloater(this, p); + + if(!strncmp(path.c_str(),"floater_",8) + || !strncmp(path.c_str(), "inspect_", 8)) // if it's a floater + { + (*floaterp)->buildFromFile(path); // just build it + (*floaterp)->openFloater((*floaterp)->getKey()); + (*floaterp)->setCanResize((*floaterp)->isResizable()); + } + else if (!strncmp(path.c_str(),"menu_",5)) // if it's a menu + { + // former 'save' processing excised + } + else // if it is a panel... + { + (*floaterp)->setCanResize(true); + + const LLFloater::Params& floater_params = LLFloater::getDefaultParams(); + S32 floater_header_size = floater_params.header_height; + + LLPanel::Params panel_params; + LLPanel* panel = LLUICtrlFactory::create<LLPanel>(panel_params); // create a new panel + + panel->buildFromFile(path); // build it + panel->setOrigin(2,2); // reset its origin point so it's not offset by -left or other XUI attributes + (*floaterp)->setTitle(path); // use the file name as its title, since panels have no guaranteed meaningful name attribute + panel->setUseBoundingRect(true); // enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements) + panel->updateBoundingRect(); // update bounding rect + LLRect bounding_rect = panel->getBoundingRect(); // get the bounding rect + LLRect new_rect = panel->getRect(); // get the panel's rect + new_rect.unionWith(bounding_rect); // union them to make sure we get the biggest one possible + LLRect floater_rect = new_rect; + floater_rect.stretch(4, 4); + (*floaterp)->reshape(floater_rect.getWidth(), floater_rect.getHeight() + floater_header_size); // reshape floater to match the union rect's dimensions + panel->reshape(new_rect.getWidth(), new_rect.getHeight()); // reshape panel to match the union rect's dimensions as well (both are needed) + (*floaterp)->addChild(panel); // add panel as child + (*floaterp)->openFloater(); // open floater (needed?) + } + + if(ID == 1) + { + (*floaterp)->setOrigin(mLastDisplayedX, mLastDisplayedY); + } + + // *HACK: Remove ability to close it; if you close it, its destructor gets called, but we don't know it's null and try to delete it again, + // resulting in a double free + (*floaterp)->setCanClose(false); + + if(ID == 1) + { + mCloseOtherButton->setEnabled(true); // enable my floater's close button + } + else + { + mCloseOtherButton_2->setEnabled(true); + } + + // Add localization to title so user knows whether it's localized or defaulted to en + std::string full_path = getLocalizedDirectory() + path; + std::string floater_lang = "EN"; + llstat dummy; + if(!LLFile::stat(full_path.c_str(), &dummy)) // if the file does not exist + { + floater_lang = getLocStr(ID); + } + std::string new_title = (*floaterp)->getTitle() + std::string(" [") + floater_lang + + (ID == 1 ? " - Primary" : " - Secondary") + std::string("]"); + (*floaterp)->setTitle(new_title); + + (*floaterp)->center(); + addDependentFloater(*floaterp); + + if(click && ID == 1) + { + // set up live file to track it + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } + mLiveFile = new LLGUIPreviewLiveFile(std::string(full_path.c_str()),std::string(path.c_str()),this); + mLiveFile->checkAndReload(); + mLiveFile->addToEventTimer(); + } + + if(ID == 1) + { + mToggleOverlapButton->setEnabled(true); + } + + if(LLView::sHighlightingDiffs && click && ID == 1) + { + highlightChangedElements(); + } + + if(ID == 1) + { + mOverlapPanel->mOverlapMap.clear(); + LLView::sPreviewClickedElement = NULL; // stop overlapping elements from drawing + mOverlapPanel->mLastClickedElement = NULL; + findOverlapsInChildren((LLView*)mDisplayedFloater); + + // highlight and enable them + if(mHighlightingOverlaps) + { + for(LLOverlapPanel::OverlapMap::iterator iter = mOverlapPanel->mOverlapMap.begin(); iter != mOverlapPanel->mOverlapMap.end(); ++iter) + { + LLView* viewp = iter->first; + LLView::sPreviewHighlightedElements.insert(viewp); + } + } + else if(LLView::sHighlightingDiffs) + { + highlightChangedElements(); + } + } + + // NOTE: language is reset here automatically when the reset forcer object falls out of scope (see header for details) +} + +// Respond to button click to edit currently-selected floater +void LLFloaterUIPreview::onClickEditFloater() +{ + // Determine file to edit. + std::string file_path; + { + std::string file_name = mFileList->getSelectedItemLabel(1); // get the file name of the currently-selected floater + if (file_name.empty()) // if no item is selected + { + LL_WARNS() << "No file selected" << LL_ENDL; + return; // ignore click + } + file_path = getLocalizedDirectory() + file_name; + + // stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file) + llstat dummy; + if(LLFile::stat(file_path.c_str(), &dummy)) // if the file does not exist + { + popupAndPrintWarning("No file for this floater exists in the selected localization. Opening the EN version instead."); + file_path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default + } + } + + // Set the editor command. + std::string cmd_override; + { + std::string bin = mEditorPathTextBox->getText(); + if (!bin.empty()) + { + // surround command with double quotes for the case if the path contains spaces + if (bin.find("\"") == std::string::npos) + { + bin = "\"" + bin + "\""; + } + + std::string args = mEditorArgsTextBox->getText(); + cmd_override = bin + " " + args; + } + } + + LLExternalEditor::EErrorCode status = mExternalEditor.setCommand("LL_XUI_EDITOR", cmd_override); + if (status != LLExternalEditor::EC_SUCCESS) + { + std::string warning; + + if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. + { + warning = getString("ExternalEditorNotSet"); + } + else + { + warning = LLExternalEditor::getErrorMessage(status); + } + + popupAndPrintWarning(warning); + return; + } + + // Run the editor. + if (mExternalEditor.run(file_path) != LLExternalEditor::EC_SUCCESS) + { + popupAndPrintWarning(LLExternalEditor::getErrorMessage(status)); + return; + } +} + +// Respond to button click to browse for an executable with which to edit XML files +void LLFloaterUIPreview::onClickBrowseForEditor() +{ + // Let the user choose an executable through the file picker dialog box + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getExecutablePath, this, _1), LLFilePicker::FFLOAD_EXE, false); +} + +void LLFloaterUIPreview::getExecutablePath(const std::vector<std::string>& filenames) +{ + // put the selected path into text field + const std::string chosen_path = filenames[0]; + std::string executable_path = chosen_path; +#if LL_DARWIN + // on Mac, if it's an application bundle, figure out the actual path from the Info.plist file + CFStringRef path_cfstr = CFStringCreateWithCString(kCFAllocatorDefault, chosen_path.c_str(), kCFStringEncodingMacRoman); // get path as a CFStringRef + CFURLRef path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstr, kCFURLPOSIXPathStyle, true); // turn it into a CFURLRef + CFBundleRef chosen_bundle = CFBundleCreate(kCFAllocatorDefault, path_url); // get a handle for the bundle + if(NULL != chosen_bundle) + { + CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(chosen_bundle); // get the bundle's dictionary + if(NULL != bundleInfoDict) + { + CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable")); // get the name of the actual executable (e.g. TextEdit or firefox-bin) + int max_file_length = 256; // (max file name length is 255 in OSX) + char executable_buf[max_file_length]; + if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman)) // convert CFStringRef to char* + { + executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf); // append path to executable directory and then executable name to exec path + } + else + { + std::string warning = "Unable to get CString from CFString for executable path"; + popupAndPrintWarning(warning); + } + } + else + { + std::string warning = "Unable to get bundle info dictionary from application bundle"; + popupAndPrintWarning(warning); + } + } + else + { + if(-1 != executable_path.find(".app")) // only warn if this path actually had ".app" in it, i.e. it probably just wasn'nt an app bundle and that's okay + { + std::string warning = std::string("Unable to get bundle from path \"") + chosen_path + std::string("\""); + popupAndPrintWarning(warning); + } + } + +#endif + mEditorPathTextBox->setText(std::string(executable_path)); // copy the path to the executable to the textfield for display and later fetching +} + +// Respond to button click to browse for a VLT-generated diffs file +void LLFloaterUIPreview::onClickBrowseForDiffs() +{ + // create load dialog box + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getDiffsFilePath, this, _1), LLFilePicker::FFLOAD_XML, false); +} + +void LLFloaterUIPreview::getDiffsFilePath(const std::vector<std::string>& filenames) +{ + // put the selected path into text field + const std::string chosen_path = filenames[0]; + mDiffPathTextBox->setText(std::string(chosen_path)); // copy the path to the executable to the textfield for display and later fetching + if(LLView::sHighlightingDiffs) // if we're already highlighting, toggle off and then on so we get the data from the new file + { + onClickToggleDiffHighlighting(); + onClickToggleDiffHighlighting(); + } +} + +void LLFloaterUIPreview::onClickToggleDiffHighlighting() +{ + if(mHighlightingOverlaps) + { + onClickToggleOverlapping(); + mToggleOverlapButton->toggleState(); + } + + LLView::sPreviewHighlightedElements.clear(); // clear lists first + mDiffsMap.clear(); + mFileList->clearHighlightedItems(); + + if(LLView::sHighlightingDiffs) // Turning highlighting off + { + LLView::sHighlightingDiffs = !sHighlightingDiffs; + return; + } + else // Turning highlighting on + { + // Get the file and make sure it exists + std::string path_in_textfield = mDiffPathTextBox->getText(); // get file path + bool error = false; + + if(std::string("") == path_in_textfield) // check for blank file + { + std::string warning = "Unable to highlight differences because no file was provided; fill in the relevant text field"; + popupAndPrintWarning(warning); + error = true; + } + + llstat dummy; + if(LLFile::stat(path_in_textfield.c_str(), &dummy) && !error) // check if the file exists (empty check is reduntant but useful for the informative error message) + { + std::string warning = std::string("Unable to highlight differences because an invalid path to a difference file was provided:\"") + path_in_textfield + "\""; + popupAndPrintWarning(warning); + error = true; + } + + // Build a list of changed elements as given by the XML + std::list<std::string> changed_element_names; + LLXmlTree xml_tree; + bool success = xml_tree.parseFile(path_in_textfield.c_str(), true); + + if(success && !error) + { + LLXmlTreeNode* root_floater = xml_tree.getRoot(); + if(!strncmp("XuiDelta",root_floater->getName().c_str(),9)) + { + for (LLXmlTreeNode* child = root_floater->getFirstChild(); // get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?) + child != NULL; + child = root_floater->getNextChild()) // get child for next iteration + { + if(!strncmp("file",child->getName().c_str(),5)) + { + scanDiffFile(child); + } + else if(!strncmp("error",child->getName().c_str(),6)) + { + std::string error_file, error_message; + child->getAttributeString("filename",error_file); + child->getAttributeString("message",error_message); + if(mDiffsMap.find(error_file) != mDiffsMap.end()) + { + mDiffsMap.insert(std::make_pair(error_file,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList)))); + } + mDiffsMap[error_file].second->push_back(error_message); + } + else + { + std::string warning = std::string("Child was neither a file or an error, but rather the following:\"") + std::string(child->getName()) + "\""; + popupAndPrintWarning(warning); + error = true; + break; + } + } + } + else + { + std::string warning = std::string("Root node not named XuiDelta:\"") + path_in_textfield + "\""; + popupAndPrintWarning(warning); + error = true; + } + } + else if(!error) + { + std::string warning = std::string("Unable to create tree from XML:\"") + path_in_textfield + "\""; + popupAndPrintWarning(warning); + error = true; + } + + if(error) // if we encountered an error, reset the button to off + { + mToggleHighlightButton->setToggleState(false); + } + else // only toggle if we didn't encounter an error + { + LLView::sHighlightingDiffs = !sHighlightingDiffs; + highlightChangedElements(); // *TODO: this is extraneous, right? + highlightChangedFiles(); // *TODO: this is extraneous, right? + } + } +} + +void LLFloaterUIPreview::scanDiffFile(LLXmlTreeNode* file_node) +{ + // Get file name + std::string file_name; + file_node->getAttributeString("name",file_name); + if(std::string("") == file_name) + { + std::string warning = std::string("Empty file name encountered in differences:\"") + file_name + "\""; + popupAndPrintWarning(warning); + return; + } + + // Get a list of changed elements + // Get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?) + for (LLXmlTreeNode* child = file_node->getFirstChild(); child != NULL; child = file_node->getNextChild()) + { + if(!strncmp("delta",child->getName().c_str(),6)) + { + std::string id; + child->getAttributeString("id",id); + if(mDiffsMap.find(file_name) == mDiffsMap.end()) + { + mDiffsMap.insert(std::make_pair(file_name,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList)))); + } + mDiffsMap[file_name].first->push_back(std::string(id.c_str())); + } + else + { + std::string warning = std::string("Child of file was not a delta, but rather the following:\"") + std::string(child->getName()) + "\""; + popupAndPrintWarning(warning); + return; + } + } +} + +void LLFloaterUIPreview::highlightChangedElements() +{ + if(NULL == mLiveFile) + { + return; + } + + // Process differences first (we want their warnings to be shown underneath other warnings) + StringListPtr changed_element_paths; + DiffMap::iterator iterExists = mDiffsMap.find(mLiveFile->mFileName); + if(iterExists != mDiffsMap.end()) + { + changed_element_paths = mDiffsMap[mLiveFile->mFileName].first; // retrieve list of changed element paths from map + } + + for(std::list<std::string>::iterator iter = changed_element_paths->begin(); iter != changed_element_paths->end(); ++iter) // for every changed element path + { + LLView* element = mDisplayedFloater; + if(!strncmp(iter->c_str(),".",1)) // if it's the root floater itself + { + continue; + } + + // Split element hierarchy path on period (*HACK: it's possible that the element name will have a period in it, in which case this won't work. See https://wiki.lindenlab.com/wiki/Viewer_Localization_Tool_Documentation.) + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("."); + tokenizer tokens(*iter, sep); + tokenizer::iterator token_iter; + bool failed = false; + for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + element = element->findChild<LLView>(*token_iter,false); // try to find element: don't recur, and don't create if missing + + // if we still didn't find it... + if(NULL == element) + { + LL_INFOS() << "Unable to find element in XuiDelta file named \"" << *iter << "\" in file \"" << mLiveFile->mFileName << + "\". The element may no longer exist, the path may be incorrect, or it may not be a non-displayable element (not an LLView) such as a \"string\" type." << LL_ENDL; + failed = true; + break; + } + } + + if(!failed) + { + // Now that we have a pointer to the actual element, add it to the list of elements to be highlighted + std::set<LLView*>::iterator iter2 = std::find(LLView::sPreviewHighlightedElements.begin(), LLView::sPreviewHighlightedElements.end(), element); + if(iter2 == LLView::sPreviewHighlightedElements.end()) + { + LLView::sPreviewHighlightedElements.insert(element); + } + } + } + + // Process errors second, so their warnings show up on top of other warnings + StringListPtr error_list; + if(iterExists != mDiffsMap.end()) + { + error_list = mDiffsMap[mLiveFile->mFileName].second; + } + for(std::list<std::string>::iterator iter = error_list->begin(); iter != error_list->end(); ++iter) // for every changed element path + { + std::string warning = std::string("Error listed among differences. Filename: \"") + mLiveFile->mFileName + "\". Message: \"" + *iter + "\""; + popupAndPrintWarning(warning); + } +} + +void LLFloaterUIPreview::highlightChangedFiles() +{ + for(DiffMap::iterator iter = mDiffsMap.begin(); iter != mDiffsMap.end(); ++iter) // for every file listed in diffs + { + LLScrollListItem* item = mFileList->getItemByLabel(std::string(iter->first), false, 1); + if(item) + { + item->setHighlighted(true); + } + } +} + +// Respond to button click to browse for an executable with which to edit XML files +void LLFloaterUIPreview::onClickCloseDisplayedFloater(S32 caller_id) +{ + if(caller_id == PRIMARY_FLOATER) + { + mCloseOtherButton->setEnabled(false); + mToggleOverlapButton->setEnabled(false); + + if(mDisplayedFloater) + { + mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft; + mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom; + delete mDisplayedFloater; + mDisplayedFloater = NULL; + } + + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } + + if(mToggleOverlapButton->getToggleState()) + { + mToggleOverlapButton->toggleState(); + onClickToggleOverlapping(); + } + + LLView::sPreviewClickedElement = NULL; // stop overlapping elements panel from drawing + mOverlapPanel->mLastClickedElement = NULL; + } + else + { + mCloseOtherButton_2->setEnabled(false); + delete mDisplayedFloater_2; + mDisplayedFloater_2 = NULL; + } + +} + +void append_view_tooltip(LLView* tooltip_view, std::string *tooltip_msg) +{ + LLRect rect = tooltip_view->getRect(); + LLRect parent_rect = tooltip_view->getParent()->getRect(); + S32 left = rect.mLeft; + // invert coordinate system for XUI top-left layout + S32 top = parent_rect.getHeight() - rect.mTop; + if (!tooltip_msg->empty()) + { + tooltip_msg->append("\n"); + } + std::string msg = llformat("%s %d, %d (%d x %d)", + tooltip_view->getName().c_str(), + left, + top, + rect.getWidth(), + rect.getHeight() ); + tooltip_msg->append( msg ); +} + +bool LLPreviewedFloater::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (!sShowRectangles) + { + return LLFloater::handleToolTip(x, y, mask); + } + + S32 screen_x, screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + std::string tooltip_msg; + LLView* tooltip_view = this; + LLView::tree_iterator_t end_it = endTreeDFS(); + for (LLView::tree_iterator_t it = beginTreeDFS(); it != end_it; ++it) + { + LLView* viewp = *it; + LLRect screen_rect; + viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect); + if (!(viewp->getVisible() + && screen_rect.pointInRect(screen_x, screen_y))) + { + it.skipDescendants(); + } + // only report xui names for LLUICtrls, not the various container LLViews + + else if (dynamic_cast<LLUICtrl*>(viewp)) + { + // if we are in a new part of the tree (not a descendent of current tooltip_view) + // then push the results for tooltip_view and start with a new potential view + // NOTE: this emulates visiting only the leaf nodes that meet our criteria + + if (tooltip_view != this + && !viewp->hasAncestor(tooltip_view)) + { + append_view_tooltip(tooltip_view, &tooltip_msg); + } + tooltip_view = viewp; + } + } + + append_view_tooltip(tooltip_view, &tooltip_msg); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(tooltip_msg) + .max_width(400)); + return true; +} + +bool LLPreviewedFloater::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + selectElement(this,x,y,0); + return true; +} + +// *NOTE: In order to hide all of the overlapping elements of the selected element so as to see it in context, here is what you would need to do: +// -This selectElement call fills the overlap panel as normal. The element which is "selected" here is actually just an intermediate selection step; +// what you've really selected is a list of elements: the one you clicked on and everything that overlaps it. +// -The user then selects one of the elements from this list the overlap panel (click handling to the overlap panel would have to be added). +// This becomes the final selection (as opposed to the intermediate selection that was just made). +// -Everything else that is currently displayed on the overlap panel should be hidden from view in the previewed floater itself (setVisible(false)). +// -Subsequent clicks on other elements in the overlap panel (they should still be there) should make other elements the final selection. +// -On close or on the click of a new button, everything should be shown again and all selection state should be cleared. +// ~Jacob, 8/08 +bool LLPreviewedFloater::selectElement(LLView* parent, int x, int y, int depth) +{ + if(getVisible()) + { + bool handled = false; + if(LLFloaterUIPreview::containerType(parent)) + { + for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it) + { + LLView* child = *child_it; + S32 local_x = x - child->getRect().mLeft; + S32 local_y = y - child->getRect().mBottom; + if (child->pointInView(local_x, local_y) && + child->getVisible() && + selectElement(child, x, y, ++depth)) + { + handled = true; + break; + } + } + } + + if(!handled) + { + LLView::sPreviewClickedElement = parent; + } + return true; + } + else + { + return false; + } +} + +void LLPreviewedFloater::draw() +{ + if(NULL != mFloaterUIPreview) + { + // Set and unset sDrawPreviewHighlights flag so as to avoid using two flags + if(mFloaterUIPreview->mHighlightingOverlaps) + { + LLView::sDrawPreviewHighlights = true; + } + + // If we're looking for truncations, draw debug rects for the displayed + // floater only. + bool old_debug_rects = LLView::sDebugRects; + bool old_show_names = LLView::sDebugRectsShowNames; + if (sShowRectangles) + { + LLView::sDebugRects = true; + LLView::sDebugRectsShowNames = false; + } + + LLFloater::draw(); + + LLView::sDebugRects = old_debug_rects; + LLView::sDebugRectsShowNames = old_show_names; + + if(mFloaterUIPreview->mHighlightingOverlaps) + { + LLView::sDrawPreviewHighlights = false; + } + } +} + +void LLFloaterUIPreview::onClickToggleOverlapping() +{ + if(LLView::sHighlightingDiffs) + { + onClickToggleDiffHighlighting(); + mToggleHighlightButton->toggleState(); + } + LLView::sPreviewHighlightedElements.clear(); // clear lists first + + S32 width, height; + getResizeLimits(&width, &height); // illegal call of non-static member function + if(mHighlightingOverlaps) + { + mHighlightingOverlaps = !mHighlightingOverlaps; + // reset list of preview highlighted elements + setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight - mOverlapPanel->getRect().getWidth(),getRect().mBottom)); + setResizeLimits(width - mOverlapPanel->getRect().getWidth(), height); + } + else + { + mHighlightingOverlaps = !mHighlightingOverlaps; + displayFloater(false,1); + setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight + mOverlapPanel->getRect().getWidth(),getRect().mBottom)); + setResizeLimits(width + mOverlapPanel->getRect().getWidth(), height); + } + getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps); +} + +void LLFloaterUIPreview::findOverlapsInChildren(LLView* parent) +{ + if(parent->getChildCount() == 0 || !containerType(parent)) // if it has no children or isn't a container type, skip it + { + return; + } + + // for every child of the parent + for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it) + { + LLView* child = *child_it; + if(overlapIgnorable(child)) + { + continue; + } + + // for every sibling + for(child_list_const_iter_t sibling_it = parent->getChildList()->begin(); sibling_it != parent->getChildList()->end(); ++sibling_it) // for each sibling + { + LLView* sibling = *sibling_it; + if(overlapIgnorable(sibling)) + { + continue; + } + + // if they overlap... (we don't care if they're visible or enabled -- we want to check those anyway, i.e. hidden tabs that can be later shown) + if(sibling != child && elementOverlap(child, sibling)) + { + mOverlapPanel->mOverlapMap[child].push_back(sibling); // add to the map + } + } + findOverlapsInChildren(child); // recur + } +} + +// *HACK: don't overlap with the drag handle and various other elements +// This is using dynamic casts because there is no object-oriented way to tell which elements contain localizable text. These are a few that are ignorable. +// *NOTE: If a list of elements which have localizable content were created, this function should return false if viewp's class is in that list. +bool LLFloaterUIPreview::overlapIgnorable(LLView* viewp) +{ + return NULL != dynamic_cast<LLDragHandle*>(viewp) || + NULL != dynamic_cast<LLViewBorder*>(viewp) || + NULL != dynamic_cast<LLResizeBar*>(viewp); +} + +// *HACK: these are the only two container types as of 8/08, per Richard +// This is using dynamic casts because there is no object-oriented way to tell which elements are containers. +bool LLFloaterUIPreview::containerType(LLView* viewp) +{ + return NULL != dynamic_cast<LLPanel*>(viewp) || NULL != dynamic_cast<LLLayoutStack*>(viewp); +} + +// Check if two llview's rectangles overlap, with some tolerance +bool LLFloaterUIPreview::elementOverlap(LLView* view1, LLView* view2) +{ + LLSD rec1 = view1->getRect().getValue(); + LLSD rec2 = view2->getRect().getValue(); + int tolerance = 2; + return (int)rec1[0] <= (int)rec2[2] - tolerance && + (int)rec2[0] <= (int)rec1[2] - tolerance && + (int)rec1[3] <= (int)rec2[1] - tolerance && + (int)rec2[3] <= (int)rec1[1] - tolerance; +} + +void LLOverlapPanel::draw() +{ + static const std::string current_selection_text("Current selection: "); + static const std::string overlapper_text("Overlapper: "); + LLColor4 text_color = LLColor4::grey; + gGL.color4fv(text_color.mV); + + if(!LLView::sPreviewClickedElement) + { + LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5 + LLView::sDrawPreviewHighlights = false; + LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text, 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + } + else + { + OverlapMap::iterator iterExists = mOverlapMap.find(LLView::sPreviewClickedElement); + if(iterExists == mOverlapMap.end()) + { + return; + } + + std::list<LLView*> overlappers = mOverlapMap[LLView::sPreviewClickedElement]; + if(overlappers.size() == 0) + { + LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5 + LLView::sDrawPreviewHighlights = false; + std::string current_selection = std::string(current_selection_text + LLView::sPreviewClickedElement->getName() + " (no elements overlap)"); + S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(current_selection) + 10; + LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection, 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + // widen panel enough to fit this text + LLRect rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); + return; + } + + // recalculate required with and height; otherwise use cached + bool need_to_recalculate_bounds = false; + if(mLastClickedElement == NULL) + { + need_to_recalculate_bounds = true; + } + + if(NULL == mLastClickedElement) + { + mLastClickedElement = LLView::sPreviewClickedElement; + } + + // recalculate bounds for scroll panel + if(need_to_recalculate_bounds || LLView::sPreviewClickedElement->getName() != mLastClickedElement->getName()) + { + // reset panel's rectangle to its default width and height (300x600) + LLRect panel_rect = getRect(); + setRect(LLRect(panel_rect.mLeft,panel_rect.mTop,panel_rect.mLeft+getRect().getWidth(),panel_rect.mTop-getRect().getHeight())); + + LLRect rect; + + // change bounds for selected element + int height_sum = mLastClickedElement->getRect().getHeight() + mSpacing + 80; + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > mLastClickedElement->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + mLastClickedElement->getRect().getWidth() + 5, rect.mBottom)); + + // and widen to accomodate text if that's wider + std::string display_text = current_selection_text + LLView::sPreviewClickedElement->getName(); + S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10; + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); + + std::list<LLView*> overlappers = mOverlapMap[LLView::sPreviewClickedElement]; + for(std::list<LLView*>::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it) + { + LLView* viewp = *overlap_it; + height_sum += viewp->getRect().getHeight() + mSpacing*3; + + // widen panel's rectangle to accommodate widest overlapping element of this floater + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > viewp->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + viewp->getRect().getWidth() + 5, rect.mBottom)); + + // and widen to accomodate text if that's wider + std::string display_text = overlapper_text + viewp->getName(); + S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10; + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); + } + // change panel's height to accommodate all element heights plus spacing between them + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.mRight,rect.mTop-height_sum)); + } + + LLUI::translate(5,getRect().getHeight()-10); // translate to top left + LLView::sDrawPreviewHighlights = false; + + // draw currently-selected element at top of overlappers + LLUI::translate(0,-mSpacing); + LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text + LLView::sPreviewClickedElement->getName(), 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + LLUI::translate(0,-mSpacing-LLView::sPreviewClickedElement->getRect().getHeight()); // skip spacing distance + height + LLView::sPreviewClickedElement->draw(); + + for(std::list<LLView*>::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it) + { + LLView* viewp = *overlap_it; + + // draw separating line + LLUI::translate(0,-mSpacing); + gl_line_2d(0,0,getRect().getWidth()-10,0,LLColor4(192.0f/255.0f,192.0f/255.0f,192.0f/255.0f)); + + // draw name + LLUI::translate(0,-mSpacing); + LLFontGL::getFontSansSerifSmall()->renderUTF8(overlapper_text + viewp->getName(), 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + + // draw element + LLUI::translate(0,-mSpacing-viewp->getRect().getHeight()); // skip spacing distance + height + viewp->draw(); + } + mLastClickedElement = LLView::sPreviewClickedElement; + } +} + +void LLFloaterUIPreviewUtil::registerFloater() +{ + LLFloaterReg::add("ui_preview", "floater_ui_preview.xml", + &LLFloaterReg::build<LLFloaterUIPreview>); +} |