sc/source/ui/cctrl/checklistmenu.cxx |  201 +++++++++++++++++++++--------------
 sc/source/ui/inc/checklistmenu.hxx   |    6 -
 2 files changed, 126 insertions(+), 81 deletions(-)

New commits:
commit b8e777d2b19c407e13c3b913bba9e9a061ade8b5
Author:     Sahil Gautam <[email protected]>
AuthorDate: Fri Mar 13 04:04:16 2026 +0530
Commit:     Michael Stahl <[email protected]>
CommitDate: Mon Mar 16 20:47:13 2026 +0100

    fix: tdf#167395 make lock work with hierarchies of dates
    
    Most of the work is done in two helper functions MarkCheckedMembers
    and UpdateVisibleMembers. The first one goes over all the visible
    members in the autofilter dropdown and marks them in the internal
    representation maMembers. Then the second one updates the visible
    members in the dropdown based on user action i.e. search, or lock
    check/uncheck.
    
    Change-Id: I0f96eb2cfadf0934c6319ec6a239346a8d5483bb
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201098
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <[email protected]>
    (cherry picked from commit d81d50be930f9b91e1ee20ad664c5387eb19583c)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201871
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/sc/source/ui/cctrl/checklistmenu.cxx 
b/sc/source/ui/cctrl/checklistmenu.cxx
index abacda0280d0..c47a0c79339e 100644
--- a/sc/source/ui/cctrl/checklistmenu.cxx
+++ b/sc/source/ui/cctrl/checklistmenu.cxx
@@ -878,7 +878,46 @@ void ScCheckListMenuControl::MarkCheckedMembers()
 {
     if (mbHasDates)
     {
-        // TODO: flesh it out later
+        mpChecks->all_foreach([this](weld::TreeIter& rEntry){
+            if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
+            {
+                int nDepth = mpChecks->get_iter_depth(rEntry);
+                OUString sText = mpChecks->get_text(rEntry);
+
+                if (!nDepth)
+                {
+                    for (ScCheckListMember& aMember : maMembers)
+                    {
+                        if (aMember.maName == sText)
+                        {
+                            aMember.mbMarked = true;
+                            break;
+                        }
+                    }
+                }
+                else if (nDepth == ScCheckListMember::DatePartType::DAY)
+                {
+                    std::unique_ptr<weld::TreeIter> xIter = 
mpChecks->make_iterator(&rEntry);
+                    OUString aYear, aMonth;
+
+                    if (mpChecks->iter_parent(*xIter))
+                        aMonth = mpChecks->get_text(*xIter);
+
+                    if (mpChecks->iter_parent(*xIter))
+                        aYear = mpChecks->get_text(*xIter);
+
+                    for (auto& aMember : maMembers)
+                    {
+                        if (aMember.maName.equals(sText) && 
aMember.meDatePartType == nDepth)
+                        {
+                            if (aMember.maDateParts[0] == aYear && 
aMember.maDateParts[1] == aMonth)
+                                aMember.mbMarked = true;
+                        }
+                    }
+                }
+            }
+            return false;
+        });
     }
     else
     {
@@ -909,6 +948,30 @@ void ScCheckListMenuControl::MarkCheckedMembers()
     }
 }
 
+IMPL_LINK_NOARG(ScCheckListMenuControl, SearchEditTimeoutHdl, Timer*, void)
+{
+    size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
+        [](const ScCheckListMember& rLMem) { return 
!rLMem.mbHiddenByOtherFilter; });
+    size_t nSelCount = UpdateVisibleMembers(true);
+
+    if ( nSelCount == nEnableMember )
+        mxChkToggleAll->set_state( TRISTATE_TRUE );
+    else if ( nSelCount == 0 )
+        mxChkToggleAll->set_state( TRISTATE_FALSE );
+    else
+        mxChkToggleAll->set_state( TRISTATE_INDET );
+
+    if ( !maConfig.mbAllowEmptySet )
+    {
+        const bool bEmptySet( nSelCount == 0 );
+        mpChecks->set_sensitive(!bEmptySet);
+        mxChkToggleAll->set_sensitive(!bEmptySet);
+        mxBtnSelectSingle->set_sensitive(!bEmptySet);
+        mxBtnUnselectSingle->set_sensitive(!bEmptySet);
+        mxBtnOk->set_sensitive(!bEmptySet);
+    }
+}
+
 IMPL_LINK_NOARG(ScCheckListMenuControl, LockCheckedHdl, weld::Toggleable&, 
void)
 {
     MarkCheckedMembers();
@@ -946,11 +1009,37 @@ IMPL_LINK_NOARG(ScCheckListMenuControl, ComboChangedHdl, 
weld::ComboBox&, void)
         mxFieldChangedAction->execute();
 }
 
-IMPL_LINK_NOARG(ScCheckListMenuControl, SearchEditTimeoutHdl, Timer*, void)
+IMPL_LINK_NOARG(ScCheckListMenuControl, EdModifyHdl, weld::Entry&, void)
+{
+    maSearchEditTimer.Start();
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, EdActivateHdl, weld::Entry&, bool)
+{
+    if (mxBtnOk->get_sensitive())
+        close(true);
+    return true;
+}
+
+IMPL_LINK( ScCheckListMenuControl, CheckHdl, const weld::TreeView::iter_col&, 
rRowCol, void )
+{
+    Check(&rRowCol.first);
+}
+
+size_t ScCheckListMenuControl::UpdateVisibleMembers(bool bSearchEditTimeout)
 {
     OUString aSearchText = mxEdSearch->get_text();
-    aSearchText = ScGlobal::getCharClass().lowercase( aSearchText );
+    aSearchText = ScGlobal::getCharClass().lowercase(aSearchText);
+
+    bool bLockChecked = mxChkLockChecked->get_active();
+
+    /* this assumes that either it's the SearchEditTimeoutHdl calling this 
function
+     * with `bSearchEditTimeout` as `true`, or it's `LockCheckedHdl` calling 
it with
+     * `bSearchEditTimeout` as `false`. so when searching we check whether 
lock is
+     * checked or not, and otherwise we always lock on lock checkbox toggles. 
*/
+    bool bLockMarkedEntries = !bSearchEditTimeout || bLockChecked;
     bool bSearchTextEmpty = aSearchText.isEmpty();
+
     size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
         [](const ScCheckListMember& rLMem) { return 
!rLMem.mbHiddenByOtherFilter; });
     size_t nSelCount = 0;
@@ -959,7 +1048,6 @@ IMPL_LINK_NOARG(ScCheckListMenuControl, 
SearchEditTimeoutHdl, Timer*, void)
     // this one where we can take advantage of knowing we have no hierarchy
     if (mbHasDates)
     {
-        // todo: move this branch to `UpdateVisibleMembers`
         mpChecks->freeze();
 
         bool bSomeDateDeletes = false;
@@ -986,18 +1074,27 @@ IMPL_LINK_NOARG(ScCheckListMenuControl, 
SearchEditTimeoutHdl, Timer*, void)
             else if ( bIsDate && maMembers[i].meDatePartType != 
ScCheckListMember::DAY )
                 continue;
 
+            bool bLockCurrentMember = bLockMarkedEntries && 
maMembers[i].mbMarked;
             if ( bSearchTextEmpty )
             {
-                auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, 
maMembers[i].mbVisible);
+                bool bCheck = bLockMarkedEntries ? bLockCurrentMember : 
maMembers[i].mbVisible;
+                auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, 
bCheck, bLockChecked);
                 updateMemberParents(xLeaf.get(), i);
                 if ( maMembers[i].mbVisible )
                     ++nSelCount;
                 continue;
             }
 
-            if ( bPartialMatch )
+            /* the whole point of lock is to show entries from the previous 
search which
+             * don't necessarily match the current search term. */
+            if ( bPartialMatch || bLockCurrentMember )
             {
-                auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i]);
+                /* when searching, we always show the entries as checked so 
that the user can
+                 * unselect ones they don't want and lock the rest in place. 
when locking however,
+                 * we only check the marked entries. `MarkCheckedMembers` does 
that, so it's called
+                 * before calling this function in `LockCheckedHdl`. */
+                bool bCheck = bSearchEditTimeout  || maMembers[i].mbMarked;
+                auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, 
bCheck, bLockChecked);
                 updateMemberParents(xLeaf.get(), i);
                 ++nSelCount;
             }
@@ -1023,60 +1120,6 @@ IMPL_LINK_NOARG(ScCheckListMenuControl, 
SearchEditTimeoutHdl, Timer*, void)
 
         mpChecks->thaw();
     }
-    else
-        nSelCount = UpdateVisibleMembers(true);
-
-    if ( nSelCount == nEnableMember )
-        mxChkToggleAll->set_state( TRISTATE_TRUE );
-    else if ( nSelCount == 0 )
-        mxChkToggleAll->set_state( TRISTATE_FALSE );
-    else
-        mxChkToggleAll->set_state( TRISTATE_INDET );
-
-    if ( !maConfig.mbAllowEmptySet )
-    {
-        const bool bEmptySet( nSelCount == 0 );
-        mpChecks->set_sensitive(!bEmptySet);
-        mxChkToggleAll->set_sensitive(!bEmptySet);
-        mxBtnSelectSingle->set_sensitive(!bEmptySet);
-        mxBtnUnselectSingle->set_sensitive(!bEmptySet);
-        mxBtnOk->set_sensitive(!bEmptySet);
-    }
-}
-
-IMPL_LINK_NOARG(ScCheckListMenuControl, EdModifyHdl, weld::Entry&, void)
-{
-    maSearchEditTimer.Start();
-}
-
-IMPL_LINK_NOARG(ScCheckListMenuControl, EdActivateHdl, weld::Entry&, bool)
-{
-    if (mxBtnOk->get_sensitive())
-        close(true);
-    return true;
-}
-
-IMPL_LINK( ScCheckListMenuControl, CheckHdl, const weld::TreeView::iter_col&, 
rRowCol, void )
-{
-    Check(&rRowCol.first);
-}
-
-size_t ScCheckListMenuControl::UpdateVisibleMembers(bool bSearchEditTimeout)
-{
-    bool bLockChecked = mxChkLockChecked->get_active();
-    /* here we pass the lock state to tell `initMembers` to preserve selection
-     * if lock is checked. this is for the case when the user has some entries
-     * locked and is now searching for more entries. */
-    bool bCheckMarkedEntries = !bSearchEditTimeout || bLockChecked;
-
-    OUString aSearchText = mxEdSearch->get_text();
-    aSearchText = ScGlobal::getCharClass().lowercase(aSearchText);
-    size_t nSelCount = 0;
-
-    if (mbHasDates)
-    {
-        // TODO: flesh it out later
-    }
     else
     {
         mpChecks->freeze();
@@ -1085,7 +1128,7 @@ size_t ScCheckListMenuControl::UpdateVisibleMembers(bool 
bSearchEditTimeout)
         mpChecks->clear();
         mpChecks->thaw();
 
-        if (aSearchText.isEmpty())
+        if (bSearchTextEmpty)
         {
             /*
              * when we click on lock, all the checked entries are marked and
@@ -1098,12 +1141,12 @@ size_t 
ScCheckListMenuControl::UpdateVisibleMembers(bool bSearchEditTimeout)
              * entries to remain selected, thus this `true` is valid even in 
the
              * uncheck case.
              */
-            nSelCount = initMembers(-1, bCheckMarkedEntries);
+            nSelCount = initMembers(-1, bLockMarkedEntries);
         }
         else
         {
             std::vector<int> aShownIndexes;
-            loadSearchedMembers(aShownIndexes, maMembers, aSearchText, 
bCheckMarkedEntries);
+            loadSearchedMembers(aShownIndexes, maMembers, aSearchText, 
bLockMarkedEntries);
             std::vector<int> aFixedWidths { mnCheckWidthReq };
 
             // tdf#122419 insert in the fastest order, this might be backwards.
@@ -1395,22 +1438,23 @@ void 
ScCheckListMenuControl::CheckEntry(std::u16string_view sName, const weld::T
 }
 
 // Recursively check all children of rParent
-void ScCheckListMenuControl::CheckAllChildren(const weld::TreeIter& rParent, 
bool bCheck)
+void ScCheckListMenuControl::CheckAllChildren(const weld::TreeIter& rParent, 
bool bCheck, bool bLock)
 {
+    mpChecks->set_sensitive(rParent, !bLock);
     mpChecks->set_toggle(rParent, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
     std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(&rParent);
     bool bEntry = mpChecks->iter_children(*xEntry);
     while (bEntry)
     {
-        CheckAllChildren(*xEntry, bCheck);
+        CheckAllChildren(*xEntry, bCheck, bLock);
         bEntry = mpChecks->iter_next_sibling(*xEntry);
     }
 }
 
-void ScCheckListMenuControl::CheckEntry(const weld::TreeIter& rParent, bool 
bCheck)
+void ScCheckListMenuControl::CheckEntry(const weld::TreeIter& rParent, bool 
bCheck, bool bLock)
 {
     // recursively check all items below rParent
-    CheckAllChildren(rParent, bCheck);
+    CheckAllChildren(rParent, bCheck, bLock);
     // checking rParent can affect ancestors, e.g. if ancestor is unchecked 
and rParent is
     // now checked then the ancestor needs to be checked also
     if (!mpChecks->get_iter_depth(rParent))
@@ -1437,16 +1481,18 @@ void ScCheckListMenuControl::CheckEntry(const 
weld::TreeIter& rParent, bool bChe
             bChild = mpChecks->iter_next_sibling(*xChild);
         }
         mpChecks->set_toggle(*xAncestor, bChildChecked ? TRISTATE_TRUE : 
TRISTATE_FALSE);
+        mpChecks->set_sensitive(*xAncestor, !bLock);
         bAncestor = mpChecks->iter_parent(*xAncestor);
     }
 }
 
-std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::ShowCheckEntry(const 
OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck)
+std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::ShowCheckEntry(const 
OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck, bool 
bLockMarked)
 {
     std::unique_ptr<weld::TreeIter> xEntry;
     if (!rMember.mbDate || rMember.mxParent)
         xEntry = FindEntry(rMember.mxParent.get(), sName);
 
+    bool bLockThisEntry = bLockMarked && rMember.mbMarked;
     if ( bShow )
     {
         if (!xEntry)
@@ -1461,29 +1507,27 @@ std::unique_ptr<weld::TreeIter> 
ScCheckListMenuControl::ShowCheckEntry(const OUS
                 {
                     xYearEntry = mpChecks->make_iterator();
                     mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, 
nullptr, false, xYearEntry.get());
-                    /* todo: change it to take into account the lock checkbox 
state,
-                     * so that we only check and disable/enable when it's 
needed. */
-                    mpChecks->set_toggle(*xYearEntry, TRISTATE_TRUE);
+                    mpChecks->set_toggle(*xYearEntry, bCheck ? TRISTATE_TRUE: 
TRISTATE_FALSE);
                     mpChecks->set_text(*xYearEntry, rMember.maDateParts[0], 0);
-                    mpChecks->set_sensitive(*xYearEntry, 
!rMember.mbHiddenByOtherFilter);
+                    mpChecks->set_sensitive(*xYearEntry, 
!rMember.mbHiddenByOtherFilter && !bLockThisEntry);
                 }
                 std::unique_ptr<weld::TreeIter> xMonthEntry = 
FindEntry(xYearEntry.get(), rMember.maDateParts[1]);
                 if (!xMonthEntry)
                 {
                     xMonthEntry = mpChecks->make_iterator();
                     mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, 
nullptr, nullptr, false, xMonthEntry.get());
-                    mpChecks->set_toggle(*xMonthEntry, TRISTATE_TRUE);
+                    mpChecks->set_toggle(*xMonthEntry, bCheck ? TRISTATE_TRUE: 
TRISTATE_FALSE);
                     mpChecks->set_text(*xMonthEntry, rMember.maDateParts[1], 
0);
-                    mpChecks->set_sensitive(*xMonthEntry, 
!rMember.mbHiddenByOtherFilter);
+                    mpChecks->set_sensitive(*xMonthEntry, 
!rMember.mbHiddenByOtherFilter && !bLockThisEntry);
                 }
                 std::unique_ptr<weld::TreeIter> xDayEntry = 
FindEntry(xMonthEntry.get(), rMember.maName);
                 if (!xDayEntry)
                 {
                     xDayEntry = mpChecks->make_iterator();
                     mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, 
nullptr, nullptr, false, xDayEntry.get());
-                    mpChecks->set_toggle(*xDayEntry, TRISTATE_TRUE);
+                    mpChecks->set_toggle(*xDayEntry, bCheck ? TRISTATE_TRUE: 
TRISTATE_FALSE);
                     mpChecks->set_text(*xDayEntry, rMember.maName, 0);
-                    mpChecks->set_sensitive(*xDayEntry, 
!rMember.mbHiddenByOtherFilter);
+                    mpChecks->set_sensitive(*xDayEntry, 
!rMember.mbHiddenByOtherFilter && !bLockThisEntry);
                 }
                 return xDayEntry; // Return leaf node
             }
@@ -1492,9 +1536,10 @@ std::unique_ptr<weld::TreeIter> 
ScCheckListMenuControl::ShowCheckEntry(const OUS
             mpChecks->append(xEntry.get());
             mpChecks->set_toggle(*xEntry, bCheck ? TRISTATE_TRUE : 
TRISTATE_FALSE);
             mpChecks->set_text(*xEntry, sName, 0);
+            mpChecks->set_sensitive(*xEntry, !rMember.mbHiddenByOtherFilter && 
!bLockThisEntry);
         }
         else
-            CheckEntry(*xEntry, bCheck);
+            CheckEntry(*xEntry, bCheck, bLockThisEntry);
     }
     else if (xEntry)
     {
diff --git a/sc/source/ui/inc/checklistmenu.hxx 
b/sc/source/ui/inc/checklistmenu.hxx
index 3c4d4a4c96e6..31eb225a1f9a 100644
--- a/sc/source/ui/inc/checklistmenu.hxx
+++ b/sc/source/ui/inc/checklistmenu.hxx
@@ -215,14 +215,14 @@ private:
     void selectCurrentMemberOnly(bool bSet);
     void updateMemberParents(const weld::TreeIter* pLeaf, size_t nIdx);
 
-    std::unique_ptr<weld::TreeIter> ShowCheckEntry(const OUString& sName, 
ScCheckListMember& rMember, bool bShow = true, bool bCheck = true);
+    std::unique_ptr<weld::TreeIter> ShowCheckEntry(const OUString& sName, 
ScCheckListMember& rMember, bool bShow = true, bool bCheck = true, bool 
bLockMarked = false);
     void CheckEntry(std::u16string_view sName, const weld::TreeIter* pParent, 
bool bCheck);
-    void CheckEntry(const weld::TreeIter& rEntry, bool bCheck);
+    void CheckEntry(const weld::TreeIter& rEntry, bool bCheck, bool bLock = 
false);
     void GetRecursiveChecked(const weld::TreeIter* pEntry, 
std::unordered_set<OUString>& vOut, OUString& rLabel);
     std::unordered_set<OUString> GetAllChecked();
     bool IsChecked(std::u16string_view sName, const weld::TreeIter* pParent);
     int GetCheckedEntryCount() const;
-    void CheckAllChildren(const weld::TreeIter& rEntry, bool bCheck);
+    void CheckAllChildren(const weld::TreeIter& rEntry, bool bCheck, bool 
bLock);
 
     void setSelectedMenuItem(size_t nPos);
 

Reply via email to