1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <checklistmenu.hxx>
21 #include <o3tl/safeint.hxx>
22 #include <o3tl/string_view.hxx>
23 #include <globstr.hrc>
24 #include <scresid.hxx>
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>
33 #include <unotools/charclass.hxx>
34 #include <comphelper/lok.hxx>
35 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
36 #include <tools/json_writer.hxx>
37 #include <svl/numformat.hxx>
39 #include <document.hxx>
40 #include <viewdata.hxx>
42 using namespace com::sun::star
;
43 using ::com::sun::star::uno::Reference
;
45 ScCheckListMenuControl::MenuItemData::MenuItemData()
50 ScCheckListMenuControl::SubMenuItemData::SubMenuItemData(ScCheckListMenuControl
* pParent
)
51 : maTimer("sc SubMenuItemData maTimer")
53 , mnMenuPos(MENU_NOT_SELECTED
)
56 maTimer
.SetInvokeHandler(LINK(this, ScCheckListMenuControl::SubMenuItemData
, TimeoutHdl
));
57 maTimer
.SetTimeout(Application::GetSettings().GetMouseSettings().GetMenuDelay());
60 void ScCheckListMenuControl::SubMenuItemData::reset()
63 mnMenuPos
= MENU_NOT_SELECTED
;
67 IMPL_LINK_NOARG(ScCheckListMenuControl::SubMenuItemData
, TimeoutHdl
, Timer
*, void)
69 mpParent
->handleMenuTimeout(this);
72 IMPL_LINK_NOARG(ScCheckListMenuControl
, RowActivatedHdl
, weld::TreeView
&, bool)
74 executeMenuItem(mxMenu
->get_selected_index());
78 IMPL_LINK(ScCheckListMenuControl
, MenuKeyInputHdl
, const KeyEvent
&, rKEvt
, bool)
80 // Assume that once the keyboard is used that focus should restore to this menu
81 // on dismissing a submenu
82 SetRestoreFocus(ScCheckListMenuControl::RestoreFocus::Menu
);
84 const vcl::KeyCode
& rKeyCode
= rKEvt
.GetKeyCode();
86 switch (rKeyCode
.GetCode())
90 if (mnSelectedMenu
>= maMenuItems
.size() || mnSelectedMenu
== MENU_NOT_SELECTED
)
93 const MenuItemData
& rMenu
= maMenuItems
[mnSelectedMenu
];
94 if (!rMenu
.mxSubMenuWin
)
97 executeMenuItem(mnSelectedMenu
);
104 IMPL_LINK_NOARG(ScCheckListMenuControl
, SelectHdl
, weld::TreeView
&, void)
106 sal_uInt32 nSelectedMenu
= MENU_NOT_SELECTED
;
107 if (!mxMenu
->get_selected(mxScratchIter
.get()))
109 // reselect current item if its submenu is up and the launching item
110 // became unselected by mouse moving out of the top level menu
111 if (mnSelectedMenu
< maMenuItems
.size() &&
112 maMenuItems
[mnSelectedMenu
].mxSubMenuWin
&&
113 maMenuItems
[mnSelectedMenu
].mxSubMenuWin
->IsVisible())
115 mxMenu
->select(mnSelectedMenu
);
120 nSelectedMenu
= mxMenu
->get_iter_index_in_parent(*mxScratchIter
);
122 setSelectedMenuItem(nSelectedMenu
);
125 void ScCheckListMenuControl::addMenuItem(const OUString
& rText
, Action
* pAction
)
128 aItem
.mbEnabled
= true;
129 aItem
.mxAction
.reset(pAction
);
130 maMenuItems
.emplace_back(std::move(aItem
));
133 mxMenu
->append_text(rText
);
134 mxMenu
->set_image(mxMenu
->n_children() - 1, css::uno::Reference
<css::graphic::XGraphic
>(), 1);
137 void ScCheckListMenuControl::addSeparator()
140 maMenuItems
.emplace_back(std::move(aItem
));
142 mxMenu
->append_separator("separator" + OUString::number(maMenuItems
.size()));
145 IMPL_LINK(ScCheckListMenuControl
, TreeSizeAllocHdl
, const Size
&, rSize
, void)
147 if (maAllocatedSize
== rSize
)
149 maAllocatedSize
= rSize
;
151 if (!mnAsyncSetDropdownPosId
&& Application::GetToolkitName().startsWith("gtk"))
153 // for gtk retry again later in case it didn't work (wayland)
154 mnAsyncSetDropdownPosId
= Application::PostUserEvent(LINK(this, ScCheckListMenuControl
, SetDropdownPosHdl
));
158 void ScCheckListMenuControl::SetDropdownPos()
160 std::vector
<int> aWidths
162 o3tl::narrowing
<int>(maAllocatedSize
.Width() - (mxMenu
->get_text_height() * 3) / 4 - 6)
164 mxMenu
->set_column_fixed_widths(aWidths
);
167 IMPL_LINK_NOARG(ScCheckListMenuControl
, SetDropdownPosHdl
, void*, void)
169 mnAsyncSetDropdownPosId
= nullptr;
171 mxMenu
->queue_resize();
174 void ScCheckListMenuControl::CreateDropDown()
176 const StyleSettings
& rStyleSettings
= Application::GetSettings().GetStyleSettings();
178 Color aSpinColor
= rStyleSettings
.GetDialogTextColor();
179 int nWidth
= (mxMenu
->get_text_height() * 3) / 4;
180 mxDropDown
->SetOutputSizePixel(Size(nWidth
, nWidth
), /*bErase*/true, /*bAlphaMaskTransparent*/true);
181 DecorationView
aDecoView(mxDropDown
.get());
182 aDecoView
.DrawSymbol(tools::Rectangle(Point(0, 0), Size(nWidth
, nWidth
)),
183 SymbolType::SPIN_RIGHT
, aSpinColor
,
184 DrawSymbolFlags::NONE
);
187 ScListSubMenuControl
* ScCheckListMenuControl::addSubMenuItem(const OUString
& rText
, bool bEnabled
, bool bColorMenu
)
190 aItem
.mbEnabled
= bEnabled
;
192 aItem
.mxSubMenuWin
.reset(new ScListSubMenuControl(mxMenu
.get(), *this, bColorMenu
));
193 maMenuItems
.emplace_back(std::move(aItem
));
196 mxMenu
->append_text(rText
);
197 mxMenu
->set_image(mxMenu
->n_children() - 1, *mxDropDown
, 1);
198 return maMenuItems
.back().mxSubMenuWin
.get();
201 void ScCheckListMenuControl::executeMenuItem(size_t nPos
)
203 if (nPos
>= maMenuItems
.size())
206 const MenuItemData
& rMenu
= maMenuItems
[nPos
];
207 if (rMenu
.mxSubMenuWin
)
211 maOpenTimer
.mnMenuPos
= nPos
;
212 maOpenTimer
.mpSubMenu
= rMenu
.mxSubMenuWin
.get();
218 if (!maMenuItems
[nPos
].mxAction
)
219 // no action is defined.
222 const bool bClosePopup
= maMenuItems
[nPos
].mxAction
->execute();
224 terminateAllPopupMenus();
227 void ScCheckListMenuControl::setSelectedMenuItem(size_t nPos
)
229 if (mnSelectedMenu
== nPos
)
233 selectMenuItem(nPos
, /*bSubMenuTimer*/true);
236 void ScCheckListMenuControl::handleMenuTimeout(const SubMenuItemData
* pTimer
)
238 if (pTimer
== &maOpenTimer
)
240 // Close any open submenu immediately.
241 if (maCloseTimer
.mpSubMenu
)
243 maCloseTimer
.mpSubMenu
->EndPopupMode();
244 maCloseTimer
.mpSubMenu
= nullptr;
245 maCloseTimer
.maTimer
.Stop();
250 else if (pTimer
== &maCloseTimer
)
253 if (maCloseTimer
.mpSubMenu
)
255 maCloseTimer
.mpSubMenu
->EndPopupMode();
256 maCloseTimer
.mpSubMenu
= nullptr;
258 // EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
259 if (!mnAsyncPostPopdownId
)
260 mnAsyncPostPopdownId
= Application::PostUserEvent(LINK(this, ScCheckListMenuControl
, PostPopdownHdl
));
265 void ScCheckListMenuControl::queueLaunchSubMenu(size_t nPos
, ScListSubMenuControl
* pMenu
)
270 // Set the submenu on launch queue.
271 if (maOpenTimer
.mpSubMenu
)
273 if (maOpenTimer
.mpSubMenu
!= pMenu
)
275 // new submenu is being requested.
280 if (pMenu
== maCloseTimer
.mpSubMenu
)
281 maCloseTimer
.reset();
285 maOpenTimer
.mpSubMenu
= pMenu
;
286 maOpenTimer
.mnMenuPos
= nPos
;
287 if (comphelper::LibreOfficeKit::isActive())
288 maOpenTimer
.maTimer
.Invoke();
290 maOpenTimer
.maTimer
.Start();
293 void ScCheckListMenuControl::queueCloseSubMenu()
295 if (!maOpenTimer
.mpSubMenu
)
296 // There is no submenu to close.
299 // Stop any submenu on queue for opening.
300 maOpenTimer
.maTimer
.Stop();
302 // Flush any pending close so it doesn't get skipped
303 if (maCloseTimer
.mpSubMenu
)
305 maCloseTimer
.mpSubMenu
->EndPopupMode();
308 maCloseTimer
.mpSubMenu
= maOpenTimer
.mpSubMenu
;
309 maCloseTimer
.mnMenuPos
= maOpenTimer
.mnMenuPos
;
310 maOpenTimer
.mpSubMenu
= nullptr;
311 maOpenTimer
.mnMenuPos
= MENU_NOT_SELECTED
;
313 if (comphelper::LibreOfficeKit::isActive())
314 maCloseTimer
.maTimer
.Invoke();
316 maCloseTimer
.maTimer
.Start();
319 tools::Rectangle
ScCheckListMenuControl::GetSubMenuParentRect()
321 if (!mxMenu
->get_selected(mxScratchIter
.get()))
322 return tools::Rectangle();
323 return mxMenu
->get_row_area(*mxScratchIter
);
326 void ScCheckListMenuControl::launchSubMenu()
328 ScListSubMenuControl
* pSubMenu
= maOpenTimer
.mpSubMenu
;
332 if (!mxMenu
->get_selected(mxScratchIter
.get()))
335 meRestoreFocus
= DetermineRestoreFocus();
337 tools::Rectangle aRect
= GetSubMenuParentRect();
338 pSubMenu
->StartPopupMode(mxMenu
.get(), aRect
);
340 mxMenu
->select(*mxScratchIter
);
342 pSubMenu
->GrabFocus();
345 ScCheckListMenuControl::RestoreFocus
ScCheckListMenuControl::DetermineRestoreFocus() const
347 if (mxEdSearch
->has_focus())
348 return RestoreFocus::EdSearch
;
349 if (mpChecks
->has_focus())
350 return RestoreFocus::Checks
;
351 if (mxChkToggleAll
->has_focus())
352 return RestoreFocus::ChkToggleAll
;
353 if (mxChkLockChecked
->has_focus())
354 return RestoreFocus::ChkLockChecked
;
355 if (mxBtnSelectSingle
->has_focus())
356 return RestoreFocus::BtnSelectSingle
;
357 if (mxBtnUnselectSingle
->has_focus())
358 return RestoreFocus::BtnUnselectSingle
;
359 return RestoreFocus::Menu
;
362 void ScCheckListMenuControl::RestorePreviousFocus()
364 switch (meRestoreFocus
)
366 case RestoreFocus::EdSearch
:
367 mxEdSearch
->grab_focus();
369 case RestoreFocus::Checks
:
370 mpChecks
->grab_focus();
372 case RestoreFocus::ChkToggleAll
:
373 mxChkToggleAll
->grab_focus();
375 case RestoreFocus::ChkLockChecked
:
376 mxChkLockChecked
->grab_focus();
378 case RestoreFocus::BtnSelectSingle
:
379 mxBtnSelectSingle
->grab_focus();
381 case RestoreFocus::BtnUnselectSingle
:
382 mxBtnUnselectSingle
->grab_focus();
385 mxMenu
->grab_focus();
390 IMPL_LINK_NOARG(ScCheckListMenuControl
, PostPopdownHdl
, void*, void)
392 mnAsyncPostPopdownId
= nullptr;
393 RestorePreviousFocus();
396 IMPL_LINK(ScCheckListMenuControl
, MouseEnterHdl
, const MouseEvent
&, rMEvt
, bool)
398 if (!rMEvt
.IsEnterWindow())
400 selectMenuItem(MENU_NOT_SELECTED
, true);
404 void ScCheckListMenuControl::endSubMenu(ScListSubMenuControl
& rSubMenu
)
406 rSubMenu
.EndPopupMode();
409 // EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
410 if (!mnAsyncPostPopdownId
)
411 mnAsyncPostPopdownId
= Application::PostUserEvent(LINK(this, ScCheckListMenuControl
, PostPopdownHdl
));
413 size_t nMenuPos
= getSubMenuPos(&rSubMenu
);
414 if (nMenuPos
!= MENU_NOT_SELECTED
)
416 mnSelectedMenu
= nMenuPos
;
417 mxMenu
->select(mnSelectedMenu
);
421 void ScCheckListMenuControl::addFields(const std::vector
<OUString
>& aFields
)
426 mxFieldsCombo
->clear();
428 for (auto& aField
: aFields
)
429 mxFieldsCombo
->append_text(aField
);
431 mxFieldsCombo
->set_active(0);
434 tools::Long
ScCheckListMenuControl::getField()
439 return mxFieldsCombo
->get_active();
442 void ScCheckListMenuControl::selectMenuItem(size_t nPos
, bool bSubMenuTimer
)
444 mxMenu
->select(nPos
== MENU_NOT_SELECTED
? -1 : nPos
);
445 mnSelectedMenu
= nPos
;
447 if (nPos
>= maMenuItems
.size() || nPos
== MENU_NOT_SELECTED
)
453 if (!maMenuItems
[nPos
].mbEnabled
)
461 if (maMenuItems
[nPos
].mxSubMenuWin
&& mxMenu
->changed_by_hover())
463 ScListSubMenuControl
* pSubMenu
= maMenuItems
[nPos
].mxSubMenuWin
.get();
464 queueLaunchSubMenu(nPos
, pSubMenu
);
471 void ScCheckListMenuControl::clearSelectedMenuItem()
473 selectMenuItem(MENU_NOT_SELECTED
, false);
476 size_t ScCheckListMenuControl::getSubMenuPos(const ScListSubMenuControl
* pSubMenu
)
478 size_t n
= maMenuItems
.size();
479 for (size_t i
= 0; i
< n
; ++i
)
481 if (maMenuItems
[i
].mxSubMenuWin
.get() == pSubMenu
)
484 return MENU_NOT_SELECTED
;
487 void ScCheckListMenuControl::setSubMenuFocused(const ScListSubMenuControl
* pSubMenu
)
489 maCloseTimer
.reset();
490 size_t nMenuPos
= getSubMenuPos(pSubMenu
);
491 if (mnSelectedMenu
!= nMenuPos
)
493 mnSelectedMenu
= nMenuPos
;
494 mxMenu
->select(mnSelectedMenu
);
498 void ScCheckListMenuControl::EndPopupMode()
502 mxPopover
->connect_closed(Link
<weld::Popover
&, void>());
503 mxPopover
->popdown();
504 PopupModeEndHdl(*mxPopover
);
505 assert(mbIsPoppedUp
== false);
508 void ScCheckListMenuControl::StartPopupMode(weld::Widget
* pParent
, const tools::Rectangle
& rRect
)
510 mxPopover
->connect_closed(LINK(this, ScCheckListMenuControl
, PopupModeEndHdl
));
512 mxPopover
->popup_at_rect(pParent
, rRect
);
516 void ScCheckListMenuControl::terminateAllPopupMenus()
521 ScCheckListMenuControl::Config::Config() :
522 mbAllowEmptySet(true), mbRTL(false)
526 ScCheckListMember::ScCheckListMember()
531 , mbHiddenByOtherFilter(false)
535 , meDatePartType(YEAR
)
539 // the value of border-width of FilterDropDown
540 constexpr int nBorderWidth
= 4;
541 // number of rows visible in checklist
542 constexpr int nCheckListVisibleRows
= 9;
543 // number of rows visible in colorlist
544 constexpr int nColorListVisibleRows
= 9;
546 ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget
* pParent
, ScViewData
& rViewData
,
547 bool bHasDates
, int nWidth
, bool bIsMultiField
)
548 : mxBuilder(Application::CreateBuilder(pParent
, u
"modules/scalc/ui/filterdropdown.ui"_ustr
))
549 , mxPopover(mxBuilder
->weld_popover(u
"FilterDropDown"_ustr
))
550 , mxContainer(mxBuilder
->weld_container(u
"container"_ustr
))
551 , mxMenu(mxBuilder
->weld_tree_view(u
"menu"_ustr
))
552 , mxScratchIter(mxMenu
->make_iterator())
553 , mxNonMenu(mxBuilder
->weld_widget(u
"nonmenu"_ustr
))
554 , mxFieldsComboLabel(mxBuilder
->weld_label(u
"select_field_label"_ustr
))
555 , mxFieldsCombo(mxBuilder
->weld_combo_box(u
"multi_field_combo"_ustr
))
556 , mxEdSearch(mxBuilder
->weld_entry(u
"search_edit"_ustr
))
557 , mxBox(mxBuilder
->weld_widget(u
"box"_ustr
))
558 , mxListChecks(mxBuilder
->weld_tree_view(u
"check_list_box"_ustr
))
559 , mxTreeChecks(mxBuilder
->weld_tree_view(u
"check_tree_box"_ustr
))
560 , mxChkToggleAll(mxBuilder
->weld_check_button(u
"toggle_all"_ustr
))
561 , mxChkLockChecked(mxBuilder
->weld_check_button(u
"lock_checked"_ustr
))
562 , mxBtnSelectSingle(mxBuilder
->weld_button(u
"select_current"_ustr
))
563 , mxBtnUnselectSingle(mxBuilder
->weld_button(u
"unselect_current"_ustr
))
564 , mxButtonBox(mxBuilder
->weld_box(u
"buttonbox"_ustr
))
565 , mxBtnOk(mxBuilder
->weld_button(u
"ok"_ustr
))
566 , mxBtnCancel(mxBuilder
->weld_button(u
"cancel"_ustr
))
567 , mxContextMenu(mxBuilder
->weld_menu(u
"contextmenu"_ustr
))
568 , mxDropDown(mxMenu
->create_virtual_device())
569 , mnCheckWidthReq(-1)
571 , mnCheckListVisibleRows(nCheckListVisibleRows
)
572 , mePrevToggleAllState(TRISTATE_INDET
)
573 , mnSelectedMenu(MENU_NOT_SELECTED
)
574 , mrViewData(rViewData
)
575 , mnAsyncPostPopdownId(nullptr)
576 , mnAsyncSetDropdownPosId(nullptr)
577 , meRestoreFocus(RestoreFocus::Menu
)
578 , mbHasDates(bHasDates
)
579 , mbIsPoppedUp(false)
582 , maSearchEditTimer("ScCheckListMenuControl maSearchEditTimer")
583 , mbIsMultiField(bIsMultiField
)
585 mxTreeChecks
->set_clicks_to_toggle(1);
586 mxListChecks
->set_clicks_to_toggle(1);
588 mxNonMenu
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
589 mxEdSearch
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
590 mxListChecks
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
591 mxTreeChecks
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
592 mxListChecks
->connect_popup_menu(LINK(this, ScCheckListMenuControl
, CommandHdl
));
593 mxTreeChecks
->connect_popup_menu(LINK(this, ScCheckListMenuControl
, CommandHdl
));
594 mxChkToggleAll
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
595 mxChkLockChecked
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
596 mxBtnSelectSingle
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
597 mxBtnUnselectSingle
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
598 mxBtnOk
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
599 mxBtnCancel
->connect_mouse_move(LINK(this, ScCheckListMenuControl
, MouseEnterHdl
));
602 tdf#136559 If we have no dates we don't need a tree
603 structure, just a list. GtkListStore can be then
604 used which is much faster than a GtkTreeStore, so
605 with no dates switch to the treeview which uses the
609 mpChecks
= mxTreeChecks
.get();
612 mxTreeChecks
->hide();
613 mxListChecks
->show();
614 mpChecks
= mxListChecks
.get();
617 int nChecksHeight
= mxTreeChecks
->get_height_rows(mnCheckListVisibleRows
);
620 mnCheckWidthReq
= nWidth
- nBorderWidth
* 2 - 4;
621 mxTreeChecks
->set_size_request(mnCheckWidthReq
, nChecksHeight
);
622 mxListChecks
->set_size_request(mnCheckWidthReq
, nChecksHeight
);
625 // sort ok/cancel into native order, if this was a dialog they would be auto-sorted, but this
626 // popup isn't a true dialog
627 mxButtonBox
->sort_native_button_order();
629 mxTreeChecks
->enable_toggle_buttons(weld::ColumnToggleType::Check
);
630 mxListChecks
->enable_toggle_buttons(weld::ColumnToggleType::Check
);
635 mxFieldsComboLabel
->show();
636 mxFieldsCombo
->show();
640 mxFieldsComboLabel
->hide();
641 mxFieldsCombo
->hide();
646 mxMenu
->connect_row_activated(LINK(this, ScCheckListMenuControl
, RowActivatedHdl
));
647 mxMenu
->connect_selection_changed(LINK(this, ScCheckListMenuControl
, SelectHdl
));
648 mxMenu
->connect_key_press(LINK(this, ScCheckListMenuControl
, MenuKeyInputHdl
));
650 mxBtnOk
->connect_clicked(LINK(this, ScCheckListMenuControl
, ButtonHdl
));
651 mxBtnCancel
->connect_clicked(LINK(this, ScCheckListMenuControl
, ButtonHdl
));
653 mxFieldsCombo
->connect_changed(LINK(this, ScCheckListMenuControl
, ComboChangedHdl
));
654 mxEdSearch
->connect_changed(LINK(this, ScCheckListMenuControl
, EdModifyHdl
));
655 mxEdSearch
->connect_activate(LINK(this, ScCheckListMenuControl
, EdActivateHdl
));
656 mxTreeChecks
->connect_toggled(LINK(this, ScCheckListMenuControl
, CheckHdl
));
657 mxTreeChecks
->connect_key_press(LINK(this, ScCheckListMenuControl
, KeyInputHdl
));
658 mxListChecks
->connect_toggled(LINK(this, ScCheckListMenuControl
, CheckHdl
));
659 mxListChecks
->connect_key_press(LINK(this, ScCheckListMenuControl
, KeyInputHdl
));
660 mxChkToggleAll
->connect_toggled(LINK(this, ScCheckListMenuControl
, TriStateHdl
));
661 mxChkLockChecked
->connect_toggled(LINK(this, ScCheckListMenuControl
, LockCheckedHdl
));
662 mxBtnSelectSingle
->connect_clicked(LINK(this, ScCheckListMenuControl
, ButtonHdl
));
663 mxBtnUnselectSingle
->connect_clicked(LINK(this, ScCheckListMenuControl
, ButtonHdl
));
666 mxMenu
->connect_size_allocate(LINK(this, ScCheckListMenuControl
, TreeSizeAllocHdl
));
668 // determine what width the checklist will end up with
669 mnCheckWidthReq
= mxContainer
->get_preferred_size().Width();
670 // make that size fixed now, we can now use mnCheckWidthReq to speed up
671 // bulk_insert_for_each
672 mxTreeChecks
->set_size_request(mnCheckWidthReq
, nChecksHeight
);
673 mxListChecks
->set_size_request(mnCheckWidthReq
, nChecksHeight
);
675 maSearchEditTimer
.SetTimeout(EDIT_UPDATEDATA_TIMEOUT
);
676 maSearchEditTimer
.SetInvokeHandler(LINK(this, ScCheckListMenuControl
, SearchEditTimeoutHdl
));
678 if (comphelper::LibreOfficeKit::isActive())
680 mxBtnSelectSingle
->hide();
681 mxBtnUnselectSingle
->hide();
686 void ScCheckListMenuControl::GrabFocus()
688 if (mxEdSearch
->get_visible())
690 mxEdSearch
->grab_focus();
691 meRestoreFocus
= RestoreFocus::EdSearch
;
695 mxMenu
->set_cursor(0);
696 mxMenu
->grab_focus();
697 meRestoreFocus
= RestoreFocus::Menu
;
701 void ScCheckListMenuControl::DropPendingEvents()
703 if (mnAsyncPostPopdownId
)
705 Application::RemoveUserEvent(mnAsyncPostPopdownId
);
706 mnAsyncPostPopdownId
= nullptr;
708 if (mnAsyncSetDropdownPosId
)
710 Application::RemoveUserEvent(mnAsyncSetDropdownPosId
);
711 mnAsyncSetDropdownPosId
= nullptr;
715 ScCheckListMenuControl::~ScCheckListMenuControl()
717 maSearchEditTimer
.Stop();
719 for (auto& rMenuItem
: maMenuItems
)
720 rMenuItem
.mxSubMenuWin
.reset();
724 void ScCheckListMenuControl::prepWindow()
726 mxMenu
->set_size_request(-1, mxMenu
->get_preferred_size().Height() + 2);
727 mnSelectedMenu
= MENU_NOT_SELECTED
;
728 if (mxMenu
->n_children())
730 mxMenu
->set_cursor(0);
731 mxMenu
->unselect_all();
734 mnWndWidth
= mxContainer
->get_preferred_size().Width() + nBorderWidth
* 2 + 4;
737 void ScCheckListMenuControl::setAllMemberState(bool bSet
)
739 mpChecks
->all_foreach([this, bSet
](weld::TreeIter
& rEntry
){
740 if (mpChecks
->get_sensitive(rEntry
, 0))
741 mpChecks
->set_toggle(rEntry
, bSet
? TRISTATE_TRUE
: TRISTATE_FALSE
);
745 if (!maConfig
.mbAllowEmptySet
)
747 // We need to have at least one member selected.
748 mxBtnOk
->set_sensitive(GetCheckedEntryCount() != 0);
752 void ScCheckListMenuControl::selectCurrentMemberOnly(bool bSet
)
754 setAllMemberState(!bSet
);
755 std::unique_ptr
<weld::TreeIter
> xEntry
= mpChecks
->make_iterator();
756 if (!mpChecks
->get_cursor(xEntry
.get()))
758 mpChecks
->set_toggle(*xEntry
, bSet
? TRISTATE_TRUE
: TRISTATE_FALSE
);
761 IMPL_LINK(ScCheckListMenuControl
, CommandHdl
, const CommandEvent
&, rCEvt
, bool)
763 if (rCEvt
.GetCommand() != CommandEventId::ContextMenu
)
766 mxContextMenu
->set_sensitive(u
"less"_ustr
, mnCheckListVisibleRows
> 4);
767 mxContextMenu
->set_sensitive(u
"more"_ustr
, mnCheckListVisibleRows
< 42);
769 OUString sCommand
= mxContextMenu
->popup_at_rect(mpChecks
, tools::Rectangle(rCEvt
.GetMousePosPixel(), Size(1,1)));
770 if (sCommand
.isEmpty())
773 if (sCommand
== "more")
774 ++mnCheckListVisibleRows
;
775 else if (sCommand
== "less")
776 --mnCheckListVisibleRows
;
782 void ScCheckListMenuControl::ResizeToRequest()
784 int nChecksHeight
= mxTreeChecks
->get_height_rows(mnCheckListVisibleRows
);
785 mxTreeChecks
->set_size_request(mnCheckWidthReq
, nChecksHeight
);
786 mxListChecks
->set_size_request(mnCheckWidthReq
, nChecksHeight
);
787 mxPopover
->resize_to_request();
790 IMPL_LINK(ScCheckListMenuControl
, ButtonHdl
, weld::Button
&, rBtn
, void)
792 if (&rBtn
== mxBtnOk
.get())
794 else if (&rBtn
== mxBtnCancel
.get())
796 else if (&rBtn
== mxBtnSelectSingle
.get() || &rBtn
== mxBtnUnselectSingle
.get())
798 std::unique_ptr
<weld::TreeIter
> xEntry
= mpChecks
->make_iterator();
799 bool bEntry
= mpChecks
->get_cursor(xEntry
.get());
802 if (bEntry
&& mpChecks
->get_sensitive(*xEntry
, 0))
804 selectCurrentMemberOnly(&rBtn
== mxBtnSelectSingle
.get());
812 void insertMember(weld::TreeView
& rView
, const weld::TreeIter
& rIter
, const ScCheckListMember
& rMember
, bool bChecked
, bool bLock
=false)
814 OUString aLabel
= rMember
.maName
;
815 if (aLabel
.isEmpty())
816 aLabel
= ScResId(STR_EMPTYDATA
);
817 rView
.set_toggle(rIter
, bChecked
? TRISTATE_TRUE
: TRISTATE_FALSE
);
818 rView
.set_text(rIter
, aLabel
, 0);
821 rView
.set_sensitive(rIter
, !rMember
.mbHiddenByOtherFilter
&& !rMember
.mbMarked
);
823 rView
.set_sensitive(rIter
, !rMember
.mbHiddenByOtherFilter
);
826 void loadSearchedMembers(std::vector
<int>& rSearchedMembers
, std::vector
<ScCheckListMember
>& rMembers
,
827 const OUString
& rSearchText
, bool bLock
=false)
829 const OUString aSearchText
= ScGlobal::getCharClass().lowercase( rSearchText
);
831 for (size_t i
= 0; i
< rMembers
.size(); ++i
)
833 assert(!rMembers
[i
].mbDate
);
835 OUString aLabelDisp
= rMembers
[i
].maName
;
836 if ( aLabelDisp
.isEmpty() )
837 aLabelDisp
= ScResId( STR_EMPTYDATA
);
839 bool bPartialMatch
= ScGlobal::getCharClass().lowercase( aLabelDisp
).indexOf( aSearchText
) != -1;
843 if (!bLock
|| (!rMembers
[i
].mbMarked
&& !rMembers
[i
].mbHiddenByOtherFilter
))
844 rSearchedMembers
.push_back(i
);
848 for (size_t i
= 0; i
< rMembers
.size(); ++i
)
849 if (rMembers
[i
].mbMarked
&& !rMembers
[i
].mbHiddenByOtherFilter
)
850 rSearchedMembers
.push_back(i
);
855 IMPL_LINK_NOARG(ScCheckListMenuControl
, LockCheckedHdl
, weld::Toggleable
&, void)
857 // assume all members are checked
858 for (auto& aMember
: maMembers
)
859 aMember
.mbCheck
= true;
861 // go over the members visible in the popup, and remember which one is
862 // checked, and which one is not
863 mpChecks
->all_foreach([this](weld::TreeIter
& rEntry
){
864 if (mpChecks
->get_toggle(rEntry
) == TRISTATE_TRUE
)
866 for (auto& aMember
: maMembers
)
867 if (aMember
.maName
== mpChecks
->get_text(rEntry
))
868 aMember
.mbMarked
= true;
872 for (auto& aMember
: maMembers
)
873 if (aMember
.maName
== mpChecks
->get_text(rEntry
))
874 aMember
.mbCheck
= false;
884 OUString aSearchText
= mxEdSearch
->get_text();
885 if (aSearchText
.isEmpty())
887 initMembers(-1, !mxChkLockChecked
->get_active());
891 std::vector
<int> aShownIndexes
;
892 loadSearchedMembers(aShownIndexes
, maMembers
, aSearchText
, true);
893 std::vector
<int> aFixedWidths
{ mnCheckWidthReq
};
895 // insert the members, remember whether checked or unchecked.
896 mpChecks
->bulk_insert_for_each(aShownIndexes
.size(), [this, &aShownIndexes
](weld::TreeIter
& rIter
, int i
) {
897 size_t nIndex
= aShownIndexes
[i
];
898 insertMember(*mpChecks
, rIter
, maMembers
[nIndex
], maMembers
[nIndex
].mbCheck
, mxChkLockChecked
->get_active());
899 }, nullptr, &aFixedWidths
);
902 // unmarking should happen after the members are inserted
903 if (!mxChkLockChecked
->get_active())
904 for (auto& aMember
: maMembers
)
905 aMember
.mbMarked
= false;
908 IMPL_LINK_NOARG(ScCheckListMenuControl
, TriStateHdl
, weld::Toggleable
&, void)
910 switch (mePrevToggleAllState
)
913 mxChkToggleAll
->set_state(TRISTATE_FALSE
);
914 setAllMemberState(false);
919 mxChkToggleAll
->set_state(TRISTATE_TRUE
);
920 setAllMemberState(true);
924 mePrevToggleAllState
= mxChkToggleAll
->get_state();
927 IMPL_LINK_NOARG(ScCheckListMenuControl
, ComboChangedHdl
, weld::ComboBox
&, void)
929 if (mbIsMultiField
&& mxFieldChangedAction
)
930 mxFieldChangedAction
->execute();
933 IMPL_LINK_NOARG(ScCheckListMenuControl
, SearchEditTimeoutHdl
, Timer
*, void)
935 OUString aSearchText
= mxEdSearch
->get_text();
936 aSearchText
= ScGlobal::getCharClass().lowercase( aSearchText
);
937 bool bSearchTextEmpty
= aSearchText
.isEmpty();
938 size_t nEnableMember
= std::count_if(maMembers
.begin(), maMembers
.end(),
939 [](const ScCheckListMember
& rLMem
) { return !rLMem
.mbHiddenByOtherFilter
; });
940 size_t nSelCount
= 0;
942 // This branch is the general case, the other is an optimized variant of
943 // this one where we can take advantage of knowing we have no hierarchy
948 bool bSomeDateDeletes
= false;
950 for (size_t i
= 0; i
< nEnableMember
; ++i
)
952 bool bIsDate
= maMembers
[i
].mbDate
;
953 bool bPartialMatch
= false;
955 OUString aLabelDisp
= maMembers
[i
].maName
;
956 if ( aLabelDisp
.isEmpty() )
957 aLabelDisp
= ScResId( STR_EMPTYDATA
);
959 if ( !bSearchTextEmpty
)
962 bPartialMatch
= ( ScGlobal::getCharClass().lowercase( aLabelDisp
).indexOf( aSearchText
) != -1 );
963 else if ( maMembers
[i
].meDatePartType
== ScCheckListMember::DAY
) // Match with both numerical and text version of month
964 bPartialMatch
= (ScGlobal::getCharClass().lowercase( OUString(
965 maMembers
[i
].maRealName
+ maMembers
[i
].maDateParts
[1] )).indexOf( aSearchText
) != -1);
969 else if ( bIsDate
&& maMembers
[i
].meDatePartType
!= ScCheckListMember::DAY
)
972 if ( bSearchTextEmpty
)
974 auto xLeaf
= ShowCheckEntry(aLabelDisp
, maMembers
[i
], true, maMembers
[i
].mbVisible
);
975 updateMemberParents(xLeaf
.get(), i
);
976 if ( maMembers
[i
].mbVisible
)
983 auto xLeaf
= ShowCheckEntry(aLabelDisp
, maMembers
[i
]);
984 updateMemberParents(xLeaf
.get(), i
);
989 ShowCheckEntry(aLabelDisp
, maMembers
[i
], false, false);
991 bSomeDateDeletes
= true;
995 if ( bSomeDateDeletes
)
997 for (size_t i
= 0; i
< nEnableMember
; ++i
)
999 if (!maMembers
[i
].mbDate
)
1001 if (maMembers
[i
].meDatePartType
!= ScCheckListMember::DAY
)
1003 updateMemberParents(nullptr, i
);
1013 // when there are a lot of rows, it is cheaper to simply clear the tree and either
1014 // re-initialise or just insert the filtered lines
1019 if (bSearchTextEmpty
)
1020 nSelCount
= initMembers();
1023 std::vector
<int> aShownIndexes
;
1024 loadSearchedMembers(aShownIndexes
, maMembers
, aSearchText
, mxChkLockChecked
->get_active());
1025 std::vector
<int> aFixedWidths
{ mnCheckWidthReq
};
1026 // tdf#122419 insert in the fastest order, this might be backwards.
1027 mpChecks
->bulk_insert_for_each(aShownIndexes
.size(), [this, &aShownIndexes
, &nSelCount
](weld::TreeIter
& rIter
, int i
) {
1028 size_t nIndex
= aShownIndexes
[i
];
1029 insertMember(*mpChecks
, rIter
, maMembers
[nIndex
], true, mxChkLockChecked
->get_active());
1031 }, nullptr, &aFixedWidths
);
1035 if ( nSelCount
== nEnableMember
)
1036 mxChkToggleAll
->set_state( TRISTATE_TRUE
);
1037 else if ( nSelCount
== 0 )
1038 mxChkToggleAll
->set_state( TRISTATE_FALSE
);
1040 mxChkToggleAll
->set_state( TRISTATE_INDET
);
1042 if ( !maConfig
.mbAllowEmptySet
)
1044 const bool bEmptySet( nSelCount
== 0 );
1045 mpChecks
->set_sensitive(!bEmptySet
);
1046 mxChkToggleAll
->set_sensitive(!bEmptySet
);
1047 mxBtnSelectSingle
->set_sensitive(!bEmptySet
);
1048 mxBtnUnselectSingle
->set_sensitive(!bEmptySet
);
1049 mxBtnOk
->set_sensitive(!bEmptySet
);
1053 IMPL_LINK_NOARG(ScCheckListMenuControl
, EdModifyHdl
, weld::Entry
&, void)
1055 maSearchEditTimer
.Start();
1058 IMPL_LINK_NOARG(ScCheckListMenuControl
, EdActivateHdl
, weld::Entry
&, bool)
1060 if (mxBtnOk
->get_sensitive())
1065 IMPL_LINK( ScCheckListMenuControl
, CheckHdl
, const weld::TreeView::iter_col
&, rRowCol
, void )
1067 Check(&rRowCol
.first
);
1070 void ScCheckListMenuControl::Check(const weld::TreeIter
* pEntry
)
1073 CheckEntry(*pEntry
, mpChecks
->get_toggle(*pEntry
) == TRISTATE_TRUE
);
1074 size_t nNumChecked
= GetCheckedEntryCount();
1075 size_t nEnableMember
= std::count_if(maMembers
.begin(), maMembers
.end(),
1076 [](const ScCheckListMember
& rLMem
) { return !rLMem
.mbHiddenByOtherFilter
; });
1077 if (nNumChecked
== nEnableMember
)
1078 // all members visible
1079 mxChkToggleAll
->set_state(TRISTATE_TRUE
);
1080 else if (nNumChecked
== 0)
1081 // no members visible
1082 mxChkToggleAll
->set_state(TRISTATE_FALSE
);
1084 mxChkToggleAll
->set_state(TRISTATE_INDET
);
1086 if (!maConfig
.mbAllowEmptySet
)
1087 // We need to have at least one member selected.
1088 mxBtnOk
->set_sensitive(nNumChecked
!= 0);
1090 mePrevToggleAllState
= mxChkToggleAll
->get_state();
1093 void ScCheckListMenuControl::updateMemberParents(const weld::TreeIter
* pLeaf
, size_t nIdx
)
1095 if ( !maMembers
[nIdx
].mbDate
|| maMembers
[nIdx
].meDatePartType
!= ScCheckListMember::DAY
)
1098 OUString aYearName
= maMembers
[nIdx
].maDateParts
[0];
1099 OUString aMonthName
= maMembers
[nIdx
].maDateParts
[1];
1100 auto aItr
= maYearMonthMap
.find(aYearName
+ aMonthName
);
1104 std::unique_ptr
<weld::TreeIter
> xYearEntry
;
1105 std::unique_ptr
<weld::TreeIter
> xMonthEntry
= mpChecks
->make_iterator(pLeaf
);
1106 if (!mpChecks
->iter_parent(*xMonthEntry
))
1107 xMonthEntry
.reset();
1110 xYearEntry
= mpChecks
->make_iterator(xMonthEntry
.get());
1111 if (!mpChecks
->iter_parent(*xYearEntry
))
1115 maMembers
[nIdx
].mxParent
= std::move(xMonthEntry
);
1116 if ( aItr
!= maYearMonthMap
.end() )
1118 size_t nMonthIdx
= aItr
->second
;
1119 maMembers
[nMonthIdx
].mxParent
= std::move(xYearEntry
);
1124 std::unique_ptr
<weld::TreeIter
> xYearEntry
= FindEntry(nullptr, aYearName
);
1125 if (aItr
!= maYearMonthMap
.end() && !xYearEntry
)
1127 size_t nMonthIdx
= aItr
->second
;
1128 maMembers
[nMonthIdx
].mxParent
.reset();
1129 maMembers
[nIdx
].mxParent
.reset();
1131 else if (xYearEntry
&& !FindEntry(xYearEntry
.get(), aMonthName
))
1132 maMembers
[nIdx
].mxParent
.reset();
1136 void ScCheckListMenuControl::setMemberSize(size_t n
)
1138 maMembers
.reserve(n
);
1141 void ScCheckListMenuControl::addDateMember(const OUString
& rsName
, double nVal
, bool bVisible
, bool bHiddenByOtherFilter
)
1143 SvNumberFormatter
* pFormatter
= mrViewData
.GetDocument().GetFormatTable();
1145 // Convert the numeric date value to a date object.
1146 Date aDate
= pFormatter
->GetNullDate();
1147 aDate
.AddDays(rtl::math::approxFloor(nVal
));
1149 sal_Int16 nYear
= aDate
.GetYear();
1150 sal_uInt16 nMonth
= aDate
.GetMonth();
1151 sal_uInt16 nDay
= aDate
.GetDay();
1153 // Get the localized month name list.
1154 CalendarWrapper
& rCalendar
= ScGlobal::GetCalendar();
1155 uno::Sequence
<i18n::CalendarItem2
> aMonths
= rCalendar
.getMonths();
1156 if (aMonths
.getLength() < nMonth
)
1159 OUString aYearName
= OUString::number(nYear
);
1160 OUString aMonthName
= aMonths
[nMonth
-1].FullName
;
1161 OUString aDayName
= OUString::number(nDay
);
1163 if ( aDayName
.getLength() == 1 )
1164 aDayName
= "0" + aDayName
;
1168 std::unique_ptr
<weld::TreeIter
> xYearEntry
= FindEntry(nullptr, aYearName
);
1171 xYearEntry
= mpChecks
->make_iterator();
1172 mpChecks
->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry
.get());
1173 mpChecks
->set_toggle(*xYearEntry
, TRISTATE_FALSE
);
1174 mpChecks
->set_text(*xYearEntry
, aYearName
, 0);
1175 mpChecks
->set_sensitive(*xYearEntry
, !bHiddenByOtherFilter
);
1176 ScCheckListMember aMemYear
;
1177 aMemYear
.maName
= aYearName
;
1178 aMemYear
.maRealName
= rsName
;
1179 aMemYear
.mbDate
= true;
1180 aMemYear
.mbLeaf
= false;
1181 aMemYear
.mbVisible
= bVisible
;
1182 aMemYear
.mbHiddenByOtherFilter
= bHiddenByOtherFilter
;
1183 aMemYear
.mxParent
.reset();
1184 aMemYear
.meDatePartType
= ScCheckListMember::YEAR
;
1185 maMembers
.emplace_back(std::move(aMemYear
));
1188 std::unique_ptr
<weld::TreeIter
> xMonthEntry
= FindEntry(xYearEntry
.get(), aMonthName
);
1191 xMonthEntry
= mpChecks
->make_iterator();
1192 mpChecks
->insert(xYearEntry
.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry
.get());
1193 mpChecks
->set_toggle(*xMonthEntry
, TRISTATE_FALSE
);
1194 mpChecks
->set_text(*xMonthEntry
, aMonthName
, 0);
1195 mpChecks
->set_sensitive(*xMonthEntry
, !bHiddenByOtherFilter
);
1196 ScCheckListMember aMemMonth
;
1197 aMemMonth
.maName
= aMonthName
;
1198 aMemMonth
.maRealName
= rsName
;
1199 aMemMonth
.mbDate
= true;
1200 aMemMonth
.mbLeaf
= false;
1201 aMemMonth
.mbVisible
= bVisible
;
1202 aMemMonth
.mbHiddenByOtherFilter
= bHiddenByOtherFilter
;
1203 aMemMonth
.mxParent
= std::move(xYearEntry
);
1204 aMemMonth
.meDatePartType
= ScCheckListMember::MONTH
;
1205 maMembers
.emplace_back(std::move(aMemMonth
));
1206 maYearMonthMap
[aYearName
+ aMonthName
] = maMembers
.size() - 1;
1209 std::unique_ptr
<weld::TreeIter
> xDayEntry
= FindEntry(xMonthEntry
.get(), aDayName
);
1212 xDayEntry
= mpChecks
->make_iterator();
1213 mpChecks
->insert(xMonthEntry
.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry
.get());
1214 mpChecks
->set_toggle(*xDayEntry
, TRISTATE_FALSE
);
1215 mpChecks
->set_text(*xDayEntry
, aDayName
, 0);
1216 mpChecks
->set_sensitive(*xDayEntry
, !bHiddenByOtherFilter
);
1217 ScCheckListMember aMemDay
;
1218 aMemDay
.maName
= aDayName
;
1219 aMemDay
.maRealName
= rsName
;
1220 aMemDay
.maDateParts
.resize(2);
1221 aMemDay
.maDateParts
[0] = aYearName
;
1222 aMemDay
.maDateParts
[1] = aMonthName
;
1223 aMemDay
.mbDate
= true;
1224 aMemDay
.mbLeaf
= true;
1225 aMemDay
.mbVisible
= bVisible
;
1226 aMemDay
.mbHiddenByOtherFilter
= bHiddenByOtherFilter
;
1227 aMemDay
.mxParent
= std::move(xMonthEntry
);
1228 aMemDay
.meDatePartType
= ScCheckListMember::DAY
;
1229 maMembers
.emplace_back(std::move(aMemDay
));
1235 void ScCheckListMenuControl::addMember(const OUString
& rName
, const double nVal
, bool bVisible
, bool bHiddenByOtherFilter
, bool bValue
)
1237 ScCheckListMember aMember
;
1238 // tdf#46062 - indicate hidden whitespaces using quotes
1239 aMember
.maName
= o3tl::trim(rName
) != rName
? "\"" + rName
+ "\"" : rName
;
1240 aMember
.maRealName
= rName
;
1241 aMember
.mnValue
= nVal
;
1242 aMember
.mbDate
= false;
1243 aMember
.mbLeaf
= true;
1244 aMember
.mbValue
= bValue
;
1245 aMember
.mbVisible
= bVisible
;
1246 aMember
.mbMarked
= false;
1247 aMember
.mbCheck
= true;
1248 aMember
.mbHiddenByOtherFilter
= bHiddenByOtherFilter
;
1249 aMember
.mxParent
.reset();
1250 maMembers
.emplace_back(std::move(aMember
));
1253 void ScCheckListMenuControl::clearMembers()
1262 std::unique_ptr
<weld::TreeIter
> ScCheckListMenuControl::FindEntry(const weld::TreeIter
* pParent
, std::u16string_view sNode
)
1264 std::unique_ptr
<weld::TreeIter
> xEntry
= mpChecks
->make_iterator(pParent
);
1265 bool bEntry
= pParent
? mpChecks
->iter_children(*xEntry
) : mpChecks
->get_iter_first(*xEntry
);
1268 if (sNode
== mpChecks
->get_text(*xEntry
, 0))
1270 bEntry
= mpChecks
->iter_next_sibling(*xEntry
);
1275 void ScCheckListMenuControl::GetRecursiveChecked(const weld::TreeIter
* pEntry
, std::unordered_set
<OUString
>& vOut
,
1278 if (mpChecks
->get_toggle(*pEntry
) != TRISTATE_TRUE
)
1281 // We have to hash parents and children together.
1282 // Per convention for easy access in getResult()
1283 // "child;parent;grandparent" while descending.
1284 if (rLabel
.isEmpty())
1285 rLabel
= mpChecks
->get_text(*pEntry
, 0);
1287 rLabel
= mpChecks
->get_text(*pEntry
, 0) + ";" + rLabel
;
1289 // Prerequisite: the selection mechanism guarantees that if a child is
1290 // selected then also the parent is selected, so we only have to
1291 // inspect the children in case the parent is selected.
1292 if (!mpChecks
->iter_has_child(*pEntry
))
1295 std::unique_ptr
<weld::TreeIter
> xChild(mpChecks
->make_iterator(pEntry
));
1296 bool bChild
= mpChecks
->iter_children(*xChild
);
1299 OUString aLabel
= rLabel
;
1300 GetRecursiveChecked(xChild
.get(), vOut
, aLabel
);
1301 if (!aLabel
.isEmpty() && aLabel
!= rLabel
)
1302 vOut
.insert(aLabel
);
1303 bChild
= mpChecks
->iter_next_sibling(*xChild
);
1305 // Let the caller not add the parent alone.
1309 std::unordered_set
<OUString
> ScCheckListMenuControl::GetAllChecked()
1311 std::unordered_set
<OUString
> vResults(0);
1313 std::unique_ptr
<weld::TreeIter
> xEntry
= mpChecks
->make_iterator();
1314 bool bEntry
= mpChecks
->get_iter_first(*xEntry
);
1318 GetRecursiveChecked(xEntry
.get(), vResults
, aLabel
);
1319 if (!aLabel
.isEmpty())
1320 vResults
.insert(aLabel
);
1321 bEntry
= mpChecks
->iter_next_sibling(*xEntry
);
1327 bool ScCheckListMenuControl::IsChecked(std::u16string_view sName
, const weld::TreeIter
* pParent
)
1329 std::unique_ptr
<weld::TreeIter
> xEntry
= FindEntry(pParent
, sName
);
1330 return xEntry
&& mpChecks
->get_toggle(*xEntry
) == TRISTATE_TRUE
;
1333 void ScCheckListMenuControl::CheckEntry(std::u16string_view sName
, const weld::TreeIter
* pParent
, bool bCheck
)
1335 std::unique_ptr
<weld::TreeIter
> xEntry
= FindEntry(pParent
, sName
);
1337 CheckEntry(*xEntry
, bCheck
);
1340 // Recursively check all children of rParent
1341 void ScCheckListMenuControl::CheckAllChildren(const weld::TreeIter
& rParent
, bool bCheck
)
1343 mpChecks
->set_toggle(rParent
, bCheck
? TRISTATE_TRUE
: TRISTATE_FALSE
);
1344 std::unique_ptr
<weld::TreeIter
> xEntry
= mpChecks
->make_iterator(&rParent
);
1345 bool bEntry
= mpChecks
->iter_children(*xEntry
);
1348 CheckAllChildren(*xEntry
, bCheck
);
1349 bEntry
= mpChecks
->iter_next_sibling(*xEntry
);
1353 void ScCheckListMenuControl::CheckEntry(const weld::TreeIter
& rParent
, bool bCheck
)
1355 // recursively check all items below rParent
1356 CheckAllChildren(rParent
, bCheck
);
1357 // checking rParent can affect ancestors, e.g. if ancestor is unchecked and rParent is
1358 // now checked then the ancestor needs to be checked also
1359 if (!mpChecks
->get_iter_depth(rParent
))
1362 std::unique_ptr
<weld::TreeIter
> xAncestor(mpChecks
->make_iterator(&rParent
));
1363 bool bAncestor
= mpChecks
->iter_parent(*xAncestor
);
1366 // if any first level children checked then ancestor
1367 // needs to be checked, similarly if no first level children
1368 // checked then ancestor needs to be unchecked
1369 std::unique_ptr
<weld::TreeIter
> xChild(mpChecks
->make_iterator(xAncestor
.get()));
1370 bool bChild
= mpChecks
->iter_children(*xChild
);
1371 bool bChildChecked
= false;
1375 if (mpChecks
->get_toggle(*xChild
) == TRISTATE_TRUE
)
1377 bChildChecked
= true;
1380 bChild
= mpChecks
->iter_next_sibling(*xChild
);
1382 mpChecks
->set_toggle(*xAncestor
, bChildChecked
? TRISTATE_TRUE
: TRISTATE_FALSE
);
1383 bAncestor
= mpChecks
->iter_parent(*xAncestor
);
1387 std::unique_ptr
<weld::TreeIter
> ScCheckListMenuControl::ShowCheckEntry(const OUString
& sName
, ScCheckListMember
& rMember
, bool bShow
, bool bCheck
)
1389 std::unique_ptr
<weld::TreeIter
> xEntry
;
1390 if (!rMember
.mbDate
|| rMember
.mxParent
)
1391 xEntry
= FindEntry(rMember
.mxParent
.get(), sName
);
1399 if (rMember
.maDateParts
.empty())
1402 std::unique_ptr
<weld::TreeIter
> xYearEntry
= FindEntry(nullptr, rMember
.maDateParts
[0]);
1405 xYearEntry
= mpChecks
->make_iterator();
1406 mpChecks
->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry
.get());
1407 mpChecks
->set_toggle(*xYearEntry
, TRISTATE_FALSE
);
1408 mpChecks
->set_text(*xYearEntry
, rMember
.maDateParts
[0], 0);
1410 std::unique_ptr
<weld::TreeIter
> xMonthEntry
= FindEntry(xYearEntry
.get(), rMember
.maDateParts
[1]);
1413 xMonthEntry
= mpChecks
->make_iterator();
1414 mpChecks
->insert(xYearEntry
.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry
.get());
1415 mpChecks
->set_toggle(*xMonthEntry
, TRISTATE_FALSE
);
1416 mpChecks
->set_text(*xMonthEntry
, rMember
.maDateParts
[1], 0);
1418 std::unique_ptr
<weld::TreeIter
> xDayEntry
= FindEntry(xMonthEntry
.get(), rMember
.maName
);
1421 xDayEntry
= mpChecks
->make_iterator();
1422 mpChecks
->insert(xMonthEntry
.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry
.get());
1423 mpChecks
->set_toggle(*xDayEntry
, TRISTATE_FALSE
);
1424 mpChecks
->set_text(*xDayEntry
, rMember
.maName
, 0);
1426 return xDayEntry
; // Return leaf node
1429 xEntry
= mpChecks
->make_iterator();
1430 mpChecks
->append(xEntry
.get());
1431 mpChecks
->set_toggle(*xEntry
, bCheck
? TRISTATE_TRUE
: TRISTATE_FALSE
);
1432 mpChecks
->set_text(*xEntry
, sName
, 0);
1435 CheckEntry(*xEntry
, bCheck
);
1439 mpChecks
->remove(*xEntry
);
1440 if (rMember
.mxParent
)
1442 std::unique_ptr
<weld::TreeIter
> xParent(mpChecks
->make_iterator(rMember
.mxParent
.get()));
1443 while (xParent
&& !mpChecks
->iter_has_child(*xParent
))
1445 std::unique_ptr
<weld::TreeIter
> xTmp(mpChecks
->make_iterator(xParent
.get()));
1446 if (!mpChecks
->iter_parent(*xParent
))
1448 mpChecks
->remove(*xTmp
);
1455 int ScCheckListMenuControl::GetCheckedEntryCount() const
1459 mpChecks
->all_foreach([this, &nRet
](weld::TreeIter
& rEntry
){
1460 if (mpChecks
->get_toggle(rEntry
) == TRISTATE_TRUE
)
1468 IMPL_LINK(ScCheckListMenuControl
, KeyInputHdl
, const KeyEvent
&, rKEvt
, bool)
1470 const vcl::KeyCode
& rKey
= rKEvt
.GetKeyCode();
1472 if ( rKey
.GetCode() == KEY_RETURN
|| rKey
.GetCode() == KEY_SPACE
)
1474 std::unique_ptr
<weld::TreeIter
> xEntry
= mpChecks
->make_iterator();
1475 bool bEntry
= mpChecks
->get_cursor(xEntry
.get());
1476 if (bEntry
&& mpChecks
->get_sensitive(*xEntry
, 0))
1478 bool bOldCheck
= mpChecks
->get_toggle(*xEntry
) == TRISTATE_TRUE
;
1479 CheckEntry(*xEntry
, !bOldCheck
);
1480 bool bNewCheck
= mpChecks
->get_toggle(*xEntry
) == TRISTATE_TRUE
;
1481 if (bOldCheck
!= bNewCheck
)
1482 Check(xEntry
.get());
1490 size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth
, bool bUnlock
)
1492 size_t n
= maMembers
.size();
1493 size_t nEnableMember
= std::count_if(maMembers
.begin(), maMembers
.end(),
1494 [](const ScCheckListMember
& rLMem
) { return !rLMem
.mbHiddenByOtherFilter
; });
1495 size_t nVisMemCount
= 0;
1497 if (nMaxMemberWidth
== -1)
1498 nMaxMemberWidth
= mnCheckWidthReq
;
1500 if (!mpChecks
->n_children() && !mbHasDates
)
1502 std::vector
<int> aFixedWidths
{ nMaxMemberWidth
};
1503 // tdf#134038 insert in the fastest order, this might be backwards so only do it for
1504 // the !mbHasDates case where no entry depends on another to exist before getting
1505 // inserted. We cannot retain pre-existing treeview content, only clear and fill it.
1506 mpChecks
->bulk_insert_for_each(n
, [this, &nVisMemCount
, &bUnlock
](weld::TreeIter
& rIter
, int i
) {
1507 assert(!maMembers
[i
].mbDate
);
1508 bool bCheck
= ((mxChkLockChecked
->get_active() || bUnlock
) ? maMembers
[i
].mbMarked
: maMembers
[i
].mbVisible
);
1509 insertMember(*mpChecks
, rIter
, maMembers
[i
], bCheck
, mxChkLockChecked
->get_active());
1513 }, nullptr, &aFixedWidths
);
1519 std::unique_ptr
<weld::TreeIter
> xEntry
= mpChecks
->make_iterator();
1520 std::vector
<std::unique_ptr
<weld::TreeIter
>> aExpandRows
;
1522 for (size_t i
= 0; i
< n
; ++i
)
1524 if (maMembers
[i
].mbDate
)
1526 CheckEntry(maMembers
[i
].maName
, maMembers
[i
].mxParent
.get(), maMembers
[i
].mbVisible
);
1527 // Expand first node of checked dates
1528 if (!maMembers
[i
].mxParent
&& IsChecked(maMembers
[i
].maName
, maMembers
[i
].mxParent
.get()))
1530 std::unique_ptr
<weld::TreeIter
> xDateEntry
= FindEntry(nullptr, maMembers
[i
].maName
);
1532 aExpandRows
.emplace_back(std::move(xDateEntry
));
1537 mpChecks
->append(xEntry
.get());
1538 insertMember(*mpChecks
, *xEntry
, maMembers
[i
], maMembers
[i
].mbVisible
);
1541 if (maMembers
[i
].mbVisible
)
1547 for (const auto& rRow
: aExpandRows
)
1548 mpChecks
->expand_row(*rRow
);
1551 if (nVisMemCount
== nEnableMember
)
1553 // all members visible
1554 mxChkToggleAll
->set_state(TRISTATE_TRUE
);
1555 mePrevToggleAllState
= TRISTATE_TRUE
;
1557 else if (nVisMemCount
== 0)
1559 // no members visible
1560 mxChkToggleAll
->set_state(TRISTATE_FALSE
);
1561 mePrevToggleAllState
= TRISTATE_FALSE
;
1565 mxChkToggleAll
->set_state(TRISTATE_INDET
);
1566 mePrevToggleAllState
= TRISTATE_INDET
;
1570 mpChecks
->set_cursor(0);
1572 return nVisMemCount
;
1575 void ScCheckListMenuControl::setConfig(const Config
& rConfig
)
1580 bool ScCheckListMenuControl::isAllSelected() const
1582 return mxChkToggleAll
->get_state() == TRISTATE_TRUE
;
1585 void ScCheckListMenuControl::getResult(ResultType
& rResult
)
1588 std::unordered_set
<OUString
> vCheckeds
= GetAllChecked();
1589 size_t n
= maMembers
.size();
1590 for (size_t i
= 0; i
< n
; ++i
)
1592 if ( maMembers
[i
].mbLeaf
)
1594 OUStringBuffer
aLabel(maMembers
[i
].maName
);
1595 if (aLabel
.isEmpty())
1596 aLabel
= ScResId(STR_EMPTYDATA
);
1598 /* TODO: performance-wise this looks suspicious, concatenating to
1599 * do the lookup for each leaf item seems wasteful. */
1600 // Checked labels are in the form "child;parent;grandparent".
1601 if (maMembers
[i
].mxParent
)
1603 std::unique_ptr
<weld::TreeIter
> xIter(mpChecks
->make_iterator(maMembers
[i
].mxParent
.get()));
1606 aLabel
.append(";" + mpChecks
->get_text(*xIter
));
1608 while (mpChecks
->iter_parent(*xIter
));
1611 bool bState
= vCheckeds
.find(aLabel
.makeStringAndClear()) != vCheckeds
.end();
1613 ResultEntry aResultEntry
;
1614 aResultEntry
.bValid
= bState
&& !maMembers
[i
].mbHiddenByOtherFilter
;
1615 aResultEntry
.aName
= maMembers
[i
].maRealName
;
1616 aResultEntry
.nValue
= maMembers
[i
].mnValue
;
1617 aResultEntry
.bDate
= maMembers
[i
].mbDate
;
1618 aResultEntry
.bValue
= maMembers
[i
].mbValue
;
1619 aResult
.insert(aResultEntry
);
1622 rResult
.swap(aResult
);
1625 void ScCheckListMenuControl::launch(weld::Widget
* pWidget
, const tools::Rectangle
& rRect
)
1628 if (!maConfig
.mbAllowEmptySet
)
1629 // We need to have at least one member selected.
1630 mxBtnOk
->set_sensitive(GetCheckedEntryCount() != 0);
1632 tools::Rectangle
aRect(rRect
);
1635 // In RTL mode, the logical "left" is visual "right".
1636 if (!comphelper::LibreOfficeKit::isActive())
1638 tools::Long nLeft
= aRect
.Left() - aRect
.GetWidth();
1639 aRect
.SetLeft( nLeft
);
1643 // in LOK mode, rRect is in document pixel coordinates, so width has to be added
1644 // to place the popup next to the (visual) left aligned button.
1645 aRect
.Move(aRect
.GetWidth(), 0);
1648 else if (mnWndWidth
< aRect
.GetWidth())
1650 // Target rectangle (i.e. cell width) is wider than the window.
1651 // Simulate right-aligned launch by modifying the target rectangle
1653 tools::Long nDiff
= aRect
.GetWidth() - mnWndWidth
;
1654 aRect
.AdjustLeft(nDiff
);
1657 StartPopupMode(pWidget
, aRect
);
1660 void ScCheckListMenuControl::close(bool bOK
)
1662 if (bOK
&& mxOKAction
)
1663 mxOKAction
->execute();
1667 void ScCheckListMenuControl::setExtendedData(std::unique_ptr
<ExtendedData
> p
)
1669 mxExtendedData
= std::move(p
);
1672 ScCheckListMenuControl::ExtendedData
* ScCheckListMenuControl::getExtendedData()
1674 return mxExtendedData
.get();
1677 void ScCheckListMenuControl::setOKAction(Action
* p
)
1679 mxOKAction
.reset(p
);
1682 void ScCheckListMenuControl::setPopupEndAction(Action
* p
)
1684 mxPopupEndAction
.reset(p
);
1687 void ScCheckListMenuControl::setFieldChangedAction(Action
* p
)
1689 mxFieldChangedAction
.reset(p
);
1692 IMPL_LINK_NOARG(ScCheckListMenuControl
, PopupModeEndHdl
, weld::Popover
&, void)
1694 mbIsPoppedUp
= false;
1695 clearSelectedMenuItem();
1696 if (mxPopupEndAction
)
1697 mxPopupEndAction
->execute();
1699 DropPendingEvents();
1702 int ScCheckListMenuControl::GetTextWidth(const OUString
& rsName
) const
1704 return mxDropDown
->GetTextWidth(rsName
);
1707 int ScCheckListMenuControl::IncreaseWindowWidthToFitText(int nMaxTextWidth
)
1709 int nBorder
= nBorderWidth
* 2 + 4;
1710 int nNewWidth
= nMaxTextWidth
- nBorder
;
1711 if (nNewWidth
> mnCheckWidthReq
)
1713 mnCheckWidthReq
= nNewWidth
;
1714 int nChecksHeight
= mpChecks
->get_height_rows(nCheckListVisibleRows
);
1715 mpChecks
->set_size_request(mnCheckWidthReq
, nChecksHeight
);
1717 return mnCheckWidthReq
+ nBorder
;
1720 ScListSubMenuControl::ScListSubMenuControl(weld::Widget
* pParent
, ScCheckListMenuControl
& rParentControl
, bool bColorMenu
)
1721 : mxBuilder(Application::CreateBuilder(pParent
, u
"modules/scalc/ui/filtersubdropdown.ui"_ustr
))
1722 , mxPopover(mxBuilder
->weld_popover(u
"FilterSubDropDown"_ustr
))
1723 , mxContainer(mxBuilder
->weld_container(u
"container"_ustr
))
1724 , mxMenu(mxBuilder
->weld_tree_view(u
"menu"_ustr
))
1725 , mxBackColorMenu(mxBuilder
->weld_tree_view(u
"background"_ustr
))
1726 , mxTextColorMenu(mxBuilder
->weld_tree_view(u
"textcolor"_ustr
))
1727 , mxScratchIter(mxMenu
->make_iterator())
1728 , mrParentControl(rParentControl
)
1729 , mnBackColorMenuPrefHeight(-1)
1730 , mnTextColorMenuPrefHeight(-1)
1731 , mbColorMenu(bColorMenu
)
1734 mxBackColorMenu
->hide();
1735 mxTextColorMenu
->hide();
1744 mxBackColorMenu
->set_clicks_to_toggle(1);
1745 mxBackColorMenu
->enable_toggle_buttons(weld::ColumnToggleType::Radio
);
1746 mxBackColorMenu
->connect_selection_changed(
1747 LINK(this, ScListSubMenuControl
, ColorSelChangedHdl
));
1748 mxTextColorMenu
->set_clicks_to_toggle(1);
1749 mxTextColorMenu
->enable_toggle_buttons(weld::ColumnToggleType::Radio
);
1750 mxTextColorMenu
->connect_selection_changed(
1751 LINK(this, ScListSubMenuControl
, ColorSelChangedHdl
));
1752 SetupMenu(*mxBackColorMenu
);
1753 SetupMenu(*mxTextColorMenu
);
1757 void ScListSubMenuControl::SetupMenu(weld::TreeView
& rMenu
)
1759 rMenu
.connect_row_activated(LINK(this, ScListSubMenuControl
, RowActivatedHdl
));
1760 rMenu
.connect_key_press(LINK(this, ScListSubMenuControl
, MenuKeyInputHdl
));
1763 void ScListSubMenuControl::StartPopupMode(weld::Widget
* pParent
, const tools::Rectangle
& rRect
)
1765 if (mxPopupStartAction
)
1766 mxPopupStartAction
->execute();
1768 mxPopover
->popup_at_rect(pParent
, rRect
, weld::Placement::End
);
1770 weld::TreeView
& rFirstMenu
= mbColorMenu
? *mxBackColorMenu
: *mxMenu
;
1771 rFirstMenu
.set_cursor(0);
1772 rFirstMenu
.select(0);
1774 mrParentControl
.setSubMenuFocused(this);
1777 void ScListSubMenuControl::EndPopupMode()
1779 mxPopover
->popdown();
1782 void ScListSubMenuControl::GrabFocus()
1784 weld::TreeView
& rFirstMenu
= mbColorMenu
? *mxBackColorMenu
: *mxMenu
;
1785 rFirstMenu
.grab_focus();
1788 bool ScListSubMenuControl::IsVisible() const
1790 return mxPopover
->get_visible();
1793 void ScListSubMenuControl::resizeToFitMenuItems()
1796 mxMenu
->set_size_request(-1, mxMenu
->get_preferred_size().Height());
1799 int nBackColorMenuPrefHeight
= mnBackColorMenuPrefHeight
;
1800 if (nBackColorMenuPrefHeight
== -1)
1801 nBackColorMenuPrefHeight
= mxBackColorMenu
->get_preferred_size().Height();
1802 mxBackColorMenu
->set_size_request(-1, nBackColorMenuPrefHeight
);
1803 int nTextColorMenuPrefHeight
= mnTextColorMenuPrefHeight
;
1804 if (nTextColorMenuPrefHeight
== -1)
1805 nTextColorMenuPrefHeight
= mxTextColorMenu
->get_preferred_size().Height();
1806 mxTextColorMenu
->set_size_request(-1, nTextColorMenuPrefHeight
);
1810 void ScListSubMenuControl::addItem(ScCheckListMenuControl::Action
* pAction
)
1812 ScCheckListMenuControl::MenuItemData aItem
;
1813 aItem
.mbEnabled
= true;
1814 aItem
.mxAction
.reset(pAction
);
1815 maMenuItems
.emplace_back(std::move(aItem
));
1818 void ScListSubMenuControl::addMenuItem(const OUString
& rText
, ScCheckListMenuControl::Action
* pAction
)
1821 mxMenu
->append(weld::toId(pAction
), rText
);
1824 void ScListSubMenuControl::addMenuColorItem(const OUString
& rText
, bool bActive
, VirtualDevice
& rImage
,
1825 int nMenu
, ScCheckListMenuControl::Action
* pAction
)
1829 weld::TreeView
& rColorMenu
= nMenu
== 0 ? *mxBackColorMenu
: *mxTextColorMenu
;
1832 OUString sId
= weld::toId(pAction
);
1833 rColorMenu
.insert(nullptr, -1, &rText
, &sId
, nullptr, nullptr, false, mxScratchIter
.get());
1834 rColorMenu
.set_toggle(*mxScratchIter
, bActive
? TRISTATE_TRUE
: TRISTATE_FALSE
);
1835 rColorMenu
.set_image(*mxScratchIter
, rImage
);
1837 if (mnTextColorMenuPrefHeight
== -1 &&
1838 &rColorMenu
== mxTextColorMenu
.get() &&
1839 mxTextColorMenu
->n_children() == nColorListVisibleRows
)
1841 mnTextColorMenuPrefHeight
= mxTextColorMenu
->get_preferred_size().Height();
1844 if (mnBackColorMenuPrefHeight
== -1 &&
1845 &rColorMenu
== mxBackColorMenu
.get() &&
1846 mxBackColorMenu
->n_children() == nColorListVisibleRows
)
1848 mnBackColorMenuPrefHeight
= mxBackColorMenu
->get_preferred_size().Height();
1852 void ScListSubMenuControl::addSeparator()
1854 ScCheckListMenuControl::MenuItemData aItem
;
1855 maMenuItems
.emplace_back(std::move(aItem
));
1857 mxMenu
->append_separator("separator" + OUString::number(maMenuItems
.size()));
1860 void ScListSubMenuControl::clearMenuItems()
1862 maMenuItems
.clear();
1864 mxBackColorMenu
->clear();
1865 mnBackColorMenuPrefHeight
= -1;
1866 mxTextColorMenu
->clear();
1867 mnTextColorMenuPrefHeight
= -1;
1870 IMPL_LINK(ScListSubMenuControl
, MenuKeyInputHdl
, const KeyEvent
&, rKEvt
, bool)
1872 bool bConsumed
= false;
1873 const vcl::KeyCode
& rKeyCode
= rKEvt
.GetKeyCode();
1874 const sal_uInt16 eKeyCode
= rKeyCode
.GetCode();
1876 // Assume that once the keyboard is used that focus should restore to the
1878 if (eKeyCode
!= KEY_ESCAPE
)
1879 mrParentControl
.SetRestoreFocus(ScCheckListMenuControl::RestoreFocus::Menu
);
1886 mrParentControl
.endSubMenu(*this);
1893 weld::TreeView
& rMenu
= !mbColorMenu
? *mxMenu
:
1894 (mxBackColorMenu
->has_focus() ? *mxBackColorMenu
: *mxTextColorMenu
);
1895 // don't toggle checkbutton, go straight to activating entry
1896 bConsumed
= RowActivatedHdl(rMenu
);
1901 if (mxTextColorMenu
->get_visible() &&
1902 mxBackColorMenu
->has_focus() &&
1903 mxBackColorMenu
->get_selected_index() == mxBackColorMenu
->n_children() - 1)
1905 mxBackColorMenu
->unselect_all();
1906 mxTextColorMenu
->select(0);
1907 mxTextColorMenu
->set_cursor(0);
1908 mxTextColorMenu
->grab_focus();
1915 if (mxBackColorMenu
->get_visible() &&
1916 mxTextColorMenu
->has_focus() &&
1917 mxTextColorMenu
->get_selected_index() == 0)
1919 mxTextColorMenu
->unselect_all();
1920 int nIndex
= mxBackColorMenu
->n_children() - 1;
1921 mxBackColorMenu
->select(nIndex
);
1922 mxBackColorMenu
->set_cursor(nIndex
);
1923 mxBackColorMenu
->grab_focus();
1933 IMPL_LINK(ScListSubMenuControl
, ColorSelChangedHdl
, weld::TreeView
&, rMenu
, void)
1935 if (rMenu
.get_selected_index() == -1)
1937 if (&rMenu
!= mxTextColorMenu
.get())
1938 mxTextColorMenu
->unselect_all();
1940 mxBackColorMenu
->unselect_all();
1944 IMPL_LINK(ScListSubMenuControl
, RowActivatedHdl
, weld::TreeView
&, rMenu
, bool)
1946 executeMenuItem(weld::fromId
<ScCheckListMenuControl::Action
*>(rMenu
.get_selected_id()));
1950 void ScListSubMenuControl::executeMenuItem(ScCheckListMenuControl::Action
* pAction
)
1952 // if no action is defined.
1956 const bool bClosePopup
= pAction
->execute();
1958 terminateAllPopupMenus();
1961 void ScListSubMenuControl::setPopupStartAction(ScCheckListMenuControl::Action
* p
)
1963 mxPopupStartAction
.reset(p
);
1966 void ScListSubMenuControl::terminateAllPopupMenus()
1969 mrParentControl
.terminateAllPopupMenus();
1972 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */