summaryrefslogtreecommitdiffstats
path: root/sc/source/ui/cctrl
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/ui/cctrl')
-rw-r--r--sc/source/ui/cctrl/cbnumberformat.cxx91
-rw-r--r--sc/source/ui/cctrl/cbuttonw.cxx139
-rw-r--r--sc/source/ui/cctrl/checklistmenu.cxx2076
-rw-r--r--sc/source/ui/cctrl/dpcontrol.cxx199
-rw-r--r--sc/source/ui/cctrl/editfield.cxx63
-rw-r--r--sc/source/ui/cctrl/tbzoomsliderctrl.cxx470
6 files changed, 3038 insertions, 0 deletions
diff --git a/sc/source/ui/cctrl/cbnumberformat.cxx b/sc/source/ui/cctrl/cbnumberformat.cxx
new file mode 100644
index 000000000..93f003ebe
--- /dev/null
+++ b/sc/source/ui/cctrl/cbnumberformat.cxx
@@ -0,0 +1,91 @@
+/* -*- 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 <cbnumberformat.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svl/intitem.hxx>
+#include <sc.hrc>
+
+ScNumberFormat::ScNumberFormat(vcl::Window* pParent)
+ : InterimItemWindow(pParent, "modules/scalc/ui/numberbox.ui", "NumberBox")
+ , m_xWidget(m_xBuilder->weld_combo_box("numbertype"))
+{
+ m_xWidget->append_text(ScResId(STR_GENERAL));
+ m_xWidget->append_text(ScResId(STR_NUMBER));
+ m_xWidget->append_text(ScResId(STR_PERCENT));
+ m_xWidget->append_text(ScResId(STR_CURRENCY));
+ m_xWidget->append_text(ScResId(STR_DATE));
+ m_xWidget->append_text(ScResId(STR_TIME));
+ m_xWidget->append_text(ScResId(STR_SCIENTIFIC));
+ m_xWidget->append_text(ScResId(STR_FRACTION));
+ m_xWidget->append_text(ScResId(STR_BOOLEAN_VALUE));
+ m_xWidget->append_text(ScResId(STR_TEXT));
+
+ m_xWidget->connect_changed(LINK(this, ScNumberFormat, NumFormatSelectHdl));
+ m_xWidget->connect_key_press(LINK(this, ScNumberFormat, KeyInputHdl));
+
+ SetSizePixel(m_xWidget->get_preferred_size());
+}
+
+void ScNumberFormat::dispose()
+{
+ m_xWidget.reset();
+ InterimItemWindow::dispose();
+}
+
+ScNumberFormat::~ScNumberFormat()
+{
+ disposeOnce();
+}
+
+void ScNumberFormat::GetFocus()
+{
+ if (m_xWidget)
+ m_xWidget->grab_focus();
+ InterimItemWindow::GetFocus();
+}
+
+IMPL_STATIC_LINK(ScNumberFormat, NumFormatSelectHdl, weld::ComboBox&, rBox, void)
+{
+ auto* pCurSh = SfxViewFrame::Current();
+ if (pCurSh)
+ {
+ SfxDispatcher* pDisp = pCurSh->GetBindings().GetDispatcher();
+ if (pDisp)
+ {
+ const sal_Int32 nVal = rBox.get_active();
+ SfxUInt16Item aItem(SID_NUMBER_TYPE_FORMAT, nVal);
+ pDisp->ExecuteList(SID_NUMBER_TYPE_FORMAT,
+ SfxCallMode::RECORD, {&aItem});
+
+ pCurSh->GetWindow().GrabFocus();
+ }
+ }
+}
+
+IMPL_LINK(ScNumberFormat, KeyInputHdl, const KeyEvent&, rKEvt, bool)
+{
+ return ChildKeyInput(rKEvt);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/cctrl/cbuttonw.cxx b/sc/source/ui/cctrl/cbuttonw.cxx
new file mode 100644
index 000000000..52660ee7a
--- /dev/null
+++ b/sc/source/ui/cctrl/cbuttonw.cxx
@@ -0,0 +1,139 @@
+/* -*- 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 <comphelper/lok.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <cbutton.hxx>
+
+
+ScDDComboBoxButton::ScDDComboBoxButton( OutputDevice* pOutputDevice )
+ : pOut( pOutputDevice )
+{
+ SetOptSizePixel();
+}
+
+ScDDComboBoxButton::~ScDDComboBoxButton()
+{
+}
+
+void ScDDComboBoxButton::SetOutputDevice( OutputDevice* pOutputDevice )
+{
+ pOut = pOutputDevice;
+}
+
+void ScDDComboBoxButton::SetOptSizePixel()
+{
+ aBtnSize = pOut->LogicToPixel(Size(8, 11), MapMode(MapUnit::MapAppFont));
+ aBtnSize.setWidth( std::max(aBtnSize.Width(), pOut->GetSettings().GetStyleSettings().GetScrollBarSize()) );
+}
+
+void ScDDComboBoxButton::Draw( const Point& rAt,
+ const Size& rSize )
+{
+ if ( rSize.IsEmpty() )
+ return;
+
+ // save old state
+ bool bHadFill = pOut->IsFillColor();
+ Color aOldFill = pOut->GetFillColor();
+ bool bHadLine = pOut->IsLineColor();
+ Color aOldLine = pOut->GetLineColor();
+ bool bOldEnable = pOut->IsMapModeEnabled();
+
+ tools::Rectangle aBtnRect( rAt, rSize );
+
+ if (!comphelper::LibreOfficeKit::isActive())
+ pOut->EnableMapMode(false);
+
+ DecorationView aDecoView( pOut);
+
+ tools::Rectangle aInnerRect=aDecoView.DrawButton( aBtnRect, DrawButtonFlags::Default );
+
+ aInnerRect.AdjustLeft(1 );
+ aInnerRect.AdjustTop(1 );
+ aInnerRect.AdjustRight( -1 );
+ aInnerRect.AdjustBottom( -1 );
+
+ Size aInnerSize = aInnerRect.GetSize();
+ Point aInnerCenter = aInnerRect.Center();
+
+ aInnerRect.SetTop( aInnerCenter.Y() - (aInnerSize.Width()>>1) );
+ aInnerRect.SetBottom( aInnerCenter.Y() + (aInnerSize.Width()>>1) );
+
+ ImpDrawArrow( aInnerRect );
+
+ // restore old state
+ pOut->EnableMapMode( bOldEnable );
+ if (bHadLine)
+ pOut->SetLineColor(aOldLine);
+ else
+ pOut->SetLineColor();
+ if (bHadFill)
+ pOut->SetFillColor(aOldFill);
+ else
+ pOut->SetFillColor();
+}
+
+void ScDDComboBoxButton::ImpDrawArrow( const tools::Rectangle& rRect )
+{
+ // no need to save old line and fill color here (is restored after the call)
+
+ tools::Rectangle aPixRect = rRect;
+ Point aCenter = aPixRect.Center();
+ Size aSize = aPixRect.GetSize();
+
+ Size aSize3;
+ aSize3.setWidth( aSize.Width() >> 1 );
+ aSize3.setHeight( aSize.Height() >> 1 );
+
+ Size aSize4;
+ aSize4.setWidth( aSize.Width() >> 2 );
+ aSize4.setHeight( aSize.Height() >> 2 );
+
+ tools::Rectangle aTempRect = aPixRect;
+
+ const StyleSettings& rSett = Application::GetSettings().GetStyleSettings();
+ Color aColor( rSett.GetButtonTextColor() );
+ pOut->SetFillColor( aColor );
+ pOut->SetLineColor( aColor );
+
+ aTempRect.SetLeft( aCenter.X() - aSize4.Width() );
+ aTempRect.SetRight( aCenter.X() + aSize4.Width() );
+ aTempRect.SetTop( aCenter.Y() - aSize3.Height() );
+ aTempRect.SetBottom( aCenter.Y() - 1 );
+
+ pOut->DrawRect( aTempRect );
+
+ Point aPos1( aCenter.X()-aSize3.Width(), aCenter.Y() );
+ Point aPos2( aCenter.X()+aSize3.Width(), aCenter.Y() );
+ while( aPos1.X() <= aPos2.X() )
+ {
+ pOut->DrawLine( aPos1, aPos2 );
+ aPos1.AdjustX( 1 ); aPos2.AdjustX( -1 );
+ aPos1.AdjustY( 1 ); aPos2.AdjustY( 1 );
+ }
+
+ pOut->DrawLine( Point( aCenter.X() - aSize3.Width(), aPos1.Y()+1 ),
+ Point( aCenter.X() + aSize3.Width(), aPos1.Y()+1 ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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: */
diff --git a/sc/source/ui/cctrl/dpcontrol.cxx b/sc/source/ui/cctrl/dpcontrol.cxx
new file mode 100644
index 000000000..2ca413055
--- /dev/null
+++ b/sc/source/ui/cctrl/dpcontrol.cxx
@@ -0,0 +1,199 @@
+/* -*- 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 <dpcontrol.hxx>
+
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <scitems.hxx>
+#include <document.hxx>
+#include <docpool.hxx>
+#include <patattr.hxx>
+
+ScDPFieldButton::ScDPFieldButton(OutputDevice* pOutDev, const StyleSettings* pStyle, const Fraction* pZoomY, ScDocument* pDoc) :
+ mpDoc(pDoc),
+ mpOutDev(pOutDev),
+ mpStyle(pStyle),
+ mbBaseButton(true),
+ mbPopupButton(false),
+ mbHasHiddenMember(false),
+ mbPopupPressed(false),
+ mbPopupLeft(false)
+{
+ if (pZoomY)
+ maZoomY = *pZoomY;
+ else
+ maZoomY = Fraction(1, 1);
+}
+
+ScDPFieldButton::~ScDPFieldButton()
+{
+}
+
+void ScDPFieldButton::setText(const OUString& rText)
+{
+ maText = rText;
+}
+
+void ScDPFieldButton::setBoundingBox(const Point& rPos, const Size& rSize, bool bLayoutRTL)
+{
+ maPos = rPos;
+ maSize = rSize;
+ if (bLayoutRTL)
+ {
+ // rPos is the logical-left position, adjust maPos to visual-left (inside the cell border)
+ maPos.AdjustX( -(maSize.Width() - 1) );
+ }
+}
+
+void ScDPFieldButton::setDrawBaseButton(bool b)
+{
+ mbBaseButton = b;
+}
+
+void ScDPFieldButton::setDrawPopupButton(bool b)
+{
+ mbPopupButton = b;
+}
+
+void ScDPFieldButton::setHasHiddenMember(bool b)
+{
+ mbHasHiddenMember = b;
+}
+
+void ScDPFieldButton::setPopupPressed(bool b)
+{
+ mbPopupPressed = b;
+}
+
+void ScDPFieldButton::setPopupLeft(bool b)
+{
+ mbPopupLeft = b;
+}
+
+void ScDPFieldButton::draw()
+{
+ if (mbBaseButton)
+ {
+ // Background
+ tools::Rectangle aRect(maPos, maSize);
+ mpOutDev->SetLineColor(mpStyle->GetFaceColor());
+ mpOutDev->SetFillColor(mpStyle->GetFaceColor());
+ mpOutDev->DrawRect(aRect);
+
+ // Border lines
+ mpOutDev->SetLineColor(mpStyle->GetLightColor());
+ mpOutDev->DrawLine(maPos, Point(maPos.X(), maPos.Y()+maSize.Height()-1));
+ mpOutDev->DrawLine(maPos, Point(maPos.X()+maSize.Width()-1, maPos.Y()));
+
+ mpOutDev->SetLineColor(mpStyle->GetShadowColor());
+ mpOutDev->DrawLine(Point(maPos.X(), maPos.Y()+maSize.Height()-1),
+ Point(maPos.X()+maSize.Width()-1, maPos.Y()+maSize.Height()-1));
+ mpOutDev->DrawLine(Point(maPos.X()+maSize.Width()-1, maPos.Y()),
+ Point(maPos.X()+maSize.Width()-1, maPos.Y()+maSize.Height()-1));
+
+ // Field name.
+ // Get the font and size the same way as in scenario selection (lcl_DrawOneFrame in gridwin4.cxx)
+ vcl::Font aTextFont( mpStyle->GetAppFont() );
+ if ( mpDoc )
+ {
+ // use ScPatternAttr::GetFont only for font size
+ vcl::Font aAttrFont;
+ mpDoc->GetPool()->GetDefaultItem(ATTR_PATTERN).
+ GetFont( aAttrFont, SC_AUTOCOL_BLACK, mpOutDev, &maZoomY );
+ aTextFont.SetFontSize( aAttrFont.GetFontSize() );
+ }
+ mpOutDev->SetFont(aTextFont);
+ mpOutDev->SetTextColor(mpStyle->GetButtonTextColor());
+
+ Point aTextPos = maPos;
+ long nTHeight = mpOutDev->GetTextHeight();
+ aTextPos.setX(maPos.getX() + 2); // 2 = Margin
+ aTextPos.setY(maPos.getY() + (maSize.Height()-nTHeight)/2);
+
+ mpOutDev->Push(PushFlags::CLIPREGION);
+ mpOutDev->IntersectClipRegion(aRect);
+ mpOutDev->DrawText(aTextPos, maText);
+ mpOutDev->Pop();
+ }
+
+ if (mbPopupButton)
+ drawPopupButton();
+}
+
+void ScDPFieldButton::getPopupBoundingBox(Point& rPos, Size& rSize) const
+{
+ float fScaleFactor = mpOutDev->GetDPIScaleFactor();
+
+ long nMaxSize = 18 * fScaleFactor; // Button max size in either dimension
+
+ long nW = std::min(maSize.getWidth() / 2, nMaxSize);
+ long nH = std::min(maSize.getHeight(), nMaxSize);
+
+ // #i114944# AutoFilter button is left-aligned in RTL.
+ // DataPilot button is always right-aligned for now, so text output isn't affected.
+ if (mbPopupLeft)
+ rPos.setX(maPos.getX());
+ else
+ rPos.setX(maPos.getX() + maSize.getWidth() - nW);
+
+ rPos.setY(maPos.getY() + maSize.getHeight() - nH);
+ rSize.setWidth(nW);
+ rSize.setHeight(nH);
+}
+
+void ScDPFieldButton::drawPopupButton()
+{
+ Point aPos;
+ Size aSize;
+ getPopupBoundingBox(aPos, aSize);
+
+ float fScaleFactor = mpOutDev->GetDPIScaleFactor();
+
+ // Background & outer black border
+ mpOutDev->SetLineColor(COL_BLACK);
+ Color aBackgroundColor = mbPopupPressed ? mpStyle->GetShadowColor() : mpStyle->GetFaceColor();
+ mpOutDev->SetFillColor(aBackgroundColor);
+ mpOutDev->DrawRect(tools::Rectangle(aPos, aSize));
+
+ // the arrowhead
+ Color aArrowColor = mbHasHiddenMember ? mpStyle->GetHighlightLinkColor() : mpStyle->GetButtonTextColor();
+ mpOutDev->SetLineColor(aArrowColor);
+ mpOutDev->SetFillColor(aArrowColor);
+
+ Point aCenter(aPos.X() + (aSize.Width() / 2), aPos.Y() + (aSize.Height() / 2));
+
+ Size aArrowSize(4 * fScaleFactor, 2 * fScaleFactor);
+
+ tools::Polygon aPoly(3);
+ aPoly.SetPoint(Point(aCenter.X() - aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 0);
+ aPoly.SetPoint(Point(aCenter.X() + aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 1);
+ aPoly.SetPoint(Point(aCenter.X(), aCenter.Y() + aArrowSize.Height()), 2);
+ mpOutDev->DrawPolygon(aPoly);
+
+ if (mbHasHiddenMember)
+ {
+ // tiny little box to display in presence of hidden member(s).
+ Point aBoxPos(aPos.X() + aSize.Width() - 5 * fScaleFactor, aPos.Y() + aSize.Height() - 5 * fScaleFactor);
+ Size aBoxSize(3 * fScaleFactor, 3 * fScaleFactor);
+ mpOutDev->DrawRect(tools::Rectangle(aBoxPos, aBoxSize));
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/cctrl/editfield.cxx b/sc/source/ui/cctrl/editfield.cxx
new file mode 100644
index 000000000..28772daaf
--- /dev/null
+++ b/sc/source/ui/cctrl/editfield.cxx
@@ -0,0 +1,63 @@
+/* -*- 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 .
+ */
+
+#ifdef SC_DLLIMPLEMENTATION
+#undef SC_DLLIMPLEMENTATION
+#endif
+#include <editfield.hxx>
+#include <comphelper/string.hxx>
+#include <rtl/math.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <global.hxx>
+
+namespace {
+
+sal_Unicode lclGetDecSep()
+{
+ return ScGlobal::getLocaleDataPtr()->getNumDecimalSep()[0];
+}
+
+} // namespace
+
+ScDoubleField::ScDoubleField(std::unique_ptr<weld::Entry> xEntry)
+ : m_xEntry(std::move(xEntry))
+{
+}
+
+bool ScDoubleField::GetValue( double& rfValue ) const
+{
+ OUString aStr(comphelper::string::strip(m_xEntry->get_text(), ' '));
+ bool bOk = !aStr.isEmpty();
+ if( bOk )
+ {
+ rtl_math_ConversionStatus eStatus;
+ sal_Int32 nEnd;
+ rfValue = ScGlobal::getLocaleDataPtr()->stringToDouble( aStr, true, &eStatus, &nEnd );
+ bOk = (eStatus == rtl_math_ConversionStatus_Ok) && (nEnd == aStr.getLength() );
+ }
+ return bOk;
+}
+
+void ScDoubleField::SetValue( double fValue, sal_Int32 nDecPlaces )
+{
+ m_xEntry->set_text( ::rtl::math::doubleToUString( fValue, rtl_math_StringFormat_G,
+ nDecPlaces, lclGetDecSep(), true/*bEraseTrailingDecZeros*/ ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/cctrl/tbzoomsliderctrl.cxx b/sc/source/ui/cctrl/tbzoomsliderctrl.cxx
new file mode 100644
index 000000000..4c9cc5832
--- /dev/null
+++ b/sc/source/ui/cctrl/tbzoomsliderctrl.cxx
@@ -0,0 +1,470 @@
+/* -*- 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 <tbzoomsliderctrl.hxx>
+#include <vcl/InterimItemWindow.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/settings.hxx>
+#include <svx/zoomslideritem.hxx>
+#include <iterator>
+#include <set>
+#include <bitmaps.hlst>
+
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/XDispatchProvider.hpp>
+
+// class ScZoomSliderControl ---------------------------------------
+
+SFX_IMPL_TOOLBOX_CONTROL( ScZoomSliderControl, SvxZoomSliderItem );
+
+ScZoomSliderControl::ScZoomSliderControl(
+ sal_uInt16 nSlotId,
+ sal_uInt16 nId,
+ ToolBox& rTbx )
+ :SfxToolBoxControl( nSlotId, nId, rTbx )
+{
+ rTbx.Invalidate();
+}
+
+ScZoomSliderControl::~ScZoomSliderControl()
+{
+
+}
+
+void ScZoomSliderControl::StateChanged( sal_uInt16 /*nSID*/, SfxItemState eState,
+ const SfxPoolItem* pState )
+{
+ sal_uInt16 nId = GetId();
+ ToolBox& rTbx = GetToolBox();
+ ScZoomSliderWnd* pBox = static_cast<ScZoomSliderWnd*>(rTbx.GetItemWindow( nId ));
+ OSL_ENSURE( pBox ,"Control not found!" );
+
+ if ( SfxItemState::DEFAULT != eState || pState->IsVoidItem() )
+ {
+ SvxZoomSliderItem aZoomSliderItem( 100 );
+ pBox->Disable();
+ pBox->UpdateFromItem( &aZoomSliderItem );
+ }
+ else
+ {
+ pBox->Enable();
+ OSL_ENSURE( dynamic_cast<const SvxZoomSliderItem*>( pState) != nullptr, "invalid item type" );
+ const SvxZoomSliderItem* pZoomSliderItem = dynamic_cast< const SvxZoomSliderItem* >( pState );
+
+ OSL_ENSURE( pZoomSliderItem, "Sc::ScZoomSliderControl::StateChanged(), wrong item type!" );
+ if( pZoomSliderItem )
+ pBox->UpdateFromItem( pZoomSliderItem );
+ }
+}
+
+VclPtr<InterimItemWindow> ScZoomSliderControl::CreateItemWindow( vcl::Window *pParent )
+{
+ // #i98000# Don't try to get a value via SfxViewFrame::Current here.
+ // The view's value is always notified via StateChanged later.
+ VclPtrInstance<ScZoomSliderWnd> xSlider( pParent,
+ css::uno::Reference< css::frame::XDispatchProvider >( m_xFrame->getController(),
+ css::uno::UNO_QUERY ), 100 );
+ return xSlider;
+}
+
+struct ScZoomSlider::ScZoomSliderWnd_Impl
+{
+ sal_uInt16 mnCurrentZoom;
+ sal_uInt16 mnMinZoom;
+ sal_uInt16 mnMaxZoom;
+ std::vector< long > maSnappingPointOffsets;
+ std::vector< sal_uInt16 > maSnappingPointZooms;
+ Image maSliderButton;
+ Image maIncreaseButton;
+ Image maDecreaseButton;
+ bool mbOmitPaint;
+ VclPtr<vcl::Window> mxParentWindow;
+
+ explicit ScZoomSliderWnd_Impl( sal_uInt16 nCurrentZoom, vcl::Window* parentWindow ) :
+ mnCurrentZoom( nCurrentZoom ),
+ mnMinZoom( 10 ),
+ mnMaxZoom( 400 ),
+ maSnappingPointOffsets(),
+ maSnappingPointZooms(),
+ maSliderButton(),
+ maIncreaseButton(),
+ maDecreaseButton(),
+ mbOmitPaint( false ),
+ mxParentWindow(parentWindow)
+ {
+ }
+};
+
+static constexpr sal_uInt16 gnSliderCenter(100);
+
+const long nButtonWidth = 10;
+const long nButtonHeight = 10;
+const long nIncDecWidth = 11;
+const long nIncDecHeight = 11;
+const long nSliderHeight = 2;
+const long nSliderWidth = 4;
+const long nSnappingHeight = 4;
+const long nSliderXOffset = 20;
+const long nSnappingEpsilon = 5; // snapping epsilon in pixels
+const long nSnappingPointsMinDist = nSnappingEpsilon; // minimum distance of two adjacent snapping points
+
+sal_uInt16 ScZoomSlider::Offset2Zoom( long nOffset ) const
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+ const long nControlWidth = aSliderWindowSize.Width();
+ sal_uInt16 nRet = 0;
+
+ if( nOffset < nSliderXOffset )
+ return mpImpl->mnMinZoom;
+ if( nOffset > nControlWidth - nSliderXOffset )
+ return mpImpl->mnMaxZoom;
+
+ // check for snapping points:
+ auto aSnappingPointIter = std::find_if(mpImpl->maSnappingPointOffsets.begin(), mpImpl->maSnappingPointOffsets.end(),
+ [nOffset](const long nCurrent) { return std::abs(nCurrent - nOffset) < nSnappingEpsilon; });
+ if (aSnappingPointIter != mpImpl->maSnappingPointOffsets.end())
+ {
+ nOffset = *aSnappingPointIter;
+ auto nCount = static_cast<sal_uInt16>(std::distance(mpImpl->maSnappingPointOffsets.begin(), aSnappingPointIter));
+ nRet = mpImpl->maSnappingPointZooms[ nCount ];
+ }
+
+ if( 0 == nRet )
+ {
+ if( nOffset < nControlWidth / 2 )
+ {
+ // first half of slider
+ const long nFirstHalfRange = gnSliderCenter - mpImpl->mnMinZoom;
+ const long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
+ const long nZoomPerSliderPixel = (1000 * nFirstHalfRange) / nHalfSliderWidth;
+ const long nOffsetToSliderLeft = nOffset - nSliderXOffset;
+ nRet = mpImpl->mnMinZoom + sal_uInt16( nOffsetToSliderLeft * nZoomPerSliderPixel / 1000 );
+ }
+ else
+ {
+ // second half of slider
+ const long nSecondHalfRange = mpImpl->mnMaxZoom - gnSliderCenter;
+ const long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
+ const long nZoomPerSliderPixel = 1000 * nSecondHalfRange / nHalfSliderWidth;
+ const long nOffsetToSliderCenter = nOffset - nControlWidth/2;
+ nRet = gnSliderCenter + sal_uInt16( nOffsetToSliderCenter * nZoomPerSliderPixel / 1000 );
+ }
+ }
+
+ if( nRet < mpImpl->mnMinZoom )
+ return mpImpl->mnMinZoom;
+
+ else if( nRet > mpImpl->mnMaxZoom )
+ return mpImpl->mnMaxZoom;
+
+ return nRet;
+}
+
+long ScZoomSlider::Zoom2Offset( sal_uInt16 nCurrentZoom ) const
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+ const long nControlWidth = aSliderWindowSize.Width();
+ long nRect = nSliderXOffset;
+
+ const long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
+ if( nCurrentZoom <= gnSliderCenter )
+ {
+ nCurrentZoom = nCurrentZoom - mpImpl->mnMinZoom;
+ const long nFirstHalfRange = gnSliderCenter - mpImpl->mnMinZoom;
+ const long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nFirstHalfRange;
+ const long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
+ nRect += nOffset;
+ }
+ else
+ {
+ nCurrentZoom = nCurrentZoom - gnSliderCenter;
+ const long nSecondHalfRange = mpImpl->mnMaxZoom - gnSliderCenter;
+ const long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nSecondHalfRange;
+ const long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
+ nRect += nHalfSliderWidth + nOffset;
+ }
+ return nRect;
+}
+
+ScZoomSliderWnd::ScZoomSliderWnd( vcl::Window* pParent,
+ const css::uno::Reference< css::frame::XDispatchProvider >& rDispatchProvider,
+ sal_uInt16 nCurrentZoom ):
+ InterimItemWindow(pParent, "modules/scalc/ui/zoombox.ui", "ZoomBox"),
+ mxWidget(new ScZoomSlider(rDispatchProvider, nCurrentZoom, pParent)),
+ mxWeld(new weld::CustomWeld(*m_xBuilder, "zoom", *mxWidget))
+{
+ Size aLogicalSize( 115, 40 );
+ Size aSliderSize = LogicToPixel(aLogicalSize, MapMode(MapUnit::Map10thMM));
+ Size aPreferredSize(aSliderSize.Width() * nSliderWidth-1, aSliderSize.Height() + nSliderHeight);
+ mxWidget->GetDrawingArea()->set_size_request(aPreferredSize.Width(), aPreferredSize.Height());
+ mxWidget->SetOutputSizePixel(aPreferredSize);
+ SetSizePixel(aPreferredSize);
+}
+
+ScZoomSliderWnd::~ScZoomSliderWnd()
+{
+ disposeOnce();
+}
+
+void ScZoomSliderWnd::dispose()
+{
+ mxWeld.reset();
+ mxWidget.reset();
+ InterimItemWindow::dispose();
+}
+
+ScZoomSlider::ScZoomSlider(const css::uno::Reference< css::frame::XDispatchProvider>& rDispatchProvider,
+ sal_uInt16 nCurrentZoom, vcl::Window* parentWindow)
+ : mpImpl(new ScZoomSliderWnd_Impl(nCurrentZoom, parentWindow))
+ , m_xDispatchProvider(rDispatchProvider)
+{
+ mpImpl->maSliderButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERBUTTON);
+ mpImpl->maIncreaseButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERINCREASE);
+ mpImpl->maDecreaseButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERDECREASE);
+}
+
+bool ScZoomSlider::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+
+ const Point aPoint = rMEvt.GetPosPixel();
+
+ const long nButtonLeftOffset = ( nSliderXOffset - nIncDecWidth )/2;
+ const long nButtonRightOffset = ( nSliderXOffset + nIncDecWidth )/2;
+
+ const long nOldZoom = mpImpl->mnCurrentZoom;
+
+ // click to - button
+ if ( aPoint.X() >= nButtonLeftOffset && aPoint.X() <= nButtonRightOffset )
+ {
+ mpImpl->mnCurrentZoom = mpImpl->mnCurrentZoom - 5;
+ }
+ // click to + button
+ else if ( aPoint.X() >= aSliderWindowSize.Width() - nSliderXOffset + nButtonLeftOffset &&
+ aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset + nButtonRightOffset )
+ {
+ mpImpl->mnCurrentZoom = mpImpl->mnCurrentZoom + 5;
+ }
+ else if( aPoint.X() >= nSliderXOffset && aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset )
+ {
+ mpImpl->mnCurrentZoom = Offset2Zoom( aPoint.X() );
+ }
+
+ if( mpImpl->mnCurrentZoom < mpImpl->mnMinZoom )
+ mpImpl->mnCurrentZoom = mpImpl->mnMinZoom;
+ else if( mpImpl->mnCurrentZoom > mpImpl->mnMaxZoom )
+ mpImpl->mnCurrentZoom = mpImpl->mnMaxZoom;
+
+ if( nOldZoom == mpImpl->mnCurrentZoom )
+ return true;
+
+ // need to invalidate parent since we rely on the toolbox drawing it's fancy gradient background
+ mpImpl->mxParentWindow->Invalidate();
+ mpImpl->mbOmitPaint = true;
+
+ SvxZoomSliderItem aZoomSliderItem( mpImpl->mnCurrentZoom );
+
+ css::uno::Any a;
+ aZoomSliderItem.QueryValue( a );
+
+ css::uno::Sequence< css::beans::PropertyValue > aArgs( 1 );
+ aArgs[0].Name = "ScalingFactor";
+ aArgs[0].Value = a;
+
+ SfxToolBoxControl::Dispatch( m_xDispatchProvider, ".uno:ScalingFactor", aArgs );
+
+ mpImpl->mbOmitPaint = false;
+
+ return true;
+}
+
+bool ScZoomSlider::MouseMove( const MouseEvent& rMEvt )
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+ const long nControlWidth = aSliderWindowSize.Width();
+ const short nButtons = rMEvt.GetButtons();
+
+ // check mouse move with button pressed
+ if ( 1 == nButtons )
+ {
+ const Point aPoint = rMEvt.GetPosPixel();
+
+ if ( aPoint.X() >= nSliderXOffset && aPoint.X() <= nControlWidth - nSliderXOffset )
+ {
+ mpImpl->mnCurrentZoom = Offset2Zoom( aPoint.X() );
+
+ // need to invalidate parent since we rely on the toolbox drawing it's fancy gradient background
+ mpImpl->mxParentWindow->Invalidate();
+
+ mpImpl->mbOmitPaint = true; // optimization: paint before executing command,
+
+ // commit state change
+ SvxZoomSliderItem aZoomSliderItem( mpImpl->mnCurrentZoom );
+
+ css::uno::Any a;
+ aZoomSliderItem.QueryValue( a );
+
+ css::uno::Sequence< css::beans::PropertyValue > aArgs( 1 );
+ aArgs[0].Name = "ScalingFactor";
+ aArgs[0].Value = a;
+
+ SfxToolBoxControl::Dispatch( m_xDispatchProvider, ".uno:ScalingFactor", aArgs );
+
+ mpImpl->mbOmitPaint = false;
+ }
+ }
+
+ return false;
+}
+
+void ScZoomSliderWnd::UpdateFromItem( const SvxZoomSliderItem* pZoomSliderItem )
+{
+ mxWidget->UpdateFromItem(pZoomSliderItem);
+}
+
+void ScZoomSlider::UpdateFromItem(const SvxZoomSliderItem* pZoomSliderItem)
+{
+ if( pZoomSliderItem )
+ {
+ mpImpl->mnCurrentZoom = pZoomSliderItem->GetValue();
+ mpImpl->mnMinZoom = pZoomSliderItem->GetMinZoom();
+ mpImpl->mnMaxZoom = pZoomSliderItem->GetMaxZoom();
+
+ OSL_ENSURE( mpImpl->mnMinZoom <= mpImpl->mnCurrentZoom &&
+ mpImpl->mnMinZoom < gnSliderCenter &&
+ mpImpl->mnMaxZoom >= mpImpl->mnCurrentZoom &&
+ mpImpl->mnMaxZoom > gnSliderCenter,
+ "Looks like the zoom slider item is corrupted" );
+ const css::uno::Sequence < sal_Int32 >& rSnappingPoints = pZoomSliderItem->GetSnappingPoints();
+ mpImpl->maSnappingPointOffsets.clear();
+ mpImpl->maSnappingPointZooms.clear();
+
+ // get all snapping points:
+ std::set< sal_uInt16 > aTmpSnappingPoints;
+ std::transform(rSnappingPoints.begin(), rSnappingPoints.end(), std::inserter(aTmpSnappingPoints, aTmpSnappingPoints.end()),
+ [](const sal_Int32 nSnappingPoint) -> sal_uInt16 { return static_cast<sal_uInt16>(nSnappingPoint); });
+
+ // remove snapping points that are too close to each other:
+ long nLastOffset = 0;
+
+ for ( const sal_uInt16 nCurrent : aTmpSnappingPoints )
+ {
+ const long nCurrentOffset = Zoom2Offset( nCurrent );
+
+ if ( nCurrentOffset - nLastOffset >= nSnappingPointsMinDist )
+ {
+ mpImpl->maSnappingPointOffsets.push_back( nCurrentOffset );
+ mpImpl->maSnappingPointZooms.push_back( nCurrent );
+ nLastOffset = nCurrentOffset;
+ }
+ }
+ }
+
+ if ( !mpImpl->mbOmitPaint )
+ // need to invalidate parent since we rely on the toolbox drawing it's fancy gradient background
+ mpImpl->mxParentWindow->Invalidate();
+}
+
+void ScZoomSlider::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ DoPaint(rRenderContext);
+}
+
+void ScZoomSlider::DoPaint(vcl::RenderContext& rRenderContext)
+{
+ if (mpImpl->mbOmitPaint)
+ return;
+
+ Size aSliderWindowSize(GetOutputSizePixel());
+ tools::Rectangle aRect(Point(0, 0), aSliderWindowSize);
+
+ ScopedVclPtrInstance< VirtualDevice > pVDev(rRenderContext, DeviceFormat::DEFAULT, DeviceFormat::BITMASK);
+ pVDev->SetOutputSizePixel(aSliderWindowSize);
+ pVDev->SetFillColor( COL_TRANSPARENT );
+ pVDev->SetLineColor( COL_TRANSPARENT );
+ pVDev->DrawRect( aRect );
+
+ tools::Rectangle aSlider = aRect;
+
+ aSlider.AdjustTop((aSliderWindowSize.Height() - nSliderHeight) / 2 - 1 );
+ aSlider.SetBottom( aSlider.Top() + nSliderHeight );
+ aSlider.AdjustLeft(nSliderXOffset );
+ aSlider.AdjustRight( -nSliderXOffset );
+
+ tools::Rectangle aFirstLine(aSlider);
+ aFirstLine.SetBottom( aFirstLine.Top() );
+
+ tools::Rectangle aSecondLine(aSlider);
+ aSecondLine.SetTop( aSecondLine.Bottom() );
+
+ tools::Rectangle aLeft(aSlider);
+ aLeft.SetRight( aLeft.Left() );
+
+ tools::Rectangle aRight(aSlider);
+ aRight.SetLeft( aRight.Right() );
+
+ // draw slider
+ pVDev->SetLineColor(COL_WHITE);
+ pVDev->DrawRect(aSecondLine);
+ pVDev->DrawRect(aRight);
+
+ pVDev->SetLineColor(COL_GRAY);
+ pVDev->DrawRect(aFirstLine);
+ pVDev->DrawRect(aLeft);
+
+ // draw snapping points:
+ for (const auto& rSnappingPointOffset : mpImpl->maSnappingPointOffsets)
+ {
+ pVDev->SetLineColor(COL_GRAY);
+ tools::Rectangle aSnapping(aRect);
+ aSnapping.SetBottom( aSlider.Top() );
+ aSnapping.SetTop( aSnapping.Bottom() - nSnappingHeight );
+ aSnapping.AdjustLeft(rSnappingPointOffset );
+ aSnapping.SetRight( aSnapping.Left() );
+ pVDev->DrawRect(aSnapping);
+
+ aSnapping.AdjustTop(nSnappingHeight + nSliderHeight );
+ aSnapping.AdjustBottom(nSnappingHeight + nSliderHeight );
+ pVDev->DrawRect(aSnapping);
+ }
+
+ // draw slider button
+ Point aImagePoint = aRect.TopLeft();
+ aImagePoint.AdjustX(Zoom2Offset(mpImpl->mnCurrentZoom) );
+ aImagePoint.AdjustX( -(nButtonWidth / 2) );
+ aImagePoint.AdjustY( (aSliderWindowSize.Height() - nButtonHeight) / 2 );
+ pVDev->DrawImage(aImagePoint, mpImpl->maSliderButton);
+
+ // draw decrease button
+ aImagePoint = aRect.TopLeft();
+ aImagePoint.AdjustX((nSliderXOffset - nIncDecWidth) / 2 );
+ aImagePoint.AdjustY((aSliderWindowSize.Height() - nIncDecHeight) / 2 );
+ pVDev->DrawImage(aImagePoint, mpImpl->maDecreaseButton);
+
+ // draw increase button
+ aImagePoint.setX( aRect.TopLeft().X() + aSliderWindowSize.Width() - nIncDecWidth - (nSliderXOffset - nIncDecWidth) / 2 );
+ pVDev->DrawImage(aImagePoint, mpImpl->maIncreaseButton);
+
+ rRenderContext.DrawOutDev(Point(0, 0), aSliderWindowSize, Point(0, 0), aSliderWindowSize, *pVDev);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */