summaryrefslogtreecommitdiffstats
path: root/sc/source/ui/cctrl
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/ui/cctrl
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--sc/source/ui/cctrl/cbnumberformat.cxx88
-rw-r--r--sc/source/ui/cctrl/cbuttonw.cxx139
-rw-r--r--sc/source/ui/cctrl/checklistmenu.cxx1737
-rw-r--r--sc/source/ui/cctrl/dpcontrol.cxx220
-rw-r--r--sc/source/ui/cctrl/editfield.cxx63
-rw-r--r--sc/source/ui/cctrl/tbzoomsliderctrl.cxx457
6 files changed, 2704 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..29ab64e84
--- /dev/null
+++ b/sc/source/ui/cctrl/cbnumberformat.cxx
@@ -0,0 +1,88 @@
+/* -*- 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", true,
+ reinterpret_cast<sal_uInt64>(SfxViewShell::Current()))
+ , 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)
+ return;
+
+ 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..b7f99f731
--- /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(), static_cast<tools::Long>(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..82e7488c9
--- /dev/null
+++ b/sc/source/ui/cctrl/checklistmenu.cxx
@@ -0,0 +1,1737 @@
+/* -*- 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 <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+
+#include <vcl/commandevent.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.hxx>
+#include <comphelper/lok.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <tools/json_writer.hxx>
+#include <svl/numformat.hxx>
+
+#include <document.hxx>
+#include <viewdata.hxx>
+
+using namespace com::sun::star;
+using ::com::sun::star::uno::Reference;
+
+ScCheckListMenuControl::MenuItemData::MenuItemData()
+ : mbEnabled(true)
+{
+}
+
+ScCheckListMenuControl::SubMenuItemData::SubMenuItemData(ScCheckListMenuControl* pParent)
+ : maTimer("sc SubMenuItemData maTimer")
+ , mpSubMenu(nullptr)
+ , mnMenuPos(MENU_NOT_SELECTED)
+ , mpParent(pParent)
+{
+ maTimer.SetInvokeHandler(LINK(this, ScCheckListMenuControl::SubMenuItemData, TimeoutHdl));
+ maTimer.SetTimeout(Application::GetSettings().GetMouseSettings().GetMenuDelay());
+}
+
+void ScCheckListMenuControl::SubMenuItemData::reset()
+{
+ mpSubMenu = nullptr;
+ mnMenuPos = MENU_NOT_SELECTED;
+ maTimer.Stop();
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl::SubMenuItemData, TimeoutHdl, Timer *, void)
+{
+ mpParent->handleMenuTimeout(this);
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, RowActivatedHdl, weld::TreeView&, bool)
+{
+ executeMenuItem(mxMenu->get_selected_index());
+ return true;
+}
+
+IMPL_LINK(ScCheckListMenuControl, MenuKeyInputHdl, const KeyEvent&, rKEvt, bool)
+{
+ const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
+
+ switch (rKeyCode.GetCode())
+ {
+ case KEY_RIGHT:
+ {
+ if (mnSelectedMenu >= maMenuItems.size() || mnSelectedMenu == MENU_NOT_SELECTED)
+ break;
+
+ const MenuItemData& rMenu = maMenuItems[mnSelectedMenu];
+ if (!rMenu.mxSubMenuWin)
+ break;
+
+ executeMenuItem(mnSelectedMenu);
+ }
+ }
+
+ return false;
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, SelectHdl, weld::TreeView&, void)
+{
+ sal_uInt32 nSelectedMenu = MENU_NOT_SELECTED;
+ if (!mxMenu->get_selected(mxScratchIter.get()))
+ {
+ // reselect current item if its submenu is up and the launching item
+ // became unselected by mouse moving out of the top level menu
+ if (mnSelectedMenu < maMenuItems.size() &&
+ maMenuItems[mnSelectedMenu].mxSubMenuWin &&
+ maMenuItems[mnSelectedMenu].mxSubMenuWin->IsVisible())
+ {
+ mxMenu->select(mnSelectedMenu);
+ return;
+ }
+ }
+ else
+ nSelectedMenu = mxMenu->get_iter_index_in_parent(*mxScratchIter);
+
+ setSelectedMenuItem(nSelectedMenu);
+}
+
+void ScCheckListMenuControl::addMenuItem(const OUString& rText, Action* pAction)
+{
+ MenuItemData aItem;
+ aItem.mbEnabled = true;
+ aItem.mxAction.reset(pAction);
+ maMenuItems.emplace_back(std::move(aItem));
+
+ mxMenu->show();
+ mxMenu->append_text(rText);
+ mxMenu->set_image(mxMenu->n_children() - 1, css::uno::Reference<css::graphic::XGraphic>(), 1);
+}
+
+void ScCheckListMenuControl::addSeparator()
+{
+ MenuItemData aItem;
+ maMenuItems.emplace_back(std::move(aItem));
+
+ mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
+}
+
+IMPL_LINK(ScCheckListMenuControl, TreeSizeAllocHdl, const Size&, rSize, void)
+{
+ if (maAllocatedSize == rSize)
+ return;
+ maAllocatedSize = rSize;
+ SetDropdownPos();
+ if (!mnAsyncSetDropdownPosId && Application::GetToolkitName().startsWith("gtk"))
+ {
+ // for gtk retry again later in case it didn't work (wayland)
+ mnAsyncSetDropdownPosId = Application::PostUserEvent(LINK(this, ScCheckListMenuControl, SetDropdownPosHdl));
+ }
+}
+
+void ScCheckListMenuControl::SetDropdownPos()
+{
+ std::vector<int> aWidths
+ {
+ o3tl::narrowing<int>(maAllocatedSize.Width() - (mxMenu->get_text_height() * 3) / 4 - 6)
+ };
+ mxMenu->set_column_fixed_widths(aWidths);
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, SetDropdownPosHdl, void*, void)
+{
+ mnAsyncSetDropdownPosId = nullptr;
+ SetDropdownPos();
+ mxMenu->queue_resize();
+}
+
+void ScCheckListMenuControl::CreateDropDown()
+{
+ int nWidth = (mxMenu->get_text_height() * 3) / 4;
+ mxDropDown->SetOutputSizePixel(Size(nWidth, nWidth));
+ DecorationView aDecoView(mxDropDown.get());
+ aDecoView.DrawSymbol(tools::Rectangle(Point(0, 0), Size(nWidth, nWidth)),
+ SymbolType::SPIN_RIGHT, mxDropDown->GetTextColor(),
+ DrawSymbolFlags::NONE);
+}
+
+ScListSubMenuControl* ScCheckListMenuControl::addSubMenuItem(const OUString& rText, bool bEnabled, bool bColorMenu)
+{
+ MenuItemData aItem;
+ aItem.mbEnabled = bEnabled;
+
+ aItem.mxSubMenuWin.reset(new ScListSubMenuControl(mxMenu.get(), *this, bColorMenu));
+ maMenuItems.emplace_back(std::move(aItem));
+
+ mxMenu->show();
+ mxMenu->append_text(rText);
+ mxMenu->set_image(mxMenu->n_children() - 1, *mxDropDown, 1);
+ return maMenuItems.back().mxSubMenuWin.get();
+}
+
+void ScCheckListMenuControl::executeMenuItem(size_t nPos)
+{
+ if (nPos >= maMenuItems.size())
+ return;
+
+ const MenuItemData& rMenu = maMenuItems[nPos];
+ if (rMenu.mxSubMenuWin)
+ {
+ if (rMenu.mbEnabled)
+ {
+ maOpenTimer.mnMenuPos = nPos;
+ maOpenTimer.mpSubMenu = rMenu.mxSubMenuWin.get();
+ launchSubMenu();
+ }
+ return;
+ }
+
+ if (!maMenuItems[nPos].mxAction)
+ // no action is defined.
+ return;
+
+ const bool bClosePopup = maMenuItems[nPos].mxAction->execute();
+ if (bClosePopup)
+ terminateAllPopupMenus();
+}
+
+void ScCheckListMenuControl::setSelectedMenuItem(size_t nPos)
+{
+ if (mnSelectedMenu == nPos)
+ // nothing to do.
+ return;
+
+ selectMenuItem(nPos, /*bSubMenuTimer*/true);
+}
+
+void ScCheckListMenuControl::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();
+ }
+ else if (pTimer == &maCloseTimer)
+ {
+ // end submenu.
+ if (maCloseTimer.mpSubMenu)
+ {
+ maCloseTimer.mpSubMenu->EndPopupMode();
+ maCloseTimer.mpSubMenu = nullptr;
+
+ // EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
+ if (!mnAsyncPostPopdownId)
+ mnAsyncPostPopdownId = Application::PostUserEvent(LINK(this, ScCheckListMenuControl, PostPopdownHdl));
+ }
+ }
+}
+
+void ScCheckListMenuControl::queueLaunchSubMenu(size_t nPos, ScListSubMenuControl* pMenu)
+{
+ if (!pMenu)
+ return;
+
+ // Set the submenu on launch queue.
+ if (maOpenTimer.mpSubMenu)
+ {
+ if (maOpenTimer.mpSubMenu != pMenu)
+ {
+ // new submenu is being requested.
+ queueCloseSubMenu();
+ }
+ else
+ {
+ if (pMenu == maCloseTimer.mpSubMenu)
+ maCloseTimer.reset();
+ }
+ }
+
+ maOpenTimer.mpSubMenu = pMenu;
+ maOpenTimer.mnMenuPos = nPos;
+ if (comphelper::LibreOfficeKit::isActive())
+ maOpenTimer.maTimer.Invoke();
+ else
+ maOpenTimer.maTimer.Start();
+}
+
+void ScCheckListMenuControl::queueCloseSubMenu()
+{
+ if (!maOpenTimer.mpSubMenu)
+ // There is no submenu to close.
+ return;
+
+ // Stop any submenu on queue for opening.
+ maOpenTimer.maTimer.Stop();
+
+ // Flush any pending close so it doesn't get skipped
+ if (maCloseTimer.mpSubMenu)
+ {
+ maCloseTimer.mpSubMenu->EndPopupMode();
+ }
+
+ maCloseTimer.mpSubMenu = maOpenTimer.mpSubMenu;
+ maCloseTimer.mnMenuPos = maOpenTimer.mnMenuPos;
+ maOpenTimer.mpSubMenu = nullptr;
+ maOpenTimer.mnMenuPos = MENU_NOT_SELECTED;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ maCloseTimer.maTimer.Invoke();
+ else
+ maCloseTimer.maTimer.Start();
+}
+
+tools::Rectangle ScCheckListMenuControl::GetSubMenuParentRect()
+{
+ if (!mxMenu->get_selected(mxScratchIter.get()))
+ return tools::Rectangle();
+ return mxMenu->get_row_area(*mxScratchIter);
+}
+
+void ScCheckListMenuControl::launchSubMenu()
+{
+ ScListSubMenuControl* pSubMenu = maOpenTimer.mpSubMenu;
+ if (!pSubMenu)
+ return;
+
+ if (!mxMenu->get_selected(mxScratchIter.get()))
+ return;
+
+ tools::Rectangle aRect = GetSubMenuParentRect();
+ pSubMenu->StartPopupMode(mxMenu.get(), aRect);
+
+ mxMenu->select(*mxScratchIter);
+ pSubMenu->GrabFocus();
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, PostPopdownHdl, void*, void)
+{
+ mnAsyncPostPopdownId = nullptr;
+ mxMenu->grab_focus();
+}
+
+IMPL_LINK(ScCheckListMenuControl, MouseEnterHdl, const MouseEvent&, rMEvt, bool)
+{
+ if (!rMEvt.IsEnterWindow())
+ return false;
+ selectMenuItem(MENU_NOT_SELECTED, true);
+ return false;
+}
+
+void ScCheckListMenuControl::endSubMenu(ScListSubMenuControl& rSubMenu)
+{
+ rSubMenu.EndPopupMode();
+ maOpenTimer.reset();
+
+ // EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
+ if (!mnAsyncPostPopdownId)
+ mnAsyncPostPopdownId = Application::PostUserEvent(LINK(this, ScCheckListMenuControl, PostPopdownHdl));
+
+ size_t nMenuPos = getSubMenuPos(&rSubMenu);
+ if (nMenuPos != MENU_NOT_SELECTED)
+ {
+ mnSelectedMenu = nMenuPos;
+ mxMenu->select(mnSelectedMenu);
+ }
+}
+
+void ScCheckListMenuControl::selectMenuItem(size_t nPos, bool bSubMenuTimer)
+{
+ mxMenu->select(nPos == MENU_NOT_SELECTED ? -1 : nPos);
+ mnSelectedMenu = nPos;
+
+ if (nPos >= maMenuItems.size() || nPos == MENU_NOT_SELECTED)
+ {
+ queueCloseSubMenu();
+ return;
+ }
+
+ if (!maMenuItems[nPos].mbEnabled)
+ {
+ queueCloseSubMenu();
+ return;
+ }
+
+ if (bSubMenuTimer)
+ {
+ if (maMenuItems[nPos].mxSubMenuWin && mxMenu->changed_by_hover())
+ {
+ ScListSubMenuControl* pSubMenu = maMenuItems[nPos].mxSubMenuWin.get();
+ queueLaunchSubMenu(nPos, pSubMenu);
+ }
+ else
+ queueCloseSubMenu();
+ }
+}
+
+void ScCheckListMenuControl::clearSelectedMenuItem()
+{
+ selectMenuItem(MENU_NOT_SELECTED, false);
+}
+
+size_t ScCheckListMenuControl::getSubMenuPos(const ScListSubMenuControl* pSubMenu)
+{
+ size_t n = maMenuItems.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (maMenuItems[i].mxSubMenuWin.get() == pSubMenu)
+ return i;
+ }
+ return MENU_NOT_SELECTED;
+}
+
+void ScCheckListMenuControl::setSubMenuFocused(const ScListSubMenuControl* pSubMenu)
+{
+ maCloseTimer.reset();
+ size_t nMenuPos = getSubMenuPos(pSubMenu);
+ if (mnSelectedMenu != nMenuPos)
+ {
+ mnSelectedMenu = nMenuPos;
+ mxMenu->select(mnSelectedMenu);
+ }
+}
+
+void ScCheckListMenuControl::EndPopupMode()
+{
+ if (!mbIsPoppedUp)
+ return;
+ mxPopover->connect_closed(Link<weld::Popover&, void>());
+ mxPopover->popdown();
+ PopupModeEndHdl(*mxPopover);
+ assert(mbIsPoppedUp == false);
+}
+
+void ScCheckListMenuControl::StartPopupMode(weld::Widget* pParent, const tools::Rectangle& rRect)
+{
+ mxPopover->connect_closed(LINK(this, ScCheckListMenuControl, PopupModeEndHdl));
+ mbIsPoppedUp = true;
+ mxPopover->popup_at_rect(pParent, rRect);
+ GrabFocus();
+}
+
+void ScCheckListMenuControl::terminateAllPopupMenus()
+{
+ EndPopupMode();
+}
+
+ScCheckListMenuControl::Config::Config() :
+ mbAllowEmptySet(true), mbRTL(false)
+{
+}
+
+ScCheckListMember::ScCheckListMember()
+ : mnValue(0.0)
+ , mbVisible(true)
+ , mbDate(false)
+ , mbLeaf(false)
+ , mbValue(false)
+ , meDatePartType(YEAR)
+{
+}
+
+// the value of border-width of FilterDropDown
+constexpr int nBorderWidth = 4;
+// number of rows visible in checklist
+constexpr int nCheckListVisibleRows = 9;
+// number of rows visible in colorlist
+constexpr int nColorListVisibleRows = 9;
+
+ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData& rViewData,
+ bool bHasDates, int nWidth)
+ : mxBuilder(Application::CreateBuilder(pParent, "modules/scalc/ui/filterdropdown.ui"))
+ , mxPopover(mxBuilder->weld_popover("FilterDropDown"))
+ , mxContainer(mxBuilder->weld_container("container"))
+ , mxMenu(mxBuilder->weld_tree_view("menu"))
+ , mxScratchIter(mxMenu->make_iterator())
+ , mxNonMenu(mxBuilder->weld_widget("nonmenu"))
+ , mxEdSearch(mxBuilder->weld_entry("search_edit"))
+ , mxBox(mxBuilder->weld_widget("box"))
+ , mxListChecks(mxBuilder->weld_tree_view("check_list_box"))
+ , mxTreeChecks(mxBuilder->weld_tree_view("check_tree_box"))
+ , mxChkToggleAll(mxBuilder->weld_check_button("toggle_all"))
+ , mxBtnSelectSingle(mxBuilder->weld_button("select_current"))
+ , mxBtnUnselectSingle(mxBuilder->weld_button("unselect_current"))
+ , mxButtonBox(mxBuilder->weld_box("buttonbox"))
+ , mxBtnOk(mxBuilder->weld_button("ok"))
+ , mxBtnCancel(mxBuilder->weld_button("cancel"))
+ , mxContextMenu(mxBuilder->weld_menu("contextmenu"))
+ , mxDropDown(mxMenu->create_virtual_device())
+ , mnCheckWidthReq(-1)
+ , mnWndWidth(0)
+ , mnCheckListVisibleRows(nCheckListVisibleRows)
+ , mePrevToggleAllState(TRISTATE_INDET)
+ , mnSelectedMenu(MENU_NOT_SELECTED)
+ , mrViewData(rViewData)
+ , mnAsyncPostPopdownId(nullptr)
+ , mnAsyncSetDropdownPosId(nullptr)
+ , mbHasDates(bHasDates)
+ , mbIsPoppedUp(false)
+ , maOpenTimer(this)
+ , maCloseTimer(this)
+{
+ mxTreeChecks->set_clicks_to_toggle(1);
+ mxListChecks->set_clicks_to_toggle(1);
+
+ mxNonMenu->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxEdSearch->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxListChecks->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxTreeChecks->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxListChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl));
+ mxTreeChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl));
+ mxChkToggleAll->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxBtnSelectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxBtnUnselectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxBtnOk->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+ mxBtnCancel->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
+
+ /*
+ tdf#136559 If we have no dates we don't need a tree
+ structure, just a list. GtkListStore can be then
+ used which is much faster than a GtkTreeStore, so
+ with no dates switch to the treeview which uses the
+ faster GtkListStore
+ */
+ if (mbHasDates)
+ mpChecks = mxTreeChecks.get();
+ else
+ {
+ mxTreeChecks->hide();
+ mxListChecks->show();
+ mpChecks = mxListChecks.get();
+ }
+
+ int nChecksHeight = mxTreeChecks->get_height_rows(mnCheckListVisibleRows);
+ if (nWidth != -1)
+ {
+ mnCheckWidthReq = nWidth - nBorderWidth * 2 - 4;
+ mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
+ mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
+ }
+
+ // sort ok/cancel into native order, if this was a dialog they would be auto-sorted, but this
+ // popup isn't a true dialog
+ mxButtonBox->sort_native_button_order();
+
+ mxTreeChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
+ mxListChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
+
+ mxBox->show();
+ mxEdSearch->show();
+ mxButtonBox->show();
+
+ mxMenu->connect_row_activated(LINK(this, ScCheckListMenuControl, RowActivatedHdl));
+ mxMenu->connect_changed(LINK(this, ScCheckListMenuControl, SelectHdl));
+ mxMenu->connect_key_press(LINK(this, ScCheckListMenuControl, MenuKeyInputHdl));
+
+ mxBtnOk->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
+ mxBtnCancel->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
+ mxEdSearch->connect_changed(LINK(this, ScCheckListMenuControl, EdModifyHdl));
+ mxEdSearch->connect_activate(LINK(this, ScCheckListMenuControl, EdActivateHdl));
+ mxTreeChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
+ mxTreeChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
+ mxListChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
+ mxListChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
+ mxChkToggleAll->connect_toggled(LINK(this, ScCheckListMenuControl, TriStateHdl));
+ mxBtnSelectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
+ mxBtnUnselectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
+
+ CreateDropDown();
+ mxMenu->connect_size_allocate(LINK(this, ScCheckListMenuControl, TreeSizeAllocHdl));
+
+ // determine what width the checklist will end up with
+ mnCheckWidthReq = mxContainer->get_preferred_size().Width();
+ // make that size fixed now, we can now use mnCheckWidthReq to speed up
+ // bulk_insert_for_each
+ mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
+ mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
+}
+
+void ScCheckListMenuControl::GrabFocus()
+{
+ if (mxEdSearch->get_visible())
+ mxEdSearch->grab_focus();
+ else
+ {
+ mxMenu->set_cursor(0);
+ mxMenu->grab_focus();
+ }
+}
+
+void ScCheckListMenuControl::DropPendingEvents()
+{
+ if (mnAsyncPostPopdownId)
+ {
+ Application::RemoveUserEvent(mnAsyncPostPopdownId);
+ mnAsyncPostPopdownId = nullptr;
+ }
+ if (mnAsyncSetDropdownPosId)
+ {
+ Application::RemoveUserEvent(mnAsyncSetDropdownPosId);
+ mnAsyncSetDropdownPosId = nullptr;
+ }
+}
+
+ScCheckListMenuControl::~ScCheckListMenuControl()
+{
+ EndPopupMode();
+ for (auto& rMenuItem : maMenuItems)
+ rMenuItem.mxSubMenuWin.reset();
+ DropPendingEvents();
+}
+
+void ScCheckListMenuControl::prepWindow()
+{
+ mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height() + 2);
+ mnSelectedMenu = MENU_NOT_SELECTED;
+ if (mxMenu->n_children())
+ {
+ mxMenu->set_cursor(0);
+ mxMenu->unselect_all();
+ }
+
+ mnWndWidth = mxContainer->get_preferred_size().Width() + nBorderWidth * 2 + 4;
+}
+
+void ScCheckListMenuControl::setAllMemberState(bool bSet)
+{
+ mpChecks->all_foreach([this, bSet](weld::TreeIter& rEntry){
+ mpChecks->set_toggle(rEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
+ return false;
+ });
+
+ if (!maConfig.mbAllowEmptySet)
+ {
+ // We need to have at least one member selected.
+ mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
+ }
+}
+
+void ScCheckListMenuControl::selectCurrentMemberOnly(bool bSet)
+{
+ setAllMemberState(!bSet);
+ std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
+ if (!mpChecks->get_cursor(xEntry.get()))
+ return;
+ mpChecks->set_toggle(*xEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
+}
+
+IMPL_LINK(ScCheckListMenuControl, CommandHdl, const CommandEvent&, rCEvt, bool)
+{
+ if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
+ return false;
+
+ mxContextMenu->set_sensitive("less", mnCheckListVisibleRows > 4);
+ mxContextMenu->set_sensitive("more", mnCheckListVisibleRows < 42);
+
+ OString sCommand = mxContextMenu->popup_at_rect(mpChecks, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
+ if (sCommand.isEmpty())
+ return true;
+
+ if (sCommand == "more")
+ ++mnCheckListVisibleRows;
+ else if (sCommand == "less")
+ --mnCheckListVisibleRows;
+ ResizeToRequest();
+
+ return true;
+}
+
+void ScCheckListMenuControl::ResizeToRequest()
+{
+ int nChecksHeight = mxTreeChecks->get_height_rows(mnCheckListVisibleRows);
+ mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
+ mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
+ mxPopover->resize_to_request();
+}
+
+IMPL_LINK(ScCheckListMenuControl, ButtonHdl, weld::Button&, rBtn, void)
+{
+ if (&rBtn == mxBtnOk.get())
+ close(true);
+ else if (&rBtn == mxBtnCancel.get())
+ close(false);
+ else if (&rBtn == mxBtnSelectSingle.get() || &rBtn == mxBtnUnselectSingle.get())
+ {
+ selectCurrentMemberOnly(&rBtn == mxBtnSelectSingle.get());
+ std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
+ if (!mpChecks->get_cursor(xEntry.get()))
+ xEntry.reset();
+ Check(xEntry.get());
+ }
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, TriStateHdl, weld::Toggleable&, void)
+{
+ switch (mePrevToggleAllState)
+ {
+ case TRISTATE_FALSE:
+ mxChkToggleAll->set_state(TRISTATE_TRUE);
+ setAllMemberState(true);
+ break;
+ case TRISTATE_TRUE:
+ mxChkToggleAll->set_state(TRISTATE_FALSE);
+ setAllMemberState(false);
+ break;
+ case TRISTATE_INDET:
+ default:
+ mxChkToggleAll->set_state(TRISTATE_TRUE);
+ setAllMemberState(true);
+ break;
+ }
+
+ mePrevToggleAllState = mxChkToggleAll->get_state();
+}
+
+namespace
+{
+ void insertMember(weld::TreeView& rView, const weld::TreeIter& rIter, const ScCheckListMember& rMember, bool bChecked)
+ {
+ OUString aLabel = rMember.maName;
+ if (aLabel.isEmpty())
+ aLabel = ScResId(STR_EMPTYDATA);
+ rView.set_toggle(rIter, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
+ rView.set_text(rIter, aLabel, 0);
+ }
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, EdModifyHdl, weld::Entry&, void)
+{
+ OUString aSearchText = mxEdSearch->get_text();
+ aSearchText = ScGlobal::getCharClass().lowercase( aSearchText );
+ bool bSearchTextEmpty = aSearchText.isEmpty();
+ size_t n = maMembers.size();
+ size_t nSelCount = 0;
+
+ // This branch is the general case, the other is an optimized variant of
+ // this one where we can take advantage of knowing we have no hierarchy
+ if (mbHasDates)
+ {
+ mpChecks->freeze();
+
+ bool bSomeDateDeletes = false;
+
+ 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::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
+ else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
+ bPartialMatch = (ScGlobal::getCharClass().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 )
+ {
+ auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, maMembers[i].mbVisible);
+ updateMemberParents(xLeaf.get(), i);
+ if ( maMembers[i].mbVisible )
+ ++nSelCount;
+ continue;
+ }
+
+ if ( bPartialMatch )
+ {
+ auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i]);
+ updateMemberParents(xLeaf.get(), i);
+ ++nSelCount;
+ }
+ else
+ {
+ 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);
+ }
+ }
+
+ mpChecks->thaw();
+ }
+ else
+ {
+ mpChecks->freeze();
+
+ // when there are a lot of rows, it is cheaper to simply clear the tree and either
+ // re-initialise or just insert the filtered lines
+ mpChecks->clear();
+
+ mpChecks->thaw();
+
+ if (bSearchTextEmpty)
+ nSelCount = initMembers();
+ else
+ {
+ std::vector<size_t> aShownIndexes;
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ assert(!maMembers[i].mbDate);
+
+ OUString aLabelDisp = maMembers[i].maName;
+ if ( aLabelDisp.isEmpty() )
+ aLabelDisp = ScResId( STR_EMPTYDATA );
+
+ bool bPartialMatch = ScGlobal::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1;
+
+ if (!bPartialMatch)
+ continue;
+
+ aShownIndexes.push_back(i);
+ }
+
+ std::vector<int> aFixedWidths { mnCheckWidthReq };
+ // tdf#122419 insert in the fastest order, this might be backwards.
+ mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes, &nSelCount](weld::TreeIter& rIter, int i) {
+ size_t nIndex = aShownIndexes[i];
+ insertMember(*mpChecks, rIter, maMembers[nIndex], true);
+ ++nSelCount;
+ }, nullptr, &aFixedWidths);
+ }
+ }
+
+ if ( nSelCount == n )
+ mxChkToggleAll->set_state( TRISTATE_TRUE );
+ else if ( nSelCount == 0 )
+ mxChkToggleAll->set_state( TRISTATE_FALSE );
+ else
+ mxChkToggleAll->set_state( TRISTATE_INDET );
+
+ if ( !maConfig.mbAllowEmptySet )
+ {
+ const bool bEmptySet( nSelCount == 0 );
+ mpChecks->set_sensitive(!bEmptySet);
+ mxChkToggleAll->set_sensitive(!bEmptySet);
+ mxBtnSelectSingle->set_sensitive(!bEmptySet);
+ mxBtnUnselectSingle->set_sensitive(!bEmptySet);
+ mxBtnOk->set_sensitive(!bEmptySet);
+ }
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, EdActivateHdl, weld::Entry&, bool)
+{
+ if (mxBtnOk->get_sensitive())
+ close(true);
+ return true;
+}
+
+IMPL_LINK( ScCheckListMenuControl, CheckHdl, const weld::TreeView::iter_col&, rRowCol, void )
+{
+ Check(&rRowCol.first);
+}
+
+void ScCheckListMenuControl::Check(const weld::TreeIter* pEntry)
+{
+ if (pEntry)
+ CheckEntry(*pEntry, mpChecks->get_toggle(*pEntry) == TRISTATE_TRUE);
+ size_t nNumChecked = GetCheckedEntryCount();
+ if (nNumChecked == maMembers.size())
+ // all members visible
+ mxChkToggleAll->set_state(TRISTATE_TRUE);
+ else if (nNumChecked == 0)
+ // no members visible
+ mxChkToggleAll->set_state(TRISTATE_FALSE);
+ else
+ mxChkToggleAll->set_state(TRISTATE_INDET);
+
+ if (!maConfig.mbAllowEmptySet)
+ // We need to have at least one member selected.
+ mxBtnOk->set_sensitive(nNumChecked != 0);
+
+ mePrevToggleAllState = mxChkToggleAll->get_state();
+}
+
+void ScCheckListMenuControl::updateMemberParents(const weld::TreeIter* 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 )
+ {
+ std::unique_ptr<weld::TreeIter> xYearEntry;
+ std::unique_ptr<weld::TreeIter> xMonthEntry = mpChecks->make_iterator(pLeaf);
+ if (!mpChecks->iter_parent(*xMonthEntry))
+ xMonthEntry.reset();
+ else
+ {
+ xYearEntry = mpChecks->make_iterator(xMonthEntry.get());
+ if (!mpChecks->iter_parent(*xYearEntry))
+ xYearEntry.reset();
+ }
+
+ maMembers[nIdx].mxParent = std::move(xMonthEntry);
+ if ( aItr != maYearMonthMap.end() )
+ {
+ size_t nMonthIdx = aItr->second;
+ maMembers[nMonthIdx].mxParent = std::move(xYearEntry);
+ }
+ }
+ else
+ {
+ std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
+ if (aItr != maYearMonthMap.end() && !xYearEntry)
+ {
+ size_t nMonthIdx = aItr->second;
+ maMembers[nMonthIdx].mxParent.reset();
+ maMembers[nIdx].mxParent.reset();
+ }
+ else if (xYearEntry && !FindEntry(xYearEntry.get(), aMonthName))
+ maMembers[nIdx].mxParent.reset();
+ }
+}
+
+void ScCheckListMenuControl::setMemberSize(size_t n)
+{
+ maMembers.reserve(n);
+}
+
+void ScCheckListMenuControl::addDateMember(const OUString& rsName, double nVal, bool bVisible)
+{
+ SvNumberFormatter* pFormatter = mrViewData.GetDocument().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& rCalendar = ScGlobal::GetCalendar();
+ uno::Sequence<i18n::CalendarItem2> aMonths = rCalendar.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;
+
+ mpChecks->freeze();
+
+ std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
+ if (!xYearEntry)
+ {
+ xYearEntry = mpChecks->make_iterator();
+ mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
+ mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
+ mpChecks->set_text(*xYearEntry, aYearName, 0);
+ ScCheckListMember aMemYear;
+ aMemYear.maName = aYearName;
+ aMemYear.maRealName = rsName;
+ aMemYear.mbDate = true;
+ aMemYear.mbLeaf = false;
+ aMemYear.mbVisible = bVisible;
+ aMemYear.mxParent.reset();
+ aMemYear.meDatePartType = ScCheckListMember::YEAR;
+ maMembers.emplace_back(std::move(aMemYear));
+ }
+
+ std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), aMonthName);
+ if (!xMonthEntry)
+ {
+ xMonthEntry = mpChecks->make_iterator();
+ mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
+ mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
+ mpChecks->set_text(*xMonthEntry, aMonthName, 0);
+ ScCheckListMember aMemMonth;
+ aMemMonth.maName = aMonthName;
+ aMemMonth.maRealName = rsName;
+ aMemMonth.mbDate = true;
+ aMemMonth.mbLeaf = false;
+ aMemMonth.mbVisible = bVisible;
+ aMemMonth.mxParent = std::move(xYearEntry);
+ aMemMonth.meDatePartType = ScCheckListMember::MONTH;
+ maMembers.emplace_back(std::move(aMemMonth));
+ maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
+ }
+
+ std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), aDayName);
+ if (!xDayEntry)
+ {
+ xDayEntry = mpChecks->make_iterator();
+ mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
+ mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
+ mpChecks->set_text(*xDayEntry, aDayName, 0);
+ 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.mxParent = std::move(xMonthEntry);
+ aMemDay.meDatePartType = ScCheckListMember::DAY;
+ maMembers.emplace_back(std::move(aMemDay));
+ }
+
+ mpChecks->thaw();
+}
+
+void ScCheckListMenuControl::addMember(const OUString& rName, const double nVal, bool bVisible, bool bValue)
+{
+ ScCheckListMember aMember;
+ // tdf#46062 - indicate hidden whitespaces using quotes
+ aMember.maName = o3tl::trim(rName) != rName ? "\"" + rName + "\"" : rName;
+ aMember.maRealName = rName;
+ aMember.mnValue = nVal;
+ aMember.mbDate = false;
+ aMember.mbLeaf = true;
+ aMember.mbValue = bValue;
+ aMember.mbVisible = bVisible;
+ aMember.mxParent.reset();
+ maMembers.emplace_back(std::move(aMember));
+}
+
+std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::FindEntry(const weld::TreeIter* pParent, std::u16string_view sNode)
+{
+ std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(pParent);
+ bool bEntry = pParent ? mpChecks->iter_children(*xEntry) : mpChecks->get_iter_first(*xEntry);
+ while (bEntry)
+ {
+ if (sNode == mpChecks->get_text(*xEntry, 0))
+ return xEntry;
+ bEntry = mpChecks->iter_next_sibling(*xEntry);
+ }
+ return nullptr;
+}
+
+void ScCheckListMenuControl::GetRecursiveChecked(const weld::TreeIter* pEntry, std::unordered_set<OUString>& vOut,
+ OUString& rLabel)
+{
+ if (mpChecks->get_toggle(*pEntry) != TRISTATE_TRUE)
+ return;
+
+ // We have to hash parents and children together.
+ // Per convention for easy access in getResult()
+ // "child;parent;grandparent" while descending.
+ if (rLabel.isEmpty())
+ rLabel = mpChecks->get_text(*pEntry, 0);
+ else
+ rLabel = mpChecks->get_text(*pEntry, 0) + ";" + 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 (!mpChecks->iter_has_child(*pEntry))
+ return;
+
+ std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(pEntry));
+ bool bChild = mpChecks->iter_children(*xChild);
+ while (bChild)
+ {
+ OUString aLabel = rLabel;
+ GetRecursiveChecked(xChild.get(), vOut, aLabel);
+ if (!aLabel.isEmpty() && aLabel != rLabel)
+ vOut.insert(aLabel);
+ bChild = mpChecks->iter_next_sibling(*xChild);
+ }
+ // Let the caller not add the parent alone.
+ rLabel.clear();
+}
+
+std::unordered_set<OUString> ScCheckListMenuControl::GetAllChecked()
+{
+ std::unordered_set<OUString> vResults(0);
+
+ std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
+ bool bEntry = mpChecks->get_iter_first(*xEntry);
+ while (bEntry)
+ {
+ OUString aLabel;
+ GetRecursiveChecked(xEntry.get(), vResults, aLabel);
+ if (!aLabel.isEmpty())
+ vResults.insert(aLabel);
+ bEntry = mpChecks->iter_next_sibling(*xEntry);
+ }
+
+ return vResults;
+}
+
+bool ScCheckListMenuControl::IsChecked(std::u16string_view sName, const weld::TreeIter* pParent)
+{
+ std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
+ return xEntry && mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
+}
+
+void ScCheckListMenuControl::CheckEntry(std::u16string_view sName, const weld::TreeIter* pParent, bool bCheck)
+{
+ std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
+ if (xEntry)
+ CheckEntry(*xEntry, bCheck);
+}
+
+// Recursively check all children of rParent
+void ScCheckListMenuControl::CheckAllChildren(const weld::TreeIter& rParent, bool bCheck)
+{
+ mpChecks->set_toggle(rParent, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
+ std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(&rParent);
+ bool bEntry = mpChecks->iter_children(*xEntry);
+ while (bEntry)
+ {
+ CheckAllChildren(*xEntry, bCheck);
+ bEntry = mpChecks->iter_next_sibling(*xEntry);
+ }
+}
+
+void ScCheckListMenuControl::CheckEntry(const weld::TreeIter& rParent, bool bCheck)
+{
+ // recursively check all items below rParent
+ CheckAllChildren(rParent, bCheck);
+ // checking rParent can affect ancestors, e.g. if ancestor is unchecked and rParent is
+ // now checked then the ancestor needs to be checked also
+ if (!mpChecks->get_iter_depth(rParent))
+ return;
+
+ std::unique_ptr<weld::TreeIter> xAncestor(mpChecks->make_iterator(&rParent));
+ bool bAncestor = mpChecks->iter_parent(*xAncestor);
+ while (bAncestor)
+ {
+ // 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
+ std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(xAncestor.get()));
+ bool bChild = mpChecks->iter_children(*xChild);
+ bool bChildChecked = false;
+
+ while (bChild)
+ {
+ if (mpChecks->get_toggle(*xChild) == TRISTATE_TRUE)
+ {
+ bChildChecked = true;
+ break;
+ }
+ bChild = mpChecks->iter_next_sibling(*xChild);
+ }
+ mpChecks->set_toggle(*xAncestor, bChildChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
+ bAncestor = mpChecks->iter_parent(*xAncestor);
+ }
+}
+
+std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::ShowCheckEntry(const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck)
+{
+ std::unique_ptr<weld::TreeIter> xEntry;
+ if (!rMember.mbDate || rMember.mxParent)
+ xEntry = FindEntry(rMember.mxParent.get(), sName);
+
+ if ( bShow )
+ {
+ if (!xEntry)
+ {
+ if (rMember.mbDate)
+ {
+ if (rMember.maDateParts.empty())
+ return nullptr;
+
+ std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, rMember.maDateParts[0]);
+ if (!xYearEntry)
+ {
+ xYearEntry = mpChecks->make_iterator();
+ mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
+ mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
+ mpChecks->set_text(*xYearEntry, rMember.maDateParts[0], 0);
+ }
+ std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), rMember.maDateParts[1]);
+ if (!xMonthEntry)
+ {
+ xMonthEntry = mpChecks->make_iterator();
+ mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
+ mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
+ mpChecks->set_text(*xMonthEntry, rMember.maDateParts[1], 0);
+ }
+ std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), rMember.maName);
+ if (!xDayEntry)
+ {
+ xDayEntry = mpChecks->make_iterator();
+ mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
+ mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
+ mpChecks->set_text(*xDayEntry, rMember.maName, 0);
+ }
+ return xDayEntry; // Return leaf node
+ }
+
+ xEntry = mpChecks->make_iterator();
+ mpChecks->append(xEntry.get());
+ mpChecks->set_toggle(*xEntry, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
+ mpChecks->set_text(*xEntry, sName, 0);
+ }
+ else
+ CheckEntry(*xEntry, bCheck);
+ }
+ else if (xEntry)
+ {
+ mpChecks->remove(*xEntry);
+ if (rMember.mxParent)
+ {
+ std::unique_ptr<weld::TreeIter> xParent(mpChecks->make_iterator(rMember.mxParent.get()));
+ while (xParent && !mpChecks->iter_has_child(*xParent))
+ {
+ std::unique_ptr<weld::TreeIter> xTmp(mpChecks->make_iterator(xParent.get()));
+ if (!mpChecks->iter_parent(*xParent))
+ xParent.reset();
+ mpChecks->remove(*xTmp);
+ }
+ }
+ }
+ return nullptr;
+}
+
+int ScCheckListMenuControl::GetCheckedEntryCount() const
+{
+ int nRet = 0;
+
+ mpChecks->all_foreach([this, &nRet](weld::TreeIter& rEntry){
+ if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
+ ++nRet;
+ return false;
+ });
+
+ return nRet;
+}
+
+IMPL_LINK(ScCheckListMenuControl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
+{
+ const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
+
+ if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
+ {
+ std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
+ bool bEntry = mpChecks->get_cursor(xEntry.get());
+ if (bEntry)
+ {
+ bool bOldCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
+ CheckEntry(*xEntry, !bOldCheck);
+ bool bNewCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
+ if (bOldCheck != bNewCheck)
+ Check(xEntry.get());
+ }
+ return true;
+ }
+
+ return false;
+}
+
+size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth)
+{
+ size_t n = maMembers.size();
+ size_t nVisMemCount = 0;
+
+ if (nMaxMemberWidth == -1)
+ nMaxMemberWidth = mnCheckWidthReq;
+
+ if (!mpChecks->n_children() && !mbHasDates)
+ {
+ std::vector<int> aFixedWidths { nMaxMemberWidth };
+ // tdf#134038 insert in the fastest order, this might be backwards so only do it for
+ // the !mbHasDates case where no entry depends on another to exist before getting
+ // inserted. We cannot retain pre-existing treeview content, only clear and fill it.
+ mpChecks->bulk_insert_for_each(n, [this, &nVisMemCount](weld::TreeIter& rIter, int i) {
+ assert(!maMembers[i].mbDate);
+ insertMember(*mpChecks, rIter, maMembers[i], maMembers[i].mbVisible);
+ if (maMembers[i].mbVisible)
+ ++nVisMemCount;
+ }, nullptr, &aFixedWidths);
+ }
+ else
+ {
+ mpChecks->freeze();
+
+ std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
+ std::vector<std::unique_ptr<weld::TreeIter>> aExpandRows;
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (maMembers[i].mbDate)
+ {
+ CheckEntry(maMembers[i].maName, maMembers[i].mxParent.get(), maMembers[i].mbVisible);
+ // Expand first node of checked dates
+ if (!maMembers[i].mxParent && IsChecked(maMembers[i].maName, maMembers[i].mxParent.get()))
+ {
+ std::unique_ptr<weld::TreeIter> xDateEntry = FindEntry(nullptr, maMembers[i].maName);
+ if (xDateEntry)
+ aExpandRows.emplace_back(std::move(xDateEntry));
+ }
+ }
+ else
+ {
+ mpChecks->append(xEntry.get());
+ insertMember(*mpChecks, *xEntry, maMembers[i], maMembers[i].mbVisible);
+ }
+
+ if (maMembers[i].mbVisible)
+ ++nVisMemCount;
+ }
+
+ mpChecks->thaw();
+
+ for (const auto& rRow : aExpandRows)
+ mpChecks->expand_row(*rRow);
+ }
+
+ if (nVisMemCount == n)
+ {
+ // all members visible
+ mxChkToggleAll->set_state(TRISTATE_TRUE);
+ mePrevToggleAllState = TRISTATE_TRUE;
+ }
+ else if (nVisMemCount == 0)
+ {
+ // no members visible
+ mxChkToggleAll->set_state(TRISTATE_FALSE);
+ mePrevToggleAllState = TRISTATE_FALSE;
+ }
+ else
+ {
+ mxChkToggleAll->set_state(TRISTATE_INDET);
+ mePrevToggleAllState = TRISTATE_INDET;
+ }
+
+ if (nVisMemCount)
+ mpChecks->set_cursor(0);
+
+ return nVisMemCount;
+}
+
+void ScCheckListMenuControl::setConfig(const Config& rConfig)
+{
+ maConfig = rConfig;
+}
+
+bool ScCheckListMenuControl::isAllSelected() const
+{
+ return mxChkToggleAll->get_state() == TRISTATE_TRUE;
+}
+
+void ScCheckListMenuControl::getResult(ResultType& rResult)
+{
+ ResultType aResult;
+ std::unordered_set<OUString> vCheckeds = 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".
+ if (maMembers[i].mxParent)
+ {
+ std::unique_ptr<weld::TreeIter> xIter(mpChecks->make_iterator(maMembers[i].mxParent.get()));
+ do
+ {
+ aLabel.append(";" + mpChecks->get_text(*xIter));
+ }
+ while (mpChecks->iter_parent(*xIter));
+ }
+
+ bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
+
+ ResultEntry aResultEntry;
+ aResultEntry.bValid = bState;
+ aResultEntry.aName = maMembers[i].maRealName;
+ aResultEntry.nValue = maMembers[i].mnValue;
+ aResultEntry.bDate = maMembers[i].mbDate;
+ aResultEntry.bValue = maMembers[i].mbValue;
+ aResult.insert(aResultEntry);
+ }
+ }
+ rResult.swap(aResult);
+}
+
+void ScCheckListMenuControl::launch(weld::Widget* pWidget, const tools::Rectangle& rRect)
+{
+ prepWindow();
+ if (!maConfig.mbAllowEmptySet)
+ // We need to have at least one member selected.
+ mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
+
+ tools::Rectangle aRect(rRect);
+ if (maConfig.mbRTL)
+ {
+ // In RTL mode, the logical "left" is visual "right".
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ tools::Long nLeft = aRect.Left() - aRect.GetWidth();
+ aRect.SetLeft( nLeft );
+ }
+ else
+ {
+ // in LOK mode, rRect is in document pixel coordinates, so width has to be added
+ // to place the popup next to the (visual) left aligned button.
+ aRect.Move(aRect.GetWidth(), 0);
+ }
+ }
+ else if (mnWndWidth < aRect.GetWidth())
+ {
+ // Target rectangle (i.e. cell width) is wider than the window.
+ // Simulate right-aligned launch by modifying the target rectangle
+ // size.
+ tools::Long nDiff = aRect.GetWidth() - mnWndWidth;
+ aRect.AdjustLeft(nDiff );
+ }
+
+ StartPopupMode(pWidget, aRect);
+}
+
+void ScCheckListMenuControl::close(bool bOK)
+{
+ if (bOK && mxOKAction)
+ mxOKAction->execute();
+ EndPopupMode();
+}
+
+void ScCheckListMenuControl::setExtendedData(std::unique_ptr<ExtendedData> p)
+{
+ mxExtendedData = std::move(p);
+}
+
+ScCheckListMenuControl::ExtendedData* ScCheckListMenuControl::getExtendedData()
+{
+ return mxExtendedData.get();
+}
+
+void ScCheckListMenuControl::setOKAction(Action* p)
+{
+ mxOKAction.reset(p);
+}
+
+void ScCheckListMenuControl::setPopupEndAction(Action* p)
+{
+ mxPopupEndAction.reset(p);
+}
+
+IMPL_LINK_NOARG(ScCheckListMenuControl, PopupModeEndHdl, weld::Popover&, void)
+{
+ mbIsPoppedUp = false;
+ clearSelectedMenuItem();
+ if (mxPopupEndAction)
+ mxPopupEndAction->execute();
+
+ DropPendingEvents();
+}
+
+int ScCheckListMenuControl::GetTextWidth(const OUString& rsName) const
+{
+ return mxDropDown->GetTextWidth(rsName);
+}
+
+int ScCheckListMenuControl::IncreaseWindowWidthToFitText(int nMaxTextWidth)
+{
+ int nBorder = nBorderWidth * 2 + 4;
+ int nNewWidth = nMaxTextWidth - nBorder;
+ if (nNewWidth > mnCheckWidthReq)
+ {
+ mnCheckWidthReq = nNewWidth;
+ int nChecksHeight = mpChecks->get_height_rows(nCheckListVisibleRows);
+ mpChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
+ }
+ return mnCheckWidthReq + nBorder;
+}
+
+ScListSubMenuControl::ScListSubMenuControl(weld::Widget* pParent, ScCheckListMenuControl& rParentControl, bool bColorMenu)
+ : mxBuilder(Application::CreateBuilder(pParent, "modules/scalc/ui/filtersubdropdown.ui"))
+ , mxPopover(mxBuilder->weld_popover("FilterSubDropDown"))
+ , mxContainer(mxBuilder->weld_container("container"))
+ , mxMenu(mxBuilder->weld_tree_view("menu"))
+ , mxBackColorMenu(mxBuilder->weld_tree_view("background"))
+ , mxTextColorMenu(mxBuilder->weld_tree_view("textcolor"))
+ , mxScratchIter(mxMenu->make_iterator())
+ , mrParentControl(rParentControl)
+ , mnBackColorMenuPrefHeight(-1)
+ , mnTextColorMenuPrefHeight(-1)
+ , mbColorMenu(bColorMenu)
+{
+ mxMenu->hide();
+ mxBackColorMenu->hide();
+ mxTextColorMenu->hide();
+
+ if (!bColorMenu)
+ {
+ SetupMenu(*mxMenu);
+ mxMenu->show();
+ }
+ else
+ {
+ mxBackColorMenu->set_clicks_to_toggle(1);
+ mxBackColorMenu->enable_toggle_buttons(weld::ColumnToggleType::Radio);
+ mxBackColorMenu->connect_changed(LINK(this, ScListSubMenuControl, ColorSelChangedHdl));
+ mxTextColorMenu->set_clicks_to_toggle(1);
+ mxTextColorMenu->enable_toggle_buttons(weld::ColumnToggleType::Radio);
+ mxTextColorMenu->connect_changed(LINK(this, ScListSubMenuControl, ColorSelChangedHdl));
+ SetupMenu(*mxBackColorMenu);
+ SetupMenu(*mxTextColorMenu);
+ }
+}
+
+void ScListSubMenuControl::SetupMenu(weld::TreeView& rMenu)
+{
+ rMenu.connect_row_activated(LINK(this, ScListSubMenuControl, RowActivatedHdl));
+ rMenu.connect_key_press(LINK(this, ScListSubMenuControl, MenuKeyInputHdl));
+}
+
+void ScListSubMenuControl::StartPopupMode(weld::Widget* pParent, const tools::Rectangle& rRect)
+{
+ if (mxPopupStartAction)
+ mxPopupStartAction->execute();
+
+ mxPopover->popup_at_rect(pParent, rRect, weld::Placement::End);
+
+ weld::TreeView& rFirstMenu = mbColorMenu ? *mxBackColorMenu : *mxMenu;
+ rFirstMenu.set_cursor(0);
+ rFirstMenu.select(0);
+
+ mrParentControl.setSubMenuFocused(this);
+}
+
+void ScListSubMenuControl::EndPopupMode()
+{
+ mxPopover->popdown();
+}
+
+void ScListSubMenuControl::GrabFocus()
+{
+ weld::TreeView& rFirstMenu = mbColorMenu ? *mxBackColorMenu : *mxMenu;
+ rFirstMenu.grab_focus();
+}
+
+bool ScListSubMenuControl::IsVisible() const
+{
+ return mxPopover->get_visible();
+}
+
+void ScListSubMenuControl::resizeToFitMenuItems()
+{
+ if (!mbColorMenu)
+ mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height());
+ else
+ {
+ int nBackColorMenuPrefHeight = mnBackColorMenuPrefHeight;
+ if (nBackColorMenuPrefHeight == -1)
+ nBackColorMenuPrefHeight = mxBackColorMenu->get_preferred_size().Height();
+ mxBackColorMenu->set_size_request(-1, nBackColorMenuPrefHeight);
+ int nTextColorMenuPrefHeight = mnTextColorMenuPrefHeight;
+ if (nTextColorMenuPrefHeight == -1)
+ nTextColorMenuPrefHeight = mxTextColorMenu->get_preferred_size().Height();
+ mxTextColorMenu->set_size_request(-1, nTextColorMenuPrefHeight);
+ }
+}
+
+void ScListSubMenuControl::addItem(ScCheckListMenuControl::Action* pAction)
+{
+ ScCheckListMenuControl::MenuItemData aItem;
+ aItem.mbEnabled = true;
+ aItem.mxAction.reset(pAction);
+ maMenuItems.emplace_back(std::move(aItem));
+}
+
+void ScListSubMenuControl::addMenuItem(const OUString& rText, ScCheckListMenuControl::Action* pAction)
+{
+ addItem(pAction);
+ mxMenu->append(weld::toId(pAction), rText);
+}
+
+void ScListSubMenuControl::addMenuColorItem(const OUString& rText, bool bActive, VirtualDevice& rImage,
+ int nMenu, ScCheckListMenuControl::Action* pAction)
+{
+ addItem(pAction);
+
+ weld::TreeView& rColorMenu = nMenu == 0 ? *mxBackColorMenu : *mxTextColorMenu;
+ rColorMenu.show();
+
+ OUString sId = weld::toId(pAction);
+ rColorMenu.insert(nullptr, -1, &rText, &sId, nullptr, nullptr, false, mxScratchIter.get());
+ rColorMenu.set_toggle(*mxScratchIter, bActive ? TRISTATE_TRUE : TRISTATE_FALSE);
+ rColorMenu.set_image(*mxScratchIter, rImage);
+
+ if (mnTextColorMenuPrefHeight == -1 &&
+ &rColorMenu == mxTextColorMenu.get() &&
+ mxTextColorMenu->n_children() == nColorListVisibleRows)
+ {
+ mnTextColorMenuPrefHeight = mxTextColorMenu->get_preferred_size().Height();
+ }
+
+ if (mnBackColorMenuPrefHeight == -1 &&
+ &rColorMenu == mxBackColorMenu.get() &&
+ mxBackColorMenu->n_children() == nColorListVisibleRows)
+ {
+ mnBackColorMenuPrefHeight = mxBackColorMenu->get_preferred_size().Height();
+ }
+}
+
+void ScListSubMenuControl::addSeparator()
+{
+ ScCheckListMenuControl::MenuItemData aItem;
+ maMenuItems.emplace_back(std::move(aItem));
+
+ mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
+}
+
+void ScListSubMenuControl::clearMenuItems()
+{
+ maMenuItems.clear();
+ mxMenu->clear();
+ mxBackColorMenu->clear();
+ mnBackColorMenuPrefHeight = -1;
+ mxTextColorMenu->clear();
+ mnTextColorMenuPrefHeight = -1;
+}
+
+IMPL_LINK(ScListSubMenuControl, MenuKeyInputHdl, const KeyEvent&, rKEvt, bool)
+{
+ bool bConsumed = false;
+ const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
+
+ switch (rKeyCode.GetCode())
+ {
+ case KEY_ESCAPE:
+ case KEY_LEFT:
+ {
+ mrParentControl.endSubMenu(*this);
+ bConsumed = true;
+ break;
+ }
+ case KEY_SPACE:
+ case KEY_RETURN:
+ {
+ weld::TreeView& rMenu = !mbColorMenu ? *mxMenu :
+ (mxBackColorMenu->has_focus() ? *mxBackColorMenu : *mxTextColorMenu);
+ // don't toggle checkbutton, go straight to activating entry
+ bConsumed = RowActivatedHdl(rMenu);
+ break;
+ }
+ case KEY_DOWN:
+ {
+ if (mxTextColorMenu->get_visible() &&
+ mxBackColorMenu->has_focus() &&
+ mxBackColorMenu->get_selected_index() == mxBackColorMenu->n_children() - 1)
+ {
+ mxBackColorMenu->unselect_all();
+ mxTextColorMenu->select(0);
+ mxTextColorMenu->set_cursor(0);
+ mxTextColorMenu->grab_focus();
+ bConsumed = true;
+ }
+ break;
+ }
+ case KEY_UP:
+ {
+ if (mxBackColorMenu->get_visible() &&
+ mxTextColorMenu->has_focus() &&
+ mxTextColorMenu->get_selected_index() == 0)
+ {
+ mxTextColorMenu->unselect_all();
+ int nIndex = mxBackColorMenu->n_children() - 1;
+ mxBackColorMenu->select(nIndex);
+ mxBackColorMenu->set_cursor(nIndex);
+ mxBackColorMenu->grab_focus();
+ bConsumed = true;
+ }
+ break;
+ }
+ }
+
+ return bConsumed;
+}
+
+IMPL_LINK(ScListSubMenuControl, ColorSelChangedHdl, weld::TreeView&, rMenu, void)
+{
+ if (rMenu.get_selected_index() == -1)
+ return;
+ if (&rMenu != mxTextColorMenu.get())
+ mxTextColorMenu->unselect_all();
+ else
+ mxBackColorMenu->unselect_all();
+ rMenu.grab_focus();
+}
+
+IMPL_LINK(ScListSubMenuControl, RowActivatedHdl, weld::TreeView&, rMenu, bool)
+{
+ executeMenuItem(weld::fromId<ScCheckListMenuControl::Action*>(rMenu.get_selected_id()));
+ return true;
+}
+
+void ScListSubMenuControl::executeMenuItem(ScCheckListMenuControl::Action* pAction)
+{
+ // if no action is defined.
+ if (!pAction)
+ return;
+
+ const bool bClosePopup = pAction->execute();
+ if (bClosePopup)
+ terminateAllPopupMenus();
+}
+
+void ScListSubMenuControl::setPopupStartAction(ScCheckListMenuControl::Action* p)
+{
+ mxPopupStartAction.reset(p);
+}
+
+void ScListSubMenuControl::terminateAllPopupMenus()
+{
+ EndPopupMode();
+ mrParentControl.terminateAllPopupMenus();
+}
+
+/* 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..4c9fbbc6a
--- /dev/null
+++ b/sc/source/ui/cctrl/dpcontrol.cxx
@@ -0,0 +1,220 @@
+/* -*- 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 <comphelper/lok.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()
+{
+ bool bOldMapEnabled = mpOutDev->IsMapModeEnabled();
+
+ if (mpOutDev->GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+ mpOutDev->EnableMapMode(false);
+
+ 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;
+ tools::Long nTHeight = mpOutDev->GetTextHeight();
+ aTextPos.setX(maPos.getX() + 2); // 2 = Margin
+ aTextPos.setY(maPos.getY() + (maSize.Height()-nTHeight)/2);
+
+ mpOutDev->Push(vcl::PushFlags::CLIPREGION);
+ mpOutDev->IntersectClipRegion(aRect);
+ mpOutDev->DrawText(aTextPos, maText);
+ mpOutDev->Pop();
+ }
+
+ if (mbPopupButton)
+ drawPopupButton();
+
+ mpOutDev->EnableMapMode(bOldMapEnabled);
+}
+
+void ScDPFieldButton::getPopupBoundingBox(Point& rPos, Size& rSize) const
+{
+ float fScaleFactor = mpOutDev->GetDPIScaleFactor();
+
+ tools::Long nMaxSize = 18 * fScaleFactor; // Button max size in either dimension
+
+ tools::Long nW = std::min(maSize.getWidth() / 2, nMaxSize);
+ tools::Long nH = std::min(maSize.getHeight(), nMaxSize);
+
+ double fZoom = static_cast<double>(maZoomY) > 1.0 ? static_cast<double>(maZoomY) : 1.0;
+ if (fZoom > 1.0)
+ {
+ nW = fZoom * (nW - 1);
+ nH = fZoom * (nH - 1);
+ }
+
+ // #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
+ = mbHasHiddenMember ? mpStyle->GetHighlightColor()
+ : mbPopupPressed ? mpStyle->GetShadowColor() : mpStyle->GetFaceColor();
+ mpOutDev->SetFillColor(aBackgroundColor);
+ mpOutDev->DrawRect(tools::Rectangle(aPos, aSize));
+
+ // the arrowhead
+ Color aArrowColor = mbHasHiddenMember ? mpStyle->GetHighlightTextColor() : mpStyle->GetButtonTextColor();
+ // FIXME: HACK: The following DrawPolygon draws twice in lok rtl mode for some reason.
+ // => one at the correct location with fill (possibly no outline)
+ // => and the other at an x offset with outline and without fill
+ // eg. Replacing this with a DrawRect() does not have any such problems.
+ comphelper::LibreOfficeKit::isActive() ? mpOutDev->SetLineColor() : 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..fd9d1e6b0
--- /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::getLocaleData().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::getLocaleData().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..7d3443bb0
--- /dev/null
+++ b/sc/source/ui/cctrl/tbzoomsliderctrl.cxx
@@ -0,0 +1,457 @@
+/* -*- 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 <comphelper/propertyvalue.hxx>
+#include <vcl/InterimItemWindow.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/virdev.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,
+ ToolBoxItemId nId,
+ ToolBox& rTbx )
+ :SfxToolBoxControl( nSlotId, nId, rTbx )
+{
+ rTbx.Invalidate();
+}
+
+ScZoomSliderControl::~ScZoomSliderControl()
+{
+
+}
+
+void ScZoomSliderControl::StateChangedAtToolBoxControl( sal_uInt16 /*nSID*/, SfxItemState eState,
+ const SfxPoolItem* pState )
+{
+ ToolBoxItemId 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;
+}
+
+constexpr sal_uInt16 gnSliderCenter(100);
+
+const tools::Long nButtonWidth = 10;
+const tools::Long nButtonHeight = 10;
+const tools::Long nIncDecWidth = 11;
+const tools::Long nIncDecHeight = 11;
+const tools::Long nSliderHeight = 2;
+const tools::Long nSliderWidth = 4;
+const tools::Long nSnappingHeight = 4;
+const tools::Long nSliderXOffset = 20;
+const tools::Long nSnappingEpsilon = 5; // snapping epsilon in pixels
+const tools::Long nSnappingPointsMinDist = nSnappingEpsilon; // minimum distance of two adjacent snapping points
+
+sal_uInt16 ScZoomSlider::Offset2Zoom( tools::Long nOffset ) const
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+ const tools::Long nControlWidth = aSliderWindowSize.Width();
+ sal_uInt16 nRet = 0;
+
+ if( nOffset < nSliderXOffset )
+ return mnMinZoom;
+ if( nOffset > nControlWidth - nSliderXOffset )
+ return mnMaxZoom;
+
+ // check for snapping points:
+ auto aSnappingPointIter = std::find_if(maSnappingPointOffsets.begin(), maSnappingPointOffsets.end(),
+ [nOffset](const tools::Long nCurrent) { return std::abs(nCurrent - nOffset) < nSnappingEpsilon; });
+ if (aSnappingPointIter != maSnappingPointOffsets.end())
+ {
+ nOffset = *aSnappingPointIter;
+ auto nCount = static_cast<sal_uInt16>(std::distance(maSnappingPointOffsets.begin(), aSnappingPointIter));
+ nRet = maSnappingPointZooms[ nCount ];
+ }
+
+ if( 0 == nRet )
+ {
+ if( nOffset < nControlWidth / 2 )
+ {
+ // first half of slider
+ const tools::Long nFirstHalfRange = gnSliderCenter - mnMinZoom;
+ const tools::Long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
+ const tools::Long nZoomPerSliderPixel = (1000 * nFirstHalfRange) / nHalfSliderWidth;
+ const tools::Long nOffsetToSliderLeft = nOffset - nSliderXOffset;
+ nRet = mnMinZoom + sal_uInt16( nOffsetToSliderLeft * nZoomPerSliderPixel / 1000 );
+ }
+ else
+ {
+ // second half of slider
+ const tools::Long nSecondHalfRange = mnMaxZoom - gnSliderCenter;
+ const tools::Long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
+ const tools::Long nZoomPerSliderPixel = 1000 * nSecondHalfRange / nHalfSliderWidth;
+ const tools::Long nOffsetToSliderCenter = nOffset - nControlWidth/2;
+ nRet = gnSliderCenter + sal_uInt16( nOffsetToSliderCenter * nZoomPerSliderPixel / 1000 );
+ }
+ }
+
+ if( nRet < mnMinZoom )
+ return mnMinZoom;
+
+ else if( nRet > mnMaxZoom )
+ return mnMaxZoom;
+
+ return nRet;
+}
+
+tools::Long ScZoomSlider::Zoom2Offset( sal_uInt16 nCurrentZoom ) const
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+ const tools::Long nControlWidth = aSliderWindowSize.Width();
+ tools::Long nRect = nSliderXOffset;
+
+ const tools::Long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
+ if( nCurrentZoom <= gnSliderCenter )
+ {
+ nCurrentZoom = nCurrentZoom - mnMinZoom;
+ const tools::Long nFirstHalfRange = gnSliderCenter - mnMinZoom;
+ const tools::Long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nFirstHalfRange;
+ const tools::Long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
+ nRect += nOffset;
+ }
+ else
+ {
+ nCurrentZoom = nCurrentZoom - gnSliderCenter;
+ const tools::Long nSecondHalfRange = mnMaxZoom - gnSliderCenter;
+ const tools::Long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nSecondHalfRange;
+ const tools::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)),
+ 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)
+ : mnCurrentZoom( nCurrentZoom ),
+ mnMinZoom( 10 ),
+ mnMaxZoom( 400 ),
+ mbOmitPaint( false ),
+ m_xDispatchProvider(rDispatchProvider)
+{
+ maSliderButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERBUTTON);
+ maIncreaseButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERINCREASE);
+ maDecreaseButton = Image(StockImage::Yes, RID_SVXBMP_SLIDERDECREASE);
+}
+
+
+bool ScZoomSlider::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+
+ const Point aPoint = rMEvt.GetPosPixel();
+
+ const tools::Long nButtonLeftOffset = ( nSliderXOffset - nIncDecWidth )/2;
+ const tools::Long nButtonRightOffset = ( nSliderXOffset + nIncDecWidth )/2;
+
+ const tools::Long nOldZoom = mnCurrentZoom;
+
+ // click to - button
+ if ( aPoint.X() >= nButtonLeftOffset && aPoint.X() <= nButtonRightOffset )
+ {
+ mnCurrentZoom = mnCurrentZoom - 5;
+ }
+ // click to + button
+ else if ( aPoint.X() >= aSliderWindowSize.Width() - nSliderXOffset + nButtonLeftOffset &&
+ aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset + nButtonRightOffset )
+ {
+ mnCurrentZoom = mnCurrentZoom + 5;
+ }
+ else if( aPoint.X() >= nSliderXOffset && aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset )
+ {
+ mnCurrentZoom = Offset2Zoom( aPoint.X() );
+ }
+
+ if( mnCurrentZoom < mnMinZoom )
+ mnCurrentZoom = mnMinZoom;
+ else if( mnCurrentZoom > mnMaxZoom )
+ mnCurrentZoom = mnMaxZoom;
+
+ if( nOldZoom == mnCurrentZoom )
+ return true;
+
+ tools::Rectangle aRect( Point( 0, 0 ), aSliderWindowSize );
+
+ Invalidate(aRect);
+ mbOmitPaint = true;
+
+ SvxZoomSliderItem aZoomSliderItem( mnCurrentZoom );
+
+ css::uno::Any a;
+ aZoomSliderItem.QueryValue( a );
+
+ css::uno::Sequence aArgs{ comphelper::makePropertyValue("ScalingFactor", a) };
+
+ SfxToolBoxControl::Dispatch( m_xDispatchProvider, ".uno:ScalingFactor", aArgs );
+
+ mbOmitPaint = false;
+
+ return true;
+}
+
+bool ScZoomSlider::MouseMove( const MouseEvent& rMEvt )
+{
+ Size aSliderWindowSize = GetOutputSizePixel();
+ const tools::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 )
+ {
+ mnCurrentZoom = Offset2Zoom( aPoint.X() );
+
+ tools::Rectangle aRect(Point(0, 0), aSliderWindowSize);
+ Invalidate(aRect);
+
+ mbOmitPaint = true; // optimization: paint before executing command,
+
+ // commit state change
+ SvxZoomSliderItem aZoomSliderItem( mnCurrentZoom );
+
+ css::uno::Any a;
+ aZoomSliderItem.QueryValue( a );
+
+ css::uno::Sequence aArgs{ comphelper::makePropertyValue("ScalingFactor", a) };
+
+ SfxToolBoxControl::Dispatch( m_xDispatchProvider, ".uno:ScalingFactor", aArgs );
+
+ mbOmitPaint = false;
+ }
+ }
+
+ return false;
+}
+
+void ScZoomSliderWnd::UpdateFromItem( const SvxZoomSliderItem* pZoomSliderItem )
+{
+ mxWidget->UpdateFromItem(pZoomSliderItem);
+}
+
+void ScZoomSlider::UpdateFromItem(const SvxZoomSliderItem* pZoomSliderItem)
+{
+ if( pZoomSliderItem )
+ {
+ mnCurrentZoom = pZoomSliderItem->GetValue();
+ mnMinZoom = pZoomSliderItem->GetMinZoom();
+ mnMaxZoom = pZoomSliderItem->GetMaxZoom();
+
+ OSL_ENSURE( mnMinZoom <= mnCurrentZoom &&
+ mnMinZoom < gnSliderCenter &&
+ mnMaxZoom >= mnCurrentZoom &&
+ mnMaxZoom > gnSliderCenter,
+ "Looks like the zoom slider item is corrupted" );
+ const css::uno::Sequence < sal_Int32 >& rSnappingPoints = pZoomSliderItem->GetSnappingPoints();
+ maSnappingPointOffsets.clear();
+ 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:
+ tools::Long nLastOffset = 0;
+
+ for ( const sal_uInt16 nCurrent : aTmpSnappingPoints )
+ {
+ const tools::Long nCurrentOffset = Zoom2Offset( nCurrent );
+
+ if ( nCurrentOffset - nLastOffset >= nSnappingPointsMinDist )
+ {
+ maSnappingPointOffsets.push_back( nCurrentOffset );
+ maSnappingPointZooms.push_back( nCurrent );
+ nLastOffset = nCurrentOffset;
+ }
+ }
+ }
+
+ Size aSliderWindowSize = GetOutputSizePixel();
+ tools::Rectangle aRect(Point(0, 0), aSliderWindowSize);
+
+ if ( !mbOmitPaint )
+ Invalidate(aRect);
+}
+
+void ScZoomSlider::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ DoPaint(rRenderContext);
+}
+
+void ScZoomSlider::DoPaint(vcl::RenderContext& rRenderContext)
+{
+ if (mbOmitPaint)
+ return;
+
+ Size aSliderWindowSize(GetOutputSizePixel());
+ tools::Rectangle aRect(Point(0, 0), aSliderWindowSize);
+
+ ScopedVclPtrInstance< VirtualDevice > pVDev(rRenderContext);
+ pVDev->SetOutputSizePixel(aSliderWindowSize);
+
+ 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 VirtualDevice's background color
+ Color aStartColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
+ Color aEndColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
+
+ if (aEndColor.IsDark())
+ aStartColor = aEndColor;
+
+ Gradient aGradient;
+ aGradient.SetAngle(0_deg10);
+ aGradient.SetStyle(GradientStyle::Linear);
+
+ aGradient.SetStartColor(aStartColor);
+ aGradient.SetEndColor(aEndColor);
+ pVDev->DrawGradient(aRect, aGradient);
+
+ // 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 : 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(mnCurrentZoom) );
+ aImagePoint.AdjustX( -(nButtonWidth / 2) );
+ aImagePoint.AdjustY( (aSliderWindowSize.Height() - nButtonHeight) / 2 );
+ pVDev->DrawImage(aImagePoint, maSliderButton);
+
+ // draw decrease button
+ aImagePoint = aRect.TopLeft();
+ aImagePoint.AdjustX((nSliderXOffset - nIncDecWidth) / 2 );
+ aImagePoint.AdjustY((aSliderWindowSize.Height() - nIncDecHeight) / 2 );
+ pVDev->DrawImage(aImagePoint, maDecreaseButton);
+
+ // draw increase button
+ aImagePoint.setX( aRect.Left() + aSliderWindowSize.Width() - nIncDecWidth - (nSliderXOffset - nIncDecWidth) / 2 );
+ pVDev->DrawImage(aImagePoint, maIncreaseButton);
+
+ rRenderContext.DrawOutDev(Point(0, 0), aSliderWindowSize, Point(0, 0), aSliderWindowSize, *pVDev);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */