diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/ui/cctrl | |
parent | Initial commit. (diff) | |
download | libreoffice-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.cxx | 88 | ||||
-rw-r--r-- | sc/source/ui/cctrl/cbuttonw.cxx | 139 | ||||
-rw-r--r-- | sc/source/ui/cctrl/checklistmenu.cxx | 1737 | ||||
-rw-r--r-- | sc/source/ui/cctrl/dpcontrol.cxx | 220 | ||||
-rw-r--r-- | sc/source/ui/cctrl/editfield.cxx | 63 | ||||
-rw-r--r-- | sc/source/ui/cctrl/tbzoomsliderctrl.cxx | 457 |
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: */ |