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->append_text(rText);
135  if (mbCanHaveSubMenu)
136  mxMenu->set_image(mxMenu->n_children() - 1, css::uno::Reference<css::graphic::XGraphic>(), 1);
137 }
138 
140 {
141  MenuItemData aItem;
142  maMenuItems.emplace_back(std::move(aItem));
143 
144  mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
145 }
146 
147 IMPL_LINK(ScCheckListMenuControl, TreeSizeAllocHdl, const Size&, rSize, void)
148 {
150  std::vector<int> aWidths;
151  aWidths.push_back(rSize.Width() - (mxMenu->get_text_height() * 3) / 4 - 6);
152  mxMenu->set_column_fixed_widths(aWidths);
153 }
154 
156 {
157  int nWidth = (mxMenu->get_text_height() * 3) / 4;
158  mxDropDown->SetOutputSizePixel(Size(nWidth, nWidth));
159  DecorationView aDecoView(mxDropDown.get());
160  aDecoView.DrawSymbol(tools::Rectangle(Point(0, 0), Size(nWidth, nWidth)),
161  SymbolType::SPIN_RIGHT, mxDropDown->GetTextColor(),
162  DrawSymbolFlags::NONE);
163 }
164 
165 ScCheckListMenuWindow* ScCheckListMenuControl::addSubMenuItem(const OUString& rText, bool bEnabled)
166 {
168 
169  MenuItemData aItem;
170  aItem.mbEnabled = bEnabled;
171  vcl::Window *pContainer = mxFrame->GetWindow(GetWindowType::FirstChild);
172 
173  vcl::ILibreOfficeKitNotifier* pNotifier = nullptr;
175  pNotifier = SfxViewShell::Current();
176 
178  false, -1, mxFrame.get(),
179  pNotifier));
180  maMenuItems.emplace_back(std::move(aItem));
181 
182  mxMenu->append_text(rText);
183  if (mbCanHaveSubMenu)
184  mxMenu->set_image(mxMenu->n_children() - 1, *mxDropDown, 1);
185 
186  return maMenuItems.back().mxSubMenuWin.get();
187 }
188 
190 {
191  if (nPos >= maMenuItems.size())
192  return;
193 
194  if (!maMenuItems[nPos].mxAction)
195  // no action is defined.
196  return;
197 
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  : mbVisible(true)
423  , mbDate(false)
424  , mbLeaf(false)
425  , meDatePartType(YEAR)
426 {
427 }
428 
430  ScDocument* pDoc, bool bCanHaveSubMenu,
431  bool bHasDates, int nWidth)
432  : mxFrame(pParent)
433  , mxBuilder(Application::CreateInterimBuilder(pContainer, "modules/scalc/ui/filterdropdown.ui", false))
434  , mxContainer(mxBuilder->weld_container("FilterDropDown"))
435  , mxMenu(mxBuilder->weld_tree_view("menu"))
436  , mxScratchIter(mxMenu->make_iterator())
437  , mxEdSearch(mxBuilder->weld_entry("search_edit"))
438  , mxBox(mxBuilder->weld_widget("box"))
439  , mxListChecks(mxBuilder->weld_tree_view("check_list_box"))
440  , mxTreeChecks(mxBuilder->weld_tree_view("check_tree_box"))
441  , mxChkToggleAll(mxBuilder->weld_check_button("toggle_all"))
442  , mxBtnSelectSingle(mxBuilder->weld_button("select_current"))
443  , mxBtnUnselectSingle(mxBuilder->weld_button("unselect_current"))
444  , mxButtonBox(mxBuilder->weld_box("buttonbox"))
445  , mxBtnOk(mxBuilder->weld_button("ok"))
446  , mxBtnCancel(mxBuilder->weld_button("cancel"))
447  , mxDropDown(mxMenu->create_virtual_device())
448  , mnCheckWidthReq(-1)
449  , mnWndWidth(0)
450  , mePrevToggleAllState(TRISTATE_INDET)
451  , mnSelectedMenu(MENU_NOT_SELECTED)
452  , mpDoc(pDoc)
453  , mnAsyncPostPopdownId(nullptr)
454  , mbHasDates(bHasDates)
455  , mbCanHaveSubMenu(bCanHaveSubMenu)
456  , maOpenTimer(this)
457  , maCloseTimer(this)
458 {
459  mxTreeChecks->set_clicks_to_toggle(1);
460  mxListChecks->set_clicks_to_toggle(1);
461 
462  /*
463  tdf#136559 If we have no dates we don't need a tree
464  structure, just a list. GtkListStore can be then
465  used which is much faster than a GtkTreeStore, so
466  with no dates switch to the treeview which uses the
467  faster GtkListStore
468  */
469  if (mbHasDates)
470  mpChecks = mxTreeChecks.get();
471  else
472  {
473  mxTreeChecks->hide();
474  mxListChecks->show();
475  mpChecks = mxListChecks.get();
476  }
477 
478  bool bIsSubMenu = pParent->GetParentMenu();
479 
480  int nChecksHeight = mxTreeChecks->get_height_rows(9);
481  if (!bIsSubMenu && nWidth != -1)
482  {
483  mnCheckWidthReq = nWidth - mxFrame->get_border_width() * 2 - 4;
484  mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
485  mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
486  }
487 
488  // sort ok/cancel into native order, if this was a dialog they would be auto-sorted, but this
489  // popup isn't a true dialog
490  mxButtonBox->sort_native_button_order();
491 
492  if (!bIsSubMenu)
493  {
494  mxTreeChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
495  mxListChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
496 
497  mxBox->show();
498  mxEdSearch->show();
499  mxButtonBox->show();
500  }
501 
502  mxContainer->connect_focus_in(LINK(this, ScCheckListMenuControl, FocusHdl));
503  mxMenu->connect_row_activated(LINK(this, ScCheckListMenuControl, RowActivatedHdl));
504  mxMenu->connect_changed(LINK(this, ScCheckListMenuControl, SelectHdl));
505  mxMenu->connect_key_press(LINK(this, ScCheckListMenuControl, MenuKeyInputHdl));
506 
507  if (!bIsSubMenu)
508  {
509  mxBtnOk->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
510  mxBtnCancel->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
511  mxEdSearch->connect_changed(LINK(this, ScCheckListMenuControl, EdModifyHdl));
512  mxEdSearch->connect_activate(LINK(this, ScCheckListMenuControl, EdActivateHdl));
513  mxTreeChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
514  mxTreeChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
515  mxListChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
516  mxListChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
517  mxChkToggleAll->connect_toggled(LINK(this, ScCheckListMenuControl, TriStateHdl));
518  mxBtnSelectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
519  mxBtnUnselectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
520  }
521 
522  if (mbCanHaveSubMenu)
523  {
524  CreateDropDown();
525  mxMenu->connect_size_allocate(LINK(this, ScCheckListMenuControl, TreeSizeAllocHdl));
526  }
527 
528  if (!bIsSubMenu)
529  {
530  // determine what width the checklist will end up with
531  mnCheckWidthReq = mxContainer->get_preferred_size().Width();
532  // make that size fixed now, we can now use mnCheckWidthReq to speed up
533  // bulk_insert_for_each
534  mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
535  mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
536  }
537 }
538 
540 {
541  GrabFocus();
542 }
543 
545 {
546  if (mxEdSearch->get_visible())
547  mxEdSearch->grab_focus();
548  else
549  {
550  mxMenu->set_cursor(0);
551  mxMenu->grab_focus();
552  }
553 }
554 
556 {
557  EndPopupMode();
558  for (auto& rMenuItem : maMenuItems)
559  rMenuItem.mxSubMenuWin.disposeAndClear();
561  {
563  mnAsyncPostPopdownId = nullptr;
564  }
565 }
566 
568  bool bTreeMode, int nWidth, ScCheckListMenuWindow* pParentMenu,
569  vcl::ILibreOfficeKitNotifier* pNotifier)
570  : DropdownDockingWindow(pParent)
571  , mxParentMenu(pParentMenu)
572 {
573  if (pNotifier)
574  SetLOKNotifier(pNotifier);
576  mxControl.reset(new ScCheckListMenuControl(this, m_xBox.get(), pDoc, bCanHaveSubMenu, bTreeMode, nWidth));
577  SetBackground(Application::GetSettings().GetStyleSettings().GetMenuColor());
578  set_id("check_list_menu");
579 }
580 
582 {
583  if (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE)
584  {
585  ScCheckListMenuControl& rMenuControl = get_widget();
586  rMenuControl.queueCloseSubMenu();
587  rMenuControl.clearSelectedMenuItem();
588  }
590 }
591 
593 {
594  disposeOnce();
595 }
596 
598 {
599  mxControl.reset();
602 }
603 
605 {
607  if (!mxControl)
608  return;
609  mxControl->GrabFocus();
610 }
611 
613 {
614  mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height() + 2);
615  mnSelectedMenu = 0;
616  mxMenu->set_cursor(mnSelectedMenu);
617  mxMenu->unselect_all();
618 
619  mnWndWidth = mxContainer->get_preferred_size().Width() + mxFrame->get_border_width() * 2 + 4;
620 }
621 
623 {
624  mpChecks->all_foreach([this, bSet](weld::TreeIter& rEntry){
625  mpChecks->set_toggle(rEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
626  return false;
627  });
628 
630  {
631  // We need to have at least one member selected.
632  mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
633  }
634 }
635 
637 {
638  setAllMemberState(!bSet);
639  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
640  if (!mpChecks->get_cursor(xEntry.get()))
641  return;
642  mpChecks->set_toggle(*xEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
643 }
644 
646 {
647  if (&rBtn == mxBtnOk.get())
648  close(true);
649  else if (&rBtn == mxBtnCancel.get())
650  close(false);
651  else if (&rBtn == mxBtnSelectSingle.get() || &rBtn == mxBtnUnselectSingle.get())
652  {
653  selectCurrentMemberOnly(&rBtn == mxBtnSelectSingle.get());
654  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
655  if (!mpChecks->get_cursor(xEntry.get()))
656  xEntry.reset();
657  Check(xEntry.get());
658  }
659 }
660 
662 {
663  switch (mePrevToggleAllState)
664  {
665  case TRISTATE_FALSE:
666  mxChkToggleAll->set_state(TRISTATE_TRUE);
667  setAllMemberState(true);
668  break;
669  case TRISTATE_TRUE:
670  mxChkToggleAll->set_state(TRISTATE_FALSE);
671  setAllMemberState(false);
672  break;
673  case TRISTATE_INDET:
674  default:
675  mxChkToggleAll->set_state(TRISTATE_TRUE);
676  setAllMemberState(true);
677  break;
678  }
679 
680  mePrevToggleAllState = mxChkToggleAll->get_state();
681 }
682 
683 namespace
684 {
685  void insertMember(weld::TreeView& rView, const weld::TreeIter& rIter, const ScCheckListMember& rMember, bool bChecked)
686  {
687  OUString aLabel = rMember.maName;
688  if (aLabel.isEmpty())
689  aLabel = ScResId(STR_EMPTYDATA);
690  rView.set_toggle(rIter, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
691  rView.set_text(rIter, aLabel, 0);
692  }
693 }
694 
696 {
697  OUString aSearchText = mxEdSearch->get_text();
698  aSearchText = ScGlobal::getCharClassPtr()->lowercase( aSearchText );
699  bool bSearchTextEmpty = aSearchText.isEmpty();
700  size_t n = maMembers.size();
701  size_t nSelCount = 0;
702 
703  mpChecks->freeze();
704 
705  // This branch is the general case, the other is an optimized variant of
706  // this one where we can take advantage of knowing we have no hierarchy
707  if (mbHasDates)
708  {
709  bool bSomeDateDeletes = false;
710 
711  for (size_t i = 0; i < n; ++i)
712  {
713  bool bIsDate = maMembers[i].mbDate;
714  bool bPartialMatch = false;
715 
716  OUString aLabelDisp = maMembers[i].maName;
717  if ( aLabelDisp.isEmpty() )
718  aLabelDisp = ScResId( STR_EMPTYDATA );
719 
720  if ( !bSearchTextEmpty )
721  {
722  if ( !bIsDate )
723  bPartialMatch = ( ScGlobal::getCharClassPtr()->lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
724  else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
725  bPartialMatch = (ScGlobal::getCharClassPtr()->lowercase( OUString(
726  maMembers[i].maRealName + maMembers[i].maDateParts[1] )).indexOf( aSearchText ) != -1);
727  else
728  continue;
729  }
730  else if ( bIsDate && maMembers[i].meDatePartType != ScCheckListMember::DAY )
731  continue;
732 
733  if ( bSearchTextEmpty )
734  {
735  auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, maMembers[i].mbVisible);
736  updateMemberParents(xLeaf.get(), i);
737  if ( maMembers[i].mbVisible )
738  ++nSelCount;
739  continue;
740  }
741 
742  if ( bPartialMatch )
743  {
744  auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i]);
745  updateMemberParents(xLeaf.get(), i);
746  ++nSelCount;
747  }
748  else
749  {
750  ShowCheckEntry(aLabelDisp, maMembers[i], false, false);
751  if( bIsDate )
752  bSomeDateDeletes = true;
753  }
754  }
755 
756  if ( bSomeDateDeletes )
757  {
758  for (size_t i = 0; i < n; ++i)
759  {
760  if (!maMembers[i].mbDate)
761  continue;
762  if (maMembers[i].meDatePartType != ScCheckListMember::DAY)
763  continue;
764  updateMemberParents(nullptr, i);
765  }
766  }
767  }
768  else
769  {
770  // when there are a lot of rows, it is cheaper to simply clear the tree and either
771  // re-initialise or just insert the filtered lines
772  mpChecks->clear();
773 
774  if (bSearchTextEmpty)
775  nSelCount = initMembers();
776  else
777  {
778  std::vector<size_t> aShownIndexes;
779 
780  for (size_t i = 0; i < n; ++i)
781  {
782  assert(!maMembers[i].mbDate);
783 
784  OUString aLabelDisp = maMembers[i].maName;
785  if ( aLabelDisp.isEmpty() )
786  aLabelDisp = ScResId( STR_EMPTYDATA );
787 
788  bool bPartialMatch = ScGlobal::getCharClassPtr()->lowercase( aLabelDisp ).indexOf( aSearchText ) != -1;
789 
790  if (!bPartialMatch)
791  continue;
792 
793  aShownIndexes.push_back(i);
794  }
795 
796  std::vector<int> aFixedWidths { mnCheckWidthReq };
797  // tdf#122419 insert in the fastest order, this might be backwards.
798  mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes, &nSelCount](weld::TreeIter& rIter, int i) {
799  size_t nIndex = aShownIndexes[i];
800  insertMember(*mpChecks, rIter, maMembers[nIndex], true);
801  ++nSelCount;
802  }, &aFixedWidths);
803  }
804  }
805 
806 
807  mpChecks->thaw();
808 
809  if ( nSelCount == n )
810  mxChkToggleAll->set_state( TRISTATE_TRUE );
811  else if ( nSelCount == 0 )
812  mxChkToggleAll->set_state( TRISTATE_FALSE );
813  else
814  mxChkToggleAll->set_state( TRISTATE_INDET );
815 
816  if ( !maConfig.mbAllowEmptySet )
817  {
818  const bool bEmptySet( nSelCount == 0 );
819  mpChecks->set_sensitive(!bEmptySet);
820  mxChkToggleAll->set_sensitive(!bEmptySet);
821  mxBtnSelectSingle->set_sensitive(!bEmptySet);
822  mxBtnUnselectSingle->set_sensitive(!bEmptySet);
823  mxBtnOk->set_sensitive(!bEmptySet);
824  }
825 }
826 
828 {
829  if (mxBtnOk->get_sensitive())
830  close(true);
831  return true;
832 }
833 
834 IMPL_LINK( ScCheckListMenuControl, CheckHdl, const weld::TreeView::iter_col&, rRowCol, void )
835 {
836  Check(&rRowCol.first);
837 }
838 
840 {
841  if (pEntry)
842  CheckEntry(*pEntry, mpChecks->get_toggle(*pEntry) == TRISTATE_TRUE);
843  size_t nNumChecked = GetCheckedEntryCount();
844  if (nNumChecked == maMembers.size())
845  // all members visible
846  mxChkToggleAll->set_state(TRISTATE_TRUE);
847  else if (nNumChecked == 0)
848  // no members visible
849  mxChkToggleAll->set_state(TRISTATE_FALSE);
850  else
851  mxChkToggleAll->set_state(TRISTATE_INDET);
852 
854  // We need to have at least one member selected.
855  mxBtnOk->set_sensitive(nNumChecked != 0);
856 
857  mePrevToggleAllState = mxChkToggleAll->get_state();
858 }
859 
861 {
862  if ( !maMembers[nIdx].mbDate || maMembers[nIdx].meDatePartType != ScCheckListMember::DAY )
863  return;
864 
865  OUString aYearName = maMembers[nIdx].maDateParts[0];
866  OUString aMonthName = maMembers[nIdx].maDateParts[1];
867  auto aItr = maYearMonthMap.find(aYearName + aMonthName);
868 
869  if ( pLeaf )
870  {
871  std::unique_ptr<weld::TreeIter> xYearEntry;
872  std::unique_ptr<weld::TreeIter> xMonthEntry = mpChecks->make_iterator(pLeaf);
873  if (!mpChecks->iter_parent(*xMonthEntry))
874  xMonthEntry.reset();
875  else
876  {
877  xYearEntry = mpChecks->make_iterator(xMonthEntry.get());
878  if (!mpChecks->iter_parent(*xYearEntry))
879  xYearEntry.reset();
880  }
881 
882  maMembers[nIdx].mxParent = std::move(xMonthEntry);
883  if ( aItr != maYearMonthMap.end() )
884  {
885  size_t nMonthIdx = aItr->second;
886  maMembers[nMonthIdx].mxParent = std::move(xYearEntry);
887  }
888  }
889  else
890  {
891  std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
892  if (aItr != maYearMonthMap.end() && !xYearEntry)
893  {
894  size_t nMonthIdx = aItr->second;
895  maMembers[nMonthIdx].mxParent.reset();
896  maMembers[nIdx].mxParent.reset();
897  }
898  else if (xYearEntry && !FindEntry(xYearEntry.get(), aMonthName))
899  maMembers[nIdx].mxParent.reset();
900  }
901 }
902 
904 {
905  maMembers.reserve(n);
906 }
907 
908 void ScCheckListMenuControl::addDateMember(const OUString& rsName, double nVal, bool bVisible)
909 {
910  SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
911 
912  // Convert the numeric date value to a date object.
913  Date aDate = pFormatter->GetNullDate();
914  aDate.AddDays(rtl::math::approxFloor(nVal));
915 
916  sal_Int16 nYear = aDate.GetYear();
917  sal_uInt16 nMonth = aDate.GetMonth();
918  sal_uInt16 nDay = aDate.GetDay();
919 
920  // Get the localized month name list.
921  CalendarWrapper* pCalendar = ScGlobal::GetCalendar();
922  uno::Sequence<i18n::CalendarItem2> aMonths = pCalendar->getMonths();
923  if (aMonths.getLength() < nMonth)
924  return;
925 
926  OUString aYearName = OUString::number(nYear);
927  OUString aMonthName = aMonths[nMonth-1].FullName;
928  OUString aDayName = OUString::number(nDay);
929 
930  if ( aDayName.getLength() == 1 )
931  aDayName = "0" + aDayName;
932 
933  mpChecks->freeze();
934 
935  std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
936  if (!xYearEntry)
937  {
938  xYearEntry = mpChecks->make_iterator();
939  mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
940  mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
941  mpChecks->set_text(*xYearEntry, aYearName, 0);
942  ScCheckListMember aMemYear;
943  aMemYear.maName = aYearName;
944  aMemYear.maRealName = rsName;
945  aMemYear.mbDate = true;
946  aMemYear.mbLeaf = false;
947  aMemYear.mbVisible = bVisible;
948  aMemYear.mxParent.reset();
950  maMembers.emplace_back(std::move(aMemYear));
951  }
952 
953  std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), aMonthName);
954  if (!xMonthEntry)
955  {
956  xMonthEntry = mpChecks->make_iterator();
957  mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
958  mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
959  mpChecks->set_text(*xMonthEntry, aMonthName, 0);
960  ScCheckListMember aMemMonth;
961  aMemMonth.maName = aMonthName;
962  aMemMonth.maRealName = rsName;
963  aMemMonth.mbDate = true;
964  aMemMonth.mbLeaf = false;
965  aMemMonth.mbVisible = bVisible;
966  aMemMonth.mxParent = std::move(xYearEntry);
968  maMembers.emplace_back(std::move(aMemMonth));
969  maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
970  }
971 
972  std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), aDayName);
973  if (!xDayEntry)
974  {
975  xDayEntry = mpChecks->make_iterator();
976  mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
977  mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
978  mpChecks->set_text(*xDayEntry, aDayName, 0);
979  ScCheckListMember aMemDay;
980  aMemDay.maName = aDayName;
981  aMemDay.maRealName = rsName;
982  aMemDay.maDateParts.resize(2);
983  aMemDay.maDateParts[0] = aYearName;
984  aMemDay.maDateParts[1] = aMonthName;
985  aMemDay.mbDate = true;
986  aMemDay.mbLeaf = true;
987  aMemDay.mbVisible = bVisible;
988  aMemDay.mxParent = std::move(xMonthEntry);
990  maMembers.emplace_back(std::move(aMemDay));
991  }
992 
993  mpChecks->thaw();
994 }
995 
996 void ScCheckListMenuControl::addMember(const OUString& rName, bool bVisible)
997 {
998  ScCheckListMember aMember;
999  aMember.maName = rName;
1000  aMember.mbDate = false;
1001  aMember.mbLeaf = true;
1002  aMember.mbVisible = bVisible;
1003  aMember.mxParent.reset();
1004  maMembers.emplace_back(std::move(aMember));
1005 }
1006 
1007 std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::FindEntry(const weld::TreeIter* pParent, std::u16string_view sNode)
1008 {
1009  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(pParent);
1010  bool bEntry = pParent ? mpChecks->iter_children(*xEntry) : mpChecks->get_iter_first(*xEntry);
1011  while (bEntry)
1012  {
1013  if (sNode == mpChecks->get_text(*xEntry, 0))
1014  return xEntry;
1015  bEntry = mpChecks->iter_next_sibling(*xEntry);
1016  }
1017  return nullptr;
1018 }
1019 
1020 void ScCheckListMenuControl::GetRecursiveChecked(const weld::TreeIter* pEntry, std::unordered_set<OUString>& vOut,
1021  OUString& rLabel)
1022 {
1023  if (mpChecks->get_toggle(*pEntry) != TRISTATE_TRUE)
1024  return;
1025 
1026  // We have to hash parents and children together.
1027  // Per convention for easy access in getResult()
1028  // "child;parent;grandparent" while descending.
1029  if (rLabel.isEmpty())
1030  rLabel = mpChecks->get_text(*pEntry, 0);
1031  else
1032  rLabel = mpChecks->get_text(*pEntry, 0) + ";" + rLabel;
1033 
1034  // Prerequisite: the selection mechanism guarantees that if a child is
1035  // selected then also the parent is selected, so we only have to
1036  // inspect the children in case the parent is selected.
1037  if (!mpChecks->iter_has_child(*pEntry))
1038  return;
1039 
1040  std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(pEntry));
1041  bool bChild = mpChecks->iter_children(*xChild);
1042  while (bChild)
1043  {
1044  OUString aLabel = rLabel;
1045  GetRecursiveChecked(xChild.get(), vOut, aLabel);
1046  if (!aLabel.isEmpty() && aLabel != rLabel)
1047  vOut.insert(aLabel);
1048  bChild = mpChecks->iter_next_sibling(*xChild);
1049  }
1050  // Let the caller not add the parent alone.
1051  rLabel.clear();
1052 }
1053 
1054 std::unordered_set<OUString> ScCheckListMenuControl::GetAllChecked()
1055 {
1056  std::unordered_set<OUString> vResults(0);
1057 
1058  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1059  bool bEntry = mpChecks->get_iter_first(*xEntry);
1060  while (bEntry)
1061  {
1062  OUString aLabel;
1063  GetRecursiveChecked(xEntry.get(), vResults, aLabel);
1064  if (!aLabel.isEmpty())
1065  vResults.insert(aLabel);
1066  bEntry = mpChecks->iter_next_sibling(*xEntry);
1067  }
1068 
1069  return vResults;
1070 }
1071 
1072 bool ScCheckListMenuControl::IsChecked(std::u16string_view sName, const weld::TreeIter* pParent)
1073 {
1074  std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
1075  return xEntry && mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1076 }
1077 
1078 void ScCheckListMenuControl::CheckEntry(std::u16string_view sName, const weld::TreeIter* pParent, bool bCheck)
1079 {
1080  std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
1081  if (xEntry)
1082  CheckEntry(*xEntry, bCheck);
1083 }
1084 
1085 // Recursively check all children of rParent
1087 {
1088  mpChecks->set_toggle(rParent, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
1089  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(&rParent);
1090  bool bEntry = mpChecks->iter_children(*xEntry);
1091  while (bEntry)
1092  {
1093  CheckAllChildren(*xEntry, bCheck);
1094  bEntry = mpChecks->iter_next_sibling(*xEntry);
1095  }
1096 }
1097 
1098 void ScCheckListMenuControl::CheckEntry(const weld::TreeIter& rParent, bool bCheck)
1099 {
1100  // recursively check all items below rParent
1101  CheckAllChildren(rParent, bCheck);
1102  // checking rParent can affect ancestors, e.g. if ancestor is unchecked and rParent is
1103  // now checked then the ancestor needs to be checked also
1104  if (!mpChecks->get_iter_depth(rParent))
1105  return;
1106 
1107  std::unique_ptr<weld::TreeIter> xAncestor(mpChecks->make_iterator(&rParent));
1108  bool bAncestor = mpChecks->iter_parent(*xAncestor);
1109  while (bAncestor)
1110  {
1111  // if any first level children checked then ancestor
1112  // needs to be checked, similarly if no first level children
1113  // checked then ancestor needs to be unchecked
1114  std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(xAncestor.get()));
1115  bool bChild = mpChecks->iter_children(*xChild);
1116  bool bChildChecked = false;
1117 
1118  while (bChild)
1119  {
1120  if (mpChecks->get_toggle(*xChild) == TRISTATE_TRUE)
1121  {
1122  bChildChecked = true;
1123  break;
1124  }
1125  bChild = mpChecks->iter_next_sibling(*xChild);
1126  }
1127  mpChecks->set_toggle(*xAncestor, bChildChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
1128  bAncestor = mpChecks->iter_parent(*xAncestor);
1129  }
1130 }
1131 
1132 std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::ShowCheckEntry(const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck)
1133 {
1134  std::unique_ptr<weld::TreeIter> xEntry;
1135  if (!rMember.mbDate || rMember.mxParent)
1136  xEntry = FindEntry(rMember.mxParent.get(), sName);
1137 
1138  if ( bShow )
1139  {
1140  if (!xEntry)
1141  {
1142  if (rMember.mbDate)
1143  {
1144  if (rMember.maDateParts.empty())
1145  return nullptr;
1146 
1147  std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, rMember.maDateParts[0]);
1148  if (!xYearEntry)
1149  {
1150  xYearEntry = mpChecks->make_iterator();
1151  mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
1152  mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
1153  mpChecks->set_text(*xYearEntry, rMember.maDateParts[0], 0);
1154  }
1155  std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), rMember.maDateParts[1]);
1156  if (!xMonthEntry)
1157  {
1158  xMonthEntry = mpChecks->make_iterator();
1159  mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
1160  mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
1161  mpChecks->set_text(*xMonthEntry, rMember.maDateParts[1], 0);
1162  }
1163  std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), rMember.maName);
1164  if (!xDayEntry)
1165  {
1166  xDayEntry = mpChecks->make_iterator();
1167  mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
1168  mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
1169  mpChecks->set_text(*xDayEntry, rMember.maName, 0);
1170  }
1171  return xDayEntry; // Return leaf node
1172  }
1173 
1174  xEntry = mpChecks->make_iterator();
1175  mpChecks->append(xEntry.get());
1176  mpChecks->set_toggle(*xEntry, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
1177  mpChecks->set_text(*xEntry, sName, 0);
1178  }
1179  else
1180  CheckEntry(*xEntry, bCheck);
1181  }
1182  else if (xEntry)
1183  {
1184  mpChecks->remove(*xEntry);
1185  if (rMember.mxParent)
1186  {
1187  std::unique_ptr<weld::TreeIter> xParent(mpChecks->make_iterator(rMember.mxParent.get()));
1188  while (xParent && !mpChecks->iter_has_child(*xParent))
1189  {
1190  std::unique_ptr<weld::TreeIter> xTmp(mpChecks->make_iterator(xParent.get()));
1191  if (!mpChecks->iter_parent(*xParent))
1192  xParent.reset();
1193  mpChecks->remove(*xTmp);
1194  }
1195  }
1196  }
1197  return nullptr;
1198 }
1199 
1201 {
1202  int nRet = 0;
1203 
1204  mpChecks->all_foreach([this, &nRet](weld::TreeIter& rEntry){
1205  if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
1206  ++nRet;
1207  return false;
1208  });
1209 
1210  return nRet;
1211 }
1212 
1213 IMPL_LINK(ScCheckListMenuControl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
1214 {
1215  const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
1216 
1217  if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
1218  {
1219  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1220  bool bEntry = mpChecks->get_cursor(xEntry.get());
1221  if (bEntry)
1222  {
1223  bool bOldCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1224  CheckEntry(*xEntry, !bOldCheck);
1225  bool bNewCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
1226  if (bOldCheck != bNewCheck)
1227  Check(xEntry.get());
1228  }
1229  return true;
1230  }
1231 
1232  return false;
1233 }
1234 
1235 size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth)
1236 {
1237  size_t n = maMembers.size();
1238  size_t nVisMemCount = 0;
1239 
1240  if (nMaxMemberWidth == -1)
1241  nMaxMemberWidth = mnCheckWidthReq;
1242 
1243  if (!mpChecks->n_children() && !mbHasDates)
1244  {
1245  std::vector<int> aFixedWidths { nMaxMemberWidth };
1246  // tdf#134038 insert in the fastest order, this might be backwards so only do it for
1247  // the !mbHasDates case where no entry depends on another to exist before getting
1248  // inserted. We cannot retain pre-existing treeview content, only clear and fill it.
1249  mpChecks->bulk_insert_for_each(n, [this, &nVisMemCount](weld::TreeIter& rIter, int i) {
1250  assert(!maMembers[i].mbDate);
1251  insertMember(*mpChecks, rIter, maMembers[i], maMembers[i].mbVisible);
1252  if (maMembers[i].mbVisible)
1253  ++nVisMemCount;
1254  }, &aFixedWidths);
1255  }
1256  else
1257  {
1258  mpChecks->freeze();
1259 
1260  std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
1261  std::vector<std::unique_ptr<weld::TreeIter>> aExpandRows;
1262 
1263  for (size_t i = 0; i < n; ++i)
1264  {
1265  if (maMembers[i].mbDate)
1266  {
1267  CheckEntry(maMembers[i].maName, maMembers[i].mxParent.get(), maMembers[i].mbVisible);
1268  // Expand first node of checked dates
1269  if (!maMembers[i].mxParent && IsChecked(maMembers[i].maName, maMembers[i].mxParent.get()))
1270  {
1271  std::unique_ptr<weld::TreeIter> xDateEntry = FindEntry(nullptr, maMembers[i].maName);
1272  if (xDateEntry)
1273  aExpandRows.emplace_back(std::move(xDateEntry));
1274  }
1275  }
1276  else
1277  {
1278  mpChecks->append(xEntry.get());
1279  insertMember(*mpChecks, *xEntry, maMembers[i], maMembers[i].mbVisible);
1280  }
1281 
1282  if (maMembers[i].mbVisible)
1283  ++nVisMemCount;
1284  }
1285 
1286  mpChecks->thaw();
1287 
1288  for (auto& rRow : aExpandRows)
1289  mpChecks->expand_row(*rRow);
1290  }
1291 
1292  if (nVisMemCount == n)
1293  {
1294  // all members visible
1295  mxChkToggleAll->set_state(TRISTATE_TRUE);
1297  }
1298  else if (nVisMemCount == 0)
1299  {
1300  // no members visible
1301  mxChkToggleAll->set_state(TRISTATE_FALSE);
1303  }
1304  else
1305  {
1306  mxChkToggleAll->set_state(TRISTATE_INDET);
1308  }
1309 
1310  if (nVisMemCount)
1311  mpChecks->select(0);
1312 
1313  return nVisMemCount;
1314 }
1315 
1317 {
1318  maConfig = rConfig;
1319 }
1320 
1322 {
1323  return mxChkToggleAll->get_state() == TRISTATE_TRUE;
1324 }
1325 
1327 {
1328  ResultType aResult;
1329  std::unordered_set<OUString> vCheckeds = GetAllChecked();
1330  size_t n = maMembers.size();
1331  for (size_t i = 0; i < n; ++i)
1332  {
1333  if ( maMembers[i].mbLeaf )
1334  {
1335  OUStringBuffer aLabel = maMembers[i].maName;
1336  if (aLabel.isEmpty())
1337  aLabel = ScResId(STR_EMPTYDATA);
1338 
1339  /* TODO: performance-wise this looks suspicious, concatenating to
1340  * do the lookup for each leaf item seems wasteful. */
1341  // Checked labels are in the form "child;parent;grandparent".
1342  if (maMembers[i].mxParent)
1343  {
1344  std::unique_ptr<weld::TreeIter> xIter(mpChecks->make_iterator(maMembers[i].mxParent.get()));
1345  do
1346  {
1347  aLabel.append(";").append(mpChecks->get_text(*xIter));
1348  }
1349  while (mpChecks->iter_parent(*xIter));
1350  }
1351 
1352  bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
1353 
1354  ResultEntry aResultEntry;
1355  aResultEntry.bValid = bState;
1356  if ( maMembers[i].mbDate )
1357  aResultEntry.aName = maMembers[i].maRealName;
1358  else
1359  aResultEntry.aName = maMembers[i].maName;
1360  aResultEntry.bDate = maMembers[i].mbDate;
1361  aResult.insert(aResultEntry);
1362  }
1363  }
1364  rResult.swap(aResult);
1365 }
1366 
1368 {
1369  prepWindow();
1371  // We need to have at least one member selected.
1372  mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
1373 
1374  tools::Rectangle aRect(rRect);
1375  if (maConfig.mbRTL)
1376  {
1377  // In RTL mode, the logical "left" is visual "right".
1378  tools::Long nLeft = aRect.Left() - aRect.GetWidth();
1379  aRect.SetLeft( nLeft );
1380  }
1381  else if (mnWndWidth < aRect.GetWidth())
1382  {
1383  // Target rectangle (i.e. cell width) is wider than the window.
1384  // Simulate right-aligned launch by modifying the target rectangle
1385  // size.
1386  tools::Long nDiff = aRect.GetWidth() - mnWndWidth;
1387  aRect.AdjustLeft(nDiff );
1388  }
1389 
1390  StartPopupMode(aRect, FloatWinPopupFlags::Down);
1391 }
1392 
1394 {
1396  if (aNotifierWindow) {
1397  const vcl::ILibreOfficeKitNotifier* pNotifier = aNotifierWindow->GetLOKNotifier();
1398  if (pNotifier)
1399  {
1400  tools::JsonWriter aJsonWriter;
1401  aJsonWriter.put("jsontype", "autofilter");
1402  aJsonWriter.put("action", "close");
1403 
1404  const std::string message = aJsonWriter.extractAsStdString();
1405  pNotifier->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, message.c_str());
1406  }
1407  }
1408 }
1409 
1411 {
1412  if (bOK && mxOKAction)
1413  mxOKAction->execute();
1414  EndPopupMode();
1415 
1417  NotifyCloseLOK();
1418 }
1419 
1420 void ScCheckListMenuControl::setExtendedData(std::unique_ptr<ExtendedData> p)
1421 {
1422  mxExtendedData = std::move(p);
1423 }
1424 
1426 {
1427  return mxExtendedData.get();
1428 }
1429 
1431 {
1432  mxOKAction.reset(p);
1433 }
1434 
1436 {
1437  mxPopupEndAction.reset(p);
1438 }
1439 
1441 {
1442  clearSelectedMenuItem();
1443  if (mxPopupEndAction)
1444  mxPopupEndAction->execute();
1445 
1447  NotifyCloseLOK();
1448 }
1449 
1450 int ScCheckListMenuControl::GetTextWidth(const OUString& rsName) const
1451 {
1452  return mxDropDown->GetTextWidth(rsName);
1453 }
1454 
1456 {
1457  int nBorder = mxFrame->get_border_width() * 2 + 4;
1458  int nNewWidth = nMaxTextWidth - nBorder;
1459  if (nNewWidth > mnCheckWidthReq)
1460  {
1461  mnCheckWidthReq = nNewWidth;
1462  int nChecksHeight = mpChecks->get_height_rows(9);
1463  mpChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
1464  }
1465  return mnCheckWidthReq + nBorder;
1466 }
1467 
1468 /* 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)
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
tools::Long GetWidth() 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)
void SetBackground()
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()
void setAllMemberState(bool bSet)
static constexpr size_t MENU_NOT_SELECTED
TRISTATE_INDET
bool IsChecked(std::u16string_view sName, const weld::TreeIter *pParent)
void addMenuItem(const OUString &rText, Action *pAction)
virtual void bulk_insert_for_each(int nSourceCount, const std::function< void(TreeIter &, int nSourceIndex)> &func, const std::vector< int > *pFixedWidths=nullptr)=0
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:438
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 addMember(const OUString &rName, bool bVisible)
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 field button, for quick access of hide-item list...
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
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()
virtual int get_iter_depth(const TreeIter &rIter) const =0
size_t getSubMenuPos(const ScCheckListMenuControl *pSubMenu)
virtual bool EventNotify(NotifyEvent &rNEvt) override