/** * @file llui.cpp * @brief UI implementation * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ // Utilities functions the user interface needs #include "linden_common.h" #include #include // Linden library includes #include "v2math.h" #include "m3math.h" #include "v4color.h" #include "llrender.h" #include "llrect.h" #include "lldir.h" #include "llgl.h" #include "llsd.h" // Project includes #include "llcommandmanager.h" #include "llcontrol.h" #include "llui.h" #include "lluicolortable.h" #include "llview.h" #include "lllineeditor.h" #include "llfloater.h" #include "llfloaterreg.h" #include "llmenugl.h" #include "llmenubutton.h" #include "llloadingindicator.h" #include "llwindow.h" #include "llspellcheck.h" // for registration #include "llfiltereditor.h" #include "llflyoutbutton.h" #include "llsearcheditor.h" #include "lltoolbar.h" #include "llcleanup.h" // for XUIParse #include "llquaternion.h" #include #include #include // // Globals // // Language for UI construction std::map gTranslation; std::list gUntranslated; // register filter editor here static LLDefaultChildRegistry::Register register_filter_editor("filter_editor"); static LLDefaultChildRegistry::Register register_flyout_button("flyout_button"); static LLDefaultChildRegistry::Register register_search_editor("search_editor"); // register other widgets which otherwise may not be linked in static LLDefaultChildRegistry::Register register_loading_indicator("loading_indicator"); static LLDefaultChildRegistry::Register register_toolbar("toolbar"); // // Functions // LLUUID find_ui_sound(const char * namep) { std::string name = ll_safe_string(namep); LLUUID uuid = LLUUID(NULL); LLUI *ui_inst = LLUI::getInstance(); if (!ui_inst->mSettingGroups["config"]->controlExists(name)) { LL_WARNS() << "tried to make UI sound for unknown sound name: " << name << LL_ENDL; } else { uuid = LLUUID(ui_inst->mSettingGroups["config"]->getString(name)); if (uuid.isNull()) { if (ui_inst->mSettingGroups["config"]->getString(name) == LLUUID::null.asString()) { if (ui_inst->mSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) { LL_INFOS() << "UI sound name: " << name << " triggered but silent (null uuid)" << LL_ENDL; } } else { LL_WARNS() << "UI sound named: " << name << " does not translate to a valid uuid" << LL_ENDL; } } else if (ui_inst->mAudioCallback != NULL) { if (ui_inst->mSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) { LL_INFOS() << "UI sound name: " << name << LL_ENDL; } } } return uuid; } void make_ui_sound(const char* namep) { LLUUID soundUUID = find_ui_sound(namep); if(soundUUID.notNull()) { LLUI::getInstance()->mAudioCallback(soundUUID); } } void make_ui_sound_deferred(const char* namep) { LLUUID soundUUID = find_ui_sound(namep); if(soundUUID.notNull()) { LLUI::getInstance()->mDeferredAudioCallback(soundUUID); } } LLUI::LLUI(const settings_map_t& settings, LLImageProviderInterface* image_provider, LLUIAudioCallback audio_callback, LLUIAudioCallback deferred_audio_callback) : mSettingGroups(settings), mAudioCallback(audio_callback), mDeferredAudioCallback(deferred_audio_callback), mWindow(NULL), // set later in startup mRootView(NULL), mHelpImpl(NULL) { LLRender2D::createInstance(image_provider); LLSpellChecker::createInstance(); if ((get_ptr_in_map(mSettingGroups, std::string("config")) == NULL) || (get_ptr_in_map(mSettingGroups, std::string("floater")) == NULL) || (get_ptr_in_map(mSettingGroups, std::string("ignores")) == NULL)) { LL_ERRS() << "Failure to initialize configuration groups" << LL_ENDL; } LLFontGL::sShadowColor = LLUIColorTable::instance().getColor("ColorDropShadow"); LLUICtrl::CommitCallbackRegistry::Registrar& reg = LLUICtrl::CommitCallbackRegistry::defaultRegistrar(); // Callbacks for associating controls with floater visibility: reg.add("Floater.Toggle", [](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::toggleInstance(param.asStringRef()); }); reg.add("Floater.ToggleOrBringToFront", [](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::toggleInstanceOrBringToFront(param.asStringRef()); }); reg.add("Floater.Show", [](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::showInstance(param.asStringRef(), LLSD(), false); }); reg.add("Floater.ShowOrBringToFront", [](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::showInstanceOrBringToFront(param.asStringRef(), LLSD()); }); reg.add("Floater.Hide", [](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::hideInstance(param.asStringRef()); }); // Button initialization callback for toggle buttons reg.add("Button.SetFloaterToggle", boost::bind(&LLButton::setFloaterToggle, _1, _2)); // Button initialization callback for toggle buttons on dockable floaters reg.add("Button.SetDockableFloaterToggle", boost::bind(&LLButton::setDockableFloaterToggle, _1, _2)); // Display the help topic for the current context reg.add("Button.ShowHelp", boost::bind(&LLButton::showHelp, _1, _2)); // Currently unused, but kept for reference: reg.add("Button.ToggleFloater", boost::bind(&LLButton::toggleFloaterAndSetToggleState, _1, _2)); // Used by menus along with Floater.Toggle to display visibility as a check-mark LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.Visible", [](LLUICtrl* ctrl, const LLSD& param) -> bool { return LLFloaterReg::instanceVisible(param.asStringRef(), LLSD()); }); LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.IsOpen", [](LLUICtrl* ctrl, const LLSD& param) -> bool { return LLFloaterReg::instanceVisible(param.asStringRef(), LLSD()); }); // Parse the master list of commands LLCommandManager::load(); } LLUI::~LLUI() { LLSpellChecker::deleteSingleton(); LLRender2D::deleteSingleton(); } void LLUI::setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t& remove_popup, const clear_popups_t& clear_popups) { mAddPopupFunc = add_popup; mRemovePopupFunc = remove_popup; mClearPopupsFunc = clear_popups; } void LLUI::setMousePositionScreen(S32 x, S32 y) { #if defined(LL_DARWIN) S32 screen_x = ll_round(((F32)x * getScaleFactor().mV[VX]) / LLView::getWindow()->getSystemUISize()); S32 screen_y = ll_round(((F32)y * getScaleFactor().mV[VY]) / LLView::getWindow()->getSystemUISize()); #else S32 screen_x = ll_round((F32)x * getScaleFactor().mV[VX]); S32 screen_y = ll_round((F32)y * getScaleFactor().mV[VY]); #endif LLView::getWindow()->setCursorPosition(LLCoordGL(screen_x, screen_y).convert()); } void LLUI::getMousePositionScreen(S32 *x, S32 *y) { LLCoordWindow cursor_pos_window; getWindow()->getCursorPosition(&cursor_pos_window); LLCoordGL cursor_pos_gl(cursor_pos_window.convert()); *x = ll_round((F32)cursor_pos_gl.mX / getScaleFactor().mV[VX]); *y = ll_round((F32)cursor_pos_gl.mY / getScaleFactor().mV[VY]); } void LLUI::setMousePositionLocal(const LLView* viewp, S32 x, S32 y) { S32 screen_x, screen_y; viewp->localPointToScreen(x, y, &screen_x, &screen_y); setMousePositionScreen(screen_x, screen_y); } void LLUI::getMousePositionLocal(const LLView* viewp, S32 *x, S32 *y) { S32 screen_x, screen_y; getMousePositionScreen(&screen_x, &screen_y); viewp->screenPointToLocal(screen_x, screen_y, x, y); } // On Windows, the user typically sets the language when they install the // app (by running it with a shortcut that sets InstallLanguage). On Mac, // or on Windows if the SecondLife.exe executable is run directly, the // language follows the OS language. In all cases the user can override // the language manually in preferences. JC std::string LLUI::getUILanguage() { std::string language = "en"; if (mSettingGroups["config"]) { language = mSettingGroups["config"]->getString("Language"); if (language.empty() || language == "default") { language = mSettingGroups["config"]->getString("InstallLanguage"); } if (language.empty() || language == "default") { language = mSettingGroups["config"]->getString("SystemLanguage"); } if (language.empty() || language == "default") { language = "en"; } } return language; } // static std::string LLUI::getLanguage() { // Note: lldateutil_test redefines this function return LLUI::getInstance()->getUILanguage(); } struct SubDir : public LLInitParam::Block { Mandatory value; SubDir() : value("value") {} }; struct Directory : public LLInitParam::Block { Multiple > subdirs; Directory() : subdirs("subdir") {} }; struct Paths : public LLInitParam::Block { Multiple > directories; Paths() : directories("directory") {} }; //static std::string LLUI::locateSkin(const std::string& filename) { std::string found_file = filename; if (gDirUtilp->fileExists(found_file)) { return found_file; } found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS? if (gDirUtilp->fileExists(found_file)) { return found_file; } found_file = gDirUtilp->findSkinnedFilename(LLDir::XUI, filename); if (! found_file.empty()) { return found_file; } found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename); if (gDirUtilp->fileExists(found_file)) { return found_file; } LL_WARNS("LLUI") << "Can't find '" << filename << "' in user settings, any skin directory or app_settings" << LL_ENDL; return ""; } LLVector2 LLUI::getWindowSize() { LLCoordWindow window_rect; mWindow->getSize(&window_rect); return LLVector2(window_rect.mX / getScaleFactor().mV[VX], window_rect.mY / getScaleFactor().mV[VY]); } void LLUI::screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y) { *gl_x = ll_round((F32)screen_x * getScaleFactor().mV[VX]); *gl_y = ll_round((F32)screen_y * getScaleFactor().mV[VY]); } void LLUI::glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y) { *screen_x = ll_round((F32)gl_x / getScaleFactor().mV[VX]); *screen_y = ll_round((F32)gl_y / getScaleFactor().mV[VY]); } void LLUI::screenRectToGL(const LLRect& screen, LLRect *gl) { screenPointToGL(screen.mLeft, screen.mTop, &gl->mLeft, &gl->mTop); screenPointToGL(screen.mRight, screen.mBottom, &gl->mRight, &gl->mBottom); } void LLUI::glRectToScreen(const LLRect& gl, LLRect *screen) { glPointToScreen(gl.mLeft, gl.mTop, &screen->mLeft, &screen->mTop); glPointToScreen(gl.mRight, gl.mBottom, &screen->mRight, &screen->mBottom); } LLControlGroup& LLUI::getControlControlGroup (std::string_view controlname) { for (settings_map_t::iterator itor = mSettingGroups.begin(); itor != mSettingGroups.end(); ++itor) { LLControlGroup* control_group = itor->second; if(control_group != NULL) { if (control_group->controlExists(controlname)) return *control_group; } } return *mSettingGroups["config"]; // default group } void LLUI::addPopup(LLView* viewp) { if (mAddPopupFunc) { mAddPopupFunc(viewp); } } void LLUI::removePopup(LLView* viewp) { if (mRemovePopupFunc) { mRemovePopupFunc(viewp); } } void LLUI::clearPopups() { if (mClearPopupsFunc) { mClearPopupsFunc(); } } void LLUI::reportBadKeystroke() { make_ui_sound("UISndBadKeystroke"); } // spawn_x and spawn_y are top left corner of view in screen GL coordinates void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y) { const S32 CURSOR_HEIGHT = 16; // Approximate "normal" cursor size const S32 CURSOR_WIDTH = 8; LLView* parent = view->getParent(); S32 mouse_x; S32 mouse_y; getMousePositionScreen(&mouse_x, &mouse_y); // If no spawn location provided, use mouse position if (spawn_x == S32_MAX || spawn_y == S32_MAX) { spawn_x = mouse_x + CURSOR_WIDTH; spawn_y = mouse_y - CURSOR_HEIGHT; } LLRect virtual_window_rect = parent->getLocalRect(); LLRect mouse_rect; const S32 MOUSE_CURSOR_PADDING = 1; mouse_rect.setLeftTopAndSize(mouse_x - MOUSE_CURSOR_PADDING, mouse_y + MOUSE_CURSOR_PADDING, CURSOR_WIDTH + MOUSE_CURSOR_PADDING * 2, CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); S32 local_x, local_y; // convert screen coordinates to tooltip view-local coordinates parent->screenPointToLocal(spawn_x, spawn_y, &local_x, &local_y); // Start at spawn position (using left/top) view->setOrigin( local_x, local_y - view->getRect().getHeight()); // Make sure we're on-screen and not overlapping the mouse view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect ); } LLView* LLUI::resolvePath(LLView* context, const std::string& path) { // Nothing about resolvePath() should require non-const LLView*. If caller // wants non-const, call the const flavor and then cast away const-ness. return const_cast(resolvePath(const_cast(context), path)); } const LLView* LLUI::resolvePath(const LLView* context, const std::string& path) { // Create an iterator over slash-separated parts of 'path'. Dereferencing // this iterator returns an iterator_range over the substring. Unlike // LLStringUtil::getTokens(), this split_iterator doesn't combine adjacent // delimiters: leading/trailing slash produces an empty substring, double // slash produces an empty substring. That's what we need. boost::split_iterator ti(path, boost::first_finder("/")), tend; if (ti == tend) { // 'path' is completely empty, no navigation return context; } // leading / means "start at root" if (ti->empty()) { context = getRootView(); ++ti; } bool recurse = false; for (; ti != tend && context; ++ti) { if (ti->empty()) { recurse = true; } else { std::string part(ti->begin(), ti->end()); context = context->findChildView(LLURI::unescape(part), recurse); recurse = false; } } return context; } //static LLVector2& LLUI::getScaleFactor() { return LLRender::sUIGLScaleFactor; } //static void LLUI::setScaleFactor(const LLVector2& scale_factor) { LLRender::sUIGLScaleFactor = scale_factor; } // LLLocalClipRect and LLScreenClipRect moved to lllocalcliprect.h/cpp namespace LLInitParam { ParamValue::ParamValue(const LLUIColor& color) : super_t(color), red("red"), green("green"), blue("blue"), alpha("alpha"), control("") { updateBlockFromValue(false); } void ParamValue::updateValueFromBlock() { if (control.isProvided() && !control().empty()) { updateValue(LLUIColorTable::instance().getColor(control())); } else { updateValue(LLColor4(red, green, blue, alpha)); } } void ParamValue::updateBlockFromValue(bool make_block_authoritative) { LLColor4 color = getValue(); red.set(color.mV[VRED], make_block_authoritative); green.set(color.mV[VGREEN], make_block_authoritative); blue.set(color.mV[VBLUE], make_block_authoritative); alpha.set(color.mV[VALPHA], make_block_authoritative); control.set("", make_block_authoritative); } bool ParamCompare::equals(const LLFontGL* a, const LLFontGL* b) { return !(a->getFontDesc() < b->getFontDesc()) && !(b->getFontDesc() < a->getFontDesc()); } ParamValue::ParamValue(const LLFontGL* fontp) : super_t(fontp), name("name"), size("size"), style("style") { if (!fontp) { updateValue(LLFontGL::getFontDefault()); } addSynonym(name, ""); updateBlockFromValue(false); } void ParamValue::updateValueFromBlock() { const LLFontGL* res_fontp = LLFontGL::getFontByName(name); if (res_fontp) { updateValue(res_fontp); return; } U8 fontstyle = 0; fontstyle = LLFontGL::getStyleFromString(style()); LLFontDescriptor desc(name(), size(), fontstyle); const LLFontGL* fontp = LLFontGL::getFont(desc); if (fontp) { updateValue(fontp); } else { updateValue(LLFontGL::getFontDefault()); } } void ParamValue::updateBlockFromValue(bool make_block_authoritative) { if (getValue()) { name.set(LLFontGL::nameFromFont(getValue()), make_block_authoritative); size.set(LLFontGL::sizeFromFont(getValue()), make_block_authoritative); style.set(LLFontGL::getStringFromStyle(getValue()->getFontDesc().getStyle()), make_block_authoritative); } } ParamValue::ParamValue(const LLRect& rect) : super_t(rect), left("left"), top("top"), right("right"), bottom("bottom"), width("width"), height("height") { updateBlockFromValue(false); } void ParamValue::updateValueFromBlock() { LLRect rect; //calculate from params // prefer explicit left and right if (left.isProvided() && right.isProvided()) { rect.mLeft = left; rect.mRight = right; } // otherwise use width along with specified side, if any else if (width.isProvided()) { // only right + width provided if (right.isProvided()) { rect.mRight = right; rect.mLeft = right - width; } else // left + width, or just width { rect.mLeft = left; rect.mRight = left + width; } } // just left, just right, or none else { rect.mLeft = left; rect.mRight = right; } // prefer explicit bottom and top if (bottom.isProvided() && top.isProvided()) { rect.mBottom = bottom; rect.mTop = top; } // otherwise height along with specified side, if any else if (height.isProvided()) { // top + height provided if (top.isProvided()) { rect.mTop = top; rect.mBottom = top - height; } // bottom + height or just height else { rect.mBottom = bottom; rect.mTop = bottom + height; } } // just bottom, just top, or none else { rect.mBottom = bottom; rect.mTop = top; } updateValue(rect); } void ParamValue::updateBlockFromValue(bool make_block_authoritative) { // because of the ambiguity in specifying a rect by position and/or dimensions // we use the lowest priority pairing so that any valid pairing in xui // will override those calculated from the rect object // in this case, that is left+width and bottom+height LLRect& value = getValue(); right.set(value.mRight, false); left.set(value.mLeft, make_block_authoritative); width.set(value.getWidth(), make_block_authoritative); top.set(value.mTop, false); bottom.set(value.mBottom, make_block_authoritative); height.set(value.getHeight(), make_block_authoritative); } ParamValue::ParamValue(const LLCoordGL& coord) : super_t(coord), x("x"), y("y") { updateBlockFromValue(false); } void ParamValue::updateValueFromBlock() { updateValue(LLCoordGL(x, y)); } void ParamValue::updateBlockFromValue(bool make_block_authoritative) { x.set(getValue().mX, make_block_authoritative); y.set(getValue().mY, make_block_authoritative); } void TypeValues::declareValues() { declare("left", LLFontGL::LEFT); declare("right", LLFontGL::RIGHT); declare("center", LLFontGL::HCENTER); } void TypeValues::declareValues() { declare("top", LLFontGL::TOP); declare("center", LLFontGL::VCENTER); declare("baseline", LLFontGL::BASELINE); declare("bottom", LLFontGL::BOTTOM); } void TypeValues::declareValues() { declare("none", LLFontGL::NO_SHADOW); declare("hard", LLFontGL::DROP_SHADOW); declare("soft", LLFontGL::DROP_SHADOW_SOFT); } }