summaryrefslogtreecommitdiffstats
path: root/sc/source/ui/cctrl/checklistmenu.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /sc/source/ui/cctrl/checklistmenu.cxx
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/ui/cctrl/checklistmenu.cxx')
-rw-r--r--sc/source/ui/cctrl/checklistmenu.cxx2076
1 files changed, 2076 insertions, 0 deletions
diff --git a/sc/source/ui/cctrl/checklistmenu.cxx b/sc/source/ui/cctrl/checklistmenu.cxx
new file mode 100644
index 000000000..27976641e
--- /dev/null
+++ b/sc/source/ui/cctrl/checklistmenu.cxx
@@ -0,0 +1,2076 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <checklistmenu.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <strings.hrc>
+#include <bitmaps.hlst>
+
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <rtl/math.hxx>
+#include <tools/wintypes.hxx>
+#include <unotools/charclass.hxx>
+
+#include <AccessibleFilterMenu.hxx>
+#include <AccessibleFilterTopWindow.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <vcl/svlbitm.hxx>
+#include <vcl/treelistentry.hxx>
+#include <document.hxx>
+
+using namespace com::sun::star;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::accessibility::XAccessible;
+using ::com::sun::star::accessibility::XAccessibleContext;
+
+ScMenuFloatingWindow::MenuItemData::MenuItemData() :
+ mbEnabled(true), mbSeparator(false),
+ mpSubMenuWin(static_cast<ScMenuFloatingWindow*>(nullptr))
+{
+}
+
+ScMenuFloatingWindow::SubMenuItemData::SubMenuItemData(ScMenuFloatingWindow* pParent) :
+ mpSubMenu(nullptr),
+ mnMenuPos(MENU_NOT_SELECTED),
+ mpParent(pParent)
+{
+ maTimer.SetInvokeHandler( LINK(this, ScMenuFloatingWindow::SubMenuItemData, TimeoutHdl) );
+ maTimer.SetTimeout(mpParent->GetSettings().GetMouseSettings().GetMenuDelay());
+}
+
+void ScMenuFloatingWindow::SubMenuItemData::reset()
+{
+ mpSubMenu = nullptr;
+ mnMenuPos = MENU_NOT_SELECTED;
+ maTimer.Stop();
+}
+
+IMPL_LINK_NOARG(ScMenuFloatingWindow::SubMenuItemData, TimeoutHdl, Timer *, void)
+{
+ mpParent->handleMenuTimeout(this);
+}
+
+ScMenuFloatingWindow::ScMenuFloatingWindow(vcl::Window* pParent, ScDocument* pDoc, sal_uInt16 nMenuStackLevel) :
+ PopupMenuFloatingWindow(pParent),
+ maOpenTimer(this),
+ maCloseTimer(this),
+ maName("ScMenuFloatingWindow"),
+ mnSelectedMenu(MENU_NOT_SELECTED),
+ mnClickedMenu(MENU_NOT_SELECTED),
+ mpDoc(pDoc),
+ mpParentMenu(dynamic_cast<ScMenuFloatingWindow*>(pParent))
+{
+ SetMenuStackLevel(nMenuStackLevel);
+ SetText("ScMenuFloatingWindow");
+
+ const StyleSettings& rStyle = GetSettings().GetStyleSettings();
+
+ const sal_uInt16 nPopupFontHeight = 12 * GetDPIScaleFactor();
+ maLabelFont = rStyle.GetLabelFont();
+ maLabelFont.SetFontHeight(nPopupFontHeight);
+}
+
+ScMenuFloatingWindow::~ScMenuFloatingWindow()
+{
+ disposeOnce();
+}
+
+void ScMenuFloatingWindow::dispose()
+{
+ EndPopupMode();
+ for (auto& rMenuItem : maMenuItems)
+ rMenuItem.mpSubMenuWin.disposeAndClear();
+ mpParentMenu.clear();
+ PopupMenuFloatingWindow::dispose();
+}
+
+void ScMenuFloatingWindow::PopupModeEnd()
+{
+ handlePopupEnd();
+}
+
+void ScMenuFloatingWindow::MouseMove(const MouseEvent& rMEvt)
+{
+ const Point& rPos = rMEvt.GetPosPixel();
+ size_t nSelectedMenu = getEnclosingMenuItem(rPos);
+ setSelectedMenuItem(nSelectedMenu, true, false);
+
+ Window::MouseMove(rMEvt);
+}
+
+void ScMenuFloatingWindow::MouseButtonDown(const MouseEvent& rMEvt)
+{
+ const Point& rPos = rMEvt.GetPosPixel();
+ mnClickedMenu = getEnclosingMenuItem(rPos);
+ Window::MouseButtonDown(rMEvt);
+}
+
+void ScMenuFloatingWindow::MouseButtonUp(const MouseEvent& rMEvt)
+{
+ executeMenuItem(mnClickedMenu);
+ mnClickedMenu = MENU_NOT_SELECTED;
+ Window::MouseButtonUp(rMEvt);
+}
+
+void ScMenuFloatingWindow::KeyInput(const KeyEvent& rKEvt)
+{
+ if (maMenuItems.empty())
+ {
+ Window::KeyInput(rKEvt);
+ return;
+ }
+
+ const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
+ bool bHandled = true;
+ size_t nSelectedMenu = mnSelectedMenu;
+ size_t nLastMenuPos = maMenuItems.size() - 1;
+ switch (rKeyCode.GetCode())
+ {
+ case KEY_UP:
+ {
+ if (nLastMenuPos == 0)
+ // There is only one menu item. Do nothing.
+ break;
+
+ size_t nOldPos = nSelectedMenu;
+
+ if (nSelectedMenu == MENU_NOT_SELECTED || nSelectedMenu == 0)
+ nSelectedMenu = nLastMenuPos;
+ else
+ --nSelectedMenu;
+
+ // Loop until a non-separator menu item is found.
+ while (nSelectedMenu != nOldPos)
+ {
+ if (maMenuItems[nSelectedMenu].mbSeparator)
+ {
+ if (nSelectedMenu)
+ --nSelectedMenu;
+ else
+ nSelectedMenu = nLastMenuPos;
+ }
+ else
+ break;
+ }
+
+ setSelectedMenuItem(nSelectedMenu, false, false);
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if (nLastMenuPos == 0)
+ // There is only one menu item. Do nothing.
+ break;
+
+ size_t nOldPos = nSelectedMenu;
+
+ if (nSelectedMenu == MENU_NOT_SELECTED || nSelectedMenu == nLastMenuPos)
+ nSelectedMenu = 0;
+ else
+ ++nSelectedMenu;
+
+ // Loop until a non-separator menu item is found.
+ while (nSelectedMenu != nOldPos)
+ {
+ if (maMenuItems[nSelectedMenu].mbSeparator)
+ {
+ if (nSelectedMenu == nLastMenuPos)
+ nSelectedMenu = 0;
+ else
+ ++nSelectedMenu;
+ }
+ else
+ break;
+ }
+
+ setSelectedMenuItem(nSelectedMenu, false, false);
+ }
+ break;
+ case KEY_LEFT:
+ if (mpParentMenu)
+ mpParentMenu->endSubMenu(this);
+ break;
+ case KEY_RIGHT:
+ {
+ if (mnSelectedMenu >= maMenuItems.size() || mnSelectedMenu == MENU_NOT_SELECTED)
+ break;
+
+ const MenuItemData& rMenu = maMenuItems[mnSelectedMenu];
+ if (!rMenu.mbEnabled || !rMenu.mpSubMenuWin)
+ break;
+
+ maOpenTimer.mnMenuPos = mnSelectedMenu;
+ maOpenTimer.mpSubMenu = rMenu.mpSubMenuWin.get();
+ launchSubMenu(true);
+ }
+ break;
+ case KEY_RETURN:
+ if (nSelectedMenu != MENU_NOT_SELECTED)
+ executeMenuItem(nSelectedMenu);
+ break;
+ default:
+ bHandled = false;
+ }
+
+ if (!bHandled)
+ Window::KeyInput(rKEvt);
+}
+
+void ScMenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ const StyleSettings& rStyle = GetSettings().GetStyleSettings();
+
+ SetFont(maLabelFont);
+
+ Color aBackColor = rStyle.GetMenuColor();
+ Color aBorderColor = rStyle.GetShadowColor();
+
+ tools::Rectangle aCtrlRect(Point(0, 0), GetOutputSizePixel());
+
+ // Window background
+ bool bNativeDrawn = true;
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ rRenderContext.SetClipRegion();
+ bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect,
+ ControlState::ENABLED, ImplControlValue(), OUString());
+ }
+ else
+ bNativeDrawn = false;
+
+ if (!bNativeDrawn)
+ {
+ rRenderContext.SetFillColor(aBackColor);
+ rRenderContext.SetLineColor(aBorderColor);
+ rRenderContext.DrawRect(aCtrlRect);
+ }
+
+ // Menu items
+ rRenderContext.SetTextColor(rStyle.GetMenuTextColor());
+ drawAllMenuItems(rRenderContext);
+}
+
+Reference<XAccessible> ScMenuFloatingWindow::CreateAccessible()
+{
+ if (!mxAccessible.is())
+ {
+ Reference<XAccessible> xAccParent = mpParentMenu ?
+ mpParentMenu->GetAccessible() : GetAccessibleParentWindow()->GetAccessible();
+
+ mxAccessible.set(new ScAccessibleFilterMenu(xAccParent, this, maName, 999));
+ ScAccessibleFilterMenu* p = static_cast<ScAccessibleFilterMenu*>(
+ mxAccessible.get());
+
+ size_t nPos = 0;
+ for (const auto& rMenuItem : maMenuItems)
+ {
+ p->appendMenuItem(rMenuItem.maText, nPos);
+ ++nPos;
+ }
+ }
+
+ return mxAccessible;
+}
+
+void ScMenuFloatingWindow::addMenuItem(const OUString& rText, Action* pAction)
+{
+ MenuItemData aItem;
+ aItem.maText = rText;
+ aItem.mbEnabled = true;
+ aItem.mpAction.reset(pAction);
+ maMenuItems.push_back(aItem);
+}
+
+void ScMenuFloatingWindow::addSeparator()
+{
+ MenuItemData aItem;
+ aItem.mbSeparator = true;
+ maMenuItems.push_back(aItem);
+}
+
+ScMenuFloatingWindow* ScMenuFloatingWindow::addSubMenuItem(const OUString& rText, bool bEnabled)
+{
+ MenuItemData aItem;
+ aItem.maText = rText;
+ aItem.mbEnabled = bEnabled;
+ aItem.mpSubMenuWin.reset(VclPtr<ScMenuFloatingWindow>::Create(this, mpDoc, GetMenuStackLevel()+1));
+ aItem.mpSubMenuWin->setName(rText);
+ maMenuItems.push_back(aItem);
+ return aItem.mpSubMenuWin.get();
+}
+
+void ScMenuFloatingWindow::handlePopupEnd()
+{
+ clearSelectedMenuItem();
+}
+
+Size ScMenuFloatingWindow::getMenuSize() const
+{
+ if (maMenuItems.empty())
+ return Size();
+
+ auto itr = std::max_element(maMenuItems.begin(), maMenuItems.end(),
+ [this](const MenuItemData& a, const MenuItemData& b) {
+ long aTextWidth = a.mbSeparator ? 0 : GetTextWidth(a.maText);
+ long bTextWidth = b.mbSeparator ? 0 : GetTextWidth(b.maText);
+ return aTextWidth < bTextWidth;
+ });
+ long nTextWidth = itr->mbSeparator ? 0 : GetTextWidth(itr->maText);
+
+ size_t nLastPos = maMenuItems.size()-1;
+ Point aPos;
+ Size aSize;
+ getMenuItemPosSize(nLastPos, aPos, aSize);
+ aPos.AdjustX(nTextWidth + 15 );
+ aPos.AdjustY(aSize.Height() + 5 );
+ return Size(aPos.X(), aPos.Y());
+}
+
+void ScMenuFloatingWindow::drawMenuItem(vcl::RenderContext& rRenderContext, size_t nPos)
+{
+ if (nPos >= maMenuItems.size())
+ return;
+
+ Point aPos;
+ Size aSize;
+ getMenuItemPosSize(nPos, aPos, aSize);
+
+ DecorationView aDecoView(&rRenderContext);
+ long const nXOffset = 5;
+ long nYOffset = (aSize.Height() - maLabelFont.GetFontHeight())/2;
+
+ // Make sure the label font is used for the menu item text.
+ rRenderContext.Push(PushFlags::FONT);
+ rRenderContext.SetFont(maLabelFont);
+ rRenderContext. DrawCtrlText(Point(aPos.X()+nXOffset, aPos.Y() + nYOffset), maMenuItems[nPos].maText, 0,
+ maMenuItems[nPos].maText.getLength(),
+ maMenuItems[nPos].mbEnabled ? DrawTextFlags::Mnemonic : DrawTextFlags::Disable);
+ rRenderContext.Pop();
+
+ if (maMenuItems[nPos].mpSubMenuWin)
+ {
+ long nFontHeight = maLabelFont.GetFontHeight();
+ Point aMarkerPos = aPos;
+ aMarkerPos.AdjustY(aSize.Height() / 2 - nFontHeight / 4 + 1 );
+ aMarkerPos.AdjustX(aSize.Width() - nFontHeight + nFontHeight / 4 );
+ Size aMarkerSize(nFontHeight / 2, nFontHeight / 2);
+ aDecoView.DrawSymbol(tools::Rectangle(aMarkerPos, aMarkerSize), SymbolType::SPIN_RIGHT, GetTextColor());
+ }
+}
+
+void ScMenuFloatingWindow::drawSeparator(vcl::RenderContext& rRenderContext, size_t nPos)
+{
+ Point aPos;
+ Size aSize;
+ getMenuItemPosSize(nPos, aPos, aSize);
+ tools::Rectangle aRegion(aPos,aSize);
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ rRenderContext.Push(PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(aRegion);
+ tools::Rectangle aCtrlRect(Point(0,0), GetOutputSizePixel());
+ rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect,
+ ControlState::ENABLED, ImplControlValue(), OUString());
+
+ rRenderContext.Pop();
+ }
+
+ bool bNativeDrawn = false;
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
+ {
+ ControlState nState = ControlState::NONE;
+ const MenuItemData& rData = maMenuItems[nPos];
+ if (rData.mbEnabled)
+ nState |= ControlState::ENABLED;
+
+ bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
+ aRegion, nState, ImplControlValue(), OUString());
+ }
+
+ if (!bNativeDrawn)
+ {
+ const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
+ Point aTmpPos = aPos;
+ aTmpPos.AdjustY(aSize.Height() / 2 );
+ rRenderContext.SetLineColor(rStyle.GetShadowColor());
+ rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y()));
+ aTmpPos.AdjustY( 1 );
+ rRenderContext.SetLineColor(rStyle.GetLightColor());
+ rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y()));
+ rRenderContext.SetLineColor();
+ }
+}
+
+void ScMenuFloatingWindow::drawAllMenuItems(vcl::RenderContext& rRenderContext)
+{
+ size_t n = maMenuItems.size();
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (maMenuItems[i].mbSeparator)
+ {
+ // Separator
+ drawSeparator(rRenderContext, i);
+ }
+ else
+ {
+ // Normal menu item
+ highlightMenuItem(rRenderContext, i, i == mnSelectedMenu);
+ }
+ }
+}
+
+void ScMenuFloatingWindow::executeMenuItem(size_t nPos)
+{
+ if (nPos >= maMenuItems.size())
+ return;
+
+ if (!maMenuItems[nPos].mpAction)
+ // no action is defined.
+ return;
+
+ terminateAllPopupMenus();
+
+ maMenuItems[nPos].mpAction->execute();
+}
+
+void ScMenuFloatingWindow::setSelectedMenuItem(size_t nPos, bool bSubMenuTimer, bool bEnsureSubMenu)
+{
+ if (mnSelectedMenu == nPos)
+ // nothing to do.
+ return;
+
+ if (bEnsureSubMenu)
+ {
+ // Dismiss any child popup menu windows.
+ if (mnSelectedMenu < maMenuItems.size() &&
+ maMenuItems[mnSelectedMenu].mpSubMenuWin &&
+ maMenuItems[mnSelectedMenu].mpSubMenuWin->IsVisible())
+ {
+ maMenuItems[mnSelectedMenu].mpSubMenuWin->ensureSubMenuNotVisible();
+ }
+
+ // The popup is not visible, yet a menu item is selected. The request
+ // most likely comes from the accessible object. Make sure this
+ // window, as well as all its parent windows are visible.
+ if (!IsVisible() && mpParentMenu)
+ mpParentMenu->ensureSubMenuVisible(this);
+ }
+
+ selectMenuItem(mnSelectedMenu, false, bSubMenuTimer);
+ selectMenuItem(nPos, true, bSubMenuTimer);
+ mnSelectedMenu = nPos;
+
+ fireMenuHighlightedEvent();
+}
+
+void ScMenuFloatingWindow::handleMenuTimeout(const SubMenuItemData* pTimer)
+{
+ if (pTimer == &maOpenTimer)
+ {
+ // Close any open submenu immediately.
+ if (maCloseTimer.mpSubMenu)
+ {
+ maCloseTimer.mpSubMenu->EndPopupMode();
+ maCloseTimer.mpSubMenu = nullptr;
+ maCloseTimer.maTimer.Stop();
+ }
+
+ launchSubMenu(false);
+ }
+ else if (pTimer == &maCloseTimer)
+ {
+ // end submenu.
+ if (maCloseTimer.mpSubMenu)
+ {
+ maOpenTimer.mpSubMenu = nullptr;
+
+ maCloseTimer.mpSubMenu->EndPopupMode();
+ maCloseTimer.mpSubMenu = nullptr;
+
+ Invalidate();
+ maOpenTimer.mnMenuPos = MENU_NOT_SELECTED;
+ }
+ }
+}
+
+void ScMenuFloatingWindow::queueLaunchSubMenu(size_t nPos, ScMenuFloatingWindow* pMenu)
+{
+ if (!pMenu)
+ return;
+
+ // Set the submenu on launch queue.
+ if (maOpenTimer.mpSubMenu)
+ {
+ if (maOpenTimer.mpSubMenu == pMenu)
+ {
+ if (pMenu == maCloseTimer.mpSubMenu)
+ maCloseTimer.reset();
+ return;
+ }
+
+ // new submenu is being requested.
+ queueCloseSubMenu();
+ }
+
+ maOpenTimer.mpSubMenu = pMenu;
+ maOpenTimer.mnMenuPos = nPos;
+ maOpenTimer.maTimer.Start();
+}
+
+void ScMenuFloatingWindow::queueCloseSubMenu()
+{
+ if (!maOpenTimer.mpSubMenu)
+ // There is no submenu to close.
+ return;
+
+ // Stop any submenu on queue for opening.
+ maOpenTimer.maTimer.Stop();
+
+ maCloseTimer.mpSubMenu = maOpenTimer.mpSubMenu;
+ maCloseTimer.mnMenuPos = maOpenTimer.mnMenuPos;
+ maCloseTimer.maTimer.Start();
+}
+
+void ScMenuFloatingWindow::launchSubMenu(bool bSetMenuPos)
+{
+ Point aPos;
+ Size aSize;
+ getMenuItemPosSize(maOpenTimer.mnMenuPos, aPos, aSize);
+ ScMenuFloatingWindow* pSubMenu = maOpenTimer.mpSubMenu;
+
+ if (!pSubMenu)
+ return;
+
+ FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
+ SetPopupModeFlags(nOldFlags | FloatWinPopupFlags::NoAppFocusClose);
+ pSubMenu->resizeToFitMenuItems(); // set the size before launching the popup to get it positioned correctly.
+ pSubMenu->StartPopupMode(
+ tools::Rectangle(aPos,aSize), (FloatWinPopupFlags::Right | FloatWinPopupFlags::GrabFocus));
+ pSubMenu->AddPopupModeWindow(this);
+ if (bSetMenuPos)
+ pSubMenu->setSelectedMenuItem(0, false, false); // select menu item after the popup becomes fully visible.
+ SetPopupModeFlags(nOldFlags);
+}
+
+void ScMenuFloatingWindow::endSubMenu(ScMenuFloatingWindow* pSubMenu)
+{
+ if (!pSubMenu)
+ return;
+
+ pSubMenu->EndPopupMode();
+ maOpenTimer.reset();
+
+ size_t nMenuPos = getSubMenuPos(pSubMenu);
+ if (nMenuPos != MENU_NOT_SELECTED)
+ {
+ mnSelectedMenu = nMenuPos;
+ Invalidate();
+ fireMenuHighlightedEvent();
+ }
+}
+
+void ScMenuFloatingWindow::fillMenuItemsToAccessible(ScAccessibleFilterMenu* pAccMenu) const
+{
+ size_t nPos = 0;
+ for (const auto& rMenuItem : maMenuItems)
+ {
+ pAccMenu->appendMenuItem(rMenuItem.maText, nPos);
+ ++nPos;
+ }
+}
+
+void ScMenuFloatingWindow::resizeToFitMenuItems()
+{
+ SetOutputSizePixel(getMenuSize());
+}
+
+void ScMenuFloatingWindow::selectMenuItem(size_t nPos, bool bSelected, bool bSubMenuTimer)
+{
+ if (nPos >= maMenuItems.size() || nPos == MENU_NOT_SELECTED)
+ {
+ queueCloseSubMenu();
+ return;
+ }
+
+ if (!maMenuItems[nPos].mbEnabled)
+ {
+ queueCloseSubMenu();
+ return;
+ }
+
+ Invalidate();
+
+ if (bSelected)
+ {
+ if (mpParentMenu)
+ mpParentMenu->setSubMenuFocused(this);
+
+ if (bSubMenuTimer)
+ {
+ if (maMenuItems[nPos].mpSubMenuWin)
+ {
+ ScMenuFloatingWindow* pSubMenu = maMenuItems[nPos].mpSubMenuWin.get();
+ queueLaunchSubMenu(nPos, pSubMenu);
+ }
+ else
+ queueCloseSubMenu();
+ }
+ }
+}
+
+void ScMenuFloatingWindow::clearSelectedMenuItem()
+{
+ selectMenuItem(mnSelectedMenu, false, false);
+ mnSelectedMenu = MENU_NOT_SELECTED;
+}
+
+ScMenuFloatingWindow* ScMenuFloatingWindow::getSubMenuWindow(size_t nPos) const
+{
+ if (maMenuItems.size() <= nPos)
+ return nullptr;
+
+ return maMenuItems[nPos].mpSubMenuWin.get();
+}
+
+bool ScMenuFloatingWindow::isMenuItemSelected(size_t nPos) const
+{
+ return nPos == mnSelectedMenu;
+}
+
+void ScMenuFloatingWindow::setName(const OUString& rName)
+{
+ maName = rName;
+}
+
+void ScMenuFloatingWindow::highlightMenuItem(vcl::RenderContext& rRenderContext, size_t nPos, bool bSelected)
+{
+ if (nPos == MENU_NOT_SELECTED)
+ return;
+
+ const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
+ Color aBackColor = rStyle.GetMenuColor();
+ rRenderContext.SetFillColor(aBackColor);
+ rRenderContext.SetLineColor(aBackColor);
+
+ Point aPos;
+ Size aSize;
+ getMenuItemPosSize(nPos, aPos, aSize);
+ tools::Rectangle aRegion(aPos,aSize);
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
+ {
+ rRenderContext.Push(PushFlags::CLIPREGION);
+ rRenderContext.IntersectClipRegion(tools::Rectangle(aPos, aSize));
+ tools::Rectangle aCtrlRect(Point(0,0), GetOutputSizePixel());
+ rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect, ControlState::ENABLED,
+ ImplControlValue(), OUString());
+ rRenderContext.Pop();
+ }
+
+ bool bNativeDrawn = true;
+ if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem))
+ {
+ ControlState nState = bSelected ? ControlState::SELECTED : ControlState::NONE;
+ if (maMenuItems[nPos].mbEnabled)
+ nState |= ControlState::ENABLED;
+ bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem,
+ aRegion, nState, ImplControlValue(), OUString());
+ }
+ else
+ bNativeDrawn = false;
+
+ if (!bNativeDrawn)
+ {
+ if (bSelected)
+ {
+ aBackColor = rStyle.GetMenuHighlightColor();
+ rRenderContext.SetFillColor(aBackColor);
+ rRenderContext.SetLineColor(aBackColor);
+ }
+ rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
+ }
+
+ Color aTextColor = bSelected ? rStyle.GetMenuHighlightTextColor() : rStyle.GetMenuTextColor();
+ rRenderContext.SetTextColor(aTextColor);
+ drawMenuItem(rRenderContext, nPos);
+}
+
+void ScMenuFloatingWindow::getMenuItemPosSize(size_t nPos, Point& rPos, Size& rSize) const
+{
+ size_t nCount = maMenuItems.size();
+ if (nPos >= nCount)
+ return;
+
+ const sal_uInt16 nLeftMargin = 5;
+ const sal_uInt16 nTopMargin = 5;
+ const sal_uInt16 nMenuItemHeight = static_cast<sal_uInt16>(maLabelFont.GetFontHeight()*1.8);
+ const sal_uInt16 nSepHeight = static_cast<sal_uInt16>(maLabelFont.GetFontHeight()*0.8);
+
+ Point aPos1(nLeftMargin, nTopMargin);
+ rPos = aPos1;
+ for (size_t i = 0; i < nPos; ++i)
+ rPos.AdjustY(maMenuItems[i].mbSeparator ? nSepHeight : nMenuItemHeight );
+
+ Size aWndSize = GetSizePixel();
+ sal_uInt16 nH = maMenuItems[nPos].mbSeparator ? nSepHeight : nMenuItemHeight;
+ rSize = Size(aWndSize.Width() - nLeftMargin*2, nH);
+}
+
+size_t ScMenuFloatingWindow::getEnclosingMenuItem(const Point& rPos) const
+{
+ size_t n = maMenuItems.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ Point aPos;
+ Size aSize;
+ getMenuItemPosSize(i, aPos, aSize);
+ tools::Rectangle aRect(aPos, aSize);
+ if (aRect.IsInside(rPos))
+ return maMenuItems[i].mbSeparator ? MENU_NOT_SELECTED : i;
+ }
+ return MENU_NOT_SELECTED;
+}
+
+size_t ScMenuFloatingWindow::getSubMenuPos(const ScMenuFloatingWindow* pSubMenu)
+{
+ size_t n = maMenuItems.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (maMenuItems[i].mpSubMenuWin.get() == pSubMenu)
+ return i;
+ }
+ return MENU_NOT_SELECTED;
+}
+
+void ScMenuFloatingWindow::fireMenuHighlightedEvent()
+{
+ if (mnSelectedMenu == MENU_NOT_SELECTED)
+ return;
+
+ if (!mxAccessible.is())
+ return;
+
+ Reference<XAccessibleContext> xAccCxt = mxAccessible->getAccessibleContext();
+ if (!xAccCxt.is())
+ return;
+
+ Reference<XAccessible> xAccMenu = xAccCxt->getAccessibleChild(mnSelectedMenu);
+ if (!xAccMenu.is())
+ return;
+
+ VclAccessibleEvent aEvent(VclEventId::MenuHighlight, xAccMenu);
+ FireVclEvent(aEvent);
+}
+
+void ScMenuFloatingWindow::setSubMenuFocused(const ScMenuFloatingWindow* pSubMenu)
+{
+ maCloseTimer.reset();
+ size_t nMenuPos = getSubMenuPos(pSubMenu);
+ if (mnSelectedMenu != nMenuPos)
+ {
+ mnSelectedMenu = nMenuPos;
+ Invalidate();
+ }
+}
+
+void ScMenuFloatingWindow::ensureSubMenuVisible(ScMenuFloatingWindow* pSubMenu)
+{
+ if (mpParentMenu)
+ mpParentMenu->ensureSubMenuVisible(this);
+
+ if (pSubMenu->IsVisible())
+ return;
+
+ // Find the menu position of the submenu.
+ size_t nMenuPos = getSubMenuPos(pSubMenu);
+ if (nMenuPos != MENU_NOT_SELECTED)
+ {
+ setSelectedMenuItem(nMenuPos, false, false);
+
+ Point aPos;
+ Size aSize;
+ getMenuItemPosSize(nMenuPos, aPos, aSize);
+
+ FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
+ SetPopupModeFlags(nOldFlags | FloatWinPopupFlags::NoAppFocusClose);
+ pSubMenu->resizeToFitMenuItems(); // set the size before launching the popup to get it positioned correctly.
+ pSubMenu->StartPopupMode(
+ tools::Rectangle(aPos,aSize), (FloatWinPopupFlags::Right | FloatWinPopupFlags::GrabFocus));
+ pSubMenu->AddPopupModeWindow(this);
+ SetPopupModeFlags(nOldFlags);
+ }
+}
+
+void ScMenuFloatingWindow::ensureSubMenuNotVisible()
+{
+ if (mnSelectedMenu < maMenuItems.size() &&
+ maMenuItems[mnSelectedMenu].mpSubMenuWin &&
+ maMenuItems[mnSelectedMenu].mpSubMenuWin->IsVisible())
+ {
+ maMenuItems[mnSelectedMenu].mpSubMenuWin->ensureSubMenuNotVisible();
+ }
+
+ EndPopupMode();
+}
+
+void ScMenuFloatingWindow::terminateAllPopupMenus()
+{
+ EndPopupMode();
+ if (mpParentMenu)
+ mpParentMenu->terminateAllPopupMenus();
+}
+
+ScCheckListMenuWindow::Config::Config() :
+ mbAllowEmptySet(true), mbRTL(false)
+{
+}
+
+ScCheckListMember::ScCheckListMember()
+ : mbVisible(true)
+ , mbDate(false)
+ , mbLeaf(false)
+ , meDatePartType(YEAR)
+ , mpParent(nullptr)
+{
+}
+
+ScCheckListMenuWindow::CancelButton::CancelButton(ScCheckListMenuWindow* pParent) :
+ ::CancelButton(pParent), mpParent(pParent) {}
+
+ScCheckListMenuWindow::CancelButton::~CancelButton()
+{
+ disposeOnce();
+}
+
+void ScCheckListMenuWindow::CancelButton::dispose()
+{
+ mpParent.clear();
+ ::CancelButton::dispose();
+}
+
+void ScCheckListMenuWindow::CancelButton::Click()
+{
+ mpParent->EndPopupMode();
+ ::CancelButton::Click();
+}
+
+ScCheckListMenuWindow::ScCheckListMenuWindow(vcl::Window* pParent, ScDocument* pDoc, int nWidth) :
+ ScMenuFloatingWindow(pParent, pDoc),
+ maEdSearch(VclPtr<ScSearchEdit>::Create(this)),
+ maChecks(VclPtr<ScCheckListBox>::Create(this)),
+ maChkToggleAll(VclPtr<CheckBox>::Create(this, 0)),
+ maBtnSelectSingle(VclPtr<ImageButton>::Create(this, 0)),
+ maBtnUnselectSingle(VclPtr<ImageButton>::Create(this, 0)),
+ maBtnOk(VclPtr<OKButton>::Create(this)),
+ maBtnCancel(VclPtr<CancelButton>::Create(this)),
+ maWndSize(),
+ mePrevToggleAllState(TRISTATE_INDET),
+ maTabStops(this),
+ mbHasDates(false)
+{
+ maChkToggleAll->EnableTriState(true);
+
+ float fScaleFactor = GetDPIScaleFactor();
+
+ nWidth = std::max<int>(nWidth, 200 * fScaleFactor);
+ maWndSize = Size(nWidth, 330 * fScaleFactor);
+
+ maTabStops.AddTabStop( this );
+ maTabStops.AddTabStop( maEdSearch.get() );
+ maTabStops.AddTabStop( maChecks.get() );
+ maTabStops.AddTabStop( maChkToggleAll.get() );
+ maTabStops.AddTabStop( maBtnSelectSingle.get() );
+ maTabStops.AddTabStop( maBtnUnselectSingle.get() );
+ maTabStops.AddTabStop( maBtnOk.get() );
+ maTabStops.AddTabStop( maBtnCancel.get() );
+
+ maEdSearch->SetTabStopsContainer( &maTabStops );
+ maChecks->SetTabStopsContainer( &maTabStops );
+
+ set_id("check_list_menu");
+ maChkToggleAll->set_id("toggle_all");
+ maBtnSelectSingle->set_id("select_current");
+ maBtnUnselectSingle->set_id("unselect_current");
+}
+
+ScCheckListMenuWindow::~ScCheckListMenuWindow()
+{
+ disposeOnce();
+}
+
+void ScCheckListMenuWindow::dispose()
+{
+ maTabStops.clear();
+ maEdSearch.disposeAndClear();
+ maChecks.disposeAndClear();
+ maChkToggleAll.disposeAndClear();
+ maBtnSelectSingle.disposeAndClear();
+ maBtnUnselectSingle.disposeAndClear();
+ maBtnOk.disposeAndClear();
+ maBtnCancel.disposeAndClear();
+ ScMenuFloatingWindow::dispose();
+}
+
+void ScCheckListMenuWindow::getSectionPosSize(
+ Point& rPos, Size& rSize, SectionType eType) const
+{
+ float fScaleFactor = GetDPIScaleFactor();
+
+ // constant parameters.
+ const long nSearchBoxMargin = 10 *fScaleFactor;
+ const long nListBoxMargin = 5 * fScaleFactor; // horizontal distance from the side of the dialog to the listbox border.
+ const long nListBoxInnerPadding = 5 * fScaleFactor;
+ const long nTopMargin = 5 * fScaleFactor;
+ const long nMenuHeight = maMenuSize.getHeight();
+ const long nSingleItemBtnAreaHeight = 32 * fScaleFactor; // height of the middle area below the list box where the single-action buttons are.
+ const long nBottomBtnAreaHeight = 50 * fScaleFactor; // height of the bottom area where the OK and Cancel buttons are.
+ const long nBtnWidth = 90 * fScaleFactor;
+ const long nLabelHeight = getLabelFont().GetFontHeight();
+ const long nBtnHeight = nLabelHeight * 2;
+ const long nBottomMargin = 10 * fScaleFactor;
+ const long nMenuListMargin = 5 * fScaleFactor;
+ const long nSearchBoxHeight = nLabelHeight * 2;
+
+ // parameters calculated from constants.
+ const long nListBoxWidth = maWndSize.Width() - nListBoxMargin*2;
+ const long nListBoxHeight = maWndSize.Height() - nTopMargin - nMenuHeight -
+ nMenuListMargin - nSearchBoxHeight - nSearchBoxMargin - nSingleItemBtnAreaHeight - nBottomBtnAreaHeight;
+
+ const long nSingleBtnAreaY = nTopMargin + nMenuHeight + nMenuListMargin + nSearchBoxHeight + nSearchBoxMargin;
+
+ switch (eType)
+ {
+ case WHOLE:
+ {
+ rPos = Point(0, 0);
+ rSize = maWndSize;
+ }
+ break;
+ case EDIT_SEARCH:
+ {
+ rPos = Point(nSearchBoxMargin, nTopMargin + nMenuHeight + nMenuListMargin);
+ rSize = Size(maWndSize.Width() - 2*nSearchBoxMargin, nSearchBoxHeight);
+ }
+ break;
+ case SINGLE_BTN_AREA:
+ {
+ rPos = Point(nListBoxMargin, nSingleBtnAreaY);
+ rSize = Size(nListBoxWidth, nSingleItemBtnAreaHeight);
+ }
+ break;
+ case CHECK_TOGGLE_ALL:
+ {
+ long h = std::min(maChkToggleAll->CalcMinimumSize().Height(), 26L);
+ rPos = Point(nListBoxMargin, nSingleBtnAreaY);
+ rPos.AdjustX(5 );
+ rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
+ rSize = Size(70, h);
+ }
+ break;
+ case BTN_SINGLE_SELECT:
+ {
+ long h = 26 * fScaleFactor;
+ rPos = Point(nListBoxMargin, nSingleBtnAreaY);
+ rPos.AdjustX(nListBoxWidth - h - 10 - h - 10 );
+ rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
+ rSize = Size(h, h);
+ }
+ break;
+ case BTN_SINGLE_UNSELECT:
+ {
+ long h = 26 * fScaleFactor;
+ rPos = Point(nListBoxMargin, nSingleBtnAreaY);
+ rPos.AdjustX(nListBoxWidth - h - 10 );
+ rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
+ rSize = Size(h, h);
+ }
+ break;
+ case LISTBOX_AREA_OUTER:
+ {
+ rPos = Point(nListBoxMargin, nSingleBtnAreaY + nSingleItemBtnAreaHeight-1);
+ rSize = Size(nListBoxWidth, nListBoxHeight);
+ }
+ break;
+ case LISTBOX_AREA_INNER:
+ {
+ rPos = Point(nListBoxMargin, nSingleBtnAreaY + nSingleItemBtnAreaHeight-1);
+ rPos.AdjustX(nListBoxInnerPadding );
+ rPos.AdjustY(nListBoxInnerPadding );
+
+ rSize = Size(nListBoxWidth, nListBoxHeight);
+ rSize.AdjustWidth( -(nListBoxInnerPadding*2) );
+ rSize.AdjustHeight( -(nListBoxInnerPadding*2) );
+ }
+ break;
+ case BTN_OK:
+ {
+ long x = (maWndSize.Width() - nBtnWidth*2)/3;
+ long y = maWndSize.Height() - nBottomMargin - nBtnHeight;
+ rPos = Point(x, y);
+ rSize = Size(nBtnWidth, nBtnHeight);
+ }
+ break;
+ case BTN_CANCEL:
+ {
+ long x = (maWndSize.Width() - nBtnWidth*2)/3*2 + nBtnWidth;
+ long y = maWndSize.Height() - nBottomMargin - nBtnHeight;
+ rPos = Point(x, y);
+ rSize = Size(nBtnWidth, nBtnHeight);
+ }
+ break;
+ default:
+ ;
+ }
+}
+
+void ScCheckListMenuWindow::packWindow()
+{
+ maMenuSize = getMenuSize();
+
+ if (maWndSize.Width() < maMenuSize.Width())
+ // Widen the window to fit the menu items.
+ maWndSize.setWidth( maMenuSize.Width() );
+
+ // Set proper window height based on the number of menu items.
+ if (maWndSize.Height() < maMenuSize.Height()*2.8)
+ maWndSize.setHeight( maMenuSize.Height()*2.8 );
+
+ // TODO: Make sure the window height never exceeds the height of the
+ // screen. Also do adjustment based on the number of check box items.
+
+ SetOutputSizePixel(maWndSize);
+
+ const StyleSettings& rStyle = GetSettings().GetStyleSettings();
+
+ Point aPos;
+ Size aSize;
+ getSectionPosSize(aPos, aSize, WHOLE);
+ SetOutputSizePixel(aSize);
+
+ getSectionPosSize(aPos, aSize, BTN_OK);
+ maBtnOk->SetPosSizePixel(aPos, aSize);
+ maBtnOk->SetFont(getLabelFont());
+ maBtnOk->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
+ maBtnOk->Show();
+
+ getSectionPosSize(aPos, aSize, BTN_CANCEL);
+ maBtnCancel->SetPosSizePixel(aPos, aSize);
+ maBtnCancel->SetFont(getLabelFont());
+ maBtnCancel->Show();
+
+ getSectionPosSize(aPos, aSize, EDIT_SEARCH);
+ maEdSearch->SetPosSizePixel(aPos, aSize);
+ maEdSearch->SetFont(getLabelFont());
+ maEdSearch->SetControlBackground(rStyle.GetFieldColor());
+ maEdSearch->SetPlaceholderText(ScResId(STR_EDIT_SEARCH_ITEMS));
+ maEdSearch->SetModifyHdl( LINK(this, ScCheckListMenuWindow, EdModifyHdl) );
+ maEdSearch->Show();
+
+ getSectionPosSize(aPos, aSize, LISTBOX_AREA_INNER);
+ maChecks->SetPosSizePixel(aPos, aSize);
+ maChecks->SetFont(getLabelFont());
+ maChecks->SetCheckButtonHdl( LINK(this, ScCheckListMenuWindow, CheckHdl) );
+ maChecks->Show();
+
+ getSectionPosSize(aPos, aSize, CHECK_TOGGLE_ALL);
+ maChkToggleAll->SetPosSizePixel(aPos, aSize);
+ maChkToggleAll->SetFont(getLabelFont());
+ maChkToggleAll->SetText(ScResId(STR_BTN_TOGGLE_ALL));
+ maChkToggleAll->SetTextColor(rStyle.GetMenuTextColor());
+ maChkToggleAll->SetControlBackground(rStyle.GetMenuColor());
+ maChkToggleAll->SetClickHdl( LINK(this, ScCheckListMenuWindow, TriStateHdl) );
+ maChkToggleAll->Show();
+
+ float fScaleFactor = GetDPIScaleFactor();
+
+ ;
+
+ getSectionPosSize(aPos, aSize, BTN_SINGLE_SELECT);
+ maBtnSelectSingle->SetPosSizePixel(aPos, aSize);
+ maBtnSelectSingle->SetQuickHelpText(ScResId(STR_BTN_SELECT_CURRENT));
+ maBtnSelectSingle->SetModeImage(Image(StockImage::Yes, RID_BMP_SELECT_CURRENT));
+ maBtnSelectSingle->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
+ maBtnSelectSingle->Show();
+
+ BitmapEx aSingleUnselectBmp(RID_BMP_UNSELECT_CURRENT);
+ if (fScaleFactor > 1)
+ aSingleUnselectBmp.Scale(fScaleFactor, fScaleFactor, BmpScaleFlag::Fast);
+ Image aSingleUnselect(aSingleUnselectBmp);
+
+ getSectionPosSize(aPos, aSize, BTN_SINGLE_UNSELECT);
+ maBtnUnselectSingle->SetPosSizePixel(aPos, aSize);
+ maBtnUnselectSingle->SetQuickHelpText(ScResId(STR_BTN_UNSELECT_CURRENT));
+ maBtnUnselectSingle->SetModeImage(aSingleUnselect);
+ maBtnUnselectSingle->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
+ maBtnUnselectSingle->Show();
+}
+
+void ScCheckListMenuWindow::setAllMemberState(bool bSet)
+{
+ size_t n = maMembers.size();
+ std::set<SvTreeListEntry*> aParents;
+ for (size_t i = 0; i < n; ++i)
+ {
+ aParents.insert(maMembers[i].mpParent);
+ }
+
+ for (const auto& pParent : aParents)
+ {
+ if (!pParent)
+ {
+ sal_uInt32 nCount = maChecks->GetEntryCount();
+ for( sal_uInt32 i = 0; i < nCount; ++i)
+ {
+ SvTreeListEntry* pEntry = maChecks->GetEntry(i);
+ if (!pEntry)
+ continue;
+
+ maChecks->CheckEntry(pEntry, bSet);
+ }
+ }
+ else
+ {
+ SvTreeListEntries& rEntries = pParent->GetChildEntries();
+ for (const auto& rxEntry : rEntries)
+ {
+ maChecks->CheckEntry(rxEntry.get(), bSet);
+ }
+ }
+ }
+
+ if (!maConfig.mbAllowEmptySet)
+ // We need to have at least one member selected.
+ maBtnOk->Enable(maChecks->GetCheckedEntryCount() != 0);
+}
+
+void ScCheckListMenuWindow::selectCurrentMemberOnly(bool bSet)
+{
+ setAllMemberState(!bSet);
+ SvTreeListEntry* pEntry = maChecks->GetCurEntry();
+ if (!pEntry)
+ return;
+ maChecks->CheckEntry(pEntry, bSet );
+
+ // Make sure all checkboxes are invalidated.
+ Invalidate();
+}
+
+IMPL_LINK( ScCheckListMenuWindow, ButtonHdl, Button*, pBtn, void )
+{
+ if (pBtn == maBtnOk.get())
+ close(true);
+ else if (pBtn == maBtnSelectSingle.get())
+ {
+ selectCurrentMemberOnly(true);
+ CheckHdl(maChecks.get());
+ }
+ else if (pBtn == maBtnUnselectSingle.get())
+ {
+ selectCurrentMemberOnly(false);
+ CheckHdl(maChecks.get());
+ }
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuWindow, TriStateHdl, Button*, void)
+{
+ switch (mePrevToggleAllState)
+ {
+ case TRISTATE_FALSE:
+ maChkToggleAll->SetState(TRISTATE_TRUE);
+ setAllMemberState(true);
+ break;
+ case TRISTATE_TRUE:
+ maChkToggleAll->SetState(TRISTATE_FALSE);
+ setAllMemberState(false);
+ break;
+ case TRISTATE_INDET:
+ default:
+ maChkToggleAll->SetState(TRISTATE_TRUE);
+ setAllMemberState(true);
+ break;
+ }
+
+ mePrevToggleAllState = maChkToggleAll->GetState();
+ maTabStops.SetTabStop(maChkToggleAll); // Needed for when accelerator is used
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuWindow, EdModifyHdl, Edit&, void)
+{
+ OUString aSearchText = maEdSearch->GetText();
+ aSearchText = ScGlobal::getCharClassPtr()->lowercase( aSearchText );
+ bool bSearchTextEmpty = aSearchText.isEmpty();
+ size_t n = maMembers.size();
+ size_t nSelCount = 0;
+ bool bSomeDateDeletes = false;
+
+ maChecks->SetUpdateMode(false);
+
+ if (bSearchTextEmpty && !mbHasDates)
+ {
+ // when there are a lot of rows, it is cheaper to simply clear the tree and re-initialise
+ maChecks->Clear();
+ nSelCount = initMembers();
+ }
+ else
+ {
+ for (size_t i = 0; i < n; ++i)
+ {
+ bool bIsDate = maMembers[i].mbDate;
+ bool bPartialMatch = false;
+
+ OUString aLabelDisp = maMembers[i].maName;
+ if ( aLabelDisp.isEmpty() )
+ aLabelDisp = ScResId( STR_EMPTYDATA );
+
+ if ( !bSearchTextEmpty )
+ {
+ if ( !bIsDate )
+ bPartialMatch = ( ScGlobal::getCharClassPtr()->lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
+ else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
+ bPartialMatch = (ScGlobal::getCharClassPtr()->lowercase( OUString(
+ maMembers[i].maRealName + maMembers[i].maDateParts[1] )).indexOf( aSearchText ) != -1);
+ else
+ continue;
+ }
+ else if ( bIsDate && maMembers[i].meDatePartType != ScCheckListMember::DAY )
+ continue;
+
+ if ( bSearchTextEmpty )
+ {
+ SvTreeListEntry* pLeaf = maChecks->ShowCheckEntry( aLabelDisp, maMembers[i], true, maMembers[i].mbVisible );
+ updateMemberParents( pLeaf, i );
+ if ( maMembers[i].mbVisible )
+ ++nSelCount;
+ continue;
+ }
+
+ if ( bPartialMatch )
+ {
+ SvTreeListEntry* pLeaf = maChecks->ShowCheckEntry( aLabelDisp, maMembers[i] );
+ updateMemberParents( pLeaf, i );
+ ++nSelCount;
+ }
+ else
+ {
+ maChecks->ShowCheckEntry( aLabelDisp, maMembers[i], false, false );
+ if( bIsDate )
+ bSomeDateDeletes = true;
+ }
+ }
+ }
+
+ if ( bSomeDateDeletes )
+ {
+ for (size_t i = 0; i < n; ++i)
+ {
+ if ( !maMembers[i].mbDate ) continue;
+ if ( maMembers[i].meDatePartType != ScCheckListMember::DAY ) continue;
+ updateMemberParents( nullptr, i );
+ }
+ }
+
+ maChecks->SetUpdateMode(true);
+
+ if ( nSelCount == n )
+ maChkToggleAll->SetState( TRISTATE_TRUE );
+ else if ( nSelCount == 0 )
+ maChkToggleAll->SetState( TRISTATE_FALSE );
+ else
+ maChkToggleAll->SetState( TRISTATE_INDET );
+
+ if ( !maConfig.mbAllowEmptySet )
+ {
+ const bool bEmptySet( nSelCount == 0 );
+ maChecks->Enable( !bEmptySet );
+ maChkToggleAll->Enable( !bEmptySet );
+ maBtnSelectSingle->Enable( !bEmptySet );
+ maBtnUnselectSingle->Enable( !bEmptySet );
+ maBtnOk->Enable( !bEmptySet );
+ }
+}
+
+IMPL_LINK( ScCheckListMenuWindow, CheckHdl, SvTreeListBox*, pChecks, void )
+{
+ if (pChecks != maChecks.get())
+ return;
+ SvTreeListEntry* pEntry = pChecks->GetHdlEntry();
+ if ( pEntry )
+ maChecks->CheckEntry( pEntry, ( pChecks->GetCheckButtonState( pEntry ) == SvButtonState::Checked ) );
+ size_t nNumChecked = maChecks->GetCheckedEntryCount();
+ if (nNumChecked == maMembers.size())
+ // all members visible
+ maChkToggleAll->SetState(TRISTATE_TRUE);
+ else if (nNumChecked == 0)
+ // no members visible
+ maChkToggleAll->SetState(TRISTATE_FALSE);
+ else
+ maChkToggleAll->SetState(TRISTATE_INDET);
+
+ if (!maConfig.mbAllowEmptySet)
+ // We need to have at least one member selected.
+ maBtnOk->Enable(nNumChecked != 0);
+
+ mePrevToggleAllState = maChkToggleAll->GetState();
+}
+
+void ScCheckListMenuWindow::MouseMove(const MouseEvent& rMEvt)
+{
+ ScMenuFloatingWindow::MouseMove(rMEvt);
+
+ size_t nSelectedMenu = getSelectedMenuItem();
+ if (nSelectedMenu == MENU_NOT_SELECTED)
+ queueCloseSubMenu();
+}
+
+bool ScCheckListMenuWindow::EventNotify(NotifyEvent& rNEvt)
+{
+ MouseNotifyEvent nType = rNEvt.GetType();
+ if (HasFocus() && nType == MouseNotifyEvent::GETFOCUS)
+ {
+ setSelectedMenuItem( 0 , false, false );
+ return true;
+ }
+ if (nType == MouseNotifyEvent::KEYINPUT)
+ {
+ const KeyEvent* pKeyEvent = rNEvt.GetKeyEvent();
+ const vcl::KeyCode& rCode = pKeyEvent->GetKeyCode();
+ const sal_uInt16 nCode = rCode.GetCode();
+ if (nCode != KEY_RETURN)
+ {
+ bool bShift = rCode.IsShift();
+ if (nCode == KEY_TAB)
+ maTabStops.CycleFocus(bShift);
+ return true;
+ }
+ }
+ return ScMenuFloatingWindow::EventNotify(rNEvt);
+}
+
+void ScCheckListMenuWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ ScMenuFloatingWindow::Paint(rRenderContext, rRect);
+
+ const StyleSettings& rStyle = GetSettings().GetStyleSettings();
+ Color aMemberBackColor = rStyle.GetFieldColor();
+ Color aBorderColor = rStyle.GetShadowColor();
+
+ Point aPos;
+ Size aSize;
+ getSectionPosSize(aPos, aSize, LISTBOX_AREA_OUTER);
+
+ // Member list box background
+ rRenderContext.SetFillColor(aMemberBackColor);
+ rRenderContext.SetLineColor(aBorderColor);
+ rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
+
+ // Single-action button box
+ getSectionPosSize(aPos, aSize, SINGLE_BTN_AREA);
+ rRenderContext.SetFillColor(rStyle.GetMenuColor());
+ rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
+}
+
+void ScCheckListMenuWindow::updateMemberParents( const SvTreeListEntry* pLeaf, size_t nIdx )
+{
+
+ if ( !maMembers[nIdx].mbDate || maMembers[nIdx].meDatePartType != ScCheckListMember::DAY )
+ return;
+
+ OUString aYearName = maMembers[nIdx].maDateParts[0];
+ OUString aMonthName = maMembers[nIdx].maDateParts[1];
+ auto aItr = maYearMonthMap.find(aYearName + aMonthName);
+
+ if ( pLeaf )
+ {
+ SvTreeListEntry* pMonthEntry = pLeaf->GetParent();
+ SvTreeListEntry* pYearEntry = pMonthEntry ? pMonthEntry->GetParent() : nullptr;
+
+ maMembers[nIdx].mpParent = pMonthEntry;
+ if ( aItr != maYearMonthMap.end() )
+ {
+ size_t nMonthIdx = aItr->second;
+ maMembers[nMonthIdx].mpParent = pYearEntry;
+ }
+ }
+ else
+ {
+ SvTreeListEntry* pYearEntry = maChecks->FindEntry( nullptr, aYearName );
+ if ( aItr != maYearMonthMap.end() && !pYearEntry )
+ {
+ size_t nMonthIdx = aItr->second;
+ maMembers[nMonthIdx].mpParent = nullptr;
+ maMembers[nIdx].mpParent = nullptr;
+ }
+ else if ( pYearEntry && !maChecks->FindEntry( pYearEntry, aMonthName ) )
+ maMembers[nIdx].mpParent = nullptr;
+ }
+}
+
+Reference<XAccessible> ScCheckListMenuWindow::CreateAccessible()
+{
+ if (!mxAccessible.is() && maEdSearch)
+ {
+ mxAccessible.set(new ScAccessibleFilterTopWindow(
+ GetAccessibleParentWindow()->GetAccessible(), this, getName()));
+ ScAccessibleFilterTopWindow* pAccTop = static_cast<ScAccessibleFilterTopWindow*>(mxAccessible.get());
+ fillMenuItemsToAccessible(pAccTop);
+
+ pAccTop->setAccessibleChild(
+ maEdSearch->CreateAccessible(), ScAccessibleFilterTopWindow::EDIT_SEARCH_BOX);
+ pAccTop->setAccessibleChild(
+ maChecks->CreateAccessible(), ScAccessibleFilterTopWindow::LISTBOX);
+ pAccTop->setAccessibleChild(
+ maChkToggleAll->CreateAccessible(), ScAccessibleFilterTopWindow::TOGGLE_ALL);
+ pAccTop->setAccessibleChild(
+ maBtnSelectSingle->CreateAccessible(), ScAccessibleFilterTopWindow::SINGLE_ON_BTN);
+ pAccTop->setAccessibleChild(
+ maBtnUnselectSingle->CreateAccessible(), ScAccessibleFilterTopWindow::SINGLE_OFF_BTN);
+ pAccTop->setAccessibleChild(
+ maBtnOk->CreateAccessible(), ScAccessibleFilterTopWindow::OK_BTN);
+ pAccTop->setAccessibleChild(
+ maBtnCancel->CreateAccessible(), ScAccessibleFilterTopWindow::CANCEL_BTN);
+ }
+
+ return mxAccessible;
+}
+
+void ScCheckListMenuWindow::setMemberSize(size_t n)
+{
+ maMembers.reserve(n);
+}
+
+void ScCheckListMenuWindow::addDateMember(const OUString& rsName, double nVal, bool bVisible)
+{
+ ScDocument* pDoc = getDoc();
+ SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
+
+ // Convert the numeric date value to a date object.
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays(rtl::math::approxFloor(nVal));
+
+ sal_Int16 nYear = aDate.GetYear();
+ sal_uInt16 nMonth = aDate.GetMonth();
+ sal_uInt16 nDay = aDate.GetDay();
+
+ // Get the localized month name list.
+ CalendarWrapper* pCalendar = ScGlobal::GetCalendar();
+ uno::Sequence<i18n::CalendarItem2> aMonths = pCalendar->getMonths();
+ if (aMonths.getLength() < nMonth)
+ return;
+
+ OUString aYearName = OUString::number(nYear);
+ OUString aMonthName = aMonths[nMonth-1].FullName;
+ OUString aDayName = OUString::number(nDay);
+
+ if ( aDayName.getLength() == 1 )
+ aDayName = "0" + aDayName;
+
+ maChecks->SetUpdateMode(false);
+
+ SvTreeListEntry* pYearEntry = maChecks->FindEntry(nullptr, aYearName);
+ if (!pYearEntry)
+ {
+ pYearEntry = maChecks->InsertEntry(aYearName, nullptr, true);
+ ScCheckListMember aMemYear;
+ aMemYear.maName = aYearName;
+ aMemYear.maRealName = rsName;
+ aMemYear.mbDate = true;
+ aMemYear.mbLeaf = false;
+ aMemYear.mbVisible = bVisible;
+ aMemYear.mpParent = nullptr;
+ aMemYear.meDatePartType = ScCheckListMember::YEAR;
+ maMembers.push_back(aMemYear);
+ }
+
+ SvTreeListEntry* pMonthEntry = maChecks->FindEntry(pYearEntry, aMonthName);
+ if (!pMonthEntry)
+ {
+ pMonthEntry = maChecks->InsertEntry(aMonthName, pYearEntry, true);
+ ScCheckListMember aMemMonth;
+ aMemMonth.maName = aMonthName;
+ aMemMonth.maRealName = rsName;
+ aMemMonth.mbDate = true;
+ aMemMonth.mbLeaf = false;
+ aMemMonth.mbVisible = bVisible;
+ aMemMonth.mpParent = pYearEntry;
+ aMemMonth.meDatePartType = ScCheckListMember::MONTH;
+ maMembers.push_back(aMemMonth);
+ maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
+ }
+
+ SvTreeListEntry* pDayEntry = maChecks->FindEntry(pMonthEntry, aDayName);
+ if (!pDayEntry)
+ {
+ maChecks->InsertEntry(aDayName, pMonthEntry);
+ ScCheckListMember aMemDay;
+ aMemDay.maName = aDayName;
+ aMemDay.maRealName = rsName;
+ aMemDay.maDateParts.resize(2);
+ aMemDay.maDateParts[0] = aYearName;
+ aMemDay.maDateParts[1] = aMonthName;
+ aMemDay.mbDate = true;
+ aMemDay.mbLeaf = true;
+ aMemDay.mbVisible = bVisible;
+ aMemDay.mpParent = pMonthEntry;
+ aMemDay.meDatePartType = ScCheckListMember::DAY;
+ maMembers.push_back(aMemDay);
+ }
+
+ maChecks->SetUpdateMode(true);
+}
+
+void ScCheckListMenuWindow::addMember(const OUString& rName, bool bVisible)
+{
+ ScCheckListMember aMember;
+ aMember.maName = rName;
+ aMember.mbDate = false;
+ aMember.mbLeaf = true;
+ aMember.mbVisible = bVisible;
+ aMember.mpParent = nullptr;
+ maMembers.push_back(aMember);
+}
+
+ScTabStops::ScTabStops( ScCheckListMenuWindow* pMenuWin ) :
+ mpMenuWindow( pMenuWin ),
+ maControlToPos( ControlToPosMap() ),
+ mnCurTabStop(0)
+{
+ maControls.reserve( 8 );
+}
+
+ScTabStops::~ScTabStops()
+{}
+
+void ScTabStops::AddTabStop( vcl::Window* pWin )
+{
+ maControls.emplace_back(pWin );
+ maControlToPos[pWin] = maControls.size() - 1;
+}
+
+void ScTabStops::SetTabStop( vcl::Window* pWin )
+{
+ if ( maControls.empty() )
+ return;
+ ControlToPosMap::const_iterator aIter = maControlToPos.find( pWin );
+ if ( aIter == maControlToPos.end() )
+ return;
+ if ( aIter->second == mnCurTabStop )
+ return;
+ if ( mnCurTabStop < maControls.size() )
+ {
+ maControls[mnCurTabStop]->SetFakeFocus( false );
+ maControls[mnCurTabStop]->LoseFocus();
+ }
+ mnCurTabStop = aIter->second;
+ maControls[mnCurTabStop]->SetFakeFocus( true );
+ maControls[mnCurTabStop]->GrabFocus();
+}
+
+void ScTabStops::CycleFocus( bool bReverse )
+{
+ if (maControls.empty())
+ return;
+ if ( mnCurTabStop < maControls.size() )
+ {
+ maControls[mnCurTabStop]->SetFakeFocus( false );
+ maControls[mnCurTabStop]->LoseFocus();
+ }
+ else
+ mnCurTabStop = 0;
+
+ if ( mpMenuWindow && mnCurTabStop == 0 )
+ mpMenuWindow->clearSelectedMenuItem();
+
+ size_t nIterCount = 0;
+
+ if ( bReverse )
+ {
+ do
+ {
+ if ( mnCurTabStop > 0 )
+ --mnCurTabStop;
+ else
+ mnCurTabStop = maControls.size() - 1;
+ ++nIterCount;
+ } while ( nIterCount <= maControls.size() && !maControls[mnCurTabStop]->IsEnabled() );
+ }
+ else
+ {
+ do
+ {
+ ++mnCurTabStop;
+ if ( mnCurTabStop >= maControls.size() )
+ mnCurTabStop = 0;
+ ++nIterCount;
+ } while ( nIterCount <= maControls.size() && !maControls[mnCurTabStop]->IsEnabled() );
+ }
+
+ if ( nIterCount <= maControls.size() )
+ {
+ maControls[mnCurTabStop]->SetFakeFocus( true );
+ maControls[mnCurTabStop]->GrabFocus();
+ }
+ // else : all controls are disabled, so can't do anything
+}
+
+void ScTabStops::clear()
+{
+ mnCurTabStop = 0;
+ maControlToPos.clear();
+ maControls.clear();
+}
+
+ScCheckListBox::ScCheckListBox( vcl::Window* pParent )
+ : SvTreeListBox( pParent, 0 ), mbSeenMouseButtonDown( false )
+{
+ Init();
+ set_id("check_list_box");
+}
+
+SvTreeListEntry* ScCheckListBox::FindEntry( SvTreeListEntry* pParent, const OUString& sNode )
+{
+ sal_uInt32 nRootPos = 0;
+ SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : GetEntry( nRootPos );
+ while ( pEntry )
+ {
+ if ( sNode == GetEntryText( pEntry ) )
+ return pEntry;
+
+ pEntry = pParent ? pEntry->NextSibling() : GetEntry( ++nRootPos );
+ }
+ return nullptr;
+}
+
+void ScCheckListBox::Init()
+{
+ mpCheckButton.reset( new SvLBoxButtonData( this ) );
+ EnableCheckButton( mpCheckButton.get() );
+ SetNodeDefaultImages();
+}
+
+void ScCheckListBox::GetRecursiveChecked( SvTreeListEntry* pEntry, std::unordered_set<OUString>& vOut,
+ OUString& rLabel )
+{
+ if (GetCheckButtonState(pEntry) == SvButtonState::Checked)
+ {
+ // We have to hash parents and children together.
+ // Per convention for easy access in getResult()
+ // "child;parent;grandparent" while descending.
+ if (rLabel.isEmpty())
+ rLabel = GetEntryText(pEntry);
+ else
+ rLabel = GetEntryText(pEntry) + ";" + rLabel;
+
+ // Prerequisite: the selection mechanism guarantees that if a child is
+ // selected then also the parent is selected, so we only have to
+ // inspect the children in case the parent is selected.
+ if (pEntry->HasChildren())
+ {
+ const SvTreeListEntries& rChildren = pEntry->GetChildEntries();
+ for (auto& rChild : rChildren)
+ {
+ OUString aLabel = rLabel;
+ GetRecursiveChecked( rChild.get(), vOut, aLabel);
+ if (!aLabel.isEmpty() && aLabel != rLabel)
+ vOut.insert( aLabel);
+ }
+ // Let the caller not add the parent alone.
+ rLabel.clear();
+ }
+ }
+}
+
+std::unordered_set<OUString> ScCheckListBox::GetAllChecked()
+{
+ std::unordered_set<OUString> vResults(0);
+ sal_uInt32 nRootPos = 0;
+ SvTreeListEntry* pEntry = GetEntry(nRootPos);
+ while (pEntry)
+ {
+ OUString aLabel;
+ GetRecursiveChecked( pEntry, vResults, aLabel);
+ if (!aLabel.isEmpty())
+ vResults.insert( aLabel);
+ pEntry = GetEntry(++nRootPos);
+ }
+
+ return vResults;
+}
+
+bool ScCheckListBox::IsChecked( const OUString& sName, SvTreeListEntry* pParent )
+{
+ SvTreeListEntry* pEntry = FindEntry( pParent, sName );
+ return pEntry && GetCheckButtonState( pEntry ) == SvButtonState::Checked;
+}
+
+void ScCheckListBox::CheckEntry( const OUString& sName, SvTreeListEntry* pParent, bool bCheck )
+{
+ SvTreeListEntry* pEntry = FindEntry( pParent, sName );
+ if ( pEntry )
+ CheckEntry( pEntry, bCheck );
+}
+
+// Recursively check all children of pParent
+void ScCheckListBox::CheckAllChildren( SvTreeListEntry* pParent, bool bCheck )
+{
+ if ( pParent )
+ {
+ SetCheckButtonState(
+ pParent, bCheck ? SvButtonState::Checked : SvButtonState::Unchecked );
+ }
+ SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : First();
+ while ( pEntry )
+ {
+ CheckAllChildren( pEntry, bCheck );
+ pEntry = pEntry->NextSibling();
+ }
+}
+
+void ScCheckListBox::CheckEntry( SvTreeListEntry* pParent, bool bCheck )
+{
+ // recursively check all items below pParent
+ CheckAllChildren( pParent, bCheck );
+ // checking pParent can affect ancestors, e.g. if ancestor is unchecked and pParent is
+ // now checked then the ancestor needs to be checked also
+ SvTreeListEntry* pAncestor = GetParent(pParent);
+ if ( pAncestor )
+ {
+ while ( pAncestor )
+ {
+ // if any first level children checked then ancestor
+ // needs to be checked, similarly if no first level children
+ // checked then ancestor needs to be unchecked
+ SvTreeListEntry* pChild = FirstChild( pAncestor );
+ bool bChildChecked = false;
+
+ while ( pChild )
+ {
+ if ( GetCheckButtonState( pChild ) == SvButtonState::Checked )
+ {
+ bChildChecked = true;
+ break;
+ }
+ pChild = pChild->NextSibling();
+ }
+ SetCheckButtonState( pAncestor, bChildChecked ? SvButtonState::Checked : SvButtonState::Unchecked );
+ pAncestor = GetParent(pAncestor);
+ }
+ }
+}
+
+SvTreeListEntry* ScCheckListBox::ShowCheckEntry( const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck )
+{
+ SvTreeListEntry* pEntry = nullptr;
+ if (!rMember.mbDate || rMember.mpParent)
+ pEntry = FindEntry( rMember.mpParent, sName );
+
+ if ( bShow )
+ {
+ if ( !pEntry )
+ {
+ if (rMember.mbDate)
+ {
+ if (rMember.maDateParts.empty())
+ return nullptr;
+
+ SvTreeListEntry* pYearEntry = FindEntry( nullptr, rMember.maDateParts[0] );
+ if ( !pYearEntry )
+ pYearEntry = InsertEntry( rMember.maDateParts[0], nullptr, true );
+ SvTreeListEntry* pMonthEntry = FindEntry( pYearEntry, rMember.maDateParts[1] );
+ if ( !pMonthEntry )
+ pMonthEntry = InsertEntry( rMember.maDateParts[1], pYearEntry, true );
+ SvTreeListEntry* pDayEntry = FindEntry( pMonthEntry, rMember.maName );
+ if ( !pDayEntry )
+ pDayEntry = InsertEntry( rMember.maName, pMonthEntry );
+
+ return pDayEntry; // Return leaf node
+ }
+
+ pEntry = InsertEntry(
+ sName);
+
+ SetCheckButtonState(
+ pEntry, bCheck ? SvButtonState::Checked : SvButtonState::Unchecked);
+ }
+ else
+ CheckEntry( pEntry, bCheck );
+ }
+ else if ( pEntry )
+ {
+ GetModel()->Remove( pEntry );
+ SvTreeListEntry* pParent = rMember.mpParent;
+ while ( pParent && !pParent->HasChildren() )
+ {
+ SvTreeListEntry* pTmp = pParent;
+ pParent = pTmp->GetParent();
+ GetModel()->Remove( pTmp );
+ }
+ }
+ return nullptr;
+}
+
+void ScCheckListBox::CountCheckedEntries( SvTreeListEntry* pParent, sal_uLong& nCount ) const
+{
+ if ( pParent && GetCheckButtonState( pParent ) == SvButtonState::Checked )
+ nCount++;
+ // Iterate over the children
+ SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : First();
+ while ( pEntry )
+ {
+ CountCheckedEntries( pEntry, nCount );
+ pEntry = pEntry->NextSibling();
+ }
+}
+
+sal_uInt16 ScCheckListBox::GetCheckedEntryCount() const
+{
+ sal_uLong nCount = 0;
+ CountCheckedEntries( nullptr, nCount );
+ return nCount;
+}
+
+void ScCheckListBox::KeyInput( const KeyEvent& rKEvt )
+{
+ const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
+
+ if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
+ {
+ SvTreeListEntry* pEntry = GetCurEntry();
+ if ( pEntry )
+ {
+ bool bCheck = ( GetCheckButtonState( pEntry ) == SvButtonState::Checked );
+ CheckEntry( pEntry, !bCheck );
+ if ( bCheck != ( GetCheckButtonState( pEntry ) == SvButtonState::Checked ) )
+ CheckButtonHdl();
+ }
+ }
+ else if ( GetEntryCount() )
+ SvTreeListBox::KeyInput( rKEvt );
+}
+
+void ScCheckListBox::MouseButtonDown(const MouseEvent& rMEvt)
+{
+ SvTreeListBox::MouseButtonDown( rMEvt );
+ if ( rMEvt.IsLeft() )
+ mbSeenMouseButtonDown = true;
+}
+
+void ScCheckListBox::MouseButtonUp(const MouseEvent& rMEvt)
+{
+ SvTreeListBox::MouseButtonUp( rMEvt );
+ if ( mpTabStops && mbSeenMouseButtonDown && rMEvt.IsLeft() )
+ {
+ mpTabStops->SetTabStop( this );
+ mbSeenMouseButtonDown = false;
+ }
+}
+
+void ScSearchEdit::MouseButtonDown(const MouseEvent& rMEvt)
+{
+ Edit::MouseButtonDown( rMEvt );
+ if ( mpTabStops && rMEvt.IsLeft() && rMEvt.GetClicks() >= 1 )
+ mpTabStops->SetTabStop( this );
+}
+
+void ScCheckListMenuWindow::setHasDates(bool bHasDates)
+{
+ mbHasDates = bHasDates;
+ // Enables type-ahead search in the check list box.
+ maChecks->SetQuickSearch(true);
+ if (mbHasDates)
+ maChecks->SetStyle(WB_HASBUTTONS | WB_HASLINES | WB_HASLINESATROOT | WB_HASBUTTONSATROOT);
+ else
+ maChecks->SetStyle(WB_HASBUTTONS);
+}
+
+size_t ScCheckListMenuWindow::initMembers()
+{
+ size_t n = maMembers.size();
+ size_t nVisMemCount = 0;
+
+ maChecks->SetUpdateMode(false);
+ maChecks->GetModel()->EnableInvalidate(false);
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (maMembers[i].mbDate)
+ {
+ maChecks->CheckEntry(maMembers[i].maName, maMembers[i].mpParent, maMembers[i].mbVisible);
+ // Expand first node of checked dates
+ if (!maMembers[i].mpParent && maChecks->IsChecked(maMembers[i].maName, maMembers[i].mpParent))
+ {
+ SvTreeListEntry* pEntry = maChecks->FindEntry(nullptr, maMembers[i].maName);
+ if (pEntry)
+ maChecks->Expand(pEntry);
+ }
+ }
+ else
+ {
+ OUString aLabel = maMembers[i].maName;
+ if (aLabel.isEmpty())
+ aLabel = ScResId(STR_EMPTYDATA);
+ SvTreeListEntry* pEntry = maChecks->InsertEntry(
+ aLabel);
+
+ maChecks->SetCheckButtonState(
+ pEntry, maMembers[i].mbVisible ? SvButtonState::Checked : SvButtonState::Unchecked);
+ }
+
+ if (maMembers[i].mbVisible)
+ ++nVisMemCount;
+ }
+ if (nVisMemCount == n)
+ {
+ // all members visible
+ maChkToggleAll->SetState(TRISTATE_TRUE);
+ mePrevToggleAllState = TRISTATE_TRUE;
+ }
+ else if (nVisMemCount == 0)
+ {
+ // no members visible
+ maChkToggleAll->SetState(TRISTATE_FALSE);
+ mePrevToggleAllState = TRISTATE_FALSE;
+ }
+ else
+ {
+ maChkToggleAll->SetState(TRISTATE_INDET);
+ mePrevToggleAllState = TRISTATE_INDET;
+ }
+
+ maChecks->GetModel()->EnableInvalidate(true);
+ maChecks->SetUpdateMode(true);
+ return nVisMemCount;
+}
+
+void ScCheckListMenuWindow::setConfig(const Config& rConfig)
+{
+ maConfig = rConfig;
+}
+
+bool ScCheckListMenuWindow::isAllSelected() const
+{
+ return maChkToggleAll->IsChecked();
+}
+
+void ScCheckListMenuWindow::getResult(ResultType& rResult)
+{
+ ResultType aResult;
+ std::unordered_set<OUString> vCheckeds = maChecks->GetAllChecked();
+ size_t n = maMembers.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ if ( maMembers[i].mbLeaf )
+ {
+ OUStringBuffer aLabel = maMembers[i].maName;
+ if (aLabel.isEmpty())
+ aLabel = ScResId(STR_EMPTYDATA);
+
+ /* TODO: performance-wise this looks suspicious, concatenating to
+ * do the lookup for each leaf item seems wasteful. */
+ // Checked labels are in the form "child;parent;grandparent".
+ for (SvTreeListEntry* pParent = maMembers[i].mpParent;
+ pParent && pParent->GetFirstItem( SvLBoxItemType::String);
+ pParent = pParent->GetParent())
+ {
+ aLabel.append(";").append(maChecks->GetEntryText( pParent));
+ }
+ bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
+
+ ResultEntry aResultEntry;
+ aResultEntry.bValid = bState;
+ if ( maMembers[i].mbDate )
+ aResultEntry.aName = maMembers[i].maRealName;
+ else
+ aResultEntry.aName = maMembers[i].maName;
+ aResultEntry.bDate = maMembers[i].mbDate;
+ aResult.insert(aResultEntry);
+ }
+ }
+ rResult.swap(aResult);
+}
+
+void ScCheckListMenuWindow::launch(const tools::Rectangle& rRect)
+{
+ packWindow();
+ if (!maConfig.mbAllowEmptySet)
+ // We need to have at least one member selected.
+ maBtnOk->Enable(maChecks->GetCheckedEntryCount() != 0);
+
+ tools::Rectangle aRect(rRect);
+ if (maConfig.mbRTL)
+ {
+ // In RTL mode, the logical "left" is visual "right".
+ long nLeft = aRect.Left() - aRect.GetWidth();
+ aRect.SetLeft( nLeft );
+ }
+ else if (maWndSize.Width() < aRect.GetWidth())
+ {
+ // Target rectangle (i.e. cell width) is wider than the window.
+ // Simulate right-aligned launch by modifying the target rectangle
+ // size.
+ long nDiff = aRect.GetWidth() - maWndSize.Width();
+ aRect.AdjustLeft(nDiff );
+ }
+
+ StartPopupMode(aRect, (FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus));
+ maTabStops.CycleFocus(); // Set initial focus to the search box ( index = 1 )
+}
+
+void ScCheckListMenuWindow::close(bool bOK)
+{
+ if (bOK && mpOKAction)
+ mpOKAction->execute();
+
+ EndPopupMode();
+}
+
+void ScCheckListMenuWindow::setExtendedData(std::unique_ptr<ExtendedData> p)
+{
+ mpExtendedData = std::move(p);
+}
+
+ScCheckListMenuWindow::ExtendedData* ScCheckListMenuWindow::getExtendedData()
+{
+ return mpExtendedData.get();
+}
+
+void ScCheckListMenuWindow::setOKAction(Action* p)
+{
+ mpOKAction.reset(p);
+}
+
+void ScCheckListMenuWindow::setPopupEndAction(Action* p)
+{
+ mpPopupEndAction.reset(p);
+}
+
+void ScCheckListMenuWindow::handlePopupEnd()
+{
+ clearSelectedMenuItem();
+ if (mpPopupEndAction)
+ mpPopupEndAction->execute();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */