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 #include <strings.hrc>
24 #include <bitmaps.hlst>
25 
26 #include <vcl/decoview.hxx>
27 #include <vcl/event.hxx>
28 #include <vcl/settings.hxx>
29 #include <tools/wintypes.hxx>
30 #include <unotools/charclass.hxx>
31 
32 #include <AccessibleFilterMenu.hxx>
33 #include <AccessibleFilterTopWindow.hxx>
34 
35 #include <com/sun/star/accessibility/XAccessible.hpp>
36 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
37 #include <vcl/svlbitm.hxx>
38 #include <vcl/treelistentry.hxx>
39 #include <document.hxx>
40 
41 using namespace com::sun::star;
42 using ::com::sun::star::uno::Reference;
43 using ::com::sun::star::accessibility::XAccessible;
44 using ::com::sun::star::accessibility::XAccessibleContext;
45 using ::std::vector;
46 
MenuItemData()47 ScMenuFloatingWindow::MenuItemData::MenuItemData() :
48     mbEnabled(true), mbSeparator(false),
49     mpAction(static_cast<ScCheckListMenuWindow::Action*>(nullptr)),
50     mpSubMenuWin(static_cast<ScMenuFloatingWindow*>(nullptr))
51 {
52 }
53 
SubMenuItemData(ScMenuFloatingWindow * pParent)54 ScMenuFloatingWindow::SubMenuItemData::SubMenuItemData(ScMenuFloatingWindow* pParent) :
55     mpSubMenu(nullptr),
56     mnMenuPos(MENU_NOT_SELECTED),
57     mpParent(pParent)
58 {
59     maTimer.SetInvokeHandler( LINK(this, ScMenuFloatingWindow::SubMenuItemData, TimeoutHdl) );
60     maTimer.SetTimeout(mpParent->GetSettings().GetMouseSettings().GetMenuDelay());
61 }
62 
reset()63 void ScMenuFloatingWindow::SubMenuItemData::reset()
64 {
65     mpSubMenu = nullptr;
66     mnMenuPos = MENU_NOT_SELECTED;
67     maTimer.Stop();
68 }
69 
IMPL_LINK_NOARG(ScMenuFloatingWindow::SubMenuItemData,TimeoutHdl,Timer *,void)70 IMPL_LINK_NOARG(ScMenuFloatingWindow::SubMenuItemData, TimeoutHdl, Timer *, void)
71 {
72     mpParent->handleMenuTimeout(this);
73 }
74 
ScMenuFloatingWindow(vcl::Window * pParent,ScDocument * pDoc,sal_uInt16 nMenuStackLevel)75 ScMenuFloatingWindow::ScMenuFloatingWindow(vcl::Window* pParent, ScDocument* pDoc, sal_uInt16 nMenuStackLevel) :
76     PopupMenuFloatingWindow(pParent),
77     maOpenTimer(this),
78     maCloseTimer(this),
79     maName("ScMenuFloatingWindow"),
80     mnSelectedMenu(MENU_NOT_SELECTED),
81     mnClickedMenu(MENU_NOT_SELECTED),
82     mpDoc(pDoc),
83     mpParentMenu(dynamic_cast<ScMenuFloatingWindow*>(pParent))
84 {
85     SetMenuStackLevel(nMenuStackLevel);
86     SetText("ScMenuFloatingWindow");
87 
88     const StyleSettings& rStyle = GetSettings().GetStyleSettings();
89 
90     const sal_uInt16 nPopupFontHeight = 12 * GetDPIScaleFactor();
91     maLabelFont = rStyle.GetLabelFont();
92     maLabelFont.SetFontHeight(nPopupFontHeight);
93 }
94 
~ScMenuFloatingWindow()95 ScMenuFloatingWindow::~ScMenuFloatingWindow()
96 {
97     disposeOnce();
98 }
99 
dispose()100 void ScMenuFloatingWindow::dispose()
101 {
102     EndPopupMode();
103     for (auto& rMenuItem : maMenuItems)
104         rMenuItem.mpSubMenuWin.disposeAndClear();
105     mpParentMenu.clear();
106     PopupMenuFloatingWindow::dispose();
107 }
108 
PopupModeEnd()109 void ScMenuFloatingWindow::PopupModeEnd()
110 {
111     handlePopupEnd();
112 }
113 
MouseMove(const MouseEvent & rMEvt)114 void ScMenuFloatingWindow::MouseMove(const MouseEvent& rMEvt)
115 {
116     const Point& rPos = rMEvt.GetPosPixel();
117     size_t nSelectedMenu = getEnclosingMenuItem(rPos);
118     setSelectedMenuItem(nSelectedMenu, true, false);
119 
120     Window::MouseMove(rMEvt);
121 }
122 
MouseButtonDown(const MouseEvent & rMEvt)123 void ScMenuFloatingWindow::MouseButtonDown(const MouseEvent& rMEvt)
124 {
125     const Point& rPos = rMEvt.GetPosPixel();
126     mnClickedMenu = getEnclosingMenuItem(rPos);
127     Window::MouseButtonDown(rMEvt);
128 }
129 
MouseButtonUp(const MouseEvent & rMEvt)130 void ScMenuFloatingWindow::MouseButtonUp(const MouseEvent& rMEvt)
131 {
132     executeMenuItem(mnClickedMenu);
133     mnClickedMenu = MENU_NOT_SELECTED;
134     Window::MouseButtonUp(rMEvt);
135 }
136 
KeyInput(const KeyEvent & rKEvt)137 void ScMenuFloatingWindow::KeyInput(const KeyEvent& rKEvt)
138 {
139     if (maMenuItems.empty())
140     {
141         Window::KeyInput(rKEvt);
142         return;
143     }
144 
145     const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
146     bool bHandled = true;
147     size_t nSelectedMenu = mnSelectedMenu;
148     size_t nLastMenuPos = maMenuItems.size() - 1;
149     switch (rKeyCode.GetCode())
150     {
151         case KEY_UP:
152         {
153             if (nLastMenuPos == 0)
154                 // There is only one menu item.  Do nothing.
155                 break;
156 
157             size_t nOldPos = nSelectedMenu;
158 
159             if (nSelectedMenu == MENU_NOT_SELECTED || nSelectedMenu == 0)
160                 nSelectedMenu = nLastMenuPos;
161             else
162                 --nSelectedMenu;
163 
164             // Loop until a non-separator menu item is found.
165             while (nSelectedMenu != nOldPos)
166             {
167                 if (maMenuItems[nSelectedMenu].mbSeparator)
168                 {
169                     if (nSelectedMenu)
170                         --nSelectedMenu;
171                     else
172                         nSelectedMenu = nLastMenuPos;
173                 }
174                 else
175                     break;
176             }
177 
178             setSelectedMenuItem(nSelectedMenu, false, false);
179         }
180         break;
181         case KEY_DOWN:
182         {
183             if (nLastMenuPos == 0)
184                 // There is only one menu item.  Do nothing.
185                 break;
186 
187             size_t nOldPos = nSelectedMenu;
188 
189             if (nSelectedMenu == MENU_NOT_SELECTED || nSelectedMenu == nLastMenuPos)
190                 nSelectedMenu = 0;
191             else
192                 ++nSelectedMenu;
193 
194             // Loop until a non-separator menu item is found.
195             while (nSelectedMenu != nOldPos)
196             {
197                 if (maMenuItems[nSelectedMenu].mbSeparator)
198                 {
199                     if (nSelectedMenu == nLastMenuPos)
200                         nSelectedMenu = 0;
201                     else
202                         ++nSelectedMenu;
203                 }
204                 else
205                     break;
206             }
207 
208             setSelectedMenuItem(nSelectedMenu, false, false);
209         }
210         break;
211         case KEY_LEFT:
212             if (mpParentMenu)
213                 mpParentMenu->endSubMenu(this);
214         break;
215         case KEY_RIGHT:
216         {
217             if (mnSelectedMenu >= maMenuItems.size() || mnSelectedMenu == MENU_NOT_SELECTED)
218                 break;
219 
220             const MenuItemData& rMenu = maMenuItems[mnSelectedMenu];
221             if (!rMenu.mbEnabled || !rMenu.mpSubMenuWin)
222                 break;
223 
224             maOpenTimer.mnMenuPos = mnSelectedMenu;
225             maOpenTimer.mpSubMenu = rMenu.mpSubMenuWin.get();
226             launchSubMenu(true);
227         }
228         break;
229         case KEY_RETURN:
230             if (nSelectedMenu != MENU_NOT_SELECTED)
231                 executeMenuItem(nSelectedMenu);
232         break;
233         default:
234             bHandled = false;
235     }
236 
237     if (!bHandled)
238         Window::KeyInput(rKEvt);
239 }
240 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle &)241 void ScMenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
242 {
243     const StyleSettings& rStyle = GetSettings().GetStyleSettings();
244 
245     SetFont(maLabelFont);
246 
247     Color aBackColor = rStyle.GetMenuColor();
248     Color aBorderColor = rStyle.GetShadowColor();
249 
250     tools::Rectangle aCtrlRect(Point(0, 0), GetOutputSizePixel());
251 
252     // Window background
253     bool bNativeDrawn = true;
254     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
255     {
256         rRenderContext.SetClipRegion();
257         bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect,
258                                                         ControlState::ENABLED, ImplControlValue(), OUString());
259     }
260     else
261         bNativeDrawn = false;
262 
263     if (!bNativeDrawn)
264     {
265         rRenderContext.SetFillColor(aBackColor);
266         rRenderContext.SetLineColor(aBorderColor);
267         rRenderContext.DrawRect(aCtrlRect);
268     }
269 
270     // Menu items
271     rRenderContext.SetTextColor(rStyle.GetMenuTextColor());
272     drawAllMenuItems(rRenderContext);
273 }
274 
CreateAccessible()275 Reference<XAccessible> ScMenuFloatingWindow::CreateAccessible()
276 {
277     if (!mxAccessible.is())
278     {
279         Reference<XAccessible> xAccParent = mpParentMenu ?
280             mpParentMenu->GetAccessible() : GetAccessibleParentWindow()->GetAccessible();
281 
282         mxAccessible.set(new ScAccessibleFilterMenu(xAccParent, this, maName, 999));
283         ScAccessibleFilterMenu* p = static_cast<ScAccessibleFilterMenu*>(
284             mxAccessible.get());
285 
286         size_t nPos = 0;
287         for (const auto& rMenuItem : maMenuItems)
288         {
289             p->appendMenuItem(rMenuItem.maText, nPos);
290             ++nPos;
291         }
292     }
293 
294     return mxAccessible;
295 }
296 
addMenuItem(const OUString & rText,Action * pAction)297 void ScMenuFloatingWindow::addMenuItem(const OUString& rText, Action* pAction)
298 {
299     MenuItemData aItem;
300     aItem.maText = rText;
301     aItem.mbEnabled = true;
302     aItem.mpAction.reset(pAction);
303     maMenuItems.push_back(aItem);
304 }
305 
addSeparator()306 void ScMenuFloatingWindow::addSeparator()
307 {
308     MenuItemData aItem;
309     aItem.mbSeparator = true;
310     maMenuItems.push_back(aItem);
311 }
312 
addSubMenuItem(const OUString & rText,bool bEnabled)313 ScMenuFloatingWindow* ScMenuFloatingWindow::addSubMenuItem(const OUString& rText, bool bEnabled)
314 {
315     MenuItemData aItem;
316     aItem.maText = rText;
317     aItem.mbEnabled = bEnabled;
318     aItem.mpSubMenuWin.reset(VclPtr<ScMenuFloatingWindow>::Create(this, mpDoc, GetMenuStackLevel()+1));
319     aItem.mpSubMenuWin->setName(rText);
320     maMenuItems.push_back(aItem);
321     return aItem.mpSubMenuWin.get();
322 }
323 
handlePopupEnd()324 void ScMenuFloatingWindow::handlePopupEnd()
325 {
326     clearSelectedMenuItem();
327 }
328 
getMenuSize() const329 Size ScMenuFloatingWindow::getMenuSize() const
330 {
331     if (maMenuItems.empty())
332         return Size();
333 
334     auto itr = std::max_element(maMenuItems.begin(), maMenuItems.end(),
335         [this](const MenuItemData& a, const MenuItemData& b) {
336             long aTextWidth = a.mbSeparator ? 0 : GetTextWidth(a.maText);
337             long bTextWidth = b.mbSeparator ? 0 : GetTextWidth(b.maText);
338             return aTextWidth < bTextWidth;
339         });
340     long nTextWidth = itr->mbSeparator ? 0 : GetTextWidth(itr->maText);
341 
342     size_t nLastPos = maMenuItems.size()-1;
343     Point aPos;
344     Size aSize;
345     getMenuItemPosSize(nLastPos, aPos, aSize);
346     aPos.AdjustX(nTextWidth + 15 );
347     aPos.AdjustY(aSize.Height() + 5 );
348     return Size(aPos.X(), aPos.Y());
349 }
350 
drawMenuItem(vcl::RenderContext & rRenderContext,size_t nPos)351 void ScMenuFloatingWindow::drawMenuItem(vcl::RenderContext& rRenderContext, size_t nPos)
352 {
353     if (nPos >= maMenuItems.size())
354         return;
355 
356     Point aPos;
357     Size aSize;
358     getMenuItemPosSize(nPos, aPos, aSize);
359 
360     DecorationView aDecoView(&rRenderContext);
361     long const nXOffset = 5;
362     long nYOffset = (aSize.Height() - maLabelFont.GetFontHeight())/2;
363 
364     // Make sure the label font is used for the menu item text.
365     rRenderContext.Push(PushFlags::FONT);
366     rRenderContext.SetFont(maLabelFont);
367     rRenderContext. DrawCtrlText(Point(aPos.X()+nXOffset, aPos.Y() + nYOffset), maMenuItems[nPos].maText, 0,
368                                  maMenuItems[nPos].maText.getLength(),
369                                  maMenuItems[nPos].mbEnabled ? DrawTextFlags::Mnemonic : DrawTextFlags::Disable);
370     rRenderContext.Pop();
371 
372     if (maMenuItems[nPos].mpSubMenuWin)
373     {
374         long nFontHeight = maLabelFont.GetFontHeight();
375         Point aMarkerPos = aPos;
376         aMarkerPos.AdjustY(aSize.Height() / 2 - nFontHeight / 4 + 1 );
377         aMarkerPos.AdjustX(aSize.Width() - nFontHeight + nFontHeight / 4 );
378         Size aMarkerSize(nFontHeight / 2, nFontHeight / 2);
379         aDecoView.DrawSymbol(tools::Rectangle(aMarkerPos, aMarkerSize), SymbolType::SPIN_RIGHT, GetTextColor());
380     }
381 }
382 
drawSeparator(vcl::RenderContext & rRenderContext,size_t nPos)383 void ScMenuFloatingWindow::drawSeparator(vcl::RenderContext& rRenderContext, size_t nPos)
384 {
385     Point aPos;
386     Size aSize;
387     getMenuItemPosSize(nPos, aPos, aSize);
388     tools::Rectangle aRegion(aPos,aSize);
389 
390     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
391     {
392         rRenderContext.Push(PushFlags::CLIPREGION);
393         rRenderContext.IntersectClipRegion(aRegion);
394         tools::Rectangle aCtrlRect(Point(0,0), GetOutputSizePixel());
395         rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect,
396                                          ControlState::ENABLED, ImplControlValue(), OUString());
397 
398         rRenderContext.Pop();
399     }
400 
401     bool bNativeDrawn = false;
402     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
403     {
404         ControlState nState = ControlState::NONE;
405         const MenuItemData& rData = maMenuItems[nPos];
406         if (rData.mbEnabled)
407             nState |= ControlState::ENABLED;
408 
409         bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
410                                                         aRegion, nState, ImplControlValue(), OUString());
411     }
412 
413     if (!bNativeDrawn)
414     {
415         const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
416         Point aTmpPos = aPos;
417         aTmpPos.AdjustY(aSize.Height() / 2 );
418         rRenderContext.SetLineColor(rStyle.GetShadowColor());
419         rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y()));
420         aTmpPos.AdjustY( 1 );
421         rRenderContext.SetLineColor(rStyle.GetLightColor());
422         rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y()));
423         rRenderContext.SetLineColor();
424     }
425 }
426 
drawAllMenuItems(vcl::RenderContext & rRenderContext)427 void ScMenuFloatingWindow::drawAllMenuItems(vcl::RenderContext& rRenderContext)
428 {
429     size_t n = maMenuItems.size();
430 
431     for (size_t i = 0; i < n; ++i)
432     {
433         if (maMenuItems[i].mbSeparator)
434         {
435             // Separator
436             drawSeparator(rRenderContext, i);
437         }
438         else
439         {
440             // Normal menu item
441             highlightMenuItem(rRenderContext, i, i == mnSelectedMenu);
442         }
443     }
444 }
445 
executeMenuItem(size_t nPos)446 void ScMenuFloatingWindow::executeMenuItem(size_t nPos)
447 {
448     if (nPos >= maMenuItems.size())
449         return;
450 
451     if (!maMenuItems[nPos].mpAction)
452         // no action is defined.
453         return;
454 
455     terminateAllPopupMenus();
456 
457     maMenuItems[nPos].mpAction->execute();
458 }
459 
setSelectedMenuItem(size_t nPos,bool bSubMenuTimer,bool bEnsureSubMenu)460 void ScMenuFloatingWindow::setSelectedMenuItem(size_t nPos, bool bSubMenuTimer, bool bEnsureSubMenu)
461 {
462     if (mnSelectedMenu == nPos)
463         // nothing to do.
464         return;
465 
466     if (bEnsureSubMenu)
467     {
468         // Dismiss any child popup menu windows.
469         if (mnSelectedMenu < maMenuItems.size() &&
470             maMenuItems[mnSelectedMenu].mpSubMenuWin &&
471             maMenuItems[mnSelectedMenu].mpSubMenuWin->IsVisible())
472         {
473             maMenuItems[mnSelectedMenu].mpSubMenuWin->ensureSubMenuNotVisible();
474         }
475 
476         // The popup is not visible, yet a menu item is selected.  The request
477         // most likely comes from the accessible object.  Make sure this
478         // window, as well as all its parent windows are visible.
479         if (!IsVisible() && mpParentMenu)
480             mpParentMenu->ensureSubMenuVisible(this);
481     }
482 
483     selectMenuItem(mnSelectedMenu, false, bSubMenuTimer);
484     selectMenuItem(nPos, true, bSubMenuTimer);
485     mnSelectedMenu = nPos;
486 
487     fireMenuHighlightedEvent();
488 }
489 
handleMenuTimeout(const SubMenuItemData * pTimer)490 void ScMenuFloatingWindow::handleMenuTimeout(const SubMenuItemData* pTimer)
491 {
492     if (pTimer == &maOpenTimer)
493     {
494         // Close any open submenu immediately.
495         if (maCloseTimer.mpSubMenu)
496         {
497             maCloseTimer.mpSubMenu->EndPopupMode();
498             maCloseTimer.mpSubMenu = nullptr;
499             maCloseTimer.maTimer.Stop();
500         }
501 
502         launchSubMenu(false);
503     }
504     else if (pTimer == &maCloseTimer)
505     {
506         // end submenu.
507         if (maCloseTimer.mpSubMenu)
508         {
509             maOpenTimer.mpSubMenu = nullptr;
510 
511             maCloseTimer.mpSubMenu->EndPopupMode();
512             maCloseTimer.mpSubMenu = nullptr;
513 
514             Invalidate();
515             maOpenTimer.mnMenuPos = MENU_NOT_SELECTED;
516         }
517     }
518 }
519 
queueLaunchSubMenu(size_t nPos,ScMenuFloatingWindow * pMenu)520 void ScMenuFloatingWindow::queueLaunchSubMenu(size_t nPos, ScMenuFloatingWindow* pMenu)
521 {
522     if (!pMenu)
523         return;
524 
525     // Set the submenu on launch queue.
526     if (maOpenTimer.mpSubMenu)
527     {
528         if (maOpenTimer.mpSubMenu == pMenu)
529         {
530             if (pMenu == maCloseTimer.mpSubMenu)
531                 maCloseTimer.reset();
532             return;
533         }
534 
535         // new submenu is being requested.
536         queueCloseSubMenu();
537     }
538 
539     maOpenTimer.mpSubMenu = pMenu;
540     maOpenTimer.mnMenuPos = nPos;
541     maOpenTimer.maTimer.Start();
542 }
543 
queueCloseSubMenu()544 void ScMenuFloatingWindow::queueCloseSubMenu()
545 {
546     if (!maOpenTimer.mpSubMenu)
547         // There is no submenu to close.
548         return;
549 
550     // Stop any submenu on queue for opening.
551     maOpenTimer.maTimer.Stop();
552 
553     maCloseTimer.mpSubMenu = maOpenTimer.mpSubMenu;
554     maCloseTimer.mnMenuPos = maOpenTimer.mnMenuPos;
555     maCloseTimer.maTimer.Start();
556 }
557 
launchSubMenu(bool bSetMenuPos)558 void ScMenuFloatingWindow::launchSubMenu(bool bSetMenuPos)
559 {
560     Point aPos;
561     Size aSize;
562     getMenuItemPosSize(maOpenTimer.mnMenuPos, aPos, aSize);
563     ScMenuFloatingWindow* pSubMenu = maOpenTimer.mpSubMenu;
564 
565     if (!pSubMenu)
566         return;
567 
568     FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
569     SetPopupModeFlags(nOldFlags | FloatWinPopupFlags::NoAppFocusClose);
570     pSubMenu->resizeToFitMenuItems(); // set the size before launching the popup to get it positioned correctly.
571     pSubMenu->StartPopupMode(
572         tools::Rectangle(aPos,aSize), (FloatWinPopupFlags::Right | FloatWinPopupFlags::GrabFocus));
573     pSubMenu->AddPopupModeWindow(this);
574     if (bSetMenuPos)
575         pSubMenu->setSelectedMenuItem(0, false, false); // select menu item after the popup becomes fully visible.
576     SetPopupModeFlags(nOldFlags);
577 }
578 
endSubMenu(ScMenuFloatingWindow * pSubMenu)579 void ScMenuFloatingWindow::endSubMenu(ScMenuFloatingWindow* pSubMenu)
580 {
581     if (!pSubMenu)
582         return;
583 
584     pSubMenu->EndPopupMode();
585     maOpenTimer.reset();
586 
587     size_t nMenuPos = getSubMenuPos(pSubMenu);
588     if (nMenuPos != MENU_NOT_SELECTED)
589     {
590         mnSelectedMenu = nMenuPos;
591         Invalidate();
592         fireMenuHighlightedEvent();
593     }
594 }
595 
fillMenuItemsToAccessible(ScAccessibleFilterMenu * pAccMenu) const596 void ScMenuFloatingWindow::fillMenuItemsToAccessible(ScAccessibleFilterMenu* pAccMenu) const
597 {
598     size_t nPos = 0;
599     for (const auto& rMenuItem : maMenuItems)
600     {
601         pAccMenu->appendMenuItem(rMenuItem.maText, nPos);
602         ++nPos;
603     }
604 }
605 
resizeToFitMenuItems()606 void ScMenuFloatingWindow::resizeToFitMenuItems()
607 {
608     SetOutputSizePixel(getMenuSize());
609 }
610 
selectMenuItem(size_t nPos,bool bSelected,bool bSubMenuTimer)611 void ScMenuFloatingWindow::selectMenuItem(size_t nPos, bool bSelected, bool bSubMenuTimer)
612 {
613     if (nPos >= maMenuItems.size() || nPos == MENU_NOT_SELECTED)
614     {
615         queueCloseSubMenu();
616         return;
617     }
618 
619     if (!maMenuItems[nPos].mbEnabled)
620     {
621         queueCloseSubMenu();
622         return;
623     }
624 
625     Invalidate();
626 
627     if (bSelected)
628     {
629         if (mpParentMenu)
630             mpParentMenu->setSubMenuFocused(this);
631 
632         if (bSubMenuTimer)
633         {
634             if (maMenuItems[nPos].mpSubMenuWin)
635             {
636                 ScMenuFloatingWindow* pSubMenu = maMenuItems[nPos].mpSubMenuWin.get();
637                 queueLaunchSubMenu(nPos, pSubMenu);
638             }
639             else
640                 queueCloseSubMenu();
641         }
642     }
643 }
644 
clearSelectedMenuItem()645 void ScMenuFloatingWindow::clearSelectedMenuItem()
646 {
647     selectMenuItem(mnSelectedMenu, false, false);
648     mnSelectedMenu = MENU_NOT_SELECTED;
649 }
650 
getSubMenuWindow(size_t nPos) const651 ScMenuFloatingWindow* ScMenuFloatingWindow::getSubMenuWindow(size_t nPos) const
652 {
653     if (maMenuItems.size() <= nPos)
654         return nullptr;
655 
656     return maMenuItems[nPos].mpSubMenuWin.get();
657 }
658 
isMenuItemSelected(size_t nPos) const659 bool ScMenuFloatingWindow::isMenuItemSelected(size_t nPos) const
660 {
661     return nPos == mnSelectedMenu;
662 }
663 
setName(const OUString & rName)664 void ScMenuFloatingWindow::setName(const OUString& rName)
665 {
666     maName = rName;
667 }
668 
highlightMenuItem(vcl::RenderContext & rRenderContext,size_t nPos,bool bSelected)669 void ScMenuFloatingWindow::highlightMenuItem(vcl::RenderContext& rRenderContext, size_t nPos, bool bSelected)
670 {
671     if (nPos == MENU_NOT_SELECTED)
672         return;
673 
674     const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
675     Color aBackColor = rStyle.GetMenuColor();
676     rRenderContext.SetFillColor(aBackColor);
677     rRenderContext.SetLineColor(aBackColor);
678 
679     Point aPos;
680     Size aSize;
681     getMenuItemPosSize(nPos, aPos, aSize);
682     tools::Rectangle aRegion(aPos,aSize);
683 
684     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
685     {
686         rRenderContext.Push(PushFlags::CLIPREGION);
687         rRenderContext.IntersectClipRegion(tools::Rectangle(aPos, aSize));
688         tools::Rectangle aCtrlRect(Point(0,0), GetOutputSizePixel());
689         rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect, ControlState::ENABLED,
690                                          ImplControlValue(), OUString());
691         rRenderContext.Pop();
692     }
693 
694     bool bNativeDrawn = true;
695     if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem))
696     {
697         ControlState nState = bSelected ? ControlState::SELECTED : ControlState::NONE;
698         if (maMenuItems[nPos].mbEnabled)
699             nState |= ControlState::ENABLED;
700         bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem,
701                                                         aRegion, nState, ImplControlValue(), OUString());
702     }
703     else
704         bNativeDrawn = false;
705 
706     if (!bNativeDrawn)
707     {
708         if (bSelected)
709         {
710             aBackColor = rStyle.GetMenuHighlightColor();
711             rRenderContext.SetFillColor(aBackColor);
712             rRenderContext.SetLineColor(aBackColor);
713         }
714         rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
715     }
716 
717     Color aTextColor = bSelected ? rStyle.GetMenuHighlightTextColor() : rStyle.GetMenuTextColor();
718     rRenderContext.SetTextColor(aTextColor);
719     drawMenuItem(rRenderContext, nPos);
720 }
721 
getMenuItemPosSize(size_t nPos,Point & rPos,Size & rSize) const722 void ScMenuFloatingWindow::getMenuItemPosSize(size_t nPos, Point& rPos, Size& rSize) const
723 {
724     size_t nCount = maMenuItems.size();
725     if (nPos >= nCount)
726         return;
727 
728     const sal_uInt16 nLeftMargin = 5;
729     const sal_uInt16 nTopMargin = 5;
730     const sal_uInt16 nMenuItemHeight = static_cast<sal_uInt16>(maLabelFont.GetFontHeight()*1.8);
731     const sal_uInt16 nSepHeight = static_cast<sal_uInt16>(maLabelFont.GetFontHeight()*0.8);
732 
733     Point aPos1(nLeftMargin, nTopMargin);
734     rPos = aPos1;
735     for (size_t i = 0; i < nPos; ++i)
736         rPos.AdjustY(maMenuItems[i].mbSeparator ? nSepHeight : nMenuItemHeight );
737 
738     Size aWndSize = GetSizePixel();
739     sal_uInt16 nH = maMenuItems[nPos].mbSeparator ? nSepHeight : nMenuItemHeight;
740     rSize = Size(aWndSize.Width() - nLeftMargin*2, nH);
741 }
742 
getEnclosingMenuItem(const Point & rPos) const743 size_t ScMenuFloatingWindow::getEnclosingMenuItem(const Point& rPos) const
744 {
745     size_t n = maMenuItems.size();
746     for (size_t i = 0; i < n; ++i)
747     {
748         Point aPos;
749         Size aSize;
750         getMenuItemPosSize(i, aPos, aSize);
751         tools::Rectangle aRect(aPos, aSize);
752         if (aRect.IsInside(rPos))
753             return maMenuItems[i].mbSeparator ? MENU_NOT_SELECTED : i;
754     }
755     return MENU_NOT_SELECTED;
756 }
757 
getSubMenuPos(const ScMenuFloatingWindow * pSubMenu)758 size_t ScMenuFloatingWindow::getSubMenuPos(const ScMenuFloatingWindow* pSubMenu)
759 {
760     size_t n = maMenuItems.size();
761     for (size_t i = 0; i < n; ++i)
762     {
763         if (maMenuItems[i].mpSubMenuWin.get() == pSubMenu)
764             return i;
765     }
766     return MENU_NOT_SELECTED;
767 }
768 
fireMenuHighlightedEvent()769 void ScMenuFloatingWindow::fireMenuHighlightedEvent()
770 {
771     if (mnSelectedMenu == MENU_NOT_SELECTED)
772         return;
773 
774     if (!mxAccessible.is())
775         return;
776 
777     Reference<XAccessibleContext> xAccCxt = mxAccessible->getAccessibleContext();
778     if (!xAccCxt.is())
779         return;
780 
781     Reference<XAccessible> xAccMenu = xAccCxt->getAccessibleChild(mnSelectedMenu);
782     if (!xAccMenu.is())
783         return;
784 
785     VclAccessibleEvent aEvent(VclEventId::MenuHighlight, xAccMenu);
786     FireVclEvent(aEvent);
787 }
788 
setSubMenuFocused(const ScMenuFloatingWindow * pSubMenu)789 void ScMenuFloatingWindow::setSubMenuFocused(const ScMenuFloatingWindow* pSubMenu)
790 {
791     maCloseTimer.reset();
792     size_t nMenuPos = getSubMenuPos(pSubMenu);
793     if (mnSelectedMenu != nMenuPos)
794     {
795         mnSelectedMenu = nMenuPos;
796         Invalidate();
797     }
798 }
799 
ensureSubMenuVisible(ScMenuFloatingWindow * pSubMenu)800 void ScMenuFloatingWindow::ensureSubMenuVisible(ScMenuFloatingWindow* pSubMenu)
801 {
802     if (mpParentMenu)
803         mpParentMenu->ensureSubMenuVisible(this);
804 
805     if (pSubMenu->IsVisible())
806         return;
807 
808     // Find the menu position of the submenu.
809     size_t nMenuPos = getSubMenuPos(pSubMenu);
810     if (nMenuPos != MENU_NOT_SELECTED)
811     {
812         setSelectedMenuItem(nMenuPos, false, false);
813 
814         Point aPos;
815         Size aSize;
816         getMenuItemPosSize(nMenuPos, aPos, aSize);
817 
818         FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
819         SetPopupModeFlags(nOldFlags | FloatWinPopupFlags::NoAppFocusClose);
820         pSubMenu->resizeToFitMenuItems(); // set the size before launching the popup to get it positioned correctly.
821         pSubMenu->StartPopupMode(
822             tools::Rectangle(aPos,aSize), (FloatWinPopupFlags::Right | FloatWinPopupFlags::GrabFocus));
823         pSubMenu->AddPopupModeWindow(this);
824         SetPopupModeFlags(nOldFlags);
825     }
826 }
827 
ensureSubMenuNotVisible()828 void ScMenuFloatingWindow::ensureSubMenuNotVisible()
829 {
830     if (mnSelectedMenu < maMenuItems.size() &&
831         maMenuItems[mnSelectedMenu].mpSubMenuWin &&
832         maMenuItems[mnSelectedMenu].mpSubMenuWin->IsVisible())
833     {
834         maMenuItems[mnSelectedMenu].mpSubMenuWin->ensureSubMenuNotVisible();
835     }
836 
837     EndPopupMode();
838 }
839 
terminateAllPopupMenus()840 void ScMenuFloatingWindow::terminateAllPopupMenus()
841 {
842     EndPopupMode();
843     if (mpParentMenu)
844         mpParentMenu->terminateAllPopupMenus();
845 }
846 
Config()847 ScCheckListMenuWindow::Config::Config() :
848     mbAllowEmptySet(true), mbRTL(false)
849 {
850 }
851 
ScCheckListMember()852 ScCheckListMember::ScCheckListMember()
853     : mbVisible(true)
854     , mbDate(false)
855     , mbLeaf(false)
856     , meDatePartType(YEAR)
857     , mpParent(nullptr)
858 {
859 }
860 
CancelButton(ScCheckListMenuWindow * pParent)861 ScCheckListMenuWindow::CancelButton::CancelButton(ScCheckListMenuWindow* pParent) :
862     ::CancelButton(pParent), mpParent(pParent) {}
863 
~CancelButton()864 ScCheckListMenuWindow::CancelButton::~CancelButton()
865 {
866     disposeOnce();
867 }
868 
dispose()869 void ScCheckListMenuWindow::CancelButton::dispose()
870 {
871     mpParent.clear();
872     ::CancelButton::dispose();
873 }
874 
Click()875 void ScCheckListMenuWindow::CancelButton::Click()
876 {
877     mpParent->EndPopupMode();
878     ::CancelButton::Click();
879 }
880 
ScCheckListMenuWindow(vcl::Window * pParent,ScDocument * pDoc,int nWidth)881 ScCheckListMenuWindow::ScCheckListMenuWindow(vcl::Window* pParent, ScDocument* pDoc, int nWidth) :
882     ScMenuFloatingWindow(pParent, pDoc),
883     maEdSearch(VclPtr<ScSearchEdit>::Create(this)),
884     maChecks(VclPtr<ScCheckListBox>::Create(this)),
885     maChkToggleAll(VclPtr<TriStateBox>::Create(this, 0)),
886     maBtnSelectSingle(VclPtr<ImageButton>::Create(this, 0)),
887     maBtnUnselectSingle(VclPtr<ImageButton>::Create(this, 0)),
888     maBtnOk(VclPtr<OKButton>::Create(this)),
889     maBtnCancel(VclPtr<CancelButton>::Create(this)),
890     maWndSize(),
891     mePrevToggleAllState(TRISTATE_INDET),
892     maTabStops(this)
893 {
894     float fScaleFactor = GetDPIScaleFactor();
895 
896     nWidth = std::max<int>(nWidth, 200 * fScaleFactor);
897     maWndSize = Size(nWidth, 330 * fScaleFactor);
898 
899     maTabStops.AddTabStop( this );
900     maTabStops.AddTabStop( maEdSearch.get() );
901     maTabStops.AddTabStop( maChecks.get() );
902     maTabStops.AddTabStop( maChkToggleAll.get() );
903     maTabStops.AddTabStop( maBtnSelectSingle.get() );
904     maTabStops.AddTabStop( maBtnUnselectSingle.get() );
905     maTabStops.AddTabStop( maBtnOk.get() );
906     maTabStops.AddTabStop( maBtnCancel.get() );
907 
908     maEdSearch->SetTabStopsContainer( &maTabStops );
909     maChecks->SetTabStopsContainer( &maTabStops );
910 
911     set_id("check_list_menu");
912     maChkToggleAll->set_id("toggle_all");
913     maBtnSelectSingle->set_id("select_current");
914     maBtnUnselectSingle->set_id("unselect_current");
915 }
916 
~ScCheckListMenuWindow()917 ScCheckListMenuWindow::~ScCheckListMenuWindow()
918 {
919     disposeOnce();
920 }
921 
dispose()922 void ScCheckListMenuWindow::dispose()
923 {
924     maTabStops.clear();
925     maEdSearch.disposeAndClear();
926     maChecks.disposeAndClear();
927     maChkToggleAll.disposeAndClear();
928     maBtnSelectSingle.disposeAndClear();
929     maBtnUnselectSingle.disposeAndClear();
930     maBtnOk.disposeAndClear();
931     maBtnCancel.disposeAndClear();
932     ScMenuFloatingWindow::dispose();
933 }
934 
getSectionPosSize(Point & rPos,Size & rSize,SectionType eType) const935 void ScCheckListMenuWindow::getSectionPosSize(
936     Point& rPos, Size& rSize, SectionType eType) const
937 {
938     float fScaleFactor = GetDPIScaleFactor();
939 
940     // constant parameters.
941     const long nSearchBoxMargin = 10 *fScaleFactor;
942     const long nListBoxMargin = 5 * fScaleFactor;            // horizontal distance from the side of the dialog to the listbox border.
943     const long nListBoxInnerPadding = 5 * fScaleFactor;
944     const long nTopMargin = 5 * fScaleFactor;
945     const long nMenuHeight = maMenuSize.getHeight();
946     const long nSingleItemBtnAreaHeight = 32 * fScaleFactor; // height of the middle area below the list box where the single-action buttons are.
947     const long nBottomBtnAreaHeight = 50 * fScaleFactor;     // height of the bottom area where the OK and Cancel buttons are.
948     const long nBtnWidth = 90 * fScaleFactor;
949     const long nLabelHeight = getLabelFont().GetFontHeight();
950     const long nBtnHeight = nLabelHeight * 2;
951     const long nBottomMargin = 10 * fScaleFactor;
952     const long nMenuListMargin = 5 * fScaleFactor;
953     const long nSearchBoxHeight = nLabelHeight * 2;
954 
955     // parameters calculated from constants.
956     const long nListBoxWidth = maWndSize.Width() - nListBoxMargin*2;
957     const long nListBoxHeight = maWndSize.Height() - nTopMargin - nMenuHeight -
958         nMenuListMargin - nSearchBoxHeight - nSearchBoxMargin - nSingleItemBtnAreaHeight - nBottomBtnAreaHeight;
959 
960     const long nSingleBtnAreaY = nTopMargin + nMenuHeight + nMenuListMargin + nSearchBoxHeight + nSearchBoxMargin;
961 
962     switch (eType)
963     {
964         case WHOLE:
965         {
966             rPos  = Point(0, 0);
967             rSize = maWndSize;
968         }
969         break;
970         case EDIT_SEARCH:
971         {
972             rPos = Point(nSearchBoxMargin, nTopMargin + nMenuHeight + nMenuListMargin);
973             rSize = Size(maWndSize.Width() - 2*nSearchBoxMargin, nSearchBoxHeight);
974         }
975         break;
976         case SINGLE_BTN_AREA:
977         {
978             rPos = Point(nListBoxMargin, nSingleBtnAreaY);
979             rSize = Size(nListBoxWidth, nSingleItemBtnAreaHeight);
980         }
981         break;
982         case CHECK_TOGGLE_ALL:
983         {
984             long h = std::min(maChkToggleAll->CalcMinimumSize().Height(), 26L);
985             rPos = Point(nListBoxMargin, nSingleBtnAreaY);
986             rPos.AdjustX(5 );
987             rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
988             rSize = Size(70, h);
989         }
990         break;
991         case BTN_SINGLE_SELECT:
992         {
993             long h = 26 * fScaleFactor;
994             rPos = Point(nListBoxMargin, nSingleBtnAreaY);
995             rPos.AdjustX(nListBoxWidth - h - 10 - h - 10 );
996             rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
997             rSize = Size(h, h);
998         }
999         break;
1000         case BTN_SINGLE_UNSELECT:
1001         {
1002             long h = 26 * fScaleFactor;
1003             rPos = Point(nListBoxMargin, nSingleBtnAreaY);
1004             rPos.AdjustX(nListBoxWidth - h - 10 );
1005             rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
1006             rSize = Size(h, h);
1007         }
1008         break;
1009         case LISTBOX_AREA_OUTER:
1010         {
1011             rPos = Point(nListBoxMargin, nSingleBtnAreaY + nSingleItemBtnAreaHeight-1);
1012             rSize = Size(nListBoxWidth, nListBoxHeight);
1013         }
1014         break;
1015         case LISTBOX_AREA_INNER:
1016         {
1017             rPos = Point(nListBoxMargin, nSingleBtnAreaY + nSingleItemBtnAreaHeight-1);
1018             rPos.AdjustX(nListBoxInnerPadding );
1019             rPos.AdjustY(nListBoxInnerPadding );
1020 
1021             rSize = Size(nListBoxWidth, nListBoxHeight);
1022             rSize.AdjustWidth( -(nListBoxInnerPadding*2) );
1023             rSize.AdjustHeight( -(nListBoxInnerPadding*2) );
1024         }
1025         break;
1026         case BTN_OK:
1027         {
1028             long x = (maWndSize.Width() - nBtnWidth*2)/3;
1029             long y = maWndSize.Height() - nBottomMargin - nBtnHeight;
1030             rPos = Point(x, y);
1031             rSize = Size(nBtnWidth, nBtnHeight);
1032         }
1033         break;
1034         case BTN_CANCEL:
1035         {
1036             long x = (maWndSize.Width() - nBtnWidth*2)/3*2 + nBtnWidth;
1037             long y = maWndSize.Height() - nBottomMargin - nBtnHeight;
1038             rPos = Point(x, y);
1039             rSize = Size(nBtnWidth, nBtnHeight);
1040         }
1041         break;
1042         default:
1043             ;
1044     }
1045 }
1046 
packWindow()1047 void ScCheckListMenuWindow::packWindow()
1048 {
1049     maMenuSize = getMenuSize();
1050 
1051     if (maWndSize.Width() < maMenuSize.Width())
1052         // Widen the window to fit the menu items.
1053         maWndSize.setWidth( maMenuSize.Width() );
1054 
1055     // Set proper window height based on the number of menu items.
1056     if (maWndSize.Height() < maMenuSize.Height()*2.8)
1057         maWndSize.setHeight( maMenuSize.Height()*2.8 );
1058 
1059     // TODO: Make sure the window height never exceeds the height of the
1060     // screen. Also do adjustment based on the number of check box items.
1061 
1062     SetOutputSizePixel(maWndSize);
1063 
1064     const StyleSettings& rStyle = GetSettings().GetStyleSettings();
1065 
1066     Point aPos;
1067     Size aSize;
1068     getSectionPosSize(aPos, aSize, WHOLE);
1069     SetOutputSizePixel(aSize);
1070 
1071     getSectionPosSize(aPos, aSize, BTN_OK);
1072     maBtnOk->SetPosSizePixel(aPos, aSize);
1073     maBtnOk->SetFont(getLabelFont());
1074     maBtnOk->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
1075     maBtnOk->Show();
1076 
1077     getSectionPosSize(aPos, aSize, BTN_CANCEL);
1078     maBtnCancel->SetPosSizePixel(aPos, aSize);
1079     maBtnCancel->SetFont(getLabelFont());
1080     maBtnCancel->Show();
1081 
1082     getSectionPosSize(aPos, aSize, EDIT_SEARCH);
1083     maEdSearch->SetPosSizePixel(aPos, aSize);
1084     maEdSearch->SetFont(getLabelFont());
1085     maEdSearch->SetControlBackground(rStyle.GetFieldColor());
1086     maEdSearch->SetPlaceholderText(ScResId(STR_EDIT_SEARCH_ITEMS));
1087     maEdSearch->SetModifyHdl( LINK(this, ScCheckListMenuWindow, EdModifyHdl) );
1088     maEdSearch->Show();
1089 
1090     getSectionPosSize(aPos, aSize, LISTBOX_AREA_INNER);
1091     maChecks->SetPosSizePixel(aPos, aSize);
1092     maChecks->SetFont(getLabelFont());
1093     maChecks->SetCheckButtonHdl( LINK(this, ScCheckListMenuWindow, CheckHdl) );
1094     maChecks->Show();
1095 
1096     getSectionPosSize(aPos, aSize, CHECK_TOGGLE_ALL);
1097     maChkToggleAll->SetPosSizePixel(aPos, aSize);
1098     maChkToggleAll->SetFont(getLabelFont());
1099     maChkToggleAll->SetText(ScResId(STR_BTN_TOGGLE_ALL));
1100     maChkToggleAll->SetTextColor(rStyle.GetMenuTextColor());
1101     maChkToggleAll->SetControlBackground(rStyle.GetMenuColor());
1102     maChkToggleAll->SetClickHdl( LINK(this, ScCheckListMenuWindow, TriStateHdl) );
1103     maChkToggleAll->Show();
1104 
1105     float fScaleFactor = GetDPIScaleFactor();
1106 
1107     ;
1108 
1109     getSectionPosSize(aPos, aSize, BTN_SINGLE_SELECT);
1110     maBtnSelectSingle->SetPosSizePixel(aPos, aSize);
1111     maBtnSelectSingle->SetQuickHelpText(ScResId(STR_BTN_SELECT_CURRENT));
1112     maBtnSelectSingle->SetModeImage(Image(StockImage::Yes, RID_BMP_SELECT_CURRENT));
1113     maBtnSelectSingle->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
1114     maBtnSelectSingle->Show();
1115 
1116     BitmapEx aSingleUnselectBmp(RID_BMP_UNSELECT_CURRENT);
1117     if (fScaleFactor > 1)
1118         aSingleUnselectBmp.Scale(fScaleFactor, fScaleFactor, BmpScaleFlag::Fast);
1119     Image aSingleUnselect(aSingleUnselectBmp);
1120 
1121     getSectionPosSize(aPos, aSize, BTN_SINGLE_UNSELECT);
1122     maBtnUnselectSingle->SetPosSizePixel(aPos, aSize);
1123     maBtnUnselectSingle->SetQuickHelpText(ScResId(STR_BTN_UNSELECT_CURRENT));
1124     maBtnUnselectSingle->SetModeImage(aSingleUnselect);
1125     maBtnUnselectSingle->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
1126     maBtnUnselectSingle->Show();
1127 }
1128 
setAllMemberState(bool bSet)1129 void ScCheckListMenuWindow::setAllMemberState(bool bSet)
1130 {
1131     size_t n = maMembers.size();
1132     std::set<SvTreeListEntry*> aParents;
1133     for (size_t i = 0; i < n; ++i)
1134     {
1135         aParents.insert(maMembers[i].mpParent);
1136     }
1137 
1138     for (const auto& pParent : aParents)
1139     {
1140         if (!pParent)
1141         {
1142             sal_uInt32 nCount = maChecks->GetEntryCount();
1143             for( sal_uInt32 i = 0; i < nCount; ++i)
1144             {
1145                 SvTreeListEntry* pEntry = maChecks->GetEntry(i);
1146                 if (!pEntry)
1147                     continue;
1148 
1149                 maChecks->CheckEntry(pEntry, bSet);
1150             }
1151         }
1152         else
1153         {
1154             SvTreeListEntries& rEntries = pParent->GetChildEntries();
1155             for (const auto& rxEntry : rEntries)
1156             {
1157                 maChecks->CheckEntry(rxEntry.get(), bSet);
1158             }
1159         }
1160     }
1161 
1162     if (!maConfig.mbAllowEmptySet)
1163         // We need to have at least one member selected.
1164         maBtnOk->Enable(maChecks->GetCheckedEntryCount() != 0);
1165 }
1166 
selectCurrentMemberOnly(bool bSet)1167 void ScCheckListMenuWindow::selectCurrentMemberOnly(bool bSet)
1168 {
1169     setAllMemberState(!bSet);
1170     SvTreeListEntry* pEntry = maChecks->GetCurEntry();
1171     if (!pEntry)
1172         return;
1173     maChecks->CheckEntry(pEntry, bSet );
1174 
1175     // Make sure all checkboxes are invalidated.
1176     Invalidate();
1177 }
1178 
IMPL_LINK(ScCheckListMenuWindow,ButtonHdl,Button *,pBtn,void)1179 IMPL_LINK( ScCheckListMenuWindow, ButtonHdl, Button*, pBtn, void )
1180 {
1181     if (pBtn == maBtnOk.get())
1182         close(true);
1183     else if (pBtn == maBtnSelectSingle.get())
1184     {
1185         selectCurrentMemberOnly(true);
1186         CheckHdl(maChecks.get());
1187     }
1188     else if (pBtn == maBtnUnselectSingle.get())
1189     {
1190         selectCurrentMemberOnly(false);
1191         CheckHdl(maChecks.get());
1192     }
1193 }
1194 
IMPL_LINK_NOARG(ScCheckListMenuWindow,TriStateHdl,Button *,void)1195 IMPL_LINK_NOARG(ScCheckListMenuWindow, TriStateHdl, Button*, void)
1196 {
1197     switch (mePrevToggleAllState)
1198     {
1199         case TRISTATE_FALSE:
1200             maChkToggleAll->SetState(TRISTATE_TRUE);
1201             setAllMemberState(true);
1202         break;
1203         case TRISTATE_TRUE:
1204             maChkToggleAll->SetState(TRISTATE_FALSE);
1205             setAllMemberState(false);
1206         break;
1207         case TRISTATE_INDET:
1208         default:
1209             maChkToggleAll->SetState(TRISTATE_TRUE);
1210             setAllMemberState(true);
1211         break;
1212     }
1213 
1214     mePrevToggleAllState = maChkToggleAll->GetState();
1215     maTabStops.SetTabStop(maChkToggleAll); // Needed for when accelerator is used
1216 }
1217 
IMPL_LINK_NOARG(ScCheckListMenuWindow,EdModifyHdl,Edit &,void)1218 IMPL_LINK_NOARG(ScCheckListMenuWindow, EdModifyHdl, Edit&, void)
1219 {
1220     OUString aSearchText = maEdSearch->GetText();
1221     aSearchText = ScGlobal::pCharClass->lowercase( aSearchText );
1222     bool bSearchTextEmpty = aSearchText.isEmpty();
1223     size_t n = maMembers.size();
1224     size_t nSelCount = 0;
1225     OUString aLabelDisp;
1226     bool bSomeDateDeletes = false;
1227 
1228     for (size_t i = 0; i < n; ++i)
1229     {
1230         bool bIsDate = maMembers[i].mbDate;
1231         bool bPartialMatch = false;
1232 
1233         aLabelDisp = maMembers[i].maName;
1234         if ( aLabelDisp.isEmpty() )
1235             aLabelDisp = ScResId( STR_EMPTYDATA );
1236 
1237         if ( !bSearchTextEmpty )
1238         {
1239             if ( !bIsDate )
1240                 bPartialMatch = ( ScGlobal::pCharClass->lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
1241             else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
1242                 bPartialMatch = (ScGlobal::pCharClass->lowercase( OUString(
1243                                 maMembers[i].maRealName + maMembers[i].maDateParts[1] )).indexOf( aSearchText ) != -1);
1244             else
1245                 continue;
1246         }
1247         else if ( bIsDate && maMembers[i].meDatePartType != ScCheckListMember::DAY )
1248             continue;
1249 
1250         if ( bSearchTextEmpty )
1251         {
1252             SvTreeListEntry* pLeaf = maChecks->ShowCheckEntry( aLabelDisp, maMembers[i], true, maMembers[i].mbVisible );
1253             updateMemberParents( pLeaf, i );
1254             if ( maMembers[i].mbVisible )
1255                 ++nSelCount;
1256             continue;
1257         }
1258 
1259         if ( bPartialMatch )
1260         {
1261             SvTreeListEntry* pLeaf = maChecks->ShowCheckEntry( aLabelDisp, maMembers[i] );
1262             updateMemberParents( pLeaf, i );
1263             ++nSelCount;
1264         }
1265         else
1266         {
1267             maChecks->ShowCheckEntry( aLabelDisp, maMembers[i], false, false );
1268             if( bIsDate )
1269                 bSomeDateDeletes = true;
1270         }
1271     }
1272 
1273     if ( bSomeDateDeletes )
1274     {
1275         for (size_t i = 0; i < n; ++i)
1276         {
1277             if ( !maMembers[i].mbDate ) continue;
1278             if ( maMembers[i].meDatePartType != ScCheckListMember::DAY ) continue;
1279             updateMemberParents( nullptr, i );
1280         }
1281     }
1282 
1283     if ( nSelCount == n )
1284         maChkToggleAll->SetState( TRISTATE_TRUE );
1285     else if ( nSelCount == 0 )
1286         maChkToggleAll->SetState( TRISTATE_FALSE );
1287     else
1288         maChkToggleAll->SetState( TRISTATE_INDET );
1289 
1290     if ( !maConfig.mbAllowEmptySet )
1291     {
1292         const bool bEmptySet( nSelCount == 0 );
1293         maChecks->Enable( !bEmptySet );
1294         maChkToggleAll->Enable( !bEmptySet );
1295         maBtnSelectSingle->Enable( !bEmptySet );
1296         maBtnUnselectSingle->Enable( !bEmptySet );
1297         maBtnOk->Enable( !bEmptySet );
1298     }
1299 }
1300 
IMPL_LINK(ScCheckListMenuWindow,CheckHdl,SvTreeListBox *,pChecks,void)1301 IMPL_LINK( ScCheckListMenuWindow, CheckHdl, SvTreeListBox*, pChecks, void )
1302 {
1303     if (pChecks != maChecks.get())
1304         return;
1305     SvTreeListEntry* pEntry = pChecks->GetHdlEntry();
1306     if ( pEntry )
1307         maChecks->CheckEntry( pEntry,  ( pChecks->GetCheckButtonState( pEntry ) == SvButtonState::Checked ) );
1308     size_t nNumChecked = maChecks->GetCheckedEntryCount();
1309     if (nNumChecked == maMembers.size())
1310         // all members visible
1311         maChkToggleAll->SetState(TRISTATE_TRUE);
1312     else if (nNumChecked == 0)
1313         // no members visible
1314         maChkToggleAll->SetState(TRISTATE_FALSE);
1315     else
1316         maChkToggleAll->SetState(TRISTATE_INDET);
1317 
1318     if (!maConfig.mbAllowEmptySet)
1319         // We need to have at least one member selected.
1320         maBtnOk->Enable(nNumChecked != 0);
1321 
1322     mePrevToggleAllState = maChkToggleAll->GetState();
1323 }
1324 
MouseMove(const MouseEvent & rMEvt)1325 void ScCheckListMenuWindow::MouseMove(const MouseEvent& rMEvt)
1326 {
1327     ScMenuFloatingWindow::MouseMove(rMEvt);
1328 
1329     size_t nSelectedMenu = getSelectedMenuItem();
1330     if (nSelectedMenu == MENU_NOT_SELECTED)
1331         queueCloseSubMenu();
1332 }
1333 
EventNotify(NotifyEvent & rNEvt)1334 bool ScCheckListMenuWindow::EventNotify(NotifyEvent& rNEvt)
1335 {
1336     MouseNotifyEvent nType = rNEvt.GetType();
1337     if (HasFocus() && nType == MouseNotifyEvent::GETFOCUS)
1338     {
1339         setSelectedMenuItem( 0 , false, false );
1340         return true;
1341     }
1342     if (nType == MouseNotifyEvent::KEYINPUT)
1343     {
1344         const KeyEvent* pKeyEvent = rNEvt.GetKeyEvent();
1345         const vcl::KeyCode& rCode = pKeyEvent->GetKeyCode();
1346         const sal_uInt16 nCode = rCode.GetCode();
1347         if (nCode != KEY_RETURN)
1348         {
1349             bool bShift = rCode.IsShift();
1350             if (nCode == KEY_TAB)
1351                 maTabStops.CycleFocus(bShift);
1352             return true;
1353         }
1354     }
1355     return ScMenuFloatingWindow::EventNotify(rNEvt);
1356 }
1357 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)1358 void ScCheckListMenuWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
1359 {
1360     ScMenuFloatingWindow::Paint(rRenderContext, rRect);
1361 
1362     const StyleSettings& rStyle = GetSettings().GetStyleSettings();
1363     Color aMemberBackColor = rStyle.GetFieldColor();
1364     Color aBorderColor = rStyle.GetShadowColor();
1365 
1366     Point aPos;
1367     Size aSize;
1368     getSectionPosSize(aPos, aSize, LISTBOX_AREA_OUTER);
1369 
1370     // Member list box background
1371     rRenderContext.SetFillColor(aMemberBackColor);
1372     rRenderContext.SetLineColor(aBorderColor);
1373     rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
1374 
1375     // Single-action button box
1376     getSectionPosSize(aPos, aSize, SINGLE_BTN_AREA);
1377     rRenderContext.SetFillColor(rStyle.GetMenuColor());
1378     rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
1379 }
1380 
updateMemberParents(const SvTreeListEntry * pLeaf,size_t nIdx)1381 void ScCheckListMenuWindow::updateMemberParents( const SvTreeListEntry* pLeaf, size_t nIdx )
1382 {
1383 
1384     if ( !maMembers[nIdx].mbDate || maMembers[nIdx].meDatePartType != ScCheckListMember::DAY )
1385         return;
1386 
1387     OUString aYearName  = maMembers[nIdx].maDateParts[0];
1388     OUString aMonthName = maMembers[nIdx].maDateParts[1];
1389     auto aItr = maYearMonthMap.find(aYearName + aMonthName);
1390 
1391     if ( pLeaf )
1392     {
1393         SvTreeListEntry* pMonthEntry = pLeaf->GetParent();
1394         SvTreeListEntry* pYearEntry = pMonthEntry ? pMonthEntry->GetParent() : nullptr;
1395 
1396         maMembers[nIdx].mpParent = pMonthEntry;
1397         if ( aItr != maYearMonthMap.end() )
1398         {
1399             size_t nMonthIdx = aItr->second;
1400             maMembers[nMonthIdx].mpParent = pYearEntry;
1401         }
1402     }
1403     else
1404     {
1405         SvTreeListEntry* pYearEntry = maChecks->FindEntry( nullptr, aYearName );
1406         if ( aItr != maYearMonthMap.end() && !pYearEntry )
1407         {
1408             size_t nMonthIdx = aItr->second;
1409             maMembers[nMonthIdx].mpParent = nullptr;
1410             maMembers[nIdx].mpParent = nullptr;
1411         }
1412         else if ( pYearEntry && !maChecks->FindEntry( pYearEntry, aMonthName ) )
1413             maMembers[nIdx].mpParent = nullptr;
1414     }
1415 }
1416 
CreateAccessible()1417 Reference<XAccessible> ScCheckListMenuWindow::CreateAccessible()
1418 {
1419     if (!mxAccessible.is() && maEdSearch)
1420     {
1421         mxAccessible.set(new ScAccessibleFilterTopWindow(
1422             GetAccessibleParentWindow()->GetAccessible(), this, getName()));
1423         ScAccessibleFilterTopWindow* pAccTop = static_cast<ScAccessibleFilterTopWindow*>(mxAccessible.get());
1424         fillMenuItemsToAccessible(pAccTop);
1425 
1426         pAccTop->setAccessibleChild(
1427             maEdSearch->CreateAccessible(), ScAccessibleFilterTopWindow::EDIT_SEARCH_BOX);
1428         pAccTop->setAccessibleChild(
1429             maChecks->CreateAccessible(), ScAccessibleFilterTopWindow::LISTBOX);
1430         pAccTop->setAccessibleChild(
1431             maChkToggleAll->CreateAccessible(), ScAccessibleFilterTopWindow::TOGGLE_ALL);
1432         pAccTop->setAccessibleChild(
1433             maBtnSelectSingle->CreateAccessible(), ScAccessibleFilterTopWindow::SINGLE_ON_BTN);
1434         pAccTop->setAccessibleChild(
1435             maBtnUnselectSingle->CreateAccessible(), ScAccessibleFilterTopWindow::SINGLE_OFF_BTN);
1436         pAccTop->setAccessibleChild(
1437             maBtnOk->CreateAccessible(), ScAccessibleFilterTopWindow::OK_BTN);
1438         pAccTop->setAccessibleChild(
1439             maBtnCancel->CreateAccessible(), ScAccessibleFilterTopWindow::CANCEL_BTN);
1440     }
1441 
1442     return mxAccessible;
1443 }
1444 
setMemberSize(size_t n)1445 void ScCheckListMenuWindow::setMemberSize(size_t n)
1446 {
1447     maMembers.reserve(n);
1448 }
1449 
addDateMember(const OUString & rsName,double nVal,bool bVisible)1450 void ScCheckListMenuWindow::addDateMember(const OUString& rsName, double nVal, bool bVisible)
1451 {
1452     ScDocument* pDoc = getDoc();
1453     SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
1454 
1455     // Convert the numeric date value to a date object.
1456     Date aDate = pFormatter->GetNullDate();
1457     aDate.AddDays(rtl::math::approxFloor(nVal));
1458 
1459     sal_Int16 nYear = aDate.GetYear();
1460     sal_uInt16 nMonth = aDate.GetMonth();
1461     sal_uInt16 nDay = aDate.GetDay();
1462 
1463     // Get the localized month name list.
1464     CalendarWrapper* pCalendar = ScGlobal::GetCalendar();
1465     uno::Sequence<i18n::CalendarItem2> aMonths = pCalendar->getMonths();
1466     if (aMonths.getLength() < nMonth)
1467         return;
1468 
1469     OUString aYearName = OUString::number(nYear);
1470     OUString aMonthName = aMonths[nMonth-1].FullName;
1471     OUString aDayName = OUString::number(nDay);
1472 
1473     if ( aDayName.getLength() == 1 )
1474         aDayName = "0" + aDayName;
1475 
1476     maChecks->SetUpdateMode(false);
1477 
1478     SvTreeListEntry* pYearEntry = maChecks->FindEntry(nullptr, aYearName);
1479     if (!pYearEntry)
1480     {
1481         pYearEntry = maChecks->InsertEntry(aYearName, nullptr, true);
1482         ScCheckListMember aMemYear;
1483         aMemYear.maName = aYearName;
1484         aMemYear.maRealName = rsName;
1485         aMemYear.mbDate = true;
1486         aMemYear.mbLeaf = false;
1487         aMemYear.mbVisible = bVisible;
1488         aMemYear.mpParent = nullptr;
1489         aMemYear.meDatePartType = ScCheckListMember::YEAR;
1490         maMembers.push_back(aMemYear);
1491     }
1492 
1493     SvTreeListEntry* pMonthEntry = maChecks->FindEntry(pYearEntry, aMonthName);
1494     if (!pMonthEntry)
1495     {
1496         pMonthEntry = maChecks->InsertEntry(aMonthName, pYearEntry, true);
1497         ScCheckListMember aMemMonth;
1498         aMemMonth.maName = aMonthName;
1499         aMemMonth.maRealName = rsName;
1500         aMemMonth.mbDate = true;
1501         aMemMonth.mbLeaf = false;
1502         aMemMonth.mbVisible = bVisible;
1503         aMemMonth.mpParent = pYearEntry;
1504         aMemMonth.meDatePartType = ScCheckListMember::MONTH;
1505         maMembers.push_back(aMemMonth);
1506         maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
1507     }
1508 
1509     SvTreeListEntry* pDayEntry = maChecks->FindEntry(pMonthEntry, aDayName);
1510     if (!pDayEntry)
1511     {
1512         maChecks->InsertEntry(aDayName, pMonthEntry);
1513         ScCheckListMember aMemDay;
1514         aMemDay.maName = aDayName;
1515         aMemDay.maRealName = rsName;
1516         aMemDay.maDateParts.resize(2);
1517         aMemDay.maDateParts[0] = aYearName;
1518         aMemDay.maDateParts[1] = aMonthName;
1519         aMemDay.mbDate = true;
1520         aMemDay.mbLeaf = true;
1521         aMemDay.mbVisible = bVisible;
1522         aMemDay.mpParent = pMonthEntry;
1523         aMemDay.meDatePartType = ScCheckListMember::DAY;
1524         maMembers.push_back(aMemDay);
1525     }
1526 
1527     maChecks->SetUpdateMode(true);
1528 }
1529 
addMember(const OUString & rName,bool bVisible)1530 void ScCheckListMenuWindow::addMember(const OUString& rName, bool bVisible)
1531 {
1532     ScCheckListMember aMember;
1533     aMember.maName = rName;
1534     aMember.mbDate = false;
1535     aMember.mbLeaf = true;
1536     aMember.mbVisible = bVisible;
1537     aMember.mpParent = nullptr;
1538     maMembers.push_back(aMember);
1539 }
1540 
ScTabStops(ScCheckListMenuWindow * pMenuWin)1541 ScTabStops::ScTabStops( ScCheckListMenuWindow* pMenuWin ) :
1542     mpMenuWindow( pMenuWin ),
1543     maControlToPos( ControlToPosMap() ),
1544     mnCurTabStop(0)
1545 {
1546     maControls.reserve( 8 );
1547 }
1548 
~ScTabStops()1549 ScTabStops::~ScTabStops()
1550 {}
1551 
AddTabStop(vcl::Window * pWin)1552 void ScTabStops::AddTabStop( vcl::Window* pWin )
1553 {
1554     maControls.emplace_back(pWin );
1555     maControlToPos[pWin] = maControls.size() - 1;
1556 }
1557 
SetTabStop(vcl::Window * pWin)1558 void ScTabStops::SetTabStop( vcl::Window* pWin )
1559 {
1560     if ( maControls.empty() )
1561         return;
1562     ControlToPosMap::const_iterator aIter = maControlToPos.find( pWin );
1563     if ( aIter == maControlToPos.end() )
1564         return;
1565     if ( aIter->second == mnCurTabStop )
1566         return;
1567     if ( mnCurTabStop < maControls.size() )
1568     {
1569         maControls[mnCurTabStop]->SetFakeFocus( false );
1570         maControls[mnCurTabStop]->LoseFocus();
1571     }
1572     mnCurTabStop = aIter->second;
1573     maControls[mnCurTabStop]->SetFakeFocus( true );
1574     maControls[mnCurTabStop]->GrabFocus();
1575 }
1576 
CycleFocus(bool bReverse)1577 void ScTabStops::CycleFocus( bool bReverse )
1578 {
1579     if (maControls.empty())
1580         return;
1581     if ( mnCurTabStop < maControls.size() )
1582     {
1583         maControls[mnCurTabStop]->SetFakeFocus( false );
1584         maControls[mnCurTabStop]->LoseFocus();
1585     }
1586     else
1587         mnCurTabStop = 0;
1588 
1589     if ( mpMenuWindow && mnCurTabStop == 0 )
1590         mpMenuWindow->clearSelectedMenuItem();
1591 
1592     size_t nIterCount = 0;
1593 
1594     if ( bReverse )
1595     {
1596         do
1597         {
1598             if ( mnCurTabStop > 0 )
1599                 --mnCurTabStop;
1600             else
1601                 mnCurTabStop = maControls.size() - 1;
1602             ++nIterCount;
1603         } while ( nIterCount <= maControls.size() && !maControls[mnCurTabStop]->IsEnabled() );
1604     }
1605     else
1606     {
1607         do
1608         {
1609             ++mnCurTabStop;
1610             if ( mnCurTabStop >= maControls.size() )
1611                 mnCurTabStop = 0;
1612             ++nIterCount;
1613         } while ( nIterCount <= maControls.size() && !maControls[mnCurTabStop]->IsEnabled() );
1614     }
1615 
1616     if ( nIterCount <= maControls.size() )
1617     {
1618         maControls[mnCurTabStop]->SetFakeFocus( true );
1619         maControls[mnCurTabStop]->GrabFocus();
1620     }
1621     // else : all controls are disabled, so can't do anything
1622 }
1623 
clear()1624 void ScTabStops::clear()
1625 {
1626     mnCurTabStop = 0;
1627     maControlToPos.clear();
1628     maControls.clear();
1629 }
1630 
ScCheckListBox(vcl::Window * pParent)1631 ScCheckListBox::ScCheckListBox( vcl::Window* pParent )
1632     :  SvTreeListBox( pParent, 0 ), mbSeenMouseButtonDown( false )
1633 {
1634     Init();
1635     set_id("check_list_box");
1636 }
1637 
FindEntry(SvTreeListEntry * pParent,const OUString & sNode)1638 SvTreeListEntry* ScCheckListBox::FindEntry( SvTreeListEntry* pParent, const OUString& sNode )
1639 {
1640     sal_uInt32 nRootPos = 0;
1641     SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : GetEntry( nRootPos );
1642     while ( pEntry )
1643     {
1644         if ( sNode == GetEntryText( pEntry ) )
1645             return pEntry;
1646 
1647         pEntry = pParent ? pEntry->NextSibling() : GetEntry( ++nRootPos );
1648     }
1649     return nullptr;
1650 }
1651 
Init()1652 void ScCheckListBox::Init()
1653 {
1654     mpCheckButton.reset( new SvLBoxButtonData( this ) );
1655     EnableCheckButton( mpCheckButton.get() );
1656     SetNodeDefaultImages();
1657 }
1658 
GetRecursiveChecked(SvTreeListEntry * pEntry,std::unordered_set<OUString> & vOut,OUString & rLabel)1659 void ScCheckListBox::GetRecursiveChecked( SvTreeListEntry* pEntry, std::unordered_set<OUString>& vOut,
1660         OUString& rLabel )
1661 {
1662     if (GetCheckButtonState(pEntry) == SvButtonState::Checked)
1663     {
1664         // We have to hash parents and children together.
1665         // Per convention for easy access in getResult()
1666         // "child;parent;grandparent" while descending.
1667         if (rLabel.isEmpty())
1668             rLabel = GetEntryText(pEntry);
1669         else
1670             rLabel = GetEntryText(pEntry) + ";" + rLabel;
1671 
1672         // Prerequisite: the selection mechanism guarantees that if a child is
1673         // selected then also the parent is selected, so we only have to
1674         // inspect the children in case the parent is selected.
1675         if (pEntry->HasChildren())
1676         {
1677             const SvTreeListEntries& rChildren = pEntry->GetChildEntries();
1678             for (auto& rChild : rChildren)
1679             {
1680                 OUString aLabel = rLabel;
1681                 GetRecursiveChecked( rChild.get(), vOut, aLabel);
1682                 if (!aLabel.isEmpty() && aLabel != rLabel)
1683                     vOut.insert( aLabel);
1684             }
1685             // Let the caller not add the parent alone.
1686             rLabel.clear();
1687         }
1688     }
1689 }
1690 
GetAllChecked()1691 std::unordered_set<OUString> ScCheckListBox::GetAllChecked()
1692 {
1693     std::unordered_set<OUString> vResults(0);
1694     sal_uInt32 nRootPos = 0;
1695     SvTreeListEntry* pEntry = GetEntry(nRootPos);
1696     while (pEntry)
1697     {
1698         OUString aLabel;
1699         GetRecursiveChecked( pEntry, vResults, aLabel);
1700         if (!aLabel.isEmpty())
1701             vResults.insert( aLabel);
1702         pEntry = GetEntry(++nRootPos);
1703     }
1704 
1705     return vResults;
1706 }
1707 
IsChecked(const OUString & sName,SvTreeListEntry * pParent)1708 bool ScCheckListBox::IsChecked( const OUString& sName, SvTreeListEntry* pParent )
1709 {
1710     SvTreeListEntry* pEntry = FindEntry( pParent, sName );
1711     return pEntry && GetCheckButtonState( pEntry ) == SvButtonState::Checked;
1712 }
1713 
CheckEntry(const OUString & sName,SvTreeListEntry * pParent,bool bCheck)1714 void ScCheckListBox::CheckEntry( const OUString& sName, SvTreeListEntry* pParent, bool bCheck )
1715 {
1716     SvTreeListEntry* pEntry = FindEntry( pParent, sName );
1717     if ( pEntry )
1718         CheckEntry(  pEntry, bCheck );
1719 }
1720 
1721 // Recursively check all children of pParent
CheckAllChildren(SvTreeListEntry * pParent,bool bCheck)1722 void ScCheckListBox::CheckAllChildren( SvTreeListEntry* pParent, bool bCheck )
1723 {
1724     if ( pParent )
1725     {
1726         SetCheckButtonState(
1727             pParent, bCheck ? SvButtonState::Checked : SvButtonState::Unchecked );
1728     }
1729     SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : First();
1730     while ( pEntry )
1731     {
1732         CheckAllChildren( pEntry, bCheck );
1733         pEntry = pEntry->NextSibling();
1734     }
1735 }
1736 
CheckEntry(SvTreeListEntry * pParent,bool bCheck)1737 void ScCheckListBox::CheckEntry( SvTreeListEntry* pParent, bool bCheck )
1738 {
1739     // recursively check all items below pParent
1740     CheckAllChildren( pParent, bCheck );
1741     // checking pParent can affect ancestors, e.g. if ancestor is unchecked and pParent is
1742     // now checked then the ancestor needs to be checked also
1743     SvTreeListEntry* pAncestor = GetParent(pParent);
1744     if ( pAncestor )
1745     {
1746         while ( pAncestor )
1747         {
1748             // if any first level children checked then ancestor
1749             // needs to be checked, similarly if no first level children
1750             // checked then ancestor needs to be unchecked
1751             SvTreeListEntry* pChild = FirstChild( pAncestor );
1752             bool bChildChecked = false;
1753 
1754             while ( pChild )
1755             {
1756                 if ( GetCheckButtonState( pChild ) == SvButtonState::Checked )
1757                 {
1758                     bChildChecked = true;
1759                     break;
1760                 }
1761                 pChild = pChild->NextSibling();
1762             }
1763             SetCheckButtonState( pAncestor, bChildChecked ? SvButtonState::Checked : SvButtonState::Unchecked );
1764             pAncestor = GetParent(pAncestor);
1765         }
1766     }
1767 }
1768 
ShowCheckEntry(const OUString & sName,ScCheckListMember & rMember,bool bShow,bool bCheck)1769 SvTreeListEntry* ScCheckListBox::ShowCheckEntry( const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck )
1770 {
1771     SvTreeListEntry* pEntry = nullptr;
1772     if (!rMember.mbDate || rMember.mpParent)
1773         pEntry = FindEntry( rMember.mpParent, sName );
1774 
1775     if ( bShow )
1776     {
1777         if ( !pEntry )
1778         {
1779             if (rMember.mbDate)
1780             {
1781                 if (rMember.maDateParts.empty())
1782                     return nullptr;
1783 
1784                 SvTreeListEntry* pYearEntry = FindEntry( nullptr, rMember.maDateParts[0] );
1785                 if ( !pYearEntry )
1786                     pYearEntry = InsertEntry( rMember.maDateParts[0], nullptr, true );
1787                 SvTreeListEntry* pMonthEntry = FindEntry( pYearEntry, rMember.maDateParts[1] );
1788                 if ( !pMonthEntry )
1789                     pMonthEntry = InsertEntry( rMember.maDateParts[1], pYearEntry, true );
1790                 SvTreeListEntry* pDayEntry = FindEntry( pMonthEntry, rMember.maName );
1791                 if ( !pDayEntry )
1792                     pDayEntry = InsertEntry( rMember.maName, pMonthEntry );
1793 
1794                 return pDayEntry; // Return leaf node
1795             }
1796 
1797             pEntry = InsertEntry(
1798                 sName);
1799 
1800             SetCheckButtonState(
1801                 pEntry, bCheck ? SvButtonState::Checked : SvButtonState::Unchecked);
1802         }
1803         else
1804             CheckEntry( pEntry, bCheck );
1805     }
1806     else if ( pEntry )
1807     {
1808         GetModel()->Remove( pEntry );
1809         SvTreeListEntry* pParent = rMember.mpParent;
1810         while ( pParent && !pParent->HasChildren() )
1811         {
1812             SvTreeListEntry* pTmp = pParent;
1813             pParent = pTmp->GetParent();
1814             GetModel()->Remove( pTmp );
1815         }
1816     }
1817     return nullptr;
1818 }
1819 
CountCheckedEntries(SvTreeListEntry * pParent,sal_uLong & nCount) const1820 void ScCheckListBox::CountCheckedEntries( SvTreeListEntry* pParent, sal_uLong& nCount ) const
1821 {
1822     if ( pParent && GetCheckButtonState( pParent ) == SvButtonState::Checked  )
1823         nCount++;
1824     // Iterate over the children
1825     SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : First();
1826     while ( pEntry )
1827     {
1828         CountCheckedEntries( pEntry, nCount );
1829         pEntry = pEntry->NextSibling();
1830     }
1831 }
1832 
GetCheckedEntryCount() const1833 sal_uInt16 ScCheckListBox::GetCheckedEntryCount() const
1834 {
1835     sal_uLong nCount = 0;
1836     CountCheckedEntries( nullptr,  nCount );
1837     return nCount;
1838 }
1839 
KeyInput(const KeyEvent & rKEvt)1840 void ScCheckListBox::KeyInput( const KeyEvent& rKEvt )
1841 {
1842     const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
1843 
1844     if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
1845     {
1846         SvTreeListEntry* pEntry = GetCurEntry();
1847         if ( pEntry )
1848         {
1849             bool bCheck = ( GetCheckButtonState( pEntry ) == SvButtonState::Checked );
1850             CheckEntry( pEntry, !bCheck );
1851             if ( bCheck != ( GetCheckButtonState( pEntry ) == SvButtonState::Checked ) )
1852                 CheckButtonHdl();
1853         }
1854     }
1855     else if ( GetEntryCount() )
1856         SvTreeListBox::KeyInput( rKEvt );
1857 }
1858 
MouseButtonDown(const MouseEvent & rMEvt)1859 void ScCheckListBox::MouseButtonDown(const MouseEvent& rMEvt)
1860 {
1861     SvTreeListBox::MouseButtonDown( rMEvt );
1862     if ( rMEvt.IsLeft() )
1863         mbSeenMouseButtonDown = true;
1864 }
1865 
MouseButtonUp(const MouseEvent & rMEvt)1866 void ScCheckListBox::MouseButtonUp(const MouseEvent& rMEvt)
1867 {
1868     SvTreeListBox::MouseButtonUp( rMEvt );
1869     if ( mpTabStops && mbSeenMouseButtonDown && rMEvt.IsLeft() )
1870     {
1871         mpTabStops->SetTabStop( this );
1872         mbSeenMouseButtonDown = false;
1873     }
1874 }
1875 
MouseButtonDown(const MouseEvent & rMEvt)1876 void ScSearchEdit::MouseButtonDown(const MouseEvent& rMEvt)
1877 {
1878     Edit::MouseButtonDown( rMEvt );
1879     if ( mpTabStops && rMEvt.IsLeft() && rMEvt.GetClicks() >= 1 )
1880         mpTabStops->SetTabStop( this );
1881 }
1882 
setHasDates(bool bHasDates)1883 void ScCheckListMenuWindow::setHasDates(bool bHasDates)
1884 {
1885     // Enables type-ahead search in the check list box.
1886     maChecks->SetQuickSearch(true);
1887     if (bHasDates)
1888         maChecks->SetStyle(WB_HASBUTTONS | WB_HASLINES | WB_HASLINESATROOT | WB_HASBUTTONSATROOT);
1889     else
1890         maChecks->SetStyle(WB_HASBUTTONS);
1891 }
1892 
initMembers()1893 void ScCheckListMenuWindow::initMembers()
1894 {
1895     size_t n = maMembers.size();
1896     size_t nVisMemCount = 0;
1897 
1898     maChecks->SetUpdateMode(false);
1899     maChecks->GetModel()->EnableInvalidate(false);
1900 
1901     for (size_t i = 0; i < n; ++i)
1902     {
1903         if (maMembers[i].mbDate)
1904         {
1905             maChecks->CheckEntry(maMembers[i].maName, maMembers[i].mpParent, maMembers[i].mbVisible);
1906             // Expand first node of checked dates
1907             if (!maMembers[i].mpParent && maChecks->IsChecked(maMembers[i].maName,  maMembers[i].mpParent))
1908             {
1909                 SvTreeListEntry* pEntry = maChecks->FindEntry(nullptr, maMembers[i].maName);
1910                 if (pEntry)
1911                     maChecks->Expand(pEntry);
1912             }
1913         }
1914         else
1915         {
1916             OUString aLabel = maMembers[i].maName;
1917             if (aLabel.isEmpty())
1918                 aLabel = ScResId(STR_EMPTYDATA);
1919             SvTreeListEntry* pEntry = maChecks->InsertEntry(
1920                 aLabel);
1921 
1922             maChecks->SetCheckButtonState(
1923                 pEntry, maMembers[i].mbVisible ? SvButtonState::Checked : SvButtonState::Unchecked);
1924         }
1925 
1926         if (maMembers[i].mbVisible)
1927             ++nVisMemCount;
1928     }
1929     if (nVisMemCount == n)
1930     {
1931         // all members visible
1932         maChkToggleAll->SetState(TRISTATE_TRUE);
1933         mePrevToggleAllState = TRISTATE_TRUE;
1934     }
1935     else if (nVisMemCount == 0)
1936     {
1937         // no members visible
1938         maChkToggleAll->SetState(TRISTATE_FALSE);
1939         mePrevToggleAllState = TRISTATE_FALSE;
1940     }
1941     else
1942     {
1943         maChkToggleAll->SetState(TRISTATE_INDET);
1944         mePrevToggleAllState = TRISTATE_INDET;
1945     }
1946 
1947     maChecks->GetModel()->EnableInvalidate(true);
1948     maChecks->SetUpdateMode(true);
1949 }
1950 
setConfig(const Config & rConfig)1951 void ScCheckListMenuWindow::setConfig(const Config& rConfig)
1952 {
1953     maConfig = rConfig;
1954 }
1955 
isAllSelected() const1956 bool ScCheckListMenuWindow::isAllSelected() const
1957 {
1958     return maChkToggleAll->IsChecked();
1959 }
1960 
getResult(ResultType & rResult)1961 void ScCheckListMenuWindow::getResult(ResultType& rResult)
1962 {
1963     ResultType aResult;
1964     std::unordered_set<OUString> vCheckeds = maChecks->GetAllChecked();
1965     size_t n = maMembers.size();
1966     for (size_t i = 0; i < n; ++i)
1967     {
1968         if ( maMembers[i].mbLeaf )
1969         {
1970             OUStringBuffer aLabel = maMembers[i].maName;
1971             if (aLabel.isEmpty())
1972                 aLabel = ScResId(STR_EMPTYDATA);
1973 
1974             /* TODO: performance-wise this looks suspicious, concatenating to
1975              * do the lookup for each leaf item seems wasteful. */
1976             // Checked labels are in the form "child;parent;grandparent".
1977             for (SvTreeListEntry* pParent = maMembers[i].mpParent;
1978                     pParent && pParent->GetFirstItem( SvLBoxItemType::String);
1979                     pParent = pParent->GetParent())
1980             {
1981                 aLabel.append(";").append(maChecks->GetEntryText( pParent));
1982             }
1983             bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
1984 
1985             ResultEntry aResultEntry;
1986             aResultEntry.bValid = bState;
1987             if ( maMembers[i].mbDate )
1988                 aResultEntry.aName = maMembers[i].maRealName;
1989             else
1990                 aResultEntry.aName = maMembers[i].maName;
1991             aResultEntry.bDate = maMembers[i].mbDate;
1992             aResult.insert(aResultEntry);
1993         }
1994     }
1995     rResult.swap(aResult);
1996 }
1997 
launch(const tools::Rectangle & rRect)1998 void ScCheckListMenuWindow::launch(const tools::Rectangle& rRect)
1999 {
2000     packWindow();
2001     if (!maConfig.mbAllowEmptySet)
2002         // We need to have at least one member selected.
2003         maBtnOk->Enable(maChecks->GetCheckedEntryCount() != 0);
2004 
2005     tools::Rectangle aRect(rRect);
2006     if (maConfig.mbRTL)
2007     {
2008         // In RTL mode, the logical "left" is visual "right".
2009         long nLeft = aRect.Left() - aRect.GetWidth();
2010         aRect.SetLeft( nLeft );
2011     }
2012     else if (maWndSize.Width() < aRect.GetWidth())
2013     {
2014         // Target rectangle (i.e. cell width) is wider than the window.
2015         // Simulate right-aligned launch by modifying the target rectangle
2016         // size.
2017         long nDiff = aRect.GetWidth() - maWndSize.Width();
2018         aRect.AdjustLeft(nDiff );
2019     }
2020 
2021     StartPopupMode(aRect, (FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus));
2022     maTabStops.CycleFocus(); // Set initial focus to the search box ( index = 1 )
2023 }
2024 
close(bool bOK)2025 void ScCheckListMenuWindow::close(bool bOK)
2026 {
2027     if (bOK && mpOKAction.get())
2028         mpOKAction->execute();
2029 
2030     EndPopupMode();
2031 }
2032 
setExtendedData(std::unique_ptr<ExtendedData> p)2033 void ScCheckListMenuWindow::setExtendedData(std::unique_ptr<ExtendedData> p)
2034 {
2035     mpExtendedData = std::move(p);
2036 }
2037 
getExtendedData()2038 ScCheckListMenuWindow::ExtendedData* ScCheckListMenuWindow::getExtendedData()
2039 {
2040     return mpExtendedData.get();
2041 }
2042 
setOKAction(Action * p)2043 void ScCheckListMenuWindow::setOKAction(Action* p)
2044 {
2045     mpOKAction.reset(p);
2046 }
2047 
setPopupEndAction(Action * p)2048 void ScCheckListMenuWindow::setPopupEndAction(Action* p)
2049 {
2050     mpPopupEndAction.reset(p);
2051 }
2052 
handlePopupEnd()2053 void ScCheckListMenuWindow::handlePopupEnd()
2054 {
2055     clearSelectedMenuItem();
2056     if (mpPopupEndAction)
2057         mpPopupEndAction->execute();
2058 }
2059 
2060 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2061