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