/* -*- 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 <memory>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <comphelper/processfactory.hxx>
#include <o3tl/make_unique.hxx>
#include <vcl/dockwin.hxx>
#include <vcl/decoview.hxx>
#include <vcl/image.hxx>
#include <vcl/taskpanelist.hxx>
#include <vcl/toolbox.hxx>
#include <vcl/settings.hxx>
#include <svtools/framestatuslistener.hxx>
#include <svtools/valueset.hxx>
#include <svtools/toolbarmenu.hxx>
#include "toolbarmenuimp.hxx"
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::frame;
using namespace ::com::sun::star::accessibility;
namespace svtools {
static vcl::Window* GetTopMostParentSystemWindow( vcl::Window* pWindow )
{
OSL_ASSERT( pWindow );
if ( pWindow )
{
// ->manually search topmost system window
// required because their might be another system window between this and the top window
pWindow = pWindow->GetParent();
SystemWindow* pTopMostSysWin = nullptr;
while ( pWindow )
{
if ( pWindow->IsSystemWindow() )
pTopMostSysWin = static_cast<SystemWindow*>(pWindow);
pWindow = pWindow->GetParent();
}
pWindow = pTopMostSysWin;
OSL_ASSERT( pWindow );
return pWindow;
}
return nullptr;
}
void ToolbarMenuEntry::init( int nEntryId, MenuItemBits nBits )
{
mnEntryId = nEntryId;
mnBits = nBits;
mbHasText = false;
mbHasImage = false;
mbChecked = false;
mbEnabled = true;
mpControl = nullptr;
}
ToolbarMenuEntry::ToolbarMenuEntry( ToolbarMenu& rMenu, int nEntryId, const OUString& rText, MenuItemBits nBits )
: mrMenu( rMenu )
{
init( nEntryId, nBits );
maText = rText;
mbHasText = true;
}
ToolbarMenuEntry::ToolbarMenuEntry( ToolbarMenu& rMenu, int nEntryId, const Image& rImage, const OUString& rText, MenuItemBits nBits )
: mrMenu( rMenu )
{
init( nEntryId, nBits );
maText = rText;
mbHasText = true;
maImage = rImage;
mbHasImage = true;
}
ToolbarMenuEntry::ToolbarMenuEntry( ToolbarMenu& rMenu, int nEntryId, Control* pControl, MenuItemBits nBits )
: mrMenu( rMenu )
{
init( nEntryId, nBits );
if( pControl )
{
mpControl = pControl;
mpControl->Show();
}
}
ToolbarMenuEntry::~ToolbarMenuEntry()
{
if( mxAccContext.is() )
{
Reference< XComponent > xComponent( mxAccContext, UNO_QUERY );
if( xComponent.is() )
xComponent->dispose();
mxAccContext.clear();
}
mpControl.disposeAndClear();
}
const Reference< XAccessibleContext >& ToolbarMenuEntry::GetAccessible()
{
if( !mxAccContext.is() )
{
if( mpControl )
{
mxAccContext.set( mpControl->GetAccessible(), UNO_QUERY );
}
else
{
mxAccContext.set( new ToolbarMenuEntryAcc( this ) );
}
}
return mxAccContext;
}
sal_Int32 ToolbarMenuEntry::getAccessibleChildCount()
{
if( mpControl )
{
const Reference< XAccessibleContext >& xContext = GetAccessible();
if( xContext.is() )
{
return xContext->getAccessibleChildCount();
}
}
return 1;
}
Reference< XAccessible > ToolbarMenuEntry::getAccessibleChild( sal_Int32 index )
{
const Reference< XAccessibleContext >& xContext = GetAccessible();
if( mpControl )
{
if( xContext.is() )
{
return xContext->getAccessibleChild(index);
}
}
else if( index == 0 )
{
Reference< XAccessible > xRet( xContext, UNO_QUERY );
if( xRet.is() )
return xRet;
}
throw IndexOutOfBoundsException();
}
ToolbarMenu_Impl::ToolbarMenu_Impl( ToolbarMenu& rMenu )
: mrMenu( rMenu )
, mnCheckPos(0)
, mnImagePos(0)
, mnTextPos(0)
, mnHighlightedEntry(-1)
, mnSelectedEntry(-1)
, mnLastColumn(0)
{
}
ToolbarMenu_Impl::~ToolbarMenu_Impl()
{
setAccessible( nullptr );
}
void ToolbarMenu_Impl::setAccessible( ToolbarMenuAcc* pAccessible )
{
if( mxAccessible.get() != pAccessible )
{
if( mxAccessible.is() )
mxAccessible->dispose();
mxAccessible.set( pAccessible );
}
}
void ToolbarMenu_Impl::fireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue )
{
if( mxAccessible.is() )
mxAccessible->FireAccessibleEvent( nEventId, rOldValue, rNewValue );
}
sal_Int32 ToolbarMenu_Impl::getAccessibleChildCount()
{
sal_Int32 nCount = 0;
for( const auto& pEntry : maEntryVector )
{
if( pEntry )
{
if( pEntry->mpControl )
{
nCount += pEntry->getAccessibleChildCount();
}
else
{
nCount += 1;
}
}
}
return nCount;
}
Reference< XAccessible > ToolbarMenu_Impl::getAccessibleChild( sal_Int32 index )
{
for( const auto& pEntry : maEntryVector )
{
if( pEntry )
{
const sal_Int32 nCount = pEntry->getAccessibleChildCount();
if( index < nCount )
{
return pEntry->getAccessibleChild( index );
}
index -= nCount;
}
}
throw IndexOutOfBoundsException();
}
Reference< XAccessible > ToolbarMenu_Impl::getAccessibleChild( Control const * pControl, sal_Int32 childIndex )
{
for( const auto& pEntry : maEntryVector )
{
if( pEntry && (pEntry->mpControl.get() == pControl) )
{
return pEntry->getAccessibleChild( childIndex );
}
}
throw IndexOutOfBoundsException();
}
void ToolbarMenu_Impl::selectAccessibleChild( sal_Int32 nChildIndex )
{
const int nEntryCount = maEntryVector.size();
for( int nEntry = 0; nEntry < nEntryCount; nEntry++ )
{
ToolbarMenuEntry* const pEntry = maEntryVector[nEntry].get();
if( pEntry )
{
const sal_Int32 nCount = pEntry->getAccessibleChildCount();
if( nChildIndex < nCount )
{
if( pEntry->mpControl )
{
Reference< XAccessibleSelection > xSel( pEntry->GetAccessible(), UNO_QUERY_THROW );
xSel->selectAccessibleChild(nChildIndex);
}
else if( pEntry->mnEntryId != TITLE_ID )
{
mrMenu.implSelectEntry( nEntry );
}
return;
}
nChildIndex -= nCount;
}
}
throw IndexOutOfBoundsException();
}
bool ToolbarMenu_Impl::isAccessibleChildSelected( sal_Int32 nChildIndex )
{
const int nEntryCount = maEntryVector.size();
for( int nEntry = 0; nEntry < nEntryCount; nEntry++ )
{
ToolbarMenuEntry* const pEntry = maEntryVector[nEntry].get();
if( pEntry )
{
const sal_Int32 nCount = pEntry->getAccessibleChildCount();
if( nChildIndex < nCount )
{
if( mnHighlightedEntry == nEntry )
{
if( pEntry->mpControl )
{
Reference< XAccessibleSelection > xSel( pEntry->GetAccessible(), UNO_QUERY_THROW );
xSel->isAccessibleChildSelected(nChildIndex);
}
return true;
}
else
{
return false;
}
}
nChildIndex -= nCount;
}
}
throw IndexOutOfBoundsException();
}
void ToolbarMenu_Impl::clearAccessibleSelection()
{
if( mnHighlightedEntry != -1 )
{
mrMenu.Invalidate();
mnHighlightedEntry = -1;
}
}
void ToolbarMenu_Impl::notifyHighlightedEntry()
{
if( !(mxAccessible.is() && mxAccessible->HasAccessibleListeners()) )
return;
ToolbarMenuEntry* pEntry = implGetEntry( mnHighlightedEntry );
if( !(pEntry && pEntry->mbEnabled && (pEntry->mnEntryId != TITLE_ID)) )
return;
Any aNew;
Any aOld( mxOldSelection );
if( pEntry->mpControl )
{
sal_Int32 nChildIndex = 0;
// todo: if other controls than ValueSet are allowed, adapt this code
ValueSet* pValueSet = dynamic_cast< ValueSet* >( pEntry->mpControl.get() );
if( pValueSet )
nChildIndex = static_cast< sal_Int32 >( pValueSet->GetItemPos( pValueSet->GetSelectedItemId() ) );
if( (nChildIndex >= pEntry->getAccessibleChildCount()) || (nChildIndex < 0) )
return;
aNew <<= getAccessibleChild( pEntry->mpControl, nChildIndex );
}
else
{
aNew <<= pEntry->GetAccessible();
}
fireAccessibleEvent( AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOld, aNew );
fireAccessibleEvent( AccessibleEventId::SELECTION_CHANGED, aOld, aNew );
fireAccessibleEvent( AccessibleEventId::STATE_CHANGED, Any(), Any( AccessibleStateType::FOCUSED ) );
aNew >>= mxOldSelection;
}
ToolbarMenuEntry* ToolbarMenu_Impl::implGetEntry( int nEntry ) const
{
if( (nEntry < 0) || (nEntry >= static_cast<int>(maEntryVector.size()) ) )
return nullptr;
return maEntryVector[nEntry].get();
}
IMPL_LINK_NOARG( ToolbarMenu, HighlightHdl, ValueSet*, void )
{
mpImpl->notifyHighlightedEntry();
}
ToolbarMenu::ToolbarMenu( const css::uno::Reference<css::frame::XFrame>& rFrame, vcl::Window* pParentWindow, WinBits nBits )
: ToolbarPopup(rFrame, pParentWindow, nBits)
{
mpImpl.reset( new ToolbarMenu_Impl( *this ) );
const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
SetControlBackground( rStyleSettings.GetMenuColor() );
initWindow();
}
ToolbarMenu::~ToolbarMenu()
{
disposeOnce();
}
void ToolbarMenu::dispose()
{
mpImpl->mxAccessible.clear();
std::unique_ptr<ToolbarMenu_Impl> pImpl = std::move(mpImpl);
mpImpl.reset();
pImpl->maEntryVector.clear();
ToolbarPopup::dispose();
}
int ToolbarMenu::getSelectedEntryId() const
{
ToolbarMenuEntry* pEntry = implGetEntry( mpImpl->mnSelectedEntry );
return pEntry ? pEntry->mnEntryId : -1;
}
int ToolbarMenu::getHighlightedEntryId() const
{
ToolbarMenuEntry* pEntry = implGetEntry( mpImpl->mnHighlightedEntry );
return pEntry ? pEntry->mnEntryId : -1;
}
void ToolbarMenu::checkEntry( int nEntryId, bool bChecked )
{
ToolbarMenuEntry* pEntry = implSearchEntry( nEntryId );
if( pEntry && pEntry->mbChecked != bChecked )
{
pEntry->mbChecked = bChecked;
Invalidate();
}
}
void ToolbarMenu::enableEntry( int nEntryId, bool bEnable )
{
ToolbarMenuEntry* pEntry = implSearchEntry( nEntryId );
if( pEntry && pEntry->mbEnabled != bEnable )
{
pEntry->mbEnabled = bEnable;
if( pEntry->mpControl )
{
pEntry->mpControl->Enable( bEnable );
// hack for the valueset to make it paint itself anew
pEntry->mpControl->Resize();
}
Invalidate();
}
}
void ToolbarMenu::setEntryText( int nEntryId, const OUString& rStr )
{
ToolbarMenuEntry* pEntry = implSearchEntry( nEntryId );
if( pEntry && pEntry->maText != rStr )
{
pEntry->maText = rStr;
mpImpl->maSize = implCalcSize();
if( IsVisible() )
Invalidate();
}
}
void ToolbarMenu::setEntryImage( int nEntryId, const Image& rImage )
{
ToolbarMenuEntry* pEntry = implSearchEntry( nEntryId );
if( pEntry && pEntry->maImage != rImage )
{
pEntry->maImage = rImage;
mpImpl->maSize = implCalcSize();
if( IsVisible() )
Invalidate();
}
}
void ToolbarMenu::initWindow()
{
const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
// FIXME RenderContext
SetPointFont(*this, rStyleSettings.GetMenuFont());
SetBackground(Wallpaper(GetControlBackground()));
SetTextColor(rStyleSettings.GetMenuTextColor());
SetTextFillColor();
SetLineColor();
mpImpl->maSize = implCalcSize();
}
static long ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext, long& rCheckHeight, long& rRadioHeight, long &rMaxWidth )
{
rMaxWidth = rCheckHeight = rRadioHeight = 0;
ImplControlValue aVal;
tools::Rectangle aNativeBounds;
tools::Rectangle aNativeContent;
Point tmp( 0, 0 );
tools::Rectangle aCtrlRegion( tmp, Size( 100, 15 ) );
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemCheckMark))
{
if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemCheckMark,
aCtrlRegion, ControlState::ENABLED, aVal,
aNativeBounds, aNativeContent)
)
{
rCheckHeight = aNativeBounds.GetHeight();
rMaxWidth = aNativeContent.GetWidth();
}
}
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark))
{
if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark,
aCtrlRegion, ControlState::ENABLED, aVal,
aNativeBounds, aNativeContent)
)
{
rRadioHeight = aNativeBounds.GetHeight();
rMaxWidth = std::max (rMaxWidth, aNativeContent.GetWidth());
}
}
return std::max(rCheckHeight, rRadioHeight);
}
#define gfxExtra 7
Size ToolbarMenu::implCalcSize()
{
const long nFontHeight = GetTextHeight();
long nExtra = nFontHeight/4;
Size aSz;
Size aMaxImgSz;
long nMaxTextWidth = 0;
long nMinMenuItemHeight = nFontHeight+2;
const StyleSettings& rSettings = GetSettings().GetStyleSettings();
const bool bUseImages = rSettings.GetUseImagesInMenus();
// get maximum image size
if( bUseImages )
{
for( const auto& pEntry : mpImpl->maEntryVector )
{
if( pEntry && pEntry->mbHasImage )
{
Size aImgSz( pEntry->maImage.GetSizePixel() );
nMinMenuItemHeight = std::max( nMinMenuItemHeight, aImgSz.Height() + 6 );
aMaxImgSz.setWidth( std::max( aMaxImgSz.Width(), aImgSz.Width() ) );
}
}
}
mpImpl->mnCheckPos = nExtra;
mpImpl->mnImagePos = nExtra;
mpImpl->mnTextPos = mpImpl->mnImagePos + aMaxImgSz.Width();
if ( aMaxImgSz.Width() )
mpImpl->mnTextPos += std::max( nExtra, 7L );
// set heights, calc maximum width
for( const auto& pEntry : mpImpl->maEntryVector )
{
if( pEntry )
{
// Text:
if( pEntry->mbHasText || pEntry->mbHasImage )
{
pEntry->maSize.setHeight( nMinMenuItemHeight );
if( pEntry->mbHasText )
{
long nTextWidth = GetCtrlTextWidth( pEntry->maText ) + mpImpl->mnTextPos + nExtra;
nMaxTextWidth = std::max( nTextWidth, nMaxTextWidth );
}
}
// Control:
else if( pEntry->mpControl )
{
Size aControlSize( pEntry->mpControl->GetOutputSizePixel() );
nMaxTextWidth = std::max( aControlSize.Width(), nMaxTextWidth );
pEntry->maSize.setHeight( aControlSize.Height() + 1 );
}
if( pEntry->HasCheck() && !pEntry->mbHasImage )
{
if (IsNativeControlSupported(ControlType::MenuPopup, (pEntry->mnBits & MenuItemBits::RADIOCHECK)
? ControlPart::MenuItemCheckMark
: ControlPart::MenuItemRadioMark ) )
{
long nCheckHeight = 0, nRadioHeight = 0, nMaxCheckWidth = 0;
ImplGetNativeCheckAndRadioSize(*this, nCheckHeight, nRadioHeight, nMaxCheckWidth);
long nCtrlHeight = (pEntry->mnBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight;
nMaxTextWidth += nCtrlHeight + gfxExtra;
}
else if( pEntry->mbChecked )
{
long nSymbolWidth = (nFontHeight*25)/40;
if ( pEntry->mnBits & MenuItemBits::RADIOCHECK )
nSymbolWidth = nFontHeight/2;
nMaxTextWidth += nSymbolWidth;
}
}
}
}
aSz.setWidth( nMaxTextWidth + (BORDER_X<<1) );
// positionate controls
int nY = BORDER_Y;
for( const auto& pEntry : mpImpl->maEntryVector )
{
if (pEntry)
{
pEntry->maSize.setWidth( nMaxTextWidth );
if( pEntry->mpControl )
{
Size aControlSize( pEntry->mpControl->GetOutputSizePixel() );
Point aControlPos( (aSz.Width() - aControlSize.Width())>>1, nY);
pEntry->mpControl->SetPosPixel( aControlPos );
pEntry->maRect = tools::Rectangle( aControlPos, aControlSize );
}
else
{
pEntry->maRect = tools::Rectangle( Point( 0, nY ), pEntry->maSize );
}
nY += pEntry->maSize.Height();
}
else
{
nY += SEPARATOR_HEIGHT;
}
}
aSz.AdjustHeight(nY + BORDER_Y );
return aSz;
}
void ToolbarMenu::GetFocus()
{
if( mpImpl && mpImpl->mnHighlightedEntry == -1 )
implChangeHighlightEntry( 0 );
ToolbarPopup::GetFocus();
}
void ToolbarMenu::LoseFocus()
{
if( mpImpl && mpImpl->mnHighlightedEntry != -1 )
implChangeHighlightEntry( -1 );
ToolbarPopup::LoseFocus();
}
void ToolbarMenu::appendEntry( int nEntryId, const OUString& rStr, MenuItemBits nItemBits )
{
appendEntry( o3tl::make_unique<ToolbarMenuEntry>( *this, nEntryId, rStr, nItemBits ) );
}
void ToolbarMenu::appendEntry( int nEntryId, const OUString& rStr, const Image& rImage )
{
appendEntry( o3tl::make_unique<ToolbarMenuEntry>( *this, nEntryId, rImage, rStr, MenuItemBits::NONE ) );
}
void ToolbarMenu::appendEntry( int nEntryId, Control* pControl )
{
appendEntry( o3tl::make_unique<ToolbarMenuEntry>( *this, nEntryId, pControl, MenuItemBits::NONE ) );
}
void ToolbarMenu::appendEntry( std::unique_ptr<ToolbarMenuEntry> pEntry )
{
mpImpl->maEntryVector.push_back(std::move(pEntry));
mpImpl->maSize = implCalcSize();
if (IsVisible())
Invalidate();
}
void ToolbarMenu::appendSeparator()
{
appendEntry( nullptr );
}
/** creates an empty ValueSet that is initialized and can be inserted with appendEntry. */
VclPtr<ValueSet> ToolbarMenu::createEmptyValueSetControl()
{
VclPtr<ValueSet> pSet = VclPtr<ValueSet>::Create( this, WB_TABSTOP | WB_MENUSTYLEVALUESET | WB_FLATVALUESET | WB_NOBORDER | WB_NO_DIRECTSELECT );
pSet->EnableFullItemMode( false );
pSet->SetColor( GetControlBackground() );
pSet->SetHighlightHdl( LINK( this, ToolbarMenu, HighlightHdl ) );
return pSet;
}
ToolbarMenuEntry* ToolbarMenu::implGetEntry( int nEntry ) const
{
return mpImpl->implGetEntry( nEntry );
}
ToolbarMenuEntry* ToolbarMenu::implSearchEntry( int nEntryId ) const
{
for( const auto& p : mpImpl->maEntryVector )
{
if( p && p->mnEntryId == nEntryId )
{
return p.get();
}
}
return nullptr;
}
void ToolbarMenu::implHighlightEntry(vcl::RenderContext& rRenderContext, int nHighlightEntry)
{
Size aSz(GetOutputSizePixel());
long nX = 0;
long nY = 0;
const int nEntryCount = mpImpl->maEntryVector.size();
for (int nEntry = 0; nEntry < nEntryCount; nEntry++)
{
ToolbarMenuEntry* const pEntry = mpImpl->maEntryVector[nEntry].get();
if (pEntry && (nEntry == nHighlightEntry))
{
// no highlights for controls only items
if (pEntry->mpControl)
{
break;
}
bool bRestoreLineColor = false;
Color oldLineColor;
bool bDrawItemRect = true;
tools::Rectangle aItemRect(Point(nX, nY), Size(aSz.Width(), pEntry->maSize.Height()));
if (pEntry->mnBits & MenuItemBits::POPUPSELECT)
{
long nFontHeight = GetTextHeight();
aItemRect.AdjustRight( -(nFontHeight + nFontHeight / 4) );
}
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
{
Size aPxSize(GetOutputSizePixel());
rRenderContext.Push(PushFlags::CLIPREGION);
rRenderContext.IntersectClipRegion(tools::Rectangle(Point(nX, nY), Size(aSz.Width(), pEntry->maSize.Height())));
tools::Rectangle aCtrlRect(Point(nX, 0), Size(aPxSize.Width() - nX, aPxSize.Height()));
rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect,
ControlState::ENABLED, ImplControlValue(), OUString());
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem))
{
bDrawItemRect = false;
ControlState eState = ControlState::SELECTED | (pEntry->mbEnabled ? ControlState::ENABLED : ControlState::NONE);
if (!rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem, aItemRect,
eState, ImplControlValue(), OUString()))
{
bDrawItemRect = true;
}
}
else
{
bDrawItemRect = true;
}
rRenderContext.Pop();
}
if (bDrawItemRect)
{
if (pEntry->mbEnabled)
{
rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
}
else
{
rRenderContext.SetFillColor();
oldLineColor = rRenderContext.GetLineColor();
rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor());
bRestoreLineColor = true;
}
rRenderContext.DrawRect(aItemRect);
}
implPaint(rRenderContext, pEntry, true/*bHighlight*/);
if (bRestoreLineColor)
rRenderContext.SetLineColor(oldLineColor);
break;
}
nY += pEntry ? pEntry->maSize.Height() : SEPARATOR_HEIGHT;
}
}
void ToolbarMenu::implSelectEntry( int nSelectedEntry )
{
mpImpl->mnSelectedEntry = nSelectedEntry;
ToolbarMenuEntry* pEntry = nullptr;
if( nSelectedEntry != -1 )
pEntry = mpImpl->maEntryVector[ nSelectedEntry ].get();
if( pEntry )
mpImpl->maSelectHdl.Call( this );
}
void ToolbarMenu::MouseButtonDown( const MouseEvent& rMEvt )
{
implHighlightAtPosition(rMEvt);
implSelectEntry(mpImpl->mnHighlightedEntry);
}
void ToolbarMenu::MouseButtonUp( const MouseEvent& )
{
}
void ToolbarMenu::MouseMove( const MouseEvent& rMEvt )
{
if (!IsVisible())
return;
implHighlightAtPosition(rMEvt);
}
void ToolbarMenu::implHighlightAtPosition(const MouseEvent& rMEvt)
{
long nMouseY = rMEvt.GetPosPixel().Y();
Size aOutSz = GetOutputSizePixel();
if ( ( nMouseY >= 0 ) && ( nMouseY < aOutSz.Height() ) )
{
long nY = 0;
bool bHighlighted = false;
const int nEntryCount = mpImpl->maEntryVector.size();
for( int nEntry = 0; nEntry < nEntryCount; nEntry++ )
{
ToolbarMenuEntry* pEntry = mpImpl->maEntryVector[nEntry].get();
if( pEntry )
{
long nOldY = nY;
nY += pEntry->maSize.Height();
if( pEntry->mnEntryId != TITLE_ID )
{
if ( ( nOldY <= nMouseY ) && ( nY > nMouseY ) )
{
if( nEntry != mpImpl->mnHighlightedEntry )
{
implChangeHighlightEntry( nEntry );
}
bHighlighted = true;
}
}
}
else
{
nY += SEPARATOR_HEIGHT;
}
}
if ( !bHighlighted )
implChangeHighlightEntry( -1 );
}
else
{
implChangeHighlightEntry( -1 );
}
}
void ToolbarMenu::implChangeHighlightEntry(int nEntry)
{
mpImpl->mnHighlightedEntry = nEntry;
Invalidate();
mpImpl->notifyHighlightedEntry();
}
static bool implCheckSubControlCursorMove( Control* pControl, bool bUp, int& nLastColumn )
{
ValueSet* pValueSet = dynamic_cast< ValueSet* >( pControl );
if( pValueSet )
{
size_t nItemPos = pValueSet->GetItemPos( pValueSet->GetSelectedItemId() );
if( nItemPos != VALUESET_ITEM_NOTFOUND )
{
const sal_uInt16 nColCount = pValueSet->GetColCount();
const size_t nLine = nItemPos / nColCount;
nLastColumn = nItemPos - (nLine * nColCount);
if( bUp )
{
return nLine > 0;
}
else
{
const size_t nLineCount = (pValueSet->GetItemCount() + nColCount - 1) / nColCount;
return (nLine+1) < nLineCount;
}
}
}
return false;
}
ToolbarMenuEntry* ToolbarMenu::implCursorUpDown( bool bUp, bool bHomeEnd )
{
int n = 0, nLoop = 0;
if( !bHomeEnd )
{
n = mpImpl->mnHighlightedEntry;
if( n == -1 )
{
if( bUp )
n = 0;
else
n = mpImpl->maEntryVector.size()-1;
}
else
{
// if we have a currently selected entry and
// cursor keys are used than check if this entry
// has a control that can use those cursor keys
ToolbarMenuEntry* pData = mpImpl->maEntryVector[n].get();
if( pData && pData->mpControl && !pData->mbHasText )
{
if( implCheckSubControlCursorMove( pData->mpControl, bUp, mpImpl->mnLastColumn ) )
return pData;
}
}
nLoop = n;
}
else
{
// absolute positioning
if( bUp )
{
n = mpImpl->maEntryVector.size();
nLoop = n-1;
}
else
{
n = -1;
nLoop = mpImpl->maEntryVector.size()-1;
}
}
do
{
if( bUp )
{
if ( n )
n--;
else
if( mpImpl->mnHighlightedEntry == -1 )
n = mpImpl->maEntryVector.size()-1;
else
break;
}
else
{
if( n < (static_cast<int>(mpImpl->maEntryVector.size())-1) )
n++;
else
if( mpImpl->mnHighlightedEntry == -1 )
n = 0;
else
break;
}
ToolbarMenuEntry* pData = mpImpl->maEntryVector[n].get();
if( pData && (pData->mnEntryId != TITLE_ID) )
{
implChangeHighlightEntry( n );
return pData;
}
} while ( n != nLoop );
return nullptr;
}
void ToolbarMenu_Impl::implHighlightControl( sal_uInt16 nCode, Control* pControl )
{
ValueSet* pValueSet = dynamic_cast< ValueSet* >( pControl );
if( !pValueSet )
return;
const size_t nItemCount = pValueSet->GetItemCount();
size_t nItemPos = VALUESET_ITEM_NOTFOUND;
switch( nCode )
{
case KEY_UP:
{
const sal_uInt16 nColCount = pValueSet->GetColCount();
const sal_uInt16 nLastLine = nItemCount / nColCount;
nItemPos = std::min( static_cast<size_t>(((nLastLine-1) * nColCount) + mnLastColumn), nItemCount-1 );
break;
}
case KEY_DOWN:
nItemPos = std::min( static_cast<size_t>(mnLastColumn), nItemCount-1 );
break;
case KEY_END:
nItemPos = nItemCount -1;
break;
case KEY_HOME:
nItemPos = 0;
break;
}
pValueSet->SelectItem( pValueSet->GetItemId( nItemPos ) );
notifyHighlightedEntry();
}
void ToolbarMenu::KeyInput( const KeyEvent& rKEvent )
{
Control* pForwardControl = nullptr;
sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode();
switch ( nCode )
{
case KEY_UP:
case KEY_DOWN:
{
int nOldEntry = mpImpl->mnHighlightedEntry;
ToolbarMenuEntry*p = implCursorUpDown( nCode == KEY_UP, false );
if( p && p->mpControl )
{
if( nOldEntry != mpImpl->mnHighlightedEntry )
{
mpImpl->implHighlightControl( nCode, p->mpControl );
}
else
{
// in case we are in a system floating window, GrabFocus does not work :-/
pForwardControl = p->mpControl;
}
}
}
break;
case KEY_END:
case KEY_HOME:
{
ToolbarMenuEntry* p = implCursorUpDown( nCode == KEY_END, true );
if( p && p->mpControl )
{
mpImpl->implHighlightControl( nCode, p->mpControl );
}
}
break;
case KEY_F6:
case KEY_ESCAPE:
{
// Ctrl-F6 acts like ESC here, the menu bar however will then put the focus in the document
if( nCode == KEY_F6 && !rKEvent.GetKeyCode().IsMod1() )
break;
implSelectEntry( -1 );
}
break;
case KEY_RETURN:
{
ToolbarMenuEntry* pEntry = implGetEntry( mpImpl->mnHighlightedEntry );
if ( pEntry && pEntry->mbEnabled && (pEntry->mnEntryId != TITLE_ID) )
{
if( pEntry->mpControl )
{
pForwardControl = pEntry->mpControl;
}
else
{
implSelectEntry( mpImpl->mnHighlightedEntry );
}
}
}
break;
default:
{
ToolbarMenuEntry* pEntry = implGetEntry( mpImpl->mnHighlightedEntry );
if ( pEntry && pEntry->mbEnabled && pEntry->mpControl && !pEntry->mbHasText )
{
pForwardControl = pEntry->mpControl;
}
}
}
if( pForwardControl )
pForwardControl->KeyInput( rKEvent );
}
static void ImplPaintCheckBackground(vcl::RenderContext& rRenderContext, vcl::Window const & rWindow, const tools::Rectangle& i_rRect, bool i_bHighlight )
{
bool bNativeOk = false;
if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button))
{
ImplControlValue aControlValue;
aControlValue.setTristateVal(ButtonValue::On);
bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button,
i_rRect,
ControlState::PRESSED | ControlState::ENABLED,
aControlValue, OUString());
}
if (!bNativeOk)
{
const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
Color aColor(i_bHighlight ? rSettings.GetMenuHighlightTextColor() : rSettings.GetHighlightColor());
vcl::RenderTools::DrawSelectionBackground(rRenderContext, rWindow, i_rRect, 0, i_bHighlight, true, false, nullptr, 2, &aColor);
}
}
void ToolbarMenu::implPaint(vcl::RenderContext& rRenderContext, ToolbarMenuEntry const * pThisOnly, bool bHighlighted)
{
long nFontHeight = GetTextHeight();
long nCheckHeight = 0, nRadioHeight = 0, nMaxCheckWidth = 0;
ImplGetNativeCheckAndRadioSize(rRenderContext, nCheckHeight, nRadioHeight, nMaxCheckWidth);
DecorationView aDecoView(&rRenderContext);
const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
const bool bUseImages = rSettings.GetUseImagesInMenus();
int nOuterSpace = 0; // ImplGetSVData()->maNWFData.mnMenuFormatExtraBorder;
Point aTopLeft(nOuterSpace, nOuterSpace), aTmpPos;
Size aOutSz(GetOutputSizePixel());
for (const auto& pEntry : mpImpl->maEntryVector)
{
Point aPos(aTopLeft);
if ((pEntry == nullptr) && !pThisOnly)
{
// Separator
aTmpPos.setY( aPos.Y() + ((SEPARATOR_HEIGHT - 2) / 2) );
aTmpPos.setX( aPos.X() + 2 + nOuterSpace );
rRenderContext.SetLineColor(rSettings.GetShadowColor());
rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpace, aTmpPos.Y()));
aTmpPos.AdjustY( 1 );
rRenderContext.SetLineColor(rSettings.GetLightColor());
rRenderContext.DrawLine(aTmpPos, Point( aOutSz.Width() - 3 - 2 * nOuterSpace, aTmpPos.Y()));
rRenderContext.SetLineColor();
}
else if (!pThisOnly || (pEntry.get() == pThisOnly))
{
const bool bTitle = pEntry->mnEntryId == TITLE_ID;
if (pThisOnly && bHighlighted)
rRenderContext.SetTextColor(rSettings.GetMenuHighlightTextColor());
if( aPos.Y() >= 0 )
{
long nTextOffsetY = ((pEntry->maSize.Height() - nFontHeight) / 2);
DrawTextFlags nTextStyle = DrawTextFlags::NONE;
DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
DrawImageFlags nImageStyle = DrawImageFlags::NONE;
if (!pEntry->mbEnabled)
{
nTextStyle |= DrawTextFlags::Disable;
nSymbolStyle |= DrawSymbolFlags::Disable;
nImageStyle |= DrawImageFlags::Disable;
}
tools::Rectangle aOuterCheckRect(Point(aPos.X() + mpImpl->mnCheckPos, aPos.Y()),
Size(pEntry->maSize.Height(), pEntry->maSize.Height()));
aOuterCheckRect.AdjustLeft(1 );
aOuterCheckRect.AdjustRight( -1 );
aOuterCheckRect.AdjustTop(1 );
aOuterCheckRect.AdjustBottom( -1 );
if (bTitle)
{
// fill the background
tools::Rectangle aRect(aTopLeft, Size(aOutSz.Width(), pEntry->maSize.Height()));
rRenderContext.SetFillColor(rSettings.GetDialogColor());
rRenderContext.SetLineColor();
rRenderContext.DrawRect(aRect);
rRenderContext.SetLineColor(rSettings.GetLightColor());
rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
rRenderContext.SetLineColor(rSettings.GetShadowColor());
rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
}
// CheckMark
if (pEntry->HasCheck())
{
// draw selection transparent marker if checked
// onto that either a checkmark or the item image
// will be painted
// however do not do this if native checks will be painted since
// the selection color too often does not fit the theme's check and/or radio
if (!pEntry->mbHasImage)
{
if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup,
(pEntry->mnBits & MenuItemBits::RADIOCHECK)
? ControlPart::MenuItemCheckMark
: ControlPart::MenuItemRadioMark))
{
ControlPart nPart = ((pEntry->mnBits & MenuItemBits::RADIOCHECK)
? ControlPart::MenuItemRadioMark
: ControlPart::MenuItemCheckMark);
ControlState nState = ControlState::NONE;
if (pEntry->mbChecked)
nState |= ControlState::PRESSED;
if (pEntry->mbEnabled)
nState |= ControlState::ENABLED;
if ( bHighlighted )
nState |= ControlState::SELECTED;
long nCtrlHeight = (pEntry->mnBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight;
aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - nCtrlHeight) / 2 );
aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - nCtrlHeight) / 2 );
tools::Rectangle aCheckRect(aTmpPos, Size(nCtrlHeight, nCtrlHeight));
rRenderContext.DrawNativeControl(ControlType::MenuPopup, nPart, aCheckRect,
nState, ImplControlValue(), OUString());
aPos.setX(aPos.getX() + nCtrlHeight + gfxExtra);
}
else if (pEntry->mbChecked) // by default do nothing for unchecked items
{
ImplPaintCheckBackground(rRenderContext, *this, aOuterCheckRect, pThisOnly && bHighlighted);
SymbolType eSymbol;
Size aSymbolSize;
if (pEntry->mnBits & MenuItemBits::RADIOCHECK)
{
eSymbol = SymbolType::RADIOCHECKMARK;
aSymbolSize = Size(nFontHeight / 2, nFontHeight / 2);
}
else
{
eSymbol = SymbolType::CHECKMARK;
aSymbolSize = Size((nFontHeight * 25) / 40, nFontHeight / 2);
}
aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - aSymbolSize.Width())/2 );
aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - aSymbolSize.Height())/2 );
tools::Rectangle aRect( aTmpPos, aSymbolSize );
aDecoView.DrawSymbol(aRect, eSymbol, GetTextColor(), nSymbolStyle);
aPos.setX(aPos.getX() + aSymbolSize.getWidth( ) + gfxExtra);
}
}
}
// Image:
if (pEntry->mbHasImage && bUseImages)
{
if (pEntry->mbChecked)
ImplPaintCheckBackground(rRenderContext, *this, aOuterCheckRect, pThisOnly && bHighlighted);
aTmpPos = aOuterCheckRect.TopLeft();
aTmpPos.AdjustX((aOuterCheckRect.GetWidth()-pEntry->maImage.GetSizePixel().Width())/2 );
aTmpPos.AdjustY((aOuterCheckRect.GetHeight()-pEntry->maImage.GetSizePixel().Height())/2 );
rRenderContext.DrawImage( aTmpPos, pEntry->maImage, nImageStyle );
}
// Text:
if (pEntry->mbHasText)
{
aTmpPos.setX( aPos.X() + (bTitle ? 4 : mpImpl->mnTextPos) );
aTmpPos.setY( aPos.Y() );
aTmpPos.AdjustY(nTextOffsetY );
DrawTextFlags nStyle = nTextStyle|DrawTextFlags::Mnemonic;
rRenderContext.DrawCtrlText(aTmpPos, pEntry->maText, 0, pEntry->maText.getLength(), nStyle);
}
if (pThisOnly && bHighlighted)
{
// This restores the normal menu or menu bar text
// color for when it is no longer highlighted.
rRenderContext.SetTextColor(rSettings.GetMenuTextColor());
}
}
}
aTopLeft.AdjustY(pEntry ? pEntry->maSize.Height() : SEPARATOR_HEIGHT );
}
}
void ToolbarMenu::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuColor());
implPaint(rRenderContext);
if (mpImpl->mnHighlightedEntry != -1)
implHighlightEntry(rRenderContext, mpImpl->mnHighlightedEntry);
}
void ToolbarMenu::StateChanged( StateChangedType nType )
{
ToolbarPopup::StateChanged( nType );
if ( ( nType == StateChangedType::ControlForeground ) || ( nType == StateChangedType::ControlBackground ) )
{
initWindow();
Invalidate();
}
}
void ToolbarMenu::DataChanged( const DataChangedEvent& rDCEvt )
{
ToolbarPopup::DataChanged( rDCEvt );
if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
(rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
(rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
{
initWindow();
Invalidate();
}
}
void ToolbarMenu::Command( const CommandEvent& rCEvt )
{
if ( rCEvt.GetCommand() == CommandEventId::Wheel )
{
const CommandWheelData* pData = rCEvt.GetWheelData();
if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
{
implCursorUpDown( pData->GetDelta() > 0, false );
}
}
}
Reference< css::accessibility::XAccessible > ToolbarMenu::CreateAccessible()
{
mpImpl->setAccessible( new ToolbarMenuAcc( *mpImpl ) );
return Reference< XAccessible >( mpImpl->mxAccessible.get() );
}
class ToolbarPopupStatusListener : public svt::FrameStatusListener
{
public:
ToolbarPopupStatusListener( const css::uno::Reference< css::frame::XFrame >& xFrame,
ToolbarPopupBase& rToolbarPopup );
virtual void SAL_CALL dispose() override;
virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override;
ToolbarPopupBase* mpPopup;
};
ToolbarPopupStatusListener::ToolbarPopupStatusListener(
const css::uno::Reference< css::frame::XFrame >& xFrame,
ToolbarPopupBase& rToolbarPopup )
: svt::FrameStatusListener( ::comphelper::getProcessComponentContext(), xFrame )
, mpPopup( &rToolbarPopup )
{
}
void SAL_CALL ToolbarPopupStatusListener::dispose()
{
mpPopup = nullptr;
svt::FrameStatusListener::dispose();
}
void SAL_CALL ToolbarPopupStatusListener::statusChanged( const css::frame::FeatureStateEvent& Event )
{
if( mpPopup )
mpPopup->statusChanged( Event );
}
ToolbarPopupBase::ToolbarPopupBase(const css::uno::Reference<css::frame::XFrame>& rFrame)
: mxFrame(rFrame)
{
}
ToolbarPopupBase::~ToolbarPopupBase()
{
if (mxStatusListener.is())
{
mxStatusListener->dispose();
mxStatusListener.clear();
}
}
ToolbarPopup::ToolbarPopup( const css::uno::Reference<css::frame::XFrame>& rFrame, vcl::Window* pParentWindow, WinBits nBits )
: DockingWindow(pParentWindow, nBits)
, ToolbarPopupBase(rFrame)
{
init();
}
ToolbarPopup::ToolbarPopup( const css::uno::Reference<css::frame::XFrame>& rFrame, vcl::Window* pParentWindow,
const OString& rID, const OUString& rUIXMLDescription )
: DockingWindow(pParentWindow, rID, rUIXMLDescription, rFrame)
, ToolbarPopupBase(rFrame)
{
init();
}
void ToolbarPopup::init()
{
vcl::Window* pWindow = GetTopMostParentSystemWindow( this );
if ( pWindow )
static_cast<SystemWindow*>(pWindow)->GetTaskPaneList()->AddWindow( this );
}
ToolbarPopup::~ToolbarPopup()
{
disposeOnce();
}
void ToolbarPopup::dispose()
{
vcl::Window* pWindow = GetTopMostParentSystemWindow( this );
if ( pWindow )
static_cast<SystemWindow*>(pWindow)->GetTaskPaneList()->RemoveWindow( this );
if ( mxStatusListener.is() )
{
mxStatusListener->dispose();
mxStatusListener.clear();
}
mxFrame.clear();
DockingWindow::dispose();
}
void ToolbarPopupBase::AddStatusListener( const OUString& rCommandURL )
{
if( !mxStatusListener.is() )
mxStatusListener.set( new ToolbarPopupStatusListener( mxFrame, *this ) );
mxStatusListener->addStatusListener( rCommandURL );
}
void ToolbarPopupBase::statusChanged( const css::frame::FeatureStateEvent& /*Event*/ )
{
}
bool ToolbarPopup::IsInPopupMode()
{
return GetDockingManager()->IsInPopupMode(this);
}
void ToolbarPopup::EndPopupMode()
{
GetDockingManager()->EndPopupMode(this);
}
const Size& ToolbarMenu::getMenuSize() const
{
return mpImpl->maSize;
}
void ToolbarMenu::SetSelectHdl( const Link<ToolbarMenu*,void>& rLink )
{
mpImpl->maSelectHdl = rLink;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1001 The 'aNew' variable is assigned but is not used by the end of the function.