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