diff options
Diffstat (limited to 'indra/llui')
112 files changed, 2807 insertions, 642 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 83b3a220a0..18329b3b8a 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -41,6 +41,7 @@ set(llui_SOURCE_FILES llfloaterreglistener.cpp llflyoutbutton.cpp llfocusmgr.cpp + llgestureautocompletehelper.cpp llfolderview.cpp llfolderviewitem.cpp llfolderviewmodel.cpp @@ -154,6 +155,7 @@ set(llui_HEADER_FILES llfloaterreglistener.h llflyoutbutton.h llfocusmgr.h + llgestureautocompletehelper.h llfolderview.h llfolderviewitem.h llfolderviewmodel.h @@ -274,6 +276,14 @@ target_link_libraries(llui include(LibraryInstall) +if (CMAKE_CXX_COMPILER_ID MATCHES GNU) + set_source_files_properties(llxuiparser.cpp PROPERTIES COMPILE_FLAGS -Wno-sfinae-incomplete) +endif () + +if (USE_PRECOMPILED_HEADERS) + target_precompile_headers(llui REUSE_FROM llprecompiled) +endif () + # Add tests if(LL_TESTS) include(LLAddBuildTest) @@ -284,10 +294,8 @@ if(LL_TESTS) ) set_property( SOURCE ${llui_TEST_SOURCE_FILES} PROPERTY LL_TEST_ADDITIONAL_LIBRARIES ${test_libs}) LL_ADD_PROJECT_UNIT_TESTS(llui "${llui_TEST_SOURCE_FILES}") - # INTEGRATION TESTS - if(NOT LINUX) - set(test_libs llui llmessage llcorehttp llxml llrender llcommon ll::hunspell ) - LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}") - endif(NOT LINUX) + # INTEGRATION TESTS + set(test_libs llui llmessage llcorehttp llxml llrender llcommon ll::hunspell ) + LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}") endif(LL_TESTS) diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index 495ba2f40f..ac8f7c2126 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -47,6 +47,8 @@ static constexpr F32 AUTO_SCROLL_RATE_ACCEL = 120.f; static LLDefaultChildRegistry::Register<LLAccordionCtrl> t2("accordion"); +std::set<LLAccordionCtrl*> LLAccordionCtrl::sPendingArrange; + LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params) , mFitParent(params.fit_parent) , mNoVisibleTabsOrigString(params.no_visible_tabs_text.initial_value().asString()) @@ -163,7 +165,11 @@ bool LLAccordionCtrl::postBuild() //--------------------------------------------------------------------------------- LLAccordionCtrl::~LLAccordionCtrl() { - mAccordionTabs.clear(); + if (mArrangePending) + { + sPendingArrange.erase(this); + } + mAccordionTabs.clear(); } //--------------------------------------------------------------------------------- @@ -184,7 +190,7 @@ void LLAccordionCtrl::reshape(S32 width, S32 height, bool called_from_parent) // necessary text paddings can be set via h_pad and v_pad mNoVisibleTabsHelpText->setRect(getLocalRect()); - arrange(); + scheduleArrange(); } //--------------------------------------------------------------------------------- @@ -325,7 +331,7 @@ void LLAccordionCtrl::addCollapsibleCtrl(LLAccordionCtrlTab* accordion_tab) mAccordionTabs.push_back(accordion_tab); accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, (S16)(mAccordionTabs.size() - 1)) ); - arrange(); + scheduleArrange(); } void LLAccordionCtrl::removeCollapsibleCtrl(LLAccordionCtrlTab* accordion_tab) @@ -494,6 +500,7 @@ void LLAccordionCtrl::arrangeMultiple() void LLAccordionCtrl::arrange() { + LL_PROFILE_ZONE_SCOPED; updateNoTabsHelpTextVisibility(); if (mAccordionTabs.empty()) @@ -681,8 +688,9 @@ S32 LLAccordionCtrl::notifyParent(const LLSD& info) std::string str_action = info["action"]; if (str_action == "size_changes") { - // - arrange(); + // Multiple children can request an arrange, + // but only need to do it once so schedule it for later. + scheduleArrange(); return 1; } if (str_action == "select_next") @@ -924,3 +932,25 @@ void LLAccordionCtrl::collapseAllTabs() arrange(); } } + +void LLAccordionCtrl::scheduleArrange() +{ + if (!mArrangePending) + { + mArrangePending = true; + sPendingArrange.insert(this); + } +} + +void LLAccordionCtrl::updateClass() +{ + for (LLAccordionCtrl* inst : sPendingArrange) + { + if (inst) + { + inst->mArrangePending = false; + inst->arrange(); + } + } + sPendingArrange.clear(); +} diff --git a/indra/llui/llaccordionctrl.h b/indra/llui/llaccordionctrl.h index 43a33a2b3c..c7bb8bc9ff 100644 --- a/indra/llui/llaccordionctrl.h +++ b/indra/llui/llaccordionctrl.h @@ -142,6 +142,9 @@ public: void setSkipScrollToChild(bool skip) { mSkipScrollToChild = skip; } + void scheduleArrange(); + static void updateClass(); + private: void initNoTabsWidget(const LLTextBox::Params& tb_params); void updateNoTabsHelpTextVisibility(); @@ -188,12 +191,15 @@ private: LLTextBox* mNoVisibleTabsHelpText = nullptr; bool mSkipScrollToChild = false; + bool mArrangePending = false; std::string mNoMatchedTabsOrigString; std::string mNoVisibleTabsOrigString; LLAccordionCtrlTab* mSelectedTab = nullptr; const LLTabComparator* mTabComparator = nullptr; + + static std::set<LLAccordionCtrl*> sPendingArrange; }; diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index ac66525030..fa9de1eb09 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -39,7 +39,7 @@ static const std::string DD_BUTTON_NAME = "dd_button"; static const std::string DD_TEXTBOX_NAME = "dd_textbox"; static const std::string DD_HEADER_NAME = "dd_header"; -static const S32 HEADER_HEIGHT = 23; +static const S32 HEADER_HEIGHT = 25; static const S32 HEADER_IMAGE_LEFT_OFFSET = 5; static const S32 HEADER_TEXT_LEFT_OFFSET = 30; static const F32 AUTO_OPEN_TIME = 1.f; @@ -248,10 +248,22 @@ void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::draw() void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::reshape(S32 width, S32 height, bool called_from_parent /* = true */) { S32 header_height = mHeaderTextbox->getTextPixelHeight(); + LLRect old_header_rect = mHeaderTextbox->getRect(); LLRect textboxRect(HEADER_TEXT_LEFT_OFFSET, (height + header_height) / 2, width, (height - header_height) / 2); - mHeaderTextbox->reshape(textboxRect.getWidth(), textboxRect.getHeight()); - mHeaderTextbox->setRect(textboxRect); + if (old_header_rect.getHeight() != textboxRect.getHeight() + || old_header_rect.mLeft != textboxRect.mLeft + || old_header_rect.mTop != textboxRect.mTop + || old_header_rect.getWidth() > textboxRect.getWidth() // reducing header's width + || (old_header_rect.getWidth() < textboxRect.getWidth() && old_header_rect.getWidth() < mHeaderTextbox->getTextPixelWidth())) + { + // Expensive text reflow + // Update if position or height changes + // Update if width reduces + // But do not update if text already fits and width increases (arguably LLTextBox::reshape should be smarter, not Accordion) + mHeaderTextbox->reshape(textboxRect.getWidth(), textboxRect.getHeight()); + mHeaderTextbox->setRect(textboxRect); + } if (mHeaderTextbox->getTextPixelWidth() > mHeaderTextbox->getRect().getWidth()) { @@ -416,8 +428,11 @@ void LLAccordionCtrlTab::reshape(S32 width, S32 height, bool called_from_parent LLRect headerRect; headerRect.setLeftTopAndSize(0, height, width, HEADER_HEIGHT); - mHeader->setRect(headerRect); - mHeader->reshape(headerRect.getWidth(), headerRect.getHeight()); + if (mHeader->getRect() != headerRect) + { + mHeader->setRect(headerRect); + mHeader->reshape(headerRect.getWidth(), headerRect.getHeight()); + } if (!mDisplayChildren) return; @@ -464,7 +479,34 @@ void LLAccordionCtrlTab::onUpdateScrollToChild(const LLUICtrl *cntrl) // Translate to parent coordinatess to check if we are in visible rectangle rect.translate(getRect().mLeft, getRect().mBottom); - if (!getRect().contains(rect)) + bool needs_to_scroll = false; + const LLRect &acc_rect = getRect(); + if (!acc_rect.contains(rect)) + { + if (acc_rect.mTop < rect.mBottom || acc_rect.mBottom > rect.mTop) + { + // Content fully not in view + needs_to_scroll = true; + } + else if (acc_rect.getHeight() >= rect.getHeight()) + { + // Content can be displayed fully, but only partially in view + needs_to_scroll = true; + } + else if (acc_rect.mTop <= rect.mTop || acc_rect.mBottom >= rect.mBottom) + { + // Intersects, but too big to be displayed fully + S32 covered_height = acc_rect.mTop > rect.mTop ? rect.mTop - acc_rect.mBottom : acc_rect.mTop - rect.mBottom; + constexpr F32 covered_ratio = 0.7f; + if (covered_height < covered_ratio * acc_rect.getHeight()) + { + // Try to show bigger portion of the content + needs_to_scroll = true; + } + } + // else too big and in the middle of the view as is + } + if (needs_to_scroll) { // for accordition's scroll, height is in pixels // Back to local coords and calculate position for scroller @@ -932,7 +974,7 @@ void LLAccordionCtrlTab::adjustContainerPanel(const LLRect& child_rect) show_hide_scrollbar(child_rect); updateLayout(child_rect); } - else + else if (mContainerPanel->getRect() != child_rect) { mContainerPanel->reshape(child_rect.getWidth(), child_rect.getHeight()); mContainerPanel->setRect(child_rect); diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h index 3fdcf9f7f2..bb0b8ce04f 100644 --- a/indra/llui/llaccordionctrltab.h +++ b/indra/llui/llaccordionctrltab.h @@ -140,7 +140,7 @@ public: S32 notify(const LLSD& info); bool notifyChildren(const LLSD& info); - void draw(); + virtual void draw(); void storeOpenCloseState(); void restoreOpenCloseState(); diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index d2534b3939..7f209c60a7 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -85,6 +85,7 @@ LLButton::Params::Params() image_top_pad("image_top_pad"), image_bottom_pad("image_bottom_pad"), imgoverlay_label_space("imgoverlay_label_space", 1), + image_overlay_right_delta("image_overlay_right_delta", 0), label_color("label_color"), label_color_selected("label_color_selected"), // requires is_toggle true label_color_disabled("label_color_disabled"), @@ -109,6 +110,8 @@ LLButton::Params::Params() commit_on_capture_lost("commit_on_capture_lost", false), display_pressed_state("display_pressed_state", true), use_draw_context_alpha("use_draw_context_alpha", true), + draw_focus_border("draw_focus_border", true), + hover_hand_cursor("hover_hand_cursor", false), badge("badge"), handle_right_mouse("handle_right_mouse"), held_down_delay("held_down_delay"), @@ -158,6 +161,7 @@ LLButton::LLButton(const LLButton::Params& p) mImageOverlayTopPad(p.image_top_pad), mImageOverlayBottomPad(p.image_bottom_pad), mImgOverlayLabelSpace(p.imgoverlay_label_space), + mImageOverlayRightDelta(p.image_overlay_right_delta), mIsToggle(p.is_toggle), mScaleImage(p.scale_image), mDropShadowedText(p.label_shadow), @@ -179,6 +183,8 @@ LLButton::LLButton(const LLButton::Params& p) mMouseUpSignal(NULL), mHeldDownSignal(NULL), mUseDrawContextAlpha(p.use_draw_context_alpha), + mDrawFocusBorder(p.draw_focus_border), + mHoverHandCursor(p.hover_hand_cursor), mHandleRightMouse(p.handle_right_mouse), mFlashingTimer(NULL) { @@ -190,7 +196,7 @@ LLButton::LLButton(const LLButton::Params& p) // Note: flashing should be allowed in settings.xml (boolean key "EnableButtonFlashing"). S32 flash_count = p.button_flash_count.isProvided()? p.button_flash_count : 0; F32 flash_rate = p.button_flash_rate.isProvided()? p.button_flash_rate : 0.0f; - mFlashingTimer = new LLFlashTimer ((LLFlashTimer::callback_t)NULL, flash_count, flash_rate); + mFlashingTimer = new LLFlashTimer((LLFlashTimer::callback_t) nullptr, flash_count, flash_rate); } else { @@ -653,7 +659,7 @@ bool LLButton::handleHover(S32 x, S32 y, MASK mask) } // We only handle the click if the click both started and ended within us - getWindow()->setCursor(UI_CURSOR_ARROW); + getWindow()->setCursor(mHoverHandCursor ? UI_CURSOR_HAND : UI_CURSOR_ARROW); LL_DEBUGS("UserInput") << "hover handled by " << getName() << LL_ENDL; } return true; @@ -840,10 +846,9 @@ void LLButton::draw() label_color = ll::ui::SearchableControl::getHighlightFontColor(); // overlay with keyboard focus border - if (hasFocus()) + if (hasFocus() && mDrawFocusBorder) { - F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); - drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, ll_round(lerp(1.f, 3.f, lerp_amt))); + drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, gFocusMgr.getFocusFlashWidth()); } if (use_glow_effect) @@ -930,6 +935,17 @@ void LLButton::draw() } overlay_color.mV[VALPHA] *= alpha; + if (mImageOverlayRightDelta > 0) + { + mImageOverlay->draw(getRect().getWidth() - overlay_width - mImageOverlayRightDelta, + center_y - (overlay_height / 2), + overlay_width, + overlay_height, + overlay_color); + } + else + { + switch(mImageOverlayAlignment) { case LLFontGL::LEFT: @@ -964,6 +980,7 @@ void LLButton::draw() // draw nothing break; } + } } // Draw label diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 890e7c2d1e..0d1a28ee31 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -110,6 +110,7 @@ public: //image overlay paddings Optional<S32> image_top_pad; Optional<S32> image_bottom_pad; + Optional<S32> image_overlay_right_delta; /** * Space between image_overlay and label @@ -132,7 +133,9 @@ public: Optional<F32> hover_glow_amount; Optional<TimeIntervalParam> held_down_delay; - Optional<bool> use_draw_context_alpha; + Optional<bool> use_draw_context_alpha, + draw_focus_border, + hover_hand_cursor; Optional<LLBadge::Params> badge; @@ -153,7 +156,7 @@ public: ~LLButton(); // For backward compatability only - typedef boost::function<void(void*)> button_callback_t; + typedef std::function<void(void*)> button_callback_t; void addImageAttributeToXML(LLXMLNodePtr node, const std::string& imageName, const LLUUID& imageID,const std::string& xmlTagName) const; @@ -366,12 +369,16 @@ protected: S32 mImageOverlayBottomPad; bool mUseDrawContextAlpha; + bool mDrawFocusBorder; + bool mHoverHandCursor; /* * Space between image_overlay and label */ S32 mImgOverlayLabelSpace; + S32 mImageOverlayRightDelta; + F32 mHoverGlowStrength; F32 mCurGlowStrength; diff --git a/indra/llui/llcallbackmap.h b/indra/llui/llcallbackmap.h index 07775dc30f..3115606d91 100644 --- a/indra/llui/llcallbackmap.h +++ b/indra/llui/llcallbackmap.h @@ -29,13 +29,13 @@ #include <map> #include <string> -#include <boost/function.hpp> +#include <functional> class LLCallbackMap { public: // callback definition. - typedef boost::function<void* (void* data)> callback_t; + typedef std::function<void* (void* data)> callback_t; typedef std::map<std::string, LLCallbackMap> map_t; typedef map_t::iterator map_iter_t; @@ -48,8 +48,8 @@ public: return (void*)panel; } - LLCallbackMap() : mCallback(NULL), mData(NULL) { } - LLCallbackMap(callback_t callback, void* data = NULL) : mCallback(callback), mData(data) { } + LLCallbackMap() : mCallback(nullptr), mData(nullptr) {} + LLCallbackMap(callback_t callback, void* data = nullptr) : mCallback(callback), mData(data) {} callback_t mCallback; void* mData; diff --git a/indra/llui/llcheckboxctrl.h b/indra/llui/llcheckboxctrl.h index 4068741978..3507d3f34a 100644 --- a/indra/llui/llcheckboxctrl.h +++ b/indra/llui/llcheckboxctrl.h @@ -124,6 +124,11 @@ public: virtual bool isDirty() const; // Returns true if the user has modified this control. virtual void resetDirty(); // Clear dirty state + LLTextBox* getTextBox() // Provide direct access to label textbox control + { + return mLabel; + } + protected: virtual std::string _getSearchText() const { diff --git a/indra/llui/llclipboard.h b/indra/llui/llclipboard.h index 12d8233e0a..9f9cc3e54d 100644 --- a/indra/llui/llclipboard.h +++ b/indra/llui/llclipboard.h @@ -27,8 +27,6 @@ #ifndef LL_LLCLIPBOARD_H #define LL_LLCLIPBOARD_H -#include <boost/function.hpp> - #include "llstring.h" #include "lluuid.h" #include "llsingleton.h" diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index f3876ef695..7535295312 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -251,11 +251,26 @@ void LLComboBox::resetDirty() } } -bool LLComboBox::itemExists(const std::string& name) +bool LLComboBox::itemExists(const std::string& name) const { return mList->getItemByLabel(name); } +bool LLComboBox::valueExists(const std::string& value) const +{ + return mList->getItemByValue(value); +} + +LLScrollListItem* LLComboBox::findItemByValue(const std::string& value) const +{ + return mList->getItemByValue(value); +} + +std::vector<LLScrollListItem*> LLComboBox::getAllData() const +{ + return mList->getAllData(); +} + // add item "name" to menu LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, bool enabled) { @@ -521,7 +536,7 @@ bool LLComboBox::setCurrentByIndex(S32 index) if (item->getEnabled()) { mList->selectItem(item, -1, true); - LLSD::String label = item->getColumn(0)->getValue().asString(); + LLSD::String label = getSelectedItemLabel(); if (mTextEntry) { mTextEntry->setText(label); @@ -1009,7 +1024,7 @@ void LLComboBox::setKeystrokeOnEsc(bool enable) void LLComboBox::onTextEntry(LLLineEditor* line_editor) { - if (mTextEntryCallback != NULL) + if (mTextEntryCallback != nullptr) { (mTextEntryCallback)(line_editor, LLSD()); } @@ -1029,7 +1044,7 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor) mList->deselectAllItems(); mLastSelectedIndex = -1; } - if (mTextChangedCallback != NULL) + if (mTextChangedCallback != nullptr) { (mTextChangedCallback)(line_editor, LLSD()); } @@ -1077,7 +1092,7 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor) // RN: presumably text entry updateSelection(); } - if (mTextChangedCallback != NULL) + if (mTextChangedCallback != nullptr) { (mTextChangedCallback)(line_editor, LLSD()); } @@ -1323,6 +1338,39 @@ bool LLComboBox::selectItemRange( S32 first, S32 last ) return mList->selectItemRange(first, last); } +void LLComboBox::addInfo(LLSD& info) +{ + LLUICtrl::addInfo(info); + + if (mList && mList->getItemCount() > 0) + { + LLSD items_array; + std::vector<LLScrollListItem*> item_list = mList->getAllData(); + for (std::vector<LLScrollListItem*>::iterator iter = item_list.begin(); iter != item_list.end(); ++iter) + { + if (LLScrollListItem* item = *iter) + { + LLSD item_info; + item_info["value"] = item->getValue(); + if (item->getNumColumns() > 0) + { + if (LLScrollListCell* cell = item->getColumn(0)) + { + item_info["label"] = cell->getValue(); + } + } + items_array.append(item_info); + } + } + info["items"] = items_array; + info["item_count"] = mList->getItemCount(); + info["current_selection"] = getSelectedItemLabel(); + } + else + { + info["item_count"] = 0; + } +} static LLDefaultChildRegistry::Register<LLIconsComboBox> register_icons_combo_box("icons_combo_box"); diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index 8be3eb57e4..acd63e0198 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -36,7 +36,6 @@ #include "llrect.h" #include "llscrolllistctrl.h" #include "lllineeditor.h" -#include <boost/function.hpp> // Classes @@ -143,7 +142,10 @@ public: LLScrollListItem* addSeparator(EAddPosition pos = ADD_BOTTOM); bool remove( S32 index ); // remove item by index, return true if found and removed void removeall() { clearRows(); } - bool itemExists(const std::string& name); + bool itemExists(const std::string& name) const; + bool valueExists(const std::string& value) const; + LLScrollListItem* findItemByValue(const std::string& value) const; + std::vector<LLScrollListItem*> getAllData() const; void sortByName(bool ascending = true); // Sort the entries in the combobox by name @@ -220,6 +222,10 @@ public: void setButtonVisible(bool visible); + // Populates the provided LLSD with combo box-specific information(list of items, item count, current selection label) + // also includes base LLUICtrl information via parent class + void addInfo(LLSD & info); + void onButtonMouseDown(); void onListMouseUp(); void onItemSelected(const LLSD& data); diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp index 91e6f281da..ca512a9883 100644 --- a/indra/llui/llconsole.cpp +++ b/indra/llui/llconsole.cpp @@ -67,6 +67,10 @@ LLConsole::LLConsole(const LLConsole::Params& p) { setFontSize(p.font_size_index); } + if (mFont == nullptr) + { + setFontSize(0); // sans-serif + } mFadeTime = mLinePersistTime - FADE_DURATION; setMaxLines(LLUI::getInstance()->mSettingGroups["config"]->getS32("ConsoleMaxLines")); } @@ -79,6 +83,13 @@ void LLConsole::setLinePersistTime(F32 seconds) void LLConsole::reshape(S32 width, S32 height, bool called_from_parent) { + if (mFont == nullptr) + { + // not initialized yet + LL_WARNS() << "LLConsole::reshape called before font is set" << LL_ENDL; + return; + } + S32 new_width = llmax(50, llmin(getRect().getWidth(), width)); S32 new_height = llmax(mFont->getLineHeight() + 15, llmin(getRect().getHeight(), height)); diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp index 19ae03cdf9..c20e5a806c 100644 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -77,7 +77,7 @@ LLDockableFloater::~LLDockableFloater() bool LLDockableFloater::postBuild() { // Remember we should force docking when the floater is opened for the first time - if (mIsDockedStateForcedCallback != NULL && mIsDockedStateForcedCallback()) + if (mIsDockedStateForcedCallback != nullptr && mIsDockedStateForcedCallback()) { mForceDocking = true; } diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h index 9c516e23a4..accd9fddfc 100644 --- a/indra/llui/lldockablefloater.h +++ b/indra/llui/lldockablefloater.h @@ -129,7 +129,7 @@ protected: // Checks if docking should be forced. // It may be useful e.g. if floater created in mouselook mode (see EXT-5609) - boost::function<bool ()> mIsDockedStateForcedCallback; + std::function<bool()> mIsDockedStateForcedCallback; private: std::unique_ptr<LLDockControl> mDockControl; diff --git a/indra/llui/lldockcontrol.h b/indra/llui/lldockcontrol.h index b6ac9c19dd..1f6c3b9f57 100644 --- a/indra/llui/lldockcontrol.h +++ b/indra/llui/lldockcontrol.h @@ -49,11 +49,11 @@ public: public: // callback for a function getting a rect valid for control's position - typedef boost::function<void (LLRect& )> get_allowed_rect_callback_t; + typedef std::function<void (LLRect& )> get_allowed_rect_callback_t; LOG_CLASS(LLDockControl); LLDockControl(LLView* dockWidget, LLFloater* dockableFloater, - const LLUIImagePtr& dockTongue, DocAt dockAt, get_allowed_rect_callback_t get_rect_callback = NULL); + const LLUIImagePtr& dockTongue, DocAt dockAt, get_allowed_rect_callback_t get_rect_callback = nullptr); virtual ~LLDockControl(); public: diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp index 15536178ab..b3b47084c5 100644 --- a/indra/llui/lldraghandle.cpp +++ b/indra/llui/lldraghandle.cpp @@ -59,7 +59,9 @@ LLDragHandle::LLDragHandle(const LLDragHandle::Params& p) mMaxTitleWidth( 0 ), mForeground( true ), mDragHighlightColor(p.drag_highlight_color()), - mDragShadowColor(p.drag_shadow_color()) + mDragShadowColor(p.drag_shadow_color()), + mFont(p.font), + mLabelVPad(p.label_vpad()) { static LLUICachedControl<S32> snap_margin ("SnapMargin", 0); @@ -98,14 +100,13 @@ void LLDragHandleTop::setTitle(const std::string& title) } else { - const LLFontGL* font = LLFontGL::getFontSansSerif(); LLTextBox::Params params; params.name("Drag Handle Title"); params.rect(getRect()); params.initial_value(trimmed_title); - params.font(font); + params.font(mFont); params.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT | FOLLOWS_RIGHT); - params.font_shadow(LLFontGL::DROP_SHADOW_SOFT); + params.font_shadow(LLFontGL::NO_SHADOW); params.use_ellipses = true; params.parse_urls = false; //cancel URL replacement in floater title mTitleBox = LLUICtrlFactory::create<LLTextBox> (params); @@ -236,7 +237,6 @@ void LLDragHandleLeft::draw() void LLDragHandleTop::reshapeTitleBox() { - static LLUICachedControl<S32> title_vpad("UIFloaterTitleVPad", 0); if( ! mTitleBox) { return; @@ -248,7 +248,7 @@ void LLDragHandleTop::reshapeTitleBox() LLRect title_rect; title_rect.setLeftTopAndSize( LEFT_PAD, - getRect().getHeight() - title_vpad, + getRect().getHeight() - mLabelVPad, title_width, title_height); diff --git a/indra/llui/lldraghandle.h b/indra/llui/lldraghandle.h index 73211d5292..f768839749 100644 --- a/indra/llui/lldraghandle.h +++ b/indra/llui/lldraghandle.h @@ -43,13 +43,17 @@ public: : public LLInitParam::Block<Params, LLView::Params> { Optional<std::string> label; + Optional<S32> label_vpad; Optional<LLUIColor> drag_highlight_color; Optional<LLUIColor> drag_shadow_color; + Optional<const LLFontGL*> font; Params() : label("label"), + label_vpad("label_vpad", 7), drag_highlight_color("drag_highlight_color", LLUIColorTable::instance().getColor("DefaultHighlightLight")), - drag_shadow_color("drag_shadow_color", LLUIColorTable::instance().getColor("DefaultShadowDark")) + drag_shadow_color("drag_shadow_color", LLUIColorTable::instance().getColor("DefaultShadowDark")), + font("font", LLFontGL::getFontSansSerif()) { changeDefault(mouse_opaque, true); changeDefault(follows.flags, FOLLOWS_ALL); @@ -82,6 +86,8 @@ protected: protected: LLTextBox* mTitleBox; + const LLFontGL* mFont; + S32 mLabelVPad; private: LLRect mButtonsRect; diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index 925608e47e..16e6f0591a 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -390,14 +390,17 @@ void LLEmojiDictionary::loadEmojis() continue; } + std::string category; std::list<std::string> categories = loadCategories(sd); if (categories.empty()) { - LL_WARNS() << "Skipping invalid emoji descriptor (no categories)" << LL_ENDL; - continue; + // Should already have a localization for "other symbols" + category = "other symbols"; + } + else + { + category = categories.front(); } - - std::string category = categories.front(); if (std::find(mSkipCategories.begin(), mSkipCategories.end(), category) != mSkipCategories.end()) { diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp index b2c59ce775..7cdd19bebc 100644 --- a/indra/llui/llemojihelper.cpp +++ b/indra/llui/llemojihelper.cpp @@ -117,7 +117,17 @@ void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, c S32 top = floater_y - HELPER_FLOATER_OFFSET_Y + rect.getHeight(); rect.setLeftTopAndSize(left, top, rect.getWidth(), rect.getHeight()); pHelperFloater->setRect(rect); + + // Hack: Trying to open floater, search for a match, + // and hide floater immediately if no match found, + // instead of checking prior to opening + // + // Supress sounds in case floater won't be shown. + // Todo: add some kind of shouldShow(short_code) + U8 sound_flags = pHelperFloater->getSoundFlags(); + pHelperFloater->setSoundFlags(LLView::SILENT); pHelperFloater->openFloater(LLSD().with("hint", short_code)); + pHelperFloater->setSoundFlags(sound_flags); } void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p, bool strict) diff --git a/indra/llui/llemojihelper.h b/indra/llui/llemojihelper.h index 26840eef94..0dc74b1a74 100644 --- a/indra/llui/llemojihelper.h +++ b/indra/llui/llemojihelper.h @@ -69,5 +69,5 @@ private: boost::signals2::connection mHelperCommitConn; boost::signals2::connection mHelperCloseConn; std::function<void(llwchar)> mEmojiCommitCb; - bool mIsHideDisabled; + bool mIsHideDisabled = false; }; diff --git a/indra/llui/llflashtimer.cpp b/indra/llui/llflashtimer.cpp index 54f54653e2..addc3375b3 100644 --- a/indra/llui/llflashtimer.cpp +++ b/indra/llui/llflashtimer.cpp @@ -50,7 +50,7 @@ LLFlashTimer::LLFlashTimer(callback_t cb, S32 count, F32 period) void LLFlashTimer::unset() { mUnset = true; - mCallback = NULL; + mCallback = nullptr; } bool LLFlashTimer::tick() diff --git a/indra/llui/llflashtimer.h b/indra/llui/llflashtimer.h index 4ef70faf2d..1cd1df392b 100644 --- a/indra/llui/llflashtimer.h +++ b/indra/llui/llflashtimer.h @@ -28,13 +28,13 @@ #define LL_FLASHTIMER_H #include "lleventtimer.h" -#include "boost/function.hpp" +#include <functional> class LLFlashTimer : public LLEventTimer { public: - typedef boost::function<void (bool)> callback_t; + typedef std::function<void (bool)> callback_t; /** * Constructor. @@ -43,7 +43,7 @@ public: * @param period - how frequently callback should be called * @param cb - callback to be called each tick */ - LLFlashTimer(callback_t cb = NULL, S32 count = 0, F32 period = 0.0); + LLFlashTimer(callback_t cb = nullptr, S32 count = 0, F32 period = 0.0); ~LLFlashTimer() {}; /*virtual*/ bool tick(); diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 8178bada42..d863cc5143 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -31,6 +31,14 @@ #include "llflatlistview.h" +#include "llfocusmgr.h" +#include "llrender2dutils.h" +#include "llui.h" +#include "lluicolortable.h" +#include "llwindow.h" + +#include <algorithm> + static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("flat_list_view"); const LLSD SELECTED_EVENT = LLSD().with("selected", true); @@ -45,17 +53,28 @@ LLFlatListView::Params::Params() multi_select("multi_select"), keep_one_selected("keep_one_selected"), keep_selection_visible_on_reshape("keep_selection_visible_on_reshape",false), + allow_reorder("allow_reorder", false), + drag_indicator_color("drag_indicator_color"), no_items_text("no_items_text") {}; void LLFlatListView::reshape(S32 width, S32 height, bool called_from_parent /* = true */) { S32 delta = height - getRect().getHeight(); + LLRect visible_rc; + LLRect selected_rc = getLastSelectedItemRect(); + bool keep_selection_visible = false; + if (delta != 0 && mKeepSelectionVisibleOnReshape && selected_rc.isValid()) + { + visible_rc = getVisibleContentRect(); + keep_selection_visible = visible_rc.overlaps(selected_rc); + } + LLScrollContainer::reshape(width, height, called_from_parent); setItemsNoScrollWidth(width); rearrangeItems(); - if(delta!= 0 && mKeepSelectionVisibleOnReshape) + if(keep_selection_visible) { ensureSelectedVisible(); } @@ -69,7 +88,7 @@ const LLRect& LLFlatListView::getItemsRect() const bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*/, EAddPosition pos /*= ADD_BOTTOM*/,bool rearrange /*= true*/) { if (!item) return false; - if (value.isUndefined()) return false; + if (value.isUndefined()) return false; // item stays an orphan?!!! //force uniqueness of items, easiest check but unreliable if (item->getParent() == mItemsPanel) return false; @@ -91,7 +110,7 @@ bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null* } //_4 is for MASK - item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT)); item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); // Children don't accept the focus @@ -142,7 +161,7 @@ bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= tr mItemsPanel->addChild(panel); //_4 is for MASK - panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT)); panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); // Children don't accept the focus panel->setTabStop(false); @@ -165,7 +184,7 @@ bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= tr mItemsPanel->addChild(panel); //_4 is for MASK - panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4)); + panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4, CLICK_LEFT)); panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, item_pair, _4)); // Children don't accept the focus panel->setTabStop(false); @@ -217,7 +236,7 @@ bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, } //_4 is for MASK - item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT)); item_to_add->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); rearrangeItems(); @@ -396,6 +415,11 @@ U32 LLFlatListView::size(const bool only_visible_items) const void LLFlatListView::clear() { + if (mReorderDragPair) + { + cancelReorderDrag(); + } + // This will clear mSelectedItemPairs, calling all appropriate callbacks. resetSelection(); @@ -460,6 +484,16 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) , mIsConsecutiveSelection(false) , mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape) , mFocusOnItemClicked(true) + , mAllowReorder(p.allow_reorder) + , mIsReordering(false) + , mReorderDragPair(nullptr) + , mDeferredSelectPair(nullptr) + , mReorderMouseDownX(0) + , mReorderMouseDownY(0) + , mReorderInsertIndex(-1) + , mDragIndicatorColor(p.drag_indicator_color.isProvided() + ? p.drag_indicator_color() + : LLUIColorTable::instance().getColor("EmphasisColor")) { mBorderThickness = getBorderWidth(); @@ -520,12 +554,29 @@ LLFlatListView::~LLFlatListView() // virtual void LLFlatListView::draw() { + // Keep scrolling while a reorder drag rests over the top/bottom edge, so the + // user does not have to drop, scroll, and grab again. + if (mIsReordering) + { + S32 local_x, local_y; + LLUI::getInstance()->getMousePositionLocal(this, &local_x, &local_y); + if (autoScroll(local_x, local_y)) + { + mReorderInsertIndex = constrainInsertIndex(getInsertIndexAt(local_x, local_y)); + } + } + // Highlight border if a child of this container has keyboard focus if( mSelectedItemsBorder->getVisible() ) { mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() ); } LLScrollContainer::draw(); + + if (mIsReordering && mReorderInsertIndex >= 0) + { + drawReorderIndicator(); + } } // virtual @@ -537,6 +588,7 @@ bool LLFlatListView::postBuild() void LLFlatListView::rearrangeItems() { + LL_PROFILE_ZONE_SCOPED; static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); setNoItemsCommentVisible(0==size()); @@ -601,7 +653,7 @@ void LLFlatListView::rearrangeItems() mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); } -void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) +void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask, EMouseClickType clicktype) { if (!item_pair) return; @@ -616,6 +668,11 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) setFocus(true); } + if (clicktype == CLICK_LEFT && !(mask & (MASK_SHIFT | MASK_CONTROL))) + { + armReorderDrag(item_pair); + } + bool select_item = !isSelected(item_pair); //*TODO find a better place for that enforcing stuff @@ -694,6 +751,16 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) return; } + // When a reorderable list has a multi-selection, a plain click on one of the + // selected rows must not collapse the selection yet: the user may be about to + // drag the whole selection. Defer collapsing to mouse-up if no drag happens. + if (mAllowReorder && mMultipleSelection && !(mask & (MASK_CONTROL | MASK_SHIFT)) + && isSelected(item_pair) && numSelected() > 1) + { + mDeferredSelectPair = item_pair; + return; + } + //no need to do additional commit on selection reset if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(true); @@ -714,8 +781,387 @@ void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask) if ( !(mask & MASK_CONTROL) && mMultipleSelection && isSelected(item_pair) ) return; - // else got same behavior as at onItemMouseClick - onItemMouseClick(item_pair, mask); + // else got same behavior as at onItemMouseClick, but a right click must never start a drag + onItemMouseClick(item_pair, mask, CLICK_RIGHT); +} + +static constexpr S32 REORDER_DRAG_THRESHOLD = 5; + +void LLFlatListView::armReorderDrag(item_pair_t* item_pair) +{ + if (!mAllowReorder) + { + return; + } + + if (!item_pair || !item_pair->first) + { + return; + } + + if (size() < 2) + { + return; // nothing to reorder against + } + + if (gFocusMgr.getMouseCapture() && !hasMouseCapture()) + { + return; + } + + mReorderDragPair = item_pair; + mReorderDragGroup.clear(); + mDeferredSelectPair = nullptr; + mIsReordering = false; + mReorderInsertIndex = -1; + + LLUI::getInstance()->getMousePositionLocal(this, &mReorderMouseDownX, &mReorderMouseDownY); + gFocusMgr.setMouseCapture(this); +} + +void LLFlatListView::updateReorderDrag(S32 x, S32 y) +{ + if (!mReorderDragPair) + return; + + if (!mIsReordering) + { + if (abs(y - mReorderMouseDownY) < REORDER_DRAG_THRESHOLD + && abs(x - mReorderMouseDownX) < REORDER_DRAG_THRESHOLD) + { + return; // still within the click slop, not a drag yet + } + mIsReordering = true; + buildReorderGroup(); + } + + mReorderInsertIndex = constrainInsertIndex(getInsertIndexAt(x, y)); +} + +void LLFlatListView::buildReorderGroup() +{ + mReorderDragGroup.clear(); + + bool drag_selection = mMultipleSelection && isSelected(mReorderDragPair) && numSelected() > 1; + + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible()) + continue; + + if (pair == mReorderDragPair) + { + mReorderDragGroup.push_back(pair); + } + else if (drag_selection && isSelected(pair) + && (!mReorderValidateCallback || mReorderValidateCallback(mReorderDragPair->second, pair->second))) + { + // only carry along selected rows that belong to the grabbed row's group + mReorderDragGroup.push_back(pair); + } + } +} + +bool LLFlatListView::isInReorderGroup(item_pair_t* item_pair) const +{ + return std::find(mReorderDragGroup.begin(), mReorderDragGroup.end(), item_pair) != mReorderDragGroup.end(); +} + +void LLFlatListView::getReorderRemaining(pairs_list_t& remaining) const +{ + remaining.clear(); + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible() || isInReorderGroup(pair)) + continue; + remaining.push_back(pair); + } +} + +void LLFlatListView::finishReorderDrag() +{ + if (mReorderDragPair && mIsReordering && mReorderInsertIndex >= 0) + { + pairs_list_t remaining; + getReorderRemaining(remaining); + + // Resolve the drop boundary to the remaining row it lands before (nullptr = append). + item_pair_t* anchor = nullptr; + S32 cur = 0; + for (item_pair_t* pair : remaining) + { + if (cur == mReorderInsertIndex) + { + anchor = pair; + break; + } + ++cur; + } + + LLSD moved_value = mReorderDragPair->second; + + for (item_pair_t* pair : mReorderDragGroup) + { + mItemPairs.remove(pair); + } + + pairs_iterator_t it = (anchor != nullptr) + ? std::find(mItemPairs.begin(), mItemPairs.end(), anchor) + : mItemPairs.end(); + for (item_pair_t* pair : mReorderDragGroup) + { + mItemPairs.insert(it, pair); // preserves group order; it stays before anchor + } + + rearrangeItems(); + + if (mReorderCallback) + { + S32 visible_index = 0; + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible()) + continue; + if (pair == mReorderDragPair) + break; + ++visible_index; + } + + mReorderCallback(moved_value, visible_index); + } + } + + cancelReorderDrag(); +} + +void LLFlatListView::cancelReorderDrag() +{ + if (hasMouseCapture()) + { + gFocusMgr.setMouseCapture(nullptr); + } + + clearReorderDragState(); +} + +void LLFlatListView::clearReorderDragState() +{ + if (mIsReordering) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + } + + bool was_armed = (mReorderDragPair != nullptr); + + mReorderDragPair = nullptr; + mReorderDragGroup.clear(); + mDeferredSelectPair = nullptr; + mIsReordering = false; + mReorderInsertIndex = -1; + + if (was_armed && mReorderEndedCallback) + { + mReorderEndedCallback(); + } +} + +void LLFlatListView::onMouseCaptureLost() +{ + clearReorderDragState(); +} + +void LLFlatListView::onVisibilityChange(bool new_visibility) +{ + if (!new_visibility && mReorderDragPair) + { + cancelReorderDrag(); + } + + LLScrollContainer::onVisibilityChange(new_visibility); +} + +S32 LLFlatListView::getInsertIndexAt(S32 x, S32 y) const +{ + S32 panel_x, panel_y; + localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel); + + // The drop boundary among the remaining rows is the count of remaining rows + // whose vertical centre sits above the cursor. + S32 index = 0; + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible() || isInReorderGroup(pair)) + continue; + + if (pair->first->getRect().getCenterY() > panel_y) + { + ++index; + } + } + return index; +} + +LLFlatListView::item_pair_t* LLFlatListView::getReorderPairAt(S32 x, S32 y) const +{ + S32 panel_x, panel_y; + localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel); + + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible()) + continue; + + // Claim the padding above each row so gap presses still resolve to a row. + LLRect rc = pair->first->getRect(); + rc.mTop += mItemPad; + if (rc.pointInRect(panel_x, panel_y)) + { + return pair; + } + } + return nullptr; +} + +S32 LLFlatListView::constrainInsertIndex(S32 dest_index) const +{ + if (!mReorderValidateCallback) + return dest_index; + + // Clamp the boundary to the contiguous run of remaining rows that share the + // grabbed row's group, so a drag can't leave its group. + pairs_list_t remaining; + getReorderRemaining(remaining); + + const LLSD& dragged = mReorderDragPair->second; + S32 first_valid = -1; + S32 last_valid = -1; + S32 i = 0; + for (item_pair_t* pair : remaining) + { + if (mReorderValidateCallback(dragged, pair->second)) + { + if (first_valid < 0) + first_valid = i; + last_valid = i; + } + ++i; + } + + if (first_valid < 0) + return -1; // no valid neighbour (whole group is being dragged) + + if (dest_index < first_valid) + return first_valid; + if (dest_index > last_valid + 1) + return last_valid + 1; + return dest_index; +} + +void LLFlatListView::drawReorderIndicator() +{ + pairs_list_t remaining; + getReorderRemaining(remaining); + if (remaining.empty()) + return; + + const LLRect& panel_rc = mItemsPanel->getRect(); + const LLColor4& color = mDragIndicatorColor.get(); + + // faint highlight on each row being moved + for (item_pair_t* pair : mReorderDragGroup) + { + const LLRect& dr = pair->first->getRect(); + gl_rect_2d(panel_rc.mLeft + dr.mLeft, panel_rc.mBottom + dr.mTop, + panel_rc.mLeft + dr.mRight, panel_rc.mBottom + dr.mBottom, + color % 0.15f, true); + } + + // insertion line at the drop boundary among the remaining rows + S32 count = (S32)remaining.size(); + bool at_end = mReorderInsertIndex >= count; + S32 anchor_idx = at_end ? count - 1 : mReorderInsertIndex; + + item_pair_t* anchor = nullptr; + S32 cur = 0; + for (item_pair_t* pair : remaining) + { + if (cur == anchor_idx) + { + anchor = pair; + break; + } + ++cur; + } + if (!anchor) + return; + + const LLRect& item_rc = anchor->first->getRect(); + S32 left = panel_rc.mLeft + item_rc.mLeft; + S32 right = panel_rc.mLeft + item_rc.mRight; + S32 line_y = panel_rc.mBottom + (at_end ? item_rc.mBottom : item_rc.mTop); + + if (line_y < 0 || line_y > getRect().getHeight()) + return; + + gl_rect_2d(left, line_y, right, line_y - 1, color, true); +} + +bool LLFlatListView::handleHover(S32 x, S32 y, MASK mask) +{ + if (mAllowReorder && mReorderDragPair && hasMouseCapture()) + { + updateReorderDrag(x, y); + if (mIsReordering) + { + getWindow()->setCursor(UI_CURSOR_ARROWDRAG); + return true; + } + } + return LLScrollContainer::handleHover(x, y, mask); +} + +bool LLFlatListView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLScrollContainer::handleMouseDown(x, y, mask); + + // A press in the padding between rows misses every item, so the per-item + // mouse-down never arms a reorder. Map it to the nearest row and arm there; + // armReorderDrag's capture guard ignores presses a child control already took. + if (mAllowReorder && !mReorderDragPair && !(mask & (MASK_CONTROL | MASK_SHIFT))) + { + if (item_pair_t* pair = getReorderPairAt(x, y)) + { + onItemMouseClick(pair, mask, CLICK_LEFT); + } + } + + return handled || (mReorderDragPair != nullptr); +} + +bool LLFlatListView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (mReorderDragPair && hasMouseCapture()) + { + bool was_reordering = mIsReordering; + item_pair_t* deferred = mDeferredSelectPair; + mDeferredSelectPair = nullptr; + + finishReorderDrag(); + + if (was_reordering) + { + return true; // a drag happened: keep the (multi-)selection on the moved rows + } + + // No drag: apply the selection collapse that was deferred on mouse-down. + if (deferred) + { + resetSelection(true); + selectItemPair(deferred, true); + return true; + } + } + return LLScrollContainer::handleMouseUp(x, y, mask); } bool LLFlatListView::handleKeyHere(KEY key, MASK mask) @@ -1086,6 +1532,18 @@ bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange) { llassert(item_pair); + bool removing_reorder_pair = item_pair == mReorderDragPair + || std::find(mReorderDragGroup.begin(), mReorderDragGroup.end(), item_pair) != mReorderDragGroup.end(); + + if (removing_reorder_pair) + { + cancelReorderDrag(); + } + if (item_pair == mDeferredSelectPair) + { + mDeferredSelectPair = nullptr; + } + bool deleted = false; bool selection_changed = false; for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) @@ -1132,6 +1590,7 @@ bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange) void LLFlatListView::notifyParentItemsRectChanged() { + LL_PROFILE_ZONE_SCOPED; S32 comment_height = 0; // take into account comment text height if exists @@ -1346,9 +1805,17 @@ bool LLFlatListViewEx::getForceShowingUnmatchedItems() const return mForceShowingUnmatchedItems; } -void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show) +void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show, bool notify_parent) +{ + if (mForceShowingUnmatchedItems != show) { mForceShowingUnmatchedItems = show; + if (!mFilterSubString.empty()) + { + updateNoItemsMessage(mFilterSubString); + filterItems(false, true); + } + } } void LLFlatListViewEx::setFilterSubString(const std::string& filter_str, bool notify_parent) @@ -1393,7 +1860,7 @@ bool LLFlatListViewEx::updateItemVisibility(LLPanel* item, const LLSD &action) return false; } -void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) +bool LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) { std::string cur_filter = mFilterSubString; LLStringUtil::toUpper(cur_filter); @@ -1416,8 +1883,11 @@ void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) if (visibility_changed && notify_parent) { + rearrangeItems(); notifyParentItemsRectChanged(); + return true; } + return false; } bool LLFlatListViewEx::hasMatchedItems() diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 6271231183..ae20a4b1eb 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -30,6 +30,7 @@ #include "llpanel.h" #include "llscrollcontainer.h" #include "lltextbox.h" +#include "lluicolor.h" /** @@ -106,12 +107,28 @@ public: /** padding between items */ Optional<U32> item_pad; + /** allow items to be reordered by dragging them with the mouse */ + Optional<bool> allow_reorder; + + /** colour of the insertion indicator drawn while reordering */ + Optional<LLUIColor> drag_indicator_color; + /** textbox with info message when list is empty*/ Optional<LLTextBox::Params> no_items_text; Params(); }; + /** Fired after the user drags an item to a new position: (moved value, new visible index). */ + typedef boost::function<void(const LLSD& value, S32 new_index)> reorder_signal_t; + + /** + * Returns true if the dragged item is allowed to move past the given neighbour. + * Lets owners constrain reordering (e.g. keep items within a group). Optional; + * when unset any reorder is permitted. + */ + typedef boost::function<bool(const LLSD& dragged, const LLSD& neighbour)> reorder_validate_signal_t; + // disable traversal when finding widget to hand focus off to /*virtual*/ bool canFocusChildren() const override { return false; } @@ -259,6 +276,18 @@ public: /** Turn on/off selection support */ void setAllowSelection(bool can_select) { mAllowSelection = can_select; } + /** Turn on/off drag-to-reorder support */ + void setAllowReorder(bool allow) { mAllowReorder = allow; } + + void setReorderCallback(reorder_signal_t cb) { mReorderCallback = cb; } + void setReorderValidateCallback(reorder_validate_signal_t cb) { mReorderValidateCallback = cb; } + + /** Fired when a reorder grab is released, whether or not it moved anything */ + void setReorderEndedCallback(boost::function<void()> cb) { mReorderEndedCallback = cb; } + + /** True while an item is grabbed for a drag-to-reorder (before or during the drag) */ + bool isReorderActive() const { return mReorderDragPair != NULL; } + /** Sets flag whether onCommit should be fired if selection was changed */ // FIXME: this should really be a separate signal, since "Commit" implies explicit user action, and selection changes can happen more indirectly. void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; } @@ -330,7 +359,7 @@ protected: LLFlatListView(const LLFlatListView::Params& p); /** Manage selection on mouse events */ - void onItemMouseClick(item_pair_t* item_pair, MASK mask); + void onItemMouseClick(item_pair_t* item_pair, MASK mask, EMouseClickType clicktype); void onItemRightMouseClick(item_pair_t* item_pair, MASK mask); @@ -368,6 +397,16 @@ protected: virtual bool handleKeyHere(KEY key, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + + virtual void onMouseCaptureLost() override; + + virtual void onVisibilityChange(bool new_visibility) override; + virtual bool postBuild() override; virtual void onFocusReceived() override; @@ -384,6 +423,29 @@ protected: private: + // Drag-to-reorder helpers. + void armReorderDrag(item_pair_t* item_pair); + void updateReorderDrag(S32 x, S32 y); + void finishReorderDrag(); + void cancelReorderDrag(); + void clearReorderDragState(); + + // Builds the set of pairs being dragged (the whole selection when the grabbed + // row is part of a multi-selection, constrained to the validator's group). + void buildReorderGroup(); + bool isInReorderGroup(item_pair_t* item_pair) const; + + // The remaining visible pairs (those not being dragged), in visual order. + void getReorderRemaining(pairs_list_t& remaining) const; + // Number of leading non-dragged items whose centre sits above (x, y). + S32 getInsertIndexAt(S32 x, S32 y) const; + // Row under (x, y), counting the padding above each row, so presses in the + // inter-row gaps still map to a row instead of falling through. + item_pair_t* getReorderPairAt(S32 x, S32 y) const; + // Clamps an insertion boundary to the validator's contiguous group. + S32 constrainInsertIndex(S32 dest_index) const; + void drawReorderIndicator(); + void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;} void setNoItemsCommentVisible(bool visible) const; @@ -427,6 +489,20 @@ private: bool mFocusOnItemClicked; + /** Drag-to-reorder state */ + bool mAllowReorder; + bool mIsReordering; // a drag is currently in progress + item_pair_t* mReorderDragPair; // the grabbed pair (drag anchor) + pairs_list_t mReorderDragGroup; // all pairs being dragged, in visual order + item_pair_t* mDeferredSelectPair; // collapse selection to this on mouse-up if no drag + S32 mReorderMouseDownX; + S32 mReorderMouseDownY; + S32 mReorderInsertIndex; // current drop boundary among the remaining (non-dragged) items + LLUIColor mDragIndicatorColor; + reorder_signal_t mReorderCallback; + reorder_validate_signal_t mReorderValidateCallback; + boost::function<void()> mReorderEndedCallback; + /** All pairs of the list */ pairs_list_t mItemPairs; @@ -484,7 +560,11 @@ public: bool getForceShowingUnmatchedItems() const; - void setForceShowingUnmatchedItems(bool show); + /** + * Sets filtered out items to stay visible. Can result in rect changes, + * so can notify_parent if rect changes + */ + void setForceShowingUnmatchedItems(bool show, bool notify_parent); /** * Sets up new filter string and filters the list. @@ -495,8 +575,9 @@ public: /** * Filters the list, rearranges and notifies parent about shape changes. * Derived classes may want to overload rearrangeItems() to exclude repeated separators after filtration. + * Returns true in case of changes */ - void filterItems(bool re_sort, bool notify_parent); + bool filterItems(bool re_sort, bool notify_parent); /** * Returns true if last call of filterItems() found at least one matching item diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index fd07b2ec5d..b5e26db1b5 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -183,8 +183,10 @@ LLFloater::Params::Params() show_title("show_title", true), auto_close("auto_close", false), positioning("positioning", LLFloaterEnums::POSITIONING_RELATIVE), + header_font("header_font", LLFontGL::getFontSansSerif()), header_height("header_height", 0), legacy_header_height("legacy_header_height", 0), + header_vpad("header_vpad", 7), close_image("close_image"), restore_image("restore_image"), minimize_image("minimize_image"), @@ -239,7 +241,7 @@ void LLFloater::initClass() } // defaults for floater param block pulled from widgets/floater.xml -static LLWidgetNameRegistry::StaticRegistrar sRegisterFloaterParams(&typeid(LLFloater::Params), "floater"); +static LLWidgetNameRegistry::StaticRegistrar sRegisterFloaterParams(typeid(LLFloater::Params), "floater"); LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) : LLPanel(), // intentionally do not pass params here, see initFromParams @@ -248,6 +250,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mShortTitle(p.short_title), mSingleInstance(p.single_instance), mReuseInstance(p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance), // reuse single-instance floaters by default + mIsReuseInitialized(p.reuse_instance.isProvided()), mKey(key), mCanTearOff(p.can_tear_off), mCanMinimize(p.can_minimize), @@ -293,7 +296,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) memset(mButtonsEnabled, 0, BUTTON_COUNT * sizeof(bool)); memset(mButtons, 0, BUTTON_COUNT * sizeof(LLButton*)); - addDragHandle(); + addDragHandle(p); addResizeCtrls(); initFromParams(p); @@ -336,7 +339,7 @@ void LLFloater::initFloater(const Params& p) } } -void LLFloater::addDragHandle() +void LLFloater::addDragHandle(const LLFloater::Params& floater_params) { if (!mDragHandle) { @@ -346,6 +349,8 @@ void LLFloater::addDragHandle() p.name("drag"); p.follows.flags(FOLLOWS_ALL); p.label(mTitle); + p.font(floater_params.header_font); + p.label_vpad(floater_params.header_vpad); mDragHandle = LLUICtrlFactory::create<LLDragHandleLeft>(p); } else // drag on top @@ -354,6 +359,8 @@ void LLFloater::addDragHandle() p.name("Drag Handle"); p.follows.flags(FOLLOWS_ALL); p.label(mTitle); + p.font(floater_params.header_font); + p.label_vpad(floater_params.header_vpad); mDragHandle = LLUICtrlFactory::create<LLDragHandleTop>(p); } addChild(mDragHandle); @@ -563,6 +570,8 @@ void LLFloater::storeRectControl() void LLFloater::storeVisibilityControl() { + // Todo: this is a bit pricey, gets called each frame + // on LLAppViewer::idle(), optimize! if( !sQuitting && mVisibilityControl.size() > 1 ) { getControlGroup()->setBOOL( mVisibilityControl, getVisible() ); @@ -2274,7 +2283,7 @@ void LLFloater::drawConeToOwner(F32 &context_cone_opacity, LLRect local_rect = getLocalRect(); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLEnable(GL_CULL_FACE); + LLGLEnable cull_face(GL_CULL_FACE); gGL.begin(LLRender::TRIANGLE_STRIP); { gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); @@ -3351,6 +3360,7 @@ void LLFloater::initFromParams(const LLFloater::Params& p) mLegacyHeaderHeight = p.legacy_header_height; mSingleInstance = p.single_instance; mReuseInstance = p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance; + mIsReuseInitialized = p.reuse_instance.isProvided(); mDefaultRelativeX = p.rel_x; mDefaultRelativeY = p.rel_y; diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 9e1594bdd2..bda2531b43 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -172,8 +172,10 @@ public: Optional<LLFloaterEnums::EOpenPositioning> positioning; + Optional<const LLFontGL*> header_font; Optional<S32> header_height, - legacy_header_height; // HACK see initFromXML() + legacy_header_height, // HACK see initFromXML() + header_vpad; Optional<F32> rel_x, rel_y; @@ -442,7 +444,7 @@ private: bool offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index); void addResizeCtrls(); void layoutResizeCtrls(); - void addDragHandle(); + void addDragHandle(const LLFloater::Params& p); void layoutDragHandle(); // repair layout static void updateActiveFloaterTransparency(); diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index a818e72f59..c18495ce71 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -40,9 +40,9 @@ LLFloaterReg::instance_list_t LLFloaterReg::sNullInstanceList; LLFloaterReg::instance_map_t LLFloaterReg::sInstanceMap; LLFloaterReg::build_map_t LLFloaterReg::sBuildMap; -std::map<std::string, std::string, std::less<>> LLFloaterReg::sGroupMap; +LLFloaterReg::group_map_t LLFloaterReg::sGroupMap; bool LLFloaterReg::sBlockShowFloaters = false; -std::set<std::string, std::less<>> LLFloaterReg::sAlwaysShowableList; +LLFloaterReg::always_showable_t LLFloaterReg::sAlwaysShowableList; static LLFloaterRegListener sFloaterRegListener; @@ -96,11 +96,8 @@ LLFloater* LLFloaterReg::getLastFloaterCascading() candidate_rect.mTop = 100000; LLFloater* candidate_floater = NULL; - std::map<std::string,std::string>::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); - for( ; it != it_end; ++it) + for (const auto& [floater_name, group_name] : sGroupMap) { - const std::string& group_name = it->second; - instance_list_t& instances = sInstanceMap[group_name]; for (LLFloater* inst : instances) @@ -604,17 +601,11 @@ U32 LLFloaterReg::getVisibleFloaterInstanceCount() { U32 count = 0; - std::map<std::string,std::string>::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); - for( ; it != it_end; ++it) + for (const auto& [floater_name, group_name] : sGroupMap) { - const std::string& group_name = it->second; - instance_list_t& instances = sInstanceMap[group_name]; - - for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter) + for (LLFloater* inst : instances) { - LLFloater* inst = *iter; - if (inst->getVisible() && !inst->isMinimized()) { count++; diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 94a67c8d8b..71a11b6f42 100644 --- a/indra/llui/llfloaterreg.h +++ b/indra/llui/llfloaterreg.h @@ -29,9 +29,11 @@ /// llcommon #include "llrect.h" #include "llsd.h" +#include "llstl.h" +#include <functional> #include <list> -#include <boost/function.hpp> +#include <unordered_set> //******************************************************* // @@ -41,7 +43,7 @@ class LLFloater; class LLUICtrl; -typedef boost::function<LLFloater* (const LLSD& key)> LLFloaterBuildFunc; +typedef std::function<LLFloater* (const LLSD& key)> LLFloaterBuildFunc; class LLFloaterReg { @@ -51,26 +53,29 @@ public: // 2) We can change the key of a floater without altering the list. typedef std::list<LLFloater*> instance_list_t; typedef const instance_list_t const_instance_list_t; - typedef std::map<std::string, instance_list_t, std::less<>> instance_map_t; + typedef std::unordered_map<std::string, instance_list_t, ll::string_hash, std::equal_to<>> instance_map_t; struct BuildData { LLFloaterBuildFunc mFunc; std::string mFile; }; - typedef std::map<std::string, BuildData, std::less<>> build_map_t; + typedef std::unordered_map<std::string, BuildData, ll::string_hash, std::equal_to<>> build_map_t; private: friend class LLFloaterRegListener; static instance_list_t sNullInstanceList; static instance_map_t sInstanceMap; static build_map_t sBuildMap; - static std::map<std::string, std::string, std::less<>> sGroupMap; + + using group_map_t = std::unordered_map<std::string, std::string, ll::string_hash, std::equal_to<>>; + static group_map_t sGroupMap; static bool sBlockShowFloaters; /** * Defines list of floater names that can be shown despite state of sBlockShowFloaters. */ - static std::set<std::string, std::less<>> sAlwaysShowableList; + using always_showable_t = std::unordered_set<std::string, ll::string_hash, std::equal_to<>>; + static always_showable_t sAlwaysShowableList; public: // Registration diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index 17641b8375..7cf09369ec 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -60,6 +60,10 @@ LLFloaterRegListener::LLFloaterRegListener(): "Ask to toggle the state of the floater specified in [\"name\"]", &LLFloaterRegListener::toggleInstance, requiredName); + add("toggleInstanceOrBringToFront", + "Ask to toggle the state of the floater specified in [\"name\"] or bring it to front if already opened", + &LLFloaterRegListener::toggleInstance, + requiredName); add("instanceVisible", "Return on [\"reply\"] an event whose [\"visible\"] indicates the visibility " "of the floater specified in [\"name\"]", @@ -107,6 +111,11 @@ void LLFloaterRegListener::toggleInstance(const LLSD& event) const LLFloaterReg::toggleInstance(event["name"].asString(), event["key"]); } +void LLFloaterRegListener::toggleInstanceOrBringToFront(const LLSD& event) const +{ + LLFloaterReg::toggleInstanceOrBringToFront(event["name"].asString(), event["key"]); +} + void LLFloaterRegListener::instanceVisible(const LLSD& event) const { sendReply(LLSDMap("visible", LLFloaterReg::instanceVisible(event["name"].asString(), event["key"])), diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h index 28f6e7c66b..610a2831dc 100644 --- a/indra/llui/llfloaterreglistener.h +++ b/indra/llui/llfloaterreglistener.h @@ -46,6 +46,7 @@ private: void showInstance(const LLSD& event) const; void hideInstance(const LLSD& event) const; void toggleInstance(const LLSD& event) const; + void toggleInstanceOrBringToFront(const LLSD& event) const; void instanceVisible(const LLSD& event) const; void clickButton(const LLSD& event) const; }; diff --git a/indra/llui/llflyoutbutton.cpp b/indra/llui/llflyoutbutton.cpp index 2e198e8dbc..9d511bcf87 100644 --- a/indra/llui/llflyoutbutton.cpp +++ b/indra/llui/llflyoutbutton.cpp @@ -31,7 +31,7 @@ //static LLDefaultChildRegistry::Register<LLFlyoutButton> r2("flyout_button"); -const S32 FLYOUT_BUTTON_ARROW_WIDTH = 24; +constexpr S32 FLYOUT_BUTTON_ARROW_WIDTH = 22; LLFlyoutButton::LLFlyoutButton(const Params& p) : LLComboBox(p), @@ -43,9 +43,13 @@ LLFlyoutButton::LLFlyoutButton(const Params& p) LLButton::Params bp(p.action_button); bp.name(p.label); bp.label(p.label); - bp.rect.left(0).bottom(0).width(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH).height(getRect().getHeight()); + bp.rect.left(0).bottom(0).width(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH - 2 * BTN_DROP_SHADOW).height(getRect().getHeight()); bp.click_callback.function(boost::bind(&LLFlyoutButton::onActionButtonClick, this, _2)); bp.follows.flags(FOLLOWS_ALL); + if (p.font.isProvided()) + { + bp.font(p.font); + } mActionButton = LLUICtrlFactory::create<LLButton>(bp); addChild(mActionButton); @@ -74,4 +78,16 @@ void LLFlyoutButton::setToggleState(bool state) mToggleState = state; } +bool LLFlyoutButton::postBuild() +{ + bool result = LLComboBox::postBuild(); + + LLRect rect = getLocalRect(); + S32 arrow_width = llmax(FLYOUT_BUTTON_ARROW_WIDTH, mArrowImage ? mArrowImage->getWidth() : 0); + mButton->setRect(LLRect(getRect().getWidth() - llmax(8, arrow_width) - 2 * BTN_DROP_SHADOW, + rect.mTop, rect.mRight, rect.mBottom)); + mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT); + + return result; +} diff --git a/indra/llui/llflyoutbutton.h b/indra/llui/llflyoutbutton.h index 73190fc984..81cdcdc382 100644 --- a/indra/llui/llflyoutbutton.h +++ b/indra/llui/llflyoutbutton.h @@ -56,6 +56,8 @@ protected: public: void draw() override; + bool postBuild() override; + void setToggleState(bool state); void onActionButtonClick(const LLSD& data); diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 0d7c98294f..ce0e8036e7 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -32,11 +32,11 @@ const F32 FOCUS_FADE_TIME = 0.3f; -LLFocusableElement::LLFocusableElement() -: mFocusLostCallback(NULL), - mFocusReceivedCallback(NULL), - mFocusChangedCallback(NULL), - mTopLostCallback(NULL) +LLFocusableElement::LLFocusableElement() : + mFocusLostCallback(nullptr), + mFocusReceivedCallback(nullptr), + mFocusChangedCallback(nullptr), + mTopLostCallback(nullptr) { } @@ -464,6 +464,11 @@ F32 LLFocusMgr::getFocusFlashAmt() const return clamp_rescale(mFocusFlashTimer.getElapsedTimeF32(), 0.f, FOCUS_FADE_TIME, 1.f, 0.f); } +S32 LLFocusMgr::getFocusFlashWidth() const +{ + return ll_round(lerp(1.f, 2.f, getFocusFlashAmt())); +} + LLColor4 LLFocusMgr::getFocusColor() const { static LLUIColor focus_color_cached = LLUIColorTable::instance().getColor("FocusColor"); diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index 89fee5c9f1..2e2293196b 100644 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -101,7 +101,7 @@ public: void setKeystrokesOnly(bool keystrokes_only) { mKeystrokesOnly = keystrokes_only; } F32 getFocusFlashAmt() const; - S32 getFocusFlashWidth() const { return ll_round(lerp(1.f, 3.f, getFocusFlashAmt())); } + S32 getFocusFlashWidth() const; LLColor4 getFocusColor() const; void triggerFocusFlash(); bool getAppHasFocus() const { return mAppHasFocus; } diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index b664065532..baab17830b 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -651,9 +651,11 @@ bool LLFolderView::startDrag() void LLFolderView::commitRename( const LLSD& data ) { + // Intentionally doesn't check mRenamer->getVisible(), + // since this can be called from 'focus lost' event. + // Ex: clicking inworld should commit the rename. finishRenamingItem(); arrange( NULL, NULL ); - } void LLFolderView::draw() @@ -727,13 +729,17 @@ void LLFolderView::draw() void LLFolderView::finishRenamingItem( void ) { - if(!mRenamer) + if (!mRenamer) { return; } if( mRenameItem ) { mRenameItem->rename( mRenamer->getText() ); + // Clear text to avoid duplicate renames. + // Ex: 'return' key will trigger rename from handleKeyHere + // and might trigger commitRename from focus lost event. + mRenamer->setText(LLStringUtil::null); } closeRenamer(); @@ -1423,6 +1429,7 @@ bool LLFolderView::search(LLFolderViewItem* first_item, const std::string &searc } } + // Note: for inventory getSearchableName should already be 'upper' case. std::string current_item_label(search_item->getViewModelItem()->getSearchableName()); LLStringUtil::toUpper(current_item_label); auto search_string_length = llmin(upper_case_string.size(), current_item_label.size()); @@ -1510,6 +1517,7 @@ bool LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask ) && ( count > 0 && (hasVisibleChildren()) ))) && // show menu only if selected items are visible !hide_folder_menu) { + LL_INFOS("Inventory") << "Opening inventory menu from path: " << getPathname() << LL_ENDL; if (mCallbackRegistrar) { mCallbackRegistrar->pushScope(); diff --git a/indra/llui/llfolderview.h b/indra/llui/llfolderview.h index bdce9dec54..368a86ea84 100644 --- a/indra/llui/llfolderview.h +++ b/indra/llui/llfolderview.h @@ -221,6 +221,7 @@ public: void scrollToShowSelection(); void scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect); void setScrollContainer( LLScrollContainer* parent ) { mScrollContainer = parent; } + LLScrollContainer* getScrollContainer() { return mScrollContainer; } LLRect getVisibleRect(); bool search(LLFolderViewItem* first_item, const std::string &search_string, bool backward); diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 6d0cfcba95..7c51574fc2 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. +* Copyright (C) 2026, 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 @@ -28,14 +28,16 @@ #include "llflashtimer.h" #include "linden_common.h" +#include "llapp.h" #include "llfolderviewitem.h" #include "llfolderview.h" #include "llfolderviewmodel.h" -#include "llpanel.h" #include "llcallbacklist.h" #include "llcriticaldamp.h" #include "llclipboard.h" #include "llfocusmgr.h" // gFocusMgr +#include "llnotificationsutil.h" +#include "llpanel.h" #include "lltrans.h" #include "llwindow.h" @@ -60,7 +62,11 @@ LLUIColor LLFolderViewItem::sSearchStatusColor; S32 LLFolderViewItem::sTopPad = 0; LLUIImagePtr LLFolderViewItem::sFolderArrowImg; LLUIImagePtr LLFolderViewItem::sSelectionImg; +LLUIImagePtr LLFolderViewItem::sFavoriteImg; +LLUIImagePtr LLFolderViewItem::sFavoriteContentImg; LLFontGL* LLFolderViewItem::sSuffixFont = nullptr; +LLUIColor LLFolderViewItem::sFavoriteColor; +bool LLFolderViewItem::sColorSetInitialized = false; // only integers can be initialized in header const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f; @@ -68,6 +74,9 @@ const F32 LLFolderViewItem::FOLDER_OPEN_TIME_CONSTANT = 0.03f; const LLColor4U DEFAULT_WHITE(255, 255, 255); +constexpr S32 FAVORITE_IMAGE_SIZE = 14; +constexpr S32 FAVORITE_IMAGE_PAD = 3; + //static LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style) @@ -102,6 +111,8 @@ void LLFolderViewItem::initClass() sTopPad = default_params.item_top_pad; sFolderArrowImg = default_params.folder_arrow_image; sSelectionImg = default_params.selection_image; + sFavoriteImg = default_params.favorite_image; + sFavoriteContentImg = default_params.favorite_content_image; sSuffixFont = getLabelFontForStyle(LLFontGL::NORMAL); sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); @@ -121,6 +132,8 @@ void LLFolderViewItem::cleanupClass() sFonts.clear(); sFolderArrowImg = nullptr; sSelectionImg = nullptr; + sFavoriteImg = nullptr; + sFavoriteContentImg = nullptr; sSuffixFont = nullptr; } @@ -129,13 +142,15 @@ void LLFolderViewItem::cleanupClass() LLFolderViewItem::Params::Params() : root(), listener(), + favorite_image("favorite_image"), + favorite_content_image("favorite_content_image"), folder_arrow_image("folder_arrow_image"), folder_indentation("folder_indentation"), selection_image("selection_image"), item_height("item_height"), item_top_pad("item_top_pad"), creation_date(), - allow_wear("allow_wear", true), + marketplace_item("marketplace_item", false), allow_drop("allow_drop", true), font_color("font_color"), font_highlight_color("font_highlight_color"), @@ -144,9 +159,11 @@ LLFolderViewItem::Params::Params() icon_width("icon_width", 0), text_pad("text_pad", 0), text_pad_right("text_pad_right", 0), + text_pad_top("text_pad_top", 1), single_folder_mode("single_folder_mode", false), double_click_override("double_click_override", false), arrow_size("arrow_size", 0), + arrow_pad_top("arrow_pad_top", 1), max_folder_item_overlap("max_folder_item_overlap", 0) { } @@ -155,6 +172,8 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) : LLView(p), mLabelWidth(0), mLabelWidthDirty(false), + mIsFavorite(false), + mHasFavorites(false), mSuffixNeedsRefresh(false), mLabelPaddingRight(DEFAULT_LABEL_PADDING_RIGHT), mParentFolder( NULL ), @@ -171,11 +190,11 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mItemHeight(p.item_height), mControlLabelRotation(0.f), mDragAndDropTarget(false), - mLabel(utf8str_to_wstring(p.name)), + mLabel(utf8str_to_wstring(p.name)), // will be immediately reset in postBuild() mRoot(p.root), mViewModelItem(p.listener), mIsMouseOverTitle(false), - mAllowWear(p.allow_wear), + mMarketplaceItem(p.marketplace_item), mAllowDrop(p.allow_drop), mFontColor(p.font_color), mFontHighlightColor(p.font_highlight_color), @@ -184,11 +203,28 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mIconWidth(p.icon_width), mTextPad(p.text_pad), mTextPadRight(p.text_pad_right), + mTextPadTop(p.text_pad_top), mArrowSize(p.arrow_size), + mArrowPadTop(p.arrow_pad_top), mSingleFolderMode(p.single_folder_mode), mMaxFolderItemOverlap(p.max_folder_item_overlap), mDoubleClickOverride(p.double_click_override) { + if (!sColorSetInitialized) + { + sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); + sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); + sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); + sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); + sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); + sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); + sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); + sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); + sFavoriteColor = LLUIColorTable::instance().getColor("InventoryFavoriteColor", DEFAULT_WHITE); + sColorSetInitialized = true; + } + if (mViewModelItem) { mViewModelItem->setFolderViewItem(this); @@ -198,7 +234,11 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) // Destroys the object LLFolderViewItem::~LLFolderViewItem() { - mViewModelItem = NULL; + if (mViewModelItem) + { + mViewModelItem->setFolderViewItem(nullptr); + mViewModelItem = nullptr; + } gFocusMgr.removeKeyboardFocusWithoutCallback(this); } @@ -208,20 +248,19 @@ bool LLFolderViewItem::postBuild() llassert(vmi); // not supposed to happen, if happens, find out why and fix if (vmi) { - // getDisplayName() is expensive (due to internal getLabelSuffix() and name building) - // it also sets search strings so it requires a filter reset + // First getDisplayName() is expensive due to internal + // lazy getLabelSuffix(), it is however needed as it sets + // search string, which can later determine visibility. + // Refreshing a search string also requires a filter reset. mLabel = utf8str_to_wstring(vmi->getDisplayName()); - setToolTip(vmi->getName()); + mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); // Dirty the filter flag of the model from the view (CHUI-849) vmi->dirtyFilter(); } - // Don't do full refresh on constructor if it is possible to avoid + // Don't do full refresh on constructor if it is possible to avoid, // it significantly slows down bulk view creation. - // Todo: Ideally we need to move getDisplayName() out of constructor as well. - // Like: make a logic that will let filter update search string, - // while LLFolderViewItem::arrange() updates visual part mSuffixNeedsRefresh = true; mLabelWidthDirty = true; return true; @@ -321,11 +360,16 @@ bool LLFolderViewItem::isPotentiallyVisible(S32 filter_generation) void LLFolderViewItem::refresh() { - LLFolderViewModelItem& vmi = *getViewModelItem(); + LLFolderViewModelItem* vmi_ptr = getViewModelItem(); + if (!vmi_ptr) + { + return; + } + LLFolderViewModelItem& vmi = *vmi_ptr; mLabel = utf8str_to_wstring(vmi.getDisplayName()); mLabelFontBuffer.reset(); - setToolTip(vmi.getName()); + mIsFavorite = vmi.isFavorite() && !vmi.isItemInTrash(); // icons are slightly expensive to get, can be optimized // see LLInventoryIcon::getIcon() mIcon = vmi.getIcon(); @@ -352,6 +396,10 @@ void LLFolderViewItem::refresh() void LLFolderViewItem::refreshSuffix() { LLFolderViewModelItem const* vmi = getViewModelItem(); + if (!vmi) + { + return; + } // icons are slightly expensive to get, can be optimized // see LLInventoryIcon::getIcon() @@ -359,6 +407,8 @@ void LLFolderViewItem::refreshSuffix() mIconOpen = vmi->getIconOpen(); mIconOverlay = vmi->getIconOverlay(); + mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); + if (mRoot->useLabelSuffix()) { // Very Expensive! @@ -428,6 +478,10 @@ S32 LLFolderViewItem::arrange( S32* width, S32* height ) } mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel.c_str()) + getLabelFontForStyle(LLFontGL::NORMAL)->getWidth(mLabelSuffix.c_str()) + mLabelPaddingRight; mLabelWidthDirty = false; + if (mIsFavorite) + { + mLabelWidth += FAVORITE_IMAGE_SIZE + FAVORITE_IMAGE_PAD; + } } *width = llmax(*width, mLabelWidth); @@ -554,10 +608,15 @@ void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags) void LLFolderViewItem::openItem( void ) { - if (mAllowWear || !getViewModelItem()->isItemWearable()) + if (!mMarketplaceItem || !getViewModelItem()->isItemWearable()) { getViewModelItem()->openItem(); } + else if (mMarketplaceItem) + { + // Wearing an object from any listing, active or not, is verbotten + LLNotificationsUtil::add("AlertMerchantListingCannotWear"); + } } void LLFolderViewItem::rename(const std::string& new_name) @@ -574,6 +633,19 @@ const std::string& LLFolderViewItem::getName( void ) const return getViewModelItem() ? getViewModelItem()->getName() : noName; } +const std::string LLFolderViewItem::getToolTip() const +{ + // Return the item name as tooltip without storing it + if (!LLView::sDebugUnicode) + { + if (const LLFolderViewModelItem* vmi = getViewModelItem()) + { + return vmi->getName(); + } + } + return LLView::getToolTip(); +} + // LLView functionality bool LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask ) { @@ -766,11 +838,50 @@ void LLFolderViewItem::drawOpenFolderArrow() if (hasVisibleChildren() || !isFolderComplete()) { gl_draw_scaled_rotated_image( - mIndentation, getRect().getHeight() - mArrowSize - mTextPad - sTopPad, + mIndentation, getRect().getHeight() - mArrowSize - mArrowPadTop - sTopPad, mArrowSize, mArrowSize, mControlLabelRotation, sFolderArrowImg->getImage(), sFgColor); } } +void LLFolderViewItem::drawFavoriteIcon() +{ + static LLUICachedControl<bool> draw_star("InventoryFavoritesUseStar", true); + static LLUICachedControl<bool> draw_hollow_star("InventoryFavoritesUseHollowStar", true); + + LLUIImage* favorite_image = nullptr; + if (draw_star && mIsFavorite) + { + favorite_image = sFavoriteImg; + } + else if (draw_hollow_star && mHasFavorites && !isOpen()) + { + favorite_image = sFavoriteContentImg; + } + + if (favorite_image) + { + S32 x_offset = 0; + LLScrollContainer* scroll = mRoot->getScrollContainer(); + if (scroll) + { + S32 width = scroll->getVisibleContentRect().getWidth(); + S32 offset = scroll->getDocPosHorizontal(); + x_offset = width + offset; + } + else + { + x_offset = getRect().getWidth(); + } + gl_draw_scaled_image( + x_offset - FAVORITE_IMAGE_SIZE - FAVORITE_IMAGE_PAD, + getRect().getHeight() - mItemHeight + FAVORITE_IMAGE_PAD, + FAVORITE_IMAGE_SIZE, + FAVORITE_IMAGE_SIZE, + favorite_image->getImage(), + sFgColor); + } +} + /*virtual*/ bool LLFolderViewItem::isHighlightAllowed() { return mIsSelected; @@ -928,6 +1039,7 @@ void LLFolderViewItem::draw() { drawOpenFolderArrow(); } + drawFavoriteIcon(); drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); @@ -960,7 +1072,7 @@ void LLFolderViewItem::draw() S32 filter_string_length = mViewModelItem->hasFilterStringMatch() ? (S32)mViewModelItem->getFilterStringSize() : 0; F32 right_x = 0; - F32 y = (F32)rect_height - line_height - (F32)mTextPad - (F32)sTopPad; + F32 y = (F32)rect_height - line_height - (F32)mTextPadTop - (F32)sTopPad; F32 text_left = (F32)getLabelXPos(); LLWString combined_string = mLabel + mLabelSuffix; @@ -999,7 +1111,20 @@ void LLFolderViewItem::draw() } } - LLColor4 color = (mIsSelected && filled) ? mFontHighlightColor : mFontColor; + static LLUICachedControl<bool> highlight_color("InventoryFavoritesColorText", true); + LLColor4 color; + if (mIsSelected && filled) + { + color = mFontHighlightColor; + } + else if (mIsFavorite && highlight_color) + { + color = sFavoriteColor; + } + else + { + color = mFontColor; + } if (isFadeItem()) { @@ -1026,7 +1151,7 @@ void LLFolderViewItem::draw() if(mLabelSuffix.empty() || (font == sSuffixFont)) { F32 match_string_left = text_left + font->getWidthF32(combined_string.c_str(), 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string.c_str(), filter_offset, filter_string_length); - F32 yy = (F32)rect_height - line_height - (F32)mTextPad - (F32)sTopPad; + F32 yy = (F32)rect_height - line_height - (F32)mTextPadTop - (F32)sTopPad; font->render(combined_string, filter_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, filter_string_length, S32_MAX, &right_x); @@ -1037,7 +1162,7 @@ void LLFolderViewItem::draw() if(label_filter_length > 0) { F32 match_string_left = text_left + font->getWidthF32(mLabel.c_str(), 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel.c_str(), filter_offset, label_filter_length); - F32 yy = (F32)rect_height - line_height - (F32)mTextPad - (F32)sTopPad; + F32 yy = (F32)rect_height - line_height - (F32)mTextPadTop - (F32)sTopPad; font->render(mLabel, filter_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, label_filter_length, S32_MAX, &right_x); @@ -1048,7 +1173,7 @@ void LLFolderViewItem::draw() { S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); F32 match_string_left = text_left + font->getWidthF32(mLabel.c_str(), 0, static_cast<S32>(mLabel.size())) + sSuffixFont->getWidthF32(mLabelSuffix.c_str(), 0, suffix_offset + suffix_filter_length) - sSuffixFont->getWidthF32(mLabelSuffix.c_str(), suffix_offset, suffix_filter_length); - F32 yy = (F32)rect_height - sSuffixFont->getLineHeight() - (F32)mTextPad - (F32)sTopPad; + F32 yy = (F32)rect_height - sSuffixFont->getLineHeight() - (F32)mTextPadTop - (F32)sTopPad; sSuffixFont->render(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, suffix_filter_length, S32_MAX, &right_x); @@ -1093,7 +1218,8 @@ LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): mIsFolderComplete(false), // folder might have children that are not loaded yet. mAreChildrenInited(false), // folder might have children that are not built yet. mLastArrangeGeneration( -1 ), - mLastCalculatedWidth(0) + mLastCalculatedWidth(0), + mFavoritesDirtyFlags(0) { } @@ -1119,6 +1245,11 @@ LLFolderViewFolder::~LLFolderViewFolder( void ) // The LLView base class takes care of object destruction. make sure that we // don't have mouse or keyboard focus gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + + if (mFavoritesDirtyFlags) + { + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, this); + } } // addToFolder() returns true if it succeeds. false otherwise @@ -1762,6 +1893,153 @@ bool LLFolderViewFolder::isMovable() return true; } +void LLFolderViewFolder::updateHasFavorites(bool new_childs_value) +{ + if (mFavoritesDirtyFlags == 0) + { + gIdleCallbacks.addFunction(&LLFolderViewFolder::onIdleUpdateFavorites, this); + } + if (new_childs_value) + { + mFavoritesDirtyFlags |= FAVORITE_ADDED; + } + else + { + mFavoritesDirtyFlags |= FAVORITE_REMOVED; + } +} + +void LLFolderViewFolder::onIdleUpdateFavorites(void* data) +{ + LLFolderViewFolder* self = reinterpret_cast<LLFolderViewFolder*>(data); + if (gDisconnected || !self) + { + return; + } + + if (self->mFavoritesDirtyFlags == FAVORITE_CLEANUP) + { + // parent or child already processed the update, clean the callback + self->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, data); + return; + } + + if (self->mFavoritesDirtyFlags == 0) + { + llassert(false); // should not happen, everything that sets to 0 should clean callback + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, data); + return; + } + + if (self->getViewModelItem()->isItemInTrash()) + { + // do not display favorite-stars in trash + self->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + return; + } + + if (self->mFavoritesDirtyFlags == FAVORITE_ADDED) + { + if (!self->mHasFavorites) + { + // propagate up, exclude root + LLFolderViewFolder* parent = self; + while (parent + && (!parent->hasFavorites() || parent->mFavoritesDirtyFlags) + && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + parent->setHasFavorites(true); + if (parent->mFavoritesDirtyFlags) + { + // Parent will remove onIdleUpdateFavorites later, don't remove now, + // We are inside gIdleCallbacks. Removing 'self' callback is safe, + // but removing 'parent' can invalidate following iterator + parent->mFavoritesDirtyFlags = FAVORITE_CLEANUP; + } + parent = parent->getParentFolder(); + } + } + else + { + // already up to date + self->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + } + } + else if (self->mFavoritesDirtyFlags > FAVORITE_ADDED) + { + // full check + LLFolderViewFolder* parent = self; + while (parent && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + bool has_favorites = false; + for (items_t::iterator iter = parent->mItems.begin(); + iter != parent->mItems.end();) + { + items_t::iterator iit = iter++; + if ((*iit)->isFavorite()) + { + has_favorites = true; + break; + } + } + + for (folders_t::iterator iter = parent->mFolders.begin(); + iter != parent->mFolders.end() && !has_favorites;) + { + folders_t::iterator fit = iter++; + if ((*fit)->isFavorite() || (*fit)->hasFavorites()) + { + has_favorites = true; + break; + } + } + + if (!has_favorites) + { + if (parent->hasFavorites()) + { + parent->setHasFavorites(false); + } + else + { + // Nothing changed + break; + } + } + else + { + // propagate up, exclude root + while (parent + && (!parent->hasFavorites() || parent->mFavoritesDirtyFlags) + && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + parent->setHasFavorites(true); + if (parent->mFavoritesDirtyFlags) + { + // Parent will remove onIdleUpdateFavorites later, don't remove now, + // We are inside gIdleCallbacks. Removing 'self' callback is safe, + // but removing 'parent' can invalidate following iterator + parent->mFavoritesDirtyFlags = FAVORITE_CLEANUP; + } + parent = parent->getParentFolder(); + } + break; + } + if (parent->mFavoritesDirtyFlags) + { + // Parent will remove onIdleUpdateFavorites later, don't remove now. + // We are inside gIdleCallbacks. Removing 'self' callback is safe, + // but removing 'parent' can invalidate following iterator + parent->mFavoritesDirtyFlags = FAVORITE_CLEANUP; + } + parent = parent->getParentFolder(); + } + } +} + bool LLFolderViewFolder::isRemovable() { @@ -1869,10 +2147,14 @@ void LLFolderViewFolder::setOpen(bool openitem) { // navigateToFolder can destroy this view // delay it in case setOpen was called from click or key processing - doOnIdleOneTime([this]() - { - getViewModelItem()->navigateToFolder(); - }); + LLPointer<LLFolderViewModelItem> view_model_item = mViewModelItem; + doOnIdleOneTime([view_model_item]() + { + if (view_model_item.notNull()) + { + view_model_item.get()->navigateToFolder(); + } + }); } else { @@ -2110,9 +2392,10 @@ bool LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) { // navigating is going to destroy views and change children // delay it untill handleDoubleClick processing is complete - doOnIdleOneTime([this]() - { - getViewModelItem()->navigateToFolder(false); + LLPointer<LLFolderViewModelItem> view_model_item = getViewModelItem(); + doOnIdleOneTime([view_model_item]() mutable + {; + view_model_item->navigateToFolder(false); }); } return true; diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 2ee018a90a..8d6de2fee3 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. +* Copyright (C) 2026, 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 @@ -50,7 +50,9 @@ class LLFolderViewItem : public LLView public: struct Params : public LLInitParam::Block<Params, LLView::Params> { - Optional<LLUIImage*> folder_arrow_image, + Optional<LLUIImage*> favorite_image, + favorite_content_image, + folder_arrow_image, selection_image; Mandatory<LLFolderView*> root; Mandatory<LLFolderViewModelItem*> listener; @@ -60,7 +62,7 @@ public: item_top_pad; Optional<time_t> creation_date; - Optional<bool> allow_wear; + Optional<bool> marketplace_item; Optional<bool> allow_drop; Optional<LLUIColor> font_color; @@ -71,7 +73,9 @@ public: icon_width, text_pad, text_pad_right, + text_pad_top, arrow_size, + arrow_pad_top, max_folder_item_overlap; Optional<bool> single_folder_mode, double_click_override; @@ -93,6 +97,8 @@ protected: LLWString mLabel; S32 mLabelWidth; bool mLabelWidthDirty; + bool mIsFavorite; + bool mHasFavorites; S32 mLabelPaddingRight; LLFolderViewFolder* mParentFolder; LLPointer<LLFolderViewModelItem> mViewModelItem; @@ -113,7 +119,9 @@ protected: mIconWidth, mTextPad, mTextPadRight, + mTextPadTop, mArrowSize, + mArrowPadTop, mMaxFolderItemOverlap; F32 mControlLabelRotation; @@ -122,7 +130,7 @@ protected: mIsCurSelection, mDragAndDropTarget, mIsMouseOverTitle, - mAllowWear, + mMarketplaceItem, mAllowDrop, mSingleFolderMode, mDoubleClickOverride, @@ -133,6 +141,7 @@ protected: LLUIColor mFontColor; LLUIColor mFontHighlightColor; + static bool sColorSetInitialized; // For now assuming all colors are the same in derived classes. static LLUIColor sFgColor; @@ -145,6 +154,8 @@ protected: static LLUIColor sFilterTextColor; static LLUIColor sSuffixColor; static LLUIColor sSearchStatusColor; + static LLUIColor sFavoriteColor; + // this is an internal method used for adding items to folders. A // no-op at this level, but reimplemented in derived classes. @@ -208,6 +219,8 @@ public: // Returns true is this object and all of its children can be moved virtual bool isMovable(); + bool isFavorite() const { return mIsFavorite; } + // destroys this item recursively virtual void destroyView(); @@ -240,6 +253,11 @@ public: // viewed. This method will ask the viewed object itself. const std::string& getName( void ) const; + // Override to provide lazy tooltip generation without memory overhead + // Inventory can consist of millions of items, yet most stay invisible, + // much less need to show a tooltip, so avoid storing tooltips. + virtual const std::string getToolTip() const; + // This method returns the label displayed on the view. This // method was primarily added to allow sorting on the folder // contents possible before the entire view has been constructed. @@ -298,6 +316,7 @@ public: // virtual void handleDropped(); virtual void draw(); void drawOpenFolderArrow(); + void drawFavoriteIcon(); void drawHighlight(bool showContent, bool hasKeyboardFocus, const LLUIColor& selectColor, const LLUIColor& flashColor, const LLUIColor& outlineColor, const LLUIColor& mouseOverColor); void drawLabel(const LLFontGL* font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x); virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, @@ -311,6 +330,8 @@ private: static S32 sTopPad; static LLUIImagePtr sFolderArrowImg; static LLUIImagePtr sSelectionImg; + static LLUIImagePtr sFavoriteImg; + static LLUIImagePtr sFavoriteContentImg; static LLFontGL* sSuffixFont; LLFontVertexBuffer mLabelFontBuffer; @@ -400,6 +421,19 @@ public: // Returns true is this object and all of its children can be moved virtual bool isMovable(); + bool isFavorite() const { return mIsFavorite; } + bool hasFavorites() const { return mHasFavorites; } + void setHasFavorites(bool val) { mHasFavorites = val; } + void updateHasFavorites(bool new_childs_value); +private: + static void onIdleUpdateFavorites(void* data); + + constexpr static S32 FAVORITE_ADDED = 1; + constexpr static S32 FAVORITE_REMOVED = 2; + constexpr static S32 FAVORITE_CLEANUP = 4; + S32 mFavoritesDirtyFlags { 0 }; +public: + // destroys this folder, and all children virtual void destroyView(); void destroyRoot(); diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index 9372818ca5..07e8b890b8 100644 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -162,6 +162,7 @@ public: virtual void navigateToFolder(bool new_window = false, bool change_mode = false) = 0; + virtual bool isFavorite() const = 0; virtual bool isItemWearable() const { return false; } virtual bool isItemRenameable() const = 0; @@ -170,7 +171,8 @@ public: virtual bool isItemMovable( void ) const = 0; // Can be moved to another folder virtual void move( LLFolderViewModelItem* parent_listener ) = 0; - virtual bool isItemRemovable( bool check_worn = true ) const = 0; // Can be destroyed + virtual bool isItemRemovable( bool check_worn = true) const = 0; // Can be destroyed + virtual bool isItemInTrash(void) const = 0; virtual bool removeItem() = 0; virtual void removeBatch(std::vector<LLFolderViewModelItem*>& batch) = 0; @@ -183,6 +185,9 @@ public: virtual void pasteFromClipboard() = 0; virtual void pasteLinkFromClipboard() = 0; + virtual bool isAgentInventory() const = 0; + virtual bool isAgentInventoryRoot() const = 0; + virtual void buildContextMenu(LLMenuGL& menu, U32 flags) = 0; virtual bool potentiallyVisible() = 0; // is the item definitely visible or we haven't made up our minds yet? @@ -219,6 +224,7 @@ public: virtual S32 getSortVersion() = 0; virtual void setSortVersion(S32 version) = 0; virtual void setParent(LLFolderViewModelItem* parent) = 0; + virtual const LLFolderViewModelItem* getParent() = 0; virtual bool hasParent() = 0; protected: @@ -249,14 +255,14 @@ public: mChildren.clear(); } - void requestSort() { mSortVersion = -1; } - S32 getSortVersion() { return mSortVersion; } - void setSortVersion(S32 version) { mSortVersion = version;} + void requestSort() override { mSortVersion = -1; } + S32 getSortVersion() override { return mSortVersion; } + void setSortVersion(S32 version) override { mSortVersion = version;} - S32 getLastFilterGeneration() const { return mLastFilterGeneration; } + S32 getLastFilterGeneration() const override { return mLastFilterGeneration; } S32 getLastFolderFilterGeneration() const { return mLastFolderFilterGeneration; } - S32 getMarkedDirtyGeneration() const { return mMarkedDirtyGeneration; } - void dirtyFilter() + S32 getMarkedDirtyGeneration() const override { return mMarkedDirtyGeneration; } + void dirtyFilter() override { if(mMarkedDirtyGeneration < 0) { @@ -271,7 +277,7 @@ public: mParent->dirtyFilter(); } } - void dirtyDescendantsFilter() + void dirtyDescendantsFilter() override { mMostFilteredDescendantGeneration = -1; if (mParent) @@ -279,13 +285,13 @@ public: mParent->dirtyDescendantsFilter(); } } - bool hasFilterStringMatch(); - std::string::size_type getFilterStringOffset(); - std::string::size_type getFilterStringSize(); + bool hasFilterStringMatch() override; + std::string::size_type getFilterStringOffset() override; + std::string::size_type getFilterStringSize() override; - typedef std::list<LLFolderViewModelItem*> child_list_t; + typedef std::list<LLPointer<LLFolderViewModelItem> > child_list_t; - virtual void addChild(LLFolderViewModelItem* child) + virtual void addChild(LLFolderViewModelItem* child) override { mChildren.push_back(child); child->setParent(this); @@ -293,15 +299,15 @@ public: requestSort(); } - virtual void removeChild(LLFolderViewModelItem* child) + virtual void removeChild(LLFolderViewModelItem* child) override final { - mChildren.remove(child); child->setParent(NULL); + mChildren.remove(child); dirtyDescendantsFilter(); dirtyFilter(); } - virtual void clearChildren() + virtual void clearChildren() override { // We are working with models that belong to views as LLPointers, clean the list, let poiters handle the rest std::for_each(mChildren.begin(), mChildren.end(), [](LLFolderViewModelItem* c) {c->setParent(NULL); }); @@ -314,7 +320,7 @@ public: child_list_t::const_iterator getChildrenEnd() const { return mChildren.end(); } child_list_t::size_type getChildrenCount() const { return mChildren.size(); } - void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) + void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) override { mPassedFilter = passed; mLastFilterGeneration = filter_generation; @@ -323,20 +329,20 @@ public: mMarkedDirtyGeneration = -1; } - void setPassedFolderFilter(bool passed, S32 filter_generation) + void setPassedFolderFilter(bool passed, S32 filter_generation) override { mPassedFolderFilter = passed; mLastFolderFilterGeneration = filter_generation; } - virtual bool potentiallyVisible() + virtual bool potentiallyVisible() override { return passedFilter() // we've passed the filter || (getLastFilterGeneration() < mRootViewModel.getFilter().getFirstSuccessGeneration()) // or we don't know yet || descendantsPassedFilter(); } - virtual bool passedFilter(S32 filter_generation = -1) + virtual bool passedFilter(S32 filter_generation = -1) override { if (filter_generation < 0) { @@ -347,7 +353,7 @@ public: return passed_folder_filter && (passed_filter || descendantsPassedFilter(filter_generation)); } - virtual bool descendantsPassedFilter(S32 filter_generation = -1) + virtual bool descendantsPassedFilter(S32 filter_generation = -1) override { if (filter_generation < 0) { @@ -356,10 +362,10 @@ public: return mMostFilteredDescendantGeneration >= filter_generation; } - protected: - virtual void setParent(LLFolderViewModelItem* parent) { mParent = parent; } - virtual bool hasParent() { return mParent != NULL; } + virtual void setParent(LLFolderViewModelItem* parent) override final { mParent = parent; } + virtual const LLFolderViewModelItem* getParent() override { return mParent; }; + virtual bool hasParent() override { return mParent != NULL; } S32 mSortVersion; bool mPassedFilter; @@ -376,7 +382,7 @@ protected: LLFolderViewModelItem* mParent; LLFolderViewModelInterface& mRootViewModel; - void setFolderViewItem(LLFolderViewItem* folder_view_item) { mFolderViewItem = folder_view_item;} + void setFolderViewItem(LLFolderViewItem* folder_view_item) override { mFolderViewItem = folder_view_item;} LLFolderViewItem* mFolderViewItem; }; @@ -390,15 +396,15 @@ public: mFolderView(NULL) {} - virtual void requestSortAll() + virtual void requestSortAll() override { // sort everything mTargetSortVersion++; } - virtual std::string getStatusText(bool is_empty_folder = false); - virtual void filter(); + virtual std::string getStatusText(bool is_empty_folder = false) override; + virtual void filter() override; - void setFolderView(LLFolderView* folder_view) { mFolderView = folder_view;} + void setFolderView(LLFolderView* folder_view) override { mFolderView = folder_view;} protected: bool needsSort(class LLFolderViewModelItem* item); @@ -426,16 +432,16 @@ public: virtual SortType& getSorter() { return *mSorter; } virtual const SortType& getSorter() const { return *mSorter; } - virtual void setSorter(const SortType& sorter) { mSorter.reset(new SortType(sorter)); requestSortAll(); } + virtual void setSorter(const SortType& sorter) { mSorter = std::make_unique<SortType>(sorter); requestSortAll(); } - virtual FilterType& getFilter() { return *mFilter; } - virtual const FilterType& getFilter() const { return *mFilter; } - virtual void setFilter(const FilterType& filter) { mFilter.reset(new FilterType(filter)); } + virtual FilterType& getFilter() override { return *mFilter; } + virtual const FilterType& getFilter() const override { return *mFilter; } + virtual void setFilter(const FilterType& filter) { mFilter = std::make_unique<FilterType>(filter); } // By default, we assume the content is available. If a network fetch mechanism is implemented for the model, // this method needs to be overloaded and return the relevant fetch status. - virtual bool contentsReady() { return true; } - virtual bool isFolderComplete(LLFolderViewFolder* folder) { return true; } + virtual bool contentsReady() override { return true; } + virtual bool isFolderComplete(LLFolderViewFolder* folder) override { return true; } struct ViewModelCompare { @@ -456,7 +462,7 @@ public: const SortType& mSorter; }; - void sort(LLFolderViewFolder* folder) + void sort(LLFolderViewFolder* folder) override { if (needsSort(folder->getViewModelItem())) { diff --git a/indra/llui/llfunctorregistry.h b/indra/llui/llfunctorregistry.h index da5570d922..40a3e439a6 100644 --- a/indra/llui/llfunctorregistry.h +++ b/indra/llui/llfunctorregistry.h @@ -29,10 +29,9 @@ #define LL_LLFUNCTORREGISTRY_H #include <string> -#include <map> - -#include <boost/function.hpp> +#include <unordered_map> +#include "llstring.h" #include "llsd.h" #include "llsingleton.h" @@ -58,7 +57,7 @@ class LLFunctorRegistry : public LLSingleton<LLFunctorRegistry<FUNCTOR_TYPE> > public: typedef FUNCTOR_TYPE ResponseFunctor; - typedef typename std::map<std::string, FUNCTOR_TYPE> FunctorMap; + typedef typename std::unordered_map<std::string, FUNCTOR_TYPE, ll::string_hash, std::equal_to<>> FunctorMap; bool registerFunctor(const std::string& name, ResponseFunctor f) { diff --git a/indra/llui/llgestureautocompletehelper.cpp b/indra/llui/llgestureautocompletehelper.cpp new file mode 100644 index 0000000000..f00f1406e5 --- /dev/null +++ b/indra/llui/llgestureautocompletehelper.cpp @@ -0,0 +1,165 @@ +/** + * @file llgestureautocompletehelper.cpp + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2026, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llgestureautocompletehelper.h" + +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "lluictrl.h" + +constexpr char GESTURE_AUTOCOMPLETE_FLOATER[] = "gesture_autocomplete_picker"; + +bool LLGestureAutocompleteHelper::isActive(const LLUICtrl* ctrl) const +{ + return mHostHandle.get() == ctrl; +} + +void LLGestureAutocompleteHelper::showHelper( + LLUICtrl* host_ctrl, + const std::vector<Row>& rows, + size_t total, + std::function<void(std::string)> commit_cb) +{ + if (mHelperHandle.isDead()) + { + LLFloater* helper_floater = LLFloaterReg::getInstance(GESTURE_AUTOCOMPLETE_FLOATER); + mHelperHandle = helper_floater->getHandle(); + mHelperCommitConn = helper_floater->setCommitCallback( + [this](LLUICtrl*, const LLSD& param) { onCommitGesture(param.asString()); }); + } + + setHostCtrl(host_ctrl); + mRows = rows; + mTotal = total; + mGestureCommitCb = commit_cb; + + S32 floater_x, floater_y; + LLRect host_rect = host_ctrl->getRect(); + if (!host_ctrl->localPointToOtherView(0, host_rect.getHeight(), &floater_x, &floater_y, gFloaterView)) + { + LL_WARNS() << "Cannot show gesture autocomplete helper for non-floater controls." << LL_ENDL; + return; + } + + LLFloater* helper_floater = mHelperHandle.get(); + LLRect rect = helper_floater->getRect(); + rect.setLeftTopAndSize(floater_x, floater_y + rect.getHeight(), rect.getWidth(), rect.getHeight()); + helper_floater->setRect(rect); + + refreshPicker(); +} + +void LLGestureAutocompleteHelper::hideHelper(const LLUICtrl* ctrl) +{ + if (ctrl && !isActive(ctrl)) + { + return; + } + + setHostCtrl(nullptr); +} + +bool LLGestureAutocompleteHelper::handleKey(const LLUICtrl* ctrl, KEY key, MASK mask) +{ + if (mHelperHandle.isDead() || !isActive(ctrl)) + { + return false; + } + + return mHelperHandle.get()->handleKey(key, mask, true); +} + +void LLGestureAutocompleteHelper::onCommitGesture(const std::string& trigger) +{ + if (!mHostHandle.isDead() && mGestureCommitCb) + { + mGestureCommitCb(trigger); + } + + hideHelper(getHostCtrl()); +} + +void LLGestureAutocompleteHelper::refreshPicker() +{ + if (mHelperHandle.isDead()) + { + return; + } + + LLFloater* helper_floater = mHelperHandle.get(); + + if (helper_floater->isShown()) + { + helper_floater->onOpen(LLSD()); + } + else + { + helper_floater->openFloater(LLSD()); + } +} + +void LLGestureAutocompleteHelper::setHostCtrl(LLUICtrl* host_ctrl) +{ + const LLUICtrl* cur_host_ctrl = mHostHandle.get(); + + if (cur_host_ctrl != host_ctrl) + { + mHostCtrlFocusLostConn.disconnect(); + mHostHandle.markDead(); + mGestureCommitCb = {}; + mRows.clear(); + mTotal = 0; + + if (!mHelperHandle.isDead()) + { + mHelperHandle.get()->closeFloater(); + } + + if (host_ctrl) + { + mHostHandle = host_ctrl->getHandle(); + mHostCtrlFocusLostConn = host_ctrl->setFocusLostCallback( + [this](auto*) + { + // Scroll list grabs focus on click. + // Keep focus on the host when the click was ours. + LLFloater* helper_floater = mHelperHandle.get(); + if (helper_floater && gFocusMgr.childHasKeyboardFocus(helper_floater)) + { + if (LLUICtrl* host = getHostCtrl()) + { + host->setFocus(true); + } + return; + } + + hideHelper(getHostCtrl()); + }); + } + } +} diff --git a/indra/llui/llgestureautocompletehelper.h b/indra/llui/llgestureautocompletehelper.h new file mode 100644 index 0000000000..6292655fc7 --- /dev/null +++ b/indra/llui/llgestureautocompletehelper.h @@ -0,0 +1,80 @@ +/** + * @file llgestureautocompletehelper.h + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2026, 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$ + */ + +#pragma once + +#include "llhandle.h" +#include "llsingleton.h" + +#include <boost/signals2.hpp> +#include <functional> +#include <string> +#include <vector> + +class LLFloater; +class LLUICtrl; + +class LLGestureAutocompleteHelper : public LLSingleton<LLGestureAutocompleteHelper> +{ + LLSINGLETON(LLGestureAutocompleteHelper) {} + ~LLGestureAutocompleteHelper() override {} + +public: + struct Row + { + std::string value; + std::string trigger; + std::string name; + }; + + bool isActive(const LLUICtrl* ctrl) const; + void showHelper( + LLUICtrl* host_ctrl, + const std::vector<Row>& rows, + size_t total, + std::function<void(std::string)> commit_cb); + void hideHelper(const LLUICtrl* ctrl = nullptr); + bool handleKey(const LLUICtrl* ctrl, KEY key, MASK mask); + void onCommitGesture(const std::string& trigger); + + const std::vector<Row>& rows() const { return mRows; } + size_t total() const { return mTotal; } + +protected: + void setHostCtrl(LLUICtrl* host_ctrl); + LLUICtrl* getHostCtrl() const { return mHostHandle.get(); } + +private: + void refreshPicker(); + + LLHandle<LLUICtrl> mHostHandle; + LLHandle<LLFloater> mHelperHandle; + boost::signals2::connection mHostCtrlFocusLostConn; + boost::signals2::connection mHelperCommitConn; + std::function<void(std::string)> mGestureCommitCb; + + std::vector<Row> mRows; + size_t mTotal = 0; +}; diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index 2bea8fb4ed..a728f66000 100644 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -33,38 +33,20 @@ #include "llsdserialize.h" #include "lltexteditor.h" #include "llstl.h" +#include "llcontrol.h" + +extern LLControlGroup gSavedSettings; inline bool LLKeywordToken::isHead(const llwchar* s) const { - // strncmp is much faster than string compare - bool res = true; - const llwchar* t = mToken.c_str(); - auto len = mToken.size(); - for (S32 i=0; i<len; i++) - { - if (s[i] != t[i]) - { - res = false; - break; - } - } - return res; + size_t bytes = mToken.size() * sizeof(llwchar); + return std::memcmp(s, mToken.c_str(), bytes) == 0; } inline bool LLKeywordToken::isTail(const llwchar* s) const { - bool res = true; - const llwchar* t = mDelimiter.c_str(); - auto len = mDelimiter.size(); - for (S32 i=0; i<len; i++) - { - if (s[i] != t[i]) - { - res = false; - break; - } - } - return res; + size_t len_bytes = mDelimiter.size() * sizeof(llwchar); + return std::memcmp(s, mDelimiter.c_str(), len_bytes) == 0; } LLKeywords::LLKeywords() @@ -80,6 +62,8 @@ LLKeywords::~LLKeywords() mLineTokenList.clear(); std::for_each(mDelimiterTokenList.begin(), mDelimiterTokenList.end(), DeletePointer()); mDelimiterTokenList.clear(); + mLineTokenByFirstChar.clear(); + mDelimiterTokenByFirstChar.clear(); } // Add the token as described @@ -114,14 +98,29 @@ void LLKeywords::addToken(LLKeywordToken::ETokenType type, break; case LLKeywordToken::TT_LINE: - mLineTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip, LLWStringUtil::null)); + { + LLKeywordToken* token = new LLKeywordToken(type, color, key, tool_tip, LLWStringUtil::null); + mLineTokenList.push_front(token); + if (!key.empty()) + { + mLineTokenByFirstChar[key[0]].push_front(token); + } break; + } case LLKeywordToken::TT_TWO_SIDED_DELIMITER: case LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS: case LLKeywordToken::TT_ONE_SIDED_DELIMITER: - mDelimiterTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip, delimiter)); + case LLKeywordToken::TT_LONG_BRACKET: + { + LLKeywordToken* token = new LLKeywordToken(type, color, key, tool_tip, delimiter); + mDelimiterTokenList.push_front(token); + if (!key.empty()) + { + mDelimiterTokenByFirstChar[key[0]].push_front(token); + } break; + } default: llassert(0); @@ -202,12 +201,7 @@ LLUIColor LLKeywords::getColorGroup(std::string_view key_in) const color_group = "SyntaxLslGodMode"; } else if (key_in == "constants" - || key_in == "constants-integer" - || key_in == "constants-float" - || key_in == "constants-string" - || key_in == "constants-key" - || key_in == "constants-rotation" - || key_in == "constants-vector") + || key_in.compare(0, 10, "constants-") == 0) { color_group = "SyntaxLslConstant"; } @@ -219,9 +213,10 @@ LLUIColor LLKeywords::getColorGroup(std::string_view key_in) const return LLUIColorTable::instance().getColor(color_group); } -void LLKeywords::initialize(LLSD SyntaxXML) +void LLKeywords::initialize(LLSD SyntaxXML, bool luau_language) { mSyntax = SyntaxXML; + mLuauLanguage = luau_language; mLoaded = true; } @@ -234,13 +229,30 @@ void LLKeywords::processTokens() // Add 'standard' stuff: Quotes, Comments, Strings, Labels, etc. before processing the LLSD std::string delimiter; - addToken(LLKeywordToken::TT_LABEL, "@", getColorGroup("misc-flow-label"), "Label\nTarget for jump statement", delimiter ); - addToken(LLKeywordToken::TT_ONE_SIDED_DELIMITER, "//", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (single-line)\nNon-functional commentary or disabled code", delimiter ); - addToken(LLKeywordToken::TT_TWO_SIDED_DELIMITER, "/*", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (multi-line)\nNon-functional commentary or disabled code", "*/" ); - addToken(LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS, "\"", LLUIColorTable::instance().getColor("SyntaxLslStringLiteral"), "String literal", "\"" ); + addToken(LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS, "\"", LLUIColorTable::instance().getColor("SyntaxLslStringLiteral"), "String literal", "\""); + + if (mLuauLanguage) + { + addToken(LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS, "\'", LLUIColorTable::instance().getColor("SyntaxLslStringLiteral"), "String literal", "\'"); + // TODO: Might be nice to add a special case for this so we can still highlight expressions in `{}`s + addToken(LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS, "`", LLUIColorTable::instance().getColor("SyntaxLslStringLiteral"), "String literal", "`"); + // Add Lua-style comments + addToken(LLKeywordToken::TT_ONE_SIDED_DELIMITER, "--", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (Lua-style single-line)\nNon-functional commentary or disabled code", delimiter); + // Add Lua multi-line comments (long brackets) + addToken(LLKeywordToken::TT_LONG_BRACKET, "--[", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (Lua-style multi-line)\nNon-functional commentary or disabled code", delimiter); + // Add Lua multi-line strings (long brackets) + addToken(LLKeywordToken::TT_LONG_BRACKET, "[", LLUIColorTable::instance().getColor("SyntaxLslStringLiteral"), "String literal (Lua-style multi-line)", delimiter); + } + else + { + addToken(LLKeywordToken::TT_LABEL, "@", getColorGroup("misc-flow-label"), "Label\nTarget for jump statement", delimiter); + // Add LSL-style comments + addToken(LLKeywordToken::TT_ONE_SIDED_DELIMITER, "//", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (single-line)\nNon-functional commentary or disabled code", delimiter); + addToken(LLKeywordToken::TT_TWO_SIDED_DELIMITER, "/*", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (multi-line)\nNon-functional commentary or disabled code", "*/"); + } LLSD::map_iterator itr = mSyntax.beginMap(); - for ( ; itr != mSyntax.endMap(); ++itr) + for (; itr != mSyntax.endMap(); ++itr) { if (itr->first == "llsd-lsl-syntax-version") { @@ -258,6 +270,7 @@ void LLKeywords::processTokens() } } } + LL_INFOS("SyntaxLSL") << "Finished processing tokens." << LL_ENDL; } @@ -346,8 +359,11 @@ void LLKeywords::processTokensGroup(const LLSD& tokens, std::string_view group) break; case LLKeywordToken::TT_FUNCTION: tooltip = getAttribute("return") + " " + outer_itr->first + "(" + getArguments(arguments) + ");"; - tooltip.append("\nEnergy: "); - tooltip.append(getAttribute("energy").empty() ? "0.0" : getAttribute("energy")); + if (std::stod(getAttribute("energy")) >= 0) + { + tooltip.append("\nEnergy: "); + tooltip.append(getAttribute("energy").empty() ? "0.0" : getAttribute("energy")); + } if (!getAttribute("sleep").empty()) { tooltip += ", Sleep: " + getAttribute("sleep"); @@ -476,34 +492,29 @@ bool LLKeywords::WStringMapIndex::operator<(const LLKeywords::WStringMapIndex &o } LLTrace::BlockTimerStatHandle FTM_SYNTAX_COLORING("Syntax Coloring"); +constexpr size_t AVERAGE_SEGMENT_LENGTH = 8; -// Walk through a string, applying the rules specified by the keyword token list and -// create a list of color segments. -void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLWString& wtext, LLTextEditor& editor, LLStyleConstSP style) +void LLKeywords::collectSegmentOps(segment_ops_t& ops, const LLWString& wtext, bool disable_syntax_highlighting) const { - LL_RECORD_BLOCK_TIME(FTM_SYNTAX_COLORING); - seg_list->clear(); + ops.clear(); - if( wtext.empty() ) + if (wtext.empty()) { return; } - - S32 text_len = static_cast<S32>(wtext.size()) + 1; - - seg_list->push_back( new LLNormalTextSegment( style, 0, text_len, editor ) ); + // Heuristic to reduce reallocation churn on large scripts. + ops.reserve(wtext.size() / AVERAGE_SEGMENT_LENGTH); const llwchar* base = wtext.c_str(); const llwchar* cur = base; + while( *cur ) { if( *cur == '\n' || cur == base ) { if( *cur == '\n' ) { - LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, (S32)(cur - base)); - text_segment->setToken( 0 ); - insertSegment( *seg_list, text_segment, text_len, style, editor); + ops.push_back({SegmentOp::OP_LINE_BREAK, (S32)(cur - base), 0, nullptr}); cur++; if( !*cur || *cur == '\n' ) { @@ -526,24 +537,25 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW // Line start tokens { bool line_done = false; - for (token_list_t::iterator iter = mLineTokenList.begin(); - iter != mLineTokenList.end(); ++iter) + auto line_token_it = mLineTokenByFirstChar.find(*cur); + if (line_token_it != mLineTokenByFirstChar.end()) { - LLKeywordToken* cur_token = *iter; - if( cur_token->isHead( cur ) ) + for (auto* cur_token : line_token_it->second) { - S32 seg_start = (S32)(cur - base); - while( *cur && *cur != '\n' ) + if( cur_token->isHead( cur ) ) { - // skip the rest of the line - cur++; - } - S32 seg_end = (S32)(cur - base); + S32 seg_start = (S32)(cur - base); + while( *cur && *cur != '\n' ) + { + // skip the rest of the line + cur++; + } + S32 seg_end = (S32)(cur - base); - //create segments from seg_start to seg_end - insertSegments(wtext, *seg_list,cur_token, text_len, seg_start, seg_end, style, editor); - line_done = true; // to break out of second loop. - break; + ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_token}); + line_done = true; // to break out of second loop. + break; + } } } @@ -560,20 +572,32 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW cur++; } + // Check if syntax highlighting is disabled + if (disable_syntax_highlighting) + { + while (*cur && *cur != '\n') + { + ++cur; + } + continue; // skip processing any further syntax highlighting + } + while( *cur && *cur != '\n' ) { // Check against delimiters { S32 seg_start = 0; LLKeywordToken* cur_delimiter = NULL; - for (token_list_t::iterator iter = mDelimiterTokenList.begin(); - iter != mDelimiterTokenList.end(); ++iter) + auto delimiter_it = mDelimiterTokenByFirstChar.find(*cur); + if (delimiter_it != mDelimiterTokenByFirstChar.end()) { - LLKeywordToken* delimiter = *iter; - if( delimiter->isHead( cur ) ) + for (auto* delimiter : delimiter_it->second) { - cur_delimiter = delimiter; - break; + if( delimiter->isHead( cur ) ) + { + cur_delimiter = delimiter; + break; + } } } @@ -583,10 +607,79 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW S32 seg_end = 0; seg_start = (S32)(cur - base); - cur += cur_delimiter->getLengthHead(); LLKeywordToken::ETokenType type = cur_delimiter->getType(); - if( type == LLKeywordToken::TT_TWO_SIDED_DELIMITER || type == LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS ) + + // Handle Lua long brackets specially - need to verify full pattern + if (type == LLKeywordToken::TT_LONG_BRACKET) + { + const llwchar* p = cur + cur_delimiter->getLengthHead(); // after --[ or [ + + // Count equals signs + S32 level = 0; + while (*p == '=') + { + level++; + p++; + } + + // Must have second [ + if (*p == '[') + { + p++; // skip the second [ + + // Build the closing pattern: ] + level equals + ] + // Search for it in the remaining text + while (*p) + { + if (*p == ']') + { + // Check if this is our closing bracket + const llwchar* close_check = p + 1; + S32 close_equals = 0; + while (*close_check == '=') + { + close_equals++; + close_check++; + } + if (close_equals == level && *close_check == ']') + { + // Found the matching close + seg_end = (S32)(close_check + 1 - base); + cur = close_check + 1; + ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_delimiter}); + break; + } + } + p++; + } + + if (!*p) + { + // No closing found, highlight to end of file + seg_end = static_cast<S32>(wtext.size()); + cur = base + seg_end; + ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_delimiter}); + } + continue; + } + else + { + // Not a valid long bracket (e.g., --[abc), skip this delimiter + cur_delimiter = NULL; + } + } + + if (!cur_delimiter) + { + // Long bracket validation failed, continue to next character + cur++; + continue; + } + + cur += cur_delimiter->getLengthHead(); + + if(type == LLKeywordToken::TT_TWO_SIDED_DELIMITER || type == LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS) { while( *cur && !cur_delimiter->isTail(cur)) { @@ -627,7 +720,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW if( *cur ) { - cur += cur_delimiter->getLengthHead(); + cur += cur_delimiter->getLengthTail(); seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead() + cur_delimiter->getLengthTail(); } else @@ -648,12 +741,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead(); } - insertSegments(wtext, *seg_list,cur_delimiter, text_len, seg_start, seg_end, style, editor); - /* - LLTextSegmentPtr text_segment = new LLNormalTextSegment( cur_delimiter->getColor(), seg_start, seg_end, editor ); - text_segment->setToken( cur_delimiter ); - insertSegment( seg_list, text_segment, text_len, defaultColor, editor); - */ + ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_delimiter}); // Note: we don't increment cur, since the end of one delimited seg may be immediately // followed by the start of another one. continue; @@ -662,34 +750,77 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW // check against words llwchar prev = cur > base ? *(cur-1) : 0; - if( !iswalnum( prev ) && (prev != '_') ) + if (!iswalnum(prev) && prev != '_' && prev != '.') { - const llwchar* p = cur; - while( iswalnum( *p ) || (*p == '_') ) + const llwchar* word_start = cur; + S32 namespace_dots = 0; + const llwchar* last_dot = nullptr; + + // Find the full extent of the word, potentially including namespace dots + while (iswalnum(*cur) || *cur == '_' || (mLuauLanguage && *cur == '.' && iswalnum(*(cur+1)))) { - p++; + if (mLuauLanguage && *cur == '.') + { + namespace_dots++; + last_dot = cur; + } + cur++; } - S32 seg_len = (S32)(p - cur); - if( seg_len > 0 ) + + S32 seg_len = (S32)(cur - word_start); + if (seg_len > 0) { - WStringMapIndex word( cur, seg_len ); - word_token_map_t::iterator map_iter = mWordTokenMap.find(word); - if( map_iter != mWordTokenMap.end() ) + S32 seg_start = (S32)(word_start - base); + S32 seg_end = seg_start + seg_len; + + // First try to match the whole token (including dots for Lua namespaces) + word_token_map_t::const_iterator map_iter = mWordTokenMap.find(WStringMapIndex(word_start, seg_len)); + + if (map_iter != mWordTokenMap.end()) + { + // Found a match for the complete token (including any namespace) + ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, map_iter->second}); + } + else if (namespace_dots > 0 && mLuauLanguage) { - LLKeywordToken* cur_token = map_iter->second; - S32 seg_start = (S32)(cur - base); - S32 seg_end = seg_start + seg_len; + // If using Lua and we have namespace dots but didn't match the whole token, + // check if we have a match for just the namespace prefix (e.g., "ll") + if (last_dot > word_start) + { + // Get the namespace prefix (part before the first dot) + S32 prefix_len = (S32)(last_dot - word_start); + map_iter = mWordTokenMap.find(WStringMapIndex(word_start, prefix_len)); - // LL_INFOS("SyntaxLSL") << "Seg: [" << word.c_str() << "]" << LL_ENDL; + if (map_iter != mWordTokenMap.end()) + { + // Found a match for the namespace prefix, highlight just that part + ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_start + prefix_len, map_iter->second}); - insertSegments(wtext, *seg_list,cur_token, text_len, seg_start, seg_end, style, editor); + // Now try to match the function part (after the dot) + const llwchar* func_part = last_dot + 1; + S32 func_len = (S32)(cur - func_part); + + if (func_len > 0) + { + // Look for complete function matches + map_iter = mWordTokenMap.find(WStringMapIndex(func_part, func_len)); + + if (map_iter != mWordTokenMap.end()) + { + // Found a match for the function part. + // Start must be after the dot (seg_start + prefix_len + 1), + // not at seg_start, to avoid overlapping with the prefix segment. + ops.push_back({SegmentOp::OP_TOKEN, seg_start + prefix_len + 1, seg_end, map_iter->second}); + } + } + } + } } - cur += seg_len; - continue; + continue; // Continue to next token regardless of match } } - if( *cur && *cur != '\n' ) + if (*cur && *cur != '\n') { cur++; } @@ -697,6 +828,84 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW } } +bool LLKeywords::applySegmentOpsRange(std::vector<LLTextSegmentPtr> *seg_list, + const LLWString& wtext, + const segment_ops_t& ops, + size_t& op_index, + size_t max_ops, + LLTextEditor& editor, + LLStyleConstSP style) +{ + if (wtext.empty()) + { + return true; + } + + if (op_index == 0) + { + // Clear the segment list + seg_list->clear(); + // Reserve capacity for segments based on an estimated average of 8 characters per segment. + seg_list->reserve(wtext.size() / AVERAGE_SEGMENT_LENGTH); + + S32 text_len = static_cast<S32>(wtext.size()) + 1; + seg_list->push_back( new LLNormalTextSegment( style, 0, text_len, editor ) ); + } + + S32 text_len = static_cast<S32>(wtext.size()) + 1; + size_t end_index = op_index + max_ops; + if (end_index > ops.size()) + { + end_index = ops.size(); + } + + for (; op_index < end_index; ++op_index) + { + const auto& op = ops[op_index]; + if (op.type == SegmentOp::OP_LINE_BREAK) + { + LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, op.start); + text_segment->setToken( 0 ); + insertSegment( *seg_list, text_segment, text_len, style, editor); + } + else + { + insertSegments(wtext, *seg_list, op.token, text_len, op.start, op.end, style, editor); + } + } + + return op_index >= ops.size(); +} + +void LLKeywords::applySegmentOps(std::vector<LLTextSegmentPtr> *seg_list, + const LLWString& wtext, + const segment_ops_t& ops, + LLTextEditor& editor, + LLStyleConstSP style) +{ + size_t op_index = 0; + applySegmentOpsRange(seg_list, wtext, ops, op_index, ops.size(), editor, style); +} + +// Walk through a string, applying the rules specified by the keyword token list and +// create a list of color segments. +void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLWString& wtext, LLTextEditor& editor, LLStyleConstSP style) +{ + LL_RECORD_BLOCK_TIME(FTM_SYNTAX_COLORING); + + if( wtext.empty() ) + { + return; + } + + static LLCachedControl<bool> sDisableSyntaxHighlighting(gSavedSettings, "ScriptEditorDisableSyntaxHighlight", false); + const bool disable_syntax_highlighting = sDisableSyntaxHighlighting; + + segment_ops_t ops; + collectSegmentOps(ops, wtext, disable_syntax_highlighting); + applySegmentOps(seg_list, wtext, ops, editor, style); +} + void LLKeywords::insertSegments(const LLWString& wtext, std::vector<LLTextSegmentPtr>& seg_list, LLKeywordToken* cur_token, S32 text_len, S32 seg_start, S32 seg_end, LLStyleConstSP style, LLTextEditor& editor ) { std::string::size_type pos = wtext.find('\n',seg_start); diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h index 5892238593..9d9c8f964c 100644 --- a/indra/llui/llkeywords.h +++ b/indra/llui/llkeywords.h @@ -36,6 +36,8 @@ #include <map> #include <list> #include <deque> +#include <vector> +#include <cstddef> #include "llpointer.h" class LLTextSegment; @@ -53,6 +55,7 @@ public: * - TT_ONE_SIDED_DELIMITER are for open-ended delimiters which are terminated by EOL. * - TT_TWO_SIDED_DELIMITER are for delimiters that end with a different delimiter than they open with. * - TT_DOUBLE_QUOTATION_MARKS are for delimiting areas using the same delimiter to open and close. + * - TT_LONG_BRACKET are for Lua tokens that use brackets with counted equals signs. */ typedef enum e_token_type { @@ -62,6 +65,7 @@ public: TT_TWO_SIDED_DELIMITER, TT_ONE_SIDED_DELIMITER, TT_DOUBLE_QUOTATION_MARKS, + TT_LONG_BRACKET, // Lua long brackets: --[=*[ or [=*[ // Following constants are more specific versions of the preceding ones TT_CONSTANT, // WORD TT_CONTROL, // WORD @@ -82,6 +86,10 @@ public: { } + ~LLKeywordToken() + { + } + S32 getLengthHead() const { return static_cast<S32>(mToken.size()); } S32 getLengthTail() const { return static_cast<S32>(mDelimiter.size()); } bool isHead(const llwchar* s) const; @@ -118,7 +126,33 @@ public: const LLWString& text, class LLTextEditor& editor, LLStyleConstSP style); - void initialize(LLSD SyntaxXML); + struct SegmentOp + { + enum EOpType + { + OP_LINE_BREAK, + OP_TOKEN + }; + EOpType type; + S32 start; + S32 end; + LLKeywordToken* token; + }; + typedef std::vector<SegmentOp> segment_ops_t; + void collectSegmentOps(segment_ops_t& ops, const LLWString& text, bool disable_syntax_highlighting) const; + void applySegmentOps(std::vector<LLTextSegmentPtr> *seg_list, + const LLWString& text, + const segment_ops_t& ops, + class LLTextEditor& editor, + LLStyleConstSP style); + bool applySegmentOpsRange(std::vector<LLTextSegmentPtr> *seg_list, + const LLWString& text, + const segment_ops_t& ops, + size_t& op_index, + size_t max_ops, + class LLTextEditor& editor, + LLStyleConstSP style); + void initialize(LLSD SyntaxXML, bool luau_language = false); void processTokens(); // Add the token as described @@ -189,12 +223,16 @@ protected: bool mLoaded; LLSD mSyntax; + bool mLuauLanguage; word_token_map_t mWordTokenMap; typedef std::deque<LLKeywordToken*> token_list_t; token_list_t mLineTokenList; token_list_t mDelimiterTokenList; + typedef std::map<llwchar, token_list_t> token_by_first_char_map_t; + token_by_first_char_map_t mLineTokenByFirstChar; + token_by_first_char_map_t mDelimiterTokenByFirstChar; - typedef std::map<std::string, std::string, std::less<>> element_attributes_t; + typedef std::unordered_map<std::string, std::string, ll::string_hash, std::equal_to<>> element_attributes_t; typedef element_attributes_t::const_iterator attribute_iterator_t; element_attributes_t mAttributes; std::string getAttribute(std::string_view key); diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index fe0591ce4b..1dc80671cc 100644 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -48,17 +48,21 @@ static LLLayoutStack::LayoutStackRegistry::Register<LLLayoutPanel> register_layo LLLayoutPanel::Params::Params() : expanded_min_dim("expanded_min_dim", 0), min_dim("min_dim", -1), + max_dim("max_dim", -1), user_resize("user_resize", false), auto_resize("auto_resize", true) { addSynonym(min_dim, "min_width"); addSynonym(min_dim, "min_height"); + addSynonym(max_dim, "max_width"); + addSynonym(max_dim, "max_height"); } LLLayoutPanel::LLLayoutPanel(const Params& p) : LLPanel(p), mExpandedMinDim(p.expanded_min_dim.isProvided() ? p.expanded_min_dim : p.min_dim), mMinDim(p.min_dim), + mMaxDim(p.max_dim), mAutoResize(p.auto_resize), mUserResize(p.user_resize), mCollapsed(false), @@ -75,6 +79,7 @@ LLLayoutPanel::LLLayoutPanel(const Params& p) { mVisibleAmt = 0.f; } + setMaxDim(mMaxDim); } void LLLayoutPanel::initFromParams(const Params& p) @@ -113,6 +118,8 @@ S32 LLLayoutPanel::getTargetDim() const void LLLayoutPanel::setTargetDim(S32 value) { + value = llmin(value, mMaxDim); + LLRect new_rect(getRect()); if (mOrientation == LLLayoutStack::HORIZONTAL) { @@ -145,6 +152,7 @@ void LLLayoutPanel::setOrientation( LLView::EOrientation orientation ) setMinDim(layout_dim); } mTargetDim = llmax(layout_dim, getMinDim()); + mTargetDim = llmin(mTargetDim, mMaxDim); } void LLLayoutPanel::setVisible( bool visible ) @@ -167,6 +175,7 @@ void LLLayoutPanel::reshape( S32 width, S32 height, bool called_from_parent /*= if (!mIgnoreReshape && !mAutoResize) { mTargetDim = (mOrientation == LLLayoutStack::HORIZONTAL) ? width : height; + mTargetDim = llmin(mTargetDim, mMaxDim); LLLayoutStack* stackp = dynamic_cast<LLLayoutStack*>(getParent()); if (stackp) { @@ -439,6 +448,7 @@ void LLLayoutStack::updateLayout() F32 fraction_to_distribute = (panelp->mFractionalSize * panelp->getAutoResizeFactor()) / (total_visible_fraction); S32 delta = ll_round((F32)space_to_distribute * fraction_to_distribute); panelp->mTargetDim += delta; + panelp->mTargetDim = llmin(panelp->mTargetDim, panelp->mMaxDim); remaining_space -= delta; } } @@ -455,6 +465,7 @@ void LLLayoutStack::updateLayout() { S32 space_for_panel = remaining_space > 0 ? 1 : -1; panelp->mTargetDim += space_for_panel; + panelp->mTargetDim = llmin(panelp->mTargetDim, panelp->mMaxDim); remaining_space -= space_for_panel; } } diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index 9e3536aaff..4c78c8a289 100644 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -140,7 +140,8 @@ public: struct Params : public LLInitParam::Block<Params, LLPanel::Params> { Optional<S32> expanded_min_dim, - min_dim; + min_dim, + max_dim; Optional<bool> user_resize, auto_resize; @@ -164,6 +165,8 @@ public: S32 getMinDim() const { return llmax(0, mMinDim); } void setMinDim(S32 value) { mMinDim = value; } + void setMaxDim(S32 value) { mMaxDim = value < 0 ? S32_MAX : value; } + S32 getExpandedMinDim() const { return mExpandedMinDim >= 0 ? mExpandedMinDim : getMinDim(); } void setExpandedMinDim(S32 value) { mExpandedMinDim = value; } @@ -198,6 +201,7 @@ protected: S32 mExpandedMinDim; S32 mMinDim; + S32 mMaxDim; bool mCollapsed; F32 mVisibleAmt; F32 mCollapseAmt; diff --git a/indra/llui/lllazyvalue.h b/indra/llui/lllazyvalue.h index 622e69ce95..fc15ead032 100644 --- a/indra/llui/lllazyvalue.h +++ b/indra/llui/lllazyvalue.h @@ -28,15 +28,16 @@ #ifndef LL_LAZY_VALUE_H #define LL_LAZY_VALUE_H -#include <boost/function.hpp> +#include <functional> +#include <type_traits> // Holds on to a value of type T *or* calls a functor to generate a value of type T template<typename T> class LLLazyValue { public: - typedef typename boost::add_reference<typename boost::add_const<T>::type>::type T_const_ref; - typedef typename boost::function<T_const_ref (void)> function_type; + typedef typename std::add_lvalue_reference<typename std::add_const<T>::type>::type T_const_ref; + typedef typename std::function<T_const_ref (void)> function_type; public: LLLazyValue(const function_type& value) diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 45dab88e87..404e394ace 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -97,6 +97,7 @@ LLLineEditor::Params::Params() ignore_tab("ignore_tab", true), is_password("is_password", false), allow_emoji("allow_emoji", true), + draw_focus_border("draw_focus_border", true), cursor_color("cursor_color"), use_bg_color("use_bg_color", false), bg_color("bg_color"), @@ -122,6 +123,7 @@ LLLineEditor::Params::Params() LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) : LLUICtrl(p), + mDefaultText(p.default_text), mMaxLengthBytes(p.max_length.bytes), mMaxLengthChars(p.max_length.chars), mCursorPos( 0 ), @@ -146,6 +148,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mIgnoreTab( p.ignore_tab ), mDrawAsterixes( p.is_password ), mAllowEmoji( p.allow_emoji ), + mDrawFocusBorder(p.draw_focus_border), mSpellCheck( p.spellcheck ), mSpellCheckStart(-1), mSpellCheckEnd(-1), @@ -490,12 +493,13 @@ void LLLineEditor::setCursor( S32 pos ) S32 pixels_after_scroll = findPixelNearestPos(); if( pixels_after_scroll > mTextRightEdge ) { - S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos); - S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); + const LLWString& wtext = mText.getWString(); + S32 width_chars_to_left = mGLFont->getWidth(wtext.c_str(), 0, mScrollHPos); + S32 last_visible_char = mGLFont->maxDrawableChars(wtext.c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); // character immediately to left of cursor should be last one visible (SCROLL_INCREMENT_ADD will scroll in more characters) // or first character if cursor is at beginning S32 new_last_visible_char = llmax(0, getCursor() - 1); - S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char); + S32 min_scroll = mGLFont->firstDrawableChar(wtext.c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char); if (old_cursor_pos == last_visible_char) { mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); @@ -1794,7 +1798,7 @@ void LLLineEditor::drawBackground() if (!image) return; // optionally draw programmatic border - if (has_focus) + if (has_focus && mDrawFocusBorder) { LLColor4 tmp_color = gFocusMgr.getFocusColor(); tmp_color.setAlpha(alpha); @@ -1954,12 +1958,11 @@ void LLLineEditor::draw() width = llmin(width, mTextRightEdge - ll_round(rendered_pixels_right)); gl_rect_2d(ll_round(rendered_pixels_right), cursor_top, ll_round(rendered_pixels_right)+width, cursor_bottom, color); - LLColor4 tmp_color( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], alpha ); rendered_text += mFontBufferSelection.render( mGLFont, mText, mScrollHPos + rendered_text, rendered_pixels_right, text_bottom, - tmp_color, + LLColor4::black, LLFontGL::LEFT, LLFontGL::BOTTOM, 0, LLFontGL::NO_SHADOW, @@ -2230,6 +2233,9 @@ void LLLineEditor::clear() { mText.clear(); setCursor(0); + mFontBufferPreSelection.reset(); + mFontBufferSelection.reset(); + mFontBufferPostSelection.reset(); } //virtual diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 7533f76f1d..fd248edda3 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -60,7 +60,7 @@ class LLLineEditor { public: - typedef boost::function<void (LLLineEditor* caller)> keystroke_callback_t; + typedef std::function<void (LLLineEditor* caller)> keystroke_callback_t; struct MaxLength : public LLInitParam::ChoiceBlock<MaxLength> { @@ -95,7 +95,8 @@ public: show_label_focused, is_password, allow_emoji, - use_bg_color; + use_bg_color, + draw_focus_border; // colors Optional<LLUIColor> cursor_color, @@ -202,6 +203,7 @@ public: void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; } const std::string& getLabel() { return mLabel.getString(); } + void setDefaultText() { setText(mDefaultText); } void setText(const LLStringExplicit &new_text); const std::string& getText() const override { return mText.getString(); } @@ -259,7 +261,7 @@ public: void setSelectAllonCommit(bool b) { mSelectAllonCommit = b; } void onKeystroke(); - typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t; + typedef std::function<void (LLLineEditor* caller, void* user_data)> callback_t; void setKeystrokeCallback(callback_t callback, void* user_data); void setMaxTextLength(S32 max_text_length); @@ -347,6 +349,7 @@ protected: LLFontVertexBuffer mFontBufferSelection; LLFontVertexBuffer mFontBufferPostSelection; LLFontVertexBuffer mFontBufferLabel; + std::string mDefaultText; S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes S32 mMaxLengthChars; // Maximum number of characters in the string S32 mCursorPos; // I-beam is just after the mCursorPos-th character. @@ -409,6 +412,7 @@ protected: bool mAllowEmoji; bool mUseBgColor; + bool mDrawFocusBorder; LLWString mPreeditWString; LLWString mPreeditOverwrittenWString; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index c11b42a348..3b21ed8f47 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -46,6 +46,7 @@ #include "llfocusmgr.h" #include "llcoord.h" #include "llwindow.h" +#include "llemojihelper.h" #include "llcriticaldamp.h" #include "lluictrlfactory.h" @@ -72,7 +73,7 @@ S32 MENU_BAR_WIDTH = 410; /// Local function declarations, constants, enums, and typedefs ///============================================================================ -const S32 LABEL_BOTTOM_PAD_PIXELS = 2; +const S32 LABEL_BOTTOM_PAD_PIXELS = 1; const U32 LEFT_PAD_PIXELS = 3; const U32 LEFT_WIDTH_PIXELS = 15; @@ -518,21 +519,25 @@ void LLMenuItemGL::draw( void ) } else { + // Munus are all of the same size, so fixed offset works here, + // but it won't work if somebody decides to use different font + // todo: adjust logic to work of rect and font height + F32 y = (F32)MENU_ITEM_PADDING / 2.f; if( !mDrawBoolLabel.empty() ) { - mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); } - mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); if( !mDrawAccelLabel.empty() ) { - mFont->render( mDrawAccelLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + mFont->render( mDrawAccelLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PLAIN_PIXELS, y, color, LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); } if( !mDrawBranchLabel.empty() ) { - mFont->render( mDrawBranchLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + mFont->render( mDrawBranchLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PAD_PIXELS, y, color, LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); } } @@ -1411,6 +1416,7 @@ void LLMenuItemBranchDownGL::openMenu( void ) } else { + LLEmojiHelper::instance().hideHelper(nullptr, true); if (branch->getTornOff()) { LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent()); @@ -1636,6 +1642,9 @@ void LLMenuItemBranchDownGL::draw( void ) { color = mDisabledColor.get(); } + // Munus are all of the same size, so fixed offset works here, + // but it won't work if somebody decides to use different font + // todo: adjust logic to work of rect and font height getFont()->render( mLabel.getWString(), 0, (F32)getRect().getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color, LLFontGL::HCENTER, LLFontGL::BOTTOM, LLFontGL::NORMAL); @@ -4402,3 +4411,17 @@ bool LLContextMenu::addChild(LLView* view, S32 tab_group) return addContextChild(view, tab_group); } +void LLContextMenu::deleteAllChildren() +{ + mHoverItem = nullptr; + LLMenuGL::deleteAllChildren(); +} + +void LLContextMenu::removeChild(LLView* ctrl) +{ + if (ctrl == mHoverItem) + { + mHoverItem = nullptr; + } + LLMenuGL::removeChild(ctrl); +} diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index ff9456acc6..bca0a731fc 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -37,7 +37,8 @@ #include "llfloater.h" #include "lluistring.h" #include "llview.h" -#include <boost/function.hpp> + +#include <boost/bind.hpp> extern S32 MENU_BAR_HEIGHT; extern S32 MENU_BAR_WIDTH; @@ -729,6 +730,8 @@ public: virtual bool handleRightMouseUp ( S32 x, S32 y, MASK mask ); virtual bool addChild (LLView* view, S32 tab_group = 0); + /*virtual*/ void deleteAllChildren(); + /*virtual*/ void removeChild(LLView* ctrl); LLHandle<LLContextMenu> getHandle() { return getDerivedHandle<LLContextMenu>(); } diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp index c5c31f7252..225ff607ad 100644 --- a/indra/llui/llmodaldialog.cpp +++ b/indra/llui/llmodaldialog.cpp @@ -28,6 +28,7 @@ #include "llmodaldialog.h" +#include "llemojihelper.h" #include "llfocusmgr.h" #include "v4color.h" #include "v2math.h" @@ -35,6 +36,7 @@ #include "llwindow.h" #include "llkeyboard.h" #include "llmenugl.h" + // static std::list<LLModalDialog*> LLModalDialog::sModalStack; @@ -98,7 +100,7 @@ void LLModalDialog::onOpen(const LLSD& key) { if (mModal) { - // If Modal, Hide the active modal dialog + // If Modal, hide the active modal dialog if (!sModalStack.empty()) { LLModalDialog* front = sModalStack.front(); @@ -155,6 +157,12 @@ void LLModalDialog::setVisible( bool visible ) { if( visible ) { + // Hide all menus currently shown + LLMenuGL::sMenuContainer->hideMenus(); + + // Hide EmojiPicker if it is shown + LLEmojiHelper::instance().hideHelper(nullptr, true); + // This is a modal dialog. It sucks up all mouse and keyboard operations. gFocusMgr.setMouseCapture( this ); @@ -301,7 +309,6 @@ void LLModalDialog::centerOnScreen() centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY]))); } - // static void LLModalDialog::onAppFocusLost() { @@ -333,6 +340,7 @@ void LLModalDialog::onAppFocusGained() } } +// static void LLModalDialog::shutdownModals() { // This method is only for use during app shutdown. ~LLModalDialog() diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index a05feab1d9..56475a2d8d 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -457,7 +457,7 @@ LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Par mTags.push_back(tag.value); } - mForm = LLNotificationFormPtr(new LLNotificationForm(p.name, p.form_ref.form)); + mForm = std::make_shared<LLNotificationForm>(p.name, p.form_ref.form); } LLNotificationVisibilityRule::LLNotificationVisibilityRule(const LLNotificationVisibilityRule::Rule &p) @@ -875,7 +875,7 @@ void LLNotification::init(const std::string& template_name, const LLSD& form_ele // TODO: something like this so that a missing alert is sensible: //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions); - mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm)); + mForm = std::make_shared<LLNotificationForm>(*mTemplatep->mForm); mForm->append(form_elements); // apply substitution to form labels @@ -1249,7 +1249,7 @@ LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything), mIgnoreAllNotifications(false) { - mListener.reset(new LLNotificationsListener(*this)); + mListener = std::make_unique<LLNotificationsListener>(*this); LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); // touch the instance tracker for notification channels, so that it will still be around in our destructor @@ -1424,6 +1424,7 @@ LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelN // this function is called once at construction time, after the object is constructed. void LLNotifications::initSingleton() { + LL_PROFILE_ZONE_SCOPED; loadTemplates(); loadVisibilityRules(); createDefaultChannels(); @@ -1436,6 +1437,8 @@ void LLNotifications::cleanupSingleton() void LLNotifications::createDefaultChannels() { + LL_PROFILE_ZONE_SCOPED; + LL_INFOS("Notifications") << "Generating default notification channels" << LL_ENDL; // now construct the various channels AFTER loading the notifications, // because the history channel is going to rewrite the stored notifications file @@ -1487,7 +1490,7 @@ bool LLNotifications::templateExists(std::string_view name) void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option) { - LLNotificationPtr temp_notify(new LLNotification(params)); + LLNotificationPtr temp_notify = std::make_shared<LLNotification>(params); if (!temp_notify->getForm()) { @@ -1578,6 +1581,8 @@ void addPathIfExists(const std::string& new_path, std::vector<std::string>& path bool LLNotifications::loadTemplates() { + LL_PROFILE_ZONE_SCOPED; + LL_INFOS("Notifications") << "Reading notifications template" << LL_ENDL; // Passing findSkinnedFilenames(constraint=LLDir::ALL_SKINS) makes it // output all relevant pathnames instead of just the ones from the most @@ -1653,7 +1658,7 @@ bool LLNotifications::loadTemplates() replaceFormText(notification.form_ref.form, "$ignoretext", notification.form_ref.form_template.ignore_text); } } - mTemplates[notification.name] = LLNotificationTemplatePtr(new LLNotificationTemplate(notification)); + mTemplates[notification.name] = std::make_shared<LLNotificationTemplate>(notification); } LL_INFOS("Notifications") << "...done" << LL_ENDL; @@ -1663,6 +1668,8 @@ bool LLNotifications::loadTemplates() bool LLNotifications::loadVisibilityRules() { + LL_PROFILE_ZONE_SCOPED; + const std::string xml_filename = "notification_visibility.xml"; // Note that here we're looking for the "en" version, the default // language, rather than the most localized version of this file. @@ -1683,7 +1690,7 @@ bool LLNotifications::loadVisibilityRules() for (const LLNotificationVisibilityRule::Rule& rule : params.rules) { - mVisibilityRules.push_back(LLNotificationVisibilityRulePtr(new LLNotificationVisibilityRule(rule))); + mVisibilityRules.push_back(std::make_shared<LLNotificationVisibilityRule>(rule)); } return true; @@ -1726,7 +1733,7 @@ LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& subs // generalized add function that takes a parameter block object for more complex instantiations LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) { - LLNotificationPtr pNotif(new LLNotification(p)); + LLNotificationPtr pNotif = std::make_shared<LLNotification>(p); add(pNotif); return pNotif; } @@ -1834,7 +1841,7 @@ void LLNotifications::update(const LLNotificationPtr pNotif) LLNotificationPtr LLNotifications::find(LLUUID uuid) { - LLNotificationPtr target = LLNotificationPtr(new LLNotification(LLNotification::Params().id(uuid))); + LLNotificationPtr target = std::make_shared<LLNotification>(LLNotification::Params().id(uuid)); LLNotificationSet::iterator it=mItems.find(target); if (it == mItems.end()) { diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 3c8e1e85fa..ea1cb7f638 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -75,6 +75,7 @@ * */ +#include <functional> #include <string> #include <list> #include <vector> @@ -83,8 +84,6 @@ #include <iomanip> #include <sstream> -#include <boost/utility.hpp> -#include <boost/type_traits.hpp> #include <boost/signals2.hpp> #include <boost/range.hpp> #include <boost/intrusive_ptr.hpp> @@ -94,6 +93,7 @@ #include "llinitparam.h" #include "llinstancetracker.h" #include "llmortician.h" +#include "llmutex.h" #include "llnotificationptr.h" #include "llpointer.h" #include "llrefcount.h" @@ -129,7 +129,7 @@ public: virtual void fromLLSD(const LLSD& params) = 0; }; -typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder; +typedef std::function<void (const LLSD&, const LLSD&)> LLNotificationResponder; typedef std::shared_ptr<LLNotificationResponderInterface> LLNotificationResponderPtr; @@ -303,7 +303,6 @@ typedef std::shared_ptr<LLNotificationVisibilityRule> LLNotificationVisibilityRu * shared pointer. */ class LLNotification : - boost::noncopyable, public std::enable_shared_from_this<LLNotification> { LOG_CLASS(LLNotification); @@ -429,6 +428,10 @@ private: public: LLNotification(const LLSDParamAdapter<Params>& p); + // Non-copyable + LLNotification(const LLNotification&) = delete; + LLNotification& operator=(const LLNotification&) = delete; + void setResponseFunctor(std::string const &responseFunctorName); void setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb); @@ -666,8 +669,8 @@ namespace LLNotificationFilters template<typename T> struct filterBy { - typedef boost::function<T (LLNotificationPtr)> field_t; - typedef typename boost::remove_reference<T>::type value_t; + typedef std::function<T (LLNotificationPtr)> field_t; + typedef typename std::remove_reference<T>::type value_t; filterBy(field_t field, value_t value, EComparison comparison = EQUAL) : mField(field), @@ -712,7 +715,7 @@ namespace LLNotificationComparators }; }; -typedef boost::function<bool (LLNotificationPtr)> LLNotificationFilter; +typedef std::function<bool (LLNotificationPtr)> LLNotificationFilter; typedef std::set<LLNotificationPtr, LLNotificationComparators::orderByUUID> LLNotificationSet; typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap; @@ -817,10 +820,9 @@ typedef boost::intrusive_ptr<LLNotificationChannel> LLNotificationChannelPtr; // manages a list of notifications // Note that if this is ever copied around, we might find ourselves with multiple copies // of a queue with notifications being added to different nonequivalent copies. So we -// make it inherit from boost::noncopyable, and then create a map of LLPointer to manage it. +// delete the copy operator and constructor, and then create a map of LLPointer to manage it. // class LLNotificationChannel : - boost::noncopyable, public LLNotificationChannelBase, public LLInstanceTracker<LLNotificationChannel, std::string> { @@ -843,6 +845,10 @@ public: virtual ~LLNotificationChannel(); typedef LLNotificationSet::iterator Iterator; + // Non-copyable + LLNotificationChannel(const LLNotificationChannel&) = delete; + LLNotificationChannel& operator=(const LLNotificationChannel&) = delete; + std::string getName() const { return mName; } typedef std::vector<std::string>::const_iterator parents_iter; boost::iterator_range<parents_iter> getParents() const @@ -854,7 +860,7 @@ public: S32 size() const; size_t size(); - typedef boost::function<void(LLNotificationPtr)> NotificationProcess; + typedef std::function<void(LLNotificationPtr)> NotificationProcess; void forEachNotification(NotificationProcess process); std::string summarize(); @@ -940,7 +946,7 @@ public: typedef std::vector<std::string> TemplateNames; TemplateNames getTemplateNames() const; // returns a list of notification names - typedef std::map<std::string, LLNotificationTemplatePtr, std::less<>> TemplateMap; + typedef std::unordered_map<std::string, LLNotificationTemplatePtr, ll::string_hash, std::equal_to<>> TemplateMap; TemplateMap::const_iterator templatesBegin() { return mTemplates.begin(); } TemplateMap::const_iterator templatesEnd() { return mTemplates.end(); } @@ -986,7 +992,7 @@ private: LLNotificationMap mUniqueNotifications; - typedef std::map<std::string, std::string, std::less<>> GlobalStringMap; + typedef std::unordered_map<std::string, std::string, ll::string_hash, std::equal_to<>> GlobalStringMap; GlobalStringMap mGlobalStrings; bool mIgnoreAllNotifications; diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp index 9c1fc27c51..d07d42bdba 100644 --- a/indra/llui/llnotificationslistener.cpp +++ b/indra/llui/llnotificationslistener.cpp @@ -266,7 +266,7 @@ void LLNotificationsListener::forward(const LLSD& params) entry(mForwarders.insert(ForwarderMap::value_type(channel, ForwarderMap::mapped_type())).first); if (! entry->second) { - entry->second.reset(new Forwarder(mNotifications, channel)); + entry->second = std::make_shared<Forwarder>(mNotifications, channel); } // Now, whether this Forwarder is brand-new or not, update it with the new // request info. diff --git a/indra/llui/llnotificationsutil.cpp b/indra/llui/llnotificationsutil.cpp index c1cad431c5..ce36f33bf9 100644 --- a/indra/llui/llnotificationsutil.cpp +++ b/indra/llui/llnotificationsutil.cpp @@ -30,20 +30,44 @@ #include "llsd.h" #include "llxmlnode.h" // apparently needed to call LLNotifications::instance() +namespace +{ + // deleteAll() must not resurrect LLNotifications after its dependencies + // are gone. A null return means the caller should skip the toast. + LLNotifications* live_notifications() + { + if (LLNotifications::wasDeleted() || LLSingletonBase::shuttingDown()) + { + return nullptr; + } + return LLNotifications::getInstance(); + } +} + LLNotificationPtr LLNotificationsUtil::add(const std::string& name) { + LLNotifications* notifications = live_notifications(); + if (!notifications) + { + return LLNotificationPtr(); + } LLNotification::Params::Functor functor_p; functor_p.name = name; - return LLNotifications::instance().add( + return notifications->add( LLNotification::Params().name(name).substitutions(LLSD()).payload(LLSD()).functor(functor_p)); } LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& substitutions) { + LLNotifications* notifications = live_notifications(); + if (!notifications) + { + return LLNotificationPtr(); + } LLNotification::Params::Functor functor_p; functor_p.name = name; - return LLNotifications::instance().add( + return notifications->add( LLNotification::Params().name(name).substitutions(substitutions).payload(LLSD()).functor(functor_p)); } @@ -51,9 +75,14 @@ LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& substitutions, const LLSD& payload) { + LLNotifications* notifications = live_notifications(); + if (!notifications) + { + return LLNotificationPtr(); + } LLNotification::Params::Functor functor_p; functor_p.name = name; - return LLNotifications::instance().add( + return notifications->add( LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); } @@ -62,20 +91,30 @@ LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& payload, const std::string& functor_name) { + LLNotifications* notifications = live_notifications(); + if (!notifications) + { + return LLNotificationPtr(); + } LLNotification::Params::Functor functor_p; functor_p.name = functor_name; - return LLNotifications::instance().add( + return notifications->add( LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); } LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, - boost::function<void (const LLSD&, const LLSD&)> functor) + std::function<void (const LLSD&, const LLSD&)> functor) { + LLNotifications* notifications = live_notifications(); + if (!notifications) + { + return LLNotificationPtr(); + } LLNotification::Params::Functor functor_p; functor_p.function = functor; - return LLNotifications::instance().add( + return notifications->add( LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); } @@ -86,10 +125,19 @@ S32 LLNotificationsUtil::getSelectedOption(const LLSD& notification, const LLSD& void LLNotificationsUtil::cancel(LLNotificationPtr pNotif) { - LLNotifications::instance().cancel(pNotif); + LLNotifications* notifications = live_notifications(); + if (notifications) + { + notifications->cancel(pNotif); + } } LLNotificationPtr LLNotificationsUtil::find(LLUUID uuid) { - return LLNotifications::instance().find(uuid); + LLNotifications* notifications = live_notifications(); + if (!notifications) + { + return LLNotificationPtr(); + } + return notifications->find(uuid); } diff --git a/indra/llui/llnotificationsutil.h b/indra/llui/llnotificationsutil.h index f21d93a50e..73e64e47ba 100644 --- a/indra/llui/llnotificationsutil.h +++ b/indra/llui/llnotificationsutil.h @@ -32,7 +32,7 @@ #include "llnotificationptr.h" #include "lluuid.h" -#include <boost/function.hpp> +#include <functional> class LLSD; @@ -55,7 +55,7 @@ namespace LLNotificationsUtil LLNotificationPtr add(const std::string& name, const LLSD& substitutions, const LLSD& payload, - boost::function<void (const LLSD&, const LLSD&)> functor); + std::function<void (const LLSD&, const LLSD&)> functor); S32 getSelectedOption(const LLSD& notification, const LLSD& response); diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index db314cae0f..ff5a28b250 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -489,58 +489,69 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP); LLXMLNodePtr referenced_xml; - std::string xml_filename = mXMLFilename; // if the panel didn't provide a filename, check the node - if (xml_filename.empty()) + if (mXMLFilename.empty()) { - node->getAttributeString("filename", xml_filename); - setXMLFilename(xml_filename); + std::string temp_filename; + node->getAttributeString("filename", temp_filename); + setXMLFilename(temp_filename); } + // Cache singleton and filename to avoid repeated calls + LLUICtrlFactory* factory = LLUICtrlFactory::getInstance(); + + // Cache node name pointer to avoid repeated dereferencing + const LLStringTableEntry* node_name = node->getName(); + + // Cache registry to avoid repeated singleton access + const child_registry_t& registry = child_registry_t::instance(); + LLXUIParser parser; - if (!xml_filename.empty()) + if (!mXMLFilename.empty()) { if (output_node) { //if we are exporting, we want to export the current xml //not the referenced xml - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, factory->getCurFileName()); Params output_params(params); setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); + output_node->setName(node_name->mString); parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); return true; } - LLUICtrlFactory::instance().pushFileName(xml_filename); + factory->pushFileName(mXMLFilename); LL_RECORD_BLOCK_TIME(FTM_EXTERNAL_PANEL_LOAD); - if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) + if (!LLUICtrlFactory::getLayeredXMLNode(mXMLFilename, referenced_xml)) { - LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL; + LL_WARNS() << "Couldn't parse panel from: " << mXMLFilename << LL_ENDL; return false; } - parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName()); + // Get filename after pushFileName + const std::string& updated_filename = factory->getCurFileName(); + parser.readXUI(referenced_xml, params, updated_filename); // add children using dimensions from referenced xml for consistent layout setShape(params.rect); - LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); + LLUICtrlFactory::createChildren(this, referenced_xml, registry); - LLUICtrlFactory::instance().popFileName(); + factory->popFileName(); } // ask LLUICtrlFactory for filename, since xml_filename might be empty - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, factory->getCurFileName()); if (output_node) { Params output_params(params); setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); + output_node->setName(node_name->mString); parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); } @@ -552,7 +563,7 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu } // add children - LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); + LLUICtrlFactory::createChildren(this, node, registry, output_node); // Connect to parent after children are built, because tab containers // do a reshape() on their child panels, which requires that the children @@ -662,12 +673,12 @@ bool LLPanel::childHasFocus(std::string_view id) // Prefer getChild<LLUICtrl>("foo")->setCommitCallback(boost:bind(...)), // which takes a generic slot. Or use mCommitCallbackRegistrar.add() with // a named callback and reference it in XML. -void LLPanel::childSetCommitCallback(std::string_view id, boost::function<void (LLUICtrl*,void*)> cb, void* data) +void LLPanel::childSetCommitCallback(std::string_view id, std::function<void (LLUICtrl*,void*)> cb, void* data) { LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { - child->setCommitCallback(boost::bind(cb, child, data)); + child->setCommitCallback(std::bind(cb, child, data)); } } @@ -759,12 +770,12 @@ void LLPanel::childSetAction(std::string_view id, const commit_signal_t::slot_ty } } -void LLPanel::childSetAction(std::string_view id, boost::function<void(void*)> function, void* value) +void LLPanel::childSetAction(std::string_view id, std::function<void(void*)> function, void* value) { LLButton* button = findChild<LLButton>(id); if (button) { - button->setClickedCallback(boost::bind(function, value)); + button->setClickedCallback(std::bind(function, value)); } } diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index f085c123c1..a928997c7d 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -191,7 +191,7 @@ public: // Prefer getChild<LLUICtrl>("foo")->setCommitCallback(boost:bind(...)), // which takes a generic slot. Or use mCommitCallbackRegistrar.add() with // a named callback and reference it in XML. - void childSetCommitCallback(std::string_view id, boost::function<void (LLUICtrl*,void*)> cb, void* data); + void childSetCommitCallback(std::string_view id, std::function<void (LLUICtrl*,void*)> cb, void* data); void childSetColor(std::string_view id, const LLUIColor& color); LLCtrlSelectionInterface* childGetSelectionInterface(std::string_view id) const; @@ -208,7 +208,7 @@ public: bool childSetLabelArg(std::string_view id, const std::string& key, const LLStringExplicit& text); // LLButton - void childSetAction(std::string_view id, boost::function<void(void*)> function, void* value); + void childSetAction(std::string_view id, std::function<void(void*)> function, void* value); void childSetAction(std::string_view id, const commit_signal_t::slot_type& function); static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); @@ -250,7 +250,7 @@ private: LLButton* mDefaultBtn; LLUIString mLabel; - typedef std::map<std::string, std::string, std::less<>> ui_string_map_t; + typedef std::unordered_map<std::string, std::string, ll::string_hash, std::equal_to<>> ui_string_map_t; ui_string_map_t mUIStrings; @@ -262,7 +262,7 @@ extern template class LLPanel* LLView::getChild<class LLPanel>( std::string_view name, bool recurse) const; #endif -typedef boost::function<LLPanel* (void)> LLPanelClassCreatorFunc; +typedef std::function<LLPanel* (void)> LLPanelClassCreatorFunc; // local static instance for registering a particular panel class @@ -292,7 +292,7 @@ public: } private: - typedef std::map< std::string, LLPanelClassCreatorFunc, std::less<>> param_name_map_t; + typedef std::unordered_map<std::string, LLPanelClassCreatorFunc, ll::string_hash, std::equal_to<>> param_name_map_t; param_name_map_t mPanelClassesNames; }; diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp index 2aff434612..1dbd9f5b0c 100644 --- a/indra/llui/llradiogroup.cpp +++ b/indra/llui/llradiogroup.cpp @@ -67,7 +67,7 @@ protected: LLSD mPayload; // stores data that this item represents in the radio group }; -static LLWidgetNameRegistry::StaticRegistrar register_radio_item(&typeid(LLRadioGroup::ItemParams), "radio_item"); +static LLWidgetNameRegistry::StaticRegistrar register_radio_item(typeid(LLRadioGroup::ItemParams), "radio_item"); LLRadioGroup::Params::Params() : allow_deselect("allow_deselect"), diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h index 68bf0fd95e..d95536c360 100644 --- a/indra/llui/llresizebar.h +++ b/indra/llui/llresizebar.h @@ -62,7 +62,7 @@ public: void setEnableSnapping(bool enable) { mSnappingEnabled = enable; } void setAllowDoubleClickSnapping(bool allow) { mAllowDoubleClickSnapping = allow; } bool canResize() const { return getEnabled() && mMaxSize > mMinSize; } - void setResizeListener(boost::function<void(void*)> listener) {mResizeListener = listener;} + void setResizeListener(std::function<void(void*)> listener) { mResizeListener = listener; } void setImagePanel(LLPanel * panelp); LLPanel * getImagePanel() const; @@ -78,7 +78,7 @@ private: bool mSnappingEnabled, mAllowDoubleClickSnapping; LLView* mResizingView; - boost::function<void(void*)> mResizeListener; + std::function<void(void*)> mResizeListener; LLPointer<LLUIImage> mDragHandleImage; LLPanel * mImagePanel; }; diff --git a/indra/llui/llresmgr.cpp b/indra/llui/llresmgr.cpp index 111c732548..f00d876b2b 100644 --- a/indra/llui/llresmgr.cpp +++ b/indra/llui/llresmgr.cpp @@ -54,11 +54,9 @@ char LLResMgr::getDecimalPoint() const return decimal; } -char LLResMgr::getThousandsSeparator() const +std::string LLResMgr::getThousandsSeparator() const { - char separator = localeconv()->thousands_sep[0]; - - return separator; + return localeconv()->thousands_sep; } char LLResMgr::getMonetaryDecimalPoint() const @@ -68,11 +66,9 @@ char LLResMgr::getMonetaryDecimalPoint() const return decimal; } -char LLResMgr::getMonetaryThousandsSeparator() const +std::string LLResMgr::getMonetaryThousandsSeparator() const { - char separator = localeconv()->mon_thousands_sep[0]; - - return separator; + return localeconv()->mon_thousands_sep; } @@ -85,7 +81,7 @@ std::string LLResMgr::getMonetaryString( S32 input ) const struct lconv *conv = localeconv(); char* negative_sign = conv->negative_sign; - char separator = getMonetaryThousandsSeparator(); + std::string separator = getMonetaryThousandsSeparator(); char* grouping = conv->mon_grouping; // Note on mon_grouping: @@ -135,10 +131,7 @@ std::string LLResMgr::getMonetaryString( S32 input ) const } S32 group_count = cur_group; - char reversed_output[20] = ""; /* Flawfinder: ignore */ - char forward_output[20] = ""; /* Flawfinder: ignore */ - S32 output_pos = 0; - + std::string formatted; cur_group = 0; S32 pos = static_cast<S32>(digits.size()) - 1; S32 count_within_group = 0; @@ -148,7 +141,7 @@ std::string LLResMgr::getMonetaryString( S32 input ) const if( count_within_group > groupings[cur_group] ) { count_within_group = 1; - reversed_output[ output_pos++ ] = separator; + formatted = separator + formatted; if( (cur_group + 1) >= group_count ) { @@ -160,28 +153,19 @@ std::string LLResMgr::getMonetaryString( S32 input ) const cur_group++; } } - reversed_output[ output_pos++ ] = digits[pos--]; + formatted = std::string(1, digits[pos--]) + formatted; } while( pos >= 0 ) { - reversed_output[ output_pos++ ] = digits[pos--]; - } - - - reversed_output[ output_pos ] = '\0'; - forward_output[ output_pos ] = '\0'; - - for( S32 i = 0; i < output_pos; i++ ) - { - forward_output[ output_pos - 1 - i ] = reversed_output[ i ]; + formatted = std::string(1, digits[pos--]) + formatted; } if( negative_before ) { output.append( negative_sign ); } - output.append( forward_output ); + output.append( formatted ); if( negative_after ) { output.append( negative_sign ); @@ -210,11 +194,11 @@ void LLResMgr::getIntegerString( std::string& output, S32 input ) const { if (fraction == remaining_count) { - fraction_string = llformat_to_utf8("%d%c", fraction, getThousandsSeparator()); + fraction_string = llformat_to_utf8("%d", fraction) + getThousandsSeparator(); } else { - fraction_string = llformat_to_utf8("%3.3d%c", fraction, getThousandsSeparator()); + fraction_string = llformat_to_utf8("%3.3d", fraction) + getThousandsSeparator(); } output = fraction_string + output; } diff --git a/indra/llui/llresmgr.h b/indra/llui/llresmgr.h index 7ad6659a44..8ceb584ec5 100644 --- a/indra/llui/llresmgr.h +++ b/indra/llui/llresmgr.h @@ -49,10 +49,10 @@ public: LLLOCALE_ID getLocale() const { return mLocale; } char getDecimalPoint() const; - char getThousandsSeparator() const; + std::string getThousandsSeparator() const; char getMonetaryDecimalPoint() const; - char getMonetaryThousandsSeparator() const; + std::string getMonetaryThousandsSeparator() const; std::string getMonetaryString( S32 input ) const; void getIntegerString( std::string& output, S32 input ) const; diff --git a/indra/llui/llrngwriter.cpp b/indra/llui/llrngwriter.cpp index 1b4008cff2..5b0d2315c1 100644 --- a/indra/llui/llrngwriter.cpp +++ b/indra/llui/llrngwriter.cpp @@ -94,7 +94,7 @@ void LLRNGWriter::addDefinition(const std::string& type_name, const LLInitParam: block.inspectBlock(*this); // add includes for all possible children - const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); + const std::type_index& type = *LLWidgetTypeRegistry::instance().getValue(type_name); const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); // add include declarations for all valid children diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h index 9607355a9d..c49d2b534a 100644 --- a/indra/llui/llscrollbar.h +++ b/indra/llui/llscrollbar.h @@ -40,7 +40,7 @@ class LLScrollbar { public: - typedef boost::function<void (S32, LLScrollbar*)> callback_t; + typedef std::function<void (S32, LLScrollbar*)> callback_t; struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index df99c4f636..ad2f58db69 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -75,6 +75,7 @@ LLScrollContainer::Params::Params() max_auto_scroll_rate("max_auto_scroll_rate", 1000), max_auto_scroll_zone("max_auto_scroll_zone", 16), reserve_scroll_corner("reserve_scroll_corner", false), + keep_scroll_pos("keep_scroll_pos", false), size("size", -1) {} @@ -93,8 +94,12 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) mMaxAutoScrollRate(p.max_auto_scroll_rate), mMaxAutoScrollZone(p.max_auto_scroll_zone), mScrolledView(NULL), + mKeepScrollPos(p.keep_scroll_pos), mSize(p.size) { + mStoredDocPos[VERTICAL] = 0; + mStoredDocPos[HORIZONTAL] = 0; + static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); @@ -480,6 +485,7 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height void LLScrollContainer::draw() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); @@ -604,6 +610,22 @@ void LLScrollContainer::updateScroll() calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); S32 border_width = getBorderWidth(); + + // Remember the position only while the scrollbar is genuinely showing scrollable + // content, so a transient empty pass (e.g. a list clearing before it repopulates) + // cannot overwrite it with 0. + if (mKeepScrollPos) + { + if (show_v_scrollbar && mScrollbar[VERTICAL]->getVisible()) + { + mStoredDocPos[VERTICAL] = mScrollbar[VERTICAL]->getDocPos(); + } + if (show_h_scrollbar && mScrollbar[HORIZONTAL]->getVisible()) + { + mStoredDocPos[HORIZONTAL] = mScrollbar[HORIZONTAL]->getDocPos(); + } + } + if( show_v_scrollbar ) { if( doc_rect.mTop < getRect().getHeight() - border_width ) @@ -672,6 +694,18 @@ void LLScrollContainer::updateScroll() mScrollbar[VERTICAL]->setDocSize( doc_height ); mScrollbar[VERTICAL]->setPageSize( visible_height ); + + if (mKeepScrollPos) + { + if (show_v_scrollbar) + { + mScrollbar[VERTICAL]->setDocPos(mStoredDocPos[VERTICAL]); + } + if (show_h_scrollbar) + { + mScrollbar[HORIZONTAL]->setDocPos(mStoredDocPos[HORIZONTAL]); + } + } } // end updateScroll void LLScrollContainer::setBorderVisible(bool b) diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index 859750d71c..7761824dbb 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -64,7 +64,8 @@ public: reserve_scroll_corner, border_visible, hide_scrollbar, - ignore_arrow_keys; + ignore_arrow_keys, + keep_scroll_pos; Optional<F32> min_auto_scroll_rate, max_auto_scroll_rate; Optional<U32> max_auto_scroll_zone; @@ -138,6 +139,8 @@ private: void calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const; LLScrollbar* mScrollbar[ORIENTATION_COUNT]; + S32 mStoredDocPos[ORIENTATION_COUNT]; + bool mKeepScrollPos; S32 mSize; bool mIsOpaque; LLUIColor mBackgroundColor; diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index a3108d77e8..bdf88768c3 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -98,6 +98,7 @@ const LLSD LLScrollListCell::getAltValue() const LLScrollListIcon::LLScrollListIcon(const LLScrollListCell::Params& p) : LLScrollListCell(p), mIcon(LLUI::getUIImage(p.value().asString())), + mIconSize(0), mColor(p.color), mAlignment(p.font_halign) {} @@ -140,20 +141,32 @@ void LLScrollListIcon::setValue(const LLSD& value) } } - void LLScrollListIcon::setColor(const LLColor4& color) { mColor = color; } +void LLScrollListIcon::setIconSize(S32 size) +{ + mIconSize = size; +} + S32 LLScrollListIcon::getWidth() const { // if no specified fix width, use width of icon - if (LLScrollListCell::getWidth() == 0 && mIcon.notNull()) + if (LLScrollListCell::getWidth() != 0) + { + return LLScrollListCell::getWidth(); + } + if (mIconSize != 0) + { + return mIconSize; + } + if (mIcon.notNull()) { return mIcon->getWidth(); } - return LLScrollListCell::getWidth(); + return 0; } @@ -161,16 +174,23 @@ void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_col { if (mIcon) { + S32 draw_width = mIcon->getWidth(); + S32 draw_height = mIcon->getHeight(); + if (mIconSize != 0) + { + draw_width = mIconSize; + draw_height = mIconSize; + } // else will draw full icon even if cell is smaller switch(mAlignment) { case LLFontGL::LEFT: - mIcon->draw(0, 0, mColor); + mIcon->draw(0, 0, draw_width, draw_height, mColor); break; case LLFontGL::RIGHT: - mIcon->draw(getWidth() - mIcon->getWidth(), 0, mColor); + mIcon->draw(getWidth() - draw_width, 0, draw_width, draw_height, mColor); break; case LLFontGL::HCENTER: - mIcon->draw((getWidth() - mIcon->getWidth()) / 2, 0, mColor); + mIcon->draw((getWidth() - draw_width) / 2, 0, draw_width, draw_height, mColor); break; default: break; diff --git a/indra/llui/llscrolllistcell.h b/indra/llui/llscrolllistcell.h index 7dded3c0b7..f0a0f216b4 100644 --- a/indra/llui/llscrolllistcell.h +++ b/indra/llui/llscrolllistcell.h @@ -197,11 +197,13 @@ public: /*virtual*/ const LLSD getValue() const; /*virtual*/ void setColor(const LLColor4&); /*virtual*/ void setValue(const LLSD& value); + void setIconSize(S32 size); private: LLPointer<LLUIImage> mIcon; LLColor4 mColor; LLFontGL::HAlign mAlignment; + S32 mIconSize; }; diff --git a/indra/llui/llscrolllistcolumn.cpp b/indra/llui/llscrolllistcolumn.cpp index a4510d1fc2..b5ba466750 100644 --- a/indra/llui/llscrolllistcolumn.cpp +++ b/indra/llui/llscrolllistcolumn.cpp @@ -39,7 +39,7 @@ const S32 MIN_COLUMN_WIDTH = 20; // defaults for LLScrollColumnHeader param block pulled from widgets/scroll_column_header.xml -static LLWidgetNameRegistry::StaticRegistrar sRegisterColumnHeaderParams(&typeid(LLScrollColumnHeader::Params), "scroll_column_header"); +static LLWidgetNameRegistry::StaticRegistrar sRegisterColumnHeaderParams(typeid(LLScrollColumnHeader::Params), "scroll_column_header"); //--------------------------------------------------------------------------- // LLScrollColumnHeader diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 245339b107..662fe97689 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -190,19 +190,19 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) mColumnsDirty(false), mMaxItemCount(INT_MAX), mBorderThickness( 2 ), - mOnDoubleClickCallback( NULL ), - mOnMaximumSelectCallback( NULL ), - mOnSortChangedCallback( NULL ), + mOnDoubleClickCallback(nullptr), + mOnMaximumSelectCallback(nullptr), + mOnSortChangedCallback(nullptr), mHighlightedItem(-1), - mBorder(NULL), - mSortCallback(NULL), + mBorder(nullptr), + mSortCallback(nullptr), mNumDynamicWidthColumns(0), mTotalStaticColumnWidth(0), mTotalColumnPadding(0), mSorted(false), mDirty(false), mOriginalSelection(-1), - mLastSelected(NULL), + mLastSelected(nullptr), mHeadingHeight(p.heading_height), mAllowMultipleSelection(p.multi_select), mDisplayColumnHeaders(p.draw_heading), @@ -222,7 +222,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) mRowPadding(p.row_padding), mAlternateSort(false), mContextMenuType(MENU_NONE), - mIsFriendSignal(NULL) + mIsFriendSignal(nullptr) { mItemListRect.setOriginAndSize( mBorderThickness, @@ -1007,7 +1007,7 @@ void LLScrollListCtrl::deleteItems(const LLSD& sd) void LLScrollListCtrl::deleteSelectedItems() { item_list::iterator iter; - for (iter = mItemList.begin(); iter < mItemList.end(); ) + for (iter = mItemList.begin(); iter != mItemList.end(); ) { LLScrollListItem* itemp = *iter; if (itemp->getSelected()) @@ -1281,6 +1281,19 @@ LLScrollListItem* LLScrollListCtrl::getItemByLabel(const std::string& label, boo return NULL; } +LLScrollListItem* LLScrollListCtrl::getItemByValue(const std::string& value) +{ + for (LLScrollListItem* item : mItemList) + { + if (item->getValue().asString() == value) + { + return item; + } + } + + return NULL; +} + LLScrollListItem* LLScrollListCtrl::getItemByIndex(S32 index) { if (index >= 0 && index < (S32)mItemList.size()) @@ -3185,6 +3198,7 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS if (cell_p.width.isProvided()) { new_column.width.pixel_width = cell_p.width; + new_column.width.pixel_width.choose(); } addColumn(new_column); columnp = mColumns[column]; diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 1f04100306..b8d63972a5 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -77,7 +77,7 @@ public: }; // *TODO: Add callbacks to Params - typedef boost::function<void (void)> callback_t; + typedef std::function<void (void)> callback_t; template<typename T> struct maximum { @@ -241,7 +241,7 @@ public: void setMaximumSelectCallback( callback_t cb) { mOnMaximumSelectCallback = cb; } void setSortChangedCallback( callback_t cb) { mOnSortChangedCallback = cb; } // Convenience function; *TODO: replace with setter above + boost::bind() in calling code - void setDoubleClickCallback( boost::function<void (void* userdata)> cb, void* userdata) { mOnDoubleClickCallback = boost::bind(cb, userdata); } + void setDoubleClickCallback( std::function<void (void* userdata)> cb, void* userdata) { mOnDoubleClickCallback = std::bind(cb, userdata); } void swapWithNext(S32 index); void swapWithPrevious(S32 index); @@ -262,7 +262,8 @@ public: bool selectItemByLabel(const std::string& item, bool case_sensitive = true, S32 column = 0); // false if item not found bool selectItemByPrefix(const std::string& target, bool case_sensitive = true, S32 column = -1); bool selectItemByPrefix(const LLWString& target, bool case_sensitive = true, S32 column = -1); - LLScrollListItem* getItemByLabel(const std::string& item, bool case_sensitive = true, S32 column = 0); + LLScrollListItem* getItemByLabel(const std::string& label, bool case_sensitive = true, S32 column = 0); + LLScrollListItem* getItemByValue(const std::string& value); LLScrollListItem* getItemByIndex(S32 index); std::string getSelectedItemLabel(S32 column = 0) const; LLSD getSelectedValue(); diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp index a0c1e9d0c0..244faf8fe9 100644 --- a/indra/llui/llsearcheditor.cpp +++ b/indra/llui/llsearcheditor.cpp @@ -106,12 +106,12 @@ LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) LLSearchEditor::~LLSearchEditor() { - mKeystrokeCallback = NULL; - mTextChangedCallback = NULL; + mKeystrokeCallback = nullptr; + mTextChangedCallback = nullptr; setCommitOnFocusLost(false); - mSearchButton = NULL; - mClearButton = NULL; + mSearchButton = nullptr; + mClearButton = nullptr; mSearchEditor->deleteAllChildren(); deleteAllChildren(); } diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 5e0985c79c..27f1dcb03d 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -483,7 +483,7 @@ void LLTabContainer::draw() tuple->mButton->setVisible( true ); } - S32 max_scroll_visible = getTabCount() - getMaxScrollPos() + getScrollPos(); + S32 max_scroll_visible = getVisibleTabCount() - getMaxScrollPos() + getScrollPos(); S32 idx = 0; for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) { @@ -1380,6 +1380,20 @@ S32 LLTabContainer::getTabCount() const return static_cast<S32>(mTabList.size()); } +S32 LLTabContainer::getVisibleTabCount() const +{ + S32 visible_count = 0; + for (tuple_list_t::const_iterator itr = mTabList.begin(); itr != mTabList.end(); ++itr) + { + const LLTabTuple* pTT = *itr; + if (pTT->mVisible) + { + visible_count++; + } + } + return visible_count; +} + LLPanel* LLTabContainer::getPanelByIndex(S32 index) const { if (index >= 0 && index < (S32)mTabList.size()) @@ -2109,6 +2123,14 @@ void LLTabContainer::updateMaxScrollPos() S32 tab_space = 0; S32 available_space = 0; tab_space = mTotalTabWidth; + for(tuple_list_t::const_iterator tab_it = mTabList.begin(); tab_it != mTabList.end(); ++tab_it) + { + const LLTabTuple* tuple = *tab_it; + if (!tuple->mVisible) + { + tab_space -= tuple->mButton->getRect().getWidth(); + } + } available_space = getRect().getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + tabcntr_tab_h_pad); if( tab_space > available_space ) @@ -2118,7 +2140,7 @@ void LLTabContainer::updateMaxScrollPos() available_width_with_arrows -= tabcntr_tab_partial_width; S32 running_tab_width = 0; - setMaxScrollPos(getTabCount()); + setMaxScrollPos(getVisibleTabCount()); for(tuple_list_t::reverse_iterator tab_it = mTabList.rbegin(); tab_it != mTabList.rend(); ++tab_it) { running_tab_width += (*tab_it)->mButton->getRect().getWidth(); @@ -2129,7 +2151,7 @@ void LLTabContainer::updateMaxScrollPos() setMaxScrollPos(getMaxScrollPos()-1); } // in case last tab doesn't actually fit on screen, make it the last scrolling position - setMaxScrollPos(llmin(getMaxScrollPos(), getTabCount() - 1)); + setMaxScrollPos(llmin(getMaxScrollPos(), getVisibleTabCount() - 1)); no_scroll = false; } } @@ -2202,3 +2224,16 @@ void LLTabContainer::setTabVisibility( LLPanel const *aPanel, bool aVisible ) updateMaxScrollPos(); } + +bool LLTabContainer::getTabVisibility(const LLPanel* panel) const +{ + for (tuple_list_t::const_iterator itr = mTabList.begin(); itr != mTabList.end(); ++itr) + { + LLTabTuple const* pTT = *itr; + if (pTT->mTabPanel == panel) + { + return pTT->mVisible; + } + } + return false; +} diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index 4ac7e73d25..3095e641f8 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -188,6 +188,7 @@ public: LLPanel* getCurrentPanel(); S32 getCurrentPanelIndex() const; S32 getTabCount() const; + S32 getVisibleTabCount() const; LLPanel* getPanelByIndex(S32 index) const; S32 getIndexForPanel(LLPanel* panel) const; S32 getPanelIndexByTitle(std::string_view title) const; @@ -225,6 +226,7 @@ public: S32 getMaxTabWidth() const { return mMaxTabWidth; } void setTabVisibility( LLPanel const *aPanel, bool ); + bool getTabVisibility(const LLPanel* panel) const; void startDragAndDropDelayTimer() { mDragAndDropDelayTimer.start(); } diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 778b253c3c..f0c3c97ef9 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -51,6 +51,9 @@ const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds const S32 CURSOR_THICKNESS = 2; const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. +constexpr F32 FOCUSED_SELECTION_BG_ALPHA = 1; +constexpr F32 UNFOCUSED_SELECTION_BG_ALPHA = 0.7f; + LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num) : mDocIndexStart(index_start), mDocIndexEnd(index_end), @@ -129,7 +132,7 @@ struct LLTextBase::line_end_compare // // register LLTextBase::Params under name "textbase" -static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase"); +static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(typeid(LLTextBase::Params), "textbase"); LLTextBase::LineSpacingParams::LineSpacingParams() : multiple("multiple", 1.f), @@ -155,6 +158,7 @@ LLTextBase::Params::Params() track_end("track_end", false), read_only("read_only", false), skip_link_underline("skip_link_underline", false), + link_color("link_color"), spellcheck("spellcheck", false), v_pad("v_pad", 0), h_pad("h_pad", 0), @@ -185,6 +189,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mURLClickSignal(NULL), mIsFriendSignal(NULL), mIsObjectBlockedSignal(NULL), + mIsObjectReachableSignal(NULL), mMaxTextByteLength( p.max_text_length ), mFont(p.font), mFontShadow(p.font_shadow), @@ -192,6 +197,8 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mReadOnly(p.read_only), mSkipTripleClick(false), mSkipLinkUnderline(p.skip_link_underline), + mHasLinkColor(p.link_color.isProvided()), + mLinkColor(p.link_color.isProvided() ? p.link_color() : LLUIColor()), mSpellCheck(p.spellcheck), mSpellCheckStart(-1), mSpellCheckEnd(-1), @@ -221,6 +228,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mTrustedContent(p.trusted_content), mAlwaysShowIcons(p.always_show_icons), mTrackEnd( p.track_end ), + mTrackValueChange(true), mScrollIndex(-1), mSelectionStart( 0 ), mSelectionEnd( 0 ), @@ -290,6 +298,7 @@ LLTextBase::~LLTextBase() delete mURLClickSignal; delete mIsFriendSignal; delete mIsObjectBlockedSignal; + delete mIsObjectReachableSignal; } void LLTextBase::initFromParams(const LLTextBase::Params& p) @@ -527,7 +536,7 @@ void LLTextBase::drawSelectionBackground() // Draw the selection box (we're using a box instead of reversing the colors on the selected text). gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); const LLColor4& color = mSelectedBGColor; - F32 alpha = hasFocus() ? 0.7f : 0.3f; + F32 alpha = hasFocus() ? FOCUSED_SELECTION_BG_ALPHA : UNFOCUSED_SELECTION_BG_ALPHA; alpha *= getDrawContext().mAlpha; LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha); @@ -962,7 +971,10 @@ void LLTextBase::drawText() S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments ) { - beforeValueChange(); + if (mTrackValueChange) + { + beforeValueChange(); + } S32 old_len = getLength(); // length() returns character length S32 insert_len = static_cast<S32>(wstr.length()); @@ -1036,8 +1048,37 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s { LLStyleSP emoji_style; LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL; + LLTextSegment* segmentp = nullptr; + segment_vec_t::iterator seg_iter; + if (segments && segments->size() > 0) + { + seg_iter = segments->begin(); + segmentp = *seg_iter; + } for (S32 text_kitty = 0, text_len = static_cast<S32>(wstr.size()); text_kitty < text_len; text_kitty++) { + if (segmentp) + { + if (segmentp->getEnd() <= pos + text_kitty) + { + seg_iter++; + if (seg_iter != segments->end()) + { + segmentp = *seg_iter; + } + else + { + segmentp = nullptr; + } + } + if (segmentp && !segmentp->getPermitsEmoji()) + { + // Some segments, like LLInlineViewSegment do not permit splitting + // and should not be interrupted by emoji segments + continue; + } + } + llwchar code = wstr[text_kitty]; bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code); if (isEmoji) @@ -1056,12 +1097,14 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s getViewModel()->getEditableDisplay().insert(pos, wstr); - if ( truncate() ) + if (mTrackValueChange) { - insert_len = getLength() - old_len; + if (truncate()) + { + insert_len = getLength() - old_len; + } + onValueChange(pos, pos + insert_len); } - - onValueChange(pos, pos + insert_len); needsReflow(pos); return insert_len; @@ -1069,7 +1112,18 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) { - beforeValueChange(); + S32 text_length = (S32)getLength(); + if (pos >= text_length || pos < 0) + { + return 0; // nothing to remove + } + // Clamp length to not go past the end of the text + length = std::min(length, text_length - pos); + + if (mTrackValueChange) + { + beforeValueChange(); + } segment_set_t::iterator seg_iter = getSegIterContaining(pos); while(seg_iter != mSegments.end()) { @@ -1120,7 +1174,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) // recreate default segment in case we erased everything createDefaultSegment(); - onValueChange(pos, pos); + if (mTrackValueChange) + { + onValueChange(pos, pos); + } needsReflow(pos); return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length @@ -1128,7 +1185,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) { - beforeValueChange(); + if (mTrackValueChange) + { + beforeValueChange(); + } if (pos > (S32)getLength()) { @@ -1136,7 +1196,10 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) } getViewModel()->getEditableDisplay()[pos] = wc; - onValueChange(pos, pos + 1); + if (mTrackValueChange) + { + onValueChange(pos, pos + 1); + } needsReflow(pos); return 1; @@ -1438,6 +1501,8 @@ void LLTextBase::reshape(S32 width, S32 height, bool called_from_parent) // up-to-date mVisibleTextRect updateRects(); + // Todo: This might be wrong. updateRects already sets needsReflow conditionaly. + // Reflow is expensive and doing it at any twith can be too much. needsReflow(); } } @@ -1581,7 +1646,7 @@ void LLTextBase::deselect() bool LLTextBase::getSpellCheck() const { - return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); + return (!mReadOnly) && (LLSpellChecker::getUseSpellCheck()) && (mSpellCheck); } const std::string& LLTextBase::getSuggestion(U32 index) const @@ -2226,8 +2291,11 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url)); registrar.add("Url.RemoveFriend", boost::bind(&LLUrlAction::removeFriend, url)); registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url)); + registrar.add("Url.ReportAbuseObj", boost::bind(&LLUrlAction::reportAbuseObj, url)); registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url)); + registrar.add("Url.ZoomInObject", boost::bind(&LLUrlAction::zoomInObject, url)); registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); + registrar.add("Url.ShowParcelOnMap", boost::bind(&LLUrlAction::showParcelOnMap, url)); registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); @@ -2271,6 +2339,15 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) unblockButton->setVisible(is_blocked); } } + + if (mIsObjectReachableSignal) + { + bool is_reachable = *(*mIsObjectReachableSignal)(LLUUID(LLUrlAction::getObjectId(url))); + if (LLView* zoom_btn = menu->getChild<LLView>("zoom_in")) + { + zoom_btn->setEnabled(is_reachable); + } + } menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } @@ -2278,6 +2355,10 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) { + beforeValueChange(); + // Can insert a lot of different segments, don't want to spam events. + mTrackValueChange = false; + // clear out the existing text and segments getViewModel()->setDisplay(LLWStringUtil::null); @@ -2298,6 +2379,8 @@ void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& startOfDoc(); } + truncate(); // was postponed to avoid micro truncations and expensive checks + mTrackValueChange = true; onValueChange(0, getLength()); } @@ -2328,6 +2411,10 @@ void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Para LLStyle::Params style_params(getStyleParams()); style_params.overwriteFrom(input_params); + // todo: this does not check for maximum size, might + // want to stop once maximum size was reached to avoid + // expensive findUrl, replaceUrl calls. + S32 part = (S32)LLTextParser::WHOLE; if ((mParseHTML || force_slurl) && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). { @@ -2342,6 +2429,11 @@ void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Para LLStyle::Params link_params(style_params); link_params.overwriteFrom(match.getStyle()); + if (mHasLinkColor) + { + link_params.color = mLinkColor; + link_params.readonly_color = mLinkColor; + } // output the text before the Url if (start > 0) @@ -2501,6 +2593,10 @@ void LLTextBase::copyContents(const LLTextBase* source) beforeValueChange(); deselect(); + // Can insert a lot of different segments, don't want to spam events. + // Do one full length onValueChange() at the end of this function. + mTrackValueChange = false; + mSegments.clear(); for (const LLTextSegmentPtr& segp : source->mSegments) { @@ -2515,6 +2611,8 @@ void LLTextBase::copyContents(const LLTextBase* source) getViewModel()->setDisplay(source->getViewModel()->getDisplay()); + truncate(); // was postponed to avoid micro truncations and expensive checks + mTrackValueChange = true; onValueChange(0, getLength()); needsReflow(); } @@ -2769,7 +2867,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round, line_seg_iter != mSegments.end(); ++line_seg_iter, line_seg_offset = 0) { - const LLTextSegmentPtr segmentp = *line_seg_iter; + LLTextSegmentPtr segmentp = *line_seg_iter; S32 segment_line_start = segmentp->getStart() + line_seg_offset; S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; @@ -2860,7 +2958,7 @@ LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const while(line_seg_iter != mSegments.end()) { - const LLTextSegmentPtr segmentp = *line_seg_iter; + LLTextSegmentPtr segmentp = *line_seg_iter; if (line_seg_iter == cursor_seg_iter) { @@ -3377,6 +3475,15 @@ boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_bloc return mIsObjectBlockedSignal->connect(cb); } +boost::signals2::connection LLTextBase::setIsObjectReachableCallback(const is_obj_reachable_signal_t::slot_type& cb) +{ + if (!mIsObjectReachableSignal) + { + mIsObjectReachableSignal = new is_obj_reachable_signal_t(); + } + return mIsObjectReachableSignal->connect(cb); +} + // // LLTextSegment // @@ -3402,8 +3509,8 @@ LLStyleSP LLTextSegment::cloneStyle(LLTextBase& target, const LLStyle* source) } -bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; } -bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = 0; return false; } +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) { F32 fwidth = 0; bool result = getDimensionsF32(first_char, num_chars, fwidth, height); @@ -3459,6 +3566,11 @@ LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 e { mFontHeight = mStyle->getFont()->getLineHeight(); mCanEdit = !mStyle->getDrawHighlightBg(); + if (!mCanEdit) + { + // Emoji shouldn't split the segment with the mention. + mPermitsEmoji = false; + } LLUIImagePtr image = mStyle->getImage(); if (image.notNull()) @@ -3495,6 +3607,7 @@ F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selec mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } return draw_rect.mLeft; } @@ -3520,6 +3633,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } const LLFontGL* font = mStyle->getFont(); @@ -3751,17 +3865,19 @@ LLTextSegmentPtr LLNormalTextSegment::clone(LLTextBase& target) const return new LLNormalTextSegment(sp, mStart, mEnd, target); } -bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { height = 0; width = 0; - if (num_chars > 0) + if (num_chars > 0 && (mStart + first_char >= 0)) { height = mFontHeight; - const LLWString &text = getWText(); - // if last character is a newline, then return true, forcing line break - width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true); + + const LLWString& text = getWText(); + const LLFontGL* font = mStyle->getFont(); + width += mFontWidthBuffer.getWidth(font, text.c_str(), mStart + first_char, num_chars, true); } + // if last character is a newline, then return true, forcing line break return false; } @@ -3843,6 +3959,7 @@ void LLNormalTextSegment::updateLayout(const class LLTextBase& editor) mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } void LLNormalTextSegment::dump() const @@ -3979,6 +4096,7 @@ LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end) mTopPad(p.top_pad), mBottomPad(p.bottom_pad) { + mPermitsEmoji = false; } LLInlineViewSegment::~LLInlineViewSegment() @@ -3993,7 +4111,7 @@ LLTextSegmentPtr LLInlineViewSegment::clone(LLTextBase& target) const return nullptr; } -bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { if (first_char == 0 && num_chars == 0) { @@ -4033,7 +4151,7 @@ S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin { return 0; } - else if (line_offset != 0 && num_pixels < mView->getRect().getWidth()) + else if (line_offset != 0 && num_pixels < (mLeftPad + mRightPad + mView->getRect().getWidth())) { return 0; } @@ -4085,7 +4203,7 @@ LLTextSegmentPtr LLLineBreakTextSegment::clone(LLTextBase& target) const copy->mFontHeight = mFontHeight; return copy; } -bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = mFontHeight; @@ -4122,7 +4240,7 @@ LLTextSegmentPtr LLImageTextSegment::clone(LLTextBase& target) const static const S32 IMAGE_HPAD = 3; // virtual -bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = mStyle->getFont()->getLineHeight(); diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 8ca653acb9..d3edd17dca 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -61,15 +61,17 @@ class LLTextSegment public: LLTextSegment(S32 start, S32 end) : mStart(start), - mEnd(end) + mEnd(end), + mPermitsEmoji(true) {} virtual ~LLTextSegment(); virtual LLTextSegmentPtr clone(LLTextBase& terget) const { return new LLTextSegment(mStart, mEnd); } static LLStyleSP cloneStyle(LLTextBase& target, const LLStyle* source); - bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const; + bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height); + bool getPermitsEmoji() const { return mPermitsEmoji; }; - virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; /** @@ -125,6 +127,8 @@ public: protected: S32 mStart; S32 mEnd; + + bool mPermitsEmoji; }; class LLNormalTextSegment : public LLTextSegment @@ -135,7 +139,7 @@ public: virtual ~LLNormalTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; /*virtual*/ void updateLayout(const class LLTextBase& editor); @@ -178,6 +182,7 @@ protected: LLFontVertexBuffer mFontBufferPreSelection; LLFontVertexBuffer mFontBufferSelection; LLFontVertexBuffer mFontBufferPostSelection; + LLFontWidthBuffer mFontWidthBuffer; S32 mLastGeneration = -1; }; @@ -250,11 +255,12 @@ public: ~LLInlineViewSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; /*virtual*/ void updateLayout(const class LLTextBase& editor); /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); /*virtual*/ bool canEdit() const { return false; } + /*virtual*/ bool getPermitsEmoji() const { return false; } /*virtual*/ void unlinkFromDocument(class LLTextBase* editor); /*virtual*/ void linkToDocument(class LLTextBase* editor); @@ -275,7 +281,7 @@ public: LLLineBreakTextSegment(S32 pos); ~LLLineBreakTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -290,7 +296,7 @@ public: ~LLImageTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -321,10 +327,12 @@ class LLTextBase public: friend class LLTextSegment; friend class LLNormalTextSegment; + friend class LLEmbeddedItemSegment; friend class LLUICtrlFactory; typedef boost::signals2::signal<bool (const LLUUID& user_id)> is_friend_signal_t; typedef boost::signals2::signal<bool (const LLUUID& blocked_id, const std::string from)> is_blocked_signal_t; + typedef boost::signals2::signal<bool (const LLUUID& obj_id)> is_obj_reachable_signal_t; struct LineSpacingParams : public LLInitParam::ChoiceBlock<LineSpacingParams> { @@ -343,7 +351,8 @@ public: bg_writeable_color, bg_focus_color, text_selected_color, - bg_selected_color; + bg_selected_color, + link_color; Optional<bool> bg_visible, border_visible, @@ -402,6 +411,7 @@ public: /*virtual*/ void setColor(const LLUIColor& c) override; virtual void setReadOnlyColor(const LLUIColor& c); /*virtual*/ void onVisibilityChange(bool new_visibility) override; + void setBgReadOnlyColor(const LLUIColor& c) { mReadOnlyBgColor = c; } /*virtual*/ void setValue(const LLSD& value) override; /*virtual*/ LLTextViewModel* getViewModel() const override; @@ -535,6 +545,7 @@ public: boost::signals2::connection setURLClickedCallback(const commit_signal_t::slot_type& cb); boost::signals2::connection setIsFriendCallback(const is_friend_signal_t::slot_type& cb); boost::signals2::connection setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb); + boost::signals2::connection setIsObjectReachableCallback(const is_obj_reachable_signal_t::slot_type& cb); void setWordWrap(bool wrap); LLScrollContainer* getScrollContainer() const { return mScroller; } @@ -754,6 +765,7 @@ protected: bool mUseEmoji; bool mUseColor; bool mTrackEnd; // if true, keeps scroll position at end of document during resize + bool mTrackValueChange; // if true, send out onValueChange() from low level text modification methods bool mReadOnly; bool mBGVisible; // render background? bool mClip; // clip text to widget rect @@ -766,6 +778,8 @@ protected: bool mAlwaysShowIcons; bool mSkipLinkUnderline; + bool mHasLinkColor; + LLUIColor mLinkColor; // support widgets LLHandle<LLContextMenu> mPopupMenuHandle; @@ -783,6 +797,7 @@ protected: // Used to check if user with given ID is avatar's friend is_friend_signal_t* mIsFriendSignal; is_blocked_signal_t* mIsObjectBlockedSignal; + is_obj_reachable_signal_t* mIsObjectReachableSignal; LLUIString mLabel; // text label that is visible when no user text provided }; diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp index 9f945d3735..d2a21998a6 100644 --- a/indra/llui/lltextbox.cpp +++ b/indra/llui/lltextbox.cpp @@ -43,7 +43,7 @@ template class LLTextBox* LLView::getChild<class LLTextBox>( LLTextBox::LLTextBox(const LLTextBox::Params& p) : LLTextBase(p), - mClickedCallback(NULL), + mClickedCallback(nullptr), mShowCursorHand(true) { mSkipTripleClick = true; @@ -135,9 +135,9 @@ void LLTextBox::setText(const LLStringExplicit& text , const LLStyle::Params& in LLTextBase::setText(mText.getString(), input_params ); } -void LLTextBox::setClickedCallback( boost::function<void (void*)> cb, void* userdata /*= NULL */ ) +void LLTextBox::setClickedCallback(std::function<void (void*)> cb, void* userdata /*= nullptr */) { - mClickedCallback = boost::bind(cb, userdata); + mClickedCallback = std::bind(cb, userdata); } S32 LLTextBox::getTextPixelWidth() diff --git a/indra/llui/lltextbox.h b/indra/llui/lltextbox.h index 507d8f3ee6..8716917ced 100644 --- a/indra/llui/lltextbox.h +++ b/indra/llui/lltextbox.h @@ -36,7 +36,7 @@ class LLTextBox : public: // *TODO: Add callback to Params - typedef boost::function<void (void)> callback_t; + typedef std::function<void (void)> callback_t; struct Params : public LLInitParam::Block<Params, LLTextBase::Params> {}; @@ -58,7 +58,7 @@ public: void setRightAlign() { mHAlign = LLFontGL::RIGHT; } void setHAlign(LLFontGL::HAlign align) { mHAlign = align; } - void setClickedCallback(boost::function<void(void*)> cb, void* userdata = NULL); + void setClickedCallback(std::function<void(void*)> cb, void* userdata = nullptr); void reshapeToFitText(bool called_from_parent = false); diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index cfe729be06..6588c481ff 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -61,6 +61,7 @@ #include "lltooltip.h" #include "llmenugl.h" #include "llchatmentionhelper.h" +#include "llgestureautocompletehelper.h" #include <queue> #include "llcombobox.h" @@ -274,7 +275,9 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mShowChatMentionPicker(false), mEnableTooltipPaste(p.enable_tooltip_paste), mPassDelete(false), - mKeepSelectionOnReturn(false) + mKeepSelectionOnReturn(false), + mSelectAllOnFocusReceived(false), + mSelectedOnFocusReceived(false) { mSourceID.generate(); @@ -398,6 +401,7 @@ void LLTextEditor::selectNext(const std::string& search_text_in, bool case_insen setCursorPos(loc); mIsSelecting = true; + mSelectedOnFocusReceived = false; mSelectionEnd = mCursorPos; mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size())); } @@ -578,7 +582,7 @@ S32 LLTextEditor::indentLine( S32 pos, S32 spaces ) LLWString wtext = getWText(); if (wtext[pos] == ' ') { - delta_spaces += remove( pos, 1, false ); + delta_spaces -= remove( pos, 1, false ); } } } @@ -677,6 +681,13 @@ bool LLTextEditor::canSelectAll() const return true; } +//virtual +void LLTextEditor::deselect() +{ + LLTextBase::deselect(); + mSelectedOnFocusReceived = false; +} + // virtual void LLTextEditor::selectAll() { @@ -694,6 +705,11 @@ void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_p endSelection(); } +void LLTextEditor::setSelectAllOnFocusReceived(bool b) +{ + mSelectAllOnFocusReceived = b; +} + void LLTextEditor::insertEmoji(llwchar emoji) { LL_INFOS() << "LLTextEditor::insertEmoji(" << wchar_utf8_preview(emoji) << ")" << LL_ENDL; @@ -795,8 +811,16 @@ bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) // Delay cursor flashing resetCursorBlink(); + mSelectedOnFocusReceived = false; if (handled && !gFocusMgr.getMouseCapture()) { + if (!mask && mSelectAllOnFocusReceived) + { + mIsSelecting = false; + mSelectionStart = getLength(); + mSelectionEnd = 0; + mSelectedOnFocusReceived = true; + } gFocusMgr.setMouseCapture( this ); } return handled; @@ -1219,7 +1243,7 @@ void LLTextEditor::addChar(llwchar wc) tryToShowEmojiHelper(); tryToShowMentionHelper(); - if (!mReadOnly && mAutoreplaceCallback != NULL) + if (!mReadOnly && mAutoreplaceCallback != nullptr) { // autoreplace the text, if necessary S32 replacement_start; @@ -1669,28 +1693,25 @@ void LLTextEditor::pasteTextWithLinebreaks(LLWString & clean_string) std::basic_string<llwchar>::size_type start = 0; std::basic_string<llwchar>::size_type pos = clean_string.find('\n',start); - while((pos != -1) && (pos != clean_string.length() -1)) + while (pos != std::basic_string<llwchar>::npos) { - if(pos!=start) + if (pos != start) { std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,pos-start); setCursorPos(mCursorPos + insert(mCursorPos, str, true, LLTextSegmentPtr())); } - addLineBreakChar(true); // Add a line break and group with the next addition. + const bool trailing_linebreak = (pos == clean_string.length() - 1); + addLineBreakChar(!trailing_linebreak); start = pos+1; pos = clean_string.find('\n',start); } - if (pos != start) + if (start < clean_string.length()) { std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,clean_string.length()-start); setCursorPos(mCursorPos + insert(mCursorPos, str, false, LLTextSegmentPtr())); } - else - { - addLineBreakChar(false); // Add a line break and end the grouping. - } } // copy selection to primary @@ -1927,7 +1948,8 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask ) // not handled and let the parent take care of field movement. if (KEY_TAB == key && mTabsToNextField) { - return mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask); + return (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask)) + || LLGestureAutocompleteHelper::instance().handleKey(this, key, mask); } if (mReadOnly && mScroller) @@ -1941,7 +1963,8 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask ) if (!mReadOnly) { if ((mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) || - (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask))) + (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask)) || + LLGestureAutocompleteHelper::instance().handleKey(this, key, mask)) { return true; } @@ -2200,6 +2223,11 @@ void LLTextEditor::focusLostHelper() gEditMenuHandler = NULL; } + if (mSelectedOnFocusReceived) + { + deselect(); + } + if (mCommitOnFocusLost) { onCommit(); diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 882bb145df..43f47a404c 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -144,8 +144,10 @@ public: virtual bool canDoDelete() const; virtual void selectAll(); virtual bool canSelectAll() const; + virtual void deselect(); void selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos); + void setSelectAllOnFocusReceived(bool b); virtual bool canLoadOrSaveToFile(); @@ -162,7 +164,7 @@ public: bool allowsEmbeddedItems() const { return mAllowEmbeddedItems; } // Autoreplace (formerly part of LLLineEditor) - typedef boost::function<void(S32&, S32&, LLWString&, S32&, const LLWString&)> autoreplace_callback_t; + typedef std::function<void(S32&, S32&, LLWString&, S32&, const LLWString&)> autoreplace_callback_t; autoreplace_callback_t mAutoreplaceCallback; void setAutoreplaceCallback(autoreplace_callback_t cb) { mAutoreplaceCallback = cb; } @@ -206,6 +208,7 @@ public: void setShowContextMenu(bool show) { mShowContextMenu = show; } bool getShowContextMenu() const { return mShowContextMenu; } + void showContextMenu(S32 x, S32 y); void showEmojiHelper(); void hideEmojiHelper(); @@ -217,7 +220,6 @@ public: LLWString getConvertedText() const; protected: - void showContextMenu(S32 x, S32 y); void drawPreeditMarker(); void removeCharOrTab(); @@ -336,6 +338,8 @@ private: bool mEnableTooltipPaste; bool mPassDelete; bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter + bool mSelectAllOnFocusReceived; + bool mSelectedOnFocusReceived; LLUUID mSourceID; diff --git a/indra/llui/lltextutil.cpp b/indra/llui/lltextutil.cpp index 8ffce1b8b4..1454de34c7 100644 --- a/indra/llui/lltextutil.cpp +++ b/indra/llui/lltextutil.cpp @@ -30,7 +30,7 @@ #include "lltextbox.h" #include "llurlmatch.h" -boost::function<bool(LLUrlMatch*,LLTextBase*)> LLTextUtil::TextHelpers::iconCallbackCreationFunction = 0; +std::function<bool(LLUrlMatch*, LLTextBase*)> LLTextUtil::TextHelpers::iconCallbackCreationFunction = nullptr; void LLTextUtil::textboxSetHighlightedVal(LLTextBox *txtbox, const LLStyle::Params& normal_style, const std::string& text, const std::string& hl) { diff --git a/indra/llui/lltextutil.h b/indra/llui/lltextutil.h index f3838e59fa..6328ff133f 100644 --- a/indra/llui/lltextutil.h +++ b/indra/llui/lltextutil.h @@ -74,7 +74,7 @@ namespace LLTextUtil //we need this special callback since we need to create LLAvataIconCtrls while parsing //avatar/group url but can't create LLAvataIconCtrl from LLUI public: - static boost::function<bool(LLUrlMatch*,LLTextBase*)> iconCallbackCreationFunction; + static std::function<bool(LLUrlMatch*,LLTextBase*)> iconCallbackCreationFunction; }; diff --git a/indra/llui/lltextvalidate.cpp b/indra/llui/lltextvalidate.cpp index 9a087d8230..1f2e7e65db 100644 --- a/indra/llui/lltextvalidate.cpp +++ b/indra/llui/lltextvalidate.cpp @@ -434,7 +434,7 @@ public: } validatorASCIINoLeadingSpaceImpl; Validator validateASCIINoLeadingSpace(validatorASCIINoLeadingSpaceImpl); -class ValidatorASCIIWithNewLine : public ValidatorImpl +class ValidatorASCIIWithNewLineNoPipe : public ValidatorImpl { // Used for multiline text stored on the server. // Example is landmark description in Places SP. @@ -446,9 +446,9 @@ class ValidatorASCIIWithNewLine : public ValidatorImpl { CHAR ch = str[len]; - if ((ch < 0x20 && ch != 0xA) || ch > 0x7f) + if ((ch < 0x20 && ch != 0xA) || ch > 0x7f || ch == '|') { - return setError("Validator_ShouldBeNewLineOrASCII", LLSD().with("NR", len + 1).with("CH", llsd(ch))); + return setError("Validator_ShouldBeNewLineOrASCIINoPipe", LLSD().with("NR", len + 1).with("CH", llsd(ch))); } } @@ -458,8 +458,8 @@ class ValidatorASCIIWithNewLine : public ValidatorImpl public: /*virtual*/ bool validate(const std::string& str) override { return validate<char>(str); } /*virtual*/ bool validate(const LLWString& str) override { return validate<llwchar>(str); } -} validatorASCIIWithNewLineImpl; -Validator validateASCIIWithNewLine(validatorASCIIWithNewLineImpl); +} validatorASCIIWithNewLineNoPipeImpl; +Validator validateASCIIWithNewLineNoPipe(validatorASCIIWithNewLineNoPipeImpl); void Validators::declareValues() { @@ -472,7 +472,7 @@ void Validators::declareValues() declare("alpha_num_space", validateAlphaNumSpace); declare("ascii_printable_no_pipe", validateASCIIPrintableNoPipe); declare("ascii_printable_no_space", validateASCIIPrintableNoSpace); - declare("ascii_with_newline", validateASCIIWithNewLine); + declare("ascii_with_newline_no_pipe", validateASCIIWithNewLineNoPipe); } } // namespace LLTextValidate diff --git a/indra/llui/lltextvalidate.h b/indra/llui/lltextvalidate.h index a4ff144d82..a95f79bfcb 100644 --- a/indra/llui/lltextvalidate.h +++ b/indra/llui/lltextvalidate.h @@ -30,7 +30,6 @@ #include "llstring.h" #include "llinitparam.h" -#include <boost/function.hpp> namespace LLTextValidate { @@ -89,7 +88,7 @@ namespace LLTextValidate extern Validator validateASCIIPrintableNoSpace; extern Validator validateASCII; extern Validator validateASCIINoLeadingSpace; - extern Validator validateASCIIWithNewLine; + extern Validator validateASCIIWithNewLineNoPipe; // Add available validators to the internal map struct Validators : public LLInitParam::TypeValuesHelper<Validator, Validators> diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index 5955a28fa3..2a9bf2585d 100644 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -99,8 +99,8 @@ LLToolBar::LLToolBar(const LLToolBar::Params& p) mWrap(p.wrap), mNeedsLayout(false), mModified(false), - mButtonPanel(NULL), - mCenteringStack(NULL), + mButtonPanel(nullptr), + mCenteringStack(nullptr), mPadLeft(p.pad_left), mPadRight(p.pad_right), mPadTop(p.pad_top), @@ -108,17 +108,17 @@ LLToolBar::LLToolBar(const LLToolBar::Params& p) mPadBetween(p.pad_between), mMinGirth(p.min_girth), mPopupMenuHandle(), - mRightMouseTargetButton(NULL), - mStartDragItemCallback(NULL), - mHandleDragItemCallback(NULL), - mHandleDropCallback(NULL), - mButtonAddSignal(NULL), - mButtonEnterSignal(NULL), - mButtonLeaveSignal(NULL), - mButtonRemoveSignal(NULL), + mRightMouseTargetButton(nullptr), + mStartDragItemCallback(nullptr), + mHandleDragItemCallback(nullptr), + mHandleDropCallback(nullptr), + mButtonAddSignal(nullptr), + mButtonEnterSignal(nullptr), + mButtonLeaveSignal(nullptr), + mButtonRemoveSignal(nullptr), mDragAndDropTarget(false), - mCaretIcon(NULL), - mCenterPanel(NULL) + mCaretIcon(nullptr), + mCenterPanel(nullptr) { mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_WITH_TEXT] = p.button_icon_and_text; mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_ONLY] = p.button_icon; @@ -792,6 +792,11 @@ void LLToolBar::updateLayoutAsNeeded() mNeedsLayout = false; } +bool LLToolBar::postBuild() +{ + mCaretIcon = getChild<LLIconCtrl>("caret"); + return LLUICtrl::postBuild(); +} void LLToolBar::draw() { @@ -835,35 +840,36 @@ void LLToolBar::draw() LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); // Position the caret - if (!mCaretIcon) - { - mCaretIcon = getChild<LLIconCtrl>("caret"); - } - - LLIconCtrl* caret = mCaretIcon; - caret->setVisible(false); - if (mDragAndDropTarget && !mButtonCommands.empty()) + // Todo: This shouldn't be on draw, but, as example, on hover + if (mCaretIcon) { - LLRect caret_rect = caret->getRect(); - if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) - { - caret->setRect(LLRect(mDragx-caret_rect.getWidth()/2+1, - mDragy, - mDragx+caret_rect.getWidth()/2+1, - mDragy-mDragGirth)); - } - else + mCaretIcon->setVisible(false); + if (mDragAndDropTarget && !mButtonCommands.empty()) { - caret->setRect(LLRect(mDragx, - mDragy+caret_rect.getHeight()/2, - mDragx+mDragGirth, - mDragy-caret_rect.getHeight()/2)); + LLRect caret_rect = mCaretIcon->getRect(); + if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) + { + mCaretIcon->setRect(LLRect(mDragx - caret_rect.getWidth() / 2 + 1, + mDragy, + mDragx + caret_rect.getWidth() / 2 + 1, + mDragy - mDragGirth)); + } + else + { + mCaretIcon->setRect(LLRect(mDragx, + mDragy + caret_rect.getHeight() / 2, + mDragx + mDragGirth, + mDragy - caret_rect.getHeight() / 2)); + } + mCaretIcon->setVisible(true); } - caret->setVisible(true); } LLUICtrl::draw(); - caret->setVisible(false); + if (mCaretIcon) + { + mCaretIcon->setVisible(false); + } mDragAndDropTarget = false; } @@ -1054,7 +1060,7 @@ bool LLToolBar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, // if drop is set, it's time to call the callback to get the operation done if (handled && drop) { - handled = mHandleDropCallback(cargo_data, x, y, this); + handled = mHandleDropCallback(cargo_data, cargo_type, x, y, this); } // We accept only single tool drop on toolbars @@ -1097,12 +1103,12 @@ LLToolBarButton::LLToolBarButton(const Params& p) mWidthRange(p.button_width), mDesiredHeight(p.desired_height), mId(""), - mIsEnabledSignal(NULL), - mIsRunningSignal(NULL), - mIsStartingSignal(NULL), + mIsEnabledSignal(nullptr), + mIsRunningSignal(nullptr), + mIsStartingSignal(nullptr), mIsDragged(false), - mStartDragItemCallback(NULL), - mHandleDragItemCallback(NULL), + mStartDragItemCallback(nullptr), + mHandleDragItemCallback(nullptr), mOriginalImageSelected(p.image_selected), mOriginalImageUnselected(p.image_unselected), mOriginalImagePressed(p.image_pressed), @@ -1256,7 +1262,7 @@ void LLToolBar::LLCenterLayoutPanel::handleReshape(const LLRect& rect, bool by_u { LLLayoutPanel::handleReshape(rect, by_user); - if (!mReshapeCallback.empty()) + if (mReshapeCallback != nullptr) { LLRect r; localRectToOtherView(mButtonPanel->getRect(), &r, gFloaterView); diff --git a/indra/llui/lltoolbar.h b/indra/llui/lltoolbar.h index 5556406fbd..b4fb3fab02 100644 --- a/indra/llui/lltoolbar.h +++ b/indra/llui/lltoolbar.h @@ -39,9 +39,9 @@ class LLToolBar; class LLToolBarButton; class LLIconCtrl; -typedef boost::function<void (S32 x, S32 y, LLToolBarButton* button)> tool_startdrag_callback_t; -typedef boost::function<bool (S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type)> tool_handledrag_callback_t; -typedef boost::function<bool (void* data, S32 x, S32 y, LLToolBar* toolbar)> tool_handledrop_callback_t; +typedef std::function<void (S32 x, S32 y, LLToolBarButton* button)> tool_startdrag_callback_t; +typedef std::function<bool (S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type)> tool_handledrag_callback_t; +typedef std::function<bool (void* data, EDragAndDropType cargo_type, S32 x, S32 y, LLToolBar* toolbar)> tool_handledrop_callback_t; class LLToolBarButton : public LLButton { @@ -167,7 +167,7 @@ public: class LLCenterLayoutPanel : public LLLayoutPanel { public: - typedef boost::function<void(LLToolBarEnums::EToolBarLocation tb, const LLRect& rect)> reshape_callback_t; + typedef std::function<void(LLToolBarEnums::EToolBarLocation tb, const LLRect& rect)> reshape_callback_t; virtual ~LLCenterLayoutPanel() {} /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); @@ -213,6 +213,7 @@ public: }; // virtuals + bool postBuild(); void draw(); void reshape(S32 width, S32 height, bool called_from_parent = true); bool handleRightMouseDown(S32 x, S32 y, MASK mask); diff --git a/indra/llui/lltooltip.cpp b/indra/llui/lltooltip.cpp index 74f03618cf..c5b1b5ba1d 100644 --- a/indra/llui/lltooltip.cpp +++ b/indra/llui/lltooltip.cpp @@ -129,7 +129,7 @@ void LLToolTipView::drawStickyRect() } // defaults for floater param block pulled from widgets/floater.xml -static LLWidgetNameRegistry::StaticRegistrar sRegisterInspectorParams(&typeid(LLInspector::Params), "inspector"); +static LLWidgetNameRegistry::StaticRegistrar sRegisterInspectorParams(typeid(LLInspector::Params), "inspector"); // // LLToolTip diff --git a/indra/llui/lltooltip.h b/indra/llui/lltooltip.h index 760acddd6f..c9d6937c1a 100644 --- a/indra/llui/lltooltip.h +++ b/indra/llui/lltooltip.h @@ -67,8 +67,8 @@ public: struct Params : public LLInitParam::Block<Params, LLPanel::Params> { - typedef boost::function<void(void)> click_callback_t; - typedef boost::function<LLToolTip*(LLToolTip::Params)> create_callback_t; + typedef std::function<void(void)> click_callback_t; + typedef std::function<LLToolTip*(LLToolTip::Params)> create_callback_t; Optional<std::string> message; Multiple<StyledText> styled_message; diff --git a/indra/llui/lltrans.h b/indra/llui/lltrans.h index c5d01e6f8d..4dba4c5c3e 100644 --- a/indra/llui/lltrans.h +++ b/indra/llui/lltrans.h @@ -125,7 +125,7 @@ public: } private: - typedef std::map<std::string, LLTransTemplate, std::less<>> template_map_t; + typedef std::unordered_map<std::string, LLTransTemplate, ll::string_hash, std::equal_to<>> template_map_t; static template_map_t sStringTemplates; static template_map_t sDefaultStringTemplates; static LLStringUtil::format_map_t sDefaultArgs; diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index e7dea79eaa..5550223e1e 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -65,7 +65,6 @@ // for XUIParse #include "llquaternion.h" -#include <boost/tokenizer.hpp> #include <boost/algorithm/string/find_iterator.hpp> #include <boost/algorithm/string/finder.hpp> @@ -157,6 +156,7 @@ mWindow(NULL), // set later in startup mRootView(NULL), mHelpImpl(NULL) { + LL_PROFILE_ZONE_SCOPED; LLRender2D::createInstance(image_provider); LLSpellChecker::createInstance(); @@ -213,13 +213,8 @@ void LLUI::setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t& rem 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()); } @@ -231,7 +226,7 @@ void LLUI::getMousePositionScreen(S32 *x, S32 *y) 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]); -#if defined(LL_SDL) +#if defined(LL_SDL_WINDOW) *x = *x * LLView::getWindow()->getSystemUISize(); *y = *y * LLView::getWindow()->getSystemUISize(); #endif diff --git a/indra/llui/llui.h b/indra/llui/llui.h index b2dcb6dc88..091e0ab1cf 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -114,7 +114,7 @@ class LLUI : public LLSimpleton<LLUI> { LOG_CLASS(LLUI); public: - typedef std::map<std::string, LLControlGroup*, std::less<> > settings_map_t; + typedef std::unordered_map<std::string, LLControlGroup*, ll::string_hash, std::equal_to<>> settings_map_t; LLUI(const settings_map_t &settings, LLImageProviderInterface* image_provider, @@ -241,9 +241,9 @@ public: // // Methods // - typedef boost::function<void(LLView*)> add_popup_t; - typedef boost::function<void(LLView*)> remove_popup_t; - typedef boost::function<void(void)> clear_popups_t; + typedef std::function<void(LLView*)> add_popup_t; + typedef std::function<void(LLView*)> remove_popup_t; + typedef std::function<void(void)> clear_popups_t; void setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t&, const clear_popups_t& ); diff --git a/indra/llui/lluicolortable.cpp b/indra/llui/lluicolortable.cpp index a792cb8103..90ba6f1ecc 100644 --- a/indra/llui/lluicolortable.cpp +++ b/indra/llui/lluicolortable.cpp @@ -198,8 +198,8 @@ LLUIColor LLUIColorTable::getColor(std::string_view name, const LLColor4& defaul // update user color, loaded colors are parsed on initialization void LLUIColorTable::setColor(std::string_view name, const LLColor4& color) { - auto it = mUserSetColors.lower_bound(name); - if(it != mUserSetColors.end() && !(mUserSetColors.key_comp()(name, it->first))) + auto it = mUserSetColors.find(name); + if(it != mUserSetColors.end()) { it->second = color; } @@ -298,7 +298,7 @@ void LLUIColorTable::saveUserSettings() const if(!output_node->isNull()) { const std::string& filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "colors.xml"); - LLFILE *fp = LLFile::fopen(filename, "w"); + LLFILE *fp = LLFile::fopen(filename, LLFILE_MODE("w")); if(fp != NULL) { @@ -330,9 +330,8 @@ void LLUIColorTable::clearTable(string_color_map_t& table) // if the color already exists it changes the color void LLUIColorTable::setColor(std::string_view name, const LLColor4& color, string_color_map_t& table) { - string_color_map_t::iterator it = table.lower_bound(name); - if(it != table.end() - && !(table.key_comp()(name, it->first))) + string_color_map_t::iterator it = table.find(name); + if(it != table.end()) { it->second = color; } diff --git a/indra/llui/lluicolortable.h b/indra/llui/lluicolortable.h index 0c6286e5eb..aff6f59db6 100644 --- a/indra/llui/lluicolortable.h +++ b/indra/llui/lluicolortable.h @@ -27,7 +27,7 @@ #ifndef LL_LLUICOLORTABLE_H_ #define LL_LLUICOLORTABLE_H_ -#include <map> +#include <unordered_map> #include "llinitparam.h" #include "llsingleton.h" @@ -42,7 +42,7 @@ class LLUIColorTable : public LLSingleton<LLUIColorTable> LOG_CLASS(LLUIColorTable); // consider using sorted vector, can be much faster - typedef std::map<std::string, LLUIColor, std::less<>> string_color_map_t; + typedef std::unordered_map<std::string, LLUIColor, ll::string_hash, std::equal_to<>> string_color_map_t; public: struct ColorParams : LLInitParam::ChoiceBlock<ColorParams> diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index cbabb5a933..9a27049d37 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -80,7 +80,7 @@ LLUICtrl::Params::Params() mouseenter_callback("mouseenter_callback"), mouseleave_callback("mouseleave_callback"), control_name("control_name"), - font("font", LLFontGL::getFontEmojiMedium()), + font("font", LLFontGL::getFontEmojiSmall()), font_halign("halign"), font_valign("valign"), length("length"), // ignore LLXMLNode cruft @@ -999,15 +999,16 @@ bool LLUICtrl::findHelpTopic(std::string& help_topic_out) } // *TODO: Deprecate; for backwards compatability only: -boost::signals2::connection LLUICtrl::setCommitCallback( boost::function<void (LLUICtrl*,void*)> cb, void* data) +boost::signals2::connection LLUICtrl::setCommitCallback( std::function<void (LLUICtrl*,void*)> cb, void* data) { - return setCommitCallback( boost::bind(cb, _1, data)); + return setCommitCallback( std::bind(cb, std::placeholders::_1, data)); } -boost::signals2::connection LLUICtrl::setValidateBeforeCommit( boost::function<bool (const LLSD& data)> cb ) + +boost::signals2::connection LLUICtrl::setValidateBeforeCommit( std::function<bool (const LLSD& data)> cb ) { if (!mValidateSignal) mValidateSignal = new enable_signal_t(); - return mValidateSignal->connect(boost::bind(cb, _2)); + return mValidateSignal->connect(std::bind(cb, std::placeholders::_2)); } // virtual diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index bcaf479b0f..749999bbfe 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -31,7 +31,8 @@ //#include "llboost.h" #include "llrect.h" #include "llsd.h" -#include <boost/function.hpp> +#include <functional> +#include <boost/bind.hpp> #include <boost/signals2.hpp> #include "llinitparam.h" @@ -47,12 +48,12 @@ class LLUICtrl : public LLView, public boost::signals2::trackable { public: - typedef boost::function<void (LLUICtrl* ctrl, const LLSD& param)> commit_callback_t; + typedef std::function<void (LLUICtrl* ctrl, const LLSD& param)> commit_callback_t; typedef boost::signals2::signal<void (LLUICtrl* ctrl, const LLSD& param)> commit_signal_t; // *TODO: add xml support for this type of signal in the future typedef boost::signals2::signal<void (LLUICtrl* ctrl, S32 x, S32 y, MASK mask)> mouse_signal_t; - typedef boost::function<bool (LLUICtrl* ctrl, const LLSD& param)> enable_callback_t; + typedef std::function<bool (LLUICtrl* ctrl, const LLSD& param)> enable_callback_t; typedef boost::signals2::signal<bool (LLUICtrl* ctrl, const LLSD& param), boost_boolean_combiner> enable_signal_t; struct CallbackParam : public LLInitParam::Block<CallbackParam> @@ -258,8 +259,8 @@ public: boost::signals2::connection setDoubleClickCallback( const mouse_signal_t::slot_type& cb ); // *TODO: Deprecate; for backwards compatability only: - boost::signals2::connection setCommitCallback( boost::function<void (LLUICtrl*,void*)> cb, void* data); - boost::signals2::connection setValidateBeforeCommit( boost::function<bool (const LLSD& data)> cb ); + boost::signals2::connection setCommitCallback( std::function<void (LLUICtrl*,void*)> cb, void* data); + boost::signals2::connection setValidateBeforeCommit( std::function<bool (const LLSD& data)> cb ); LLUICtrl* findRootMostFocusRoot(); diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index a2a6d661ff..740f187ed8 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -67,7 +67,7 @@ public: static LLDefaultChildRegistry::Register<LLUICtrlLocate> r1("locate"); // Build time optimization, generate this once in .cpp file -template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance(); +template class LLUICtrlFactory* LLSimpleton<class LLUICtrlFactory>::getInstance(); //----------------------------------------------------------------------------- // LLUICtrlFactory() @@ -100,16 +100,19 @@ void LLUICtrlFactory::loadWidgetTemplate(const std::string& widget_tag, LLInitPa std::string base_filename = search_paths.front(); if (!base_filename.empty()) { - LLUICtrlFactory::instance().pushFileName(base_filename); + LLUICtrlFactory *factory = LLUICtrlFactory::getInstance(); + factory->mFileNames.push_back(base_filename); - if (!LLXMLNode::getLayeredXMLNode(root_node, search_paths)) + if (LLXMLNode::getLayeredXMLNode(root_node, search_paths)) + { + LLXUIParser parser; + parser.readXUI(root_node, block, base_filename); + } + else { LL_WARNS() << "Couldn't parse widget from: " << base_filename << LL_ENDL; - return; } - LLXUIParser parser; - parser.readXUI(root_node, block, base_filename); - LLUICtrlFactory::instance().popFileName(); + factory->mFileNames.pop_back(); } } @@ -250,7 +253,7 @@ const LLInitParam::BaseBlock& get_empty_param_block() // adds a widget and its param block to various registries //static -void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, const std::string& name) +void LLUICtrlFactory::registerWidget(std::type_index widget_type, std::type_index param_block_type, const std::string& name) { // associate parameter block type with template .xml file std::string* existing_name = LLWidgetNameRegistry::instance().getValue(param_block_type); diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index 91221dc7f3..75cee5f004 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -65,7 +65,7 @@ class LLDefaultChildRegistry : public LLChildRegistry<LLDefaultChildRegistry> // lookup widget name by type class LLWidgetNameRegistry -: public LLRegistrySingleton<const std::type_info*, std::string, LLWidgetNameRegistry> +: public LLRegistrySingleton<std::type_index, std::string, LLWidgetNameRegistry> { LLSINGLETON_EMPTY_CTOR(LLWidgetNameRegistry); }; @@ -74,19 +74,20 @@ class LLWidgetNameRegistry // this is used for schema generation //typedef const LLInitParam::BaseBlock& (*empty_param_block_func_t)(); //class LLDefaultParamBlockRegistry -//: public LLRegistrySingleton<const std::type_info*, empty_param_block_func_t, LLDefaultParamBlockRegistry> +//: public LLRegistrySingleton<std::type_index, empty_param_block_func_t, LLDefaultParamBlockRegistry> //{ // LLSINGLETON(LLDefaultParamBlockRegistry); //}; // Build time optimization, generate this once in .cpp file #ifndef LLUICTRLFACTORY_CPP -extern template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance(); +extern template class LLUICtrlFactory* LLSimpleton<class LLUICtrlFactory>::getInstance(); #endif -class LLUICtrlFactory : public LLSingleton<LLUICtrlFactory> +class LLUICtrlFactory : public LLSimpleton<LLUICtrlFactory> { - LLSINGLETON(LLUICtrlFactory); +public: + LLUICtrlFactory(); ~LLUICtrlFactory(); // only partial specialization allowed in inner classes, so use extra dummy parameter @@ -202,7 +203,7 @@ private: static void copyName(LLXMLNodePtr src, LLXMLNodePtr dest); // helper function for adding widget type info to various registries - static void registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, const std::string& tag); + static void registerWidget(std::type_index widget_type, std::type_index param_block_type, const std::string& tag); static void loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block); @@ -290,7 +291,7 @@ template <typename PARAM_BLOCK, int DUMMY> LLUICtrlFactory::ParamDefaults<PARAM_BLOCK, DUMMY>::ParamDefaults() { // look up template file for this param block... - const std::string* param_block_tag = LLWidgetNameRegistry::instance().getValue(&typeid(PARAM_BLOCK)); + const std::string* param_block_tag = LLWidgetNameRegistry::instance().getValue(typeid(PARAM_BLOCK)); if (param_block_tag) { // ...and if it exists, back fill values using the most specific template first PARAM_BLOCK params; @@ -311,15 +312,19 @@ LLUICtrlFactory::ParamDefaults<LLInitParam::BaseBlock, DUMMY>::ParamDefaults() { template<typename DERIVED> template<typename T> LLChildRegistry<DERIVED>::Register<T>::Register(const char* tag, LLWidgetCreatorFunc func) -: LLChildRegistry<DERIVED>::StaticRegistrar(tag, func.empty() ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder<T> : func) +: LLChildRegistry<DERIVED>::StaticRegistrar(tag, func == nullptr ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder<T> : func) { + if (!LLUICtrlFactory::instanceExists()) + { + LLUICtrlFactory::createInstance(); + } // add this widget to various registries - LLUICtrlFactory::instance().registerWidget(&typeid(T), &typeid(typename T::Params), tag); + LLUICtrlFactory::instance().registerWidget(typeid(T), typeid(typename T::Params), tag); // since registry_t depends on T, do this in line here // TODO: uncomment this for schema generation //typedef typename T::child_registry_t registry_t; - //LLChildRegistryRegistry::instance().defaultRegistrar().add(&typeid(T), registry_t::instance()); + //LLChildRegistryRegistry::instance().defaultRegistrar().add(typeid(T), registry_t::instance()); } #endif //LLUICTRLFACTORY_H diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp index b6b450c2a1..61a529c5d2 100644 --- a/indra/llui/llurlaction.cpp +++ b/indra/llui/llurlaction.cpp @@ -30,6 +30,7 @@ #include "llview.h" #include "llwindow.h" #include "llurlregistry.h" +#include "v3dmath.h" // global state for the callback functions @@ -116,6 +117,16 @@ void LLUrlAction::teleportToLocation(std::string url) } } +void LLUrlAction::zoomInObject(std::string url) +{ + LLUrlMatch match; + std::string object_id = getObjectId(url); + if (LLUUID::validate(object_id) && LLUrlRegistry::instance().findUrl(url, match)) + { + executeSLURL("secondlife:///app/object/" + object_id + "/zoomin/" + match.getLocation()); + } +} + void LLUrlAction::showLocationOnMap(std::string url) { LLUrlMatch match; @@ -128,6 +139,23 @@ void LLUrlAction::showLocationOnMap(std::string url) } } +void LLUrlAction::showParcelOnMap(std::string url) +{ + LLSD path_array = LLURI(url).pathArray(); + auto path_parts = path_array.size(); + + if (path_parts < 3) // no parcel id + { + LL_WARNS() << "Global coordinates are missing in url: [" << url << "]" << LL_ENDL; + return; + } + + LLVector3d parcel_pos = LLUrlEntryParcel::getParcelPos(LLUUID(LLURI::unescape(path_array[2]))); + std::ostringstream pos; + pos << parcel_pos.mdV[VX] << '/' << parcel_pos.mdV[VY] << '/' << parcel_pos.mdV[VZ]; + executeSLURL("secondlife:///app/worldmap_global/" + pos.str()); +} + void LLUrlAction::copyURLToClipboard(std::string url) { LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(url)); @@ -142,6 +170,16 @@ void LLUrlAction::copyLabelToClipboard(std::string url) } } +std::string LLUrlAction::getURLLabel(std::string url) +{ + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + return match.getLabel(); + } + return ""; +} + void LLUrlAction::showProfile(std::string url) { // Get id from 'secondlife:///app/{cmd}/{id}/{action}' @@ -231,13 +269,22 @@ void LLUrlAction::reportAbuse(std::string url) } } +void LLUrlAction::reportAbuseObj(std::string url) +{ + std::string object_id = getObjectId(url); + if (LLUUID::validate(object_id)) + { + executeSLURL("secondlife:///app/object/" + object_id + "/reportAbuse"); + } +} + void LLUrlAction::blockObject(std::string url) { std::string object_id = getObjectId(url); std::string object_name = getObjectName(url); if (LLUUID::validate(object_id)) { - executeSLURL("secondlife:///app/agent/" + object_id + "/block/" + LLURI::escape(object_name)); + executeSLURL("secondlife:///app/object/" + object_id + "/block/" + LLURI::escape(object_name)); } } @@ -247,6 +294,6 @@ void LLUrlAction::unblockObject(std::string url) std::string object_name = getObjectName(url); if (LLUUID::validate(object_id)) { - executeSLURL("secondlife:///app/agent/" + object_id + "/unblock/" + object_name); + executeSLURL("secondlife:///app/object/" + object_id + "/unblock/" + LLURI::escape(object_name)); } } diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h index ac9741a7ad..d110fe56d3 100644 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -28,8 +28,8 @@ #ifndef LL_LLURLACTION_H #define LL_LLURLACTION_H +#include <functional> #include <string> -#include <boost/function.hpp> /// /// The LLUrlAction class provides a number of static functions that @@ -60,9 +60,15 @@ public: /// if the Url specifies an SL location, teleport there static void teleportToLocation(std::string url); + /// If the Url specifies an object id, attempt to zoom in. + /// If not possible to zoom in, show on map + static void zoomInObject(std::string url); + /// if the Url specifies an SL location, show it on a map static void showLocationOnMap(std::string url); + static void showParcelOnMap(std::string url); + /// perform the appropriate action for left-clicking on a Url static void clickAction(std::string url, bool trusted_content); @@ -72,6 +78,8 @@ public: /// copy a Url to the clipboard static void copyURLToClipboard(std::string url); + static std::string getURLLabel(std::string url); + /// if the Url specifies an SL command in the form like 'app/{cmd}/{id}/*', show its profile static void showProfile(std::string url); static std::string getUserID(std::string url); @@ -81,12 +89,13 @@ public: static void addFriend(std::string url); static void removeFriend(std::string url); static void reportAbuse(std::string url); + static void reportAbuseObj(std::string url); static void blockObject(std::string url); static void unblockObject(std::string url); /// specify the callbacks to enable this class's functionality - typedef boost::function<void (const std::string&)> url_callback_t; - typedef boost::function<bool(const std::string& url, bool trusted_content)> execute_url_callback_t; + typedef std::function<void (const std::string&)> url_callback_t; + typedef std::function<bool(const std::string& url, bool trusted_content)> execute_url_callback_t; static void setOpenURLCallback(url_callback_t cb); static void setOpenURLInternalCallback(url_callback_t cb); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index bcd13b7f0b..bba80dd20e 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -41,8 +41,7 @@ #include "lluicolortable.h" #include "message.h" #include "llexperiencecache.h" - -#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" +#include "v3dmath.h" // Utility functions std::string localize_slapp_label(const std::string& url, const std::string& full_name); @@ -235,7 +234,7 @@ bool LLUrlEntryBase::isWikiLinkCorrect(const std::string &labeled_url) const || label.find("www.") != std::string::npos) && label.find("://") == std::string::npos) { - label = "http://" + label; + label = "https://" + label; } return !LLUrlRegistry::instance().hasUrl(label); @@ -316,7 +315,7 @@ std::string LLUrlEntryHTTP::getUrl(const std::string &string) const { if (string.find("://") == std::string::npos) { - return "http://" + escapeUrl(string); + return "https://" + escapeUrl(string); } return escapeUrl(string); } @@ -1084,6 +1083,7 @@ LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null); LLHost LLUrlEntryParcel::sRegionHost; bool LLUrlEntryParcel::sDisconnected(false); std::set<LLUrlEntryParcel*> LLUrlEntryParcel::sParcelInfoObservers; +std::map<LLUUID, LLVector3d> LLUrlEntryParcel::sParcelPos; /// /// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., @@ -1176,6 +1176,20 @@ void LLUrlEntryParcel::processParcelInfo(const LLParcelData& parcel_data) url_entry->onParcelInfoReceived(parcel_data.parcel_id.asString(), label); } } + if (sParcelPos.find(parcel_data.parcel_id) == sParcelPos.end()) + { + sParcelPos[parcel_data.parcel_id] = LLVector3d(parcel_data.global_x, parcel_data.global_y, parcel_data.global_z); + } +} + +// static +LLVector3d LLUrlEntryParcel::getParcelPos(const LLUUID& parcel_id) +{ + if (sParcelPos.find(parcel_id) != sParcelPos.end()) + { + return sParcelPos[parcel_id]; + } + return LLVector3d(); } // diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 6e7d2fc80f..efb5081103 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -41,6 +41,9 @@ #include <map> class LLAvatarName; +class LLVector3d; + +#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" typedef boost::signals2::signal<void (const std::string& url, const std::string& label, @@ -434,6 +437,8 @@ public: // Processes parcel label and triggers notifying observers. static void processParcelInfo(const LLParcelData& parcel_data); + static LLVector3d getParcelPos(const LLUUID& parcel_id); + // Next setters are used to update agent and viewer connection information // upon events like user login, viewer disconnect and user changing region host. // These setters are made public to be accessible from newview and should not be @@ -447,6 +452,7 @@ private: static LLHost sRegionHost; static bool sDisconnected; static std::set<LLUrlEntryParcel*> sParcelInfoObservers; + static std::map<LLUUID, LLVector3d> sParcelPos; }; /// diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index cb101d325d..8a21222b3c 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -149,12 +149,77 @@ static bool stringHasUrl(const std::string &text) // fast heuristic test for a URL in a string. This is used // to avoid lots of costly regex calls, BUT it needs to be // kept in sync with the LLUrlEntry regexes we support. - return (text.find("://") != std::string::npos || - text.find("www.") != std::string::npos || - text.find(".com") != std::string::npos || - text.find("<nolink>") != std::string::npos || - text.find("<icon") != std::string::npos || - text.find("@") != std::string::npos); + + // Early exit for empty or very short strings + // Smallest url is 5 characters + if (text.length() < 3) + { + return false; + } + + // Single pass search for common URL indicators + for (size_t i = 0; i < text.length(); ++i) + { + char c = text[i]; + + // Check for @ (email or mention) + if (c == '@') + { + return true; + } + + if (i + 3 >= text.length()) + { + // Nothing else is going to match or fit if we don't + // have at least 4 characters left + // Ex: expectation is that there is something after protocol delimiter + // and .com takes 4 characters. + return false; + } + + // Check for protocol delimiter + if (c == ':' && text[i + 1] == '/' && text[i + 2] == '/') + { + return true; + } + + // Check for www. at start of word + if (c == 'w' + && text[i + 1] == 'w' + && text[i + 2] == 'w' + && text[i + 3] == '.') + { + return true; + } + + // Check for .com (and similar) + if (c == '.') + { + const char* suffix = text.c_str() + i + 1; + if ((suffix[0] == 'c' && suffix[1] == 'o' && suffix[2] == 'm') || + (suffix[0] == 'n' && suffix[1] == 'e' && suffix[2] == 't') || + (suffix[0] == 'o' && suffix[1] == 'r' && suffix[2] == 'g') || + (suffix[0] == 'e' && suffix[1] == 'd' && suffix[2] == 'u')) + { + return true; + } + } + + // Check for <nolink> or <icon + if (c == '<') + { + if (i + 7 < text.length() && text.compare(i + 1, 6, "nolink") == 0) + { + return true; + } + if (i + 4 < text.length() && text.compare(i + 1, 4, "icon") == 0) + { + return true; + } + } + } + + return false; } bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted, bool skip_non_mentions) diff --git a/indra/llui/llview.h b/indra/llui/llview.h index 97212a9d2d..d747ef9555 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -48,9 +48,8 @@ #include "lltreeiterators.h" #include "llfocusmgr.h" +#include <functional> #include <list> -#include <boost/function.hpp> -#include <boost/noncopyable.hpp> class LLSD; @@ -627,7 +626,7 @@ private: LLView& getDefaultWidgetContainer() const; // This allows special mouse-event targeting logic for testing. - typedef boost::function<bool(const LLView*, S32 x, S32 y)> DrilldownFunc; + typedef std::function<bool(const LLView*, S32 x, S32 y)> DrilldownFunc; static DrilldownFunc sDrilldown; public: @@ -637,7 +636,7 @@ public: // LLView::TemporaryDrilldownFunc scoped_func(myfunctor); // // ... test with myfunctor ... // } // exiting block restores original LLView::sDrilldown - class TemporaryDrilldownFunc: public boost::noncopyable + class TemporaryDrilldownFunc { public: TemporaryDrilldownFunc(const DrilldownFunc& func): @@ -651,6 +650,10 @@ public: sDrilldown = mOldDrilldown; } + // Non-copyable + TemporaryDrilldownFunc(const TemporaryDrilldownFunc&) = delete; + TemporaryDrilldownFunc& operator=(const TemporaryDrilldownFunc&) = delete; + private: DrilldownFunc mOldDrilldown; }; diff --git a/indra/llui/llviewborder.cpp b/indra/llui/llviewborder.cpp index d53fd6eb91..68ca61681c 100644 --- a/indra/llui/llviewborder.cpp +++ b/indra/llui/llviewborder.cpp @@ -149,7 +149,7 @@ void LLViewBorder::drawOnePixelLines() top_color = gFocusMgr.getFocusColor(); bottom_color = top_color; - LLUI::setLineWidth(lerp(1.f, 3.f, gFocusMgr.getFocusFlashAmt())); + LLUI::setLineWidth(lerp(1.f, 2.f, gFocusMgr.getFocusFlashAmt())); } S32 left = 0; diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp index 6d907d7e45..9eefba1390 100644 --- a/indra/llui/llviewereventrecorder.cpp +++ b/indra/llui/llviewereventrecorder.cpp @@ -34,8 +34,6 @@ LLViewerEventRecorder::LLViewerEventRecorder() { logEvents = false; // Remove any previous event log file std::string old_log_ui_events_to_llsd_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.old"); - LLFile::remove(old_log_ui_events_to_llsd_file, ENOENT); - mLogFilename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.llsd"); LLFile::rename(mLogFilename, old_log_ui_events_to_llsd_file, ENOENT); diff --git a/indra/llui/llviewereventrecorder.h b/indra/llui/llviewereventrecorder.h index 5636c068d8..3bd8eb1f75 100644 --- a/indra/llui/llviewereventrecorder.h +++ b/indra/llui/llviewereventrecorder.h @@ -75,7 +75,7 @@ public: bool logEvents; std::string mLogFilename; - llofstream mLog; + llofstream mLog; private: diff --git a/indra/llui/llxuiparser.cpp b/indra/llui/llxuiparser.cpp index 8cd11b86b3..dfb978624d 100644 --- a/indra/llui/llxuiparser.cpp +++ b/indra/llui/llxuiparser.cpp @@ -602,7 +602,7 @@ void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& p LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); // add includes for all possible children - const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); + const std::type_index& type = *LLWidgetTypeRegistry::instance().getValue(type_name); const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); // add choices for valid children @@ -637,7 +637,7 @@ void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& p } } - LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); + LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), LLFILE_MODE("w")); LLXMLNode::writeHeaderToFile(xsd_file); root_nodep->writeToFile(xsd_file); fclose(xsd_file); @@ -1326,7 +1326,7 @@ void LLXUIParser::parserError(const std::string& message) struct ScopedFile { - ScopedFile( const std::string& filename, const char* accessmode ) + ScopedFile( const std::string& filename, const LLFile::fopen_flags_t* accessmode ) { mFile = LLFile::fopen(filename, accessmode); } @@ -1397,7 +1397,7 @@ bool LLSimpleXUIParser::readXUI(const std::string& filename, LLInitParam::BaseBl mCurReadDepth = 0; setParseSilently(silent); - ScopedFile file(filename, "rb"); + ScopedFile file(filename, LLFILE_MODE("rb")); if( !file.isOpen() ) { LL_WARNS("ReadXUI") << "Unable to open file " << filename << LL_ENDL; @@ -1459,7 +1459,7 @@ void LLSimpleXUIParser::characterDataHandler(void *userData, const char *s, int void LLSimpleXUIParser::characterData(const char *s, int len) { - mTextContents += std::string(s, len); + mTextContents.append(s, len); } void LLSimpleXUIParser::startElement(const char *name, const char **atts) @@ -1480,24 +1480,24 @@ void LLSimpleXUIParser::startElement(const char *name, const char **atts) mOutputStack.back().second++; S32 num_tokens_pushed = 0; - std::string child_name(name); + std::string_view child_name(name); if (mOutputStack.back().second == 1) { // root node for this block - mScope.push_back(child_name); + mScope.emplace_back(child_name); } else { // compound attribute if (child_name.find(".") == std::string::npos) { - mNameStack.push_back(std::make_pair(child_name, true)); + mNameStack.emplace_back(child_name, true); num_tokens_pushed++; - mScope.push_back(child_name); + mScope.emplace_back(child_name); } else { // parse out "dotted" name into individual tokens - tokenizer name_tokens(child_name, sep); + tokenizer name_tokens(std::string(child_name), sep); tokenizer::iterator name_token_it = name_tokens.begin(); if(name_token_it == name_tokens.end()) @@ -1603,8 +1603,8 @@ bool LLSimpleXUIParser::processText() LLStringUtil::trim(mTextContents); if (!mTextContents.empty()) { - mNameStack.push_back(std::make_pair(std::string("value"), true)); - mCurAttributeValueBegin = mTextContents.c_str(); + mNameStack.emplace_back("value", true); + mCurAttributeValueBegin = std::move(mTextContents); mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); mNameStack.pop_back(); } @@ -1648,12 +1648,12 @@ bool LLSimpleXUIParser::readFlag(Parser& parser, void* val_ptr) bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - if (!strcmp(self.mCurAttributeValueBegin, "true")) + if (!strcmp(self.mCurAttributeValueBegin.c_str(), "true")) { *((bool*)val_ptr) = true; return true; } - else if (!strcmp(self.mCurAttributeValueBegin, "false")) + else if (!strcmp(self.mCurAttributeValueBegin.c_str(), "false")) { *((bool*)val_ptr) = false; return true; @@ -1665,56 +1665,56 @@ bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr) bool LLSimpleXUIParser::readStringValue(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - *((std::string*)val_ptr) = self.mCurAttributeValueBegin; + *((std::string*)val_ptr) = std::move(self.mCurAttributeValueBegin); return true; } bool LLSimpleXUIParser::readU8Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U8*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), uint_p[assign_a(*(U8*)val_ptr)]).full; } bool LLSimpleXUIParser::readS8Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S8*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), int_p[assign_a(*(S8*)val_ptr)]).full; } bool LLSimpleXUIParser::readU16Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U16*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), uint_p[assign_a(*(U16*)val_ptr)]).full; } bool LLSimpleXUIParser::readS16Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S16*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), int_p[assign_a(*(S16*)val_ptr)]).full; } bool LLSimpleXUIParser::readU32Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U32*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), uint_p[assign_a(*(U32*)val_ptr)]).full; } bool LLSimpleXUIParser::readS32Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S32*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), int_p[assign_a(*(S32*)val_ptr)]).full; } bool LLSimpleXUIParser::readF32Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F32*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), real_p[assign_a(*(F32*)val_ptr)]).full; } bool LLSimpleXUIParser::readF64Value(Parser& parser, void* val_ptr) { LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); - return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F64*)val_ptr)]).full; + return parse(self.mCurAttributeValueBegin.c_str(), real_p[assign_a(*(F64*)val_ptr)]).full; } bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr) @@ -1722,7 +1722,7 @@ bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr) LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); LLColor4 value; - if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + if (parse(self.mCurAttributeValueBegin.c_str(), real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) { *(LLColor4*)(val_ptr) = value; return true; @@ -1736,7 +1736,7 @@ bool LLSimpleXUIParser::readUIColorValue(Parser& parser, void* val_ptr) LLColor4 value; LLUIColor* colorp = (LLUIColor*)val_ptr; - if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + if (parse(self.mCurAttributeValueBegin.c_str(), real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) { colorp->set(value); return true; @@ -1749,7 +1749,7 @@ bool LLSimpleXUIParser::readUUIDValue(Parser& parser, void* val_ptr) LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser); LLUUID temp_id; // LLUUID::set is destructive, so use temporary value - if (temp_id.set(std::string(self.mCurAttributeValueBegin))) + if (temp_id.set(self.mCurAttributeValueBegin)) { *(LLUUID*)(val_ptr) = temp_id; return true; diff --git a/indra/llui/llxuiparser.h b/indra/llui/llxuiparser.h index f755c12cbf..3cc564772e 100644 --- a/indra/llui/llxuiparser.h +++ b/indra/llui/llxuiparser.h @@ -31,7 +31,7 @@ #include "llregistry.h" #include "llxmlnode.h" -#include <boost/function.hpp> +#include <functional> #include <iosfwd> #include <stack> #include <set> @@ -40,19 +40,19 @@ class LLView; // lookup widget type by name class LLWidgetTypeRegistry -: public LLRegistrySingleton<std::string, const std::type_info*, LLWidgetTypeRegistry> +: public LLRegistrySingleton<std::string, std::type_index, LLWidgetTypeRegistry> { LLSINGLETON_EMPTY_CTOR(LLWidgetTypeRegistry); }; // global static instance for registering all widget types -typedef boost::function<LLView* (LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node)> LLWidgetCreatorFunc; +typedef std::function<LLView* (LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node)> LLWidgetCreatorFunc; typedef LLRegistry<std::string, LLWidgetCreatorFunc> widget_registry_t; class LLChildRegistryRegistry -: public LLRegistrySingleton<const std::type_info*, widget_registry_t, LLChildRegistryRegistry> +: public LLRegistrySingleton<std::type_index, widget_registry_t, LLChildRegistryRegistry> { LLSINGLETON_EMPTY_CTOR(LLChildRegistryRegistry); }; @@ -247,7 +247,7 @@ private: S32 mCurReadDepth; std::string mCurFileName; std::string mTextContents; - const char* mCurAttributeValueBegin; + std::string mCurAttributeValueBegin; std::vector<S32> mTokenSizeStack; std::vector<std::string> mScope; std::vector<bool> mEmptyLeafNode; diff --git a/indra/llui/llxyvector.cpp b/indra/llui/llxyvector.cpp index 1521823ce2..b3c38bf82d 100644 --- a/indra/llui/llxyvector.cpp +++ b/indra/llui/llxyvector.cpp @@ -211,7 +211,16 @@ void LLXYVector::draw() mGhostY = pointY; } - if (abs(mValueX) >= mIncrementX || abs(mValueY) >= mIncrementY) + S32 dx = abs(pointX - centerX); + S32 dy = abs(pointY - centerY); + bool draw_arrow = + (abs(mValueX) >= mIncrementX || abs(mValueY) >= mIncrementY) + && (dx >= 1 || dy >= 1); // At least 1 pixel displacement + + // Todo: Arrow doesn't display well with small values. + // Ex: (0.1, 0.05) will point to the right, as if it is (0.1, 0.0) + // as position will be offset by a single pixes on X, and 0 pixels on Y. + if (draw_arrow) { // draw the vector arrow drawArrow(centerX, centerY, pointX, pointY, mArrowColor); diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 95d1586495..8277c23691 100755..100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -50,16 +50,16 @@ LLAvatarNameCache::callback_connection_t LLAvatarNameCache::get(const LLUUID& ag // // Stub implementation for LLCacheName // -BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname) +bool LLCacheName::getFullName(const LLUUID& id, std::string& fullname) { fullname = "Lynx Linden"; - return TRUE; + return true; } -BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) +bool LLCacheName::getGroupName(const LLUUID& id, std::string& group) { group = "My Group"; - return TRUE; + return true; } boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback) |
