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 /vcl/source/window/toolbox.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.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 'vcl/source/window/toolbox.cxx')
-rw-r--r-- | vcl/source/window/toolbox.cxx | 4846 |
1 files changed, 4846 insertions, 0 deletions
diff --git a/vcl/source/window/toolbox.cxx b/vcl/source/window/toolbox.cxx new file mode 100644 index 000000000..64879c682 --- /dev/null +++ b/vcl/source/window/toolbox.cxx @@ -0,0 +1,4846 @@ +/* -*- 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 <vcl/toolbox.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/event.hxx> +#include <vcl/decoview.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/svapp.hxx> +#include <vcl/help.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/gradient.hxx> +#include <vcl/layout.hxx> +#include <vcl/menu.hxx> +#include <vcl/settings.hxx> +#include <vclstatuslistener.hxx> +#include <vcl/ptrstyle.hxx> +#include <bitmaps.hlst> +#include <toolbarvalue.hxx> + +#include <tools/poly.hxx> +#include <svl/imageitm.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include <accel.hxx> +#include <svdata.hxx> +#include <window.h> +#include <toolbox.h> +#include <spin.hxx> +#if defined(_WIN32) +#include <svsys.h> +#endif + +#include <cstdlib> +#include <map> +#include <string_view> +#include <vector> +#include <math.h> + +#include "impldockingwrapper.hxx" + +#define SMALLBUTTON_HSIZE 7 +#define SMALLBUTTON_VSIZE 7 + +#define SMALLBUTTON_OFF_NORMAL_X 3 +#define SMALLBUTTON_OFF_NORMAL_Y 3 + +#define TB_TEXTOFFSET 2 +#define TB_IMAGETEXTOFFSET 3 +#define TB_LINESPACING 3 +#define TB_SPIN_SIZE 14 +#define TB_SPIN_OFFSET 2 +#define TB_BORDER_OFFSET1 4 +#define TB_BORDER_OFFSET2 2 +#define TB_MAXLINES 5 +#define TB_MAXNOSCROLL 32765 + +#define TB_DRAGWIDTH 8 // the default width of the drag grip + +#define TB_CALCMODE_HORZ 1 +#define TB_CALCMODE_VERT 2 +#define TB_CALCMODE_FLOAT 3 + +#define TB_WBLINESIZING (WB_SIZEABLE | WB_DOCKABLE | WB_SCROLL) + +#define DOCK_LINEHSIZE (sal_uInt16(0x0001)) +#define DOCK_LINEVSIZE (sal_uInt16(0x0002)) +#define DOCK_LINERIGHT (sal_uInt16(0x1000)) +#define DOCK_LINEBOTTOM (sal_uInt16(0x2000)) +#define DOCK_LINELEFT (sal_uInt16(0x4000)) +#define DOCK_LINETOP (sal_uInt16(0x8000)) +#define DOCK_LINEOFFSET 3 + +class ImplTBDragMgr +{ +private: + VclPtr<ToolBox> mpDragBox; + Point maMouseOff; + tools::Rectangle maRect; + tools::Rectangle maStartRect; + Accelerator maAccel; + sal_uInt16 mnLineMode; + ToolBox::ImplToolItems::size_type mnStartLines; + + ImplTBDragMgr(const ImplTBDragMgr&) = delete; + ImplTBDragMgr& operator=(const ImplTBDragMgr&) = delete; + +public: + ImplTBDragMgr(); + + void StartDragging( ToolBox* pDragBox, const Point& rPos, const tools::Rectangle& rRect, sal_uInt16 nLineMode ); + void Dragging( const Point& rPos ); + void EndDragging( bool bOK = true ); + DECL_LINK( SelectHdl, Accelerator&, void ); +}; + + +static ImplTBDragMgr* ImplGetTBDragMgr() +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( !pSVData->maCtrlData.mpTBDragMgr ) + pSVData->maCtrlData.mpTBDragMgr = new ImplTBDragMgr; + return pSVData->maCtrlData.mpTBDragMgr; +} + +int ToolBox::ImplGetDragWidth( const vcl::Window& rWindow, bool bHorz ) +{ + return ImplGetDragWidth(*rWindow.GetOutDev(), bHorz); +} +int ToolBox::ImplGetDragWidth( const vcl::RenderContext& rRenderContext, bool bHorz ) +{ + int nWidth = TB_DRAGWIDTH; + if( rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire ) ) + { + + ImplControlValue aControlValue; + tools::Rectangle aContent, aBound; + tools::Rectangle aArea( Point(), rRenderContext.GetOutputSizePixel() ); + + if ( rRenderContext.GetNativeControlRegion(ControlType::Toolbar, + bHorz ? ControlPart::ThumbVert : ControlPart::ThumbHorz, + aArea, ControlState::NONE, aControlValue, aBound, aContent) ) + { + nWidth = bHorz ? aContent.GetWidth() : aContent.GetHeight(); + } + } + + // increase the hit area of the drag handle according to DPI scale factor + nWidth *= rRenderContext.GetDPIScaleFactor(); + + return nWidth; +} + +int ToolBox::ImplGetDragWidth() const +{ + return ToolBox::ImplGetDragWidth( *this, mbHorz ); +} + +static ButtonType determineButtonType( ImplToolItem const * pItem, ButtonType defaultType ) +{ + ButtonType tmpButtonType = defaultType; + ToolBoxItemBits nBits = pItem->mnBits & ( ToolBoxItemBits::TEXT_ONLY | ToolBoxItemBits::ICON_ONLY ); + if ( nBits != ToolBoxItemBits::NONE ) // item has custom setting + { + tmpButtonType = ButtonType::SYMBOLTEXT; + if ( nBits == ToolBoxItemBits::TEXT_ONLY ) + tmpButtonType = ButtonType::TEXT; + else if ( nBits == ToolBoxItemBits::ICON_ONLY ) + tmpButtonType = ButtonType::SYMBOLONLY; + } + return tmpButtonType; +} + +void ToolBox::ImplUpdateDragArea() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + if ( ImplIsFloatingMode() || pWrapper->IsLocked() ) + pWrapper->SetDragArea( tools::Rectangle() ); + else + { + if( meAlign == WindowAlign::Top || meAlign == WindowAlign::Bottom ) + pWrapper->SetDragArea( tools::Rectangle( 0, 0, ImplGetDragWidth(), GetOutputSizePixel().Height() ) ); + else + pWrapper->SetDragArea( tools::Rectangle( 0, 0, GetOutputSizePixel().Width(), ImplGetDragWidth() ) ); + } + } +} + +void ToolBox::ImplCalcBorder( WindowAlign eAlign, tools::Long& rLeft, tools::Long& rTop, + tools::Long& rRight, tools::Long& rBottom ) const +{ + if( ImplIsFloatingMode() || !(mnWinStyle & WB_BORDER) ) + { + // no border in floating mode + rLeft = rTop = rRight = rBottom = 0; + return; + } + + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + + // reserve DragArea only for dockable toolbars + int dragwidth = ( pWrapper && !pWrapper->IsLocked() ) ? ImplGetDragWidth() : 0; + + // no shadow border for dockable toolbars and toolbars with WB_NOSHADOW bit set, e.g. Calc's formulabar + int borderwidth = ( pWrapper || mnWinStyle & WB_NOSHADOW ) ? 0 : 2; + + if ( eAlign == WindowAlign::Top ) + { + rLeft = borderwidth+dragwidth; + rTop = borderwidth; + rRight = borderwidth; + rBottom = 0; + } + else if ( eAlign == WindowAlign::Left ) + { + rLeft = borderwidth; + rTop = borderwidth+dragwidth; + rRight = 0; + rBottom = borderwidth; + } + else if ( eAlign == WindowAlign::Bottom ) + { + rLeft = borderwidth+dragwidth; + rTop = 0; + rRight = borderwidth; + rBottom = borderwidth; + } + else + { + rLeft = 0; + rTop = borderwidth+dragwidth; + rRight = borderwidth; + rBottom = borderwidth; + } +} + +void ToolBox::ImplCheckUpdate() +{ + // remove any pending invalidates to avoid + // have them triggered when paint is locked (see mpData->mbIsPaintLocked) + // which would result in erasing the background only and not painting any items + // this must not be done when we're already in Paint() + + // this is only required for transparent toolbars (see ImplDrawTransparentBackground() ) + if( !IsBackground() && HasPaintEvent() && !IsInPaint() ) + PaintImmediately(); +} + +void ToolBox::ImplDrawGrip(vcl::RenderContext& rRenderContext, + const tools::Rectangle &aDragArea, int nDragWidth, WindowAlign eAlign, bool bHorz) +{ + bool bNativeOk = false; + const ControlPart ePart = bHorz ? ControlPart::ThumbVert : ControlPart::ThumbHorz; + const Size aSz( rRenderContext.GetOutputSizePixel() ); + if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ePart)) + { + ToolbarValue aToolbarValue; + aToolbarValue.maGripRect = aDragArea; + + tools::Rectangle aCtrlRegion(Point(), aSz); + + bNativeOk = rRenderContext.DrawNativeControl( ControlType::Toolbar, ePart, + aCtrlRegion, ControlState::ENABLED, aToolbarValue, OUString() ); + } + + if( bNativeOk ) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + + float fScaleFactor = rRenderContext.GetDPIScaleFactor(); + + if (eAlign == WindowAlign::Top || eAlign == WindowAlign::Bottom) + { + int height = static_cast<int>(0.6 * aSz.Height() + 0.5); + int i = (aSz.Height() - height) / 2; + height += i; + while (i <= height) + { + int x = nDragWidth / 2; + rRenderContext.DrawEllipse(tools::Rectangle(Point(x, i), Size(2 * fScaleFactor, 2 * fScaleFactor))); + i += 4 * fScaleFactor; + } + } + else + { + int width = static_cast<int>(0.6 * aSz.Width() + 0.5); + int i = (aSz.Width() - width) / 2; + width += i; + while (i <= width) + { + int y = nDragWidth / 2; + rRenderContext.DrawEllipse(tools::Rectangle(Point(i, y), Size(2 * fScaleFactor, 2 * fScaleFactor))); + i += 4 * fScaleFactor; + } + } +} + +void ToolBox::ImplDrawGrip(vcl::RenderContext& rRenderContext) +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this); + if( pWrapper && !pWrapper->GetDragArea().IsEmpty() ) + { + // execute pending paint requests + ImplCheckUpdate(); + ImplDrawGrip( rRenderContext, pWrapper->GetDragArea(), + ImplGetDragWidth(), meAlign, mbHorz ); + } +} + +void ToolBox::ImplDrawGradientBackground(vcl::RenderContext& rRenderContext) +{ + // draw a nice gradient + + Color startCol, endCol; + const StyleSettings rSettings = rRenderContext.GetSettings().GetStyleSettings(); + + startCol = rSettings.GetFaceGradientColor(); + endCol = rSettings.GetFaceColor(); + if (rSettings.GetHighContrastMode()) + // no 'extreme' gradient when high contrast + startCol = endCol; + + Gradient g; + g.SetAngle(Degree10(mbHorz ? 0 : 900)); + g.SetStyle(GradientStyle::Linear); + + g.SetStartColor(startCol); + g.SetEndColor(endCol); + + bool bLineColor = rRenderContext.IsLineColor(); + Color aOldCol = rRenderContext.GetLineColor(); + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor()); + + Size aFullSz(GetOutputSizePixel()); + Size aLineSz(aFullSz); + + // use the linesize only when floating + // full window height is used when docked (single line) + if (ImplIsFloatingMode()) + { + tools::Long nLineSize; + if (mbHorz) + { + nLineSize = mnMaxItemHeight; + if (mnWinHeight > mnMaxItemHeight) + nLineSize = mnWinHeight; + + aLineSz.setHeight( nLineSize ); + } + else + { + nLineSize = mnMaxItemWidth; + aLineSz.setWidth( nLineSize ); + } + } + + tools::Long nLeft, nTop, nRight, nBottom; + ImplCalcBorder(meAlign, nLeft, nTop, nRight, nBottom); + + Size aTopLineSz(aLineSz); + Size aBottomLineSz(aLineSz); + + if (mnWinStyle & WB_BORDER) + { + if (mbHorz) + { + aTopLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nTop ); + aBottomLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nBottom ); + + if (mnCurLines == 1) + aTopLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nBottom ); + } + else + { + aTopLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nLeft ); + aBottomLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nRight ); + + if (mnCurLines == 1) + aTopLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nLeft ); + } + } + + if (mbLineSpacing) + { + if (mbHorz) + { + aLineSz.AdjustHeight(TB_LINESPACING ); + if (mnCurLines > 1) + aTopLineSz.AdjustHeight(TB_LINESPACING ); + } + else + { + aLineSz.AdjustWidth(TB_LINESPACING ); + if (mnCurLines > 1) + aTopLineSz.AdjustWidth(TB_LINESPACING ); + } + } + + if (mbHorz) + { + tools::Long y = 0; + + rRenderContext.DrawGradient(tools::Rectangle(0, y, aTopLineSz.Width(), y + aTopLineSz.Height()), g); + y += aTopLineSz.Height(); + + while (y < (mnDY - aBottomLineSz.Height())) + { + rRenderContext.DrawGradient(tools::Rectangle(0, y, aLineSz.Width(), y + aLineSz.Height()), g); + y += aLineSz.Height(); + } + + rRenderContext.DrawGradient(tools::Rectangle(0, y, aBottomLineSz.Width(), y + aBottomLineSz.Height()), g); + } + else + { + tools::Long x = 0; + + rRenderContext.DrawGradient(tools::Rectangle(x, 0, x + aTopLineSz.Width(), aTopLineSz.Height()), g); + x += aTopLineSz.Width(); + + while (x < (mnDX - aBottomLineSz.Width())) + { + rRenderContext.DrawGradient(tools::Rectangle(x, 0, x + aLineSz.Width(), aLineSz.Height()), g); + x += aLineSz.Width(); + } + + rRenderContext.DrawGradient(tools::Rectangle( x, 0, x + aBottomLineSz.Width(), aBottomLineSz.Height()), g); + } + + if( bLineColor ) + rRenderContext.SetLineColor( aOldCol ); + +} + +bool ToolBox::ImplDrawNativeBackground(vcl::RenderContext& rRenderContext) const +{ + // use NWF + tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel()); + + return rRenderContext.DrawNativeControl( ControlType::Toolbar, mbHorz ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert, + aCtrlRegion, ControlState::ENABLED, ImplControlValue(), OUString() ); +} + +void ToolBox::ImplDrawTransparentBackground(const vcl::Region &rRegion) +{ + // just invalidate to trigger paint of the parent + const bool bOldPaintLock = mpData->mbIsPaintLocked; + mpData->mbIsPaintLocked = true; + + // send an invalidate to the first opaque parent and invalidate the whole hierarchy from there (noclipchildren) + Invalidate(rRegion, InvalidateFlags::Update | InvalidateFlags::NoClipChildren); + + mpData->mbIsPaintLocked = bOldPaintLock; +} + +void ToolBox::ImplDrawConstantBackground(vcl::RenderContext& rRenderContext, const vcl::Region &rRegion, bool bIsInPopupMode) +{ + // draw a constant color + if (!bIsInPopupMode) + { + // default background + rRenderContext.Erase(rRegion.GetBoundRect()); + } + else + { + // use different color in popupmode + const StyleSettings rSettings = rRenderContext.GetSettings().GetStyleSettings(); + Wallpaper aWallpaper(rSettings.GetFaceGradientColor()); + rRenderContext.DrawWallpaper(rRegion.GetBoundRect(), aWallpaper); + } +} + +void ToolBox::ImplDrawBackground(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + // execute pending paint requests + ImplCheckUpdate(); + + ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this); + bool bIsInPopupMode = ImplIsInPopupMode(); + + vcl::Region aPaintRegion(rRect); + + // make sure we do not invalidate/erase too much + if (IsInPaint()) + aPaintRegion.Intersect(GetOutDev()->GetActiveClipRegion()); + + rRenderContext.Push(vcl::PushFlags::CLIPREGION); + rRenderContext.IntersectClipRegion( aPaintRegion ); + + if (!pWrapper) + { + // no gradient for ordinary toolbars (not dockable) + if( !IsBackground() && !IsInPaint() ) + ImplDrawTransparentBackground(aPaintRegion); + else + ImplDrawConstantBackground(rRenderContext, aPaintRegion, bIsInPopupMode); + } + else + { + // toolbars known to the dockingmanager will be drawn using NWF or a gradient + // docked toolbars are transparent and NWF is already used in the docking area which is their common background + // so NWF is used here for floating toolbars only + bool bNativeOk = false; + if( ImplIsFloatingMode() && rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire) ) + bNativeOk = ImplDrawNativeBackground(rRenderContext); + if (!bNativeOk) + { + const StyleSettings rSetting = Application::GetSettings().GetStyleSettings(); + const bool isHeader = GetAlign() == WindowAlign::Top && !rSetting.GetPersonaHeader().IsEmpty(); + const bool isFooter = GetAlign() == WindowAlign::Bottom && !rSetting.GetPersonaFooter().IsEmpty(); + if (!IsBackground() || isHeader || isFooter) + { + if (!IsInPaint()) + ImplDrawTransparentBackground(aPaintRegion); + } + else + ImplDrawGradientBackground(rRenderContext); + } + } + + // restore clip region + rRenderContext.Pop(); +} + +void ToolBox::ImplErase(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect, bool bHighlight, bool bHasOpenPopup) +{ + // the background of non NWF buttons is painted in a constant color + // to have the same highlight color (transparency in DrawSelectionBackground()) + // items with open popups will also painted using a constant color + if (!mpData->mbNativeButtons && + (bHighlight || !(GetStyle() & WB_3DLOOK))) + { + if (GetStyle() & WB_3DLOOK) + { + rRenderContext.Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + rRenderContext.SetLineColor(); + if (bHasOpenPopup) + // choose the same color as the popup will use + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetFaceGradientColor()); + else + rRenderContext.SetFillColor(COL_WHITE); + + rRenderContext.DrawRect(rRect); + rRenderContext.Pop(); + } + else + ImplDrawBackground(rRenderContext, rRect); + } + else + ImplDrawBackground(rRenderContext, rRect); +} + +void ToolBox::ImplDrawBorder(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Long nDX = mnDX; + tools::Long nDY = mnDY; + + ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this); + + // draw borders for ordinary toolbars only (not dockable), do not draw borders for toolbars with WB_NOSHADOW bit set, + // e.g. Calc's formulabar + + if( pWrapper || mnWinStyle & WB_NOSHADOW ) + return; + + if (meAlign == WindowAlign::Bottom) + { + // draw bottom border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-1, nDY-2 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) ); + } + else + { + // draw top border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, 0 ), Point( nDX-1, 0 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 0, 1 ), Point( nDX-1, 1 ) ); + + if (meAlign == WindowAlign::Left || meAlign == WindowAlign::Right) + { + if (meAlign == WindowAlign::Left) + { + // draw left-bottom border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, 0 ), Point( 0, nDY-1 ) ); + rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-1, nDY-2 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 1, 1 ), Point( 1, nDY-3 ) ); + rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) ); + } + else + { + // draw right-bottom border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( nDX-2, 0 ), Point( nDX-2, nDY-3 ) ); + rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-2, nDY-2 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( nDX-1, 0 ), Point( nDX-1, nDY-1 ) ); + rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) ); + } + } + } + + if ( meAlign == WindowAlign::Bottom || meAlign == WindowAlign::Top ) + { + // draw right border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( nDX-2, 0 ), Point( nDX-2, nDY-1 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( nDX-1, 0 ), Point( nDX-1, nDY-1 ) ); + } +} + +static bool ImplIsFixedControl( const ImplToolItem *pItem ) +{ + return ( pItem->mpWindow && + (pItem->mbNonInteractiveWindow || + pItem->mpWindow->GetType() == WindowType::FIXEDTEXT || + pItem->mpWindow->GetType() == WindowType::FIXEDLINE || + pItem->mpWindow->GetType() == WindowType::GROUPBOX) ); +} + +const ImplToolItem *ToolBox::ImplGetFirstClippedItem() const +{ + for (auto & item : mpData->m_aItems) + { + if( item.IsClipped() ) + return &item; + } + return nullptr; +} + +Size ToolBox::ImplCalcSize( ImplToolItems::size_type nCalcLines, sal_uInt16 nCalcMode ) +{ + sal_Int32 nMax; + tools::Long nLeft = 0; + tools::Long nTop = 0; + tools::Long nRight = 0; + tools::Long nBottom = 0; + Size aSize; + WindowAlign eOldAlign = meAlign; + bool bOldHorz = mbHorz; + bool bOldAssumeDocked = mpData->mbAssumeDocked; + bool bOldAssumeFloating = mpData->mbAssumeFloating; + + if ( nCalcMode ) + { + bool bOldFloatingMode = ImplIsFloatingMode(); + + mpData->mbAssumeDocked = false; + mpData->mbAssumeFloating = false; + + if ( nCalcMode == TB_CALCMODE_HORZ ) + { + mpData->mbAssumeDocked = true; // force non-floating mode during calculation + ImplCalcBorder( WindowAlign::Top, nLeft, nTop, nRight, nBottom ); + mbHorz = true; + if ( mbHorz != bOldHorz ) + meAlign = WindowAlign::Top; + } + else if ( nCalcMode == TB_CALCMODE_VERT ) + { + mpData->mbAssumeDocked = true; // force non-floating mode during calculation + ImplCalcBorder( WindowAlign::Left, nLeft, nTop, nRight, nBottom ); + mbHorz = false; + if ( mbHorz != bOldHorz ) + meAlign = WindowAlign::Left; + } + else if ( nCalcMode == TB_CALCMODE_FLOAT ) + { + mpData->mbAssumeFloating = true; // force non-floating mode during calculation + nLeft = nTop = nRight = nBottom = 0; + mbHorz = true; + if ( mbHorz != bOldHorz ) + meAlign = WindowAlign::Top; + } + + if ( (meAlign != eOldAlign) || (mbHorz != bOldHorz) || + (ImplIsFloatingMode() != bOldFloatingMode ) ) + mbCalc = true; + } + else + ImplCalcBorder( meAlign, nLeft, nTop, nRight, nBottom ); + + ImplCalcItem(); + + if( !nCalcMode && ImplIsFloatingMode() ) + { + aSize = ImplCalcFloatSize( nCalcLines ); + } + else + { + if ( mbHorz ) + { + if ( mnWinHeight > mnMaxItemHeight ) + aSize.setHeight( nCalcLines * mnWinHeight ); + else + aSize.setHeight( nCalcLines * mnMaxItemHeight ); + + if ( mbLineSpacing ) + aSize.AdjustHeight((nCalcLines-1)*TB_LINESPACING ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustHeight((TB_BORDER_OFFSET2*2) + nTop + nBottom ); + + nMax = 0; + ImplCalcBreaks( TB_MAXNOSCROLL, &nMax, mbHorz ); + if ( nMax ) + aSize.AdjustWidth(nMax ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustWidth((TB_BORDER_OFFSET1*2) + nLeft + nRight ); + } + else + { + aSize.setWidth( nCalcLines * mnMaxItemWidth ); + + if ( mbLineSpacing ) + aSize.AdjustWidth((nCalcLines-1)*TB_LINESPACING ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustWidth((TB_BORDER_OFFSET2*2) + nLeft + nRight ); + + nMax = 0; + ImplCalcBreaks( TB_MAXNOSCROLL, &nMax, mbHorz ); + if ( nMax ) + aSize.AdjustHeight(nMax ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustHeight((TB_BORDER_OFFSET1*2) + nTop + nBottom ); + } + } + // restore previous values + if ( nCalcMode ) + { + mpData->mbAssumeDocked = bOldAssumeDocked; + mpData->mbAssumeFloating = bOldAssumeFloating; + if ( (meAlign != eOldAlign) || (mbHorz != bOldHorz) ) + { + meAlign = eOldAlign; + mbHorz = bOldHorz; + mbCalc = true; + } + } + + return aSize; +} + +void ToolBox::ImplCalcFloatSizes() +{ + if ( !maFloatSizes.empty() ) + return; + + // calculate the minimal size, i.e. where the biggest item just fits + tools::Long nCalcSize = 0; + + for (auto const& item : mpData->m_aItems) + { + if ( item.mbVisible ) + { + if ( item.mpWindow ) + { + tools::Long nTempSize = item.mpWindow->GetSizePixel().Width(); + if ( nTempSize > nCalcSize ) + nCalcSize = nTempSize; + } + else + { + if( item.maItemSize.Width() > nCalcSize ) + nCalcSize = item.maItemSize.Width(); + } + } + } + + // calc an upper bound for ImplCalcBreaks below + tools::Long upperBoundWidth = nCalcSize * mpData->m_aItems.size(); + + ImplToolItems::size_type nLines; + ImplToolItems::size_type nCalcLines; + ImplToolItems::size_type nTempLines; + sal_Int32 nMaxLineWidth; + nCalcLines = ImplCalcBreaks( nCalcSize, &nMaxLineWidth, true ); + + maFloatSizes.reserve( nCalcLines ); + + nTempLines = nLines = nCalcLines; + while ( nLines ) + { + tools::Long nHeight = ImplCalcSize( nTempLines, TB_CALCMODE_FLOAT ).Height(); + + ImplToolSize aSize; + aSize.mnWidth = nMaxLineWidth+(TB_BORDER_OFFSET1*2); + aSize.mnHeight = nHeight; + aSize.mnLines = nTempLines; + maFloatSizes.push_back( aSize ); + nLines--; + if ( nLines ) + { + do + { + nCalcSize += mnMaxItemWidth; + nTempLines = ImplCalcBreaks( nCalcSize, &nMaxLineWidth, true ); + } + while ((nCalcSize < upperBoundWidth) && (nLines < nTempLines)); // implies nTempLines>1 + if ( nTempLines < nLines ) + nLines = nTempLines; + } + } +} + +Size ToolBox::ImplCalcFloatSize( ImplToolItems::size_type& rLines ) +{ + ImplCalcFloatSizes(); + + if ( !rLines ) + { + rLines = mnFloatLines; + if ( !rLines ) + rLines = mnLines; + } + + sal_uInt16 i = 0; + while ( i + 1u < maFloatSizes.size() && rLines < maFloatSizes[i].mnLines ) + { + i++; + } + + Size aSize( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight ); + rLines = maFloatSizes[i].mnLines; + + return aSize; +} + +void ToolBox::ImplCalcMinMaxFloatSize( Size& rMinSize, Size& rMaxSize ) +{ + ImplCalcFloatSizes(); + + sal_uInt16 i = 0; + rMinSize = Size( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight ); + rMaxSize = Size( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight ); + while ( ++i < maFloatSizes.size() ) + { + if( maFloatSizes[i].mnWidth < rMinSize.Width() ) + rMinSize.setWidth( maFloatSizes[i].mnWidth ); + if( maFloatSizes[i].mnHeight < rMinSize.Height() ) + rMinSize.setHeight( maFloatSizes[i].mnHeight ); + + if( maFloatSizes[i].mnWidth > rMaxSize.Width() ) + rMaxSize.setWidth( maFloatSizes[i].mnWidth ); + if( maFloatSizes[i].mnHeight > rMaxSize.Height() ) + rMaxSize.setHeight( maFloatSizes[i].mnHeight ); + } +} + +void ToolBox::ImplSetMinMaxFloatSize() +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + Size aMinSize, aMaxSize; + ImplCalcMinMaxFloatSize( aMinSize, aMaxSize ); + if( pWrapper ) + { + pWrapper->SetMinOutputSizePixel( aMinSize ); + pWrapper->SetMaxOutputSizePixel( aMaxSize ); + pWrapper->ShowMenuTitleButton( bool( GetMenuType() & ToolBoxMenuType::Customize) ); + } + else + { + // TODO: change SetMinOutputSizePixel to be not inline + SetMinOutputSizePixel( aMinSize ); + SetMaxOutputSizePixel( aMaxSize ); + } +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplCalcLines( tools::Long nToolSize ) const +{ + tools::Long nLineHeight; + + if ( mbHorz ) + { + if ( mnWinHeight > mnMaxItemHeight ) + nLineHeight = mnWinHeight; + else + nLineHeight = mnMaxItemHeight; + } + else + nLineHeight = mnMaxItemWidth; + + if ( mnWinStyle & WB_BORDER ) + nToolSize -= TB_BORDER_OFFSET2*2; + + if ( mbLineSpacing ) + { + nLineHeight += TB_LINESPACING; + nToolSize += TB_LINESPACING; + } + + // #i91917# always report at least one line + tools::Long nLines = nToolSize/nLineHeight; + if( nLines < 1 ) + nLines = 1; + + return nLines; +} + +sal_uInt16 ToolBox::ImplTestLineSize( const Point& rPos ) const +{ + if ( !ImplIsFloatingMode() && + (!mbScroll || (mnLines > 1) || (mnCurLines > mnVisLines)) ) + { + WindowAlign eAlign = GetAlign(); + + if ( eAlign == WindowAlign::Left ) + { + if ( rPos.X() > mnDX-DOCK_LINEOFFSET ) + return DOCK_LINEHSIZE | DOCK_LINERIGHT; + } + else if ( eAlign == WindowAlign::Top ) + { + if ( rPos.Y() > mnDY-DOCK_LINEOFFSET ) + return DOCK_LINEVSIZE | DOCK_LINEBOTTOM; + } + else if ( eAlign == WindowAlign::Right ) + { + if ( rPos.X() < DOCK_LINEOFFSET ) + return DOCK_LINEHSIZE | DOCK_LINELEFT; + } + else if ( eAlign == WindowAlign::Bottom ) + { + if ( rPos.Y() < DOCK_LINEOFFSET ) + return DOCK_LINEVSIZE | DOCK_LINETOP; + } + } + + return 0; +} + +void ToolBox::ImplLineSizing( const Point& rPos, tools::Rectangle& rRect, sal_uInt16 nLineMode ) +{ + bool bHorz; + tools::Long nOneLineSize; + tools::Long nCurSize; + tools::Long nMaxSize; + tools::Long nSize; + Size aSize; + + if ( nLineMode & DOCK_LINERIGHT ) + { + nCurSize = rPos.X() - rRect.Left(); + bHorz = false; + } + else if ( nLineMode & DOCK_LINEBOTTOM ) + { + nCurSize = rPos.Y() - rRect.Top(); + bHorz = true; + } + else if ( nLineMode & DOCK_LINELEFT ) + { + nCurSize = rRect.Right() - rPos.X(); + bHorz = false; + } + else if ( nLineMode & DOCK_LINETOP ) + { + nCurSize = rRect.Bottom() - rPos.Y(); + bHorz = true; + } + else { + OSL_FAIL( "ImplLineSizing: Trailing else" ); + nCurSize = 0; + bHorz = false; + } + + Size aWinSize = GetSizePixel(); + ImplToolItems::size_type nMaxLines = std::max(mnLines, mnCurLines); + if ( nMaxLines > TB_MAXLINES ) + nMaxLines = TB_MAXLINES; + if ( bHorz ) + { + nOneLineSize = ImplCalcSize( 1 ).Height(); + nMaxSize = - 20; + if ( nMaxSize < aWinSize.Height() ) + nMaxSize = aWinSize.Height(); + } + else + { + nOneLineSize = ImplCalcSize( 1 ).Width(); + nMaxSize = - 20; + if ( nMaxSize < aWinSize.Width() ) + nMaxSize = aWinSize.Width(); + } + + ImplToolItems::size_type i = 1; + if ( nCurSize <= nOneLineSize ) + nSize = nOneLineSize; + else + { + nSize = 0; + while ( (nSize < nCurSize) && (i < nMaxLines) ) + { + i++; + aSize = ImplCalcSize( i ); + if ( bHorz ) + nSize = aSize.Height(); + else + nSize = aSize.Width(); + if ( nSize > nMaxSize ) + { + i--; + aSize = ImplCalcSize( i ); + if ( bHorz ) + nSize = aSize.Height(); + else + nSize = aSize.Width(); + break; + } + } + } + + if ( nLineMode & DOCK_LINERIGHT ) + rRect.SetRight( rRect.Left()+nSize-1 ); + else if ( nLineMode & DOCK_LINEBOTTOM ) + rRect.SetBottom( rRect.Top()+nSize-1 ); + else if ( nLineMode & DOCK_LINELEFT ) + rRect.SetLeft( rRect.Right()-nSize ); + else + rRect.SetTop( rRect.Bottom()-nSize ); + + mnDockLines = i; +} + +ImplTBDragMgr::ImplTBDragMgr() + : mpDragBox(nullptr) + , mnLineMode(0) + , mnStartLines(0) +{ + maAccel.InsertItem( KEY_RETURN, vcl::KeyCode( KEY_RETURN ) ); + maAccel.InsertItem( KEY_ESCAPE, vcl::KeyCode( KEY_ESCAPE ) ); + maAccel.SetSelectHdl( LINK( this, ImplTBDragMgr, SelectHdl ) ); +} + +void ImplTBDragMgr::StartDragging( ToolBox* pToolBox, + const Point& rPos, const tools::Rectangle& rRect, + sal_uInt16 nDragLineMode ) +{ + mpDragBox = pToolBox; + pToolBox->CaptureMouse(); + pToolBox->mbDragging = true; + Application::InsertAccel( &maAccel ); + + mnLineMode = nDragLineMode; + mnStartLines = pToolBox->mnDockLines; + + // calculate MouseOffset + maMouseOff.setX( rRect.Left() - rPos.X() ); + maMouseOff.setY( rRect.Top() - rPos.Y() ); + maRect = rRect; + maStartRect = rRect; + pToolBox->ShowTracking( maRect ); +} + +void ImplTBDragMgr::Dragging( const Point& rPos ) +{ + mpDragBox->ImplLineSizing( rPos, maRect, mnLineMode ); + Point aOff = mpDragBox->OutputToScreenPixel( Point() ); + maRect.Move( aOff.X(), aOff.Y() ); + mpDragBox->Docking( rPos, maRect ); + maRect.Move( -aOff.X(), -aOff.Y() ); + mpDragBox->ShowTracking( maRect ); +} + +void ImplTBDragMgr::EndDragging( bool bOK ) +{ + mpDragBox->HideTracking(); + if (mpDragBox->IsMouseCaptured()) + mpDragBox->ReleaseMouse(); + mpDragBox->mbDragging = false; + Application::RemoveAccel( &maAccel ); + + if ( !bOK ) + { + mpDragBox->mnDockLines = mnStartLines; + mpDragBox->EndDocking( maStartRect, false ); + } + else + mpDragBox->EndDocking( maRect, false ); + mnStartLines = 0; + + mpDragBox = nullptr; +} + +IMPL_LINK( ImplTBDragMgr, SelectHdl, Accelerator&, rAccel, void ) +{ + if ( rAccel.GetCurItemId() == KEY_ESCAPE ) + EndDragging( false ); + else + EndDragging(); +} + +void ToolBox::ImplInitToolBoxData() +{ + // initialize variables + ImplGetWindowImpl()->mbToolBox = true; + mpData.reset(new ImplToolBoxPrivateData); + + mpFloatWin = nullptr; + mnDX = 0; + mnDY = 0; + mnMaxItemWidth = 0; + mnMaxItemHeight = 0; + mnWinHeight = 0; + mnLeftBorder = 0; + mnTopBorder = 0; + mnRightBorder = 0; + mnBottomBorder = 0; + mnLastResizeDY = 0; + mnHighItemId = ToolBoxItemId(0); + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnCurPos = ITEM_NOTFOUND; + mnLines = 1; + mnCurLine = 1; + mnCurLines = 1; + mnVisLines = 1; + mnFloatLines = 0; + mnDockLines = 0; + mnMouseModifier = 0; + mbDrag = false; + mbUpper = false; + mbLower = false; + mbIn = false; + mbCalc = true; + mbFormat = false; + mbFullPaint = false; + mbHorz = true; + mbScroll = false; + mbLastFloatMode = false; + mbCustomize = false; + mbDragging = false; + mbIsKeyEvent = false; + mbChangingHighlight = false; + mbImagesMirrored = false; + mbLineSpacing = false; + mbIsArranged = false; + meButtonType = ButtonType::SYMBOLONLY; + meAlign = WindowAlign::Top; + meDockAlign = WindowAlign::Top; + meLastStyle = PointerStyle::Arrow; + mnWinStyle = 0; + meLayoutMode = ToolBoxLayoutMode::Normal; + meTextPosition = ToolBoxTextPosition::Right; + mnLastFocusItemId = ToolBoxItemId(0); + mnActivateCount = 0; + mnImagesRotationAngle = 0_deg10; + + mpStatusListener = new VclStatusListener<ToolBox>(this, ".uno:ImageOrientation"); + mpStatusListener->startListening(); + + mpIdle.reset(new Idle("vcl::ToolBox maIdle update")); + mpIdle->SetPriority( TaskPriority::RESIZE ); + mpIdle->SetInvokeHandler( LINK( this, ToolBox, ImplUpdateHdl ) ); + + // set timeout and handler for dropdown items + mpData->maDropdownTimer.SetTimeout( 250 ); + mpData->maDropdownTimer.SetInvokeHandler( LINK( this, ToolBox, ImplDropdownLongClickHdl ) ); +} + +void ToolBox::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + // initialize variables + mbScroll = (nStyle & WB_SCROLL) != 0; + mnWinStyle = nStyle; + + DockingWindow::ImplInit( pParent, nStyle & ~WB_BORDER ); + + // dockingwindow's ImplInit removes some bits, so restore them here to allow keyboard handling for toolbars + ImplGetWindowImpl()->mnStyle |= WB_TABSTOP|WB_NODIALOGCONTROL; // always set WB_TABSTOP for ToolBars + ImplGetWindowImpl()->mnStyle &= ~WB_DIALOGCONTROL; + + ImplInitSettings(true, true, true); +} + +void ToolBox::ApplyForegroundSettings(vcl::RenderContext& rRenderContext, const StyleSettings& rStyleSettings) +{ + Color aColor; + if (IsControlForeground()) + aColor = GetControlForeground(); + else if (Window::GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetButtonTextColor(); + else + aColor = rStyleSettings.GetWindowTextColor(); + rRenderContext.SetTextColor(aColor); + rRenderContext.SetTextFillColor(); +} + +void ToolBox::ApplyBackgroundSettings(vcl::RenderContext& rRenderContext, const StyleSettings& rStyleSettings) +{ + if (IsControlBackground()) + { + rRenderContext.SetBackground(GetControlBackground()); + SetPaintTransparent(false); + SetParentClipMode(); + } + else + { + if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire) + || (GetAlign() == WindowAlign::Top && !Application::GetSettings().GetStyleSettings().GetPersonaHeader().IsEmpty()) + || (GetAlign() == WindowAlign::Bottom && !Application::GetSettings().GetStyleSettings().GetPersonaFooter().IsEmpty())) + { + rRenderContext.SetBackground(); + rRenderContext.SetTextColor(rStyleSettings.GetToolTextColor()); + SetPaintTransparent(true); + SetParentClipMode(ParentClipMode::NoClip); + mpData->maDisplayBackground = Wallpaper(rStyleSettings.GetFaceColor()); + } + else + { + Color aColor; + if (Window::GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetFaceColor(); + else + aColor = rStyleSettings.GetWindowColor(); + rRenderContext.SetBackground(aColor); + SetPaintTransparent(false); + SetParentClipMode(); + } + } +} + +void ToolBox::ApplySettings(vcl::RenderContext& rRenderContext) +{ + mpData->mbNativeButtons = rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button); + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + ApplyControlFont(rRenderContext, rStyleSettings.GetToolFont()); + ApplyForegroundSettings(rRenderContext, rStyleSettings); + ApplyBackgroundSettings(rRenderContext, rStyleSettings); +} + +void ToolBox::ImplInitSettings(bool bFont, bool bForeground, bool bBackground) +{ + mpData->mbNativeButtons = IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ); + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + ApplyControlFont(*GetOutDev(), rStyleSettings.GetToolFont()); + if (bForeground || bFont) + ApplyForegroundSettings(*GetOutDev(), rStyleSettings); + if (bBackground) + { + ApplyBackgroundSettings(*GetOutDev(), rStyleSettings); + EnableChildTransparentMode(IsPaintTransparent()); + } +} + +void ToolBox::doDeferredInit(WinBits nBits) +{ + VclPtr<vcl::Window> pParent = mpDialogParent; + mpDialogParent = nullptr; + ImplInit(pParent, nBits); + mbIsDeferredInit = false; +} + +void ToolBox::queue_resize(StateChangedType eReason) +{ + Window::queue_resize(eReason); +} + +ToolBox::ToolBox( vcl::Window* pParent, WinBits nStyle ) : + DockingWindow( WindowType::TOOLBOX, "vcl::ToolBox maLayoutIdle" ) +{ + ImplInitToolBoxData(); + ImplInit( pParent, nStyle ); +} + +ToolBox::ToolBox(vcl::Window* pParent, const OString& rID, + const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame) + : DockingWindow(WindowType::TOOLBOX, "vcl::ToolBox maLayoutIdle") +{ + ImplInitToolBoxData(); + + loadUI(pParent, rID, rUIXMLDescription, rFrame); + + // calculate size of floating windows and switch if the + // toolbox is initially in floating mode + if ( ImplIsFloatingMode() ) + mbHorz = true; + else + Resize(); + + if (!(GetStyle() & WB_HIDE)) + Show(); +} + +ToolBox::~ToolBox() +{ + disposeOnce(); +} + +void ToolBox::dispose() +{ + // #103005# make sure our activate/deactivate balance is right + while( mnActivateCount > 0 ) + Deactivate(); + + // terminate popupmode if the floating window is + // still connected + if ( mpFloatWin ) + mpFloatWin->EndPopupMode( FloatWinPopupEndFlags::Cancel ); + mpFloatWin = nullptr; + + // delete private data + mpData.reset(); + + ImplSVData* pSVData = ImplGetSVData(); + delete pSVData->maCtrlData.mpTBDragMgr; + pSVData->maCtrlData.mpTBDragMgr = nullptr; + + if (mpStatusListener.is()) + mpStatusListener->dispose(); + + mpFloatWin.clear(); + + mpIdle.reset(); + + DockingWindow::dispose(); +} + +ImplToolItem* ToolBox::ImplGetItem( ToolBoxItemId nItemId ) const +{ + if (!mpData) + return nullptr; + + for (auto & item : mpData->m_aItems) + { + if ( item.mnId == nItemId ) + return &item; + } + + return nullptr; +} + +static void ImplAddButtonBorder( tools::Long &rWidth, tools::Long& rHeight, bool bNativeButtons ) +{ + rWidth += SMALLBUTTON_HSIZE; + rHeight += SMALLBUTTON_VSIZE; + + if( bNativeButtons ) + { + // give more border space for rounded buttons + rWidth += 2; + rHeight += 4; + } +} + +bool ToolBox::ImplCalcItem() +{ + // recalc required ? + if ( !mbCalc ) + return false; + + OutputDevice *pDefault = Application::GetDefaultDevice(); + float fScaleFactor = pDefault ? pDefault->GetDPIScaleFactor() : 1.0; + + tools::Long nDefWidth; + tools::Long nDefHeight; + tools::Long nMaxWidth = 0; + tools::Long nMaxHeight = 0; + tools::Long nMinWidth = 6; + tools::Long nMinHeight = 6; + tools::Long nDropDownArrowWidth = TB_DROPDOWNARROWWIDTH * fScaleFactor; +#ifdef IOS + nDropDownArrowWidth *= 3; +#endif + + // set defaults if image or text is needed but empty + nDefWidth = GetDefaultImageSize().Width(); + nDefHeight = GetDefaultImageSize().Height(); + + mnWinHeight = 0; + // determine minimum size necessary in NWF + { + tools::Rectangle aRect( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + tools::Rectangle aReg( aRect ); + ImplControlValue aVal; + tools::Rectangle aNativeBounds, aNativeContent; + if( IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ) ) + { + if( GetNativeControlRegion( ControlType::Toolbar, ControlPart::Button, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetWidth() > nMinWidth ) + nMinWidth = aRect.GetWidth(); + if( aRect.GetHeight() > nMinHeight ) + nMinHeight = aRect.GetHeight(); + if( nDropDownArrowWidth < nMinWidth ) + nDropDownArrowWidth = nMinWidth; + if( nMinWidth > mpData->mnMenuButtonWidth ) + mpData->mnMenuButtonWidth = nMinWidth; + else if( nMinWidth < TB_MENUBUTTON_SIZE ) + mpData->mnMenuButtonWidth = TB_MENUBUTTON_SIZE; + } + } + + // also calculate the area for comboboxes, drop down list boxes and spinfields + // as these are often inserted into toolboxes; set mnWinHeight to the + // greater of those values to prevent toolbar flickering (#i103385#) + aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + aReg = aRect; + if( GetNativeControlRegion( ControlType::Combobox, ControlPart::Entire, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetHeight() > mnWinHeight ) + mnWinHeight = aRect.GetHeight(); + } + aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + aReg = aRect; + if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetHeight() > mnWinHeight ) + mnWinHeight = aRect.GetHeight(); + } + aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + aReg = aRect; + if( GetNativeControlRegion( ControlType::Spinbox, ControlPart::Entire, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetHeight() > mnWinHeight ) + mnWinHeight = aRect.GetHeight(); + } + } + + if ( ! mpData->m_aItems.empty() ) + { + for (auto & item : mpData->m_aItems) + { + item.mbVisibleText = false; // indicates if text will definitely be drawn, influences dropdown pos + + if ( item.meType == ToolBoxItemType::BUTTON ) + { + bool bImage; + bool bText; + + // check if image and/or text exists + bImage = !!item.maImage; + bText = !item.maText.isEmpty(); + ButtonType tmpButtonType = determineButtonType( &item, meButtonType ); // default to toolbox setting + if ( bImage || bText ) + { + + item.mbEmptyBtn = false; + + if ( tmpButtonType == ButtonType::SYMBOLONLY ) + { + // we're drawing images only + if ( bImage || !bText ) + { + item.maItemSize = item.maImage.GetSizePixel(); + } + else + { + item.maItemSize = Size( GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET, + GetTextHeight() ); + item.mbVisibleText = true; + } + } + else if ( tmpButtonType == ButtonType::TEXT ) + { + // we're drawing text only + if ( bText || !bImage ) + { + item.maItemSize = Size( GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET, + GetTextHeight() ); + item.mbVisibleText = true; + } + else + { + item.maItemSize = item.maImage.GetSizePixel(); + } + } + else + { + // we're drawing images and text + item.maItemSize.setWidth( bText ? GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET : 0 ); + item.maItemSize.setHeight( bText ? GetTextHeight() : 0 ); + + if ( meTextPosition == ToolBoxTextPosition::Right ) + { + // leave space between image and text + if( bText ) + item.maItemSize.AdjustWidth(TB_IMAGETEXTOFFSET ); + + // image and text side by side + item.maItemSize.AdjustWidth(item.maImage.GetSizePixel().Width() ); + if ( item.maImage.GetSizePixel().Height() > item.maItemSize.Height() ) + item.maItemSize.setHeight( item.maImage.GetSizePixel().Height() ); + } + else + { + // leave space between image and text + if( bText ) + item.maItemSize.AdjustHeight(TB_IMAGETEXTOFFSET ); + + // text below image + item.maItemSize.AdjustHeight(item.maImage.GetSizePixel().Height() ); + if ( item.maImage.GetSizePixel().Width() > item.maItemSize.Width() ) + item.maItemSize.setWidth( item.maImage.GetSizePixel().Width() ); + } + + item.mbVisibleText = bText; + } + } + else + { // no image and no text + item.maItemSize = Size( nDefWidth, nDefHeight ); + item.mbEmptyBtn = true; + } + + // save the content size + item.maContentSize = item.maItemSize; + + // if required, take window height into consideration + if ( item.mpWindow ) + { + tools::Long nHeight = item.mpWindow->GetSizePixel().Height(); + if ( nHeight > mnWinHeight ) + mnWinHeight = nHeight; + } + + // add in drop down arrow + if( item.mnBits & ToolBoxItemBits::DROPDOWN ) + { + item.maItemSize.AdjustWidth(nDropDownArrowWidth ); + item.mnDropDownArrowWidth = nDropDownArrowWidth; + } + + // text items will be rotated in vertical mode + // -> swap width and height + if( item.mbVisibleText && !mbHorz ) + { + tools::Long tmp = item.maItemSize.Width(); + item.maItemSize.setWidth( item.maItemSize.Height() ); + item.maItemSize.setHeight( tmp ); + + tmp = item.maContentSize.Width(); + item.maContentSize.setWidth( item.maContentSize.Height() ); + item.maContentSize.setHeight( tmp ); + } + } + else if ( item.meType == ToolBoxItemType::SPACE ) + { + item.maItemSize = Size( nDefWidth, nDefHeight ); + item.maContentSize = item.maItemSize; + } + + if ( item.meType == ToolBoxItemType::BUTTON || item.meType == ToolBoxItemType::SPACE ) + { + // add borders + tools::Long w = item.maItemSize.Width(); + tools::Long h = item.maItemSize.Height(); + ImplAddButtonBorder( w, h, mpData->mbNativeButtons ); + item.maItemSize.setWidth(w); + item.maItemSize.setHeight(h); + + if( item.meType == ToolBoxItemType::BUTTON ) + { + tools::Long nMinW = std::max(nMinWidth, item.maMinimalItemSize.Width()); + tools::Long nMinH = std::max(nMinHeight, item.maMinimalItemSize.Height()); + + tools::Long nGrowContentWidth = 0; + tools::Long nGrowContentHeight = 0; + + if( item.maItemSize.Width() < nMinW ) + { + nGrowContentWidth = nMinW - item.maItemSize.Width(); + item.maItemSize.setWidth( nMinW ); + } + if( item.maItemSize.Height() < nMinH ) + { + nGrowContentHeight = nMinH - item.maItemSize.Height(); + item.maItemSize.setHeight( nMinH ); + } + + // grow the content size by the additional available space + item.maContentSize.AdjustWidth(nGrowContentWidth ); + item.maContentSize.AdjustHeight(nGrowContentHeight ); + } + + // keep track of max item size + if ( item.maItemSize.Width() > nMaxWidth ) + nMaxWidth = item.maItemSize.Width(); + if ( item.maItemSize.Height() > nMaxHeight ) + nMaxHeight = item.maItemSize.Height(); + } + } + } + else + { + nMaxWidth = nDefWidth; + nMaxHeight = nDefHeight; + + ImplAddButtonBorder( nMaxWidth, nMaxHeight, mpData->mbNativeButtons ); + } + + if( !ImplIsFloatingMode() && GetToolboxButtonSize() != ToolBoxButtonSize::DontCare + && ( meTextPosition == ToolBoxTextPosition::Right ) ) + { + // make sure all vertical toolbars have the same width and horizontal have the same height + // this depends on the used button sizes + // as this is used for alignment of multiple toolbars + // it is only required for docked toolbars + + tools::Long nFixedWidth = nDefWidth+nDropDownArrowWidth; + tools::Long nFixedHeight = nDefHeight; + ImplAddButtonBorder( nFixedWidth, nFixedHeight, mpData->mbNativeButtons ); + + if( mbHorz ) + nMaxHeight = nFixedHeight; + else + nMaxWidth = nFixedWidth; + } + + mbCalc = false; + mbFormat = true; + + // do we have to recalc the sizes ? + if ( (nMaxWidth != mnMaxItemWidth) || (nMaxHeight != mnMaxItemHeight) ) + { + mnMaxItemWidth = nMaxWidth; + mnMaxItemHeight = nMaxHeight; + + return true; + } + else + return false; +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplCalcBreaks( tools::Long nWidth, sal_Int32* pMaxLineWidth, bool bCalcHorz ) const +{ + sal_uLong nLineStart = 0; + sal_uLong nGroupStart = 0; + tools::Long nLineWidth = 0; + tools::Long nCurWidth; + tools::Long nLastGroupLineWidth = 0; + tools::Long nMaxLineWidth = 0; + ImplToolItems::size_type nLines = 1; + bool bWindow; + bool bBreak = false; + tools::Long nWidthTotal = nWidth; + tools::Long nMenuWidth = 0; + + // when docked the menubutton will be in the first line + if( IsMenuEnabled() && !ImplIsFloatingMode() ) + nMenuWidth = mpData->maMenubuttonItem.maItemSize.Width(); + + // we need to know which item is the last visible one to be able to add + // the menu width in case we are unable to show all the items + ImplToolItems::iterator it, lastVisible; + for ( it = mpData->m_aItems.begin(); it != mpData->m_aItems.end(); ++it ) + { + if ( it->mbVisible ) + lastVisible = it; + } + + it = mpData->m_aItems.begin(); + while ( it != mpData->m_aItems.end() ) + { + it->mbBreak = bBreak; + bBreak = false; + + if ( it->mbVisible ) + { + bWindow = false; + bBreak = false; + nCurWidth = 0; + + if ( it->meType == ToolBoxItemType::BUTTON || it->meType == ToolBoxItemType::SPACE ) + { + if ( bCalcHorz ) + nCurWidth = it->maItemSize.Width(); + else + nCurWidth = it->maItemSize.Height(); + + if ( it->mpWindow && bCalcHorz ) + { + tools::Long nWinItemWidth = it->mpWindow->GetSizePixel().Width(); + if ( !mbScroll || (nWinItemWidth <= nWidthTotal) ) + { + nCurWidth = nWinItemWidth; + bWindow = true; + } + else + { + if ( it->mbEmptyBtn ) + { + nCurWidth = 0; + } + } + } + + // in case we are able to show all the items, we do not want + // to show the toolbar's menu; otherwise yes + if ( ( ( it == lastVisible ) && (nLineWidth+nCurWidth > nWidthTotal) && mbScroll ) || + ( ( it != lastVisible ) && (nLineWidth+nCurWidth+nMenuWidth > nWidthTotal) && mbScroll ) ) + bBreak = true; + } + else if ( it->meType == ToolBoxItemType::SEPARATOR ) + { + nCurWidth = it->mnSepSize; + if ( !ImplIsFloatingMode() && ( it != lastVisible ) && (nLineWidth+nCurWidth+nMenuWidth > nWidthTotal) ) + bBreak = true; + } + // treat breaks as separators, except when using old style toolbars (ie. no menu button) + else if ( (it->meType == ToolBoxItemType::BREAK) && !IsMenuEnabled() ) + bBreak = true; + + if ( bBreak ) + { + nLines++; + + // Add break before the entire group or take group apart? + if ( (it->meType == ToolBoxItemType::BREAK) || + (nLineStart == nGroupStart) ) + { + if ( nLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLineWidth; + + nLineWidth = 0; + nLineStart = it - mpData->m_aItems.begin(); + nGroupStart = nLineStart; + it->mbBreak = true; + bBreak = false; + } + else + { + if ( nLastGroupLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLastGroupLineWidth; + + // if the break is added before the group, set it to + // beginning of line and re-calculate + nLineWidth = 0; + nLineStart = nGroupStart; + it = mpData->m_aItems.begin() + nGroupStart; + continue; + } + } + else + { + if( ImplIsFloatingMode() || !IsMenuEnabled() ) // no group breaking when being docked single-line + { + if ( (it->meType != ToolBoxItemType::BUTTON) || bWindow ) + { + // found separator or break + nLastGroupLineWidth = nLineWidth; + nGroupStart = it - mpData->m_aItems.begin(); + if ( !bWindow ) + nGroupStart++; + } + } + } + + nLineWidth += nCurWidth; + } + + ++it; + } + + if ( pMaxLineWidth ) + { + if ( nLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLineWidth; + + if( ImplIsFloatingMode() && !ImplIsInPopupMode() ) + { + // leave enough space to display buttons in the decoration + tools::Long aMinWidth = 2 * GetSettings().GetStyleSettings().GetFloatTitleHeight(); + if( nMaxLineWidth < aMinWidth ) + nMaxLineWidth = aMinWidth; + } + *pMaxLineWidth = nMaxLineWidth; + } + + return nLines; +} + +Size ToolBox::ImplGetOptimalFloatingSize() +{ + if( !ImplIsFloatingMode() ) + return Size(); + + Size aCurrentSize( mnDX, mnDY ); + Size aSize1( aCurrentSize ); + Size aSize2( aCurrentSize ); + + // try to preserve current height + + // calc number of floating lines for current window height + ImplToolItems::size_type nFloatLinesHeight = ImplCalcLines( mnDY ); + // calc window size according to this number + aSize1 = ImplCalcFloatSize( nFloatLinesHeight ); + + if( aCurrentSize == aSize1 ) + return aSize1; + + // try to preserve current width + + tools::Long nLineHeight = std::max( mnWinHeight, mnMaxItemHeight ); + int nBorderX = 2*TB_BORDER_OFFSET1 + mnLeftBorder + mnRightBorder; + int nBorderY = 2*TB_BORDER_OFFSET2 + mnTopBorder + mnBottomBorder; + Size aSz( aCurrentSize ); + sal_Int32 maxX; + ImplToolItems::size_type nLines = ImplCalcBreaks( aSz.Width()-nBorderX, &maxX, mbHorz ); + + ImplToolItems::size_type manyLines = 1000; + Size aMinimalFloatSize = ImplCalcFloatSize( manyLines ); + + aSz.setHeight( nBorderY + nLineHeight * nLines ); + // line space when more than one line + if ( mbLineSpacing ) + aSz.AdjustHeight((nLines-1)*TB_LINESPACING ); + + aSz.setWidth( nBorderX + maxX ); + + // avoid clipping of any items + if( aSz.Width() < aMinimalFloatSize.Width() ) + aSize2 = ImplCalcFloatSize( nLines ); + else + aSize2 = aSz; + + if( aCurrentSize == aSize2 ) + return aSize2; + + // set the size with the smallest delta as the current size + tools::Long dx1 = std::abs( mnDX - aSize1.Width() ); + tools::Long dy1 = std::abs( mnDY - aSize1.Height() ); + + tools::Long dx2 = std::abs( mnDX - aSize2.Width() ); + tools::Long dy2 = std::abs( mnDY - aSize2.Height() ); + + if( dx1*dy1 < dx2*dy2 ) + aCurrentSize = aSize1; + else + aCurrentSize = aSize2; + + return aCurrentSize; +} + +namespace +{ +void lcl_hideDoubleSeparators( ToolBox::ImplToolItems& rItems ) +{ + bool bLastSep( true ); + ToolBox::ImplToolItems::iterator it; + for ( it = rItems.begin(); it != rItems.end(); ++it ) + { + if ( it->meType == ToolBoxItemType::SEPARATOR ) + { + it->mbVisible = false; + if ( !bLastSep ) + { + // check if any visible items have to appear behind it + if (std::any_of(it + 1, rItems.end(), [](const ImplToolItem& rItem) { + return (rItem.meType == ToolBoxItemType::BUTTON) && rItem.mbVisible; })) + it->mbVisible = true; + } + bLastSep = true; + } + else if ( it->mbVisible ) + bLastSep = false; + } +} +} + +void ToolBox::ImplFormat( bool bResize ) +{ + // Has to re-formatted + if ( !mbFormat ) + return; + + mpData->ImplClearLayoutData(); + + // recalculate positions and sizes + tools::Rectangle aEmptyRect; + tools::Long nLineSize; + tools::Long nLeft; + tools::Long nTop; + tools::Long nMax; // width of layoutarea in pixels + ImplToolItems::size_type nFormatLine; + bool bMustFullPaint; + + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + bool bIsInPopupMode = ImplIsInPopupMode(); + + maFloatSizes.clear(); + + // compute border sizes + ImplCalcBorder( meAlign, mnLeftBorder, mnTopBorder, mnRightBorder, mnBottomBorder ); + + // update drag area (where the 'grip' will be placed) + tools::Rectangle aOldDragRect; + if( pWrapper ) + aOldDragRect = pWrapper->GetDragArea(); + ImplUpdateDragArea(); + + bMustFullPaint = ImplCalcItem(); + + // calculate new size during interactive resize or + // set computed size when formatting only + if ( ImplIsFloatingMode() ) + { + if ( bResize ) + mnFloatLines = ImplCalcLines( mnDY ); + else + SetOutputSizePixel( ImplGetOptimalFloatingSize() ); + } + + // Horizontal + if ( mbHorz ) + { + tools::Long nBottom; + // nLineSize: height of a single line, will fit highest item + nLineSize = mnMaxItemHeight; + + if ( mnWinHeight > mnMaxItemHeight ) + nLineSize = mnWinHeight; + + if ( mbScroll ) + { + nMax = mnDX; + mnVisLines = ImplCalcLines( mnDY ); + } + else + { + // layout over all lines + mnVisLines = mnLines; + nMax = TB_MAXNOSCROLL; + } + + // add in all border offsets + if ( mnWinStyle & WB_BORDER ) + { + nLeft = TB_BORDER_OFFSET1 + mnLeftBorder; + nTop = TB_BORDER_OFFSET2 + mnTopBorder; + nBottom = TB_BORDER_OFFSET1 + mnBottomBorder; + nMax -= nLeft + TB_BORDER_OFFSET1 + mnRightBorder; + } + else + { + nLeft = 0; + nTop = 0; + nBottom = 0; + } + + // adjust linesize if docked in single-line mode (i.e. when using a clipped item menu) + // we have to center all items in the window height + if( IsMenuEnabled() && !ImplIsFloatingMode() ) + { + tools::Long nWinHeight = mnDY - nTop - nBottom; + if( nWinHeight > nLineSize ) + nLineSize = nWinHeight; + } + } + else + { + tools::Long nRight; + nLineSize = mnMaxItemWidth; + + if ( mbScroll ) + { + mnVisLines = ImplCalcLines( mnDX ); + nMax = mnDY; + } + else + { + mnVisLines = mnLines; + nMax = TB_MAXNOSCROLL; + } + + if ( mnWinStyle & WB_BORDER ) + { + nTop = TB_BORDER_OFFSET1 + mnTopBorder; + nLeft = TB_BORDER_OFFSET2 + mnLeftBorder; + nRight = TB_BORDER_OFFSET2 + mnRightBorder; + nMax -= nTop + TB_BORDER_OFFSET1 + mnBottomBorder; + } + else + { + nLeft = 0; + nTop = 0; + nRight = 0; + } + + // adjust linesize if docked in single-line mode (i.e. when using a clipped item menu) + // we have to center all items in the window height + if( !ImplIsFloatingMode() && IsMenuEnabled() ) + { + tools::Long nWinWidth = mnDX - nLeft - nRight; + if( nWinWidth > nLineSize ) + nLineSize = nWinWidth; + } + } + + // no calculation if the window has no size (nMax=0) + // non scrolling toolboxes must be computed though + if ( (nMax <= 0) && mbScroll ) + { + mnVisLines = 1; + mnCurLine = 1; + mnCurLines = 1; + + for (auto & item : mpData->m_aItems) + { + item.maRect = aEmptyRect; + } + + maLowerRect = aEmptyRect; + maUpperRect = aEmptyRect; + } + else + { + // init start values + tools::Long nX = nLeft; // top-left offset + tools::Long nY = nTop; + nFormatLine = 1; + + // save old scroll rectangles and reset them + tools::Rectangle aOldLowerRect = maLowerRect; + tools::Rectangle aOldUpperRect = maUpperRect; + tools::Rectangle aOldMenubuttonRect = mpData->maMenubuttonItem.maRect; + maUpperRect = aEmptyRect; + maLowerRect = aEmptyRect; + mpData->maMenubuttonItem.maRect = aEmptyRect; + + // do we have any toolbox items at all ? + if ( !mpData->m_aItems.empty() || IsMenuEnabled() ) + { + lcl_hideDoubleSeparators( mpData->m_aItems ); + + // compute line breaks and visible lines give the current window width (nMax) + // the break indicators will be stored within each item (it->mbBreak) + mnCurLines = ImplCalcBreaks( nMax, nullptr, mbHorz ); + + // check for scrollbar buttons or dropdown menu + // (if a menu is enabled, this will be used to store clipped + // items and no scroll buttons will appear) + if ( (!ImplIsFloatingMode() && (mnCurLines > mnVisLines) && mbScroll ) || + IsMenuEnabled() ) + { + // compute linebreaks again, incorporating scrollbar buttons + if( !IsMenuEnabled() ) + { + nMax -= TB_SPIN_SIZE+TB_SPIN_OFFSET; + mnCurLines = ImplCalcBreaks( nMax, nullptr, mbHorz ); + } + + // compute scroll rectangles or menu button + if ( mbHorz ) + { + if( IsMenuEnabled() && !ImplHasExternalMenubutton() && !bIsInPopupMode ) + { + if( !ImplIsFloatingMode() ) + { + mpData->maMenubuttonItem.maRect.SetRight( mnDX - 2 ); + mpData->maMenubuttonItem.maRect.SetTop( nTop ); + mpData->maMenubuttonItem.maRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 ); + } + else + { + mpData->maMenubuttonItem.maRect.SetRight( mnDX - mnRightBorder-TB_BORDER_OFFSET1-1 ); + mpData->maMenubuttonItem.maRect.SetTop( nTop ); + mpData->maMenubuttonItem.maRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 ); + } + mpData->maMenubuttonItem.maRect.SetLeft( mpData->maMenubuttonItem.maRect.Right() - mpData->mnMenuButtonWidth ); + } + else + { + maUpperRect.SetLeft( nLeft+nMax+TB_SPIN_OFFSET ); + maUpperRect.SetRight( maUpperRect.Left()+TB_SPIN_SIZE-1 ); + maUpperRect.SetTop( nTop ); + maLowerRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 ); + maLowerRect.SetLeft( maUpperRect.Left() ); + maLowerRect.SetRight( maUpperRect.Right() ); + maUpperRect.SetBottom( maUpperRect.Top() + + (maLowerRect.Bottom()-maUpperRect.Top())/2 ); + maLowerRect.SetTop( maUpperRect.Bottom() ); + } + } + else + { + if( IsMenuEnabled() && !ImplHasExternalMenubutton() && !bIsInPopupMode ) + { + if( !ImplIsFloatingMode() ) + { + mpData->maMenubuttonItem.maRect.SetBottom( mnDY - 2 ); + mpData->maMenubuttonItem.maRect.SetLeft( nLeft ); + mpData->maMenubuttonItem.maRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 ); + } + else + { + mpData->maMenubuttonItem.maRect.SetBottom( mnDY - mnBottomBorder-TB_BORDER_OFFSET1-1 ); + mpData->maMenubuttonItem.maRect.SetLeft( nLeft ); + mpData->maMenubuttonItem.maRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 ); + } + mpData->maMenubuttonItem.maRect.SetTop( mpData->maMenubuttonItem.maRect.Bottom() - mpData->mnMenuButtonWidth ); + } + else + { + maUpperRect.SetTop( nTop+nMax+TB_SPIN_OFFSET ); + maUpperRect.SetBottom( maUpperRect.Top()+TB_SPIN_SIZE-1 ); + maUpperRect.SetLeft( nLeft ); + maLowerRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 ); + maLowerRect.SetTop( maUpperRect.Top() ); + maLowerRect.SetBottom( maUpperRect.Bottom() ); + maUpperRect.SetRight( maUpperRect.Left() + + (maLowerRect.Right()-maUpperRect.Left())/2 ); + maLowerRect.SetLeft( maUpperRect.Right() ); + } + } + } + + // no scrolling when there is a "more"-menu + // anything will "fit" in a single line then + if( IsMenuEnabled() ) + mnCurLines = 1; + + // determine the currently visible line + if ( mnVisLines >= mnCurLines ) + mnCurLine = 1; + else if ( mnCurLine+mnVisLines-1 > mnCurLines ) + mnCurLine = mnCurLines - (mnVisLines-1); + + tools::Long firstItemCenter = 0; + for (auto & item : mpData->m_aItems) + { + item.mbShowWindow = false; + + // check for line break and advance nX/nY accordingly + if ( item.mbBreak ) + { + nFormatLine++; + + // increment starting with the second line + if ( nFormatLine > mnCurLine ) + { + if ( mbHorz ) + { + nX = nLeft; + if ( mbLineSpacing ) + nY += nLineSize+TB_LINESPACING; + else + nY += nLineSize; + } + else + { + nY = nTop; + if ( mbLineSpacing ) + nX += nLineSize+TB_LINESPACING; + else + nX += nLineSize; + } + } + } + + if ( !item.mbVisible || (nFormatLine < mnCurLine) || + (nFormatLine > mnCurLine+mnVisLines-1) ) + // item is not visible + item.maCalcRect = aEmptyRect; + else + { + // 1. determine current item width/height + // take window size and orientation into account, because this affects the size of item windows + + Size aCurrentItemSize( item.GetSize( mbHorz, mbScroll, nMax, Size(mnMaxItemWidth, mnMaxItemHeight) ) ); + + // 2. position item rect and use size from step 1 + // items will be centered horizontally (if mbHorz) or vertically + // advance nX and nY accordingly + + if ( mbHorz ) + { + // In special mode Locked horizontal positions of all items remain unchanged. + + if ( mbIsArranged && meLayoutMode == ToolBoxLayoutMode::Locked && mnLines == 1 && item.maRect.Left() > 0 ) + nX = item.maRect.Left(); + item.maCalcRect.SetLeft( nX ); + + // In special mode Locked first item's vertical position remains unchanged. Consecutive items vertical + // positions are centered around first item's vertical position. If an item's height exceeds available + // space, item's vertical position remains unchanged too. + + if ( mbIsArranged && meLayoutMode == ToolBoxLayoutMode::Locked && mnLines == 1 ) + if ( firstItemCenter > 0 ) + if ( firstItemCenter-aCurrentItemSize.Height()/2 > nY ) + item.maCalcRect.SetTop( firstItemCenter-aCurrentItemSize.Height()/2 ); + else + item.maCalcRect.SetTop( item.maRect.Top() ); + else + { + item.maCalcRect.SetTop( item.maRect.Top() ); + firstItemCenter = item.maRect.Top()+aCurrentItemSize.Height()/2; + } + else + item.maCalcRect.SetTop( nY+(nLineSize-aCurrentItemSize.Height())/2 ); + item.maCalcRect.SetRight( nX+aCurrentItemSize.Width()-1 ); + item.maCalcRect.SetBottom( item.maCalcRect.Top()+aCurrentItemSize.Height()-1 ); + nX += aCurrentItemSize.Width(); + } + else + { + item.maCalcRect.SetLeft( nX+(nLineSize-aCurrentItemSize.Width())/2 ); + item.maCalcRect.SetTop( nY ); + item.maCalcRect.SetRight( item.maCalcRect.Left()+aCurrentItemSize.Width()-1 ); + item.maCalcRect.SetBottom( nY+aCurrentItemSize.Height()-1 ); + nY += aCurrentItemSize.Height(); + } + } + + // position window items into calculated item rect + if ( item.mpWindow ) + { + if ( item.mbShowWindow ) + { + Point aPos( item.maCalcRect.Left(), item.maCalcRect.Top() ); + + assert( item.maCalcRect.Top() >= 0 ); + + item.mpWindow->SetPosPixel( aPos ); + item.mpWindow->Show(); + } + else + item.mpWindow->Hide(); + } + } // end of loop over all items + mbIsArranged = true; + } + else + // we have no toolbox items + mnCurLines = 1; + + if( IsMenuEnabled() && ImplIsFloatingMode() && !ImplHasExternalMenubutton() && !bIsInPopupMode ) + { + // custom menu will be the last button in floating mode + ImplToolItem &rIt = mpData->maMenubuttonItem; + + if ( mbHorz ) + { + rIt.maRect.SetLeft( nX+TB_MENUBUTTON_OFFSET ); + rIt.maRect.SetTop( nY ); + rIt.maRect.SetRight( rIt.maRect.Left() + mpData->mnMenuButtonWidth ); + rIt.maRect.SetBottom( nY+nLineSize-1 ); + nX += rIt.maItemSize.Width(); + } + else + { + rIt.maRect.SetLeft( nX ); + rIt.maRect.SetTop( nY+TB_MENUBUTTON_OFFSET ); + rIt.maRect.SetRight( nX+nLineSize-1 ); + rIt.maRect.SetBottom( rIt.maRect.Top() + mpData->mnMenuButtonWidth ); + nY += rIt.maItemSize.Height(); + } + } + + // if toolbox visible trigger paint for changed regions + if ( IsVisible() && !mbFullPaint ) + { + if ( bMustFullPaint ) + { + maPaintRect = tools::Rectangle( mnLeftBorder, mnTopBorder, + mnDX-mnRightBorder, mnDY-mnBottomBorder ); + } + else + { + if ( aOldLowerRect != maLowerRect ) + { + maPaintRect.Union( maLowerRect ); + maPaintRect.Union( aOldLowerRect ); + } + if ( aOldUpperRect != maUpperRect ) + { + maPaintRect.Union( maUpperRect ); + maPaintRect.Union( aOldUpperRect ); + } + if ( aOldMenubuttonRect != mpData->maMenubuttonItem.maRect ) + { + maPaintRect.Union( mpData->maMenubuttonItem.maRect ); + maPaintRect.Union( aOldMenubuttonRect ); + } + if ( pWrapper && aOldDragRect != pWrapper->GetDragArea() ) + { + maPaintRect.Union( pWrapper->GetDragArea() ); + maPaintRect.Union( aOldDragRect ); + } + + for (auto const& item : mpData->m_aItems) + { + if ( item.maRect != item.maCalcRect ) + { + maPaintRect.Union( item.maRect ); + maPaintRect.Union( item.maCalcRect ); + } + } + } + + Invalidate( maPaintRect ); + } + + // store the new calculated item rects + maPaintRect = aEmptyRect; + for (auto & item : mpData->m_aItems) + item.maRect = item.maCalcRect; + } + + // indicate formatting is done + mbFormat = false; +} + +IMPL_LINK_NOARG(ToolBox, ImplDropdownLongClickHdl, Timer *, void) +{ + if (mnCurPos == ITEM_NOTFOUND || + !(mpData->m_aItems[ mnCurPos ].mnBits & ToolBoxItemBits::DROPDOWN)) + return; + + mpData->mbDropDownByKeyboard = false; + mpData->maDropdownClickHdl.Call( this ); + + // do not reset data if the dropdown handler opened a floating window + // see ImplFloatControl() + if( !mpFloatWin ) + { + // no floater was opened + Deactivate(); + InvalidateItem(mnCurPos); + + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; + mnHighItemId = ToolBoxItemId(0); + } +} + +IMPL_LINK_NOARG(ToolBox, ImplUpdateHdl, Timer *, void) +{ + + if( mbFormat && mpData ) + ImplFormat(); +} + +static void ImplDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + const Image pImage(StockImage::Yes, CHEVRON); + Size aImageSize = pImage.GetSizePixel(); + tools::Long x = rRect.Left() + (rRect.getWidth() - aImageSize.Width())/2; + tools::Long y = rRect.Top() + (rRect.getHeight() - aImageSize.Height())/2; + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + + rRenderContext.DrawImage(Point(x,y), pImage, nImageStyle); +} + +static void ImplDrawDropdownArrow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rDropDownRect, bool bSetColor, bool bRotate ) +{ + bool bLineColor = rRenderContext.IsLineColor(); + bool bFillColor = rRenderContext.IsFillColor(); + Color aOldFillColor = rRenderContext.GetFillColor(); + Color aOldLineColor = rRenderContext.GetLineColor(); + rRenderContext.SetLineColor(); + + if ( bSetColor ) + { + if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark()) + rRenderContext.SetFillColor(COL_WHITE); + else + rRenderContext.SetFillColor(COL_BLACK); + } + + tools::Polygon aPoly(4); + + // the assumption is, that the width always specifies the size of the expected arrow. + const tools::Long nMargin = round(2 * rRenderContext.GetDPIScaleFactor()); + const tools::Long nSize = rDropDownRect.getWidth() - 2 * nMargin; + const tools::Long nHalfSize = (nSize + 1) / 2; + const tools::Long x = rDropDownRect.Left() + nMargin + (bRotate ? (rDropDownRect.getWidth() - nHalfSize) / 2 : 0); + const tools::Long y = rDropDownRect.Top() + nMargin + (rDropDownRect.getHeight() - (bRotate ? nSize : nHalfSize)) / 2; + + aPoly.SetPoint(Point(x, y), 0); + if (bRotate) // > + { + aPoly.SetPoint(Point(x, y + nSize), 1); + aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 2); + } + else // v + { + aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 1); + aPoly.SetPoint(Point(x + nSize, y), 2); + } + aPoly.SetPoint(Point(x, y), 3); + + auto aaflags = rRenderContext.GetAntialiasing(); + rRenderContext.SetAntialiasing(AntialiasingFlags::Enable); + rRenderContext.DrawPolygon( aPoly ); + rRenderContext.SetAntialiasing(aaflags); + + if( bFillColor ) + rRenderContext.SetFillColor(aOldFillColor); + else + rRenderContext.SetFillColor(); + if( bLineColor ) + rRenderContext.SetLineColor(aOldLineColor); + else + rRenderContext.SetLineColor(); +} + +void ToolBox::ImplDrawMenuButton(vcl::RenderContext& rRenderContext, bool bHighlight) +{ + if (mpData->maMenubuttonItem.maRect.IsEmpty()) + return; + + // #i53937# paint menu button only if necessary + if (!ImplHasClippedItems()) + return; + + // execute pending paint requests + ImplCheckUpdate(); + + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + + // draw the 'more' indicator / button (>>) + ImplErase(rRenderContext, mpData->maMenubuttonItem.maRect, bHighlight); + + if (bHighlight) + ImplDrawButton(rRenderContext, mpData->maMenubuttonItem.maRect, 2, false, true, false ); + + if (ImplHasClippedItems()) + ImplDrawMoreIndicator(rRenderContext, mpData->maMenubuttonItem.maRect); + + // store highlight state + mpData->mbMenubuttonSelected = bHighlight; + + // restore colors + rRenderContext.Pop(); +} + +void ToolBox::ImplDrawSpin(vcl::RenderContext& rRenderContext) +{ + bool bTmpUpper; + bool bTmpLower; + + if ( maUpperRect.IsEmpty() || maLowerRect.IsEmpty() ) + return; + + bTmpUpper = mnCurLine > 1; + + bTmpLower = mnCurLine+mnVisLines-1 < mnCurLines; + + if ( !IsEnabled() ) + { + bTmpUpper = false; + bTmpLower = false; + } + + ImplDrawUpDownButtons(rRenderContext, maUpperRect, maLowerRect, + false/*bUpperIn*/, false/*bLowerIn*/, bTmpUpper, bTmpLower, !mbHorz); +} + +void ToolBox::ImplDrawSeparator(vcl::RenderContext& rRenderContext, ImplToolItems::size_type nPos, const tools::Rectangle& rRect) +{ + if ( nPos >= mpData->m_aItems.size() - 1 ) + // no separator if it's the last item + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + ImplToolItem* pPreviousItem = &mpData->m_aItems[nPos-1]; + ImplToolItem* pNextItem = &mpData->m_aItems[nPos+1]; + + if ( ( pPreviousItem->mbShowWindow && pNextItem->mbShowWindow ) || pNextItem->mbBreak ) + // no separator between two windows or before a break + return; + + bool bNativeOk = false; + ControlPart nPart = IsHorizontal() ? ControlPart::SeparatorVert : ControlPart::SeparatorHorz; + if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, nPart)) + { + ImplControlValue aControlValue; + bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, nPart, rRect, ControlState::NONE, aControlValue, OUString()); + } + + /* Draw the widget only if it can't be drawn natively. */ + if (bNativeOk) + return; + + tools::Long nCenterPos, nSlim; + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(rStyleSettings.GetSeparatorColor()); + if (IsHorizontal()) + { + nSlim = (pItem->maRect.Bottom() - pItem->maRect.Top ()) / 4; + nCenterPos = pItem->maRect.Center().X(); + rRenderContext.DrawLine(Point(nCenterPos, pItem->maRect.Top() + nSlim), + Point(nCenterPos, pItem->maRect.Bottom() - nSlim)); + } + else + { + nSlim = (pItem->maRect.Right() - pItem->maRect.Left ()) / 4; + nCenterPos = pItem->maRect.Center().Y(); + rRenderContext.DrawLine(Point(pItem->maRect.Left() + nSlim, nCenterPos), + Point(pItem->maRect.Right() - nSlim, nCenterPos)); + } +} + +void ToolBox::ImplDrawButton(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect, sal_uInt16 highlight, + bool bChecked, bool bEnabled, bool bIsWindow ) +{ + // draws toolbar button background either native or using a coloured selection + // if bIsWindow is true, the corresponding item is a control and only a selection border will be drawn + + bool bNativeOk = false; + if( !bIsWindow && rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ) ) + { + ImplControlValue aControlValue; + ControlState nState = ControlState::NONE; + + if ( highlight == 1 ) nState |= ControlState::PRESSED; + if ( highlight == 2 ) nState |= ControlState::ROLLOVER; + if ( bEnabled ) nState |= ControlState::ENABLED; + + aControlValue.setTristateVal( bChecked ? ButtonValue::On : ButtonValue::Off ); + + bNativeOk = rRenderContext.DrawNativeControl( ControlType::Toolbar, ControlPart::Button, + rRect, nState, aControlValue, OUString() ); + } + + if (!bNativeOk) + vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, rRect, bIsWindow ? 3 : highlight, + bChecked, true, bIsWindow, nullptr, 2); +} + +void ToolBox::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplToolItems::size_type nPos, sal_uInt16 nHighlight) +{ + if (nPos >= mpData->m_aItems.size()) + return; + + // execute pending paint requests + ImplCheckUpdate(); + + rRenderContext.SetFillColor(); + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + + if (!pItem->mbEnabled) + nHighlight = 0; + + // if the rectangle is outside visible area + if (pItem->maRect.IsEmpty()) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + // no gradient background for items that have a popup open + bool bHasOpenPopup = mpFloatWin && (mnDownItemId==pItem->mnId); + + bool bHighContrastWhite = false; + // check the face color as highcontrast indicator + // because the toolbox itself might have a gradient + if (rStyleSettings.GetFaceColor() == COL_WHITE) + bHighContrastWhite = true; + + // Compute buttons area. + Size aBtnSize = pItem->maRect.GetSize(); + + /* Compute the button/separator rectangle here, we'll need it for + * both the buttons and the separators. */ + tools::Rectangle aButtonRect( pItem->maRect.TopLeft(), aBtnSize ); + tools::Long nOffX = SMALLBUTTON_OFF_NORMAL_X; + tools::Long nOffY = SMALLBUTTON_OFF_NORMAL_Y; + tools::Long nImageOffX = 0; + tools::Long nImageOffY = 0; + DrawButtonFlags nStyle = DrawButtonFlags::NONE; + + // draw separators + if ( (pItem->meType == ToolBoxItemType::SEPARATOR) && nPos > 0 ) + { + ImplDrawSeparator(rRenderContext, nPos, aButtonRect); + } + + // do nothing if item is no button or will be displayed as window + if ( (pItem->meType != ToolBoxItemType::BUTTON) || pItem->mbShowWindow ) + return; + + if ( pItem->meState == TRISTATE_TRUE ) + { + nStyle |= DrawButtonFlags::Checked; + } + else if ( pItem->meState == TRISTATE_INDET ) + { + nStyle |= DrawButtonFlags::DontKnow; + } + if ( nHighlight == 1 ) + { + nStyle |= DrawButtonFlags::Pressed; + } + + ImplErase(rRenderContext, pItem->maRect, nHighlight != 0, bHasOpenPopup ); + + nOffX += pItem->maRect.Left(); + nOffY += pItem->maRect.Top(); + + // determine what has to be drawn on the button: image, text or both + bool bImage; + bool bText; + ButtonType tmpButtonType = determineButtonType( pItem, meButtonType ); // default to toolbox setting + pItem->DetermineButtonDrawStyle( tmpButtonType, bImage, bText ); + + // compute output values + tools::Long nBtnWidth = aBtnSize.Width()-SMALLBUTTON_HSIZE; + tools::Long nBtnHeight = aBtnSize.Height()-SMALLBUTTON_VSIZE; + Size aImageSize; + + const bool bDropDown = (pItem->mnBits & ToolBoxItemBits::DROPDOWN) == ToolBoxItemBits::DROPDOWN; + tools::Rectangle aDropDownRect; + if (bDropDown) + aDropDownRect = pItem->GetDropDownRect(mbHorz); + + if ( bImage ) + { + const Image* pImage = &(pItem->maImage); + aImageSize = pImage->GetSizePixel(); + + // determine drawing flags + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + + if ( !pItem->mbEnabled || !IsEnabled() ) + nImageStyle |= DrawImageFlags::Disable; + + // #i35563# the dontknow state indicates different states at the same time + // which should not be rendered disabled but normal + + // draw the image + nImageOffX = nOffX; + nImageOffY = nOffY; + if ( ( (pItem->mnBits & (ToolBoxItemBits::LEFT|ToolBoxItemBits::DROPDOWN)) || bText ) + && ( meTextPosition == ToolBoxTextPosition::Right ) ) + { + // left align also to leave space for drop down arrow + // and when drawing text+image + // just center in y, except for vertical (ie rotated text) + if( mbHorz || !bText ) + nImageOffY += (nBtnHeight-aImageSize.Height())/2; + } + else + { + nImageOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aImageSize.Width())/2; + if ( meTextPosition == ToolBoxTextPosition::Right ) + nImageOffY += (nBtnHeight-aImageSize.Height())/2; + } + if ( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) + { + if( bHasOpenPopup ) + ImplDrawFloatwinBorder(rRenderContext, pItem); + else + ImplDrawButton(rRenderContext, aButtonRect, nHighlight, pItem->meState == TRISTATE_TRUE, + pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow); + + if( nHighlight != 0 ) + { + if( bHighContrastWhite ) + nImageStyle |= DrawImageFlags::ColorTransform; + } + } + rRenderContext.DrawImage(Point( nImageOffX, nImageOffY ), *pImage, nImageStyle); + } + + // draw the text + bool bRotate = false; + if ( bText ) + { + const Size aTxtSize(GetOutDev()->GetCtrlTextWidth(pItem->maText), GetTextHeight()); + tools::Long nTextOffX = nOffX; + tools::Long nTextOffY = nOffY; + + // rotate text when vertically docked + vcl::Font aOldFont = rRenderContext.GetFont(); + if( pItem->mbVisibleText && !ImplIsFloatingMode() && + ((meAlign == WindowAlign::Left) || (meAlign == WindowAlign::Right)) ) + { + bRotate = true; + + vcl::Font aRotateFont = aOldFont; + aRotateFont.SetOrientation( 2700_deg10 ); + + // center horizontally + nTextOffX += aTxtSize.Height(); + nTextOffX += (nBtnWidth-aTxtSize.Height())/2; + + // add in image offset + if( bImage ) + nTextOffY = nImageOffY + aImageSize.Height() + TB_IMAGETEXTOFFSET; + + rRenderContext.SetFont(aRotateFont); + } + else + { + if ( meTextPosition == ToolBoxTextPosition::Right ) + { + // center vertically + nTextOffY += (nBtnHeight-aTxtSize.Height())/2; + + // add in image offset + if( bImage ) + nTextOffX = nImageOffX + aImageSize.Width() + TB_IMAGETEXTOFFSET; + } + else + { + // center horizontally + nTextOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aTxtSize.Width() - TB_IMAGETEXTOFFSET)/2; + // set vertical position + nTextOffY += nBtnHeight - aTxtSize.Height(); + } + } + + // draw selection only if not already drawn during image output (see above) + if ( !bImage && (nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) ) + { + if( bHasOpenPopup ) + ImplDrawFloatwinBorder(rRenderContext, pItem); + else + ImplDrawButton(rRenderContext, pItem->maRect, nHighlight, pItem->meState == TRISTATE_TRUE, + pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow ); + } + + DrawTextFlags nTextStyle = DrawTextFlags::NONE; + if ( !pItem->mbEnabled ) + nTextStyle |= DrawTextFlags::Disable; + rRenderContext.DrawCtrlText( Point( nTextOffX, nTextOffY ), pItem->maText, + 0, pItem->maText.getLength(), nTextStyle ); + if ( bRotate ) + SetFont( aOldFont ); + } + + // paint optional drop down arrow + if (!bDropDown) + return; + + bool bSetColor = true; + if ( !pItem->mbEnabled || !IsEnabled() ) + { + bSetColor = false; + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + } + + // dropdown only will be painted without inner border + if( (pItem->mnBits & ToolBoxItemBits::DROPDOWNONLY) != ToolBoxItemBits::DROPDOWNONLY ) + { + ImplErase(rRenderContext, aDropDownRect, nHighlight != 0, bHasOpenPopup); + + if( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) + { + if( bHasOpenPopup ) + ImplDrawFloatwinBorder(rRenderContext, pItem); + else + ImplDrawButton(rRenderContext, aDropDownRect, nHighlight, pItem->meState == TRISTATE_TRUE, + pItem->mbEnabled && IsEnabled(), false); + } + } + ImplDrawDropdownArrow(rRenderContext, aDropDownRect, bSetColor, bRotate); +} + +void ToolBox::ImplDrawFloatwinBorder(vcl::RenderContext& rRenderContext, ImplToolItem const * pItem) +{ + if ( pItem->maRect.IsEmpty() ) + return; + + tools::Rectangle aRect( mpFloatWin->ImplGetItemEdgeClipRect() ); + aRect.SetPos( AbsoluteScreenToOutputPixel( aRect.TopLeft() ) ); + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor()); + Point p1, p2; + + p1 = pItem->maRect.TopLeft(); + p1.AdjustX( 1 ); + p2 = pItem->maRect.TopRight(); + p2.AdjustX( -1 ); + rRenderContext.DrawLine( p1, p2); + p1 = pItem->maRect.BottomLeft(); + p1.AdjustX( 1 ); + p2 = pItem->maRect.BottomRight(); + p2.AdjustX( -1 ); + rRenderContext.DrawLine( p1, p2); + + p1 = pItem->maRect.TopLeft(); + p1.AdjustY( 1 ); + p2 = pItem->maRect.BottomLeft(); + p2.AdjustY( -1 ); + rRenderContext.DrawLine( p1, p2); + p1 = pItem->maRect.TopRight(); + p1.AdjustY( 1 ); + p2 = pItem->maRect.BottomRight(); + p2.AdjustY( -1 ); + rRenderContext.DrawLine( p1, p2); + +} + +void ToolBox::ImplFloatControl( bool bStart, FloatingWindow* pFloatWindow ) +{ + + if ( bStart ) + { + mpFloatWin = pFloatWindow; + + // redraw item, to trigger drawing of a special border + InvalidateItem(mnCurPos); + + mbDrag = false; + EndTracking(); + if (IsMouseCaptured()) + ReleaseMouse(); + } + else + { + mpFloatWin = nullptr; + + // if focus is still in this toolbox, then the floater was opened by keyboard + // draw current item with highlight and keep old state + bool bWasKeyboardActivate = mpData->mbDropDownByKeyboard; + + if ( mnCurPos != ITEM_NOTFOUND ) + InvalidateItem(mnCurPos); + Deactivate(); + + if( !bWasKeyboardActivate ) + { + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnHighItemId = ToolBoxItemId(0); + } + mnDownItemId = ToolBoxItemId(0); + + } +} + +void ToolBox::ShowLine( bool bNext ) +{ + mbFormat = true; + + if ( bNext ) + mnCurLine++; + else + mnCurLine--; + + ImplFormat(); +} + +bool ToolBox::ImplHandleMouseMove( const MouseEvent& rMEvt, bool bRepeat ) +{ + Point aMousePos = rMEvt.GetPosPixel(); + + if ( !mpData ) + return false; + + // ToolBox active? + if ( mbDrag && mnCurPos != ITEM_NOTFOUND ) + { + // is the cursor over the item? + ImplToolItem* pItem = &mpData->m_aItems[mnCurPos]; + if ( pItem->maRect.Contains( aMousePos ) ) + { + if ( !mnCurItemId ) + { + InvalidateItem(mnCurPos); + mnCurItemId = pItem->mnId; + Highlight(); + } + + if ( (pItem->mnBits & ToolBoxItemBits::REPEAT) && bRepeat ) + Select(); + } + else + { + if ( mnCurItemId ) + { + InvalidateItem(mnCurPos); + mnCurItemId = ToolBoxItemId(0); + InvalidateItem(mnCurPos); + Highlight(); + } + } + + return true; + } + + if ( mbUpper ) + { + bool bNewIn = maUpperRect.Contains( aMousePos ); + if ( bNewIn != mbIn ) + { + mbIn = bNewIn; + InvalidateSpin(true, false); + } + return true; + } + + if ( mbLower ) + { + bool bNewIn = maLowerRect.Contains( aMousePos ); + if ( bNewIn != mbIn ) + { + mbIn = bNewIn; + InvalidateSpin(false); + } + return true; + } + + return false; +} + +bool ToolBox::ImplHandleMouseButtonUp( const MouseEvent& rMEvt, bool bCancel ) +{ + if ( !mpData ) + return false; + + // stop eventual running dropdown timer + if( mnCurPos < mpData->m_aItems.size() && + (mpData->m_aItems[mnCurPos].mnBits & ToolBoxItemBits::DROPDOWN ) ) + { + mpData->maDropdownTimer.Stop(); + } + + if ( mbDrag ) + { + Deactivate(); + + if ( mbDrag ) + mbDrag = false; + else + { + if ( mnCurPos == ITEM_NOTFOUND ) + return true; + } + + // has mouse been released on top of item? + if( mnCurPos < mpData->m_aItems.size() ) + { + ImplToolItem* pItem = &mpData->m_aItems[mnCurPos]; + if ( pItem->maRect.Contains( rMEvt.GetPosPixel() ) ) + { + mnCurItemId = pItem->mnId; + if ( !bCancel ) + { + // execute AutoCheck if required + if ( pItem->mnBits & ToolBoxItemBits::AUTOCHECK ) + { + if ( pItem->mnBits & ToolBoxItemBits::RADIOCHECK ) + { + if ( pItem->meState != TRISTATE_TRUE ) + SetItemState( pItem->mnId, TRISTATE_TRUE ); + } + else + { + if ( pItem->meState != TRISTATE_TRUE ) + pItem->meState = TRISTATE_TRUE; + else + pItem->meState = TRISTATE_FALSE; + } + } + + // do not call Select when Repeat is active, as in this + // case that was triggered already in MouseButtonDown + if ( !(pItem->mnBits & ToolBoxItemBits::REPEAT) ) + { + // prevent from being destroyed in the select handler + VclPtr<vcl::Window> xWindow = this; + Select(); + if ( xWindow->isDisposed() ) + return true; + } + } + + { + } + + // Items not destroyed, in Select handler + if ( mnCurItemId ) + { + // Get current pos for the case that items are inserted/removed + // in the toolBox + mnCurPos = GetItemPos( mnCurItemId ); + if ( mnCurPos != ITEM_NOTFOUND ) + { + InvalidateItem(mnCurPos); + GetOutDev()->Flush(); + } + } + } + } + + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; + return true; + } + else if ( mbUpper || mbLower ) + { + if ( mbIn ) + ShowLine( !mbUpper ); + mbUpper = false; + mbLower = false; + mbIn = false; + InvalidateSpin(); + return true; + } + + return false; +} + +void ToolBox::MouseMove( const MouseEvent& rMEvt ) +{ + // pressing a modifier generates synthetic mouse moves + // ignore it if keyboard selection is active + if( HasFocus() && ( rMEvt.GetMode() & MouseEventModifiers::MODIFIERCHANGED ) ) + return; + + if ( ImplHandleMouseMove( rMEvt ) ) + return; + + Point aMousePos = rMEvt.GetPosPixel(); + + // only highlight when the focus is not inside a child window of a toolbox + // eg, in an edit control + // and do not highlight when focus is in a different toolbox + bool bDrawHotSpot = true; + vcl::Window *pFocusWin = Application::GetFocusWindow(); + + bool bFocusWindowIsAToolBoxChild = false; + if (pFocusWin) + { + vcl::Window *pWin = pFocusWin->GetParent(); + while (pWin) + { + if(pWin->ImplGetWindowImpl() && pWin->ImplGetWindowImpl()->mbToolBox) + { + bFocusWindowIsAToolBoxChild = true; + break; + } + pWin = pWin->GetParent(); + } + } + + if( bFocusWindowIsAToolBoxChild || (pFocusWin && pFocusWin->ImplGetWindowImpl() && pFocusWin->ImplGetWindowImpl()->mbToolBox && pFocusWin != this) ) + bDrawHotSpot = false; + + if ( mbDragging ) + { + ImplTBDragMgr* pMgr = ImplGetTBDragMgr(); + pMgr->Dragging( aMousePos ); + return; + } + + PointerStyle eStyle = PointerStyle::Arrow; + + // change mouse cursor over drag area + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper && pWrapper->GetDragArea().Contains( rMEvt.GetPosPixel() ) ) + eStyle = PointerStyle::Move; + + if ( (mnWinStyle & TB_WBLINESIZING) == TB_WBLINESIZING ) + { + if ( rMEvt.GetMode() & MouseEventModifiers::SIMPLEMOVE ) + { + sal_uInt16 nLinePtr = ImplTestLineSize( rMEvt.GetPosPixel() ); + if ( nLinePtr & DOCK_LINEHSIZE ) + { + if ( meAlign == WindowAlign::Left ) + eStyle = PointerStyle::WindowESize; + else + eStyle = PointerStyle::WindowWSize; + } + else if ( nLinePtr & DOCK_LINEVSIZE ) + { + if ( meAlign == WindowAlign::Top ) + eStyle = PointerStyle::WindowSSize; + else + eStyle = PointerStyle::WindowNSize; + } + } + } + + if ( bDrawHotSpot ) + { + bool bClearHigh = true; + if ( !rMEvt.IsLeaveWindow() && (mnCurPos == ITEM_NOTFOUND) ) + { + ImplToolItems::size_type nTempPos = 0; + for (auto const& item : mpData->m_aItems) + { + if ( item.maRect.Contains( aMousePos ) ) + { + if ( (item.meType == ToolBoxItemType::BUTTON) && item.mbEnabled ) + { + bClearHigh = false; + if ( mnHighItemId != item.mnId ) + { + if ( mnHighItemId ) + { + ImplHideFocus(); + ImplToolItems::size_type nPos = GetItemPos( mnHighItemId ); + InvalidateItem(nPos); + CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nPos ) ); + } + if ( mpData->mbMenubuttonSelected ) + { + // remove highlight from menubutton + InvalidateMenuButton(); + } + mnHighItemId = item.mnId; + InvalidateItem(nTempPos); + ImplShowFocus(); + CallEventListeners( VclEventId::ToolboxHighlight ); + } + } + break; + } + ++nTempPos; + } + } + + // only clear highlight when focus is not in toolbar + bool bMenuButtonHit = mpData->maMenubuttonItem.maRect.Contains( aMousePos ) && ImplHasClippedItems(); + if ( !HasFocus() && (bClearHigh || bMenuButtonHit) ) + { + if ( !bMenuButtonHit && mpData->mbMenubuttonSelected ) + { + // remove highlight from menubutton + InvalidateMenuButton(); + } + + if( mnHighItemId ) + { + ImplToolItems::size_type nClearPos = GetItemPos( mnHighItemId ); + if ( nClearPos != ITEM_NOTFOUND ) + { + InvalidateItem(nClearPos); + if( nClearPos != mnCurPos ) + CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nClearPos ) ); + } + ImplHideFocus(); + mnHighItemId = ToolBoxItemId(0); + } + + if( bMenuButtonHit ) + { + InvalidateMenuButton(); + } + } + } + + if ( meLastStyle != eStyle ) + { + meLastStyle = eStyle; + SetPointer( eStyle ); + } + + DockingWindow::MouseMove( rMEvt ); +} + +void ToolBox::MouseButtonDown( const MouseEvent& rMEvt ) +{ + // only trigger toolbox for left mouse button and when + // we're not in normal operation + if ( rMEvt.IsLeft() && !mbDrag && (mnCurPos == ITEM_NOTFOUND) ) + { + // call activate already here, as items could + // be exchanged + Activate(); + + // update ToolBox here, such that user knows it + if ( mbFormat ) + { + ImplFormat(); + PaintImmediately(); + } + + Point aMousePos = rMEvt.GetPosPixel(); + ImplToolItems::size_type i = 0; + ImplToolItems::size_type nNewPos = ITEM_NOTFOUND; + + // search for item that was clicked + for (auto const& item : mpData->m_aItems) + { + // is this the item? + if ( item.maRect.Contains( aMousePos ) ) + { + // do nothing if it is a separator or + // if the item has been disabled + if ( (item.meType == ToolBoxItemType::BUTTON) && + !item.mbShowWindow ) + nNewPos = i; + + break; + } + + i++; + } + + // item found + if ( nNewPos != ITEM_NOTFOUND ) + { + if ( !mpData->m_aItems[nNewPos].mbEnabled ) + { + Deactivate(); + return; + } + + // update actual data + StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE; + mnCurPos = i; + mnCurItemId = mpData->m_aItems[nNewPos].mnId; + mnDownItemId = mnCurItemId; + mnMouseModifier = rMEvt.GetModifier(); + if ( mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::REPEAT ) + nTrackFlags |= StartTrackingFlags::ButtonRepeat; + + // update bDrag here, as it is evaluated in the EndSelection + mbDrag = true; + + // on double-click: only call the handler, but do so before the button + // is hit, as in the handler dragging + // can be terminated + if ( rMEvt.GetClicks() == 2 ) + DoubleClick(); + + if ( mbDrag ) + { + InvalidateItem(mnCurPos); + Highlight(); + } + + // was dropdown arrow pressed + if( mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::DROPDOWN ) + { + if( ( (mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::DROPDOWNONLY) == ToolBoxItemBits::DROPDOWNONLY) + || mpData->m_aItems[nNewPos].GetDropDownRect( mbHorz ).Contains( aMousePos )) + { + // dropdownonly always triggers the dropdown handler, over the whole button area + + // the drop down arrow should not trigger the item action + mpData->mbDropDownByKeyboard = false; + mpData->maDropdownClickHdl.Call( this ); + + // do not reset data if the dropdown handler opened a floating window + // see ImplFloatControl() + if( !mpFloatWin ) + { + // no floater was opened + Deactivate(); + InvalidateItem(mnCurPos); + + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; + mnHighItemId = ToolBoxItemId(0); + } + return; + } + else // activate long click timer + mpData->maDropdownTimer.Start(); + } + + // call Click handler + if ( rMEvt.GetClicks() != 2 ) + Click(); + + // also call Select handler at repeat + if ( nTrackFlags & StartTrackingFlags::ButtonRepeat ) + Select(); + + // if the actions was not aborted in Click handler + if ( mbDrag ) + StartTracking( nTrackFlags ); + + // if mouse was clicked over an item we + // can abort here + return; + } + + Deactivate(); + + // menu button hit ? + if( mpData->maMenubuttonItem.maRect.Contains( aMousePos ) && ImplHasClippedItems() ) + { + if ( maMenuButtonHdl.IsSet() ) + maMenuButtonHdl.Call( this ); + else + ExecuteCustomMenu( mpData->maMenubuttonItem.maRect ); + return; + } + + // check scroll- and next-buttons here + if ( maUpperRect.Contains( aMousePos ) ) + { + if ( mnCurLine > 1 ) + { + StartTracking(); + mbUpper = true; + mbIn = true; + InvalidateSpin(true, false); + } + return; + } + if ( maLowerRect.Contains( aMousePos ) ) + { + if ( mnCurLine+mnVisLines-1 < mnCurLines ) + { + StartTracking(); + mbLower = true; + mbIn = true; + InvalidateSpin(false); + } + return; + } + + // Linesizing testen + if ( (mnWinStyle & TB_WBLINESIZING) == TB_WBLINESIZING ) + { + sal_uInt16 nLineMode = ImplTestLineSize( aMousePos ); + if ( nLineMode ) + { + ImplTBDragMgr* pMgr = ImplGetTBDragMgr(); + + // call handler, such that we can set the + // dock rectangles + StartDocking(); + + Point aPos = GetParent()->OutputToScreenPixel( GetPosPixel() ); + Size aSize = GetSizePixel(); + aPos = ScreenToOutputPixel( aPos ); + + // start dragging + pMgr->StartDragging( this, aMousePos, tools::Rectangle( aPos, aSize ), + nLineMode ); + return; + } + } + + // no item, then only click or double click + if ( rMEvt.GetClicks() == 2 ) + DoubleClick(); + else + Click(); + } + + if ( !mbDrag && (mnCurPos == ITEM_NOTFOUND) ) + DockingWindow::MouseButtonDown( rMEvt ); +} + +void ToolBox::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( ImplHandleMouseButtonUp( rMEvt ) ) + return; + + if ( mbDragging && rMEvt.IsLeft() ) + { + ImplTBDragMgr* pMgr = ImplGetTBDragMgr(); + pMgr->EndDragging(); + return; + } + + DockingWindow::MouseButtonUp( rMEvt ); +} + +void ToolBox::Tracking( const TrackingEvent& rTEvt ) +{ + VclPtr<vcl::Window> xWindow = this; + + if ( rTEvt.IsTrackingEnded() ) + ImplHandleMouseButtonUp( rTEvt.GetMouseEvent(), rTEvt.IsTrackingCanceled() ); + else + ImplHandleMouseMove( rTEvt.GetMouseEvent(), rTEvt.IsTrackingRepeat() ); + + if ( xWindow->isDisposed() ) + // toolbox was deleted + return; + DockingWindow::Tracking( rTEvt ); +} + +void ToolBox::InvalidateItem(ImplToolItems::size_type nPosition) +{ + if (mpData && nPosition < mpData->m_aItems.size()) + { + ImplToolItem* pItem = &mpData->m_aItems[nPosition]; + Invalidate(pItem->maRect); + } +} + +void ToolBox::InvalidateMenuButton() +{ + if (!mpData->maMenubuttonItem.maRect.IsEmpty()) + Invalidate(mpData->maMenubuttonItem.maRect); +} + +void ToolBox::InvalidateSpin(bool bUpperIn, bool bLowerIn) +{ + if (bUpperIn && !maUpperRect.IsEmpty()) + Invalidate(maUpperRect); + + if (bLowerIn && !maLowerRect.IsEmpty()) + Invalidate(maLowerRect); +} + +void ToolBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rPaintRect) +{ + if( mpData->mbIsPaintLocked ) + return; + + if (rPaintRect == tools::Rectangle(0, 0, mnDX-1, mnDY-1)) + mbFullPaint = true; + ImplFormat(); + mbFullPaint = false; + + ImplDrawBackground(rRenderContext, rPaintRect); + + if ( (mnWinStyle & WB_BORDER) && !ImplIsFloatingMode() ) + ImplDrawBorder(rRenderContext); + + if( !ImplIsFloatingMode() ) + ImplDrawGrip(rRenderContext); + + ImplDrawMenuButton(rRenderContext, mpData->mbMenubuttonSelected); + + // draw SpinButtons + if (mnWinStyle & WB_SCROLL) + { + if (mnCurLines > mnLines) + ImplDrawSpin(rRenderContext); + } + + // draw buttons + ImplToolItems::size_type nHighPos; + if ( mnHighItemId ) + nHighPos = GetItemPos( mnHighItemId ); + else + nHighPos = ITEM_NOTFOUND; + + ImplToolItems::size_type nCount = mpData->m_aItems.size(); + for( ImplToolItems::size_type i = 0; i < nCount; i++ ) + { + ImplToolItem* pItem = &mpData->m_aItems[i]; + + // only draw when the rectangle is in the draw rectangle + if ( !pItem->maRect.IsEmpty() && rPaintRect.Overlaps( pItem->maRect ) ) + { + sal_uInt16 nHighlight = 0; + if ( i == mnCurPos ) + nHighlight = 1; + else if ( i == nHighPos ) + nHighlight = 2; + ImplDrawItem(rRenderContext, i, nHighlight); + } + } + ImplShowFocus(); +} + +void ToolBox::Resize() +{ + Size aSize = GetOutputSizePixel(); + // #i31422# some WindowManagers send (0,0) sizes when + // switching virtual desktops - ignore this and avoid reformatting + if( !aSize.Width() && !aSize.Height() ) + return; + + tools::Long nOldDX = mnDX; + tools::Long nOldDY = mnDY; + mnDX = aSize.Width(); + mnDY = aSize.Height(); + + mnLastResizeDY = 0; + + // invalidate everything to have gradient backgrounds properly drawn + Invalidate(); + + // If we have any expandable entries, then force a reformat first using + // their optimal sizes, then share out the excess space evenly across those + // expandables and reformat again + std::vector<size_t> aExpandables; + for (size_t i = 0; i < mpData->m_aItems.size(); ++i) + { + if (mpData->m_aItems[i].mbExpand) + { + vcl::Window *pWindow = mpData->m_aItems[i].mpWindow; + SAL_INFO_IF(!pWindow, "vcl.layout", "only tabitems with window supported at the moment"); + if (!pWindow) + continue; + Size aWinSize(pWindow->GetSizePixel()); + Size aPrefSize(pWindow->get_preferred_size()); + aWinSize.setWidth( aPrefSize.Width() ); + pWindow->SetSizePixel(aWinSize); + aExpandables.push_back(i); + } + } + + // re-format or re-draw + if ( mbScroll || !aExpandables.empty() ) + { + if ( !mbFormat || !aExpandables.empty() ) + { + mbFormat = true; + if( IsReallyVisible() || !aExpandables.empty() ) + { + ImplFormat(true); + + if (!aExpandables.empty()) + { + //Get how big the optimal size is + tools::Rectangle aBounds; + for (const ImplToolItem & rItem : mpData->m_aItems) + { + aBounds.Union( rItem.maRect ); + } + + auto nOptimalWidth = aBounds.GetWidth(); + auto nDiff = aSize.Width() - nOptimalWidth; + decltype(nDiff) nExpandablesSize = aExpandables.size(); + nDiff /= nExpandablesSize; + + //share out the diff from optimal to real across + //expandable entries + for (size_t nIndex : aExpandables) + { + vcl::Window *pWindow = mpData->m_aItems[nIndex].mpWindow; + Size aWinSize(pWindow->GetSizePixel()); + Size aPrefSize(pWindow->get_preferred_size()); + aWinSize.setWidth( aPrefSize.Width() + nDiff ); + pWindow->SetSizePixel(aWinSize); + } + + //now reformat with final sizes + mbFormat = true; + ImplFormat(true); + } + } + } + } + + // redraw border + if ( !(mnWinStyle & WB_BORDER) ) + return; + + // as otherwise, when painting we might think we have to re-draw everything + if ( mbFormat && IsReallyVisible() ) + Invalidate(); + else + { + if ( mnRightBorder ) + { + if ( nOldDX > mnDX ) + Invalidate( tools::Rectangle( mnDX-mnRightBorder-1, 0, mnDX, mnDY ) ); + else + Invalidate( tools::Rectangle( nOldDX-mnRightBorder-1, 0, nOldDX, nOldDY ) ); + } + + if ( mnBottomBorder ) + { + if ( nOldDY > mnDY ) + Invalidate( tools::Rectangle( 0, mnDY-mnBottomBorder-1, mnDX, mnDY ) ); + else + Invalidate( tools::Rectangle( 0, nOldDY-mnBottomBorder-1, nOldDX, nOldDY ) ); + } + } +} + +namespace +{ + bool DispatchableCommand(std::u16string_view rName) + { + return o3tl::starts_with(rName, u".uno") || + o3tl::starts_with(rName, u"slot:") || + o3tl::starts_with(rName, u"macro:") || + o3tl::starts_with(rName, u"vnd.sun.star.script"); + } +} + +const OUString& ToolBox::ImplGetHelpText( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + assert( pItem ); + + if ( pItem->maHelpText.isEmpty() && ( !pItem->maHelpId.isEmpty() || pItem->maCommandStr.getLength() )) + { + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + { + if (DispatchableCommand(pItem->maCommandStr)) + pItem->maHelpText = pHelp->GetHelpText( pItem->maCommandStr, this ); + if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() ) + pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this ); + } + } + + return pItem->maHelpText; +} + +void ToolBox::RequestHelp( const HelpEvent& rHEvt ) +{ + ToolBoxItemId nItemId; + Point aHelpPos; + + if( !rHEvt.KeyboardActivated() ) + { + nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) ); + aHelpPos = rHEvt.GetMousePosPixel(); + } + else + { + if( !mnHighItemId ) + return; + else + nItemId = mnHighItemId; + tools::Rectangle aRect( GetItemRect( nItemId ) ); + if( aRect.IsEmpty() ) + return; + else + aHelpPos = OutputToScreenPixel( aRect.Center() ); + } + + if ( nItemId ) + { + if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) ) + { + // get rectangle + tools::Rectangle aTempRect = GetItemRect( nItemId ); + Point aPt = OutputToScreenPixel( aTempRect.TopLeft() ); + aTempRect.SetLeft( aPt.X() ); + aTempRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel( aTempRect.BottomRight() ); + aTempRect.SetRight( aPt.X() ); + aTempRect.SetBottom( aPt.Y() ); + + // get text and display it + OUString aStr = GetQuickHelpText( nItemId ); + if (aStr.isEmpty()) + aStr = MnemonicGenerator::EraseAllMnemonicChars( GetItemText( nItemId ) ); + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + { + const OUString& rHelpStr = GetHelpText( nItemId ); + if (!rHelpStr.isEmpty()) + aStr = rHelpStr; + Help::ShowBalloon( this, aHelpPos, aTempRect, aStr ); + } + else + Help::ShowQuickHelp( this, aTempRect, aStr, QuickHelpFlags::CtrlText ); + return; + } + } + + DockingWindow::RequestHelp( rHEvt ); +} + +bool ToolBox::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + KeyEvent aKEvt = *rNEvt.GetKeyEvent(); + vcl::KeyCode aKeyCode = aKEvt.GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + switch( nKeyCode ) + { + case KEY_TAB: + { + // internal TAB cycling only if parent is not a dialog or if we are the only child + // otherwise the dialog control will take over + vcl::Window *pParent = ImplGetParent(); + bool bOldSchoolContainer = + ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL && + pParent->GetChildCount() != 1); + bool bNoTabCycling = bOldSchoolContainer || isContainerWindow(pParent); + + if( bNoTabCycling ) + return DockingWindow::EventNotify( rNEvt ); + else if( ImplChangeHighlightUpDn( aKeyCode.IsShift() , bNoTabCycling ) ) + return true; + else + return DockingWindow::EventNotify( rNEvt ); + } + default: + break; + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + { + if( rNEvt.GetWindow() == this ) + { + // the toolbar itself got the focus + if( mnLastFocusItemId != ToolBoxItemId(0) || mpData->mbMenubuttonWasLastSelected ) + { + // restore last item + if( mpData->mbMenubuttonWasLastSelected ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else + { + ImplChangeHighlight( ImplGetItem( mnLastFocusItemId ) ); + mnLastFocusItemId = ToolBoxItemId(0); + } + } + else if( (GetGetFocusFlags() & (GetFocusFlags::Backward|GetFocusFlags::Tab) ) == (GetFocusFlags::Backward|GetFocusFlags::Tab)) + // Shift-TAB was pressed in the parent + ImplChangeHighlightUpDn( false ); + else + ImplChangeHighlightUpDn( true ); + + mnLastFocusItemId = ToolBoxItemId(0); + + return true; + } + else + { + // a child window got the focus so update current item to + // allow for proper lose focus handling in keyboard navigation + for (auto const& item : mpData->m_aItems) + { + if ( item.mbVisible ) + { + if ( item.mpWindow && item.mpWindow->ImplIsWindowOrChild( rNEvt.GetWindow() ) ) + { + mnHighItemId = item.mnId; + break; + } + } + } + return DockingWindow::EventNotify( rNEvt ); + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + // deselect + ImplHideFocus(); + mpData->mbMenubuttonWasLastSelected = false; + mnHighItemId = ToolBoxItemId(0); + mnCurPos = ITEM_NOTFOUND; + } + + return DockingWindow::EventNotify( rNEvt ); +} + +void ToolBox::Command( const CommandEvent& rCEvt ) +{ + if ( rCEvt.GetCommand() == CommandEventId::Wheel ) + { + if ( (mnCurLine > 1) || (mnCurLine+mnVisLines-1 < mnCurLines) ) + { + const CommandWheelData* pData = rCEvt.GetWheelData(); + if ( pData->GetMode() == CommandWheelMode::SCROLL ) + { + if ( (mnCurLine > 1) && (pData->GetDelta() > 0) ) + ShowLine( false ); + else if ( (mnCurLine+mnVisLines-1 < mnCurLines) && (pData->GetDelta() < 0) ) + ShowLine( true ); + InvalidateSpin(); + return; + } + } + } + else if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) + { + ExecuteCustomMenu( tools::Rectangle( rCEvt.GetMousePosPixel(), rCEvt.GetMousePosPixel() ) ); + return; + } + + DockingWindow::Command( rCEvt ); +} + +void ToolBox::StateChanged( StateChangedType nType ) +{ + DockingWindow::StateChanged( nType ); + + if ( nType == StateChangedType::InitShow ) + ImplFormat(); + else if ( nType == StateChangedType::Enable ) + ImplUpdateItem(); + else if ( nType == StateChangedType::UpdateMode ) + { + if ( IsUpdateMode() ) + Invalidate(); + } + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + mbCalc = true; + mbFormat = true; + ImplInitSettings( true, false, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings( false, true, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings( false, false, true ); // font, foreground, background + Invalidate(); + } + + maStateChangedHandler.Call( &nType ); +} + +void ToolBox::DataChanged( const DataChangedEvent& rDCEvt ) +{ + DockingWindow::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::DISPLAY) || + (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + mbCalc = true; + mbFormat = true; + ImplInitSettings( true, true, true ); + Invalidate(); + } + + maDataChangedHandler.Call( &rDCEvt ); +} + +void ToolBox::statusChanged( const css::frame::FeatureStateEvent& Event ) +{ + // Update image mirroring/rotation + if ( Event.FeatureURL.Complete != ".uno:ImageOrientation" ) + return; + + SfxImageItem aItem( 1 ); + aItem.PutValue( Event.State, 0 ); + + mbImagesMirrored = aItem.IsMirrored(); + mnImagesRotationAngle = aItem.GetRotation(); + + // update image orientation + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(mpStatusListener->getFrame())); + for (auto const& item : mpData->m_aItems) + { + if (vcl::CommandInfoProvider::IsMirrored(item.maCommandStr, aModuleName)) + SetItemImageMirrorMode(item.mnId, mbImagesMirrored); + if (vcl::CommandInfoProvider::IsRotated(item.maCommandStr, aModuleName)) + SetItemImageAngle(item.mnId, mnImagesRotationAngle); + } +} + +void ToolBox::SetStyle(WinBits nNewStyle) +{ + mnWinStyle = nNewStyle; + if (!ImplIsFloatingMode()) + { + bool bOldScroll = mbScroll; + mbScroll = (mnWinStyle & WB_SCROLL) != 0; + if (mbScroll != bOldScroll) + { + mbFormat = true; + ImplFormat(); + } + } +} + +void ToolBox::ToggleFloatingMode() +{ + DockingWindow::ToggleFloatingMode(); + + if (!mpData) + return; + + bool bOldHorz = mbHorz; + + if ( ImplIsFloatingMode() ) + { + mbHorz = true; + meAlign = WindowAlign::Top; + mbScroll = true; + + if( bOldHorz != mbHorz ) + mbCalc = true; // orientation was changed ! + + ImplSetMinMaxFloatSize(); + SetOutputSizePixel( ImplCalcFloatSize( mnFloatLines ) ); + } + else + { + mbScroll = (mnWinStyle & WB_SCROLL) != 0; + if ( (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom) ) + mbHorz = true; + else + mbHorz = false; + + // set focus back to document + ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->GrabFocus(); + } + + if( bOldHorz != mbHorz ) + { + // if orientation changes, the toolbox has to be initialized again + // to update the direction of the gradient + mbCalc = true; + ImplInitSettings( true, true, true ); + } + + mbFormat = true; + ImplFormat(); +} + +void ToolBox::StartDocking() +{ + meDockAlign = meAlign; + mnDockLines = mnLines; + mbLastFloatMode = ImplIsFloatingMode(); + DockingWindow::StartDocking(); +} + +bool ToolBox::Docking( const Point& rPos, tools::Rectangle& rRect ) +{ + // do nothing during dragging, it was calculated before + if ( mbDragging ) + return false; + + bool bFloatMode = false; + + DockingWindow::Docking( rPos, rRect ); + + // if the mouse is outside the area, it can only become a floating window + tools::Rectangle aDockingRect( rRect ); + if ( !ImplIsFloatingMode() ) + { + // don't use tracking rectangle for alignment check, because it will be too large + // to get a floating mode as result - switch to floating size + // so the calculation only depends on the position of the rectangle, not the current + // docking state of the window + ImplToolItems::size_type nTemp = 0; + aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) ); + + // in this mode docking is never done by keyboard, so it's OK to use the mouse position + aDockingRect.SetPos( ImplGetFrameWindow()->GetPointerPosPixel() ); + } + + bFloatMode = true; + + meDockAlign = meAlign; + if ( !mbLastFloatMode ) + { + ImplToolItems::size_type nTemp = 0; + aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) ); + } + + rRect = aDockingRect; + mbLastFloatMode = bFloatMode; + + return bFloatMode; +} + +void ToolBox::EndDocking( const tools::Rectangle& rRect, bool bFloatMode ) +{ + if ( !IsDockingCanceled() ) + { + if ( mnLines != mnDockLines ) + SetLineCount( mnDockLines ); + if ( meAlign != meDockAlign ) + SetAlign( meDockAlign ); + } + if ( bFloatMode || (bFloatMode != ImplIsFloatingMode()) ) + DockingWindow::EndDocking( rRect, bFloatMode ); +} + +void ToolBox::Resizing( Size& rSize ) +{ + ImplToolItems::size_type nCalcLines; + ImplToolItems::size_type nTemp; + + // calculate all floating sizes + ImplCalcFloatSizes(); + + if ( !mnLastResizeDY ) + mnLastResizeDY = mnDY; + + // is vertical resizing needed + if ( (mnLastResizeDY != rSize.Height()) && (mnDY != rSize.Height()) ) + { + nCalcLines = ImplCalcLines( rSize.Height() ); + if ( nCalcLines < 1 ) + nCalcLines = 1; + rSize = ImplCalcFloatSize( nCalcLines ); + } + else + { + nCalcLines = 1; + nTemp = nCalcLines; + Size aTempSize = ImplCalcFloatSize( nTemp ); + while ( (aTempSize.Width() > rSize.Width()) && + (nCalcLines <= maFloatSizes[0].mnLines) ) + { + nCalcLines++; + nTemp = nCalcLines; + aTempSize = ImplCalcFloatSize( nTemp ); + } + rSize = aTempSize; + } + + mnLastResizeDY = rSize.Height(); +} + +Size ToolBox::GetOptimalSize() const +{ + // If we have any expandable entries, then force them to their + // optimal sizes, then reset them afterwards + std::map<vcl::Window*, Size> aExpandables; + for (const ImplToolItem & rItem : mpData->m_aItems) + { + if (rItem.mbExpand) + { + vcl::Window *pWindow = rItem.mpWindow; + SAL_INFO_IF(!pWindow, "vcl.layout", "only tabitems with window supported at the moment"); + if (!pWindow) + continue; + Size aWinSize(pWindow->GetSizePixel()); + aExpandables[pWindow] = aWinSize; + Size aPrefSize(pWindow->get_preferred_size()); + aWinSize.setWidth( aPrefSize.Width() ); + pWindow->SetSizePixel(aWinSize); + } + } + + Size aSize(const_cast<ToolBox *>(this)->ImplCalcSize( mnLines )); + + for (auto const& [pWindow, aWinSize] : aExpandables) + pWindow->SetSizePixel(aWinSize); + + return aSize; +} + +Size ToolBox::CalcWindowSizePixel( ImplToolItems::size_type nCalcLines ) +{ + return ImplCalcSize( nCalcLines ); +} + +Size ToolBox::CalcWindowSizePixel( ImplToolItems::size_type nCalcLines, WindowAlign eAlign ) +{ + return ImplCalcSize( nCalcLines, + (eAlign == WindowAlign::Top || eAlign == WindowAlign::Bottom) ? TB_CALCMODE_HORZ : TB_CALCMODE_VERT ); +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplCountLineBreaks() const +{ + ImplToolItems::size_type nLines = 0; + + for (auto const& item : mpData->m_aItems) + { + if( item.meType == ToolBoxItemType::BREAK ) + ++nLines; + } + return nLines; +} + +Size ToolBox::CalcPopupWindowSizePixel() +{ + // count number of breaks and calc corresponding floating window size + ImplToolItems::size_type nLines = ImplCountLineBreaks(); + + if( nLines ) + ++nLines; // add the first line + else + { + // no breaks found: use quadratic layout + nLines = static_cast<ImplToolItems::size_type>(ceil( sqrt( static_cast<double>(GetItemCount()) ) )); + } + + bool bPopup = mpData->mbAssumePopupMode; + mpData->mbAssumePopupMode = true; + + Size aSize = CalcFloatingWindowSizePixel( nLines ); + + mpData->mbAssumePopupMode = bPopup; + return aSize; +} + +Size ToolBox::CalcFloatingWindowSizePixel() +{ + ImplToolItems::size_type nLines = ImplCountLineBreaks(); + ++nLines; // add the first line + return CalcFloatingWindowSizePixel( nLines ); +} + +Size ToolBox::CalcFloatingWindowSizePixel( ImplToolItems::size_type nCalcLines ) +{ + bool bFloat = mpData->mbAssumeFloating; + bool bDocking = mpData->mbAssumeDocked; + + // simulate floating mode and force reformat before calculating + mpData->mbAssumeFloating = true; + mpData->mbAssumeDocked = false; + + Size aSize = ImplCalcFloatSize( nCalcLines ); + + mbFormat = true; + mpData->mbAssumeFloating = bFloat; + mpData->mbAssumeDocked = bDocking; + + return aSize; +} + +Size ToolBox::CalcMinimumWindowSizePixel() +{ + if( ImplIsFloatingMode() ) + return ImplCalcSize( mnFloatLines ); + else + { + // create dummy toolbox for measurements + VclPtrInstance< ToolBox > pToolBox( GetParent(), GetStyle() ); + + // copy until first useful item + for (auto const& item : mpData->m_aItems) + { + pToolBox->CopyItem( *this, item.mnId ); + if( (item.meType == ToolBoxItemType::BUTTON) && + item.mbVisible && !ImplIsFixedControl( &item ) ) + break; + } + + // add to docking manager if required to obtain a drag area + // (which is accounted for in calcwindowsizepixel) + if( ImplGetDockingManager()->GetDockingWindowWrapper( this ) ) + ImplGetDockingManager()->AddWindow( pToolBox ); + + // account for menu + if( IsMenuEnabled() ) + pToolBox->SetMenuType( GetMenuType() ); + + pToolBox->SetAlign( GetAlign() ); + Size aSize = pToolBox->CalcWindowSizePixel( 1 ); + + ImplGetDockingManager()->RemoveWindow( pToolBox ); + pToolBox->Clear(); + + pToolBox.disposeAndClear(); + + return aSize; + } +} + +void ToolBox::EnableCustomize( bool bEnable ) +{ + mbCustomize = bEnable; +} + +void ToolBox::LoseFocus() +{ + ImplChangeHighlight( nullptr, true ); + + DockingWindow::LoseFocus(); +} + +// performs the action associated with an item, ie simulates clicking the item +void ToolBox::TriggerItem( ToolBoxItemId nItemId ) +{ + mnHighItemId = nItemId; + vcl::KeyCode aKeyCode( 0, 0 ); + ImplActivateItem( aKeyCode ); +} + +// calls the button's action handler +// returns true if action was called +bool ToolBox::ImplActivateItem( vcl::KeyCode aKeyCode ) +{ + bool bRet = true; + if( mnHighItemId ) + { + ImplToolItem *pToolItem = ImplGetItem( mnHighItemId ); + + // #107712#, activate can also be called for disabled entries + if( pToolItem && !pToolItem->mbEnabled ) + return true; + + if( pToolItem && pToolItem->mpWindow && HasFocus() ) + { + ImplHideFocus(); + mbChangingHighlight = true; // avoid focus change due to loss of focus + pToolItem->mpWindow->ImplControlFocus( GetFocusFlags::Tab ); + mbChangingHighlight = false; + } + else + { + mnDownItemId = mnCurItemId = mnHighItemId; + if (pToolItem && (pToolItem->mnBits & ToolBoxItemBits::AUTOCHECK)) + { + if ( pToolItem->mnBits & ToolBoxItemBits::RADIOCHECK ) + { + if ( pToolItem->meState != TRISTATE_TRUE ) + SetItemState( pToolItem->mnId, TRISTATE_TRUE ); + } + else + { + if ( pToolItem->meState != TRISTATE_TRUE ) + pToolItem->meState = TRISTATE_TRUE; + else + pToolItem->meState = TRISTATE_FALSE; + } + } + mnMouseModifier = aKeyCode.GetModifier(); + mbIsKeyEvent = true; + Activate(); + Click(); + + // #107776# we might be destroyed in the selecthandler + VclPtr<vcl::Window> xWindow = this; + Select(); + if ( xWindow->isDisposed() ) + return bRet; + + Deactivate(); + mbIsKeyEvent = false; + mnMouseModifier = 0; + } + } + else + bRet = false; + return bRet; +} + +static bool ImplCloseLastPopup( vcl::Window const *pParent ) +{ + // close last popup toolbox (see also: + // ImplHandleMouseFloatMode(...) in winproc.cxx ) + + if (ImplGetSVData()->mpWinData->mpFirstFloat) + { + FloatingWindow* pLastLevelFloat = ImplGetSVData()->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + // only close the floater if it is not our direct parent, which would kill ourself + if( pLastLevelFloat && pLastLevelFloat != pParent ) + { + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + return true; + } + } + return false; +} + +// opens a drop down toolbox item +// returns true if item was opened +bool ToolBox::ImplOpenItem( vcl::KeyCode aKeyCode ) +{ + sal_uInt16 nCode = aKeyCode.GetCode(); + bool bRet = true; + + // arrow keys should work only in the opposite direction of alignment (to not break cursor travelling) + if ( ((nCode == KEY_LEFT || nCode == KEY_RIGHT) && IsHorizontal()) + || ((nCode == KEY_UP || nCode == KEY_DOWN) && !IsHorizontal()) ) + return false; + + if( mpData->mbMenubuttonSelected ) + { + if( ImplCloseLastPopup( GetParent() ) ) + return bRet; + mbIsKeyEvent = true; + if ( maMenuButtonHdl.IsSet() ) + maMenuButtonHdl.Call( this ); + else + ExecuteCustomMenu( mpData->maMenubuttonItem.maRect ); + mpData->mbMenubuttonWasLastSelected = true; + mbIsKeyEvent = false; + } + else if( mnHighItemId && ImplGetItem( mnHighItemId ) && + (ImplGetItem( mnHighItemId )->mnBits & ToolBoxItemBits::DROPDOWN) ) + { + mnDownItemId = mnCurItemId = mnHighItemId; + mnCurPos = GetItemPos( mnCurItemId ); + mnLastFocusItemId = mnCurItemId; // save item id for possible later focus restore + mnMouseModifier = aKeyCode.GetModifier(); + mbIsKeyEvent = true; + Activate(); + + mpData->mbDropDownByKeyboard = true; + mpData->maDropdownClickHdl.Call( this ); + + mbIsKeyEvent = false; + mnMouseModifier = 0; + } + else + bRet = false; + + return bRet; +} + +void ToolBox::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nCode = aKeyCode.GetCode(); + + vcl::Window *pParent = ImplGetParent(); + bool bOldSchoolContainer = ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL); + bool bParentIsContainer = bOldSchoolContainer || isContainerWindow(pParent); + + bool bForwardKey = false; + bool bGrabFocusToDocument = false; + + // #107776# we might be destroyed in the keyhandler + VclPtr<vcl::Window> xWindow = this; + + switch ( nCode ) + { + case KEY_UP: + { + // Ctrl-Cursor activates next toolbox, indicated by a blue arrow pointing to the left/up + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( !IsHorizontal() ) + ImplChangeHighlightUpDn( true ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_LEFT: + { + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( IsHorizontal() ) + ImplChangeHighlightUpDn( true ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_DOWN: + { + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( !IsHorizontal() ) + ImplChangeHighlightUpDn( false ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_RIGHT: + { + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( IsHorizontal() ) + ImplChangeHighlightUpDn( false ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_PAGEUP: + if ( mnCurLine > 1 ) + { + if( mnCurLine > mnVisLines ) + mnCurLine = mnCurLine - mnVisLines; + else + mnCurLine = 1; + mbFormat = true; + ImplFormat(); + InvalidateSpin(); + ImplChangeHighlight( ImplGetFirstValidItem( mnCurLine ) ); + } + break; + case KEY_PAGEDOWN: + if ( mnCurLine+mnVisLines-1 < mnCurLines ) + { + if( mnCurLine + 2*mnVisLines-1 < mnCurLines ) + mnCurLine = mnCurLine + mnVisLines; + else + mnCurLine = mnCurLines; + mbFormat = true; + ImplFormat(); + InvalidateSpin(); + ImplChangeHighlight( ImplGetFirstValidItem( mnCurLine ) ); + } + break; + case KEY_END: + { + ImplChangeHighlight( nullptr ); + ImplChangeHighlightUpDn( false ); + } + break; + case KEY_HOME: + { + ImplChangeHighlight( nullptr ); + ImplChangeHighlightUpDn( true ); + } + break; + case KEY_ESCAPE: + { + if( !ImplIsFloatingMode() && bParentIsContainer ) + DockingWindow::KeyInput( rKEvt ); + else + { + // send focus to document pane + vcl::Window *pWin = this; + while( pWin ) + { + if( !pWin->GetParent() ) + { + pWin->ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->GrabFocus(); + break; + } + pWin = pWin->GetParent(); + } + } + } + break; + case KEY_RETURN: + { + // #107712#, disabled entries are selectable now + // leave toolbox and move focus to document + if( mnHighItemId ) + { + ImplToolItem *pItem = ImplGetItem(mnHighItemId); + if (!pItem || !pItem->mbEnabled) + { + bGrabFocusToDocument = true; + } + } + if( !bGrabFocusToDocument ) + bForwardKey = !ImplActivateItem( aKeyCode ); + } + break; + case KEY_SPACE: + { + ImplOpenItem( aKeyCode ); + } + break; + default: + { + sal_uInt16 aKeyGroup = aKeyCode.GetGroup(); + ImplToolItem *pItem = nullptr; + if( mnHighItemId ) + pItem = ImplGetItem( mnHighItemId ); + // #i13931# forward alphanum keyinput into embedded control + if( (aKeyGroup == KEYGROUP_NUM || aKeyGroup == KEYGROUP_ALPHA ) && pItem && pItem->mpWindow && pItem->mbEnabled ) + { + vcl::Window *pFocusWindow = Application::GetFocusWindow(); + ImplHideFocus(); + mbChangingHighlight = true; // avoid focus change due to loss of focus + pItem->mpWindow->ImplControlFocus( GetFocusFlags::Tab ); + mbChangingHighlight = false; + if( pFocusWindow != Application::GetFocusWindow() ) + Application::GetFocusWindow()->KeyInput( rKEvt ); + } + else + { + // do nothing to avoid key presses going into the document + // while the toolbox has the focus + // just forward function and special keys and combinations with Alt-key + if( aKeyGroup == KEYGROUP_FKEYS || aKeyGroup == KEYGROUP_MISC || aKeyCode.IsMod2() ) + bForwardKey = true; + } + } + } + + if ( xWindow->isDisposed() ) + return; + + // #107251# move focus away if this toolbox was disabled during keyinput + if (HasFocus() && mpData->mbKeyInputDisabled && bParentIsContainer) + { + vcl::Window *pFocusControl = pParent->ImplGetDlgWindow( 0, GetDlgWindowType::First ); + if ( pFocusControl && pFocusControl != this ) + pFocusControl->ImplControlFocus( GetFocusFlags::Init ); + } + + // #107712#, leave toolbox + if( bGrabFocusToDocument ) + { + GrabFocusToDocument(); + return; + } + + if( bForwardKey ) + DockingWindow::KeyInput( rKEvt ); +} + +// returns the current toolbox line of the item +ToolBox::ImplToolItems::size_type ToolBox::ImplGetItemLine( ImplToolItem const * pCurrentItem ) +{ + ImplToolItems::size_type nLine = 1; + for (auto const& item : mpData->m_aItems) + { + if ( item.mbBreak ) + ++nLine; + if( &item == pCurrentItem) + break; + } + return nLine; +} + +// returns the first displayable item in the given line +ImplToolItem* ToolBox::ImplGetFirstValidItem( ImplToolItems::size_type nLine ) +{ + if( !nLine || nLine > mnCurLines ) + return nullptr; + + nLine--; + + ImplToolItems::iterator it = mpData->m_aItems.begin(); + while( it != mpData->m_aItems.end() ) + { + // find correct line + if ( it->mbBreak ) + nLine--; + if( !nLine ) + { + // find first useful item + while( it != mpData->m_aItems.end() && ((it->meType != ToolBoxItemType::BUTTON) || + /*!it->mbEnabled ||*/ !it->mbVisible || ImplIsFixedControl( &(*it) )) ) + { + ++it; + if( it == mpData->m_aItems.end() || it->mbBreak ) + return nullptr; // no valid items in this line + } + return &(*it); + } + ++it; + } + + return (it == mpData->m_aItems.end()) ? nullptr : &(*it); +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplFindItemPos( const ImplToolItem* pItem, const ImplToolItems& rList ) +{ + if( pItem ) + { + for( ImplToolItems::size_type nPos = 0; nPos < rList.size(); ++nPos ) + if( &rList[ nPos ] == pItem ) + return nPos; + } + return ITEM_NOTFOUND; +} + +void ToolBox::ChangeHighlight( ImplToolItems::size_type nPos ) +{ + if ( nPos < GetItemCount() ) { + ImplGrabFocus( GetFocusFlags::NONE ); + ImplChangeHighlight ( ImplGetItem ( GetItemId ( nPos ) ) ); + } +} + +void ToolBox::ImplChangeHighlight( ImplToolItem const * pItem, bool bNoGrabFocus ) +{ + // avoid recursion due to focus change + if( mbChangingHighlight ) + return; + + mbChangingHighlight = true; + + ImplToolItem* pOldItem = nullptr; + + if ( mnHighItemId ) + { + ImplHideFocus(); + ImplToolItems::size_type nPos = GetItemPos( mnHighItemId ); + pOldItem = ImplGetItem( mnHighItemId ); + // #i89962# ImplDrawItem can cause Invalidate/Update + // which will in turn ImplShowFocus again + // set mnHighItemId to 0 already to prevent this hen/egg problem + mnHighItemId = ToolBoxItemId(0); + InvalidateItem(nPos); + CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nPos ) ); + } + + if( !bNoGrabFocus && pItem != pOldItem && pOldItem && pOldItem->mpWindow ) + { + // move focus into toolbox + GrabFocus(); + } + + if( pItem ) + { + ImplToolItems::size_type aPos = ToolBox::ImplFindItemPos( pItem, mpData->m_aItems ); + if( aPos != ITEM_NOTFOUND) + { + // check for line breaks + ImplToolItems::size_type nLine = ImplGetItemLine( pItem ); + + if( nLine >= mnCurLine + mnVisLines ) + { + mnCurLine = nLine - mnVisLines + 1; + mbFormat = true; + } + else if ( nLine < mnCurLine ) + { + mnCurLine = nLine; + mbFormat = true; + } + + if( mbFormat ) + { + ImplFormat(); + } + + mnHighItemId = pItem->mnId; + InvalidateItem(aPos); + + ImplShowFocus(); + + if( pItem->mpWindow ) + pItem->mpWindow->GrabFocus(); + if( pItem != pOldItem ) + CallEventListeners( VclEventId::ToolboxHighlight ); + } + } + else + { + ImplHideFocus(); + mnHighItemId = ToolBoxItemId(0); + mnCurPos = ITEM_NOTFOUND; + } + + mbChangingHighlight = false; +} + +// check for keyboard accessible items +static bool ImplIsValidItem( const ImplToolItem* pItem, bool bNotClipped ) +{ + bool bValid = (pItem && pItem->meType == ToolBoxItemType::BUTTON && pItem->mbVisible && !ImplIsFixedControl( pItem ) + && pItem->mbEnabled); + if( bValid && bNotClipped && pItem->IsClipped() ) + bValid = false; + return bValid; +} + +bool ToolBox::ImplChangeHighlightUpDn( bool bUp, bool bNoCycle ) +{ + ImplToolItem* pToolItem = ImplGetItem( mnHighItemId ); + + if( !pToolItem || !mnHighItemId ) + { + // menubutton highlighted ? + if( mpData->mbMenubuttonSelected ) + { + mpData->mbMenubuttonSelected = false; + if( bUp ) + { + // select last valid non-clipped item + ImplToolItem* pItem = nullptr; + auto it = std::find_if(mpData->m_aItems.rbegin(), mpData->m_aItems.rend(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, true ); }); + if( it != mpData->m_aItems.rend() ) + pItem = &(*it); + + InvalidateMenuButton(); + ImplChangeHighlight( pItem ); + } + else + { + // select first valid non-clipped item + ImplToolItems::iterator it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, true ); }); + if( it != mpData->m_aItems.end() ) + { + InvalidateMenuButton(); + ImplChangeHighlight( &(*it) ); + } + } + return true; + } + + if( bUp ) + { + // Select first valid item + ImplToolItems::iterator it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, false ); }); + + // select the menu button if a clipped item would be selected + if( (it != mpData->m_aItems.end() && &(*it) == ImplGetFirstClippedItem()) && IsMenuEnabled() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else + ImplChangeHighlight( (it != mpData->m_aItems.end()) ? &(*it) : nullptr ); + return true; + } + else + { + // Select last valid item + + // docked toolbars have the menubutton as last item - if this button is enabled + if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else + { + ImplToolItem* pItem = nullptr; + auto it = std::find_if(mpData->m_aItems.rbegin(), mpData->m_aItems.rend(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, false ); }); + if( it != mpData->m_aItems.rend() ) + pItem = &(*it); + + ImplChangeHighlight( pItem ); + } + return true; + } + } + + assert(pToolItem); + + ImplToolItems::size_type pos = ToolBox::ImplFindItemPos( pToolItem, mpData->m_aItems ); + ImplToolItems::size_type nCount = mpData->m_aItems.size(); + + ImplToolItems::size_type i=0; + do + { + if( bUp ) + { + if( !pos-- ) + { + if( bNoCycle ) + return false; + + // highlight the menu button if it is the last item + if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + return true; + } + else + pos = nCount-1; + } + } + else + { + if( ++pos >= nCount ) + { + if( bNoCycle ) + return false; + + // highlight the menu button if it is the last item + if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + return true; + } + else + pos = 0; + } + } + + pToolItem = &mpData->m_aItems[pos]; + + if ( ImplIsValidItem( pToolItem, false ) ) + break; + + } while( ++i < nCount); + + if( pToolItem->IsClipped() && IsMenuEnabled() ) + { + // select the menu button if a clipped item would be selected + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else if( i != nCount ) + ImplChangeHighlight( pToolItem ); + else + return false; + + return true; +} + +void ToolBox::ImplShowFocus() +{ + if( mnHighItemId && HasFocus() ) + { + ImplToolItem* pItem = ImplGetItem( mnHighItemId ); + if (pItem && pItem->mpWindow && !pItem->mpWindow->isDisposed()) + { + vcl::Window *pWin = pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow ? pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow.get() : pItem->mpWindow.get(); + pWin->ImplGetWindowImpl()->mbDrawSelectionBackground = true; + pWin->Invalidate(); + } + } +} + +void ToolBox::ImplHideFocus() +{ + if( mnHighItemId ) + { + mpData->mbMenubuttonWasLastSelected = false; + ImplToolItem* pItem = ImplGetItem( mnHighItemId ); + if( pItem && pItem->mpWindow ) + { + vcl::Window *pWin = pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow ? pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow.get() : pItem->mpWindow.get(); + pWin->ImplGetWindowImpl()->mbDrawSelectionBackground = false; + pWin->Invalidate(); + } + } + + if ( mpData && mpData->mbMenubuttonSelected ) + { + mpData->mbMenubuttonWasLastSelected = true; + // remove highlight from menubutton + mpData->mbMenubuttonSelected = false; + InvalidateMenuButton(); + } +} + +void ToolBox::SetToolbarLayoutMode( ToolBoxLayoutMode eLayout ) +{ + if ( meLayoutMode != eLayout ) + meLayoutMode = eLayout; +} + +void ToolBox::SetToolBoxTextPosition( ToolBoxTextPosition ePosition ) +{ + meTextPosition = ePosition; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |