LibreOffice Module sc (master) 1
checklistmenu.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <checklistmenu.hxx>
21#include <o3tl/safeint.hxx>
22#include <o3tl/string_view.hxx>
23#include <globstr.hrc>
24#include <scresid.hxx>
25
26#include <vcl/commandevent.hxx>
27#include <vcl/decoview.hxx>
28#include <vcl/event.hxx>
29#include <vcl/settings.hxx>
30#include <vcl/svapp.hxx>
31#include <vcl/virdev.hxx>
32#include <rtl/math.hxx>
34#include <comphelper/lok.hxx>
35#include <LibreOfficeKit/LibreOfficeKitEnums.h>
36#include <tools/json_writer.hxx>
37#include <svl/numformat.hxx>
38
39#include <document.hxx>
40#include <viewdata.hxx>
41
42using namespace com::sun::star;
43using ::com::sun::star::uno::Reference;
44
46 : mbEnabled(true)
47{
48}
49
51 : maTimer("sc SubMenuItemData maTimer")
52 , mpSubMenu(nullptr)
53 , mnMenuPos(MENU_NOT_SELECTED)
54 , mpParent(pParent)
55{
57 maTimer.SetTimeout(Application::GetSettings().GetMouseSettings().GetMenuDelay());
58}
59
61{
62 mpSubMenu = nullptr;
63 mnMenuPos = MENU_NOT_SELECTED;
64 maTimer.Stop();
65}
66
68{
69 mpParent->handleMenuTimeout(this);
70}
71
73{
74 executeMenuItem(mxMenu->get_selected_index());
75 return true;
76}
77
78IMPL_LINK(ScCheckListMenuControl, MenuKeyInputHdl, const KeyEvent&, rKEvt, bool)
79{
80 const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
81
82 switch (rKeyCode.GetCode())
83 {
84 case KEY_RIGHT:
85 {
87 break;
88
90 if (!rMenu.mxSubMenuWin)
91 break;
92
94 }
95 }
96
97 return false;
98}
99
101{
102 sal_uInt32 nSelectedMenu = MENU_NOT_SELECTED;
103 if (!mxMenu->get_selected(mxScratchIter.get()))
104 {
105 // reselect current item if its submenu is up and the launching item
106 // became unselected by mouse moving out of the top level menu
107 if (mnSelectedMenu < maMenuItems.size() &&
108 maMenuItems[mnSelectedMenu].mxSubMenuWin &&
109 maMenuItems[mnSelectedMenu].mxSubMenuWin->IsVisible())
110 {
111 mxMenu->select(mnSelectedMenu);
112 return;
113 }
114 }
115 else
116 nSelectedMenu = mxMenu->get_iter_index_in_parent(*mxScratchIter);
117
118 setSelectedMenuItem(nSelectedMenu);
119}
120
121void ScCheckListMenuControl::addMenuItem(const OUString& rText, Action* pAction)
122{
123 MenuItemData aItem;
124 aItem.mbEnabled = true;
125 aItem.mxAction.reset(pAction);
126 maMenuItems.emplace_back(std::move(aItem));
127
128 mxMenu->show();
129 mxMenu->append_text(rText);
130 mxMenu->set_image(mxMenu->n_children() - 1, css::uno::Reference<css::graphic::XGraphic>(), 1);
131}
132
134{
135 MenuItemData aItem;
136 maMenuItems.emplace_back(std::move(aItem));
137
138 mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
139}
140
141IMPL_LINK(ScCheckListMenuControl, TreeSizeAllocHdl, const Size&, rSize, void)
142{
143 if (maAllocatedSize == rSize)
144 return;
145 maAllocatedSize = rSize;
147 if (!mnAsyncSetDropdownPosId && Application::GetToolkitName().startsWith("gtk"))
148 {
149 // for gtk retry again later in case it didn't work (wayland)
151 }
152}
153
155{
156 std::vector<int> aWidths
157 {
158 o3tl::narrowing<int>(maAllocatedSize.Width() - (mxMenu->get_text_height() * 3) / 4 - 6)
159 };
160 mxMenu->set_column_fixed_widths(aWidths);
161}
162
163IMPL_LINK_NOARG(ScCheckListMenuControl, SetDropdownPosHdl, void*, void)
164{
165 mnAsyncSetDropdownPosId = nullptr;
167 mxMenu->queue_resize();
168}
169
171{
172 int nWidth = (mxMenu->get_text_height() * 3) / 4;
173 mxDropDown->SetOutputSizePixel(Size(nWidth, nWidth));
174 DecorationView aDecoView(mxDropDown.get());
175 aDecoView.DrawSymbol(tools::Rectangle(Point(0, 0), Size(nWidth, nWidth)),
176 SymbolType::SPIN_RIGHT, mxDropDown->GetTextColor(),
177 DrawSymbolFlags::NONE);
178}
179
180ScListSubMenuControl* ScCheckListMenuControl::addSubMenuItem(const OUString& rText, bool bEnabled, bool bColorMenu)
181{
182 MenuItemData aItem;
183 aItem.mbEnabled = bEnabled;
184
185 aItem.mxSubMenuWin.reset(new ScListSubMenuControl(mxMenu.get(), *this, bColorMenu));
186 maMenuItems.emplace_back(std::move(aItem));
187
188 mxMenu->show();
189 mxMenu->append_text(rText);
190 mxMenu->set_image(mxMenu->n_children() - 1, *mxDropDown, 1);
191 return maMenuItems.back().mxSubMenuWin.get();
192}
193
195{
196 if (nPos >= maMenuItems.size())
197 return;
198
199 const MenuItemData& rMenu = maMenuItems[nPos];
200 if (rMenu.mxSubMenuWin)
201 {
202 if (rMenu.mbEnabled)
203 {
205 maOpenTimer.mpSubMenu = rMenu.mxSubMenuWin.get();
207 }
208 return;
209 }
210
211 if (!maMenuItems[nPos].mxAction)
212 // no action is defined.
213 return;
214
215 const bool bClosePopup = maMenuItems[nPos].mxAction->execute();
216 if (bClosePopup)
218}
219
221{
222 if (mnSelectedMenu == nPos)
223 // nothing to do.
224 return;
225
226 selectMenuItem(nPos, /*bSubMenuTimer*/true);
227}
228
230{
231 if (pTimer == &maOpenTimer)
232 {
233 // Close any open submenu immediately.
235 {
237 maCloseTimer.mpSubMenu = nullptr;
239 }
240
242 }
243 else if (pTimer == &maCloseTimer)
244 {
245 // end submenu.
247 {
249 maCloseTimer.mpSubMenu = nullptr;
250
251 // EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
254 }
255 }
256}
257
259{
260 if (!pMenu)
261 return;
262
263 // Set the submenu on launch queue.
265 {
266 if (maOpenTimer.mpSubMenu != pMenu)
267 {
268 // new submenu is being requested.
270 }
271 else
272 {
273 if (pMenu == maCloseTimer.mpSubMenu)
275 }
276 }
277
278 maOpenTimer.mpSubMenu = pMenu;
282 else
284}
285
287{
289 // There is no submenu to close.
290 return;
291
292 // Stop any submenu on queue for opening.
294
295 // Flush any pending close so it doesn't get skipped
297 {
299 }
300
303 maOpenTimer.mpSubMenu = nullptr;
305
308 else
310}
311
313{
314 if (!mxMenu->get_selected(mxScratchIter.get()))
315 return tools::Rectangle();
316 return mxMenu->get_row_area(*mxScratchIter);
317}
318
320{
322 if (!pSubMenu)
323 return;
324
325 if (!mxMenu->get_selected(mxScratchIter.get()))
326 return;
327
329 pSubMenu->StartPopupMode(mxMenu.get(), aRect);
330
331 mxMenu->select(*mxScratchIter);
332 pSubMenu->GrabFocus();
333}
334
335IMPL_LINK_NOARG(ScCheckListMenuControl, PostPopdownHdl, void*, void)
336{
337 mnAsyncPostPopdownId = nullptr;
338 mxMenu->grab_focus();
339}
340
341IMPL_LINK(ScCheckListMenuControl, MouseEnterHdl, const MouseEvent&, rMEvt, bool)
342{
343 if (!rMEvt.IsEnterWindow())
344 return false;
346 return false;
347}
348
350{
351 rSubMenu.EndPopupMode();
353
354 // EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
357
358 size_t nMenuPos = getSubMenuPos(&rSubMenu);
359 if (nMenuPos != MENU_NOT_SELECTED)
360 {
361 mnSelectedMenu = nMenuPos;
362 mxMenu->select(mnSelectedMenu);
363 }
364}
365
366void ScCheckListMenuControl::selectMenuItem(size_t nPos, bool bSubMenuTimer)
367{
368 mxMenu->select(nPos == MENU_NOT_SELECTED ? -1 : nPos);
370
371 if (nPos >= maMenuItems.size() || nPos == MENU_NOT_SELECTED)
372 {
374 return;
375 }
376
377 if (!maMenuItems[nPos].mbEnabled)
378 {
380 return;
381 }
382
383 if (bSubMenuTimer)
384 {
385 if (maMenuItems[nPos].mxSubMenuWin && mxMenu->changed_by_hover())
386 {
387 ScListSubMenuControl* pSubMenu = maMenuItems[nPos].mxSubMenuWin.get();
388 queueLaunchSubMenu(nPos, pSubMenu);
389 }
390 else
392 }
393}
394
396{
398}
399
401{
402 size_t n = maMenuItems.size();
403 for (size_t i = 0; i < n; ++i)
404 {
405 if (maMenuItems[i].mxSubMenuWin.get() == pSubMenu)
406 return i;
407 }
408 return MENU_NOT_SELECTED;
409}
410
412{
414 size_t nMenuPos = getSubMenuPos(pSubMenu);
415 if (mnSelectedMenu != nMenuPos)
416 {
417 mnSelectedMenu = nMenuPos;
418 mxMenu->select(mnSelectedMenu);
419 }
420}
421
423{
424 if (!mbIsPoppedUp)
425 return;
426 mxPopover->connect_closed(Link<weld::Popover&, void>());
427 mxPopover->popdown();
428 PopupModeEndHdl(*mxPopover);
429 assert(mbIsPoppedUp == false);
430}
431
433{
434 mxPopover->connect_closed(LINK(this, ScCheckListMenuControl, PopupModeEndHdl));
435 mbIsPoppedUp = true;
436 mxPopover->popup_at_rect(pParent, rRect);
437 GrabFocus();
438}
439
441{
442 EndPopupMode();
443}
444
446 mbAllowEmptySet(true), mbRTL(false)
447{
448}
449
451 : mnValue(0.0)
452 , mbVisible(true)
453 , mbHiddenByOtherFilter(false)
454 , mbDate(false)
455 , mbLeaf(false)
456 , mbValue(false)
457 , meDatePartType(YEAR)
458{
459}
460
461// the value of border-width of FilterDropDown
462constexpr int nBorderWidth = 4;
463// number of rows visible in checklist
464constexpr int nCheckListVisibleRows = 9;
465// number of rows visible in colorlist
466constexpr int nColorListVisibleRows = 9;
467
469 bool bHasDates, int nWidth)
470 : mxBuilder(Application::CreateBuilder(pParent, "modules/scalc/ui/filterdropdown.ui"))
471 , mxPopover(mxBuilder->weld_popover("FilterDropDown"))
472 , mxContainer(mxBuilder->weld_container("container"))
473 , mxMenu(mxBuilder->weld_tree_view("menu"))
474 , mxScratchIter(mxMenu->make_iterator())
475 , mxNonMenu(mxBuilder->weld_widget("nonmenu"))
476 , mxEdSearch(mxBuilder->weld_entry("search_edit"))
477 , mxBox(mxBuilder->weld_widget("box"))
478 , mxListChecks(mxBuilder->weld_tree_view("check_list_box"))
479 , mxTreeChecks(mxBuilder->weld_tree_view("check_tree_box"))
480 , mxChkToggleAll(mxBuilder->weld_check_button("toggle_all"))
481 , mxBtnSelectSingle(mxBuilder->weld_button("select_current"))
482 , mxBtnUnselectSingle(mxBuilder->weld_button("unselect_current"))
483 , mxButtonBox(mxBuilder->weld_box("buttonbox"))
484 , mxBtnOk(mxBuilder->weld_button("ok"))
485 , mxBtnCancel(mxBuilder->weld_button("cancel"))
486 , mxContextMenu(mxBuilder->weld_menu("contextmenu"))
487 , mxDropDown(mxMenu->create_virtual_device())
488 , mnCheckWidthReq(-1)
489 , mnWndWidth(0)
490 , mnCheckListVisibleRows(nCheckListVisibleRows)
491 , mePrevToggleAllState(TRISTATE_INDET)
492 , mnSelectedMenu(MENU_NOT_SELECTED)
493 , mrViewData(rViewData)
494 , mnAsyncPostPopdownId(nullptr)
495 , mnAsyncSetDropdownPosId(nullptr)
496 , mbHasDates(bHasDates)
497 , mbIsPoppedUp(false)
498 , maOpenTimer(this)
499 , maCloseTimer(this)
500{
501 mxTreeChecks->set_clicks_to_toggle(1);
502 mxListChecks->set_clicks_to_toggle(1);
503
504 mxNonMenu->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
505 mxEdSearch->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
506 mxListChecks->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
507 mxTreeChecks->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
508 mxListChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl));
509 mxTreeChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl));
510 mxChkToggleAll->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
511 mxBtnSelectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
512 mxBtnUnselectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
513 mxBtnOk->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
514 mxBtnCancel->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
515
516 /*
517 tdf#136559 If we have no dates we don't need a tree
518 structure, just a list. GtkListStore can be then
519 used which is much faster than a GtkTreeStore, so
520 with no dates switch to the treeview which uses the
521 faster GtkListStore
522 */
523 if (mbHasDates)
524 mpChecks = mxTreeChecks.get();
525 else
526 {
527 mxTreeChecks->hide();
528 mxListChecks->show();
529 mpChecks = mxListChecks.get();
530 }
531
532 int nChecksHeight = mxTreeChecks->get_height_rows(mnCheckListVisibleRows);
533 if (nWidth != -1)
534 {
535 mnCheckWidthReq = nWidth - nBorderWidth * 2 - 4;
536 mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
537 mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
538 }
539
540 // sort ok/cancel into native order, if this was a dialog they would be auto-sorted, but this
541 // popup isn't a true dialog
542 mxButtonBox->sort_native_button_order();
543
544 mxTreeChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
545 mxListChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
546
547 mxBox->show();
548 mxEdSearch->show();
549 mxButtonBox->show();
550
551 mxMenu->connect_row_activated(LINK(this, ScCheckListMenuControl, RowActivatedHdl));
552 mxMenu->connect_changed(LINK(this, ScCheckListMenuControl, SelectHdl));
553 mxMenu->connect_key_press(LINK(this, ScCheckListMenuControl, MenuKeyInputHdl));
554
555 mxBtnOk->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
556 mxBtnCancel->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
557 mxEdSearch->connect_changed(LINK(this, ScCheckListMenuControl, EdModifyHdl));
558 mxEdSearch->connect_activate(LINK(this, ScCheckListMenuControl, EdActivateHdl));
559 mxTreeChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
560 mxTreeChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
561 mxListChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
562 mxListChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
563 mxChkToggleAll->connect_toggled(LINK(this, ScCheckListMenuControl, TriStateHdl));
564 mxBtnSelectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
565 mxBtnUnselectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
566
568 mxMenu->connect_size_allocate(LINK(this, ScCheckListMenuControl, TreeSizeAllocHdl));
569
570 // determine what width the checklist will end up with
571 mnCheckWidthReq = mxContainer->get_preferred_size().Width();
572 // make that size fixed now, we can now use mnCheckWidthReq to speed up
573 // bulk_insert_for_each
574 mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
575 mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
576}
577
579{
580 if (mxEdSearch->get_visible())
581 mxEdSearch->grab_focus();
582 else
583 {
584 mxMenu->set_cursor(0);
585 mxMenu->grab_focus();
586 }
587}
588
590{
592 {
594 mnAsyncPostPopdownId = nullptr;
595 }
597 {
599 mnAsyncSetDropdownPosId = nullptr;
600 }
601}
602
604{
605 EndPopupMode();
606 for (auto& rMenuItem : maMenuItems)
607 rMenuItem.mxSubMenuWin.reset();
609}
610
612{
613 mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height() + 2);
615 if (mxMenu->n_children())
616 {
617 mxMenu->set_cursor(0);
618 mxMenu->unselect_all();
619 }
620
621 mnWndWidth = mxContainer->get_preferred_size().Width() + nBorderWidth * 2 + 4;
622}
623
625{
626 mpChecks->all_foreach([this, bSet](weld::TreeIter& rEntry){
627 if (mpChecks->get_sensitive(rEntry, 0))
629 return false;
630 });
631
633 {
634 // We need to have at least one member selected.
635 mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
636 }
637}
638
640{
641 setAllMemberState(!bSet);
642 std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
643 if (!mpChecks->get_cursor(xEntry.get()))
644 return;
646}
647
648IMPL_LINK(ScCheckListMenuControl, CommandHdl, const CommandEvent&, rCEvt, bool)
649{
650 if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
651 return false;
652
653 mxContextMenu->set_sensitive("less", mnCheckListVisibleRows > 4);
654 mxContextMenu->set_sensitive("more", mnCheckListVisibleRows < 42);
655
656 OString sCommand = mxContextMenu->popup_at_rect(mpChecks, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
657 if (sCommand.isEmpty())
658 return true;
659
660 if (sCommand == "more")
661 ++mnCheckListVisibleRows;
662 else if (sCommand == "less")
663 --mnCheckListVisibleRows;
664 ResizeToRequest();
665
666 return true;
667}
668
670{
671 int nChecksHeight = mxTreeChecks->get_height_rows(mnCheckListVisibleRows);
672 mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
673 mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
674 mxPopover->resize_to_request();
675}
676
678{
679 if (&rBtn == mxBtnOk.get())
680 close(true);
681 else if (&rBtn == mxBtnCancel.get())
682 close(false);
683 else if (&rBtn == mxBtnSelectSingle.get() || &rBtn == mxBtnUnselectSingle.get())
684 {
685 std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
686 bool bEntry = mpChecks->get_cursor(xEntry.get());
687 if (!bEntry)
688 xEntry.reset();
689 if (bEntry && mpChecks->get_sensitive(*xEntry, 0))
690 {
691 selectCurrentMemberOnly(&rBtn == mxBtnSelectSingle.get());
692 Check(xEntry.get());
693 }
694 }
695}
696
698{
699 switch (mePrevToggleAllState)
700 {
701 case TRISTATE_FALSE:
702 mxChkToggleAll->set_state(TRISTATE_TRUE);
703 setAllMemberState(true);
704 break;
705 case TRISTATE_TRUE:
706 mxChkToggleAll->set_state(TRISTATE_FALSE);
707 setAllMemberState(false);
708 break;
709 case TRISTATE_INDET:
710 default:
711 mxChkToggleAll->set_state(TRISTATE_TRUE);
712 setAllMemberState(true);
713 break;
714 }
715
716 mePrevToggleAllState = mxChkToggleAll->get_state();
717}
718
719namespace
720{
721 void insertMember(weld::TreeView& rView, const weld::TreeIter& rIter, const ScCheckListMember& rMember, bool bChecked)
722 {
723 OUString aLabel = rMember.maName;
724 if (aLabel.isEmpty())
725 aLabel = ScResId(STR_EMPTYDATA);
726 rView.set_toggle(rIter, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
727 rView.set_text(rIter, aLabel, 0);
728 rView.set_sensitive(rIter, !rMember.mbHiddenByOtherFilter);
729 }
730}
731
733{
734 OUString aSearchText = mxEdSearch->get_text();
735 aSearchText = ScGlobal::getCharClass().lowercase( aSearchText );
736 bool bSearchTextEmpty = aSearchText.isEmpty();
737 size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
738 [](const ScCheckListMember& rLMem) { return !rLMem.mbHiddenByOtherFilter; });
739 size_t nSelCount = 0;
740
741 // This branch is the general case, the other is an optimized variant of
742 // this one where we can take advantage of knowing we have no hierarchy
743 if (mbHasDates)
744 {
745 mpChecks->freeze();
746
747 bool bSomeDateDeletes = false;
748
749 for (size_t i = 0; i < nEnableMember; ++i)
750 {
751 bool bIsDate = maMembers[i].mbDate;
752 bool bPartialMatch = false;
753
754 OUString aLabelDisp = maMembers[i].maName;
755 if ( aLabelDisp.isEmpty() )
756 aLabelDisp = ScResId( STR_EMPTYDATA );
757
758 if ( !bSearchTextEmpty )
759 {
760 if ( !bIsDate )
761 bPartialMatch = ( ScGlobal::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
762 else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
763 bPartialMatch = (ScGlobal::getCharClass().lowercase( OUString(
764 maMembers[i].maRealName + maMembers[i].maDateParts[1] )).indexOf( aSearchText ) != -1);
765 else
766 continue;
767 }
768 else if ( bIsDate && maMembers[i].meDatePartType != ScCheckListMember::DAY )
769 continue;
770
771 if ( bSearchTextEmpty )
772 {
773 auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, maMembers[i].mbVisible);
774 updateMemberParents(xLeaf.get(), i);
775 if ( maMembers[i].mbVisible )
776 ++nSelCount;
777 continue;
778 }
779
780 if ( bPartialMatch )
781 {
782 auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i]);
783 updateMemberParents(xLeaf.get(), i);
784 ++nSelCount;
785 }
786 else
787 {
788 ShowCheckEntry(aLabelDisp, maMembers[i], false, false);
789 if( bIsDate )
790 bSomeDateDeletes = true;
791 }
792 }
793
794 if ( bSomeDateDeletes )
795 {
796 for (size_t i = 0; i < nEnableMember; ++i)
797 {
798 if (!maMembers[i].mbDate)
799 continue;
800 if (maMembers[i].meDatePartType != ScCheckListMember::DAY)
801 continue;
802 updateMemberParents(nullptr, i);
803 }
804 }
805
806 mpChecks->thaw();
807 }
808 else
809 {
810 mpChecks->freeze();
811
812 // when there are a lot of rows, it is cheaper to simply clear the tree and either
813 // re-initialise or just insert the filtered lines
814 mpChecks->clear();
815
816 mpChecks->thaw();
817
818 if (bSearchTextEmpty)
819 nSelCount = initMembers();
820 else
821 {
822 std::vector<size_t> aShownIndexes;
823
824 for (size_t i = 0; i < nEnableMember; ++i)
825 {
826 assert(!maMembers[i].mbDate);
827
828 OUString aLabelDisp = maMembers[i].maName;
829 if ( aLabelDisp.isEmpty() )
830 aLabelDisp = ScResId( STR_EMPTYDATA );
831
832 bool bPartialMatch = ScGlobal::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1;
833
834 if (!bPartialMatch)
835 continue;
836
837 aShownIndexes.push_back(i);
838 }
839
840 std::vector<int> aFixedWidths { mnCheckWidthReq };
841 // tdf#122419 insert in the fastest order, this might be backwards.
842 mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes, &nSelCount](weld::TreeIter& rIter, int i) {
843 size_t nIndex = aShownIndexes[i];
844 insertMember(*mpChecks, rIter, maMembers[nIndex], true);
845 ++nSelCount;
846 }, nullptr, &aFixedWidths);
847 }
848 }
849
850 if ( nSelCount == nEnableMember )
851 mxChkToggleAll->set_state( TRISTATE_TRUE );
852 else if ( nSelCount == 0 )
853 mxChkToggleAll->set_state( TRISTATE_FALSE );
854 else
855 mxChkToggleAll->set_state( TRISTATE_INDET );
856
857 if ( !maConfig.mbAllowEmptySet )
858 {
859 const bool bEmptySet( nSelCount == 0 );
860 mpChecks->set_sensitive(!bEmptySet);
861 mxChkToggleAll->set_sensitive(!bEmptySet);
862 mxBtnSelectSingle->set_sensitive(!bEmptySet);
863 mxBtnUnselectSingle->set_sensitive(!bEmptySet);
864 mxBtnOk->set_sensitive(!bEmptySet);
865 }
866}
867
869{
870 if (mxBtnOk->get_sensitive())
871 close(true);
872 return true;
873}
874
876{
877 Check(&rRowCol.first);
878}
879
881{
882 if (pEntry)
883 CheckEntry(*pEntry, mpChecks->get_toggle(*pEntry) == TRISTATE_TRUE);
884 size_t nNumChecked = GetCheckedEntryCount();
885 size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
886 [](const ScCheckListMember& rLMem) { return !rLMem.mbHiddenByOtherFilter; });
887 if (nNumChecked == nEnableMember)
888 // all members visible
889 mxChkToggleAll->set_state(TRISTATE_TRUE);
890 else if (nNumChecked == 0)
891 // no members visible
892 mxChkToggleAll->set_state(TRISTATE_FALSE);
893 else
894 mxChkToggleAll->set_state(TRISTATE_INDET);
895
897 // We need to have at least one member selected.
898 mxBtnOk->set_sensitive(nNumChecked != 0);
899
901}
902
904{
905 if ( !maMembers[nIdx].mbDate || maMembers[nIdx].meDatePartType != ScCheckListMember::DAY )
906 return;
907
908 OUString aYearName = maMembers[nIdx].maDateParts[0];
909 OUString aMonthName = maMembers[nIdx].maDateParts[1];
910 auto aItr = maYearMonthMap.find(aYearName + aMonthName);
911
912 if ( pLeaf )
913 {
914 std::unique_ptr<weld::TreeIter> xYearEntry;
915 std::unique_ptr<weld::TreeIter> xMonthEntry = mpChecks->make_iterator(pLeaf);
916 if (!mpChecks->iter_parent(*xMonthEntry))
917 xMonthEntry.reset();
918 else
919 {
920 xYearEntry = mpChecks->make_iterator(xMonthEntry.get());
921 if (!mpChecks->iter_parent(*xYearEntry))
922 xYearEntry.reset();
923 }
924
925 maMembers[nIdx].mxParent = std::move(xMonthEntry);
926 if ( aItr != maYearMonthMap.end() )
927 {
928 size_t nMonthIdx = aItr->second;
929 maMembers[nMonthIdx].mxParent = std::move(xYearEntry);
930 }
931 }
932 else
933 {
934 std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
935 if (aItr != maYearMonthMap.end() && !xYearEntry)
936 {
937 size_t nMonthIdx = aItr->second;
938 maMembers[nMonthIdx].mxParent.reset();
939 maMembers[nIdx].mxParent.reset();
940 }
941 else if (xYearEntry && !FindEntry(xYearEntry.get(), aMonthName))
942 maMembers[nIdx].mxParent.reset();
943 }
944}
945
947{
948 maMembers.reserve(n);
949}
950
951void ScCheckListMenuControl::addDateMember(const OUString& rsName, double nVal, bool bVisible, bool bHiddenByOtherFilter)
952{
954
955 // Convert the numeric date value to a date object.
956 Date aDate = pFormatter->GetNullDate();
957 aDate.AddDays(rtl::math::approxFloor(nVal));
958
959 sal_Int16 nYear = aDate.GetYear();
960 sal_uInt16 nMonth = aDate.GetMonth();
961 sal_uInt16 nDay = aDate.GetDay();
962
963 // Get the localized month name list.
965 uno::Sequence<i18n::CalendarItem2> aMonths = rCalendar.getMonths();
966 if (aMonths.getLength() < nMonth)
967 return;
968
969 OUString aYearName = OUString::number(nYear);
970 OUString aMonthName = aMonths[nMonth-1].FullName;
971 OUString aDayName = OUString::number(nDay);
972
973 if ( aDayName.getLength() == 1 )
974 aDayName = "0" + aDayName;
975
976 mpChecks->freeze();
977
978 std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
979 if (!xYearEntry)
980 {
981 xYearEntry = mpChecks->make_iterator();
982 mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
983 mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
984 mpChecks->set_text(*xYearEntry, aYearName, 0);
985 mpChecks->set_sensitive(*xYearEntry, !bHiddenByOtherFilter);
986 ScCheckListMember aMemYear;
987 aMemYear.maName = aYearName;
988 aMemYear.maRealName = rsName;
989 aMemYear.mbDate = true;
990 aMemYear.mbLeaf = false;
991 aMemYear.mbVisible = bVisible;
992 aMemYear.mbHiddenByOtherFilter = bHiddenByOtherFilter;
993 aMemYear.mxParent.reset();
995 maMembers.emplace_back(std::move(aMemYear));
996 }
997
998 std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), aMonthName);
999 if (!xMonthEntry)
1000 {
1001 xMonthEntry = mpChecks->make_iterator();
1002 mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
1003 mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
1004 mpChecks->set_text(*xMonthEntry, aMonthName, 0);
1005 mpChecks->set_sensitive(*xMonthEntry, !bHiddenByOtherFilter);
1006 ScCheckListMember aMemMonth;
1007 aMemMonth.maName = aMonthName;
1008 aMemMonth.maRealName = rsName;
1009 aMemMonth.mbDate = true;
1010 aMemMonth.mbLeaf = false;
1011 aMemMonth.mbVisible = bVisible;
1012 aMemMonth.mbHiddenByOtherFilter = bHiddenByOtherFilter;
1013 aMemMonth.mxParent = std::move(xYearEntry);
1015 maMembers.emplace_back(std::move(aMemMonth));
1016 maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
1017 }
1018
1019 std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), aDayName);
1020 if (!xDayEntry)
1021 {
1022 xDayEntry = mpChecks->make_iterator();
1023 mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
1024 mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
1025 mpChecks->set_text(*xDayEntry, aDayName, 0);
1026 mpChecks->set_sensitive(*xDayEntry, !bHiddenByOtherFilter);
1027 ScCheckListMember aMemDay;
1028 aMemDay.maName = aDayName;
1029 aMemDay.maRealName = rsName;
1030 aMemDay.maDateParts.resize(2);
1031 aMemDay.maDateParts[0] = aYearName;
1032 aMemDay.maDateParts[1] = aMonthName;
1033 aMemDay.mbDate = true;
1034 aMemDay.mbLeaf = true;
1035 aMemDay.mbVisible = bVisible;
1036 aMemDay.mbHiddenByOtherFilter = bHiddenByOtherFilter;
1037 aMemDay.mxParent = std::move(xMonthEntry);
1039 maMembers.emplace_back(std::move(aMemDay));
1040 }
1041
1042 mpChecks->thaw();
1043}
1044
1045void ScCheckListMenuControl::addMember(const OUString& rName, const double nVal, bool bVisible, bool bHiddenByOtherFilter, bool bValue)
1046{
1047 ScCheckListMember aMember;
1048 // tdf#46062 - indicate hidden whitespaces using quotes
1049 aMember.maName = o3tl::trim(rName) != rName ? "\"" + rName + "\"" : rName;
1050 aMember.maRealName = rName;
1051 aMember.mnValue = nVal;
1052 aMember.mbDate = false;
1053 aMember.mbLeaf = true;
1054 aMember.mbValue = bValue;
1055 aMember.mbVisible = bVisible;
1056 aMember.mbHiddenByOtherFilter = bHiddenByOtherFilter;
1057 aMember.mxParent.reset();
1058 maMembers.emplace_back(std::move(aMember));
1059}
1060
1061std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::FindEntry(const weld::TreeIter* pParent, std::u16string_view sNode)
1062{
1063 std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(pParent);
1064 bool bEntry = pParent ? mpChecks->iter_children(*xEntry) : mpChecks->get_iter_first(*xEntry);
1065 while (bEntry)
1066 {
1067 if (sNode == mpChecks->get_text(*xEntry, 0))
1068 return xEntry;
1069 bEntry = mpChecks->iter_next_sibling(*xEntry);
1070 }
1071 return nullptr;
1072}
1073
1074void ScCheckListMenuControl::GetRecursiveChecked(const weld::TreeIter* pEntry, std::unordered_set<OUString>& vOut,
1075 OUString& rLabel)
1076{
1077 if (mpChecks->get_toggle(*pEntry) != TRISTATE_TRUE)
1078 return;
1079
1080 // We have to hash parents and children together.
1081 // Per convention for easy access in getResult()
1082 // "child;parent;grandparent" while descending.
1083 if (rLabel.isEmpty())
1084 rLabel = mpChecks->get_text(*pEntry, 0);
1085 else
1086 rLabel = mpChecks->get_text(*pEntry, 0) + ";" + rLabel;
1087
1088 // Prerequisite: the selection mechanism guarantees that if a child is
1089 // selected then also the parent is selected, so we only have to
1090 // inspect the children in case the parent is selected.
1091 if (!mpChecks->iter_has_child(*pEntry))
1092 return;
1093
1094 std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(pEntry));
1095 bool bChild = mpChecks->iter_children(*xChild);
1096 while (bChild)
1097 {
1098 OUString aLabel = rLabel;
1099 GetRecursiveChecked(xChild.get(), vOut, aLabel);
1100 if (!aLabel.isEmpty() && aLabel != rLabel)
1101 vOut.insert(aLabel);
1102 bChild = mpChecks->iter_next_sibling(*xChild);
1103 }
1104 // Let the caller not add the parent alone.
1105 rLabel.clear();
1106}
1107
1108std::unordered_set<OUString> ScCheckListMenuControl::GetAllChecked()
1109{
1110 std::unordered_set<OUString> vResults(0);
1111
1112 std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1113 bool bEntry = mpChecks->get_iter_first(*xEntry);
1114 while (bEntry)
1115 {
1116 OUString aLabel;
1117 GetRecursiveChecked(xEntry.get(), vResults, aLabel);
1118 if (!aLabel.isEmpty())
1119 vResults.insert(aLabel);
1120 bEntry = mpChecks->iter_next_sibling(*xEntry);
1121 }
1122
1123 return vResults;
1124}
1125
1126bool ScCheckListMenuControl::IsChecked(std::u16string_view sName, const weld::TreeIter* pParent)
1127{
1128 std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
1129 return xEntry && mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1130}
1131
1132void ScCheckListMenuControl::CheckEntry(std::u16string_view sName, const weld::TreeIter* pParent, bool bCheck)
1133{
1134 std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
1135 if (xEntry)
1136 CheckEntry(*xEntry, bCheck);
1137}
1138
1139// Recursively check all children of rParent
1141{
1142 mpChecks->set_toggle(rParent, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
1143 std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(&rParent);
1144 bool bEntry = mpChecks->iter_children(*xEntry);
1145 while (bEntry)
1146 {
1147 CheckAllChildren(*xEntry, bCheck);
1148 bEntry = mpChecks->iter_next_sibling(*xEntry);
1149 }
1150}
1151
1153{
1154 // recursively check all items below rParent
1155 CheckAllChildren(rParent, bCheck);
1156 // checking rParent can affect ancestors, e.g. if ancestor is unchecked and rParent is
1157 // now checked then the ancestor needs to be checked also
1158 if (!mpChecks->get_iter_depth(rParent))
1159 return;
1160
1161 std::unique_ptr<weld::TreeIter> xAncestor(mpChecks->make_iterator(&rParent));
1162 bool bAncestor = mpChecks->iter_parent(*xAncestor);
1163 while (bAncestor)
1164 {
1165 // if any first level children checked then ancestor
1166 // needs to be checked, similarly if no first level children
1167 // checked then ancestor needs to be unchecked
1168 std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(xAncestor.get()));
1169 bool bChild = mpChecks->iter_children(*xChild);
1170 bool bChildChecked = false;
1171
1172 while (bChild)
1173 {
1174 if (mpChecks->get_toggle(*xChild) == TRISTATE_TRUE)
1175 {
1176 bChildChecked = true;
1177 break;
1178 }
1179 bChild = mpChecks->iter_next_sibling(*xChild);
1180 }
1181 mpChecks->set_toggle(*xAncestor, bChildChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
1182 bAncestor = mpChecks->iter_parent(*xAncestor);
1183 }
1184}
1185
1186std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::ShowCheckEntry(const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck)
1187{
1188 std::unique_ptr<weld::TreeIter> xEntry;
1189 if (!rMember.mbDate || rMember.mxParent)
1190 xEntry = FindEntry(rMember.mxParent.get(), sName);
1191
1192 if ( bShow )
1193 {
1194 if (!xEntry)
1195 {
1196 if (rMember.mbDate)
1197 {
1198 if (rMember.maDateParts.empty())
1199 return nullptr;
1200
1201 std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, rMember.maDateParts[0]);
1202 if (!xYearEntry)
1203 {
1204 xYearEntry = mpChecks->make_iterator();
1205 mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
1206 mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
1207 mpChecks->set_text(*xYearEntry, rMember.maDateParts[0], 0);
1208 }
1209 std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), rMember.maDateParts[1]);
1210 if (!xMonthEntry)
1211 {
1212 xMonthEntry = mpChecks->make_iterator();
1213 mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
1214 mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
1215 mpChecks->set_text(*xMonthEntry, rMember.maDateParts[1], 0);
1216 }
1217 std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), rMember.maName);
1218 if (!xDayEntry)
1219 {
1220 xDayEntry = mpChecks->make_iterator();
1221 mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
1222 mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
1223 mpChecks->set_text(*xDayEntry, rMember.maName, 0);
1224 }
1225 return xDayEntry; // Return leaf node
1226 }
1227
1228 xEntry = mpChecks->make_iterator();
1229 mpChecks->append(xEntry.get());
1230 mpChecks->set_toggle(*xEntry, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
1231 mpChecks->set_text(*xEntry, sName, 0);
1232 }
1233 else
1234 CheckEntry(*xEntry, bCheck);
1235 }
1236 else if (xEntry)
1237 {
1238 mpChecks->remove(*xEntry);
1239 if (rMember.mxParent)
1240 {
1241 std::unique_ptr<weld::TreeIter> xParent(mpChecks->make_iterator(rMember.mxParent.get()));
1242 while (xParent && !mpChecks->iter_has_child(*xParent))
1243 {
1244 std::unique_ptr<weld::TreeIter> xTmp(mpChecks->make_iterator(xParent.get()));
1245 if (!mpChecks->iter_parent(*xParent))
1246 xParent.reset();
1247 mpChecks->remove(*xTmp);
1248 }
1249 }
1250 }
1251 return nullptr;
1252}
1253
1255{
1256 int nRet = 0;
1257
1258 mpChecks->all_foreach([this, &nRet](weld::TreeIter& rEntry){
1259 if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
1260 ++nRet;
1261 return false;
1262 });
1263
1264 return nRet;
1265}
1266
1267IMPL_LINK(ScCheckListMenuControl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
1268{
1269 const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
1270
1271 if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
1272 {
1273 std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1274 bool bEntry = mpChecks->get_cursor(xEntry.get());
1275 if (bEntry && mpChecks->get_sensitive(*xEntry, 0))
1276 {
1277 bool bOldCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1278 CheckEntry(*xEntry, !bOldCheck);
1279 bool bNewCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1280 if (bOldCheck != bNewCheck)
1281 Check(xEntry.get());
1282 }
1283 return true;
1284 }
1285
1286 return false;
1287}
1288
1289size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth)
1290{
1291 size_t n = maMembers.size();
1292 size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
1293 [](const ScCheckListMember& rLMem) { return !rLMem.mbHiddenByOtherFilter; });
1294 size_t nVisMemCount = 0;
1295
1296 if (nMaxMemberWidth == -1)
1297 nMaxMemberWidth = mnCheckWidthReq;
1298
1299 if (!mpChecks->n_children() && !mbHasDates)
1300 {
1301 std::vector<int> aFixedWidths { nMaxMemberWidth };
1302 // tdf#134038 insert in the fastest order, this might be backwards so only do it for
1303 // the !mbHasDates case where no entry depends on another to exist before getting
1304 // inserted. We cannot retain pre-existing treeview content, only clear and fill it.
1305 mpChecks->bulk_insert_for_each(n, [this, &nVisMemCount](weld::TreeIter& rIter, int i) {
1306 assert(!maMembers[i].mbDate);
1307 insertMember(*mpChecks, rIter, maMembers[i], maMembers[i].mbVisible);
1308 if (maMembers[i].mbVisible)
1309 ++nVisMemCount;
1310 }, nullptr, &aFixedWidths);
1311 }
1312 else
1313 {
1314 mpChecks->freeze();
1315
1316 std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1317 std::vector<std::unique_ptr<weld::TreeIter>> aExpandRows;
1318
1319 for (size_t i = 0; i < n; ++i)
1320 {
1321 if (maMembers[i].mbDate)
1322 {
1323 CheckEntry(maMembers[i].maName, maMembers[i].mxParent.get(), maMembers[i].mbVisible);
1324 // Expand first node of checked dates
1326 {
1327 std::unique_ptr<weld::TreeIter> xDateEntry = FindEntry(nullptr, maMembers[i].maName);
1328 if (xDateEntry)
1329 aExpandRows.emplace_back(std::move(xDateEntry));
1330 }
1331 }
1332 else
1333 {
1334 mpChecks->append(xEntry.get());
1335 insertMember(*mpChecks, *xEntry, maMembers[i], maMembers[i].mbVisible);
1336 }
1337
1338 if (maMembers[i].mbVisible)
1339 ++nVisMemCount;
1340 }
1341
1342 mpChecks->thaw();
1343
1344 for (const auto& rRow : aExpandRows)
1345 mpChecks->expand_row(*rRow);
1346 }
1347
1348 if (nVisMemCount == nEnableMember)
1349 {
1350 // all members visible
1351 mxChkToggleAll->set_state(TRISTATE_TRUE);
1353 }
1354 else if (nVisMemCount == 0)
1355 {
1356 // no members visible
1357 mxChkToggleAll->set_state(TRISTATE_FALSE);
1359 }
1360 else
1361 {
1362 mxChkToggleAll->set_state(TRISTATE_INDET);
1364 }
1365
1366 if (nVisMemCount)
1367 mpChecks->select(0);
1368
1369 return nVisMemCount;
1370}
1371
1373{
1374 maConfig = rConfig;
1375}
1376
1378{
1379 return mxChkToggleAll->get_state() == TRISTATE_TRUE;
1380}
1381
1383{
1384 ResultType aResult;
1385 std::unordered_set<OUString> vCheckeds = GetAllChecked();
1386 size_t n = maMembers.size();
1387 for (size_t i = 0; i < n; ++i)
1388 {
1389 if ( maMembers[i].mbLeaf )
1390 {
1391 OUStringBuffer aLabel(maMembers[i].maName);
1392 if (aLabel.isEmpty())
1393 aLabel = ScResId(STR_EMPTYDATA);
1394
1395 /* TODO: performance-wise this looks suspicious, concatenating to
1396 * do the lookup for each leaf item seems wasteful. */
1397 // Checked labels are in the form "child;parent;grandparent".
1398 if (maMembers[i].mxParent)
1399 {
1400 std::unique_ptr<weld::TreeIter> xIter(mpChecks->make_iterator(maMembers[i].mxParent.get()));
1401 do
1402 {
1403 aLabel.append(";" + mpChecks->get_text(*xIter));
1404 }
1405 while (mpChecks->iter_parent(*xIter));
1406 }
1407
1408 bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
1409
1410 ResultEntry aResultEntry;
1411 aResultEntry.bValid = bState && !maMembers[i].mbHiddenByOtherFilter;
1412 aResultEntry.aName = maMembers[i].maRealName;
1413 aResultEntry.nValue = maMembers[i].mnValue;
1414 aResultEntry.bDate = maMembers[i].mbDate;
1415 aResultEntry.bValue = maMembers[i].mbValue;
1416 aResult.insert(aResultEntry);
1417 }
1418 }
1419 rResult.swap(aResult);
1420}
1421
1423{
1424 prepWindow();
1426 // We need to have at least one member selected.
1427 mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
1428
1429 tools::Rectangle aRect(rRect);
1430 if (maConfig.mbRTL)
1431 {
1432 // In RTL mode, the logical "left" is visual "right".
1434 {
1435 tools::Long nLeft = aRect.Left() - aRect.GetWidth();
1436 aRect.SetLeft( nLeft );
1437 }
1438 else
1439 {
1440 // in LOK mode, rRect is in document pixel coordinates, so width has to be added
1441 // to place the popup next to the (visual) left aligned button.
1442 aRect.Move(aRect.GetWidth(), 0);
1443 }
1444 }
1445 else if (mnWndWidth < aRect.GetWidth())
1446 {
1447 // Target rectangle (i.e. cell width) is wider than the window.
1448 // Simulate right-aligned launch by modifying the target rectangle
1449 // size.
1450 tools::Long nDiff = aRect.GetWidth() - mnWndWidth;
1451 aRect.AdjustLeft(nDiff );
1452 }
1453
1454 StartPopupMode(pWidget, aRect);
1455}
1456
1458{
1459 if (bOK && mxOKAction)
1460 mxOKAction->execute();
1461 EndPopupMode();
1462}
1463
1464void ScCheckListMenuControl::setExtendedData(std::unique_ptr<ExtendedData> p)
1465{
1466 mxExtendedData = std::move(p);
1467}
1468
1470{
1471 return mxExtendedData.get();
1472}
1473
1475{
1476 mxOKAction.reset(p);
1477}
1478
1480{
1481 mxPopupEndAction.reset(p);
1482}
1483
1485{
1486 mbIsPoppedUp = false;
1487 clearSelectedMenuItem();
1488 if (mxPopupEndAction)
1489 mxPopupEndAction->execute();
1490
1491 DropPendingEvents();
1492}
1493
1494int ScCheckListMenuControl::GetTextWidth(const OUString& rsName) const
1495{
1496 return mxDropDown->GetTextWidth(rsName);
1497}
1498
1500{
1501 int nBorder = nBorderWidth * 2 + 4;
1502 int nNewWidth = nMaxTextWidth - nBorder;
1503 if (nNewWidth > mnCheckWidthReq)
1504 {
1505 mnCheckWidthReq = nNewWidth;
1506 int nChecksHeight = mpChecks->get_height_rows(nCheckListVisibleRows);
1507 mpChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
1508 }
1509 return mnCheckWidthReq + nBorder;
1510}
1511
1513 : mxBuilder(Application::CreateBuilder(pParent, "modules/scalc/ui/filtersubdropdown.ui"))
1514 , mxPopover(mxBuilder->weld_popover("FilterSubDropDown"))
1515 , mxContainer(mxBuilder->weld_container("container"))
1516 , mxMenu(mxBuilder->weld_tree_view("menu"))
1517 , mxBackColorMenu(mxBuilder->weld_tree_view("background"))
1518 , mxTextColorMenu(mxBuilder->weld_tree_view("textcolor"))
1519 , mxScratchIter(mxMenu->make_iterator())
1520 , mrParentControl(rParentControl)
1521 , mnBackColorMenuPrefHeight(-1)
1522 , mnTextColorMenuPrefHeight(-1)
1523 , mbColorMenu(bColorMenu)
1524{
1525 mxMenu->hide();
1526 mxBackColorMenu->hide();
1527 mxTextColorMenu->hide();
1528
1529 if (!bColorMenu)
1530 {
1531 SetupMenu(*mxMenu);
1532 mxMenu->show();
1533 }
1534 else
1535 {
1536 mxBackColorMenu->set_clicks_to_toggle(1);
1537 mxBackColorMenu->enable_toggle_buttons(weld::ColumnToggleType::Radio);
1538 mxBackColorMenu->connect_changed(LINK(this, ScListSubMenuControl, ColorSelChangedHdl));
1539 mxTextColorMenu->set_clicks_to_toggle(1);
1540 mxTextColorMenu->enable_toggle_buttons(weld::ColumnToggleType::Radio);
1541 mxTextColorMenu->connect_changed(LINK(this, ScListSubMenuControl, ColorSelChangedHdl));
1544 }
1545}
1546
1548{
1549 rMenu.connect_row_activated(LINK(this, ScListSubMenuControl, RowActivatedHdl));
1550 rMenu.connect_key_press(LINK(this, ScListSubMenuControl, MenuKeyInputHdl));
1551}
1552
1554{
1556 mxPopupStartAction->execute();
1557
1558 mxPopover->popup_at_rect(pParent, rRect, weld::Placement::End);
1559
1561 rFirstMenu.set_cursor(0);
1562 rFirstMenu.select(0);
1563
1565}
1566
1568{
1569 mxPopover->popdown();
1570}
1571
1573{
1575 rFirstMenu.grab_focus();
1576}
1577
1579{
1580 return mxPopover->get_visible();
1581}
1582
1584{
1585 if (!mbColorMenu)
1586 mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height());
1587 else
1588 {
1589 int nBackColorMenuPrefHeight = mnBackColorMenuPrefHeight;
1590 if (nBackColorMenuPrefHeight == -1)
1591 nBackColorMenuPrefHeight = mxBackColorMenu->get_preferred_size().Height();
1592 mxBackColorMenu->set_size_request(-1, nBackColorMenuPrefHeight);
1593 int nTextColorMenuPrefHeight = mnTextColorMenuPrefHeight;
1594 if (nTextColorMenuPrefHeight == -1)
1595 nTextColorMenuPrefHeight = mxTextColorMenu->get_preferred_size().Height();
1596 mxTextColorMenu->set_size_request(-1, nTextColorMenuPrefHeight);
1597 }
1598}
1599
1601{
1603 aItem.mbEnabled = true;
1604 aItem.mxAction.reset(pAction);
1605 maMenuItems.emplace_back(std::move(aItem));
1606}
1607
1609{
1610 addItem(pAction);
1611 mxMenu->append(weld::toId(pAction), rText);
1612}
1613
1614void ScListSubMenuControl::addMenuColorItem(const OUString& rText, bool bActive, VirtualDevice& rImage,
1615 int nMenu, ScCheckListMenuControl::Action* pAction)
1616{
1617 addItem(pAction);
1618
1619 weld::TreeView& rColorMenu = nMenu == 0 ? *mxBackColorMenu : *mxTextColorMenu;
1620 rColorMenu.show();
1621
1622 OUString sId = weld::toId(pAction);
1623 rColorMenu.insert(nullptr, -1, &rText, &sId, nullptr, nullptr, false, mxScratchIter.get());
1624 rColorMenu.set_toggle(*mxScratchIter, bActive ? TRISTATE_TRUE : TRISTATE_FALSE);
1625 rColorMenu.set_image(*mxScratchIter, rImage);
1626
1627 if (mnTextColorMenuPrefHeight == -1 &&
1628 &rColorMenu == mxTextColorMenu.get() &&
1629 mxTextColorMenu->n_children() == nColorListVisibleRows)
1630 {
1631 mnTextColorMenuPrefHeight = mxTextColorMenu->get_preferred_size().Height();
1632 }
1633
1634 if (mnBackColorMenuPrefHeight == -1 &&
1635 &rColorMenu == mxBackColorMenu.get() &&
1636 mxBackColorMenu->n_children() == nColorListVisibleRows)
1637 {
1638 mnBackColorMenuPrefHeight = mxBackColorMenu->get_preferred_size().Height();
1639 }
1640}
1641
1643{
1645 maMenuItems.emplace_back(std::move(aItem));
1646
1647 mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
1648}
1649
1651{
1652 maMenuItems.clear();
1653 mxMenu->clear();
1654 mxBackColorMenu->clear();
1656 mxTextColorMenu->clear();
1658}
1659
1660IMPL_LINK(ScListSubMenuControl, MenuKeyInputHdl, const KeyEvent&, rKEvt, bool)
1661{
1662 bool bConsumed = false;
1663 const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
1664
1665 switch (rKeyCode.GetCode())
1666 {
1667 case KEY_ESCAPE:
1668 case KEY_LEFT:
1669 {
1670 mrParentControl.endSubMenu(*this);
1671 bConsumed = true;
1672 break;
1673 }
1674 case KEY_SPACE:
1675 case KEY_RETURN:
1676 {
1677 weld::TreeView& rMenu = !mbColorMenu ? *mxMenu :
1678 (mxBackColorMenu->has_focus() ? *mxBackColorMenu : *mxTextColorMenu);
1679 // don't toggle checkbutton, go straight to activating entry
1680 bConsumed = RowActivatedHdl(rMenu);
1681 break;
1682 }
1683 case KEY_DOWN:
1684 {
1685 if (mxTextColorMenu->get_visible() &&
1686 mxBackColorMenu->has_focus() &&
1687 mxBackColorMenu->get_selected_index() == mxBackColorMenu->n_children() - 1)
1688 {
1689 mxBackColorMenu->unselect_all();
1690 mxTextColorMenu->select(0);
1691 mxTextColorMenu->set_cursor(0);
1692 mxTextColorMenu->grab_focus();
1693 bConsumed = true;
1694 }
1695 break;
1696 }
1697 case KEY_UP:
1698 {
1699 if (mxBackColorMenu->get_visible() &&
1700 mxTextColorMenu->has_focus() &&
1701 mxTextColorMenu->get_selected_index() == 0)
1702 {
1703 mxTextColorMenu->unselect_all();
1704 int nIndex = mxBackColorMenu->n_children() - 1;
1705 mxBackColorMenu->select(nIndex);
1706 mxBackColorMenu->set_cursor(nIndex);
1707 mxBackColorMenu->grab_focus();
1708 bConsumed = true;
1709 }
1710 break;
1711 }
1712 }
1713
1714 return bConsumed;
1715}
1716
1717IMPL_LINK(ScListSubMenuControl, ColorSelChangedHdl, weld::TreeView&, rMenu, void)
1718{
1719 if (rMenu.get_selected_index() == -1)
1720 return;
1721 if (&rMenu != mxTextColorMenu.get())
1722 mxTextColorMenu->unselect_all();
1723 else
1724 mxBackColorMenu->unselect_all();
1725 rMenu.grab_focus();
1726}
1727
1728IMPL_LINK(ScListSubMenuControl, RowActivatedHdl, weld::TreeView&, rMenu, bool)
1729{
1730 executeMenuItem(weld::fromId<ScCheckListMenuControl::Action*>(rMenu.get_selected_id()));
1731 return true;
1732}
1733
1735{
1736 // if no action is defined.
1737 if (!pAction)
1738 return;
1739
1740 const bool bClosePopup = pAction->execute();
1741 if (bClosePopup)
1743}
1744
1746{
1747 mxPopupStartAction.reset(p);
1748}
1749
1751{
1752 EndPopupMode();
1754}
1755
1756/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Reference< container::XNameAccess > mxContainer
WeakReference< XInterface > mxParent
OUString maName
IMPL_LINK_NOARG(ScCheckListMenuControl::SubMenuItemData, TimeoutHdl, Timer *, void)
constexpr int nCheckListVisibleRows
constexpr int nColorListVisibleRows
IMPL_LINK(ScCheckListMenuControl, MenuKeyInputHdl, const KeyEvent &, rKEvt, bool)
constexpr int nBorderWidth
static OUString GetToolkitName()
static const AllSettings & GetSettings()
static ImplSVEvent * PostUserEvent(const Link< void *, void > &rLink, void *pCaller=nullptr, bool bReferenceLink=false)
static void RemoveUserEvent(ImplSVEvent *nUserEvent)
css::uno::Sequence< css::i18n::CalendarItem2 > getMonths() const
OUString lowercase(const OUString &rStr, sal_Int32 nPos, sal_Int32 nCount) const
void AddDays(sal_Int32 nAddDays)
sal_Int16 GetYear() const
sal_uInt16 GetDay() const
sal_uInt16 GetMonth() const
void DrawSymbol(const tools::Rectangle &rRect, SymbolType eType, const Color &rColor, DrawSymbolFlags nStyle=DrawSymbolFlags::NONE)
Action to perform when an event takes place.
This class implements a popup window for the auto filter dropdown.
std::unique_ptr< Action > mxPopupEndAction
std::unordered_set< OUString > GetAllChecked()
void setAllMemberState(bool bSet)
std::unique_ptr< weld::Entry > mxEdSearch
void addMember(const OUString &rName, const double nVal, bool bVisible, bool bHiddenByOtherFilter, bool bValue=false)
std::unique_ptr< weld::TreeIter > ShowCheckEntry(const OUString &sName, ScCheckListMember &rMember, bool bShow=true, bool bCheck=true)
void StartPopupMode(weld::Widget *pParent, const tools::Rectangle &rRect)
void getResult(ResultType &rResult)
void CheckEntry(std::u16string_view sName, const weld::TreeIter *pParent, bool bCheck)
std::unique_ptr< weld::Button > mxBtnUnselectSingle
std::unique_ptr< weld::Widget > mxNonMenu
std::unique_ptr< Action > mxOKAction
void handleMenuTimeout(const SubMenuItemData *pTimer)
void GetRecursiveChecked(const weld::TreeIter *pEntry, std::unordered_set< OUString > &vOut, OUString &rLabel)
void addMenuItem(const OUString &rText, Action *pAction)
void CheckAllChildren(const weld::TreeIter &rEntry, bool bCheck)
std::unique_ptr< weld::CheckButton > mxChkToggleAll
void setExtendedData(std::unique_ptr< ExtendedData > p)
Set auxiliary data that the client code might need.
weld::TreeView * mpChecks
std::unique_ptr< weld::Widget > mxBox
ScListSubMenuControl * addSubMenuItem(const OUString &rText, bool bEnabled, bool bColorMenu)
void setSelectedMenuItem(size_t nPos)
size_t getSubMenuPos(const ScListSubMenuControl *pSubMenu)
std::unique_ptr< weld::Popover > mxPopover
ScCheckListMenuControl(weld::Widget *pParent, ScViewData &rViewData, bool bTreeMode, int nWidth)
void setConfig(const Config &rConfig)
void setPopupEndAction(Action *p)
std::unique_ptr< weld::Button > mxBtnSelectSingle
void endSubMenu(ScListSubMenuControl &rSubMenu)
std::unique_ptr< ExtendedData > mxExtendedData
void terminateAllPopupMenus()
Dismiss all visible popup menus and set focus back to the application window.
int mnCheckListVisibleRows
whole window width.
void executeMenuItem(size_t nPos)
std::unique_ptr< weld::TreeIter > FindEntry(const weld::TreeIter *pParent, std::u16string_view sNode)
ImplSVEvent * mnAsyncSetDropdownPosId
void prepWindow()
Calculate the appropriate window size based on the menu items.
void setMemberSize(size_t n)
size_t initMembers(int nMaxMemberWidth=-1)
void Check(const weld::TreeIter *pIter)
std::unique_ptr< weld::TreeView > mxMenu
std::unique_ptr< weld::Container > mxContainer
tools::Rectangle GetSubMenuParentRect()
Get the area of the active row.
ScopedVclPtr< VirtualDevice > mxDropDown
void selectCurrentMemberOnly(bool bSet)
int IncreaseWindowWidthToFitText(int nMaxTextWidth)
bool IsChecked(std::u16string_view sName, const weld::TreeIter *pParent)
ImplSVEvent * mnAsyncPostPopdownId
SubMenuItemData maOpenTimer
std::unique_ptr< weld::TreeView > mxTreeChecks
void updateMemberParents(const weld::TreeIter *pLeaf, size_t nIdx)
std::map< OUString, size_t > maYearMonthMap
int mnWndWidth
matching width request for mxChecks
void selectMenuItem(size_t nPos, bool bSubMenuTimer)
std::unique_ptr< weld::Button > mxBtnCancel
int GetTextWidth(const OUString &rsName) const
std::unique_ptr< weld::TreeIter > mxScratchIter
void setOKAction(Action *p)
std::vector< ScCheckListMember > maMembers
ExtendedData * getExtendedData()
Get the store auxiliary data, or NULL if no such data is stored.
std::unique_ptr< weld::TreeView > mxListChecks
void launch(weld::Widget *pWidget, const tools::Rectangle &rRect)
std::unique_ptr< weld::Box > mxButtonBox
int GetCheckedEntryCount() const
static constexpr size_t MENU_NOT_SELECTED
std::unique_ptr< weld::Button > mxBtnOk
void setSubMenuFocused(const ScListSubMenuControl *pSubMenu)
std::vector< MenuItemData > maMenuItems
std::set< ResultEntry > ResultType
SubMenuItemData maCloseTimer
void queueLaunchSubMenu(size_t nPos, ScListSubMenuControl *pMenu)
void addDateMember(const OUString &rName, double nVal, bool bVisible, bool bHiddenByOtherFilter)
SC_DLLPUBLIC SvNumberFormatter * GetFormatTable() const
Definition: documen2.cxx:461
static SC_DLLPUBLIC const CharClass & getCharClass()
Definition: global.cxx:1062
static CalendarWrapper & GetCalendar()
Definition: global.cxx:1071
std::unique_ptr< weld::TreeIter > mxScratchIter
std::unique_ptr< weld::Popover > mxPopover
void addMenuItem(const OUString &rText, ScCheckListMenuControl::Action *pAction)
std::unique_ptr< weld::TreeView > mxTextColorMenu
std::unique_ptr< ScCheckListMenuControl::Action > mxPopupStartAction
std::unique_ptr< weld::TreeView > mxMenu
void executeMenuItem(ScCheckListMenuControl::Action *pAction)
void SetupMenu(weld::TreeView &rMenu)
void StartPopupMode(weld::Widget *pParent, const tools::Rectangle &rRect)
std::vector< ScCheckListMenuControl::MenuItemData > maMenuItems
void setPopupStartAction(ScCheckListMenuControl::Action *p)
ScCheckListMenuControl & mrParentControl
void terminateAllPopupMenus()
Dismiss all visible popup menus and set focus back to the application window.
void addItem(ScCheckListMenuControl::Action *pAction)
std::unique_ptr< weld::TreeView > mxBackColorMenu
void addMenuColorItem(const OUString &rText, bool bActive, VirtualDevice &rImage, int nMenu, ScCheckListMenuControl::Action *pAction)
ScListSubMenuControl(weld::Widget *pParent, ScCheckListMenuControl &rParentControl, bool bColorMenu)
ScDocument & GetDocument() const
Definition: viewdata.hxx:380
constexpr tools::Long Width() const
const Date & GetNullDate() const
void Stop()
void SetTimeout(sal_uInt64 nTimeoutMs)
void SetInvokeHandler(const Link< Timer *, void > &rLink)
virtual void Start(bool bStartTimer=true) override
virtual void Invoke() override
reference_type * get() const
constexpr tools::Long GetWidth() const
constexpr void SetLeft(tools::Long v)
void Move(tools::Long nHorzMoveDelta, tools::Long nVertMoveDelta)
tools::Long AdjustLeft(tools::Long nHorzMoveDelta)
constexpr tools::Long Left() const
sal_uInt16 GetCode() const
virtual std::unique_ptr< TreeIter > make_iterator(const TreeIter *pOrig=nullptr) const=0
virtual void expand_row(const TreeIter &rIter)=0
void connect_row_activated(const Link< TreeView &, bool > &rLink)
virtual void set_text(int row, const OUString &rText, int col=-1)=0
virtual OUString get_text(int row, int col=-1) const=0
virtual void set_toggle(int row, TriState eState, int col=-1)=0
virtual int n_children() const=0
virtual void insert(const TreeIter *pParent, int pos, const OUString *pStr, const OUString *pId, const OUString *pIconName, VirtualDevice *pImageSurface, bool bChildrenOnDemand, TreeIter *pRet)=0
std::pair< const TreeIter &, int > iter_col
virtual void set_image(int row, const OUString &rImage, int col=-1)=0
virtual bool get_iter_first(TreeIter &rIter) const=0
void append(TreeIter *pRet=nullptr)
virtual int get_height_rows(int nRows) const=0
virtual void remove(int pos)=0
virtual bool iter_next_sibling(TreeIter &rIter) const=0
virtual bool get_sensitive(int row, int col) const=0
virtual void select(int pos)=0
virtual bool iter_parent(TreeIter &rIter) const=0
virtual bool iter_children(TreeIter &rIter) const=0
virtual TriState get_toggle(int row, int col=-1) const=0
virtual bool iter_has_child(const TreeIter &rIter) const=0
virtual void bulk_insert_for_each(int nSourceCount, const std::function< void(TreeIter &, int nSourceIndex)> &func, const weld::TreeIter *pParent=nullptr, const std::vector< int > *pFixedWidths=nullptr)=0
virtual bool get_cursor(TreeIter *pIter) const=0
virtual void set_cursor(int pos)=0
virtual int get_iter_depth(const TreeIter &rIter) const=0
virtual void all_foreach(const std::function< bool(TreeIter &)> &func)=0
virtual void set_sensitive(int row, bool bSensitive, int col=-1)=0
virtual void show()=0
virtual void grab_focus()=0
virtual void set_size_request(int nWidth, int nHeight)=0
virtual void freeze()=0
virtual void thaw()=0
virtual void connect_key_press(const Link< const KeyEvent &, bool > &rLink)
RegionData_Impl * mpParent
bool close
TRISTATE_FALSE
TRISTATE_INDET
TRISTATE_TRUE
sal_Int32 nIndex
void * p
sal_Int64 n
constexpr sal_uInt16 KEY_RETURN
constexpr sal_uInt16 KEY_ESCAPE
constexpr sal_uInt16 KEY_LEFT
constexpr sal_uInt16 KEY_UP
constexpr sal_uInt16 KEY_RIGHT
constexpr sal_uInt16 KEY_DOWN
constexpr sal_uInt16 KEY_SPACE
sal_uInt16 nPos
const char * sName
tools::Long const nBorder
int i
std::u16string_view trim(std::u16string_view str)
long Long
OUString toId(const void *pValue)
OUString ScResId(TranslateId aId)
Definition: scdll.cxx:90
const double mnValue
std::vector< OUString > maDateParts
DatePartType meDatePartType
std::unique_ptr< weld::TreeIter > mxParent
Configuration options for this popup window.
Extended data that the client code may need to store.
std::unique_ptr< ScListSubMenuControl > mxSubMenuWin
std::shared_ptr< Action > mxAction
SubMenuItemData(ScCheckListMenuControl *pParent)
bool mbVisible
bool bVisible
OUString aLabel
OUString sId