sc/source/ui/cctrl/checklistmenu.cxx | 150 +++++++++++++++++++++++++-------- sc/source/ui/inc/checklistmenu.hxx | 6 + sc/uiconfig/scalc/ui/filterdropdown.ui | 24 ++++- 3 files changed, 139 insertions(+), 41 deletions(-)
New commits: commit d4c34206bcf64b94eac4f0761aeacc285e08af55 Author: Sahil <me.sahilgau...@gmail.com> AuthorDate: Wed Dec 27 16:01:07 2023 +0530 Commit: Heiko Tietze <heiko.tie...@documentfoundation.org> CommitDate: Mon Feb 12 12:53:39 2024 +0100 tdf#133836 Autofilter allow adding up members to the current selection Added a new checkbox [x] Lock, which if checked locks the current selection, and further items then can be added to that locked selection Change-Id: If8a5da308443458abfed59b303ee25feffa0aa6d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159737 Tested-by: Heiko Tietze <heiko.tie...@documentfoundation.org> Tested-by: Jenkins Reviewed-by: Heiko Tietze <heiko.tie...@documentfoundation.org> diff --git a/sc/source/ui/cctrl/checklistmenu.cxx b/sc/source/ui/cctrl/checklistmenu.cxx index d11d5405347e..60078c335437 100644 --- a/sc/source/ui/cctrl/checklistmenu.cxx +++ b/sc/source/ui/cctrl/checklistmenu.cxx @@ -474,6 +474,8 @@ ScCheckListMenuControl::Config::Config() : ScCheckListMember::ScCheckListMember() : mnValue(0.0) , mbVisible(true) + , mbMarked(false) + , mbCheck(true) , mbHiddenByOtherFilter(false) , mbDate(false) , mbLeaf(false) @@ -504,6 +506,7 @@ ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData , mxListChecks(mxBuilder->weld_tree_view("check_list_box")) , mxTreeChecks(mxBuilder->weld_tree_view("check_tree_box")) , mxChkToggleAll(mxBuilder->weld_check_button("toggle_all")) + , mxChkLockChecked(mxBuilder->weld_check_button("lock_checked")) , mxBtnSelectSingle(mxBuilder->weld_button("select_current")) , mxBtnUnselectSingle(mxBuilder->weld_button("unselect_current")) , mxButtonBox(mxBuilder->weld_box("buttonbox")) @@ -536,6 +539,7 @@ ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData mxListChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl)); mxTreeChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl)); mxChkToggleAll->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl)); + mxChkLockChecked->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl)); mxBtnSelectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl)); mxBtnUnselectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl)); mxBtnOk->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl)); @@ -601,6 +605,7 @@ ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData mxListChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl)); mxListChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl)); mxChkToggleAll->connect_toggled(LINK(this, ScCheckListMenuControl, TriStateHdl)); + mxChkLockChecked->connect_toggled(LINK(this, ScCheckListMenuControl, LockCheckedHdl)); mxBtnSelectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl)); mxBtnUnselectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl)); @@ -745,6 +750,104 @@ IMPL_LINK(ScCheckListMenuControl, ButtonHdl, weld::Button&, rBtn, void) } } +namespace +{ + void insertMember(weld::TreeView& rView, const weld::TreeIter& rIter, const ScCheckListMember& rMember, bool bChecked, bool bLock=false) + { + OUString aLabel = rMember.maName; + if (aLabel.isEmpty()) + aLabel = ScResId(STR_EMPTYDATA); + rView.set_toggle(rIter, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE); + rView.set_text(rIter, aLabel, 0); + + if (bLock) + rView.set_sensitive(rIter, !rMember.mbHiddenByOtherFilter && !rMember.mbMarked); + else + rView.set_sensitive(rIter, !rMember.mbHiddenByOtherFilter); + } + + void loadSearchedMembers(std::vector<int>& rSearchedMembers, std::vector<ScCheckListMember>& rMembers, + const OUString& rSearchText, bool bLock=false) + { + const OUString aSearchText = ScGlobal::getCharClass().lowercase( rSearchText ); + + for (size_t i = 0; i < rMembers.size(); ++i) + { + assert(!rMembers[i].mbDate); + + OUString aLabelDisp = rMembers[i].maName; + if ( aLabelDisp.isEmpty() ) + aLabelDisp = ScResId( STR_EMPTYDATA ); + + bool bPartialMatch = ScGlobal::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1; + + if (!bPartialMatch) + continue; + if (!bLock || (!rMembers[i].mbMarked && !rMembers[i].mbHiddenByOtherFilter)) + rSearchedMembers.push_back(i); + } + + if (bLock) + for (size_t i = 0; i < rMembers.size(); ++i) + if (rMembers[i].mbMarked && !rMembers[i].mbHiddenByOtherFilter) + rSearchedMembers.push_back(i); + + } +} + +IMPL_LINK_NOARG(ScCheckListMenuControl, LockCheckedHdl, weld::Toggleable&, void) +{ + // assume all members are checked + for (auto& aMember : maMembers) + aMember.mbCheck = true; + + // go over the members visible in the popup, and remember which one is + // checked, and which one is not + mpChecks->all_foreach([this](weld::TreeIter& rEntry){ + if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE) + { + for (auto& aMember : maMembers) + if (aMember.maName == mpChecks->get_text(rEntry)) + aMember.mbMarked = true; + } + else + { + for (auto& aMember : maMembers) + if (aMember.maName == mpChecks->get_text(rEntry)) + aMember.mbCheck = false; + } + + return false; + }); + + mpChecks->freeze(); + mpChecks->clear(); + mpChecks->thaw(); + + OUString aSearchText = mxEdSearch->get_text(); + if (aSearchText.isEmpty()) + { + initMembers(-1, !mxChkLockChecked->get_active()); + } + else + { + std::vector<int> aShownIndexes; + loadSearchedMembers(aShownIndexes, maMembers, aSearchText, true); + std::vector<int> aFixedWidths { mnCheckWidthReq }; + + // insert the members, remember whether checked or unchecked. + mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes](weld::TreeIter& rIter, int i) { + size_t nIndex = aShownIndexes[i]; + insertMember(*mpChecks, rIter, maMembers[nIndex], maMembers[nIndex].mbCheck, mxChkLockChecked->get_active()); + }, nullptr, &aFixedWidths); + } + + // unmarking should happen after the members are inserted + if (!mxChkLockChecked->get_active()) + for (auto& aMember : maMembers) + aMember.mbMarked = false; +} + IMPL_LINK_NOARG(ScCheckListMenuControl, TriStateHdl, weld::Toggleable&, void) { switch (mePrevToggleAllState) @@ -767,19 +870,6 @@ IMPL_LINK_NOARG(ScCheckListMenuControl, TriStateHdl, weld::Toggleable&, void) mePrevToggleAllState = mxChkToggleAll->get_state(); } -namespace -{ - void insertMember(weld::TreeView& rView, const weld::TreeIter& rIter, const ScCheckListMember& rMember, bool bChecked) - { - OUString aLabel = rMember.maName; - if (aLabel.isEmpty()) - aLabel = ScResId(STR_EMPTYDATA); - rView.set_toggle(rIter, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE); - rView.set_text(rIter, aLabel, 0); - rView.set_sensitive(rIter, !rMember.mbHiddenByOtherFilter); - } -} - IMPL_LINK_NOARG(ScCheckListMenuControl, ComboChangedHdl, weld::ComboBox&, void) { if (mbIsMultiField && mxFieldChangedAction) @@ -876,29 +966,13 @@ IMPL_LINK_NOARG(ScCheckListMenuControl, SearchEditTimeoutHdl, Timer*, void) nSelCount = initMembers(); else { - std::vector<size_t> aShownIndexes; - - for (size_t i = 0; i < nEnableMember; ++i) - { - assert(!maMembers[i].mbDate); - - OUString aLabelDisp = maMembers[i].maName; - if ( aLabelDisp.isEmpty() ) - aLabelDisp = ScResId( STR_EMPTYDATA ); - - bool bPartialMatch = ScGlobal::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1; - - if (!bPartialMatch) - continue; - - aShownIndexes.push_back(i); - } - + std::vector<int> aShownIndexes; + loadSearchedMembers(aShownIndexes, maMembers, aSearchText, mxChkLockChecked->get_active()); std::vector<int> aFixedWidths { mnCheckWidthReq }; // tdf#122419 insert in the fastest order, this might be backwards. mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes, &nSelCount](weld::TreeIter& rIter, int i) { size_t nIndex = aShownIndexes[i]; - insertMember(*mpChecks, rIter, maMembers[nIndex], true); + insertMember(*mpChecks, rIter, maMembers[nIndex], true, mxChkLockChecked->get_active()); ++nSelCount; }, nullptr, &aFixedWidths); } @@ -1115,6 +1189,8 @@ void ScCheckListMenuControl::addMember(const OUString& rName, const double nVal, aMember.mbLeaf = true; aMember.mbValue = bValue; aMember.mbVisible = bVisible; + aMember.mbMarked = false; + aMember.mbCheck = true; aMember.mbHiddenByOtherFilter = bHiddenByOtherFilter; aMember.mxParent.reset(); maMembers.emplace_back(std::move(aMember)); @@ -1357,7 +1433,7 @@ IMPL_LINK(ScCheckListMenuControl, KeyInputHdl, const KeyEvent&, rKEvt, bool) return false; } -size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth) +size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth, bool bUnlock) { size_t n = maMembers.size(); size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(), @@ -1373,10 +1449,12 @@ size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth) // tdf#134038 insert in the fastest order, this might be backwards so only do it for // the !mbHasDates case where no entry depends on another to exist before getting // inserted. We cannot retain pre-existing treeview content, only clear and fill it. - mpChecks->bulk_insert_for_each(n, [this, &nVisMemCount](weld::TreeIter& rIter, int i) { + mpChecks->bulk_insert_for_each(n, [this, &nVisMemCount, &bUnlock](weld::TreeIter& rIter, int i) { assert(!maMembers[i].mbDate); - insertMember(*mpChecks, rIter, maMembers[i], maMembers[i].mbVisible); - if (maMembers[i].mbVisible) + bool bCheck = ((mxChkLockChecked->get_active() || bUnlock) ? maMembers[i].mbMarked : maMembers[i].mbVisible); + insertMember(*mpChecks, rIter, maMembers[i], bCheck, mxChkLockChecked->get_active()); + + if (bCheck) ++nVisMemCount; }, nullptr, &aFixedWidths); } diff --git a/sc/source/ui/inc/checklistmenu.hxx b/sc/source/ui/inc/checklistmenu.hxx index 1e85b17c48f2..8e24baef433b 100644 --- a/sc/source/ui/inc/checklistmenu.hxx +++ b/sc/source/ui/inc/checklistmenu.hxx @@ -36,6 +36,8 @@ struct ScCheckListMember OUString maRealName; double mnValue; // number value of filter condition bool mbVisible; + bool mbMarked; + bool mbCheck; bool mbHiddenByOtherFilter; bool mbDate; bool mbLeaf; @@ -139,7 +141,7 @@ public: void addMember(const OUString& rName, const double nVal, bool bVisible, bool bHiddenByOtherFilter, bool bValue = false); void clearMembers(); - size_t initMembers(int nMaxMemberWidth = -1); + size_t initMembers(int nMaxMemberWidth = -1, bool bUnlock=false); void setConfig(const Config& rConfig); bool isAllSelected() const; @@ -231,6 +233,7 @@ private: DECL_LINK(ButtonHdl, weld::Button&, void); DECL_LINK(TriStateHdl, weld::Toggleable&, void); + DECL_LINK(LockCheckedHdl, weld::Toggleable&, void); void Check(const weld::TreeIter* pIter); @@ -279,6 +282,7 @@ private: weld::TreeView* mpChecks; std::unique_ptr<weld::CheckButton> mxChkToggleAll; + std::unique_ptr<weld::CheckButton> mxChkLockChecked; std::unique_ptr<weld::Button> mxBtnSelectSingle; std::unique_ptr<weld::Button> mxBtnUnselectSingle; diff --git a/sc/uiconfig/scalc/ui/filterdropdown.ui b/sc/uiconfig/scalc/ui/filterdropdown.ui index d7f081749ef5..8fa9e8f98395 100644 --- a/sc/uiconfig/scalc/ui/filterdropdown.ui +++ b/sc/uiconfig/scalc/ui/filterdropdown.ui @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.38.2 --> +<!-- Generated with glade 3.40.0 --> <interface domain="sc"> <requires lib="gtk+" version="3.20"/> <object class="GtkMenu" id="contextmenu"> @@ -153,7 +153,7 @@ <object class="GtkLabel" id="select_field_label"> <property name="can-focus">False</property> <property name="label" translatable="yes" context="filterdropdown|select_field_label">Select Field</property> - <property name="mnemonic_widget">multi_field_combo</property> + <property name="mnemonic-widget">multi_field_combo</property> </object> <packing> <property name="expand">False</property> @@ -227,6 +227,22 @@ <property name="position">0</property> </packing> </child> + <child> + <object class="GtkCheckButton" id="lock_checked"> + <property name="label" translatable="yes" context="filterdropdown|STR_BTN_TOGGLE_ALL">Lock</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="hexpand">True</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> <child> <object class="GtkButton" id="select_current"> <property name="visible">True</property> @@ -239,7 +255,7 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">1</property> + <property name="position">2</property> </packing> </child> <child> @@ -254,7 +270,7 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> </object>