tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / sc / source / ui / cctrl / checklistmenu.cxx
blob23727ebe9677ff9d4c62409e14bceae87f382814
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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()
46 : mbEnabled(true)
50 ScCheckListMenuControl::SubMenuItemData::SubMenuItemData(ScCheckListMenuControl* pParent)
51 : maTimer("sc SubMenuItemData maTimer")
52 , mpSubMenu(nullptr)
53 , mnMenuPos(MENU_NOT_SELECTED)
54 , mpParent(pParent)
56 maTimer.SetInvokeHandler(LINK(this, ScCheckListMenuControl::SubMenuItemData, TimeoutHdl));
57 maTimer.SetTimeout(Application::GetSettings().GetMouseSettings().GetMenuDelay());
60 void ScCheckListMenuControl::SubMenuItemData::reset()
62 mpSubMenu = nullptr;
63 mnMenuPos = MENU_NOT_SELECTED;
64 maTimer.Stop();
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());
75 return true;
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())
88 case KEY_RIGHT:
90 if (mnSelectedMenu >= maMenuItems.size() || mnSelectedMenu == MENU_NOT_SELECTED)
91 break;
93 const MenuItemData& rMenu = maMenuItems[mnSelectedMenu];
94 if (!rMenu.mxSubMenuWin)
95 break;
97 executeMenuItem(mnSelectedMenu);
101 return false;
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);
116 return;
119 else
120 nSelectedMenu = mxMenu->get_iter_index_in_parent(*mxScratchIter);
122 setSelectedMenuItem(nSelectedMenu);
125 void ScCheckListMenuControl::addMenuItem(const OUString& rText, Action* pAction)
127 MenuItemData aItem;
128 aItem.mbEnabled = true;
129 aItem.mxAction.reset(pAction);
130 maMenuItems.emplace_back(std::move(aItem));
132 mxMenu->show();
133 mxMenu->append_text(rText);
134 mxMenu->set_image(mxMenu->n_children() - 1, css::uno::Reference<css::graphic::XGraphic>(), 1);
137 void ScCheckListMenuControl::addSeparator()
139 MenuItemData aItem;
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)
148 return;
149 maAllocatedSize = rSize;
150 SetDropdownPos();
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;
170 SetDropdownPos();
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)
189 MenuItemData aItem;
190 aItem.mbEnabled = bEnabled;
192 aItem.mxSubMenuWin.reset(new ScListSubMenuControl(mxMenu.get(), *this, bColorMenu));
193 maMenuItems.emplace_back(std::move(aItem));
195 mxMenu->show();
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())
204 return;
206 const MenuItemData& rMenu = maMenuItems[nPos];
207 if (rMenu.mxSubMenuWin)
209 if (rMenu.mbEnabled)
211 maOpenTimer.mnMenuPos = nPos;
212 maOpenTimer.mpSubMenu = rMenu.mxSubMenuWin.get();
213 launchSubMenu();
215 return;
218 if (!maMenuItems[nPos].mxAction)
219 // no action is defined.
220 return;
222 const bool bClosePopup = maMenuItems[nPos].mxAction->execute();
223 if (bClosePopup)
224 terminateAllPopupMenus();
227 void ScCheckListMenuControl::setSelectedMenuItem(size_t nPos)
229 if (mnSelectedMenu == nPos)
230 // nothing to do.
231 return;
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();
248 launchSubMenu();
250 else if (pTimer == &maCloseTimer)
252 // end submenu.
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)
267 if (!pMenu)
268 return;
270 // Set the submenu on launch queue.
271 if (maOpenTimer.mpSubMenu)
273 if (maOpenTimer.mpSubMenu != pMenu)
275 // new submenu is being requested.
276 queueCloseSubMenu();
278 else
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();
289 else
290 maOpenTimer.maTimer.Start();
293 void ScCheckListMenuControl::queueCloseSubMenu()
295 if (!maOpenTimer.mpSubMenu)
296 // There is no submenu to close.
297 return;
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();
315 else
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;
329 if (!pSubMenu)
330 return;
332 if (!mxMenu->get_selected(mxScratchIter.get()))
333 return;
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();
368 break;
369 case RestoreFocus::Checks:
370 mpChecks->grab_focus();
371 break;
372 case RestoreFocus::ChkToggleAll:
373 mxChkToggleAll->grab_focus();
374 break;
375 case RestoreFocus::ChkLockChecked:
376 mxChkLockChecked->grab_focus();
377 break;
378 case RestoreFocus::BtnSelectSingle:
379 mxBtnSelectSingle->grab_focus();
380 break;
381 case RestoreFocus::BtnUnselectSingle:
382 mxBtnUnselectSingle->grab_focus();
383 break;
384 default:
385 mxMenu->grab_focus();
386 break;
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())
399 return false;
400 selectMenuItem(MENU_NOT_SELECTED, true);
401 return false;
404 void ScCheckListMenuControl::endSubMenu(ScListSubMenuControl& rSubMenu)
406 rSubMenu.EndPopupMode();
407 maOpenTimer.reset();
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)
423 if (!mbIsMultiField)
424 return;
426 mxFieldsCombo->clear();
428 for (auto& aField: aFields)
429 mxFieldsCombo->append_text(aField);
431 mxFieldsCombo->set_active(0);
434 tools::Long ScCheckListMenuControl::getField()
436 if (!mbIsMultiField)
437 return -1;
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)
449 queueCloseSubMenu();
450 return;
453 if (!maMenuItems[nPos].mbEnabled)
455 queueCloseSubMenu();
456 return;
459 if (bSubMenuTimer)
461 if (maMenuItems[nPos].mxSubMenuWin && mxMenu->changed_by_hover())
463 ScListSubMenuControl* pSubMenu = maMenuItems[nPos].mxSubMenuWin.get();
464 queueLaunchSubMenu(nPos, pSubMenu);
466 else
467 queueCloseSubMenu();
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)
482 return i;
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()
500 if (!mbIsPoppedUp)
501 return;
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));
511 mbIsPoppedUp = true;
512 mxPopover->popup_at_rect(pParent, rRect);
513 GrabFocus();
516 void ScCheckListMenuControl::terminateAllPopupMenus()
518 EndPopupMode();
521 ScCheckListMenuControl::Config::Config() :
522 mbAllowEmptySet(true), mbRTL(false)
526 ScCheckListMember::ScCheckListMember()
527 : mnValue(0.0)
528 , mbVisible(true)
529 , mbMarked(false)
530 , mbCheck(true)
531 , mbHiddenByOtherFilter(false)
532 , mbDate(false)
533 , mbLeaf(false)
534 , mbValue(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)
570 , mnWndWidth(0)
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)
580 , maOpenTimer(this)
581 , maCloseTimer(this)
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
606 faster GtkListStore
608 if (mbHasDates)
609 mpChecks = mxTreeChecks.get();
610 else
612 mxTreeChecks->hide();
613 mxListChecks->show();
614 mpChecks = mxListChecks.get();
617 int nChecksHeight = mxTreeChecks->get_height_rows(mnCheckListVisibleRows);
618 if (nWidth != -1)
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);
632 mxBox->show();
633 if (mbIsMultiField)
635 mxFieldsComboLabel->show();
636 mxFieldsCombo->show();
638 else
640 mxFieldsComboLabel->hide();
641 mxFieldsCombo->hide();
643 mxEdSearch->show();
644 mxButtonBox->show();
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));
652 if (mbIsMultiField)
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));
665 CreateDropDown();
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;
693 else
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();
718 EndPopupMode();
719 for (auto& rMenuItem : maMenuItems)
720 rMenuItem.mxSubMenuWin.reset();
721 DropPendingEvents();
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);
742 return 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()))
757 return;
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)
764 return false;
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())
771 return true;
773 if (sCommand == "more")
774 ++mnCheckListVisibleRows;
775 else if (sCommand == "less")
776 --mnCheckListVisibleRows;
777 ResizeToRequest();
779 return true;
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())
793 close(true);
794 else if (&rBtn == mxBtnCancel.get())
795 close(false);
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());
800 if (!bEntry)
801 xEntry.reset();
802 if (bEntry && mpChecks->get_sensitive(*xEntry, 0))
804 selectCurrentMemberOnly(&rBtn == mxBtnSelectSingle.get());
805 Check(xEntry.get());
810 namespace
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);
820 if (bLock)
821 rView.set_sensitive(rIter, !rMember.mbHiddenByOtherFilter && !rMember.mbMarked);
822 else
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;
841 if (!bPartialMatch)
842 continue;
843 if (!bLock || (!rMembers[i].mbMarked && !rMembers[i].mbHiddenByOtherFilter))
844 rSearchedMembers.push_back(i);
847 if (bLock)
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;
870 else
872 for (auto& aMember : maMembers)
873 if (aMember.maName == mpChecks->get_text(rEntry))
874 aMember.mbCheck = false;
877 return false;
880 mpChecks->freeze();
881 mpChecks->clear();
882 mpChecks->thaw();
884 OUString aSearchText = mxEdSearch->get_text();
885 if (aSearchText.isEmpty())
887 initMembers(-1, !mxChkLockChecked->get_active());
889 else
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)
912 case TRISTATE_TRUE:
913 mxChkToggleAll->set_state(TRISTATE_FALSE);
914 setAllMemberState(false);
915 break;
916 case TRISTATE_FALSE:
917 case TRISTATE_INDET:
918 default:
919 mxChkToggleAll->set_state(TRISTATE_TRUE);
920 setAllMemberState(true);
921 break;
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
944 if (mbHasDates)
946 mpChecks->freeze();
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 )
961 if ( !bIsDate )
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);
966 else
967 continue;
969 else if ( bIsDate && maMembers[i].meDatePartType != ScCheckListMember::DAY )
970 continue;
972 if ( bSearchTextEmpty )
974 auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, maMembers[i].mbVisible);
975 updateMemberParents(xLeaf.get(), i);
976 if ( maMembers[i].mbVisible )
977 ++nSelCount;
978 continue;
981 if ( bPartialMatch )
983 auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i]);
984 updateMemberParents(xLeaf.get(), i);
985 ++nSelCount;
987 else
989 ShowCheckEntry(aLabelDisp, maMembers[i], false, false);
990 if( bIsDate )
991 bSomeDateDeletes = true;
995 if ( bSomeDateDeletes )
997 for (size_t i = 0; i < nEnableMember; ++i)
999 if (!maMembers[i].mbDate)
1000 continue;
1001 if (maMembers[i].meDatePartType != ScCheckListMember::DAY)
1002 continue;
1003 updateMemberParents(nullptr, i);
1007 mpChecks->thaw();
1009 else
1011 mpChecks->freeze();
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
1015 mpChecks->clear();
1017 mpChecks->thaw();
1019 if (bSearchTextEmpty)
1020 nSelCount = initMembers();
1021 else
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());
1030 ++nSelCount;
1031 }, nullptr, &aFixedWidths);
1035 if ( nSelCount == nEnableMember )
1036 mxChkToggleAll->set_state( TRISTATE_TRUE );
1037 else if ( nSelCount == 0 )
1038 mxChkToggleAll->set_state( TRISTATE_FALSE );
1039 else
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())
1061 close(true);
1062 return true;
1065 IMPL_LINK( ScCheckListMenuControl, CheckHdl, const weld::TreeView::iter_col&, rRowCol, void )
1067 Check(&rRowCol.first);
1070 void ScCheckListMenuControl::Check(const weld::TreeIter* pEntry)
1072 if (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);
1083 else
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 )
1096 return;
1098 OUString aYearName = maMembers[nIdx].maDateParts[0];
1099 OUString aMonthName = maMembers[nIdx].maDateParts[1];
1100 auto aItr = maYearMonthMap.find(aYearName + aMonthName);
1102 if ( pLeaf )
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();
1108 else
1110 xYearEntry = mpChecks->make_iterator(xMonthEntry.get());
1111 if (!mpChecks->iter_parent(*xYearEntry))
1112 xYearEntry.reset();
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);
1122 else
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)
1157 return;
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;
1166 mpChecks->freeze();
1168 std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
1169 if (!xYearEntry)
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);
1189 if (!xMonthEntry)
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);
1210 if (!xDayEntry)
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));
1232 mpChecks->thaw();
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()
1255 maMembers.clear();
1257 mpChecks->freeze();
1258 mpChecks->clear();
1259 mpChecks->thaw();
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);
1266 while (bEntry)
1268 if (sNode == mpChecks->get_text(*xEntry, 0))
1269 return xEntry;
1270 bEntry = mpChecks->iter_next_sibling(*xEntry);
1272 return nullptr;
1275 void ScCheckListMenuControl::GetRecursiveChecked(const weld::TreeIter* pEntry, std::unordered_set<OUString>& vOut,
1276 OUString& rLabel)
1278 if (mpChecks->get_toggle(*pEntry) != TRISTATE_TRUE)
1279 return;
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);
1286 else
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))
1293 return;
1295 std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(pEntry));
1296 bool bChild = mpChecks->iter_children(*xChild);
1297 while (bChild)
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.
1306 rLabel.clear();
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);
1315 while (bEntry)
1317 OUString aLabel;
1318 GetRecursiveChecked(xEntry.get(), vResults, aLabel);
1319 if (!aLabel.isEmpty())
1320 vResults.insert(aLabel);
1321 bEntry = mpChecks->iter_next_sibling(*xEntry);
1324 return vResults;
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);
1336 if (xEntry)
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);
1346 while (bEntry)
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))
1360 return;
1362 std::unique_ptr<weld::TreeIter> xAncestor(mpChecks->make_iterator(&rParent));
1363 bool bAncestor = mpChecks->iter_parent(*xAncestor);
1364 while (bAncestor)
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;
1373 while (bChild)
1375 if (mpChecks->get_toggle(*xChild) == TRISTATE_TRUE)
1377 bChildChecked = true;
1378 break;
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);
1393 if ( bShow )
1395 if (!xEntry)
1397 if (rMember.mbDate)
1399 if (rMember.maDateParts.empty())
1400 return nullptr;
1402 std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, rMember.maDateParts[0]);
1403 if (!xYearEntry)
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]);
1411 if (!xMonthEntry)
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);
1419 if (!xDayEntry)
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);
1434 else
1435 CheckEntry(*xEntry, bCheck);
1437 else if (xEntry)
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))
1447 xParent.reset();
1448 mpChecks->remove(*xTmp);
1452 return nullptr;
1455 int ScCheckListMenuControl::GetCheckedEntryCount() const
1457 int nRet = 0;
1459 mpChecks->all_foreach([this, &nRet](weld::TreeIter& rEntry){
1460 if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
1461 ++nRet;
1462 return false;
1465 return nRet;
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());
1484 return true;
1487 return false;
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());
1511 if (bCheck)
1512 ++nVisMemCount;
1513 }, nullptr, &aFixedWidths);
1515 else
1517 mpChecks->freeze();
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);
1531 if (xDateEntry)
1532 aExpandRows.emplace_back(std::move(xDateEntry));
1535 else
1537 mpChecks->append(xEntry.get());
1538 insertMember(*mpChecks, *xEntry, maMembers[i], maMembers[i].mbVisible);
1541 if (maMembers[i].mbVisible)
1542 ++nVisMemCount;
1545 mpChecks->thaw();
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;
1563 else
1565 mxChkToggleAll->set_state(TRISTATE_INDET);
1566 mePrevToggleAllState = TRISTATE_INDET;
1569 if (nVisMemCount)
1570 mpChecks->set_cursor(0);
1572 return nVisMemCount;
1575 void ScCheckListMenuControl::setConfig(const Config& rConfig)
1577 maConfig = rConfig;
1580 bool ScCheckListMenuControl::isAllSelected() const
1582 return mxChkToggleAll->get_state() == TRISTATE_TRUE;
1585 void ScCheckListMenuControl::getResult(ResultType& rResult)
1587 ResultType aResult;
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)
1627 prepWindow();
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);
1633 if (maConfig.mbRTL)
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 );
1641 else
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
1652 // size.
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();
1664 EndPopupMode();
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)
1733 mxMenu->hide();
1734 mxBackColorMenu->hide();
1735 mxTextColorMenu->hide();
1737 if (!bColorMenu)
1739 SetupMenu(*mxMenu);
1740 mxMenu->show();
1742 else
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()
1795 if (!mbColorMenu)
1796 mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height());
1797 else
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)
1820 addItem(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)
1827 addItem(pAction);
1829 weld::TreeView& rColorMenu = nMenu == 0 ? *mxBackColorMenu : *mxTextColorMenu;
1830 rColorMenu.show();
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();
1863 mxMenu->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
1877 // parent menu
1878 if (eKeyCode != KEY_ESCAPE)
1879 mrParentControl.SetRestoreFocus(ScCheckListMenuControl::RestoreFocus::Menu);
1881 switch (eKeyCode)
1883 case KEY_ESCAPE:
1884 case KEY_LEFT:
1886 mrParentControl.endSubMenu(*this);
1887 bConsumed = true;
1888 break;
1890 case KEY_SPACE:
1891 case KEY_RETURN:
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);
1897 break;
1899 case KEY_DOWN:
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();
1909 bConsumed = true;
1911 break;
1913 case KEY_UP:
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();
1924 bConsumed = true;
1926 break;
1930 return bConsumed;
1933 IMPL_LINK(ScListSubMenuControl, ColorSelChangedHdl, weld::TreeView&, rMenu, void)
1935 if (rMenu.get_selected_index() == -1)
1936 return;
1937 if (&rMenu != mxTextColorMenu.get())
1938 mxTextColorMenu->unselect_all();
1939 else
1940 mxBackColorMenu->unselect_all();
1941 rMenu.grab_focus();
1944 IMPL_LINK(ScListSubMenuControl, RowActivatedHdl, weld::TreeView&, rMenu, bool)
1946 executeMenuItem(weld::fromId<ScCheckListMenuControl::Action*>(rMenu.get_selected_id()));
1947 return true;
1950 void ScListSubMenuControl::executeMenuItem(ScCheckListMenuControl::Action* pAction)
1952 // if no action is defined.
1953 if (!pAction)
1954 return;
1956 const bool bClosePopup = pAction->execute();
1957 if (bClosePopup)
1958 terminateAllPopupMenus();
1961 void ScListSubMenuControl::setPopupStartAction(ScCheckListMenuControl::Action* p)
1963 mxPopupStartAction.reset(p);
1966 void ScListSubMenuControl::terminateAllPopupMenus()
1968 EndPopupMode();
1969 mrParentControl.terminateAllPopupMenus();
1972 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */