diff options
Diffstat (limited to 'sc/source/ui/cctrl')
-rw-r--r-- | sc/source/ui/cctrl/cbnumberformat.cxx | 91 | ||||
-rw-r--r-- | sc/source/ui/cctrl/cbuttonw.cxx | 139 | ||||
-rw-r--r-- | sc/source/ui/cctrl/checklistmenu.cxx | 2076 | ||||
-rw-r--r-- | sc/source/ui/cctrl/dpcontrol.cxx | 199 | ||||
-rw-r--r-- | sc/source/ui/cctrl/editfield.cxx | 63 | ||||
-rw-r--r-- | sc/source/ui/cctrl/tbzoomsliderctrl.cxx | 470 |
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: */ |