From e918d240fd516d9116f0d1c170df34039c63a3ac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 19 Sep 2025 01:00:43 +0300 Subject: #4679 Logging for crash on updateMenuOptions Logs say that viewer closed settings picker and immediately crashed Yet callstack indicates that some inventory was right clicked, which shouldn't be possible if picker already closed. May be some click is closing the picker and opens menu at the same time, but it's better to gather information first. --- indra/llui/llfolderview.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llui') diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index b664065532..db4ab8487e 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -1510,6 +1510,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(); -- cgit v1.3 From d7bd769129100332791569cbffa07f54d2fca991 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 22 Sep 2025 15:46:04 +0300 Subject: #4715 disable ZoomIn an object item when the region is not connected to the current region --- indra/llui/lltextbase.cpp | 20 ++++++++++++++++++ indra/llui/lltextbase.h | 3 +++ indra/newview/llchathistory.cpp | 9 ++++++-- indra/newview/llviewermenu.cpp | 2 +- indra/newview/llviewerobject.cpp | 45 ++++++++++++++++++++++++++++++++++++++++ indra/newview/llviewerobject.h | 3 +++ 6 files changed, 79 insertions(+), 3 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 3fe0df1848..69729fa421 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -185,6 +185,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), @@ -290,6 +291,7 @@ LLTextBase::~LLTextBase() delete mURLClickSignal; delete mIsFriendSignal; delete mIsObjectBlockedSignal; + delete mIsObjectReachableSignal; } void LLTextBase::initFromParams(const LLTextBase::Params& p) @@ -2281,6 +2283,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("zoom_in")) + { + zoom_btn->setEnabled(is_reachable); + } + } menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } @@ -3387,6 +3398,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 // diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 8ca653acb9..07cd9a1ee5 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -325,6 +325,7 @@ public: typedef boost::signals2::signal is_friend_signal_t; typedef boost::signals2::signal is_blocked_signal_t; + typedef boost::signals2::signal is_obj_reachable_signal_t; struct LineSpacingParams : public LLInitParam::ChoiceBlock { @@ -535,6 +536,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; } @@ -783,6 +785,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/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 2facc43811..c1af09ebc7 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -215,7 +215,8 @@ public: LLUUID obj_id = mObjectData["object_id"]; if (obj_id.notNull()) { - return nullptr != gObjectList.findObject(mAvatarID); + LLViewerObject* object = gObjectList.findObject(obj_id); + return object && object->isReachable(); } return false; } @@ -1118,7 +1119,11 @@ LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) mEditor = LLUICtrlFactory::create(editor_params, this); mEditor->setIsFriendCallback(LLAvatarActions::isFriend); mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); - + mEditor->setIsObjectReachableCallback([](const LLUUID& obj_id) + { + LLViewerObject* object = gObjectList.findObject(obj_id); + return object && object->isReachable(); + }); } LLSD LLChatHistory::getValue() const diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index f7688b762f..34a3745222 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -6516,7 +6516,7 @@ bool handle_zoom_to_object(const LLUUID& object_id) LLViewerObject* object = gObjectList.findObject(object_id); - if (object) + if (object && object->isReachable()) { gAgentCamera.setFocusOnAvatar(false, ANIMATE); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 9e77b40a45..ce93e6f371 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -7717,6 +7717,51 @@ void LLViewerObject::clearTEWaterExclusion(const U8 te) } } +bool LLViewerObject::isReachable() +{ + LLViewerRegion* agent_region = gAgent.getRegion(); + LLViewerRegion* object_region = getRegion(); + + if (!agent_region || !object_region) + { + return false; + } + if (agent_region == object_region) + { + return true; + } + + std::unordered_set visited; + std::queue pending; + visited.insert(agent_region); + pending.push(agent_region); + + while (!pending.empty()) + { + LLViewerRegion* current = pending.front(); + pending.pop(); + + std::vector neighbors; + current->getNeighboringRegions(neighbors); + + for (LLViewerRegion* neighbor : neighbors) + { + if (!neighbor) continue; + + if (neighbor == object_region) + { + return true; + } + // region's neighbors were not checked + if (visited.insert(neighbor).second) + { + pending.push(neighbor); + } + } + } + return false; +} + class ObjectPhysicsProperties : public LLHTTPNode { public: diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index 2b52ea2076..66ed3b8dfa 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -249,6 +249,9 @@ public: // Accessor functions LLViewerRegion* getRegion() const { return mRegionp; } + // Check if object is reachable from agent region by traversing loaded neighboring regions + bool isReachable(); + bool isSelected() const { return mUserSelected; } // Check whole linkset bool isAnySelected() const; -- cgit v1.3 From c2460e2dbd6dc3525703f5eb7312714716bb5bc8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:13:11 +0300 Subject: #4724 Fix performance problems with My Outfits --- indra/llui/llaccordionctrl.cpp | 8 +++- indra/llui/llaccordionctrltab.cpp | 25 ++++++++-- indra/llui/lltextbase.cpp | 2 + indra/newview/llinventoryitemslist.cpp | 85 ++++++++++++++++++++++++++++++---- indra/newview/llinventoryitemslist.h | 13 +++++- indra/newview/lloutfitgallery.cpp | 1 + indra/newview/lloutfitslist.cpp | 8 +++- 7 files changed, 122 insertions(+), 20 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index 495ba2f40f..1a64c2699d 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -303,8 +303,11 @@ void LLAccordionCtrl::ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S3 return; LLRect panel_rect = panel->getRect(); panel_rect.setLeftTopAndSize( left, top, width, height); - panel->reshape( width, height, 1); - panel->setRect(panel_rect); + if (panel->getRect() != panel_rect) + { + panel->reshape( width, height, 1); + panel->setRect(panel_rect); + } } void LLAccordionCtrl::ctrlShiftVertical(LLView* panel, S32 delta) @@ -494,6 +497,7 @@ void LLAccordionCtrl::arrangeMultiple() void LLAccordionCtrl::arrange() { + LL_PROFILE_ZONE_SCOPED; updateNoTabsHelpTextVisibility(); if (mAccordionTabs.empty()) diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index ac66525030..bdf93348bb 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -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; @@ -932,7 +947,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/lltextbase.cpp b/indra/llui/lltextbase.cpp index 69729fa421..1de12896eb 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1448,6 +1448,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(); } } diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index cfa37cc3ee..73cc953692 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -40,6 +40,10 @@ #include "llinventorymodel.h" #include "llviewerinventory.h" +bool LLInventoryItemsList::sListIdleRegistered = false; +LLInventoryItemsList::all_list_t LLInventoryItemsList::sAllLists; +LLInventoryItemsList::all_list_t::iterator LLInventoryItemsList::sAllListIter; + LLInventoryItemsList::Params::Params() {} @@ -55,13 +59,39 @@ LLInventoryItemsList::LLInventoryItemsList(const LLInventoryItemsList::Params& p setNoFilteredItemsMsg(LLTrans::getString("InventoryNoMatchingItems")); - gIdleCallbacks.addFunction(idle, this); + sAllLists.push_back(this); + sAllListIter = sAllLists.begin(); + + if (!sListIdleRegistered) + { + sAllListIter = sAllLists.begin(); + gIdleCallbacks.addFunction(idle, nullptr); + + LLEventPumps::instance().obtain("LLApp").listen( + "LLInventoryItemsList", + [](const LLSD& stat) + { + std::string status(stat["status"]); + if (status != "running") + { + // viewer is starting shutdown + gIdleCallbacks.deleteFunction(idle, nullptr); + } + return false; + }); + sListIdleRegistered = true; + } } // virtual LLInventoryItemsList::~LLInventoryItemsList() { - gIdleCallbacks.deleteFunction(idle, this); + auto it = std::find(sAllLists.begin(), sAllLists.end(), this); + if (it != sAllLists.end()) + { + sAllLists.erase(it); + sAllListIter = sAllLists.begin(); + } } void LLInventoryItemsList::refreshList(const LLInventoryModel::item_array_t item_array) @@ -111,25 +141,55 @@ void LLInventoryItemsList::updateSelection() mSelectTheseIDs.clear(); } -void LLInventoryItemsList::doIdle() +bool LLInventoryItemsList::doIdle() { - if (mRefreshState == REFRESH_COMPLETE) return; + if (mRefreshState == REFRESH_COMPLETE) return true; // done if (isInVisibleChain() || mForceRefresh || !getFilterSubString().empty()) { refresh(); mRefreshCompleteSignal(this, LLSD()); + return false; // keep going } + return true; // done } //static void LLInventoryItemsList::idle(void* user_data) { - LLInventoryItemsList* self = static_cast(user_data); - if ( self ) - { // Do the real idle - self->doIdle(); + if (sAllLists.empty()) + return; + + LL_PROFILE_ZONE_SCOPED; + + using namespace std::chrono; + auto start = steady_clock::now(); + const milliseconds time_limit = milliseconds(3); + const auto end_time = start + time_limit; + S32 max_update_count = 50; + + if (sAllListIter == sAllLists.end()) + { + sAllListIter = sAllLists.begin(); + } + + S32 updated = 0; + while (steady_clock::now() < end_time + && updated < max_update_count + && sAllListIter != sAllLists.end()) + { + LLInventoryItemsList* list = *sAllListIter; + // Refresh is split into multiple separate parts, + // so keep repeating it while there is time, until done. + // Todo: refresh() split is pointless now? + // Or still useful for large folders? + if (list->doIdle()) + { + // Item is done + ++sAllListIter; + updated++; + } } } @@ -141,6 +201,7 @@ void LLInventoryItemsList::refresh() { case REFRESH_ALL: { + LL_PROFILE_ZONE_NAMED("items_refresh_all"); mAddedItems.clear(); mRemovedItems.clear(); computeDifference(getIDs(), mAddedItems, mRemovedItems); @@ -163,6 +224,7 @@ void LLInventoryItemsList::refresh() } case REFRESH_LIST_ERASE: { + LL_PROFILE_ZONE_NAMED("items_refresh_erase"); uuid_vec_t::const_iterator it = mRemovedItems.begin(); for (; mRemovedItems.end() != it; ++it) { @@ -175,6 +237,7 @@ void LLInventoryItemsList::refresh() } case REFRESH_LIST_APPEND: { + LL_PROFILE_ZONE_NAMED("items_refresh_append"); static const unsigned ADD_LIMIT = 25; // Note: affects perfomance unsigned int nadded = 0; @@ -239,6 +302,7 @@ void LLInventoryItemsList::refresh() } case REFRESH_LIST_SORT: { + LL_PROFILE_ZONE_NAMED("items_refresh_sort"); // Filter, sort, rearrange and notify parent about shape changes filterItems(true, true); @@ -255,7 +319,10 @@ void LLInventoryItemsList::refresh() break; } default: - break; + { + mRefreshState = REFRESH_COMPLETE; + break; + } } setForceRefresh(mRefreshState != REFRESH_COMPLETE); diff --git a/indra/newview/llinventoryitemslist.h b/indra/newview/llinventoryitemslist.h index 9ebeb5e52b..b20c27eec8 100644 --- a/indra/newview/llinventoryitemslist.h +++ b/indra/newview/llinventoryitemslist.h @@ -59,7 +59,10 @@ public: * Sets the flag indicating that the list needs to be refreshed even if it is * not currently visible. */ - void setForceRefresh(bool force_refresh) { mForceRefresh = force_refresh; } + void setForceRefresh(bool force_refresh) + { + mForceRefresh = force_refresh; + } /** * If refreshes when invisible. @@ -76,7 +79,7 @@ public: * This is needed for example to filter items of the list hidden by closed * accordion tab. */ - virtual void doIdle(); // Real idle routine + bool doIdle(); // Real idle routine static void idle(void* user_data); // static glue to doIdle() protected: @@ -126,6 +129,12 @@ private: bool mForceRefresh; commit_signal_t mRefreshCompleteSignal; + + // Update synchronization + typedef std::vector all_list_t; + static all_list_t sAllLists; + static all_list_t::iterator sAllListIter; + static bool sListIdleRegistered; }; #endif //LL_LLINVENTORYITEMSLIST_H diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index 3adee9fa16..8589afae06 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -818,6 +818,7 @@ void LLOutfitGallery::getCurrentCategories(uuid_vec_t& vcur) void LLOutfitGallery::updateAddedCategory(LLUUID cat_id) { + LL_PROFILE_ZONE_SCOPED; LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); if (!cat) return; diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 4e594af432..58cd9fab83 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -181,6 +181,7 @@ void LLOutfitsList::onOpen(const LLSD& info) void LLOutfitsList::updateAddedCategory(LLUUID cat_id) { + LL_PROFILE_ZONE_SCOPED; LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); if (!cat) return; @@ -251,7 +252,9 @@ void LLOutfitsList::updateAddedCategory(LLUUID cat_id) if (AISAPI::isAvailable() && LLInventoryModelBackgroundFetch::instance().folderFetchActive()) { - // for reliability just fetch it whole, linked items included + // For reliability just fetch it whole, linked items included + // Todo: list is not warrantied to exist once callback arrives + // Fix it! LLInventoryModelBackgroundFetch::instance().fetchFolderAndLinks(cat_id, [cat_id, list] { if (list) @@ -1059,6 +1062,7 @@ void LLOutfitListBase::onIdle(void* userdata) void LLOutfitListBase::onIdleRefreshList() { + LL_PROFILE_ZONE_SCOPED; if (LLAppViewer::instance()->quitRequested()) { mRefreshListState.CategoryUUID.setNull(); @@ -1072,7 +1076,7 @@ void LLOutfitListBase::onIdleRefreshList() return; } - const F64 MAX_TIME = 0.05f; + const F64 MAX_TIME = 0.005f; F64 curent_time = LLTimer::getTotalSeconds(); const F64 end_time = curent_time + MAX_TIME; -- cgit v1.3 From 8976d69dba6a45d7a507d15794e98e26d00c0ea3 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 24 Sep 2025 21:32:12 +0300 Subject: #4723 Fix emoji warnings and missing emojis --- indra/llui/llemojidictionary.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'indra/llui') 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 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()) { -- cgit v1.3 From d5e8f51b615cee91bf072e2c3b111c7f5af0e052 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 29 Sep 2025 21:51:30 +0300 Subject: #4750 Crash in LLToolBarView::handleDropTool --- indra/llui/lltoolbar.cpp | 2 +- indra/llui/lltoolbar.h | 2 +- indra/newview/llfloatertoybox.cpp | 2 +- indra/newview/lltoolbarview.cpp | 29 +++++++++++++++++++---------- indra/newview/lltoolbarview.h | 2 +- 5 files changed, 23 insertions(+), 14 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index 5955a28fa3..56ab6e9bae 100644 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -1054,7 +1054,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 diff --git a/indra/llui/lltoolbar.h b/indra/llui/lltoolbar.h index 5556406fbd..a3f044c256 100644 --- a/indra/llui/lltoolbar.h +++ b/indra/llui/lltoolbar.h @@ -41,7 +41,7 @@ class LLIconCtrl; typedef boost::function tool_startdrag_callback_t; typedef boost::function tool_handledrag_callback_t; -typedef boost::function tool_handledrop_callback_t; +typedef boost::function tool_handledrop_callback_t; class LLToolBarButton : public LLButton { diff --git a/indra/newview/llfloatertoybox.cpp b/indra/newview/llfloatertoybox.cpp index f6257dbd3d..5e3ec366d5 100644 --- a/indra/newview/llfloatertoybox.cpp +++ b/indra/newview/llfloatertoybox.cpp @@ -63,7 +63,7 @@ bool LLFloaterToybox::postBuild() mToolBar->setStartDragCallback(boost::bind(LLToolBarView::startDragTool,_1,_2,_3)); mToolBar->setHandleDragCallback(boost::bind(LLToolBarView::handleDragTool,_1,_2,_3,_4)); - mToolBar->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4)); + mToolBar->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4,_5)); mToolBar->setButtonEnterCallback(boost::bind(&LLFloaterToybox::onToolBarButtonEnter,this,_1)); // diff --git a/indra/newview/lltoolbarview.cpp b/indra/newview/lltoolbarview.cpp index 0063e0b7fd..c1ec5fa183 100644 --- a/indra/newview/lltoolbarview.cpp +++ b/indra/newview/lltoolbarview.cpp @@ -110,7 +110,7 @@ bool LLToolBarView::postBuild() { mToolbars[i]->setStartDragCallback(boost::bind(LLToolBarView::startDragTool,_1,_2,_3)); mToolbars[i]->setHandleDragCallback(boost::bind(LLToolBarView::handleDragTool,_1,_2,_3,_4)); - mToolbars[i]->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4)); + mToolbars[i]->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4,_5)); mToolbars[i]->setButtonAddCallback(boost::bind(LLToolBarView::onToolBarButtonAdded,_1)); mToolbars[i]->setButtonRemoveCallback(boost::bind(LLToolBarView::onToolBarButtonRemoved,_1)); } @@ -624,8 +624,14 @@ bool LLToolBarView::handleDragTool( S32 x, S32 y, const LLUUID& uuid, LLAssetTyp return false; } -bool LLToolBarView::handleDropTool( void* cargo_data, S32 x, S32 y, LLToolBar* toolbar) +bool LLToolBarView::handleDropTool( void* cargo_data, EDragAndDropType cargo_type, S32 x, S32 y, LLToolBar* toolbar) { + if (cargo_type == DAD_PERSON) + { + // DAD_PERSON means that cargo_data contains an uuid, not an LLInventoryObject + resetDragTool(NULL); + return false; + } bool handled = false; LLInventoryObject* inv_item = static_cast(cargo_data); @@ -647,15 +653,18 @@ bool LLToolBarView::handleDropTool( void* cargo_data, S32 x, S32 y, LLToolBar* t if (old_toolbar_loc != LLToolBarEnums::TOOLBAR_NONE) { llassert(gToolBarView->mDragToolbarButton); - old_toolbar = gToolBarView->mDragToolbarButton->getParentByType(); - if (old_toolbar->isReadOnly() && toolbar->isReadOnly()) - { - // do nothing - } - else + if (gToolBarView->mDragToolbarButton) { - int old_rank = LLToolBar::RANK_NONE; - gToolBarView->removeCommand(command_id, old_rank); + old_toolbar = gToolBarView->mDragToolbarButton->getParentByType(); + if (old_toolbar->isReadOnly() && toolbar->isReadOnly()) + { + // do nothing + } + else + { + int old_rank = LLToolBar::RANK_NONE; + gToolBarView->removeCommand(command_id, old_rank); + } } } diff --git a/indra/newview/lltoolbarview.h b/indra/newview/lltoolbarview.h index 7cecd81052..7212f099b7 100644 --- a/indra/newview/lltoolbarview.h +++ b/indra/newview/lltoolbarview.h @@ -92,7 +92,7 @@ public: static void startDragTool(S32 x, S32 y, LLToolBarButton* toolbarButton); static bool handleDragTool(S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type); - static bool handleDropTool(void* cargo_data, S32 x, S32 y, LLToolBar* toolbar); + static bool handleDropTool(void* cargo_data, EDragAndDropType cargo_type, S32 x, S32 y, LLToolBar* toolbar); static void resetDragTool(LLToolBarButton* toolbarButton); LLInventoryObject* getDragItem(); LLView* getBottomToolbar() { return mBottomToolbarPanel; } -- cgit v1.3 From 503affdc9799311e6adeaf0271e470c46f8ceda8 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 1 Oct 2025 17:54:46 +0300 Subject: #vp484 Crash in LLFolderViewFolder::setOpen --- indra/llui/llfolderviewitem.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 9ca77dbe46..0f9fd35868 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -2106,10 +2106,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 view_model_item = mViewModelItem; + doOnIdleOneTime([view_model_item]() + { + if (view_model_item.notNull()) + { + view_model_item.get()->navigateToFolder(); + } + }); } else { -- cgit v1.3 From 167b7bae6294045ee873bb23c67ceadc38ba1184 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 14 Oct 2025 21:53:09 +0300 Subject: #3263 Repeat Notification Noise on Find Emoji Floater --- indra/llui/llemojihelper.cpp | 10 ++++++++++ indra/newview/llfloateremojipicker.cpp | 9 +++++++++ 2 files changed, 19 insertions(+) (limited to 'indra/llui') 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/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index c5f4a2f0cf..ab76f3473b 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -377,6 +377,9 @@ void LLFloaterEmojiPicker::initialize() { if (!mHint.empty()) { + // Hack: Trying to open floater, search for a match, + // and hide floater immediately if no match found, + // instead of checking prior to opening hideFloater(); return; } @@ -406,6 +409,12 @@ void LLFloaterEmojiPicker::initialize() return; } + if (!mHint.empty() && getSoundFlags() == LLView::SILENT) + { + // Sounds were supressed + make_ui_sound("UISndWindowOpen"); + } + mGroups->setVisible(true); mPreview->setIcon(nullptr); showPreview(true); -- cgit v1.3 From 6cdd9cff7057474aa810943a37bed6c4720b4244 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 15 Oct 2025 19:00:44 +0200 Subject: Fix C26444 compiler warning (creating unnamed RAII instances that could lead to unintended behavior) --- indra/llui/llfloater.cpp | 2 +- indra/newview/pipeline.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index fd07b2ec5d..52a5e3dbd6 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -2274,7 +2274,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); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 8b6680ee10..fc66e9283e 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -4340,7 +4340,7 @@ void LLPipeline::renderPhysicsDisplay() gGL.flush(); gDebugProgram.bind(); - LLGLEnable(GL_POLYGON_OFFSET_LINE); + LLGLEnable polygon_offset_line(GL_POLYGON_OFFSET_LINE); glPolygonOffset(3.f, 3.f); glLineWidth(3.f); LLGLEnable blend(GL_BLEND); -- cgit v1.3 From b011263b60d95048f89947717f6cb915f3628aae Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:14:06 +0300 Subject: #3379 Fix callback cleanup Instead of marking as 0, mark for cleanup --- indra/llui/llfolderviewitem.cpp | 18 +++++++++++++----- indra/llui/llfolderviewitem.h | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 0f9fd35868..878f1cb856 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -1884,10 +1884,18 @@ void LLFolderViewFolder::updateHasFavorites(bool new_childs_value) void LLFolderViewFolder::onIdleUpdateFavorites(void* data) { LLFolderViewFolder* self = reinterpret_cast(data); + 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) { - // already processed either on previous run or by a different callback - gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + llassert(false); // should not happen, everything that sets to 0 should clean callback + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, data); return; } @@ -1915,7 +1923,7 @@ void LLFolderViewFolder::onIdleUpdateFavorites(void* data) // 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 = 0; + parent->mFavoritesDirtyFlags = FAVORITE_CLEANUP; } parent = parent->getParentFolder(); } @@ -1981,7 +1989,7 @@ void LLFolderViewFolder::onIdleUpdateFavorites(void* data) // 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 = 0; + parent->mFavoritesDirtyFlags = FAVORITE_CLEANUP; } parent = parent->getParentFolder(); } @@ -1992,7 +2000,7 @@ void LLFolderViewFolder::onIdleUpdateFavorites(void* data) // 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 = 0; + parent->mFavoritesDirtyFlags = FAVORITE_CLEANUP; } parent = parent->getParentFolder(); } diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index c9b003b892..23d794bf26 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -421,6 +421,7 @@ private: constexpr static S32 FAVORITE_ADDED = 1; constexpr static S32 FAVORITE_REMOVED = 2; + constexpr static S32 FAVORITE_CLEANUP = 4; S32 mFavoritesDirtyFlags { 0 }; public: -- cgit v1.3 From e12958161c6f4edec1300db2045bba97aff26048 Mon Sep 17 00:00:00 2001 From: RolfKal Date: Wed, 15 Oct 2025 01:00:42 +0200 Subject: Improve LLFile to be consistent between Windows and Linux/Mac --- indra/llcommon/llfile.cpp | 352 ++++++++++++++++++++++------------ indra/llcommon/llfile.h | 90 +++++++-- indra/llcommon/llsys.cpp | 8 - indra/llcrashlogger/llcrashlogger.cpp | 4 - indra/llfilesystem/llfilesystem.cpp | 3 - indra/llui/llviewereventrecorder.cpp | 2 - indra/newview/llvoicevivox.cpp | 1 - 7 files changed, 302 insertions(+), 158 deletions(-) (limited to 'indra/llui') diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 53659ac13f..a24524d699 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -35,7 +35,6 @@ #if LL_WINDOWS #include "llwin32headers.h" -#include // Windows errno #include #else #include @@ -49,6 +48,86 @@ static std::string empty; // variants of strerror() to report errors. #if LL_WINDOWS +namespace +{ + struct errentry + { + unsigned long oserr; // OS return value + int errcode; // System V error code + }; +} + +static errentry const errtable[] +{ + { ERROR_INVALID_FUNCTION, EINVAL }, // 1 + { ERROR_FILE_NOT_FOUND, ENOENT }, // 2 + { ERROR_PATH_NOT_FOUND, ENOENT }, // 3 + { ERROR_TOO_MANY_OPEN_FILES, EMFILE }, // 4 + { ERROR_ACCESS_DENIED, EACCES }, // 5 + { ERROR_INVALID_HANDLE, EBADF }, // 6 + { ERROR_ARENA_TRASHED, ENOMEM }, // 7 + { ERROR_NOT_ENOUGH_MEMORY, ENOMEM }, // 8 + { ERROR_INVALID_BLOCK, ENOMEM }, // 9 + { ERROR_BAD_ENVIRONMENT, E2BIG }, // 10 + { ERROR_BAD_FORMAT, ENOEXEC }, // 11 + { ERROR_INVALID_ACCESS, EINVAL }, // 12 + { ERROR_INVALID_DATA, EINVAL }, // 13 + { ERROR_INVALID_DRIVE, ENOENT }, // 15 + { ERROR_CURRENT_DIRECTORY, EACCES }, // 16 + { ERROR_NOT_SAME_DEVICE, EXDEV }, // 17 + { ERROR_NO_MORE_FILES, ENOENT }, // 18 + { ERROR_LOCK_VIOLATION, EACCES }, // 33 + { ERROR_BAD_NETPATH, ENOENT }, // 53 + { ERROR_NETWORK_ACCESS_DENIED, EACCES }, // 65 + { ERROR_BAD_NET_NAME, ENOENT }, // 67 + { ERROR_FILE_EXISTS, EEXIST }, // 80 + { ERROR_CANNOT_MAKE, EACCES }, // 82 + { ERROR_FAIL_I24, EACCES }, // 83 + { ERROR_INVALID_PARAMETER, EINVAL }, // 87 + { ERROR_NO_PROC_SLOTS, EAGAIN }, // 89 + { ERROR_DRIVE_LOCKED, EACCES }, // 108 + { ERROR_BROKEN_PIPE, EPIPE }, // 109 + { ERROR_DISK_FULL, ENOSPC }, // 112 + { ERROR_INVALID_TARGET_HANDLE, EBADF }, // 114 + { ERROR_WAIT_NO_CHILDREN, ECHILD }, // 128 + { ERROR_CHILD_NOT_COMPLETE, ECHILD }, // 129 + { ERROR_DIRECT_ACCESS_HANDLE, EBADF }, // 130 + { ERROR_NEGATIVE_SEEK, EINVAL }, // 131 + { ERROR_SEEK_ON_DEVICE, EACCES }, // 132 + { ERROR_DIR_NOT_EMPTY, ENOTEMPTY }, // 145 + { ERROR_NOT_LOCKED, EACCES }, // 158 + { ERROR_BAD_PATHNAME, ENOENT }, // 161 + { ERROR_MAX_THRDS_REACHED, EAGAIN }, // 164 + { ERROR_LOCK_FAILED, EACCES }, // 167 + { ERROR_ALREADY_EXISTS, EEXIST }, // 183 + { ERROR_FILENAME_EXCED_RANGE, ENOENT }, // 206 + { ERROR_NESTING_NOT_ALLOWED, EAGAIN }, // 215 + { ERROR_NO_UNICODE_TRANSLATION, EILSEQ }, // 1113 + { ERROR_NOT_ENOUGH_QUOTA, ENOMEM } // 1816 +}; + +// Number of elements in the error translation table +#define ERRTABLECOUNT (sizeof(errtable) / sizeof(errtable[0])) + +static int set_errno_from_oserror(unsigned long oserr) +{ + if (!oserr) + return 0; + + // Check the table for the OS error code + for (unsigned i{0}; i < ERRTABLECOUNT; ++i) + { + if (oserr == errtable[i].oserr) + { + _set_errno(errtable[i].errcode); + return -1; + } + } + + _set_errno(EINVAL); + return -1; +} + // On Windows, use strerror_s(). std::string strerr(int errn) { @@ -57,7 +136,61 @@ std::string strerr(int errn) return buffer; } -typedef std::basic_ios > _Myios; +inline bool is_slash(wchar_t const c) +{ + return c == L'\\' || c == L'/'; +} + +static std::wstring utf8path_to_wstring(const std::string& utf8path) +{ + if (utf8path.size() >= MAX_PATH) + { + // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names + std::wstring utf16path = L"\\\\?\\" + ll_convert(utf8path); + // We need to make sure that the path does not contain forward slashes as above + // prefix does bypass the path normalization that replaces slashes with backslashes + // before passing the path to kernel mode APIs + std::replace(utf16path.begin(), utf16path.end(), L'/', L'\\'); + return utf16path; + } + return ll_convert(utf8path); +} + +static unsigned short get_fileattr(const std::wstring& utf16path) +{ + unsigned short st_mode = 0; + HANDLE file_handle = CreateFileW(utf16path.c_str(), FILE_READ_ATTRIBUTES, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (file_handle == INVALID_HANDLE_VALUE) + { + set_errno_from_oserror(GetLastError()); + return 0; + } + + FILE_ATTRIBUTE_TAG_INFO attribute_info; + if (GetFileInformationByHandleEx(file_handle, FileAttributeTagInfo, &attribute_info, sizeof(attribute_info))) + { + // A volume path alone (only drive letter) is not recognized as directory while it technically is + bool is_directory = (attribute_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) || + (iswalpha(utf16path[0]) && utf16path[1] == ':' && + (!utf16path[2] || (is_slash(utf16path[2]) && !utf16path[3]))); + st_mode |= is_directory ? S_IFDIR : S_IFREG; + st_mode |= (attribute_info.FileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD | S_IWRITE; + // we do not try to guess executable flag + + // propagate user bits to group/other fields: + st_mode |= (st_mode & 0700) >> 3; + st_mode |= (st_mode & 0700) >> 6; + } + else + { + // Retrieve last error before calling CloseHandle() + set_errno_from_oserror(GetLastError()); + } + CloseHandle(file_handle); + return st_mode; +} #else // On Posix we want to call strerror_r(), but alarmingly, there are two @@ -108,18 +241,13 @@ std::string strerr(int errn) } #endif // ! LL_WINDOWS -// On either system, shorthand call just infers global 'errno'. -std::string strerr() -{ - return strerr(errno); -} - -int warnif(const std::string& desc, const std::string& filename, int rc, int accept=0) +int warnif(const std::string& desc, const std::string& filename, int rc, int accept = 0) { if (rc < 0) { // Capture errno before we start emitting output int errn = errno; + // For certain operations, a particular errno value might be // acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit // EEXIST. Don't warn if caller explicitly says this errno is okay. @@ -176,62 +304,58 @@ int warnif(const std::string& desc, const std::string& filename, int rc, int acc // static int LLFile::mkdir(const std::string& dirname, int perms) { + // We often use mkdir() to ensure the existence of a directory that might + // already exist. There is no known case in which we want to call out as + // an error the requested directory already existing. #if LL_WINDOWS // permissions are ignored on Windows - std::wstring utf16dirname = ll_convert(dirname); - int rc = _wmkdir(utf16dirname.c_str()); + int rc = 0; + std::wstring utf16dirname = utf8path_to_wstring(dirname); + if (!CreateDirectoryW(utf16dirname.c_str(), nullptr)) + { + // Only treat other errors than an already existing file as a real error + unsigned long oserr = GetLastError(); + if (oserr != ERROR_ALREADY_EXISTS) + { + rc = set_errno_from_oserror(oserr); + } + } #else int rc = ::mkdir(dirname.c_str(), (mode_t)perms); -#endif - // We often use mkdir() to ensure the existence of a directory that might - // already exist. There is no known case in which we want to call out as - // an error the requested directory already existing. if (rc < 0 && errno == EEXIST) { // this is not the error you want, move along return 0; } +#endif // anything else might be a problem - return warnif("mkdir", dirname, rc, EEXIST); + return warnif("mkdir", dirname, rc); } // static -int LLFile::rmdir(const std::string& dirname) +int LLFile::rmdir(const std::string& dirname, int supress_error) { #if LL_WINDOWS - // permissions are ignored on Windows - std::wstring utf16dirname = ll_convert(dirname); + std::wstring utf16dirname = utf8path_to_wstring(dirname); int rc = _wrmdir(utf16dirname.c_str()); #else int rc = ::rmdir(dirname.c_str()); #endif - return warnif("rmdir", dirname, rc); + return warnif("rmdir", dirname, rc, supress_error); } // static LLFILE* LLFile::fopen(const std::string& filename, const char* mode) /* Flawfinder: ignore */ { #if LL_WINDOWS - std::wstring utf16filename = ll_convert(filename); + std::wstring utf16filename = utf8path_to_wstring(filename); std::wstring utf16mode = ll_convert(std::string(mode)); - return _wfopen(utf16filename.c_str(),utf16mode.c_str()); + return _wfopen(utf16filename.c_str(), utf16mode.c_str()); #else return ::fopen(filename.c_str(),mode); /* Flawfinder: ignore */ #endif } -LLFILE* LLFile::_fsopen(const std::string& filename, const char* mode, int sharingFlag) -{ -#if LL_WINDOWS - std::wstring utf16filename = ll_convert(filename); - std::wstring utf16mode = ll_convert(std::string(mode)); - return _wfsopen(utf16filename.c_str(),utf16mode.c_str(),sharingFlag); -#else - llassert(0);//No corresponding function on non-windows - return NULL; -#endif -} - int LLFile::close(LLFILE * file) { int ret_value = 0; @@ -264,8 +388,27 @@ std::string LLFile::getContents(const std::string& filename) int LLFile::remove(const std::string& filename, int supress_error) { #if LL_WINDOWS - std::wstring utf16filename = ll_convert(filename); - int rc = _wremove(utf16filename.c_str()); + // Posix remove() works on both files and directories although on Windows + // remove() and its wide char variant _wremove() only removes files just + // as its siblings unlink() and _wunlink(). + // If we really only want to support files we should instead use + // unlink() in the non-Windows part below too + int rc = 0; + ; + std::wstring utf16filename = utf8path_to_wstring(filename); + unsigned short st_mode = get_fileattr(utf16filename); + if (S_ISDIR(st_mode)) + { + rc = _wrmdir(utf16filename.c_str()); + } + else if (S_ISREG(st_mode)) + { + rc = _wunlink(utf16filename.c_str()); + } + else + { + rc = set_errno_from_oserror(ERROR_INVALID_PARAMETER); + } #else int rc = ::remove(filename.c_str()); #endif @@ -275,28 +418,38 @@ int LLFile::remove(const std::string& filename, int supress_error) int LLFile::rename(const std::string& filename, const std::string& newname, int supress_error) { #if LL_WINDOWS - std::wstring utf16filename = ll_convert(filename); - std::wstring utf16newname = ll_convert(newname); - int rc = _wrename(utf16filename.c_str(),utf16newname.c_str()); + // Posix rename() will gladly overwrite a file at newname if it exists, the Windows + // rename(), respectively _wrename(), will bark on that. Instead call directly the Windows + // API MoveFileEx() and use its flags to specify that overwrite is allowed. + std::wstring utf16filename = utf8path_to_wstring(filename); + std::wstring utf16newname = utf8path_to_wstring(newname); + int rc = 0; + if (!MoveFileExW(utf16filename.c_str(), utf16newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + { + rc = set_errno_from_oserror(GetLastError()); + } #else int rc = ::rename(filename.c_str(),newname.c_str()); #endif return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, supress_error); } +// Make this a define rather than using magic numbers multiple times in the code +#define LLFILE_COPY_BUFFER_SIZE 16384 + bool LLFile::copy(const std::string& from, const std::string& to) { bool copied = false; - LLFILE* in = LLFile::fopen(from, "rb"); /* Flawfinder: ignore */ + LLFILE* in = fopen(from, "rb"); /* Flawfinder: ignore */ if (in) { - LLFILE* out = LLFile::fopen(to, "wb"); /* Flawfinder: ignore */ + LLFILE* out = fopen(to, "wb"); /* Flawfinder: ignore */ if (out) { - char buf[16384]; /* Flawfinder: ignore */ + char buf[LLFILE_COPY_BUFFER_SIZE]; /* Flawfinder: ignore */ size_t readbytes; bool write_ok = true; - while(write_ok && (readbytes = fread(buf, 1, 16384, in))) /* Flawfinder: ignore */ + while (write_ok && (readbytes = fread(buf, 1, LLFILE_COPY_BUFFER_SIZE, in))) /* Flawfinder: ignore */ { if (fwrite(buf, 1, readbytes, out) != readbytes) { @@ -315,31 +468,51 @@ bool LLFile::copy(const std::string& from, const std::string& to) return copied; } -int LLFile::stat(const std::string& filename, llstat* filestatus) +int LLFile::stat(const std::string& filename, llstat* filestatus, int supress_error) { #if LL_WINDOWS - std::wstring utf16filename = ll_convert(filename); - int rc = _wstat(utf16filename.c_str(),filestatus); + std::wstring utf16filename = utf8path_to_wstring(filename); + int rc = _wstat64(utf16filename.c_str(), filestatus); #else - int rc = ::stat(filename.c_str(),filestatus); + int rc = ::stat(filename.c_str(), filestatus); #endif - // We use stat() to determine existence (see isfile(), isdir()). - // Don't spam the log if the subject pathname doesn't exist. - return warnif("stat", filename, rc, ENOENT); + return warnif("stat", filename, rc, supress_error); } -bool LLFile::isdir(const std::string& filename) +// _wstat() is a bit heavyweight on Windows, use a more lightweight API +// to just get the attributes +unsigned short LLFile::getattr(const std::string& filename, int suppress_error) { - llstat st; +#if LL_WINDOWS + int rc = -1; + std::wstring utf16filename = utf8path_to_wstring(filename); + unsigned short st_mode = get_fileattr(utf16filename); + if (st_mode) + { + return st_mode; + } +#else + llstat filestatus; + int rc = ::stat(filename.c_str(), &filestatus); + if (rc == 0) + { + return filestatus.st_mode; + } +#endif + warnif("getattr", filename, rc, suppress_error); + return 0; +} - return stat(filename, &st) == 0 && S_ISDIR(st.st_mode); +bool LLFile::isdir(const std::string& filename) +{ + // Don't spam the log if the subject pathname doesn't exist. + return S_ISDIR(getattr(filename, ENOENT)); } bool LLFile::isfile(const std::string& filename) { - llstat st; - - return stat(filename, &st) == 0 && S_ISREG(st.st_mode); + // Don't spam the log if the subject pathname doesn't exist. + return S_ISREG(getattr(filename, ENOENT)); } const char *LLFile::tmpdir() @@ -368,75 +541,8 @@ const char *LLFile::tmpdir() return utf8path.c_str(); } - -/***************** Modified file stream created to overcome the incorrect behaviour of posix fopen in windows *******************/ - #if LL_WINDOWS -LLFILE * LLFile::_Fiopen(const std::string& filename, - std::ios::openmode mode) -{ // open a file - static const char *mods[] = - { // fopen mode strings corresponding to valid[i] - "r", "w", "w", "a", "rb", "wb", "wb", "ab", - "r+", "w+", "a+", "r+b", "w+b", "a+b", - 0}; - static const int valid[] = - { // valid combinations of open flags - ios_base::in, - ios_base::out, - ios_base::out | ios_base::trunc, - ios_base::out | ios_base::app, - ios_base::in | ios_base::binary, - ios_base::out | ios_base::binary, - ios_base::out | ios_base::trunc | ios_base::binary, - ios_base::out | ios_base::app | ios_base::binary, - ios_base::in | ios_base::out, - ios_base::in | ios_base::out | ios_base::trunc, - ios_base::in | ios_base::out | ios_base::app, - ios_base::in | ios_base::out | ios_base::binary, - ios_base::in | ios_base::out | ios_base::trunc - | ios_base::binary, - ios_base::in | ios_base::out | ios_base::app - | ios_base::binary, - 0}; - - LLFILE *fp = 0; - int n; - ios_base::openmode atendflag = mode & ios_base::ate; - ios_base::openmode norepflag = mode & ios_base::_Noreplace; - - if (mode & ios_base::_Nocreate) - mode |= ios_base::in; // file must exist - mode &= ~(ios_base::ate | ios_base::_Nocreate | ios_base::_Noreplace); - for (n = 0; valid[n] != 0 && valid[n] != mode; ++n) - ; // look for a valid mode - - if (valid[n] == 0) - return (0); // no valid mode - else if (norepflag && mode & (ios_base::out | ios_base::app) - && (fp = LLFile::fopen(filename, "r")) != 0) /* Flawfinder: ignore */ - { // file must not exist, close and fail - fclose(fp); - return (0); - } - else if (fp != 0 && fclose(fp) != 0) - return (0); // can't close after test open -// should open with protection here, if other than default - else if ((fp = LLFile::fopen(filename, mods[n])) == 0) /* Flawfinder: ignore */ - return (0); // open failed - - if (!atendflag || fseek(fp, 0, SEEK_END) == 0) - return (fp); // no need to seek to end, or seek succeeded - - fclose(fp); // can't position at end - return (0); -} - -#endif /* LL_WINDOWS */ - - -#if LL_WINDOWS /************** input file stream ********************************/ llifstream::llifstream() {} diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 1661cbeb55..0bd6be7f94 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -41,8 +41,8 @@ typedef FILE LLFILE; #include #if LL_WINDOWS -// windows version of stat function and stat data structure are called _stat -typedef struct _stat llstat; +// windows version of stat function and stat data structure are called _statxx +typedef struct _stat64 llstat; #else typedef struct stat llstat; #include @@ -58,33 +58,89 @@ typedef struct stat llstat; #include "llstring.h" // safe char* -> std::string conversion +/// LLFile is a class of static functions operating on paths +/// All the functions with a path string input take UTF8 path/filenames class LL_COMMON_API LLFile { public: - // All these functions take UTF8 path/filenames. - static LLFILE* fopen(const std::string& filename,const char* accessmode); /* Flawfinder: ignore */ - static LLFILE* _fsopen(const std::string& filename,const char* accessmode,int sharingFlag); + /// open a file with the specified access mode + static LLFILE* fopen(const std::string& filename, const char* accessmode); /* Flawfinder: ignore */ + ///< 'accessmode' follows the rules of the Posix fopen() mode parameter + /// "r" open the file for reading only and positions the stream at the beginning + /// "r+" open the file for reading and writing and positions the stream at the beginning + /// "w" open the file for reading and writing and truncate it to zero length + /// "w+" open or create the file for reading and writing and truncate to zero length if it existed + /// "a" open the file for reading and writing and position the stream at the end of the file + /// "a+" open or create the file for reading and writing and position the stream at the end of the file + /// + /// in addition to these values, "b" can be appended to indicate binary stream access, but on Linux and Mac + /// this is strictly for compatibility and has no effect. On Windows this makes the file functions not + /// try to translate line endings. Windows also allows to append "t" to indicate text mode. If neither + /// "b" or "t" is defined, Windows uses the value set by _fmode which by default is _O_TEXT. + /// This means that it is always a good idea to append "b" specifically for binary file access to + /// avoid corruption of the binary consistency of the data stream when reading or writing + /// Other characters in 'accessmode' will usually cause an error as fopen will verify this parameter + /// @returns a valid LLFILE* pointer on success or NULL on failure static int close(LLFILE * file); + /// retrieve the content of a file into a string static std::string getContents(const std::string& filename); + ///< @returns the content of the file or an empty string on failure - // perms is a permissions mask like 0777 or 0700. In most cases it will - // be overridden by the user's umask. It is ignored on Windows. - // mkdir() considers "directory already exists" to be SUCCESS. + /// create a directory static int mkdir(const std::string& filename, int perms = 0700); - - static int rmdir(const std::string& filename); + ///< perms is a permissions mask like 0777 or 0700. In most cases it will be + /// overridden by the user's umask. It is ignored on Windows. + /// mkdir() considers "directory already exists" to be not an error. + /// @returns 0 on success and -1 on failure. + + //// remove a directory + static int rmdir(const std::string& filename, int supress_error = 0); + ///< pass ENOENT in the optional 'supress_error' parameter + /// if you don't want a warning in the log when the directory does not exist + /// @returns 0 on success and -1 on failure. + + /// remove a file or directory static int remove(const std::string& filename, int supress_error = 0); - static int rename(const std::string& filename,const std::string& newname, int supress_error = 0); - static bool copy(const std::string& from, const std::string& to); + ///< pass ENOENT in the optional 'supress_error' parameter + /// if you don't want a warning in the log when the directory does not exist + /// @returns 0 on success and -1 on failure. - static int stat(const std::string& filename,llstat* file_status); - static bool isdir(const std::string& filename); - static bool isfile(const std::string& filename); - static LLFILE * _Fiopen(const std::string& filename, - std::ios::openmode mode); + /// rename a file, + static int rename(const std::string& filename, const std::string& newname, int supress_error = 0); + ///< it will silently overwrite newname if it exists without returning an error + /// @returns 0 on success and -1 on failure. + + // copy the contents of file from 'from' to 'to' filename + static bool copy(const std::string& from, const std::string& to); + ///< @returns true on success and false on failure. + + /// return the file stat structure for filename + static int stat(const std::string& filename, llstat* file_status, int supress_error = ENOENT); + ///< for compatibility with existing uses of LL_File::stat() we use ENOENT as default in the + /// optional 'supress_error' parameter to avoid spamming the log with warnings when the API + /// is used to detect if a file exists + /// @returns 0 on success and -1 on failure. + + /// get the file or directory attributes for filename + static unsigned short getattr(const std::string& filename, int supress_error = 0); + ///< a more lightweight function on Windows to stat, that just returns the file attribute flags + /// pass ENOENT in the optional 'supress_error' parameter if you don't want a warning + /// in the log when the file or directory does not exist + /// @returns 0 on failure and a st_mode value with either S_IFDIR or S_IFREG set otherwise + /// together with the three access bits which under Windows only the write bit is relevant. + + /// check if filename is an existing directory + static bool isdir(const std::string& filename); + ///< @returns true if the path is for an existing directory + + /// check if filename is an existing file + static bool isfile(const std::string& filename); + ///< @returns true if the path is for an existing file + + /// return a path to the temporary directory on the system static const char * tmpdir(); }; diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 21b11c5311..270ca40086 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -1348,10 +1348,6 @@ bool gunzip_file(const std::string& srcfile, const std::string& dstfile) } while(gzeof(src) == 0); fclose(dst); dst = NULL; -#if LL_WINDOWS - // Rename in windows needs the dstfile to not exist. - LLFile::remove(dstfile, ENOENT); -#endif if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ retval = true; err: @@ -1399,10 +1395,6 @@ bool gzip_file(const std::string& srcfile, const std::string& dstfile) gzclose(dst); dst = NULL; -#if LL_WINDOWS - // Rename in windows needs the dstfile to not exist. - LLFile::remove(dstfile); -#endif if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ retval = true; err: diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp index b1b698375a..d5c585b45e 100644 --- a/indra/llcrashlogger/llcrashlogger.cpp +++ b/indra/llcrashlogger/llcrashlogger.cpp @@ -576,10 +576,6 @@ bool LLCrashLogger::init() std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "crashreport.log.old"); std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "crashreport.log"); -#if LL_WINDOWS - LLAPRFile::remove(old_log_file); -#endif - LLFile::rename(log_file.c_str(), old_log_file.c_str()); // Set the log file to crashreport.log diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index 5ce5244107..541266af4f 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -103,9 +103,6 @@ bool LLFileSystem::renameFile(const LLUUID& old_file_id, const LLAssetType::ETyp const std::string old_filename = LLDiskCache::metaDataToFilepath(old_file_id, old_file_type); const std::string new_filename = LLDiskCache::metaDataToFilepath(new_file_id, new_file_type); - // Rename needs the new file to not exist. - LLFileSystem::removeFile(new_file_id, new_file_type, ENOENT); - if (LLFile::rename(old_filename, new_filename) != 0) { // We would like to return false here indicating the operation 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/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 6d29038a23..8792ae3285 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -1014,7 +1014,6 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old"); if (gDirUtilp->fileExists(new_log)) { - LLFile::remove(old_log, ENOENT); LLFile::rename(new_log, old_log); } -- cgit v1.3 From 6979b1ff501d3db226d540475c17ef9c85fd15a0 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 3 Nov 2025 19:05:46 +0200 Subject: #4920 fix group categories not being displayed when one below is expanded --- indra/llui/llaccordionctrl.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index 1a64c2699d..2cd394476e 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -303,11 +303,8 @@ void LLAccordionCtrl::ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S3 return; LLRect panel_rect = panel->getRect(); panel_rect.setLeftTopAndSize( left, top, width, height); - if (panel->getRect() != panel_rect) - { - panel->reshape( width, height, 1); - panel->setRect(panel_rect); - } + panel->reshape( width, height, 1); + panel->setRect(panel_rect); } void LLAccordionCtrl::ctrlShiftVertical(LLView* panel, S32 delta) -- cgit v1.3 From d23379939c73daa7236fd279458f6037f8baf57e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:13:34 +0200 Subject: #4879 An Emoji in a display name can affect text in chat An emoji was spliting inlineview thus affecting displayed text. --- indra/llui/lltextbase.cpp | 30 ++++++++++++++++++++++++++++++ indra/llui/lltextbase.h | 2 ++ 2 files changed, 32 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 1de12896eb..44151a4355 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1038,8 +1038,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(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) @@ -3448,6 +3477,7 @@ S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offs void LLTextSegment::updateLayout(const LLTextBase& editor) {} F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { return draw_rect.mLeft; } bool LLTextSegment::canEdit() const { return false; } +bool LLTextSegment::getPermitsEmoji() const { return true; } void LLTextSegment::unlinkFromDocument(LLTextBase*) {} void LLTextSegment::linkToDocument(LLTextBase*) {} const LLUIColor& LLTextSegment::getColor() const { static const LLUIColor white = LLUIColorTable::instance().getColor("White", LLColor4::white); return white; } diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 07cd9a1ee5..50767a35b3 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -87,6 +87,7 @@ public: 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; + virtual bool getPermitsEmoji() const; virtual void unlinkFromDocument(class LLTextBase* editor); virtual void linkToDocument(class LLTextBase* editor); @@ -255,6 +256,7 @@ public: /*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); -- cgit v1.3 From 3694df79bf11cf9ad37ed74b7b9821d674f78730 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:52:56 +0200 Subject: #4656 Don't scroll to a child that is already in view and can not fit --- indra/llui/llaccordionctrltab.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index bdf93348bb..828bfb289b 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -479,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 -- cgit v1.3