LibreOffice Module sc (master)  1
checklistmenu.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <checklistmenu.hxx>
21 #include <globstr.hrc>
22 #include <scresid.hxx>
23 
24 #include <vcl/decoview.hxx>
25 #include <vcl/event.hxx>
26 #include <vcl/dockwin.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/virdev.hxx>
30 #include <rtl/math.hxx>
31 #include <unotools/charclass.hxx>
32 #include <comphelper/lok.hxx>
33 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
34 #include <tools/json_writer.hxx>
35 #include <sfx2/viewsh.hxx>
37 
38 #include <document.hxx>
39 
40 using namespace com::sun::star;
41 using ::com::sun::star::uno::Reference;
42 
44  : mbEnabled(true)
45 {
46 }
47 
49  : mpSubMenu(nullptr)
50  , mnMenuPos(MENU_NOT_SELECTED)
51  , mpParent(pParent)
52 {
54  maTimer.SetTimeout(Application::GetSettings().GetMouseSettings().GetMenuDelay());
55 }
56 
58 {
59  mpSubMenu = nullptr;
60  mnMenuPos = MENU_NOT_SELECTED;
61  maTimer.Stop();
62 }
63 
65 {
66  mpParent->handleMenuTimeout(this);
67 }
68 
70 {
71  executeMenuItem(mxMenu->get_selected_index());
72  return true;
73 }
74 
75 IMPL_LINK(ScCheckListMenuControl, MenuKeyInputHdl, const KeyEvent&, rKEvt, bool)
76 {
77  const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
78 
79  switch (rKeyCode.GetCode())
80  {
81  case KEY_LEFT:
82  {
84  if (pParentMenu)
85  pParentMenu->get_widget().endSubMenu(*this);
86  break;
87  }
88  case KEY_RIGHT:
89  {
91  break;
92 
93  const MenuItemData& rMenu = maMenuItems[mnSelectedMenu];
94  if (!rMenu.mbEnabled || !rMenu.mxSubMenuWin)
95  break;
96 
98  maOpenTimer.mpSubMenu = rMenu.mxSubMenuWin.get();
99  launchSubMenu(true);
100  }
101  }
102 
103  return false;
104 }
105 
107 {
108  sal_uInt32 nSelectedMenu = MENU_NOT_SELECTED;
109  if (!mxMenu->get_selected(mxScratchIter.get()))
110  {
111  // reselect current item if its submenu is up and the launching item
112  // became unselected
113  if (mnSelectedMenu < maMenuItems.size() &&
114  maMenuItems[mnSelectedMenu].mxSubMenuWin &&
115  maMenuItems[mnSelectedMenu].mxSubMenuWin->IsVisible())
116  {
117  mxMenu->select(mnSelectedMenu);
118  return;
119  }
120  }
121  else
122  nSelectedMenu = mxMenu->get_iter_index_in_parent(*mxScratchIter);
123 
124  setSelectedMenuItem(nSelectedMenu, true);
125 }
126 
127 void ScCheckListMenuControl::addMenuItem(const OUString& rText, Action* pAction)
128 {
129  MenuItemData aItem;
130  aItem.mbEnabled = true;
131  aItem.mxAction.reset(pAction);
132  maMenuItems.emplace_back(std::move(aItem));
133 
134  mxMenu->show();
135  mxMenu->append_text(rText);
136  if (mbCanHaveSubMenu)
137  mxMenu->set_image(mxMenu->n_children() - 1, css::uno::Reference<css::graphic::XGraphic>(), 1);
138 }
139 
141 {
142  MenuItemData aItem;
143  maMenuItems.emplace_back(std::move(aItem));
144 
145  mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
146 }
147 
148 IMPL_LINK(ScCheckListMenuControl, TreeSizeAllocHdl, const Size&, rSize, void)
149 {
151  std::vector<int> aWidths;
152  aWidths.push_back(rSize.Width() - (mxMenu->get_text_height() * 3) / 4 - 6);
153  mxMenu->set_column_fixed_widths(aWidths);
154 }
155 
157 {
158  int nWidth = (mxMenu->get_text_height() * 3) / 4;
159  mxDropDown->SetOutputSizePixel(Size(nWidth, nWidth));
160  DecorationView aDecoView(mxDropDown.get());
161  aDecoView.DrawSymbol(tools::Rectangle(Point(0, 0), Size(nWidth, nWidth)),
162  SymbolType::SPIN_RIGHT, mxDropDown->GetTextColor(),
163  DrawSymbolFlags::NONE);
164 }
165 
166 ScCheckListMenuWindow* ScCheckListMenuControl::addSubMenuItem(const OUString& rText, bool bEnabled)
167 {
169 
170  MenuItemData aItem;
171  aItem.mbEnabled = bEnabled;
172  vcl::Window *pContainer = mxFrame->GetWindow(GetWindowType::FirstChild);
173 
174  vcl::ILibreOfficeKitNotifier* pNotifier = nullptr;
176  pNotifier = SfxViewShell::Current();
177 
179  false, -1, mxFrame.get(),
180  pNotifier));
181  maMenuItems.emplace_back(std::move(aItem));
182 
183  mxMenu->show();
184  mxMenu->append_text(rText);
185  if (mbCanHaveSubMenu)
186  mxMenu->set_image(mxMenu->n_children() - 1, *mxDropDown, 1);
187 
188  return maMenuItems.back().mxSubMenuWin.get();
189 }
190 
192 {
193  if (nPos >= maMenuItems.size())
194  return;
195 
196  if (!maMenuItems[nPos].mxAction)
197  // no action is defined.
198  return;
199 
200  maMenuItems[nPos].mxAction->execute();
201 }
202 
203 void ScCheckListMenuControl::setSelectedMenuItem(size_t nPos, bool bSubMenuTimer)
204 {
205  if (mnSelectedMenu == nPos)
206  // nothing to do.
207  return;
208 
209  selectMenuItem(nPos, bSubMenuTimer);
210 }
211 
213 {
214  if (pTimer == &maOpenTimer)
215  {
216  // Close any open submenu immediately.
218  {
220  maCloseTimer.mpSubMenu = nullptr;
222  }
223 
224  launchSubMenu(false);
225  }
226  else if (pTimer == &maCloseTimer)
227  {
228  // end submenu.
230  {
231  maOpenTimer.mpSubMenu = nullptr;
232 
234  maCloseTimer.mpSubMenu = nullptr;
235 
237  }
238  }
239 }
240 
242 {
243  if (!pMenu)
244  return;
245 
246  // Set the submenu on launch queue.
248  {
249  if (maOpenTimer.mpSubMenu == pMenu)
250  {
251  if (pMenu == maCloseTimer.mpSubMenu)
253  return;
254  }
255 
256  // new submenu is being requested.
258  }
259 
260  maOpenTimer.mpSubMenu = pMenu;
261  maOpenTimer.mnMenuPos = nPos;
263 }
264 
266 {
267  if (!maOpenTimer.mpSubMenu)
268  // There is no submenu to close.
269  return;
270 
271  // Stop any submenu on queue for opening.
273 
277 }
278 
280 {
282  if (!pSubMenu)
283  return;
284 
285  if (!mxMenu->get_selected(mxScratchIter.get()))
286  return;
287 
288  tools::Rectangle aRect = mxMenu->get_row_area(*mxScratchIter);
289  ScCheckListMenuControl& rSubMenuControl = pSubMenu->get_widget();
290  rSubMenuControl.StartPopupMode(aRect, FloatWinPopupFlags::Right);
291  if (bSetMenuPos)
292  rSubMenuControl.setSelectedMenuItem(0, false); // select menu item after the popup becomes fully visible.
293 
294  mxMenu->select(*mxScratchIter);
295  rSubMenuControl.GrabFocus();
296 
298  jsdialog::SendFullUpdate(pSubMenu->GetLOKWindowId(), "toggle_all");
299 }
300 
301 IMPL_LINK_NOARG(ScCheckListMenuControl, PostPopdownHdl, void*, void)
302 {
303  mnAsyncPostPopdownId = nullptr;
304  mxMenu->grab_focus();
305 }
306 
308 {
309  rSubMenu.EndPopupMode();
310  maOpenTimer.reset();
311 
312  // EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
315 
316  size_t nMenuPos = getSubMenuPos(&rSubMenu);
317  if (nMenuPos != MENU_NOT_SELECTED)
318  {
319  mnSelectedMenu = nMenuPos;
320  mxMenu->select(mnSelectedMenu);
321  }
322 }
323 
325 {
326  mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height() + 2);
327 }
328 
329 void ScCheckListMenuControl::selectMenuItem(size_t nPos, bool bSubMenuTimer)
330 {
331  mxMenu->select(nPos == MENU_NOT_SELECTED ? -1 : nPos);
332  mnSelectedMenu = nPos;
333 
334  if (nPos >= maMenuItems.size() || nPos == MENU_NOT_SELECTED)
335  {
337  return;
338  }
339 
340  if (!maMenuItems[nPos].mbEnabled)
341  {
343  return;
344  }
345 
346  ScCheckListMenuWindow* pParentMenu = mxFrame->GetParentMenu();
347  if (pParentMenu)
348  pParentMenu->get_widget().setSubMenuFocused(this);
349 
350  if (bSubMenuTimer)
351  {
352  if (maMenuItems[nPos].mxSubMenuWin)
353  {
354  ScCheckListMenuWindow* pSubMenu = maMenuItems[nPos].mxSubMenuWin.get();
355  queueLaunchSubMenu(nPos, pSubMenu);
356  }
357  else
359  }
360 }
361 
363 {
365 }
366 
368 {
369  size_t n = maMenuItems.size();
370  for (size_t i = 0; i < n; ++i)
371  {
372  if (!maMenuItems[i].mxSubMenuWin)
373  continue;
374  if (&maMenuItems[i].mxSubMenuWin->get_widget() == pSubMenu)
375  return i;
376  }
377  return MENU_NOT_SELECTED;
378 }
379 
381 {
383  size_t nMenuPos = getSubMenuPos(pSubMenu);
384  if (mnSelectedMenu != nMenuPos)
385  {
386  mnSelectedMenu = nMenuPos;
387  mxMenu->select(mnSelectedMenu);
388  }
389 }
390 
392 {
394  mxFrame->EnableDocking(false);
395 }
396 
398 {
399  mxFrame->EnableDocking(true);
400  DockingManager* pDockingManager = vcl::Window::GetDockingManager();
401  pDockingManager->SetPopupModeEndHdl(mxFrame, LINK(this, ScCheckListMenuControl, PopupModeEndHdl));
402  pDockingManager->StartPopupMode(mxFrame, rRect, (eFlags | FloatWinPopupFlags::GrabFocus));
403 }
404 
406 {
408  NotifyCloseLOK();
409 
410  EndPopupMode();
411  ScCheckListMenuWindow* pParentMenu = mxFrame->GetParentMenu();
412  if (pParentMenu)
413  pParentMenu->get_widget().terminateAllPopupMenus();
414 }
415 
417  mbAllowEmptySet(true), mbRTL(false)
418 {
419 }
420 
422  : mnValue(0.0)
423  , mbVisible(true)
424  , mbDate(false)
425  , mbLeaf(false)
426  , mbValue(false)
427  , mbDuplicated(false)
428  , meDatePartType(YEAR)
429 {
430 }
431 
433  ScDocument* pDoc, bool bCanHaveSubMenu,
434  bool bHasDates, int nWidth)
435  : mxFrame(pParent)
436  , mxBuilder(Application::CreateInterimBuilder(pContainer, "modules/scalc/ui/filterdropdown.ui", false))
437  , mxContainer(mxBuilder->weld_container("FilterDropDown"))
438  , mxMenu(mxBuilder->weld_tree_view("menu"))
439  , mxScratchIter(mxMenu->make_iterator())
440  , mxEdSearch(mxBuilder->weld_entry("search_edit"))
441  , mxBox(mxBuilder->weld_widget("box"))
442  , mxListChecks(mxBuilder->weld_tree_view("check_list_box"))
443  , mxTreeChecks(mxBuilder->weld_tree_view("check_tree_box"))
444  , mxChkToggleAll(mxBuilder->weld_check_button("toggle_all"))
445  , mxBtnSelectSingle(mxBuilder->weld_button("select_current"))
446  , mxBtnUnselectSingle(mxBuilder->weld_button("unselect_current"))
447  , mxButtonBox(mxBuilder->weld_box("buttonbox"))
448  , mxBtnOk(mxBuilder->weld_button("ok"))
449  , mxBtnCancel(mxBuilder->weld_button("cancel"))
450  , mxDropDown(mxMenu->create_virtual_device())
451  , mnCheckWidthReq(-1)
452  , mnWndWidth(0)
453  , mePrevToggleAllState(TRISTATE_INDET)
454  , mnSelectedMenu(MENU_NOT_SELECTED)
455  , mpDoc(pDoc)
456  , mnAsyncPostPopdownId(nullptr)
457  , mbHasDates(bHasDates)
458  , mbCanHaveSubMenu(bCanHaveSubMenu)
459  , maOpenTimer(this)
460  , maCloseTimer(this)
461 {
462  mxTreeChecks->set_clicks_to_toggle(1);
463  mxListChecks->set_clicks_to_toggle(1);
464  mxMenu->hide(); // show only when has items
465 
466  /*
467  tdf#136559 If we have no dates we don't need a tree
468  structure, just a list. GtkListStore can be then
469  used which is much faster than a GtkTreeStore, so
470  with no dates switch to the treeview which uses the
471  faster GtkListStore
472  */
473  if (mbHasDates)
474  mpChecks = mxTreeChecks.get();
475  else
476  {
477  mxTreeChecks->hide();
478  mxListChecks->show();
479  mpChecks = mxListChecks.get();
480  }
481 
482  bool bIsSubMenu = pParent->GetParentMenu();
483 
484  int nChecksHeight = mxTreeChecks->get_height_rows(9);
485  if (!bIsSubMenu && nWidth != -1)
486  {
487  mnCheckWidthReq = nWidth - mxFrame->get_border_width() * 2 - 4;
488  mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
489  mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
490  }
491 
492  // sort ok/cancel into native order, if this was a dialog they would be auto-sorted, but this
493  // popup isn't a true dialog
494  mxButtonBox->sort_native_button_order();
495 
496  if (!bIsSubMenu)
497  {
498  mxTreeChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
499  mxListChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
500 
501  mxBox->show();
502  mxEdSearch->show();
503  mxButtonBox->show();
504  }
505 
506  mxContainer->connect_focus_in(LINK(this, ScCheckListMenuControl, FocusHdl));
507  mxMenu->connect_row_activated(LINK(this, ScCheckListMenuControl, RowActivatedHdl));
508  mxMenu->connect_changed(LINK(this, ScCheckListMenuControl, SelectHdl));
509  mxMenu->connect_key_press(LINK(this, ScCheckListMenuControl, MenuKeyInputHdl));
510 
511  if (!bIsSubMenu)
512  {
513  mxBtnOk->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
514  mxBtnCancel->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
515  mxEdSearch->connect_changed(LINK(this, ScCheckListMenuControl, EdModifyHdl));
516  mxEdSearch->connect_activate(LINK(this, ScCheckListMenuControl, EdActivateHdl));
517  mxTreeChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
518  mxTreeChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
519  mxListChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
520  mxListChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
521  mxChkToggleAll->connect_toggled(LINK(this, ScCheckListMenuControl, TriStateHdl));
522  mxBtnSelectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
523  mxBtnUnselectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
524  }
525 
526  if (mbCanHaveSubMenu)
527  {
528  CreateDropDown();
529  mxMenu->connect_size_allocate(LINK(this, ScCheckListMenuControl, TreeSizeAllocHdl));
530  }
531 
532  if (!bIsSubMenu)
533  {
534  // determine what width the checklist will end up with
535  mnCheckWidthReq = mxContainer->get_preferred_size().Width();
536  // make that size fixed now, we can now use mnCheckWidthReq to speed up
537  // bulk_insert_for_each
538  mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
539  mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
540  }
541 }
542 
544 {
545  GrabFocus();
546 }
547 
549 {
550  if (mxEdSearch->get_visible())
551  mxEdSearch->grab_focus();
552  else
553  {
554  mxMenu->set_cursor(0);
555  mxMenu->grab_focus();
556  }
557 }
558 
560 {
561  EndPopupMode();
562  for (auto& rMenuItem : maMenuItems)
563  rMenuItem.mxSubMenuWin.disposeAndClear();
565  {
567  mnAsyncPostPopdownId = nullptr;
568  }
569 }
570 
572  bool bTreeMode, int nWidth, ScCheckListMenuWindow* pParentMenu,
573  vcl::ILibreOfficeKitNotifier* pNotifier)
574  : DropdownDockingWindow(pParent)
575  , mxParentMenu(pParentMenu)
576 {
577  if (pNotifier)
578  SetLOKNotifier(pNotifier);
580  mxControl.reset(new ScCheckListMenuControl(this, m_xBox.get(), pDoc, bCanHaveSubMenu, bTreeMode, nWidth));
581  SetBackground(Application::GetSettings().GetStyleSettings().GetMenuColor());
582  set_id("check_list_menu");
583 }
584 
586 {
587  if (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE)
588  {
589  ScCheckListMenuControl& rMenuControl = get_widget();
590  rMenuControl.queueCloseSubMenu();
591  rMenuControl.clearSelectedMenuItem();
592  }
594 }
595 
597 {
598  disposeOnce();
599 }
600 
602 {
603  mxControl.reset();
606 }
607 
609 {
611  if (!mxControl)
612  return;
613  mxControl->GrabFocus();
614 }
615 
617 {
618  mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height() + 2);
619  mnSelectedMenu = 0;
620  mxMenu->set_cursor(mnSelectedMenu);
621  mxMenu->unselect_all();
622 
623  mnWndWidth = mxContainer->get_preferred_size().Width() + mxFrame->get_border_width() * 2 + 4;
624 }
625 
627 {
628  mpChecks->all_foreach([this, bSet](weld::TreeIter& rEntry){
629  mpChecks->set_toggle(rEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
630  return false;
631  });
632 
634  {
635  // We need to have at least one member selected.
636  mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
637  }
638 }
639 
641 {
642  setAllMemberState(!bSet);
643  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
644  if (!mpChecks->get_cursor(xEntry.get()))
645  return;
646  mpChecks->set_toggle(*xEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
647 }
648 
650 {
651  if (&rBtn == mxBtnOk.get())
652  close(true);
653  else if (&rBtn == mxBtnCancel.get())
654  close(false);
655  else if (&rBtn == mxBtnSelectSingle.get() || &rBtn == mxBtnUnselectSingle.get())
656  {
657  selectCurrentMemberOnly(&rBtn == mxBtnSelectSingle.get());
658  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
659  if (!mpChecks->get_cursor(xEntry.get()))
660  xEntry.reset();
661  Check(xEntry.get());
662  }
663 }
664 
666 {
667  switch (mePrevToggleAllState)
668  {
669  case TRISTATE_FALSE:
670  mxChkToggleAll->set_state(TRISTATE_TRUE);
671  setAllMemberState(true);
672  break;
673  case TRISTATE_TRUE:
674  mxChkToggleAll->set_state(TRISTATE_FALSE);
675  setAllMemberState(false);
676  break;
677  case TRISTATE_INDET:
678  default:
679  mxChkToggleAll->set_state(TRISTATE_TRUE);
680  setAllMemberState(true);
681  break;
682  }
683 
684  mePrevToggleAllState = mxChkToggleAll->get_state();
685 }
686 
687 namespace
688 {
689  void insertMember(weld::TreeView& rView, const weld::TreeIter& rIter, const ScCheckListMember& rMember, bool bChecked)
690  {
691  OUString aLabel = rMember.maName;
692  if (aLabel.isEmpty())
693  aLabel = ScResId(STR_EMPTYDATA);
694  rView.set_toggle(rIter, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
695  rView.set_text(rIter, aLabel, 0);
696  }
697 }
698 
700 {
701  OUString aSearchText = mxEdSearch->get_text();
702  aSearchText = ScGlobal::getCharClassPtr()->lowercase( aSearchText );
703  bool bSearchTextEmpty = aSearchText.isEmpty();
704  size_t n = maMembers.size();
705  size_t nSelCount = 0;
706 
707  mpChecks->freeze();
708 
709  // This branch is the general case, the other is an optimized variant of
710  // this one where we can take advantage of knowing we have no hierarchy
711  if (mbHasDates)
712  {
713  bool bSomeDateDeletes = false;
714 
715  for (size_t i = 0; i < n; ++i)
716  {
717  bool bIsDate = maMembers[i].mbDate;
718  bool bPartialMatch = false;
719 
720  OUString aLabelDisp = maMembers[i].maName;
721  if ( aLabelDisp.isEmpty() )
722  aLabelDisp = ScResId( STR_EMPTYDATA );
723 
724  if ( !bSearchTextEmpty )
725  {
726  if ( !bIsDate )
727  bPartialMatch = ( ScGlobal::getCharClassPtr()->lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
728  else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
729  bPartialMatch = (ScGlobal::getCharClassPtr()->lowercase( OUString(
730  maMembers[i].maRealName + maMembers[i].maDateParts[1] )).indexOf( aSearchText ) != -1);
731  else
732  continue;
733  }
734  else if ( bIsDate && maMembers[i].meDatePartType != ScCheckListMember::DAY )
735  continue;
736 
737  if ( bSearchTextEmpty )
738  {
739  auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, maMembers[i].mbVisible);
740  updateMemberParents(xLeaf.get(), i);
741  if ( maMembers[i].mbVisible )
742  ++nSelCount;
743  continue;
744  }
745 
746  if ( bPartialMatch )
747  {
748  auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i]);
749  updateMemberParents(xLeaf.get(), i);
750  ++nSelCount;
751  }
752  else
753  {
754  ShowCheckEntry(aLabelDisp, maMembers[i], false, false);
755  if( bIsDate )
756  bSomeDateDeletes = true;
757  }
758  }
759 
760  if ( bSomeDateDeletes )
761  {
762  for (size_t i = 0; i < n; ++i)
763  {
764  if (!maMembers[i].mbDate)
765  continue;
766  if (maMembers[i].meDatePartType != ScCheckListMember::DAY)
767  continue;
768  updateMemberParents(nullptr, i);
769  }
770  }
771  }
772  else
773  {
774  // when there are a lot of rows, it is cheaper to simply clear the tree and either
775  // re-initialise or just insert the filtered lines
776  mpChecks->clear();
777 
778  if (bSearchTextEmpty)
779  nSelCount = initMembers();
780  else
781  {
782  std::vector<size_t> aShownIndexes;
783 
784  for (size_t i = 0; i < n; ++i)
785  {
786  assert(!maMembers[i].mbDate);
787 
788  OUString aLabelDisp = maMembers[i].maName;
789  if ( aLabelDisp.isEmpty() )
790  aLabelDisp = ScResId( STR_EMPTYDATA );
791 
792  bool bPartialMatch = ScGlobal::getCharClassPtr()->lowercase( aLabelDisp ).indexOf( aSearchText ) != -1;
793 
794  if (!bPartialMatch)
795  continue;
796 
797  aShownIndexes.push_back(i);
798  }
799 
800  std::vector<int> aFixedWidths { mnCheckWidthReq };
801  // tdf#122419 insert in the fastest order, this might be backwards.
802  mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes, &nSelCount](weld::TreeIter& rIter, int i) {
803  size_t nIndex = aShownIndexes[i];
804  insertMember(*mpChecks, rIter, maMembers[nIndex], true);
805  ++nSelCount;
806  }, nullptr, &aFixedWidths);
807  }
808  }
809 
810 
811  mpChecks->thaw();
812 
813  if ( nSelCount == n )
814  mxChkToggleAll->set_state( TRISTATE_TRUE );
815  else if ( nSelCount == 0 )
816  mxChkToggleAll->set_state( TRISTATE_FALSE );
817  else
818  mxChkToggleAll->set_state( TRISTATE_INDET );
819 
820  if ( !maConfig.mbAllowEmptySet )
821  {
822  const bool bEmptySet( nSelCount == 0 );
823  mpChecks->set_sensitive(!bEmptySet);
824  mxChkToggleAll->set_sensitive(!bEmptySet);
825  mxBtnSelectSingle->set_sensitive(!bEmptySet);
826  mxBtnUnselectSingle->set_sensitive(!bEmptySet);
827  mxBtnOk->set_sensitive(!bEmptySet);
828  }
829 }
830 
832 {
833  if (mxBtnOk->get_sensitive())
834  close(true);
835  return true;
836 }
837 
838 IMPL_LINK( ScCheckListMenuControl, CheckHdl, const weld::TreeView::iter_col&, rRowCol, void )
839 {
840  Check(&rRowCol.first);
841 }
842 
844 {
845  if (pEntry)
846  CheckEntry(*pEntry, mpChecks->get_toggle(*pEntry) == TRISTATE_TRUE);
847  size_t nNumChecked = GetCheckedEntryCount();
848  if (nNumChecked == maMembers.size())
849  // all members visible
850  mxChkToggleAll->set_state(TRISTATE_TRUE);
851  else if (nNumChecked == 0)
852  // no members visible
853  mxChkToggleAll->set_state(TRISTATE_FALSE);
854  else
855  mxChkToggleAll->set_state(TRISTATE_INDET);
856 
858  // We need to have at least one member selected.
859  mxBtnOk->set_sensitive(nNumChecked != 0);
860 
861  mePrevToggleAllState = mxChkToggleAll->get_state();
862 }
863 
865 {
866  if ( !maMembers[nIdx].mbDate || maMembers[nIdx].meDatePartType != ScCheckListMember::DAY )
867  return;
868 
869  OUString aYearName = maMembers[nIdx].maDateParts[0];
870  OUString aMonthName = maMembers[nIdx].maDateParts[1];
871  auto aItr = maYearMonthMap.find(aYearName + aMonthName);
872 
873  if ( pLeaf )
874  {
875  std::unique_ptr<weld::TreeIter> xYearEntry;
876  std::unique_ptr<weld::TreeIter> xMonthEntry = mpChecks->make_iterator(pLeaf);
877  if (!mpChecks->iter_parent(*xMonthEntry))
878  xMonthEntry.reset();
879  else
880  {
881  xYearEntry = mpChecks->make_iterator(xMonthEntry.get());
882  if (!mpChecks->iter_parent(*xYearEntry))
883  xYearEntry.reset();
884  }
885 
886  maMembers[nIdx].mxParent = std::move(xMonthEntry);
887  if ( aItr != maYearMonthMap.end() )
888  {
889  size_t nMonthIdx = aItr->second;
890  maMembers[nMonthIdx].mxParent = std::move(xYearEntry);
891  }
892  }
893  else
894  {
895  std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
896  if (aItr != maYearMonthMap.end() && !xYearEntry)
897  {
898  size_t nMonthIdx = aItr->second;
899  maMembers[nMonthIdx].mxParent.reset();
900  maMembers[nIdx].mxParent.reset();
901  }
902  else if (xYearEntry && !FindEntry(xYearEntry.get(), aMonthName))
903  maMembers[nIdx].mxParent.reset();
904  }
905 }
906 
908 {
909  maMembers.reserve(n);
910 }
911 
912 void ScCheckListMenuControl::addDateMember(const OUString& rsName, double nVal, bool bVisible)
913 {
914  SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
915 
916  // Convert the numeric date value to a date object.
917  Date aDate = pFormatter->GetNullDate();
918  aDate.AddDays(rtl::math::approxFloor(nVal));
919 
920  sal_Int16 nYear = aDate.GetYear();
921  sal_uInt16 nMonth = aDate.GetMonth();
922  sal_uInt16 nDay = aDate.GetDay();
923 
924  // Get the localized month name list.
925  CalendarWrapper* pCalendar = ScGlobal::GetCalendar();
926  uno::Sequence<i18n::CalendarItem2> aMonths = pCalendar->getMonths();
927  if (aMonths.getLength() < nMonth)
928  return;
929 
930  OUString aYearName = OUString::number(nYear);
931  OUString aMonthName = aMonths[nMonth-1].FullName;
932  OUString aDayName = OUString::number(nDay);
933 
934  if ( aDayName.getLength() == 1 )
935  aDayName = "0" + aDayName;
936 
937  mpChecks->freeze();
938 
939  std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
940  if (!xYearEntry)
941  {
942  xYearEntry = mpChecks->make_iterator();
943  mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
944  mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
945  mpChecks->set_text(*xYearEntry, aYearName, 0);
946  ScCheckListMember aMemYear;
947  aMemYear.maName = aYearName;
948  aMemYear.maRealName = rsName;
949  aMemYear.mbDate = true;
950  aMemYear.mbLeaf = false;
951  aMemYear.mbVisible = bVisible;
952  aMemYear.mxParent.reset();
954  maMembers.emplace_back(std::move(aMemYear));
955  }
956 
957  std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), aMonthName);
958  if (!xMonthEntry)
959  {
960  xMonthEntry = mpChecks->make_iterator();
961  mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
962  mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
963  mpChecks->set_text(*xMonthEntry, aMonthName, 0);
964  ScCheckListMember aMemMonth;
965  aMemMonth.maName = aMonthName;
966  aMemMonth.maRealName = rsName;
967  aMemMonth.mbDate = true;
968  aMemMonth.mbLeaf = false;
969  aMemMonth.mbVisible = bVisible;
970  aMemMonth.mxParent = std::move(xYearEntry);
972  maMembers.emplace_back(std::move(aMemMonth));
973  maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
974  }
975 
976  std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), aDayName);
977  if (!xDayEntry)
978  {
979  xDayEntry = mpChecks->make_iterator();
980  mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
981  mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
982  mpChecks->set_text(*xDayEntry, aDayName, 0);
983  ScCheckListMember aMemDay;
984  aMemDay.maName = aDayName;
985  aMemDay.maRealName = rsName;
986  aMemDay.maDateParts.resize(2);
987  aMemDay.maDateParts[0] = aYearName;
988  aMemDay.maDateParts[1] = aMonthName;
989  aMemDay.mbDate = true;
990  aMemDay.mbLeaf = true;
991  aMemDay.mbVisible = bVisible;
992  aMemDay.mxParent = std::move(xMonthEntry);
994  maMembers.emplace_back(std::move(aMemDay));
995  }
996 
997  mpChecks->thaw();
998 }
999 
1000 void ScCheckListMenuControl::addMember(const OUString& rName, const double nVal, bool bVisible, bool bValue, bool bDuplicated)
1001 {
1002  ScCheckListMember aMember;
1003  // tdf#46062 - indicate hidden whitespaces using quotes
1004  aMember.maName = rName.trim() != rName ? "\"" + rName + "\"" : rName;
1005  aMember.maRealName = rName;
1006  aMember.mnValue = nVal;
1007  aMember.mbDate = false;
1008  aMember.mbLeaf = true;
1009  aMember.mbValue = bValue;
1010  aMember.mbDuplicated = bDuplicated;
1011  aMember.mbVisible = bVisible;
1012  aMember.mxParent.reset();
1013  maMembers.emplace_back(std::move(aMember));
1014 }
1015 
1016 std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::FindEntry(const weld::TreeIter* pParent, std::u16string_view sNode)
1017 {
1018  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(pParent);
1019  bool bEntry = pParent ? mpChecks->iter_children(*xEntry) : mpChecks->get_iter_first(*xEntry);
1020  while (bEntry)
1021  {
1022  if (sNode == mpChecks->get_text(*xEntry, 0))
1023  return xEntry;
1024  bEntry = mpChecks->iter_next_sibling(*xEntry);
1025  }
1026  return nullptr;
1027 }
1028 
1029 void ScCheckListMenuControl::GetRecursiveChecked(const weld::TreeIter* pEntry, std::unordered_set<OUString>& vOut,
1030  OUString& rLabel)
1031 {
1032  if (mpChecks->get_toggle(*pEntry) != TRISTATE_TRUE)
1033  return;
1034 
1035  // We have to hash parents and children together.
1036  // Per convention for easy access in getResult()
1037  // "child;parent;grandparent" while descending.
1038  if (rLabel.isEmpty())
1039  rLabel = mpChecks->get_text(*pEntry, 0);
1040  else
1041  rLabel = mpChecks->get_text(*pEntry, 0) + ";" + rLabel;
1042 
1043  // Prerequisite: the selection mechanism guarantees that if a child is
1044  // selected then also the parent is selected, so we only have to
1045  // inspect the children in case the parent is selected.
1046  if (!mpChecks->iter_has_child(*pEntry))
1047  return;
1048 
1049  std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(pEntry));
1050  bool bChild = mpChecks->iter_children(*xChild);
1051  while (bChild)
1052  {
1053  OUString aLabel = rLabel;
1054  GetRecursiveChecked(xChild.get(), vOut, aLabel);
1055  if (!aLabel.isEmpty() && aLabel != rLabel)
1056  vOut.insert(aLabel);
1057  bChild = mpChecks->iter_next_sibling(*xChild);
1058  }
1059  // Let the caller not add the parent alone.
1060  rLabel.clear();
1061 }
1062 
1063 std::unordered_set<OUString> ScCheckListMenuControl::GetAllChecked()
1064 {
1065  std::unordered_set<OUString> vResults(0);
1066 
1067  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1068  bool bEntry = mpChecks->get_iter_first(*xEntry);
1069  while (bEntry)
1070  {
1071  OUString aLabel;
1072  GetRecursiveChecked(xEntry.get(), vResults, aLabel);
1073  if (!aLabel.isEmpty())
1074  vResults.insert(aLabel);
1075  bEntry = mpChecks->iter_next_sibling(*xEntry);
1076  }
1077 
1078  return vResults;
1079 }
1080 
1081 bool ScCheckListMenuControl::IsChecked(std::u16string_view sName, const weld::TreeIter* pParent)
1082 {
1083  std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
1084  return xEntry && mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1085 }
1086 
1087 void ScCheckListMenuControl::CheckEntry(std::u16string_view sName, const weld::TreeIter* pParent, bool bCheck)
1088 {
1089  std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
1090  if (xEntry)
1091  CheckEntry(*xEntry, bCheck);
1092 }
1093 
1094 // Recursively check all children of rParent
1096 {
1097  mpChecks->set_toggle(rParent, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
1098  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(&rParent);
1099  bool bEntry = mpChecks->iter_children(*xEntry);
1100  while (bEntry)
1101  {
1102  CheckAllChildren(*xEntry, bCheck);
1103  bEntry = mpChecks->iter_next_sibling(*xEntry);
1104  }
1105 }
1106 
1107 void ScCheckListMenuControl::CheckEntry(const weld::TreeIter& rParent, bool bCheck)
1108 {
1109  // recursively check all items below rParent
1110  CheckAllChildren(rParent, bCheck);
1111  // checking rParent can affect ancestors, e.g. if ancestor is unchecked and rParent is
1112  // now checked then the ancestor needs to be checked also
1113  if (!mpChecks->get_iter_depth(rParent))
1114  return;
1115 
1116  std::unique_ptr<weld::TreeIter> xAncestor(mpChecks->make_iterator(&rParent));
1117  bool bAncestor = mpChecks->iter_parent(*xAncestor);
1118  while (bAncestor)
1119  {
1120  // if any first level children checked then ancestor
1121  // needs to be checked, similarly if no first level children
1122  // checked then ancestor needs to be unchecked
1123  std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(xAncestor.get()));
1124  bool bChild = mpChecks->iter_children(*xChild);
1125  bool bChildChecked = false;
1126 
1127  while (bChild)
1128  {
1129  if (mpChecks->get_toggle(*xChild) == TRISTATE_TRUE)
1130  {
1131  bChildChecked = true;
1132  break;
1133  }
1134  bChild = mpChecks->iter_next_sibling(*xChild);
1135  }
1136  mpChecks->set_toggle(*xAncestor, bChildChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
1137  bAncestor = mpChecks->iter_parent(*xAncestor);
1138  }
1139 }
1140 
1141 std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::ShowCheckEntry(const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck)
1142 {
1143  std::unique_ptr<weld::TreeIter> xEntry;
1144  if (!rMember.mbDate || rMember.mxParent)
1145  xEntry = FindEntry(rMember.mxParent.get(), sName);
1146 
1147  if ( bShow )
1148  {
1149  if (!xEntry)
1150  {
1151  if (rMember.mbDate)
1152  {
1153  if (rMember.maDateParts.empty())
1154  return nullptr;
1155 
1156  std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, rMember.maDateParts[0]);
1157  if (!xYearEntry)
1158  {
1159  xYearEntry = mpChecks->make_iterator();
1160  mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
1161  mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
1162  mpChecks->set_text(*xYearEntry, rMember.maDateParts[0], 0);
1163  }
1164  std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), rMember.maDateParts[1]);
1165  if (!xMonthEntry)
1166  {
1167  xMonthEntry = mpChecks->make_iterator();
1168  mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
1169  mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
1170  mpChecks->set_text(*xMonthEntry, rMember.maDateParts[1], 0);
1171  }
1172  std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), rMember.maName);
1173  if (!xDayEntry)
1174  {
1175  xDayEntry = mpChecks->make_iterator();
1176  mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
1177  mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
1178  mpChecks->set_text(*xDayEntry, rMember.maName, 0);
1179  }
1180  return xDayEntry; // Return leaf node
1181  }
1182 
1183  xEntry = mpChecks->make_iterator();
1184  mpChecks->append(xEntry.get());
1185  mpChecks->set_toggle(*xEntry, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
1186  mpChecks->set_text(*xEntry, sName, 0);
1187  }
1188  else
1189  CheckEntry(*xEntry, bCheck);
1190  }
1191  else if (xEntry)
1192  {
1193  mpChecks->remove(*xEntry);
1194  if (rMember.mxParent)
1195  {
1196  std::unique_ptr<weld::TreeIter> xParent(mpChecks->make_iterator(rMember.mxParent.get()));
1197  while (xParent && !mpChecks->iter_has_child(*xParent))
1198  {
1199  std::unique_ptr<weld::TreeIter> xTmp(mpChecks->make_iterator(xParent.get()));
1200  if (!mpChecks->iter_parent(*xParent))
1201  xParent.reset();
1202  mpChecks->remove(*xTmp);
1203  }
1204  }
1205  }
1206  return nullptr;
1207 }
1208 
1210 {
1211  int nRet = 0;
1212 
1213  mpChecks->all_foreach([this, &nRet](weld::TreeIter& rEntry){
1214  if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
1215  ++nRet;
1216  return false;
1217  });
1218 
1219  return nRet;
1220 }
1221 
1222 IMPL_LINK(ScCheckListMenuControl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
1223 {
1224  const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
1225 
1226  if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
1227  {
1228  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1229  bool bEntry = mpChecks->get_cursor(xEntry.get());
1230  if (bEntry)
1231  {
1232  bool bOldCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1233  CheckEntry(*xEntry, !bOldCheck);
1234  bool bNewCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1235  if (bOldCheck != bNewCheck)
1236  Check(xEntry.get());
1237  }
1238  return true;
1239  }
1240 
1241  return false;
1242 }
1243 
1244 size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth)
1245 {
1246  size_t n = maMembers.size();
1247  size_t nVisMemCount = 0;
1248 
1249  if (nMaxMemberWidth == -1)
1250  nMaxMemberWidth = mnCheckWidthReq;
1251 
1252  if (!mpChecks->n_children() && !mbHasDates)
1253  {
1254  std::vector<int> aFixedWidths { nMaxMemberWidth };
1255  // tdf#134038 insert in the fastest order, this might be backwards so only do it for
1256  // the !mbHasDates case where no entry depends on another to exist before getting
1257  // inserted. We cannot retain pre-existing treeview content, only clear and fill it.
1258  mpChecks->bulk_insert_for_each(n, [this, &nVisMemCount](weld::TreeIter& rIter, int i) {
1259  assert(!maMembers[i].mbDate);
1260  insertMember(*mpChecks, rIter, maMembers[i], maMembers[i].mbVisible);
1261  if (maMembers[i].mbVisible)
1262  ++nVisMemCount;
1263  }, nullptr, &aFixedWidths);
1264  }
1265  else
1266  {
1267  mpChecks->freeze();
1268 
1269  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1270  std::vector<std::unique_ptr<weld::TreeIter>> aExpandRows;
1271 
1272  for (size_t i = 0; i < n; ++i)
1273  {
1274  if (maMembers[i].mbDate)
1275  {
1276  CheckEntry(maMembers[i].maName, maMembers[i].mxParent.get(), maMembers[i].mbVisible);
1277  // Expand first node of checked dates
1278  if (!maMembers[i].mxParent && IsChecked(maMembers[i].maName, maMembers[i].mxParent.get()))
1279  {
1280  std::unique_ptr<weld::TreeIter> xDateEntry = FindEntry(nullptr, maMembers[i].maName);
1281  if (xDateEntry)
1282  aExpandRows.emplace_back(std::move(xDateEntry));
1283  }
1284  }
1285  else
1286  {
1287  mpChecks->append(xEntry.get());
1288  insertMember(*mpChecks, *xEntry, maMembers[i], maMembers[i].mbVisible);
1289  }
1290 
1291  if (maMembers[i].mbVisible)
1292  ++nVisMemCount;
1293  }
1294 
1295  mpChecks->thaw();
1296 
1297  for (auto& rRow : aExpandRows)
1298  mpChecks->expand_row(*rRow);
1299  }
1300 
1301  if (nVisMemCount == n)
1302  {
1303  // all members visible
1304  mxChkToggleAll->set_state(TRISTATE_TRUE);
1306  }
1307  else if (nVisMemCount == 0)
1308  {
1309  // no members visible
1310  mxChkToggleAll->set_state(TRISTATE_FALSE);
1312  }
1313  else
1314  {
1315  mxChkToggleAll->set_state(TRISTATE_INDET);
1317  }
1318 
1319  if (nVisMemCount)
1320  mpChecks->select(0);
1321 
1322  return nVisMemCount;
1323 }
1324 
1326 {
1327  maConfig = rConfig;
1328 }
1329 
1331 {
1332  return mxChkToggleAll->get_state() == TRISTATE_TRUE;
1333 }
1334 
1336 {
1337  ResultType aResult;
1338  std::unordered_set<OUString> vCheckeds = GetAllChecked();
1339  size_t n = maMembers.size();
1340  for (size_t i = 0; i < n; ++i)
1341  {
1342  if ( maMembers[i].mbLeaf )
1343  {
1344  OUStringBuffer aLabel = maMembers[i].maName;
1345  if (aLabel.isEmpty())
1346  aLabel = ScResId(STR_EMPTYDATA);
1347 
1348  /* TODO: performance-wise this looks suspicious, concatenating to
1349  * do the lookup for each leaf item seems wasteful. */
1350  // Checked labels are in the form "child;parent;grandparent".
1351  if (maMembers[i].mxParent)
1352  {
1353  std::unique_ptr<weld::TreeIter> xIter(mpChecks->make_iterator(maMembers[i].mxParent.get()));
1354  do
1355  {
1356  aLabel.append(";" + mpChecks->get_text(*xIter));
1357  }
1358  while (mpChecks->iter_parent(*xIter));
1359  }
1360 
1361  bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
1362 
1363  ResultEntry aResultEntry;
1364  aResultEntry.bValid = bState;
1365  aResultEntry.aName = maMembers[i].maRealName;
1366  aResultEntry.nValue = maMembers[i].mnValue;
1367  aResultEntry.bDate = maMembers[i].mbDate;
1368  aResultEntry.bValue = maMembers[i].mbValue;
1369  aResultEntry.bDuplicated = maMembers[i].mbDuplicated;
1370  aResult.insert(aResultEntry);
1371  }
1372  }
1373  rResult.swap(aResult);
1374 }
1375 
1377 {
1378  prepWindow();
1380  // We need to have at least one member selected.
1381  mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
1382 
1383  tools::Rectangle aRect(rRect);
1384  if (maConfig.mbRTL)
1385  {
1386  // In RTL mode, the logical "left" is visual "right".
1387  tools::Long nLeft = aRect.Left() - aRect.GetWidth();
1388  aRect.SetLeft( nLeft );
1389  }
1390  else if (mnWndWidth < aRect.GetWidth())
1391  {
1392  // Target rectangle (i.e. cell width) is wider than the window.
1393  // Simulate right-aligned launch by modifying the target rectangle
1394  // size.
1395  tools::Long nDiff = aRect.GetWidth() - mnWndWidth;
1396  aRect.AdjustLeft(nDiff );
1397  }
1398 
1399  StartPopupMode(aRect, FloatWinPopupFlags::Down);
1400 }
1401 
1403 {
1405  if (!aNotifierWindow)
1406  return;
1407 
1408  const vcl::ILibreOfficeKitNotifier* pNotifier = aNotifierWindow->GetLOKNotifier();
1409  if (pNotifier)
1410  {
1411  tools::JsonWriter aJsonWriter;
1412  aJsonWriter.put("jsontype", "autofilter");
1413  aJsonWriter.put("action", "close");
1414 
1415  const std::string message = aJsonWriter.extractAsStdString();
1416  pNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str());
1417  }
1418 }
1419 
1421 {
1422  if (bOK && mxOKAction)
1423  mxOKAction->execute();
1424  EndPopupMode();
1425 
1427  NotifyCloseLOK();
1428 }
1429 
1430 void ScCheckListMenuControl::setExtendedData(std::unique_ptr<ExtendedData> p)
1431 {
1432  mxExtendedData = std::move(p);
1433 }
1434 
1436 {
1437  return mxExtendedData.get();
1438 }
1439 
1441 {
1442  mxOKAction.reset(p);
1443 }
1444 
1446 {
1447  mxPopupEndAction.reset(p);
1448 }
1449 
1451 {
1452  clearSelectedMenuItem();
1453  if (mxPopupEndAction)
1454  mxPopupEndAction->execute();
1455 
1457  NotifyCloseLOK();
1458 }
1459 
1460 int ScCheckListMenuControl::GetTextWidth(const OUString& rsName) const
1461 {
1462  return mxDropDown->GetTextWidth(rsName);
1463 }
1464 
1466 {
1467  int nBorder = mxFrame->get_border_width() * 2 + 4;
1468  int nNewWidth = nMaxTextWidth - nBorder;
1469  if (nNewWidth > mnCheckWidthReq)
1470  {
1471  mnCheckWidthReq = nNewWidth;
1472  int nChecksHeight = mpChecks->get_height_rows(9);
1473  mpChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
1474  }
1475  return mnCheckWidthReq + nBorder;
1476 }
1477 
1478 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
virtual void set_text(int row, const OUString &rText, int col=-1)=0
IMPL_LINK_NOARG(ScCheckListMenuControl::SubMenuItemData, TimeoutHdl, Timer *, void)
void SetBackground()
std::set< ResultEntry > ResultType
void setDeferredProperties()
vcl::LOKWindowId GetLOKWindowId() const
sal_Int32 nIndex
std::unique_ptr< weld::Button > mxBtnUnselectSingle
VclPtr< ScCheckListMenuWindow > mxParentMenu
void updateMemberParents(const weld::TreeIter *pLeaf, size_t nIdx)
void setOKAction(Action *p)
std::vector< MenuItemData > maMenuItems
void prepWindow()
Calculate the appropriate window size based on the menu items.
virtual bool iter_parent(TreeIter &rIter) const =0
std::unique_ptr< weld::TreeIter > mxParent
std::unique_ptr< weld::Button > mxBtnSelectSingle
virtual void insert(const TreeIter *pParent, int pos, const OUString *pStr, const OUString *pId, const OUString *pIconName, VirtualDevice *pImageSurface, bool bChildrenOnDemand, TreeIter *pRet)=0
void executeMenuItem(size_t nPos)
int mnWndWidth
matching width request for mxChecks
void CheckEntry(std::u16string_view sName, const weld::TreeIter *pParent, bool bCheck)
long Long
WeakReference< XInterface > mxParent
static const AllSettings & GetSettings()
sal_Int64 n
VclPtr< ScCheckListMenuWindow > mxFrame
void SetPopupModeEndHdl(const vcl::Window *pWindow, const Link< FloatingWindow *, void > &rLink)
Reference< container::XNameAccess > mxContainer
void SetLOKNotifier(const vcl::ILibreOfficeKitNotifier *pNotifier, bool bParent=false)
static ImplSVEvent * PostUserEvent(const Link< void *, void > &rLink, void *pCaller=nullptr, bool bReferenceLink=false)
std::unique_ptr< weld::TreeView > mxMenu
virtual ~ScCheckListMenuWindow() override
sal_uInt16 GetCode() const
ImplSVEvent * mnAsyncPostPopdownId
void queueLaunchSubMenu(size_t nPos, ScCheckListMenuWindow *pMenu)
std::unordered_set< OUString > GetAllChecked()
ScCheckListMenuWindow * GetParentMenu()
void selectMenuItem(size_t nPos, bool bSubMenuTimer)
constexpr sal_uInt16 KEY_SPACE
FloatWinPopupFlags
void StartPopupMode(const tools::Rectangle &rRect, FloatWinPopupFlags eFlags)
std::unique_ptr< weld::TreeIter > FindEntry(const weld::TreeIter *pParent, std::u16string_view sNode)
TRISTATE_TRUE
VCL_DLLPUBLIC void SendFullUpdate(sal_uInt64 nWindowId, const OString &rWidget)
TriState mePrevToggleAllState
whole window width.
void CheckAllChildren(const weld::TreeIter &rEntry, bool bCheck)
virtual TriState get_toggle(int row, int col=-1) const =0
virtual std::unique_ptr< TreeIter > make_iterator(const TreeIter *pOrig=nullptr) const =0
virtual void thaw()=0
IMPL_LINK(ScCheckListMenuControl, MenuKeyInputHdl, const KeyEvent &, rKEvt, bool)
void setPopupEndAction(Action *p)
Action to perform when an event takes place.
Configuration options for this popup window.
std::unique_ptr< weld::TreeIter > mxScratchIter
ScCheckListMenuControl(ScCheckListMenuWindow *pParent, vcl::Window *pContainer, ScDocument *pDoc, bool bCanHaveSubMenu, bool bTreeMode, int nWidth)
tools::Long Left() const
VclPtr< vcl::Window > GetParentWithLOKNotifier()
void SetLeft(tools::Long v)
const BorderLinePrimitive2D *pCandidateB assert(pCandidateA)
sal_uInt16 GetMonth() const
std::unique_ptr< weld::TreeIter > ShowCheckEntry(const OUString &sName, ScCheckListMember &rMember, bool bShow=true, bool bCheck=true)
std::pair< const TreeIter &, int > iter_col
virtual int n_children() const =0
VclPtr< ScCheckListMenuWindow > mpSubMenu
static SfxViewShell * Current()
constexpr tools::Long GetWidth() const
void setAllMemberState(bool bSet)
static constexpr size_t MENU_NOT_SELECTED
TRISTATE_INDET
void addMember(const OUString &rName, const double nVal, bool bVisible, bool bValue=false, bool bDuplicated=false)
bool IsChecked(std::u16string_view sName, const weld::TreeIter *pParent)
void addMenuItem(const OUString &rText, Action *pAction)
sal_Int16 GetYear() const
void handleMenuTimeout(const SubMenuItemData *pTimer)
static void RemoveUserEvent(ImplSVEvent *nUserEvent)
SubMenuItemData maOpenTimer
std::unique_ptr< Action > mxOKAction
SC_DLLPUBLIC SvNumberFormatter * GetFormatTable() const
Definition: documen2.cxx:440
void clear()
void getResult(ResultType &rResult)
int IncreaseWindowWidthToFitText(int nMaxTextWidth)
std::vector< OUString > maDateParts
std::string extractAsStdString()
void setMemberSize(size_t n)
std::unique_ptr< weld::TreeView > mxTreeChecks
int i
VclPtr< ScCheckListMenuWindow > mxSubMenuWin
virtual void set_toggle(int row, TriState eState, int col=-1)=0
VclPtr< vcl::Window > m_xBox
int GetCheckedEntryCount() const
std::shared_ptr< Action > mxAction
virtual void freeze()=0
std::unique_ptr< weld::CheckButton > mxChkToggleAll
std::vector< ScCheckListMember > maMembers
void setSubMenuFocused(const ScCheckListMenuControl *pSubMenu)
TRISTATE_FALSE
std::map< OUString, size_t > maYearMonthMap
virtual bool iter_has_child(const TreeIter &rIter) const =0
std::unique_ptr< weld::Box > mxButtonBox
std::unique_ptr< weld::Button > mxBtnOk
size_t initMembers(int nMaxMemberWidth=-1)
OUString ScResId(const char *pId)
Definition: scdll.cxx:89
virtual void GetFocus() override
virtual void Start() override
ExtendedData * getExtendedData()
Get the store auxiliary data, or NULL if no such data is stored.
sal_uInt16 GetDay() const
void setExtendedData(std::unique_ptr< ExtendedData > p)
Set auxiliary data that the client code might need.
MouseNotifyEvent GetType() const
void AddDays(sal_Int32 nAddDays)
virtual OUString get_text(int row, int col=-1) const =0
virtual void dispose() override
ScCheckListMenuWindow * addSubMenuItem(const OUString &rText, bool bEnabled)
void endSubMenu(ScCheckListMenuControl &rSubMenu)
void setConfig(const Config &rConfig)
OUString lowercase(const OUString &rStr, sal_Int32 nPos, sal_Int32 nCount) const
virtual void expand_row(const TreeIter &rIter)=0
void SetTimeout(sal_uInt64 nTimeoutMs)
DatePartType meDatePartType
virtual bool get_cursor(TreeIter *pIter) const =0
void launchSubMenu(bool bSetMenuPos)
void put(const char *pPropName, const OUString &rPropValue)
std::unique_ptr< ExtendedData > mxExtendedData
constexpr sal_uInt16 KEY_RETURN
SubMenuItemData(ScCheckListMenuControl *pParent)
vcl::Window * GetWindow(GetWindowType nType) const
bool close
constexpr sal_uInt16 KEY_RIGHT
virtual bool iter_children(TreeIter &rIter) const =0
virtual void remove(int pos)=0
virtual bool EventNotify(NotifyEvent &rNEvt) override
void reset(reference_type *pBody)
void launch(const tools::Rectangle &rRect)
tools::Long const nBorder
RegionData_Impl * mpParent
void set_id(const OUString &rID)
ScopedVclPtr< VirtualDevice > mxDropDown
SubMenuItemData maCloseTimer
void EnableDocking(bool bEnable=true)
virtual void select(int pos)=0
virtual void all_foreach(const std::function< bool(TreeIter &)> &func)=0
ScCheckListMenuWindow(vcl::Window *pParent, ScDocument *pDoc, bool bCanHaveSubMenu, bool bTreeMode, int nWidth=-1, ScCheckListMenuWindow *pParentMenu=nullptr, vcl::ILibreOfficeKitNotifier *pNotifier=nullptr)
weld::TreeView * mpChecks
bool mbVisible
static CalendarWrapper * GetCalendar()
Definition: global.cxx:1018
void Stop()
int GetTextWidth(const OUString &rsName) const
virtual void GetFocus()
std::unique_ptr< weld::Widget > mxBox
virtual void dispose() override
void terminateAllPopupMenus()
Dismiss all visible popup menus and set focus back to the application window.
OUString maName
css::uno::Sequence< css::i18n::CalendarItem2 > getMonths() const
virtual bool get_iter_first(TreeIter &rIter) const =0
std::unique_ptr< weld::Container > mxContainer
virtual int get_height_rows(int nRows) const =0
static SC_DLLPUBLIC const CharClass * getCharClassPtr()
Definition: global.cxx:1009
OUString aLabel
void SetInvokeHandler(const Link< Timer *, void > &rLink)
void EndPopupMode(const vcl::Window *pWin)
ScCheckListMenuControl & get_widget()
std::unique_ptr< Action > mxPopupEndAction
void selectCurrentMemberOnly(bool bSet)
This class implements a popup window for the auto filter dropdown.
void append(TreeIter *pRet=nullptr)
void addDateMember(const OUString &rName, double nVal, bool bVisible)
VirtualDevice * get() const
void GetRecursiveChecked(const weld::TreeIter *pEntry, std::unordered_set< OUString > &vOut, OUString &rLabel)
void StartPopupMode(const vcl::Window *pWin, const tools::Rectangle &rRect, FloatWinPopupFlags nPopupModeFlags)
const vcl::ILibreOfficeKitNotifier * GetLOKNotifier() const
tools::Long AdjustLeft(tools::Long nHorzMoveDelta)
virtual bool iter_next_sibling(TreeIter &rIter) const =0
void DrawSymbol(const tools::Rectangle &rRect, SymbolType eType, const Color &rColor, DrawSymbolFlags nStyle=DrawSymbolFlags::NONE)
constexpr sal_uInt16 KEY_LEFT
std::unique_ptr< ScCheckListMenuControl, o3tl::default_delete< ScCheckListMenuControl > > mxControl
const Date & GetNullDate() const
virtual void set_size_request(int nWidth, int nHeight)=0
void setSelectedMenuItem(size_t nPos, bool bSubMenuTimer)
Extended data that the client code may need to store.
std::unique_ptr< weld::TreeView > mxListChecks
virtual void bulk_insert_for_each(int nSourceCount, const std::function< void(TreeIter &, int nSourceIndex)> &func, const weld::TreeIter *pParent=nullptr, const std::vector< int > *pFixedWidths=nullptr)=0
sal_Int32 get_border_width() const
void Check(const weld::TreeIter *pIter)
std::unique_ptr< weld::Button > mxBtnCancel
std::unique_ptr< weld::Entry > mxEdSearch
static DockingManager * GetDockingManager()
const double mnValue
virtual int get_iter_depth(const TreeIter &rIter) const =0
size_t getSubMenuPos(const ScCheckListMenuControl *pSubMenu)
virtual bool EventNotify(NotifyEvent &rNEvt) override