office-gobmx/vcl/source/control/tabctrl.cxx
Michael Weghorn 59cba4298d notebookbar: Pass menu button as parent for its menu
Use the Notebookbar menu button as parent for its
menu. This also makes the button position be used
as the reference.

This allows to drop the now unused
NotebookbarTabControlBase::GetHeaderHeight.

Also, set the reference point to be at the bottom
of the button, so the menu opens below it instead
of approximately in the middle, which matches
how other menu buttons behave.

To test: Set "Tabbed" for the UI variant in
"View" -> "User Interface", then click the
"Hamburger menu" button in the notebookbar.

Change-Id: I0717fad73ff7a42d2bdaaa53a73d055234003d3c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176927
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
Tested-by: Jenkins
2024-11-22 00:15:46 +01:00

2401 lines
75 KiB
C++

/* -*- 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 <sal/config.h>
#include <sal/log.hxx>
#include <vcl/notebookbar/notebookbar.hxx>
#include <vcl/svapp.hxx>
#include <vcl/help.hxx>
#include <vcl/event.hxx>
#include <vcl/menu.hxx>
#include <vcl/toolkit/button.hxx>
#include <vcl/tabpage.hxx>
#include <vcl/tabctrl.hxx>
#include <vcl/toolbox.hxx>
#include <vcl/layout.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/toolkit/lstbox.hxx>
#include <vcl/settings.hxx>
#include <vcl/uitest/uiobject.hxx>
#include <bitmaps.hlst>
#include <tools/json_writer.hxx>
#include <svdata.hxx>
#include <window.h>
#include <deque>
#include <unordered_map>
#include <vector>
#define TAB_OFFSET 3
/// Space to the left and right of the tabitem
#define TAB_ITEM_OFFSET_X 10
/// Space to the top and bottom of the tabitem
#define TAB_ITEM_OFFSET_Y 3
#define TAB_EXTRASPACE_X 6
#define TAB_BORDER_LEFT 1
#define TAB_BORDER_TOP 1
#define TAB_BORDER_RIGHT 2
#define TAB_BORDER_BOTTOM 2
class ImplTabItem final
{
sal_uInt16 m_nId;
public:
VclPtr<TabPage> mpTabPage;
OUString maText;
OUString maFormatText;
OUString maHelpText;
OUString maAccessibleName;
OUString maAccessibleDescription;
OUString maTabName;
tools::Rectangle maRect;
sal_uInt16 mnLine;
bool mbFullVisible;
bool m_bEnabled; ///< the tab / page is selectable
bool m_bVisible; ///< the tab / page can be visible
Image maTabImage;
ImplTabItem(sal_uInt16 nId);
sal_uInt16 id() const { return m_nId; }
};
ImplTabItem::ImplTabItem(sal_uInt16 nId)
: m_nId(nId)
, mnLine(0)
, mbFullVisible(false)
, m_bEnabled(true)
, m_bVisible(true)
{
}
struct ImplTabCtrlData
{
std::vector< ImplTabItem > maItemList;
VclPtr<ListBox> mpListBox;
};
// for the Tab positions
#define TAB_PAGERECT 0xFFFF
void TabControl::ImplInit( vcl::Window* pParent, WinBits nStyle )
{
mbLayoutDirty = true;
if ( !(nStyle & WB_NOTABSTOP) )
nStyle |= WB_TABSTOP;
if ( !(nStyle & WB_NOGROUP) )
nStyle |= WB_GROUP;
if ( !(nStyle & WB_NODIALOGCONTROL) )
nStyle |= WB_DIALOGCONTROL;
Control::ImplInit( pParent, nStyle, nullptr );
mnLastWidth = 0;
mnLastHeight = 0;
mnActPageId = 0;
mnCurPageId = 0;
mbFormat = true;
mbShowTabs = true;
mbRestoreHelpId = false;
mbSmallInvalidate = false;
mpTabCtrlData.reset(new ImplTabCtrlData);
mpTabCtrlData->mpListBox = nullptr;
ImplInitSettings( true );
if( nStyle & WB_DROPDOWN )
{
mpTabCtrlData->mpListBox = VclPtr<ListBox>::Create( this, WB_DROPDOWN );
mpTabCtrlData->mpListBox->SetPosSizePixel( Point( 0, 0 ), Size( 200, 20 ) );
mpTabCtrlData->mpListBox->SetSelectHdl( LINK( this, TabControl, ImplListBoxSelectHdl ) );
mpTabCtrlData->mpListBox->Show();
}
// if the tabcontrol is drawn (ie filled) by a native widget, make sure all controls will have transparent background
// otherwise they will paint with a wrong background
if( IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire) )
EnableChildTransparentMode();
if (pParent && pParent->IsDialog())
pParent->AddChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
}
const vcl::Font& TabControl::GetCanonicalFont( const StyleSettings& _rStyle ) const
{
return _rStyle.GetTabFont();
}
const Color& TabControl::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
{
return _rStyle.GetTabTextColor();
}
void TabControl::ImplInitSettings( bool bBackground )
{
Control::ImplInitSettings();
if ( !bBackground )
return;
vcl::Window* pParent = GetParent();
if ( !IsControlBackground() &&
(pParent->IsChildTransparentModeEnabled()
|| IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire)
|| IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) ) )
{
// set transparent mode for NWF tabcontrols to have
// the background always cleared properly
EnableChildTransparentMode();
SetParentClipMode( ParentClipMode::NoClip );
SetPaintTransparent( true );
SetBackground();
ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
}
else
{
EnableChildTransparentMode( false );
SetParentClipMode();
SetPaintTransparent( false );
if ( IsControlBackground() )
SetBackground( GetControlBackground() );
else
SetBackground( pParent->GetBackground() );
}
}
TabControl::TabControl( vcl::Window* pParent, WinBits nStyle ) :
Control( WindowType::TABCONTROL )
{
ImplInit( pParent, nStyle );
SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER ) ? "true" : "false") );
}
TabControl::~TabControl()
{
disposeOnce();
}
void TabControl::dispose()
{
Window *pParent = GetParent();
if (pParent && pParent->IsDialog())
GetParent()->RemoveChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
// delete TabCtrl data
if (mpTabCtrlData)
mpTabCtrlData->mpListBox.disposeAndClear();
mpTabCtrlData.reset();
Control::dispose();
}
ImplTabItem* TabControl::ImplGetItem( sal_uInt16 nId ) const
{
for (auto & item : mpTabCtrlData->maItemList)
{
if (item.id() == nId)
return &item;
}
return nullptr;
}
Size TabControl::ImplGetItemSize( ImplTabItem* pItem, tools::Long nMaxWidth )
{
pItem->maFormatText = pItem->maText;
Size aSize( GetOutDev()->GetCtrlTextWidth( pItem->maFormatText ), GetTextHeight() );
Size aImageSize( 0, 0 );
if( !!pItem->maTabImage )
{
aImageSize = pItem->maTabImage.GetSizePixel();
if( !pItem->maFormatText.isEmpty() )
aImageSize.AdjustWidth(GetTextHeight()/4 );
}
aSize.AdjustWidth(aImageSize.Width() );
if( aImageSize.Height() > aSize.Height() )
aSize.setHeight( aImageSize.Height() );
aSize.AdjustWidth(TAB_ITEM_OFFSET_X*2 );
aSize.AdjustHeight(TAB_ITEM_OFFSET_Y*2 );
tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
tools::Rectangle aBoundingRgn, aContentRgn;
const TabitemValue aControlValue(tools::Rectangle(TAB_ITEM_OFFSET_X, TAB_ITEM_OFFSET_Y,
aSize.Width() - TAB_ITEM_OFFSET_X * 2,
aSize.Height() - TAB_ITEM_OFFSET_Y * 2),
TabBarPosition::Top);
if(GetNativeControlRegion( ControlType::TabItem, ControlPart::Entire, aCtrlRegion,
ControlState::ENABLED, aControlValue,
aBoundingRgn, aContentRgn ) )
{
return aContentRgn.GetSize();
}
// For languages with short names (e.g. Chinese), because the space is
// normally only one pixel per char
if ( pItem->maFormatText.getLength() < TAB_EXTRASPACE_X )
aSize.AdjustWidth(TAB_EXTRASPACE_X-pItem->maFormatText.getLength() );
// shorten Text if needed
if ( aSize.Width()+4 >= nMaxWidth )
{
OUString aAppendStr(u"..."_ustr);
pItem->maFormatText += aAppendStr;
do
{
if (pItem->maFormatText.getLength() > aAppendStr.getLength())
pItem->maFormatText = pItem->maFormatText.replaceAt( pItem->maFormatText.getLength()-aAppendStr.getLength()-1, 1, u"" );
aSize.setWidth( GetOutDev()->GetCtrlTextWidth( pItem->maFormatText ) );
aSize.AdjustWidth(aImageSize.Width() );
aSize.AdjustWidth(TAB_ITEM_OFFSET_X*2 );
}
while ( (aSize.Width()+4 >= nMaxWidth) && (pItem->maFormatText.getLength() > aAppendStr.getLength()) );
if ( aSize.Width()+4 >= nMaxWidth )
{
pItem->maFormatText = ".";
aSize.setWidth( 1 );
}
}
if( pItem->maFormatText.isEmpty() )
{
if( aSize.Height() < aImageSize.Height()+4 ) //leave space for focus rect
aSize.setHeight( aImageSize.Height()+4 );
}
return aSize;
}
// Feel free to move this to some more general place for reuse
// http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
// Mostly based on Alexey Frunze's nifty example at
// http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
namespace MinimumRaggednessWrap
{
static std::deque<size_t> GetEndOfLineIndexes(const std::vector<sal_Int32>& rWidthsOf, sal_Int32 nLineWidth)
{
++nLineWidth;
size_t nWidthsCount = rWidthsOf.size();
std::vector<sal_Int32> aCosts(nWidthsCount * nWidthsCount);
// cost function c(i, j) that computes the cost of a line consisting of
// the words Word[i] to Word[j]
for (size_t i = 0; i < nWidthsCount; ++i)
{
for (size_t j = 0; j < nWidthsCount; ++j)
{
if (j >= i)
{
sal_Int32 c = nLineWidth - (j - i);
for (size_t k = i; k <= j; ++k)
c -= rWidthsOf[k];
c = (c >= 0) ? c * c : SAL_MAX_INT32;
aCosts[j * nWidthsCount + i] = c;
}
else
{
aCosts[j * nWidthsCount + i] = SAL_MAX_INT32;
}
}
}
std::vector<sal_Int32> aFunction(nWidthsCount);
std::vector<sal_Int32> aWrapPoints(nWidthsCount);
// f(j) in aFunction[], collect wrap points in aWrapPoints[]
for (size_t j = 0; j < nWidthsCount; ++j)
{
aFunction[j] = aCosts[j * nWidthsCount];
if (aFunction[j] == SAL_MAX_INT32)
{
for (size_t k = 0; k < j; ++k)
{
sal_Int32 s;
if (aFunction[k] == SAL_MAX_INT32 || aCosts[j * nWidthsCount + k + 1] == SAL_MAX_INT32)
s = SAL_MAX_INT32;
else
s = aFunction[k] + aCosts[j * nWidthsCount + k + 1];
if (aFunction[j] > s)
{
aFunction[j] = s;
aWrapPoints[j] = k + 1;
}
}
}
}
std::deque<size_t> aSolution;
// no solution
if (aFunction[nWidthsCount - 1] == SAL_MAX_INT32)
return aSolution;
// optimal solution
size_t j = nWidthsCount - 1;
while (true)
{
aSolution.push_front(j);
if (!aWrapPoints[j])
break;
j = aWrapPoints[j] - 1;
}
return aSolution;
}
};
static void lcl_AdjustSingleLineTabs(tools::Long nMaxWidth, ImplTabCtrlData *pTabCtrlData)
{
if (!ImplGetSVData()->maNWFData.mbCenteredTabs)
return;
int nRightSpace = nMaxWidth; // space left on the right by the tabs
for (auto const& item : pTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
nRightSpace -= item.maRect.GetWidth();
}
nRightSpace /= 2;
for (auto& item : pTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
item.maRect.AdjustLeft(nRightSpace);
item.maRect.AdjustRight(nRightSpace);
}
}
bool TabControl::ImplPlaceTabs( tools::Long nWidth )
{
if ( nWidth <= 0 )
return false;
if ( mpTabCtrlData->maItemList.empty() )
return false;
tools::Long nMaxWidth = nWidth;
const tools::Long nOffsetX = 2;
const tools::Long nOffsetY = 2;
//fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
//of ugly bare tabs on lines of their own
//collect widths
std::vector<sal_Int32> aWidths;
for (auto & item : mpTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
aWidths.push_back(ImplGetItemSize(&item, nMaxWidth).Width());
}
//aBreakIndexes will contain the indexes of the last tab on each row
std::deque<size_t> aBreakIndexes(MinimumRaggednessWrap::GetEndOfLineIndexes(aWidths, nMaxWidth - nOffsetX - 2));
tools::Long nX = nOffsetX;
tools::Long nY = nOffsetY;
sal_uInt16 nLines = 0;
sal_uInt16 nCurLine = 0;
tools::Long nLineWidthAry[100];
sal_uInt16 nLinePosAry[101];
nLineWidthAry[0] = 0;
nLinePosAry[0] = 0;
size_t nIndex = 0;
for (auto & item : mpTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
Size aSize = ImplGetItemSize( &item, nMaxWidth );
bool bNewLine = false;
if (!aBreakIndexes.empty() && nIndex > aBreakIndexes.front())
{
aBreakIndexes.pop_front();
bNewLine = true;
}
if ( bNewLine && (nWidth > 2+nOffsetX) )
{
if ( nLines == 99 )
break;
nX = nOffsetX;
nY += aSize.Height();
nLines++;
nLineWidthAry[nLines] = 0;
nLinePosAry[nLines] = nIndex;
}
tools::Rectangle aNewRect( Point( nX, nY ), aSize );
if ( mbSmallInvalidate && (item.maRect != aNewRect) )
mbSmallInvalidate = false;
item.maRect = aNewRect;
item.mnLine = nLines;
item.mbFullVisible = true;
nLineWidthAry[nLines] += aSize.Width();
nX += aSize.Width();
if (item.id() == mnCurPageId)
nCurLine = nLines;
++nIndex;
}
if (nLines) // two or more lines
{
tools::Long nLineHeightAry[100];
tools::Long nIH = 0;
for (const auto& item : mpTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
nIH = item.maRect.Bottom() - 1;
break;
}
for ( sal_uInt16 i = 0; i < nLines+1; i++ )
{
if ( i <= nCurLine )
nLineHeightAry[i] = nIH*(nLines-(nCurLine-i));
else
nLineHeightAry[i] = nIH*(i-nCurLine-1);
}
nLinePosAry[nLines+1] = static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
tools::Long nDX = 0;
tools::Long nModDX = 0;
tools::Long nIDX = 0;
sal_uInt16 i = 0;
sal_uInt16 n = 0;
for (auto & item : mpTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
if ( i == nLinePosAry[n] )
{
if ( n == nLines+1 )
break;
nIDX = 0;
if( nLinePosAry[n+1]-i > 0 )
{
nDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) / ( nLinePosAry[n+1] - i );
nModDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) % ( nLinePosAry[n+1] - i );
}
else
{
// FIXME: this is a case of tabctrl way too small
nDX = 0;
nModDX = 0;
}
n++;
}
item.maRect.AdjustLeft(nIDX );
item.maRect.AdjustRight(nIDX + nDX );
item.maRect.SetTop( nLineHeightAry[n-1] );
item.maRect.SetBottom(nLineHeightAry[n-1] + nIH - 1);
nIDX += nDX;
if ( nModDX )
{
nIDX++;
item.maRect.AdjustRight( 1 );
nModDX--;
}
i++;
}
}
else // only one line
lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
return true;
}
tools::Rectangle TabControl::ImplGetTabRect( sal_uInt16 nItemPos, tools::Long nWidth, tools::Long nHeight )
{
Size aWinSize = Control::GetOutputSizePixel();
if ( nWidth < 0 )
nWidth = aWinSize.Width();
if ( nHeight < 0 )
nHeight = aWinSize.Height();
if ( mpTabCtrlData->maItemList.empty() )
{
tools::Long nW = nWidth-TAB_OFFSET*2;
tools::Long nH = nHeight-TAB_OFFSET*2;
return (nW > 0 && nH > 0)
? tools::Rectangle(Point(TAB_OFFSET, TAB_OFFSET), Size(nW, nH))
: tools::Rectangle();
}
if ( nItemPos == TAB_PAGERECT )
{
sal_uInt16 nLastPos;
if ( mnCurPageId )
nLastPos = GetPagePos( mnCurPageId );
else
nLastPos = 0;
tools::Rectangle aRect = ImplGetTabRect( nLastPos, nWidth, nHeight );
if (aRect.IsEmpty())
return aRect;
// with show-tabs of true (the usual) the page rect is from under the
// visible tab to the bottom of the TabControl, otherwise it extends
// from the top of the TabControl
tools::Long nTabBottom = mbShowTabs ? aRect.Bottom() : 0;
tools::Long nW = nWidth-TAB_OFFSET*2;
tools::Long nH = nHeight - nTabBottom - TAB_OFFSET*2;
return (nW > 0 && nH > 0)
? tools::Rectangle( Point( TAB_OFFSET, nTabBottom + TAB_OFFSET ), Size( nW, nH ) )
: tools::Rectangle();
}
ImplTabItem* const pItem = (nItemPos < mpTabCtrlData->maItemList.size())
? &mpTabCtrlData->maItemList[nItemPos] : nullptr;
return ImplGetTabRect(pItem, nWidth, nHeight);
}
tools::Rectangle TabControl::ImplGetTabRect(const ImplTabItem* pItem, tools::Long nWidth, tools::Long nHeight)
{
if ((nWidth <= 1) || (nHeight <= 0) || !pItem || !pItem->m_bVisible)
return tools::Rectangle();
nWidth -= 1;
if ( mbFormat || (mnLastWidth != nWidth) || (mnLastHeight != nHeight) )
{
vcl::Font aFont( GetFont() );
aFont.SetTransparent( true );
SetFont( aFont );
bool bRet = ImplPlaceTabs( nWidth );
if ( !bRet )
return tools::Rectangle();
mnLastWidth = nWidth;
mnLastHeight = nHeight;
mbFormat = false;
}
return pItem->maRect;
}
void TabControl::ImplChangeTabPage( sal_uInt16 nId, sal_uInt16 nOldId )
{
ImplTabItem* pOldItem = ImplGetItem( nOldId );
ImplTabItem* pItem = ImplGetItem( nId );
TabPage* pOldPage = pOldItem ? pOldItem->mpTabPage.get() : nullptr;
TabPage* pPage = pItem ? pItem->mpTabPage.get() : nullptr;
vcl::Window* pCtrlParent = GetParent();
if ( IsReallyVisible() && IsUpdateMode() )
{
sal_uInt16 nPos = GetPagePos( nId );
tools::Rectangle aRect = ImplGetTabRect( nPos );
if ( !pOldItem || !pItem || (pOldItem->mnLine != pItem->mnLine) )
{
aRect.SetLeft( 0 );
aRect.SetTop( 0 );
aRect.SetRight( Control::GetOutputSizePixel().Width() );
}
else
{
aRect.AdjustLeft( -3 );
aRect.AdjustTop( -2 );
aRect.AdjustRight(3 );
Invalidate( aRect );
nPos = GetPagePos( nOldId );
aRect = ImplGetTabRect( nPos );
aRect.AdjustLeft( -3 );
aRect.AdjustTop( -2 );
aRect.AdjustRight(3 );
}
Invalidate( aRect );
}
if ( pOldPage == pPage )
return;
tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
if ( pOldPage )
{
if ( mbRestoreHelpId )
pCtrlParent->SetHelpId({});
}
if ( pPage )
{
if ( GetStyle() & WB_NOBORDER )
{
tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
pPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
}
else
pPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
// activate page here so the controls can be switched
// also set the help id of the parent window to that of the tab page
if ( GetHelpId().isEmpty() )
{
mbRestoreHelpId = true;
pCtrlParent->SetHelpId( pPage->GetHelpId() );
}
pPage->Show();
if ( pOldPage && pOldPage->HasChildPathFocus() )
{
vcl::Window* pFirstChild = pPage->ImplGetDlgWindow( 0, GetDlgWindowType::First );
if ( pFirstChild )
pFirstChild->ImplControlFocus( GetFocusFlags::Init );
else
GrabFocus();
}
}
if ( pOldPage )
pOldPage->Hide();
// Invalidate the same region that will be send to NWF
// to always allow for bitmap caching
// see Window::DrawNativeControl()
if( IsNativeControlSupported( ControlType::TabPane, ControlPart::Entire ) )
{
aRect.AdjustLeft( -(TAB_OFFSET) );
aRect.AdjustTop( -(TAB_OFFSET) );
aRect.AdjustRight(TAB_OFFSET );
aRect.AdjustBottom(TAB_OFFSET );
}
Invalidate( aRect );
}
bool TabControl::ImplPosCurTabPage()
{
// resize/position current TabPage
ImplTabItem* pItem = ImplGetItem( GetCurPageId() );
if ( pItem && pItem->mpTabPage )
{
if ( GetStyle() & WB_NOBORDER )
{
tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
pItem->mpTabPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
return true;
}
tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
pItem->mpTabPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
return true;
}
return false;
}
void TabControl::ImplActivateTabPage( bool bNext )
{
sal_uInt16 nCurPos = GetPagePos( GetCurPageId() );
if ( bNext )
nCurPos = (nCurPos + 1) % GetPageCount();
else
{
if ( !nCurPos )
nCurPos = GetPageCount()-1;
else
nCurPos--;
}
SelectTabPage( GetPageId( nCurPos ) );
}
void TabControl::ImplShowFocus()
{
if ( !GetPageCount() || mpTabCtrlData->mpListBox )
return;
sal_uInt16 nCurPos = GetPagePos( mnCurPageId );
tools::Rectangle aRect = ImplGetTabRect( nCurPos );
const ImplTabItem& rItem = mpTabCtrlData->maItemList[ nCurPos ];
Size aTabSize = aRect.GetSize();
Size aImageSize( 0, 0 );
tools::Long nTextHeight = GetTextHeight();
tools::Long nTextWidth = GetOutDev()->GetCtrlTextWidth( rItem.maFormatText );
sal_uInt16 nOff;
if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::Mono) )
nOff = 1;
else
nOff = 0;
if( !! rItem.maTabImage )
{
aImageSize = rItem.maTabImage.GetSizePixel();
if( !rItem.maFormatText.isEmpty() )
aImageSize.AdjustWidth(GetTextHeight()/4 );
}
if( !rItem.maFormatText.isEmpty() )
{
// show focus around text
aRect.SetLeft( aRect.Left()+aImageSize.Width()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1-1 );
aRect.SetTop( aRect.Top()+((aTabSize.Height()-nTextHeight)/2)-1-1 );
aRect.SetRight( aRect.Left()+nTextWidth+2 );
aRect.SetBottom( aRect.Top()+nTextHeight+2 );
}
else
{
// show focus around image
tools::Long nXPos = aRect.Left()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1;
tools::Long nYPos = aRect.Top();
if( aImageSize.Height() < aRect.GetHeight() )
nYPos += (aRect.GetHeight() - aImageSize.Height())/2;
aRect.SetLeft( nXPos - 2 );
aRect.SetTop( nYPos - 2 );
aRect.SetRight( aRect.Left() + aImageSize.Width() + 4 );
aRect.SetBottom( aRect.Top() + aImageSize.Height() + 4 );
}
ShowFocus( aRect );
}
void TabControl::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplTabItem const * pItem, const tools::Rectangle& rCurRect,
bool bFirstInGroup, bool bLastInGroup )
{
if (!pItem->m_bVisible || pItem->maRect.IsEmpty())
return;
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
tools::Rectangle aRect = pItem->maRect;
tools::Long nLeftBottom = aRect.Bottom();
tools::Long nRightBottom = aRect.Bottom();
bool bLeftBorder = true;
bool bRightBorder = true;
sal_uInt16 nOff;
bool bNativeOK = false;
sal_uInt16 nOff2 = 0;
sal_uInt16 nOff3 = 0;
if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
nOff = 1;
else
nOff = 0;
// if this is the active Page, we have to draw a little more
if (pItem->id() == mnCurPageId)
{
nOff2 = 2;
if (!ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise)
nOff3 = 1;
}
else
{
Point aLeftTestPos = aRect.BottomLeft();
Point aRightTestPos = aRect.BottomRight();
if (aLeftTestPos.Y() == rCurRect.Bottom())
{
aLeftTestPos.AdjustX( -2 );
if (rCurRect.Contains(aLeftTestPos))
bLeftBorder = false;
aRightTestPos.AdjustX(2 );
if (rCurRect.Contains(aRightTestPos))
bRightBorder = false;
}
else
{
if (rCurRect.Contains(aLeftTestPos))
nLeftBottom -= 2;
if (rCurRect.Contains(aRightTestPos))
nRightBottom -= 2;
}
}
ControlState nState = ControlState::NONE;
if (pItem->id() == mnCurPageId)
{
nState |= ControlState::SELECTED;
// only the selected item can be focused
if (HasFocus())
nState |= ControlState::FOCUSED;
}
if (IsEnabled())
nState |= ControlState::ENABLED;
if (IsMouseOver() && pItem->maRect.Contains(GetPointerPosPixel()))
{
nState |= ControlState::ROLLOVER;
for (auto const& item : mpTabCtrlData->maItemList)
if ((&item != pItem) && item.m_bVisible && item.maRect.Contains(GetPointerPosPixel()))
{
nState &= ~ControlState::ROLLOVER; // avoid multiple highlighted tabs
break;
}
assert(nState & ControlState::ROLLOVER);
}
bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire);
if ( bNativeOK )
{
TabitemValue tiValue(tools::Rectangle(pItem->maRect.Left() + TAB_ITEM_OFFSET_X,
pItem->maRect.Top() + TAB_ITEM_OFFSET_Y,
pItem->maRect.Right() - TAB_ITEM_OFFSET_X,
pItem->maRect.Bottom() - TAB_ITEM_OFFSET_Y),
TabBarPosition::Top);
if (pItem->maRect.Left() < 5)
tiValue.mnAlignment |= TabitemFlags::LeftAligned;
if (pItem->maRect.Right() > mnLastWidth - 5)
tiValue.mnAlignment |= TabitemFlags::RightAligned;
if (bFirstInGroup)
tiValue.mnAlignment |= TabitemFlags::FirstInGroup;
if (bLastInGroup)
tiValue.mnAlignment |= TabitemFlags::LastInGroup;
tools::Rectangle aCtrlRegion( pItem->maRect );
aCtrlRegion.AdjustBottom(TabPaneValue::m_nOverlap);
bNativeOK = rRenderContext.DrawNativeControl(ControlType::TabItem, ControlPart::Entire,
aCtrlRegion, nState, tiValue, OUString() );
}
if (!bNativeOK)
{
if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
{
rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2)); // diagonally indented top-left pixel
if (bLeftBorder)
{
rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
Point(aRect.Left() - nOff2, nLeftBottom - 1));
}
rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2), // top line starting 2px from left border
Point(aRect.Right() + nOff2 - 3, aRect.Top() - nOff2)); // ending 3px from right border
if (bRightBorder)
{
rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2),
Point(aRect.Right() + nOff2 - 2, nRightBottom - 1));
rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 3 - nOff2),
Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
}
}
else
{
rRenderContext.SetLineColor(COL_BLACK);
rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2));
rRenderContext.DrawPixel(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2));
if (bLeftBorder)
{
rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
Point(aRect.Left() - nOff2, nLeftBottom - 1));
}
rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2),
Point(aRect.Right() - 3, aRect.Top() - nOff2));
if (bRightBorder)
{
rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 2 - nOff2),
Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
}
}
}
// set font accordingly, current item is painted bold
// we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
vcl::Font aFont(rRenderContext.GetFont());
aFont.SetTransparent(true);
rRenderContext.SetFont(aFont);
Size aTabSize = aRect.GetSize();
Size aImageSize(0, 0);
tools::Long nTextHeight = rRenderContext.GetTextHeight();
tools::Long nTextWidth = rRenderContext.GetCtrlTextWidth(pItem->maFormatText);
if (!!pItem->maTabImage)
{
aImageSize = pItem->maTabImage.GetSizePixel();
if (!pItem->maFormatText.isEmpty())
aImageSize.AdjustWidth(GetTextHeight() / 4 );
}
tools::Long nXPos = aRect.Left() + ((aTabSize.Width() - nTextWidth - aImageSize.Width()) / 2) - nOff - nOff3;
tools::Long nYPos = aRect.Top() + ((aTabSize.Height() - nTextHeight) / 2) - nOff3;
if (!pItem->maFormatText.isEmpty())
{
DrawTextFlags nStyle = DrawTextFlags::Mnemonic;
if (!pItem->m_bEnabled)
nStyle |= DrawTextFlags::Disable;
Color aColor(rStyleSettings.GetTabTextColor());
if (nState & ControlState::SELECTED)
aColor = rStyleSettings.GetTabHighlightTextColor();
else if (nState & ControlState::ROLLOVER)
aColor = rStyleSettings.GetTabRolloverTextColor();
Color aOldColor(rRenderContext.GetTextColor());
rRenderContext.SetTextColor(aColor);
const tools::Rectangle aOutRect(nXPos + aImageSize.Width(), nYPos,
nXPos + aImageSize.Width() + nTextWidth, nYPos + nTextHeight);
DrawControlText(rRenderContext, aOutRect, pItem->maFormatText, nStyle,
nullptr, nullptr);
rRenderContext.SetTextColor(aOldColor);
}
if (!!pItem->maTabImage)
{
Point aImgTL( nXPos, aRect.Top() );
if (aImageSize.Height() < aRect.GetHeight())
aImgTL.AdjustY((aRect.GetHeight() - aImageSize.Height()) / 2 );
rRenderContext.DrawImage(aImgTL, pItem->maTabImage, pItem->m_bEnabled ? DrawImageFlags::NONE : DrawImageFlags::Disable );
}
}
bool TabControl::ImplHandleKeyEvent( const KeyEvent& rKeyEvent )
{
bool bRet = false;
if ( GetPageCount() > 1 )
{
vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
sal_uInt16 nKeyCode = aKeyCode.GetCode();
if ( aKeyCode.IsMod1() )
{
if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
{
if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
{
ImplActivateTabPage( false );
bRet = true;
}
}
else
{
if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
{
ImplActivateTabPage( true );
bRet = true;
}
}
}
}
return bRet;
}
IMPL_LINK_NOARG(TabControl, ImplListBoxSelectHdl, ListBox&, void)
{
SelectTabPage( GetPageId( mpTabCtrlData->mpListBox->GetSelectedEntryPos() ) );
}
IMPL_LINK( TabControl, ImplWindowEventListener, VclWindowEvent&, rEvent, void )
{
if ( rEvent.GetId() == VclEventId::WindowKeyInput )
{
// Do not handle events from TabControl or its children, which is done in Notify(), where the events can be consumed.
if ( !IsWindowOrChild( rEvent.GetWindow() ) )
{
KeyEvent* pKeyEvent = static_cast< KeyEvent* >(rEvent.GetData());
ImplHandleKeyEvent( *pKeyEvent );
}
}
}
void TabControl::MouseButtonDown( const MouseEvent& rMEvt )
{
if (mpTabCtrlData->mpListBox || !rMEvt.IsLeft())
return;
ImplTabItem *pItem = ImplGetItem(rMEvt.GetPosPixel());
if (pItem && pItem->m_bEnabled)
SelectTabPage(pItem->id());
}
void TabControl::KeyInput( const KeyEvent& rKEvt )
{
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->KeyInput( rKEvt );
else if ( GetPageCount() > 1 )
{
vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
sal_uInt16 nKeyCode = aKeyCode.GetCode();
if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_RIGHT) )
{
bool bNext = (nKeyCode == KEY_RIGHT);
ImplActivateTabPage( bNext );
}
}
Control::KeyInput( rKEvt );
}
static bool lcl_canPaint(const vcl::RenderContext& rRenderContext, const tools::Rectangle& rDrawRect,
const tools::Rectangle& rItemRect)
{
vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
aClipRgn.Intersect(rItemRect);
if (!rDrawRect.IsEmpty())
aClipRgn.Intersect(rDrawRect);
return !aClipRgn.IsEmpty();
}
void TabControl::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
if (GetStyle() & WB_NOBORDER)
return;
Control::Paint(rRenderContext, rRect);
HideFocus();
// reformat if needed
tools::Rectangle aRect = ImplGetTabRect(TAB_PAGERECT);
// find current item
ImplTabItem* pCurItem = nullptr;
for (auto & item : mpTabCtrlData->maItemList)
{
if (item.id() == mnCurPageId)
{
pCurItem = &item;
break;
}
}
// Draw the TabPage border
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
tools::Rectangle aCurRect;
aRect.AdjustLeft( -(TAB_OFFSET) );
aRect.AdjustTop( -(TAB_OFFSET) );
aRect.AdjustRight(TAB_OFFSET );
aRect.AdjustBottom(TAB_OFFSET );
// if we have an invisible tabpage or no tabpage at all the tabpage rect should be
// increased to avoid round corners that might be drawn by a theme
// in this case we're only interested in the top border of the tabpage because the tabitems are used
// standalone (eg impress)
bool bNoTabPage = false;
TabPage* pCurPage = pCurItem ? pCurItem->mpTabPage.get() : nullptr;
if (!pCurPage || !pCurPage->IsVisible())
{
bNoTabPage = true;
aRect.AdjustLeft( -10 );
aRect.AdjustRight(10 );
}
if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
{
const bool bPaneWithHeader = mbShowTabs && rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::TabPaneWithHeader);
tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
if (!mpTabCtrlData->maItemList.empty())
{
tools::Long nLeft = LONG_MAX;
tools::Long nRight = 0;
for (const auto &item : mpTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
nRight = std::max(nRight, item.maRect.Right());
nLeft = std::min(nLeft, item.maRect.Left());
}
aHeaderRect.SetLeft(nLeft);
aHeaderRect.SetRight(nRight);
}
if (bPaneWithHeader)
aRect.SetTop(0);
const TabPaneValue aTabPaneValue(aHeaderRect, pCurItem ? pCurItem->maRect : tools::Rectangle());
ControlState nState = ControlState::ENABLED;
if (!IsEnabled())
nState &= ~ControlState::ENABLED;
if (HasFocus())
nState |= ControlState::FOCUSED;
if (lcl_canPaint(rRenderContext, rRect, aRect))
rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
aRect, nState, aTabPaneValue, OUString());
if (!bPaneWithHeader && rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire)
&& lcl_canPaint(rRenderContext, rRect, aHeaderRect))
rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
aHeaderRect, nState, aTabPaneValue, OUString());
}
else
{
tools::Long nTopOff = 1;
if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
else
rRenderContext.SetLineColor(COL_BLACK);
if (mbShowTabs && pCurItem && !pCurItem->maRect.IsEmpty())
{
aCurRect = pCurItem->maRect;
rRenderContext.DrawLine(aRect.TopLeft(), Point(aCurRect.Left() - 2, aRect.Top()));
if (aCurRect.Right() + 1 < aRect.Right())
{
rRenderContext.DrawLine(Point(aCurRect.Right(), aRect.Top()), aRect.TopRight());
}
else
{
nTopOff = 0;
}
}
else
rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
{
// if we have not tab page the bottom line of the tab page
// directly touches the tab items, so choose a color that fits seamlessly
if (bNoTabPage)
rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
else
rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
rRenderContext.DrawLine(Point(1, aRect.Bottom() - 1), Point(aRect.Right() - 1, aRect.Bottom() - 1));
rRenderContext.DrawLine(Point(aRect.Right() - 1, aRect.Top() + nTopOff), Point(aRect.Right() - 1, aRect.Bottom() - 1));
if (bNoTabPage)
rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
else
rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
rRenderContext.DrawLine(Point(0, aRect.Bottom()), Point(aRect.Right(), aRect.Bottom()));
rRenderContext.DrawLine(Point(aRect.Right(), aRect.Top() + nTopOff), Point(aRect.Right(), aRect.Bottom()));
}
else
{
rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
}
}
const size_t nItemListSize = mpTabCtrlData->maItemList.size();
if (mbShowTabs && nItemListSize > 0 && mpTabCtrlData->mpListBox == nullptr)
{
// Some native toolkits (GTK+) draw tabs right-to-left, with an
// overlap between adjacent tabs
bool bDrawTabsRTL = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::TabsDrawRtl);
ImplTabItem* pFirstTab = nullptr;
ImplTabItem* pLastTab = nullptr;
size_t idx;
// Even though there is a tab overlap with GTK+, the first tab is not
// overlapped on the left side. Other toolkits ignore this option.
if (bDrawTabsRTL)
{
pFirstTab = mpTabCtrlData->maItemList.data();
pLastTab = pFirstTab + nItemListSize - 1;
idx = nItemListSize - 1;
}
else
{
pLastTab = mpTabCtrlData->maItemList.data();
pFirstTab = pLastTab + nItemListSize - 1;
idx = 0;
}
while (idx < nItemListSize)
{
ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
if (pItem != pCurItem && pItem->m_bVisible && lcl_canPaint(rRenderContext, rRect, pItem->maRect))
ImplDrawItem(rRenderContext, pItem, aCurRect, pItem == pFirstTab, pItem == pLastTab);
if (bDrawTabsRTL)
idx--;
else
idx++;
}
if (pCurItem && lcl_canPaint(rRenderContext, rRect, pCurItem->maRect))
ImplDrawItem(rRenderContext, pCurItem, aCurRect, pCurItem == pFirstTab, pCurItem == pLastTab);
}
if (HasFocus())
ImplShowFocus();
mbSmallInvalidate = true;
}
void TabControl::setAllocation(const Size &rAllocation)
{
if ( !IsReallyShown() )
return;
if( mpTabCtrlData->mpListBox )
{
// get the listbox' preferred size
Size aTabCtrlSize( GetSizePixel() );
tools::Long nPrefWidth = mpTabCtrlData->mpListBox->get_preferred_size().Width();
if( nPrefWidth > aTabCtrlSize.Width() )
nPrefWidth = aTabCtrlSize.Width();
Size aNewSize( nPrefWidth, LogicToPixel( Size( 12, 12 ), MapMode( MapUnit::MapAppFont ) ).Height() );
Point aNewPos( (aTabCtrlSize.Width() - nPrefWidth) / 2, 0 );
mpTabCtrlData->mpListBox->SetPosSizePixel( aNewPos, aNewSize );
}
mbFormat = true;
// resize/position active TabPage
bool bTabPage = ImplPosCurTabPage();
// check what needs to be invalidated
Size aNewSize = rAllocation;
tools::Long nNewWidth = aNewSize.Width();
for (auto const& item : mpTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
if (!item.mbFullVisible || (item.maRect.Right()-2 >= nNewWidth))
{
mbSmallInvalidate = false;
break;
}
}
if ( mbSmallInvalidate )
{
tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
aRect.AdjustLeft( -(TAB_OFFSET+TAB_BORDER_LEFT) );
aRect.AdjustTop( -(TAB_OFFSET+TAB_BORDER_TOP) );
aRect.AdjustRight(TAB_OFFSET+TAB_BORDER_RIGHT );
aRect.AdjustBottom(TAB_OFFSET+TAB_BORDER_BOTTOM );
if ( bTabPage )
Invalidate( aRect, InvalidateFlags::NoChildren );
else
Invalidate( aRect );
}
else
{
if ( bTabPage )
Invalidate( InvalidateFlags::NoChildren );
else
Invalidate();
}
mbLayoutDirty = false;
}
void TabControl::SetPosSizePixel(const Point& rNewPos, const Size& rNewSize)
{
Window::SetPosSizePixel(rNewPos, rNewSize);
//if size changed, TabControl::Resize got called already
if (mbLayoutDirty)
setAllocation(rNewSize);
}
void TabControl::SetSizePixel(const Size& rNewSize)
{
Window::SetSizePixel(rNewSize);
//if size changed, TabControl::Resize got called already
if (mbLayoutDirty)
setAllocation(rNewSize);
}
void TabControl::SetPosPixel(const Point& rPos)
{
Window::SetPosPixel(rPos);
if (mbLayoutDirty)
setAllocation(GetOutputSizePixel());
}
void TabControl::Resize()
{
setAllocation(Control::GetOutputSizePixel());
}
void TabControl::GetFocus()
{
if( ! mpTabCtrlData->mpListBox )
{
if (mbShowTabs)
{
ImplShowFocus();
SetInputContext( InputContext( GetFont() ) );
}
else
{
// no tabs, focus first thing in current page
ImplTabItem* pItem = ImplGetItem(GetCurPageId());
if (pItem && pItem->mpTabPage)
{
vcl::Window* pFirstChild = pItem->mpTabPage->ImplGetDlgWindow(0, GetDlgWindowType::First);
if ( pFirstChild )
pFirstChild->ImplControlFocus(GetFocusFlags::Init);
}
}
}
else
{
if( mpTabCtrlData->mpListBox->IsReallyVisible() )
mpTabCtrlData->mpListBox->GrabFocus();
}
Control::GetFocus();
}
void TabControl::LoseFocus()
{
if( mpTabCtrlData && ! mpTabCtrlData->mpListBox )
HideFocus();
Control::LoseFocus();
}
void TabControl::RequestHelp( const HelpEvent& rHEvt )
{
sal_uInt16 nItemId = rHEvt.KeyboardActivated() ? mnCurPageId : GetPageId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
if ( nItemId )
{
if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
{
OUString aStr = GetHelpText( nItemId );
if ( !aStr.isEmpty() )
{
tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
aItemRect.SetLeft( aPt.X() );
aItemRect.SetTop( aPt.Y() );
aPt = OutputToScreenPixel( aItemRect.BottomRight() );
aItemRect.SetRight( aPt.X() );
aItemRect.SetBottom( aPt.Y() );
Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
return;
}
}
// for Quick or Ballon Help, we show the text, if it is cut
if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
{
ImplTabItem* pItem = ImplGetItem( nItemId );
const OUString& rStr = pItem->maText;
if ( rStr != pItem->maFormatText )
{
tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
aItemRect.SetLeft( aPt.X() );
aItemRect.SetTop( aPt.Y() );
aPt = OutputToScreenPixel( aItemRect.BottomRight() );
aItemRect.SetRight( aPt.X() );
aItemRect.SetBottom( aPt.Y() );
if ( !rStr.isEmpty() )
{
if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
Help::ShowBalloon( this, aItemRect.Center(), aItemRect, rStr );
else
Help::ShowQuickHelp( this, aItemRect, rStr );
return;
}
}
}
if ( rHEvt.GetMode() & HelpEventMode::QUICK )
{
ImplTabItem* pItem = ImplGetItem( nItemId );
const OUString& rHelpText = pItem->maHelpText;
if (!rHelpText.isEmpty())
{
tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
aItemRect.SetLeft( aPt.X() );
aItemRect.SetTop( aPt.Y() );
aPt = OutputToScreenPixel( aItemRect.BottomRight() );
aItemRect.SetRight( aPt.X() );
aItemRect.SetBottom( aPt.Y() );
Help::ShowQuickHelp( this, aItemRect, rHelpText );
return;
}
}
}
Control::RequestHelp( rHEvt );
}
void TabControl::Command( const CommandEvent& rCEvt )
{
if( (mpTabCtrlData->mpListBox == nullptr) && (rCEvt.GetCommand() == CommandEventId::ContextMenu) && (GetPageCount() > 1) )
{
Point aMenuPos;
bool bMenu;
if ( rCEvt.IsMouseEvent() )
{
aMenuPos = rCEvt.GetMousePosPixel();
bMenu = GetPageId( aMenuPos ) != 0;
}
else
{
aMenuPos = ImplGetTabRect( GetPagePos( mnCurPageId ) ).Center();
bMenu = true;
}
if ( bMenu )
{
ScopedVclPtrInstance<PopupMenu> aMenu;
for (auto const& item : mpTabCtrlData->maItemList)
{
aMenu->InsertItem(item.id(), item.maText, MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK);
if (item.id() == mnCurPageId)
aMenu->CheckItem(item.id());
aMenu->SetHelpId(item.id(), {});
}
sal_uInt16 nId = aMenu->Execute( this, aMenuPos );
if ( nId && (nId != mnCurPageId) )
SelectTabPage( nId );
return;
}
}
Control::Command( rCEvt );
}
void TabControl::StateChanged( StateChangedType nType )
{
Control::StateChanged( nType );
if ( nType == StateChangedType::InitShow )
{
ImplPosCurTabPage();
if( mpTabCtrlData->mpListBox )
Resize();
}
else if ( nType == StateChangedType::UpdateMode )
{
if ( IsUpdateMode() )
Invalidate();
}
else if ( (nType == StateChangedType::Zoom) ||
(nType == StateChangedType::ControlFont) )
{
ImplInitSettings( false );
Invalidate();
}
else if ( nType == StateChangedType::ControlForeground )
{
ImplInitSettings( false );
Invalidate();
}
else if ( nType == StateChangedType::ControlBackground )
{
ImplInitSettings( true );
Invalidate();
}
}
void TabControl::DataChanged( const DataChangedEvent& rDCEvt )
{
Control::DataChanged( rDCEvt );
if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
(rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
(rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
{
ImplInitSettings( true );
Invalidate();
}
}
ImplTabItem* TabControl::ImplGetItem(const Point& rPt) const
{
ImplTabItem* pFoundItem = nullptr;
int nFound = 0;
for (auto & item : mpTabCtrlData->maItemList)
{
if (item.m_bVisible && item.maRect.Contains(rPt))
{
nFound++;
pFoundItem = &item;
}
}
// assure that only one tab is highlighted at a time
assert(nFound <= 1);
return nFound == 1 ? pFoundItem : nullptr;
}
bool TabControl::PreNotify( NotifyEvent& rNEvt )
{
if( rNEvt.GetType() == NotifyEventType::MOUSEMOVE )
{
const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
{
// trigger redraw if mouse over state has changed
if( IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) )
{
ImplTabItem *pItem = ImplGetItem(GetPointerPosPixel());
ImplTabItem *pLastItem = ImplGetItem(GetLastPointerPosPixel());
if ((pItem != pLastItem) || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
{
vcl::Region aClipRgn;
if (pLastItem)
{
// allow for slightly bigger tabitems
// as used by gtk
// TODO: query for the correct sizes
tools::Rectangle aRect(pLastItem->maRect);
aRect.AdjustLeft( -2 );
aRect.AdjustRight(2 );
aRect.AdjustTop( -3 );
aClipRgn.Union( aRect );
}
if (pItem)
{
// allow for slightly bigger tabitems
// as used by gtk
// TODO: query for the correct sizes
tools::Rectangle aRect(pItem->maRect);
aRect.AdjustLeft( -2 );
aRect.AdjustRight(2 );
aRect.AdjustTop( -3 );
aClipRgn.Union( aRect );
}
if( !aClipRgn.IsEmpty() )
Invalidate( aClipRgn );
}
}
}
}
return Control::PreNotify(rNEvt);
}
bool TabControl::EventNotify( NotifyEvent& rNEvt )
{
bool bRet = false;
if ( rNEvt.GetType() == NotifyEventType::KEYINPUT )
bRet = ImplHandleKeyEvent( *rNEvt.GetKeyEvent() );
return bRet || Control::EventNotify( rNEvt );
}
void TabControl::ActivatePage()
{
maActivateHdl.Call( this );
}
bool TabControl::DeactivatePage()
{
return !maDeactivateHdl.IsSet() || maDeactivateHdl.Call( this );
}
void TabControl::SetTabPageSizePixel( const Size& rSize )
{
Size aNewSize( rSize );
aNewSize.AdjustWidth(TAB_OFFSET*2 );
tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT,
aNewSize.Width(), aNewSize.Height() );
aNewSize.AdjustHeight(aRect.Top()+TAB_OFFSET );
Window::SetOutputSizePixel( aNewSize );
}
void TabControl::InsertPage( sal_uInt16 nPageId, const OUString& rText,
sal_uInt16 nPos )
{
SAL_WARN_IF( !nPageId, "vcl", "TabControl::InsertPage(): PageId == 0" );
SAL_WARN_IF( GetPagePos( nPageId ) != TAB_PAGE_NOTFOUND, "vcl",
"TabControl::InsertPage(): PageId already exists" );
// insert new page item
ImplTabItem* pItem = nullptr;
if( nPos == TAB_APPEND || size_t(nPos) >= mpTabCtrlData->maItemList.size() )
{
mpTabCtrlData->maItemList.emplace_back(nPageId);
pItem = &mpTabCtrlData->maItemList.back();
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->InsertEntry( rText );
}
else
{
std::vector< ImplTabItem >::iterator new_it =
mpTabCtrlData->maItemList.emplace(mpTabCtrlData->maItemList.begin() + nPos, nPageId);
pItem = &(*new_it);
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->InsertEntry( rText, nPos);
}
if( mpTabCtrlData->mpListBox )
{
if( ! mnCurPageId )
mpTabCtrlData->mpListBox->SelectEntryPos( 0 );
mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
}
// set current page id
if ( !mnCurPageId )
mnCurPageId = nPageId;
// init new page item
pItem->maText = rText;
pItem->mbFullVisible = false;
mbFormat = true;
if ( IsUpdateMode() )
Invalidate();
if( mpTabCtrlData->mpListBox ) // reposition/resize listbox
Resize();
CallEventListeners( VclEventId::TabpageInserted, reinterpret_cast<void*>(nPageId) );
}
void TabControl::RemovePage( sal_uInt16 nPageId )
{
sal_uInt16 nPos = GetPagePos( nPageId );
// does the item exist ?
if ( nPos == TAB_PAGE_NOTFOUND )
return;
//remove page item
std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin() + nPos;
bool bIsCurrentPage = (it->id() == mnCurPageId);
mpTabCtrlData->maItemList.erase( it );
if( mpTabCtrlData->mpListBox )
{
mpTabCtrlData->mpListBox->RemoveEntry( nPos );
mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
}
// If current page is removed, then first page gets the current page
if ( bIsCurrentPage )
{
mnCurPageId = 0;
if( ! mpTabCtrlData->maItemList.empty() )
{
// don't do this by simply setting mnCurPageId to pFirstItem->id()
// this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
// instead, call SetCurPageId
// without this, the next (outside) call to SetCurPageId with the id of the first page
// will result in doing nothing (as we assume that nothing changed, then), and the page
// will never be shown.
// 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
SetCurPageId(mpTabCtrlData->maItemList[0].id());
}
}
mbFormat = true;
if ( IsUpdateMode() )
Invalidate();
CallEventListeners( VclEventId::TabpageRemoved, reinterpret_cast<void*>(nPageId) );
}
void TabControl::SetPageEnabled( sal_uInt16 i_nPageId, bool i_bEnable )
{
ImplTabItem* pItem = ImplGetItem( i_nPageId );
if (!pItem || pItem->m_bEnabled == i_bEnable)
return;
pItem->m_bEnabled = i_bEnable;
if (!pItem->m_bVisible)
return;
mbFormat = true;
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->SetEntryFlags( GetPagePos( i_nPageId ),
i_bEnable ? ListBoxEntryFlags::NONE : (ListBoxEntryFlags::DisableSelection | ListBoxEntryFlags::DrawDisabled) );
// SetCurPageId will change to a valid page
if (pItem->id() == mnCurPageId)
SetCurPageId( mnCurPageId );
else if ( IsUpdateMode() )
Invalidate();
}
void TabControl::SetPageVisible( sal_uInt16 nPageId, bool bVisible )
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if (!pItem || pItem->m_bVisible == bVisible)
return;
pItem->m_bVisible = bVisible;
if (!bVisible)
{
if (pItem->mbFullVisible)
mbSmallInvalidate = false;
pItem->mbFullVisible = false;
pItem->maRect.SetEmpty();
}
mbFormat = true;
// SetCurPageId will change to a valid page
if (pItem->id() == mnCurPageId)
SetCurPageId(mnCurPageId);
else if (IsUpdateMode())
Invalidate();
}
sal_uInt16 TabControl::GetPageCount() const
{
return static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
}
sal_uInt16 TabControl::GetPageId( sal_uInt16 nPos ) const
{
if( size_t(nPos) < mpTabCtrlData->maItemList.size() )
return mpTabCtrlData->maItemList[nPos].id();
return 0;
}
sal_uInt16 TabControl::GetPagePos( sal_uInt16 nPageId ) const
{
sal_uInt16 nPos = 0;
for (auto const& item : mpTabCtrlData->maItemList)
{
if (item.id() == nPageId)
return nPos;
++nPos;
}
return TAB_PAGE_NOTFOUND;
}
sal_uInt16 TabControl::GetPageId( const Point& rPos ) const
{
Size winSize = Control::GetOutputSizePixel();
const auto &rList = mpTabCtrlData->maItemList;
const auto it = std::find_if(rList.begin(), rList.end(), [&rPos, &winSize, this](const auto &item) {
return const_cast<TabControl*>(this)->ImplGetTabRect(&item, winSize.Width(), winSize.Height()).Contains(rPos); });
return (it != rList.end()) ? it->id() : 0;
}
sal_uInt16 TabControl::GetPageId( const OUString& rName ) const
{
const auto &rList = mpTabCtrlData->maItemList;
const auto it = std::find_if(rList.begin(), rList.end(), [&rName](const auto &item) {
return item.maTabName == rName; });
return (it != rList.end()) ? it->id() : 0;
}
void TabControl::SetCurPageId( sal_uInt16 nPageId )
{
sal_uInt16 nPos = GetPagePos( nPageId );
while (nPos != TAB_PAGE_NOTFOUND && !mpTabCtrlData->maItemList[nPos].m_bEnabled)
{
nPos++;
if( size_t(nPos) >= mpTabCtrlData->maItemList.size() )
nPos = 0;
if (mpTabCtrlData->maItemList[nPos].id() == nPageId)
break;
}
if( nPos == TAB_PAGE_NOTFOUND )
return;
nPageId = mpTabCtrlData->maItemList[nPos].id();
if ( nPageId == mnCurPageId )
{
if ( mnActPageId )
mnActPageId = nPageId;
return;
}
if ( mnActPageId )
mnActPageId = nPageId;
else
{
mbFormat = true;
sal_uInt16 nOldId = mnCurPageId;
mnCurPageId = nPageId;
ImplChangeTabPage( nPageId, nOldId );
}
}
sal_uInt16 TabControl::GetCurPageId() const
{
if ( mnActPageId )
return mnActPageId;
else
return mnCurPageId;
}
void TabControl::SelectTabPage( sal_uInt16 nPageId )
{
if ( !nPageId || (nPageId == mnCurPageId) )
return;
CallEventListeners( VclEventId::TabpageDeactivate, reinterpret_cast<void*>(mnCurPageId) );
if ( DeactivatePage() )
{
mnActPageId = nPageId;
ActivatePage();
// Page could have been switched by the Activate handler
nPageId = mnActPageId;
mnActPageId = 0;
SetCurPageId( nPageId );
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->SelectEntryPos( GetPagePos( nPageId ) );
CallEventListeners( VclEventId::TabpageActivate, reinterpret_cast<void*>(nPageId) );
}
}
void TabControl::SetTabPage( sal_uInt16 nPageId, TabPage* pTabPage )
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if ( !pItem || (pItem->mpTabPage.get() == pTabPage) )
return;
if ( pTabPage )
{
if ( IsDefaultSize() )
SetTabPageSizePixel( pTabPage->GetSizePixel() );
// only set here, so that Resize does not reposition TabPage
pItem->mpTabPage = pTabPage;
queue_resize();
if (pItem->id() == mnCurPageId)
ImplChangeTabPage(pItem->id(), 0);
}
else
{
pItem->mpTabPage = nullptr;
queue_resize();
}
}
TabPage* TabControl::GetTabPage( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if ( pItem )
return pItem->mpTabPage;
else
return nullptr;
}
void TabControl::SetPageText( sal_uInt16 nPageId, const OUString& rText )
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if ( !pItem || pItem->maText == rText )
return;
pItem->maText = rText;
mbFormat = true;
if( mpTabCtrlData->mpListBox )
{
sal_uInt16 nPos = GetPagePos( nPageId );
mpTabCtrlData->mpListBox->RemoveEntry( nPos );
mpTabCtrlData->mpListBox->InsertEntry( rText, nPos );
}
if ( IsUpdateMode() )
Invalidate();
CallEventListeners( VclEventId::TabpagePageTextChanged, reinterpret_cast<void*>(nPageId) );
}
OUString const & TabControl::GetPageText( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
assert( pItem );
return pItem->maText;
}
void TabControl::SetHelpText( sal_uInt16 nPageId, const OUString& rText )
{
ImplTabItem* pItem = ImplGetItem( nPageId );
assert( pItem );
pItem->maHelpText = rText;
}
const OUString& TabControl::GetHelpText( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
assert( pItem );
return pItem->maHelpText;
}
void TabControl::SetAccessibleName(sal_uInt16 nPageId, const OUString& rName)
{
ImplTabItem* pItem = ImplGetItem( nPageId );
assert( pItem );
pItem->maAccessibleName = rName;
}
OUString TabControl::GetAccessibleName( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
assert( pItem );
if (!pItem->maAccessibleName.isEmpty())
return pItem->maAccessibleName;
return removeMnemonicFromString(pItem->maText);
}
void TabControl::SetAccessibleDescription(sal_uInt16 nPageId, const OUString& rDesc)
{
ImplTabItem* pItem = ImplGetItem( nPageId );
assert( pItem );
pItem->maAccessibleDescription = rDesc;
}
OUString TabControl::GetAccessibleDescription( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
assert( pItem );
if (!pItem->maAccessibleDescription.isEmpty())
return pItem->maAccessibleDescription;
return pItem->maHelpText;
}
void TabControl::SetPageName( sal_uInt16 nPageId, const OUString& rName ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if ( pItem )
pItem->maTabName = rName;
}
OUString TabControl::GetPageName( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if (pItem)
return pItem->maTabName;
return {};
}
void TabControl::SetPageImage( sal_uInt16 i_nPageId, const Image& i_rImage )
{
ImplTabItem* pItem = ImplGetItem( i_nPageId );
if ( pItem )
{
pItem->maTabImage = i_rImage;
mbFormat = true;
if ( IsUpdateMode() )
Invalidate();
}
}
tools::Rectangle TabControl::GetTabBounds( sal_uInt16 nPageId ) const
{
tools::Rectangle aRet;
ImplTabItem* pItem = ImplGetItem( nPageId );
if (pItem && pItem->m_bVisible)
aRet = pItem->maRect;
return aRet;
}
Size TabControl::ImplCalculateRequisition(sal_uInt16& nHeaderHeight) const
{
Size aOptimalPageSize(0, 0);
sal_uInt16 nOrigPageId = GetCurPageId();
for (auto const& item : mpTabCtrlData->maItemList)
{
const TabPage *pPage = item.mpTabPage;
//it's a real nuisance if the page is not inserted yet :-(
//We need to force all tabs to exist to get overall optimal size for dialog
if (!pPage)
{
TabControl *pThis = const_cast<TabControl*>(this);
pThis->SetCurPageId(item.id());
pThis->ActivatePage();
pPage = item.mpTabPage;
}
if (!pPage)
continue;
Size aPageSize(VclContainer::getLayoutRequisition(*pPage));
if (aPageSize.Width() > aOptimalPageSize.Width())
aOptimalPageSize.setWidth( aPageSize.Width() );
if (aPageSize.Height() > aOptimalPageSize.Height())
aOptimalPageSize.setHeight( aPageSize.Height() );
}
//fdo#61940 If we were forced to activate pages in order to on-demand
//create them to get their optimal size, then switch back to the original
//page and re-activate it
if (nOrigPageId != GetCurPageId())
{
TabControl *pThis = const_cast<TabControl*>(this);
pThis->SetCurPageId(nOrigPageId);
pThis->ActivatePage();
}
tools::Long nTabLabelsBottom = 0, nTabLabelsRight = 0;
if (mbShowTabs)
{
for (sal_uInt16 nPos(0), sizeList(static_cast <sal_uInt16> (mpTabCtrlData->maItemList.size()));
nPos < sizeList; ++nPos)
{
TabControl* pThis = const_cast<TabControl*>(this);
tools::Rectangle aTabRect = pThis->ImplGetTabRect(nPos, aOptimalPageSize.Width(), LONG_MAX);
if (aTabRect.Bottom() > nTabLabelsBottom)
{
nTabLabelsBottom = aTabRect.Bottom();
nHeaderHeight = nTabLabelsBottom;
}
if (!aTabRect.IsEmpty() && aTabRect.Right() > nTabLabelsRight)
nTabLabelsRight = aTabRect.Right();
}
}
Size aOptimalSize(aOptimalPageSize);
aOptimalSize.AdjustHeight(nTabLabelsBottom );
aOptimalSize.setWidth( std::max(nTabLabelsRight, aOptimalSize.Width()) );
aOptimalSize.AdjustWidth(TAB_OFFSET * 2 );
aOptimalSize.AdjustHeight(TAB_OFFSET * 2 );
return aOptimalSize;
}
Size TabControl::calculateRequisition() const
{
sal_uInt16 nHeaderHeight;
return ImplCalculateRequisition(nHeaderHeight);
}
Size TabControl::GetOptimalSize() const
{
return calculateRequisition();
}
void TabControl::queue_resize(StateChangedType eReason)
{
mbLayoutDirty = true;
Window::queue_resize(eReason);
}
std::vector<sal_uInt16> TabControl::GetPageIDs() const
{
std::vector<sal_uInt16> aIDs;
for (auto const& item : mpTabCtrlData->maItemList)
{
aIDs.push_back(item.id());
}
return aIDs;
}
bool TabControl::set_property(const OUString &rKey, const OUString &rValue)
{
if (rKey == "show-tabs")
{
mbShowTabs = toBool(rValue);
queue_resize();
}
else
return Control::set_property(rKey, rValue);
return true;
}
FactoryFunction TabControl::GetUITestFactory() const
{
return TabControlUIObject::create;
}
void TabControl::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
rJsonWriter.put("id", get_id());
rJsonWriter.put("type", "tabcontrol");
rJsonWriter.put("selected", GetCurPageId());
{
auto childrenNode = rJsonWriter.startArray("children");
for (auto id : GetPageIDs())
{
TabPage* pChild = GetTabPage(id);
if (pChild)
{
auto childNode = rJsonWriter.startStruct();
pChild->DumpAsPropertyTree(rJsonWriter);
if (!pChild->IsVisible())
rJsonWriter.put("hidden", true);
}
}
}
{
auto tabsNode = rJsonWriter.startArray("tabs");
for(auto id : GetPageIDs())
{
auto tabNode = rJsonWriter.startStruct();
rJsonWriter.put("text", GetPageText(id));
rJsonWriter.put("id", id);
rJsonWriter.put("name", GetPageName(id));
}
}
}
sal_uInt16 NotebookbarTabControlBase::m_nHeaderHeight = 0;
IMPL_LINK_NOARG(NotebookbarTabControlBase, OpenMenu, Button*, void)
{
m_aIconClickHdl.Call(static_cast<NotebookBar*>(GetParent()->GetParent()));
}
NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window* pParent)
: TabControl(pParent, WB_STDTABCONTROL)
, bLastContextWasSupported(true)
, eLastContext(vcl::EnumContext::Context::Any)
{
m_pOpenMenu = VclPtr<PushButton>::Create( this , WB_CENTER | WB_VCENTER );
m_pOpenMenu->SetClickHdl(LINK(this, NotebookbarTabControlBase, OpenMenu));
m_pOpenMenu->SetModeImage(Image(StockImage::Yes, SV_RESID_BITMAP_NOTEBOOKBAR));
m_pOpenMenu->SetSizePixel(m_pOpenMenu->GetOptimalSize());
m_pOpenMenu->Show();
}
NotebookbarTabControlBase::~NotebookbarTabControlBase()
{
disposeOnce();
}
void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext )
{
if (eLastContext == eContext)
return;
bool bHandled = false;
TabPage* pPage = GetTabPage(mnCurPageId);
// Try to stay on the current tab (unless the new context has a special tab)
if (pPage && eLastContext != vcl::EnumContext::Context::Any
&& pPage->HasContext(vcl::EnumContext::Context::Any) && pPage->IsEnabled())
{
bHandled = true;
}
for (int nChild = 0; nChild < GetPageCount(); ++nChild)
{
sal_uInt16 nPageId = TabControl::GetPageId(nChild);
pPage = GetTabPage(nPageId);
if (!pPage)
continue;
SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any));
if (eContext != vcl::EnumContext::Context::Any
&& (!bHandled || !pPage->HasContext(vcl::EnumContext::Context::Any))
&& pPage->HasContext(eContext))
{
SetCurPageId(nPageId);
bHandled = true;
bLastContextWasSupported = true;
}
if (!bHandled && bLastContextWasSupported
&& pPage->HasContext(vcl::EnumContext::Context::Default))
{
SetCurPageId(nPageId);
}
}
if (!bHandled)
bLastContextWasSupported = false;
eLastContext = eContext;
// tdf#152908 Tabbed compact toolbar does not repaint itself when tabs getting removed
// For unknown reason this is needed by the tabbed compact toolbar for other than gtk
// vcl backends.
Resize();
}
void NotebookbarTabControlBase::dispose()
{
m_pShortcuts.disposeAndClear();
m_pOpenMenu.disposeAndClear();
TabControl::dispose();
}
void NotebookbarTabControlBase::SetToolBox( ToolBox* pToolBox )
{
m_pShortcuts.set( pToolBox );
}
void NotebookbarTabControlBase::SetIconClickHdl( Link<NotebookBar*, void> aHdl )
{
m_aIconClickHdl = aHdl;
}
static bool lcl_isValidPage(const ImplTabItem& rItem)
{
return rItem.m_bVisible && rItem.m_bEnabled;
}
void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext )
{
sal_Int32 nCurPos = GetPagePos(GetCurPageId());
if (bNext)
{
for (sal_Int32 nPos = nCurPos + 1; nPos < GetPageCount(); nPos++)
if (lcl_isValidPage(mpTabCtrlData->maItemList[nPos]))
{
nCurPos = nPos;
break;
}
}
else
{
for (sal_Int32 nPos = nCurPos - 1; nPos >= 0; nPos--)
if (lcl_isValidPage(mpTabCtrlData->maItemList[nPos]))
{
nCurPos = nPos;
break;
}
}
SelectTabPage( TabControl::GetPageId( nCurPos ) );
}
bool NotebookbarTabControlBase::ImplPlaceTabs( tools::Long nWidth )
{
if ( nWidth <= 0 )
return false;
if ( mpTabCtrlData->maItemList.empty() )
return false;
if (!m_pOpenMenu || m_pOpenMenu->isDisposed())
return false;
const tools::Long nHamburgerWidth = m_pOpenMenu->GetSizePixel().Width();
tools::Long nMaxWidth = nWidth - nHamburgerWidth;
tools::Long nShortcutsWidth = m_pShortcuts != nullptr ? m_pShortcuts->GetSizePixel().getWidth() + 1 : 0;
tools::Long nFullWidth = nShortcutsWidth;
const tools::Long nOffsetX = 2 + nShortcutsWidth;
const tools::Long nOffsetY = 2;
//fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
//of ugly bare tabs on lines of their own
for (auto & item : mpTabCtrlData->maItemList)
{
tools::Long nTabWidth = 0;
if (item.m_bVisible)
{
nTabWidth = ImplGetItemSize(&item, nMaxWidth).getWidth();
if (!item.maText.isEmpty() && nTabWidth < 100)
nTabWidth = 100;
}
nFullWidth += nTabWidth;
}
tools::Long nX = nOffsetX;
tools::Long nY = nOffsetY;
tools::Long nLineWidthAry[100];
nLineWidthAry[0] = 0;
for (auto & item : mpTabCtrlData->maItemList)
{
if (!item.m_bVisible)
continue;
Size aSize = ImplGetItemSize( &item, nMaxWidth );
// set minimum tab size
if( nFullWidth < nMaxWidth && !item.maText.isEmpty() && aSize.getWidth() < 100)
aSize.setWidth( 100 );
if( !item.maText.isEmpty() && aSize.getHeight() < 28 )
aSize.setHeight( 28 );
tools::Rectangle aNewRect( Point( nX, nY ), aSize );
if ( mbSmallInvalidate && (item.maRect != aNewRect) )
mbSmallInvalidate = false;
item.maRect = aNewRect;
item.mnLine = 0;
item.mbFullVisible = true;
nLineWidthAry[0] += aSize.Width();
nX += aSize.Width();
}
// we always have only one line of tabs
// tdf#127610 subtract width of shortcuts from width available for tab items
lcl_AdjustSingleLineTabs(nMaxWidth - nShortcutsWidth, mpTabCtrlData.get());
// position the shortcutbox
if (m_pShortcuts)
{
tools::Long nPosY = (m_nHeaderHeight - m_pShortcuts->GetSizePixel().getHeight()) / 2;
m_pShortcuts->SetPosPixel(Point(0, nPosY));
}
tools::Long nPosY = (m_nHeaderHeight - m_pOpenMenu->GetSizePixel().getHeight()) / 2;
// position the menu
m_pOpenMenu->SetPosPixel(Point(nWidth - nHamburgerWidth, nPosY));
return true;
}
Size NotebookbarTabControlBase::calculateRequisition() const
{
return TabControl::ImplCalculateRequisition(m_nHeaderHeight);
}
Control* NotebookbarTabControlBase::GetOpenMenu()
{
return m_pOpenMenu;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */