Files
libreoffice/vcl/source/control/tabctrl.cxx
Szymon Kłos e681749ffa tdf#105040 Notebookbar: added shortcuts toolbox
In the tabbed notebookbar the shortcuts toolbox is introduced.
It is placed before tab labels, after the menu button.
Customization is possible, buttons are loaded from the
newly created toolbar "Notebookbar shortcuts".

NotebookbarTabControl was moved to the sfx module.

Change-Id: Ifa4a1688c6e92c2f03bd386608e3e9d1c28069d4
Reviewed-on: https://gerrit.libreoffice.org/35194
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de>
2017-03-15 15:54:19 +00:00

2791 lines
88 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 "tools/debug.hxx"
#include "tools/rc.h"
#include <vcl/svapp.hxx>
#include <vcl/help.hxx>
#include <vcl/event.hxx>
#include <vcl/menu.hxx>
#include <vcl/button.hxx>
#include <vcl/tabpage.hxx>
#include <vcl/tabctrl.hxx>
#include <vcl/controllayout.hxx>
#include <vcl/layout.hxx>
#include <vcl/lstbox.hxx>
#include <vcl/settings.hxx>
#include <vcl/uitest/uiobject.hxx>
#include <vcl/builderfactory.hxx>
#include <svids.hrc>
#include "controldata.hxx"
#include "svdata.hxx"
#include "window.h"
#include <unordered_map>
#include <vector>
struct ImplTabItem
{
sal_uInt16 mnId;
VclPtr<TabPage> mpTabPage;
OUString maText;
OUString maFormatText;
OUString maHelpText;
OString maHelpId;
OString maTabName;
Rectangle maRect;
sal_uInt16 mnLine;
bool mbFullVisible;
bool mbEnabled;
Image maTabImage;
ImplTabItem()
: mnId( 0 ), mpTabPage( nullptr ),
mnLine( 0 ), mbFullVisible( false ), mbEnabled( true )
{}
};
struct ImplTabCtrlData
{
std::unordered_map< int, int > maLayoutPageIdToLine;
std::unordered_map< int, int > maLayoutLineToPageId;
Point maItemsOffset; // offset of the tabitems
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;
mnMaxPageWidth = 0;
mnActPageId = 0;
mnCurPageId = 0;
mbFormat = true;
mbRestoreHelpId = false;
mbSmallInvalidate = false;
mpTabCtrlData = new ImplTabCtrlData;
mpTabCtrlData->mpListBox = nullptr;
ImplInitSettings( true, true, 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 bFont,
bool bForeground, bool bBackground )
{
Control::ImplInitSettings( bFont, bForeground );
if ( bBackground )
{
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() );
}
}
}
void TabControl::ImplFreeLayoutData()
{
if( HasLayoutData() )
{
ImplClearLayoutData();
mpTabCtrlData->maLayoutPageIdToLine.clear();
mpTabCtrlData->maLayoutLineToPageId.clear();
}
}
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 ) );
ImplFreeLayoutData();
// delete TabCtrl data
if (mpTabCtrlData)
mpTabCtrlData->mpListBox.disposeAndClear();
delete mpTabCtrlData;
mpTabCtrlData = nullptr;
Control::dispose();
}
ImplTabItem* TabControl::ImplGetItem( sal_uInt16 nId ) const
{
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if( it->mnId == nId )
return &(*it);
}
return nullptr;
}
Size TabControl::ImplGetItemSize( ImplTabItem* pItem, long nMaxWidth )
{
pItem->maFormatText = pItem->maText;
Size aSize( GetCtrlTextWidth( pItem->maFormatText ), GetTextHeight() );
Size aImageSize( 0, 0 );
if( !!pItem->maTabImage )
{
aImageSize = pItem->maTabImage.GetSizePixel();
if( !pItem->maFormatText.isEmpty() )
aImageSize.Width() += GetTextHeight()/4;
}
aSize.Width() += aImageSize.Width();
if( aImageSize.Height() > aSize.Height() )
aSize.Height() = aImageSize.Height();
aSize.Width() += TAB_TABOFFSET_X*2;
aSize.Height() += TAB_TABOFFSET_Y*2;
Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
Rectangle aBoundingRgn, aContentRgn;
const TabitemValue aControlValue(Rectangle(TAB_TABOFFSET_X, TAB_TABOFFSET_Y,
aSize.Width() - TAB_TABOFFSET_X * 2,
aSize.Height() - TAB_TABOFFSET_Y * 2));
if(GetNativeControlRegion( ControlType::TabItem, ControlPart::Entire, aCtrlRegion,
ControlState::ENABLED, aControlValue, OUString(),
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.Width() += TAB_EXTRASPACE_X-pItem->maFormatText.getLength();
// shorten Text if needed
if ( aSize.Width()+4 >= nMaxWidth )
{
OUString aAppendStr("...");
pItem->maFormatText += aAppendStr;
do
{
pItem->maFormatText = pItem->maFormatText.replaceAt( pItem->maFormatText.getLength()-aAppendStr.getLength()-1, 1, "" );
aSize.Width() = GetCtrlTextWidth( pItem->maFormatText );
aSize.Width() += aImageSize.Width();
aSize.Width() += TAB_TABOFFSET_X*2;
}
while ( (aSize.Width()+4 >= nMaxWidth) && (pItem->maFormatText.getLength() > aAppendStr.getLength()) );
if ( aSize.Width()+4 >= nMaxWidth )
{
pItem->maFormatText = ".";
aSize.Width() = 1;
}
}
if( pItem->maFormatText.isEmpty() )
{
if( aSize.Height() < aImageSize.Height()+4 ) //leave space for focus rect
aSize.Height() = 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
{
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;
}
};
bool TabControl::ImplPlaceTabs( long nWidth )
{
if ( nWidth <= 0 )
return false;
if ( mpTabCtrlData->maItemList.empty() )
return false;
long nMaxWidth = nWidth;
const long nOffsetX = 2 + GetItemsOffset().X();
const long nOffsetY = 2 + GetItemsOffset().Y();
//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( std::vector<ImplTabItem>::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
aWidths.push_back(ImplGetItemSize( &(*it), 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));
if ( (mnMaxPageWidth > 0) && (mnMaxPageWidth < nMaxWidth) )
nMaxWidth = mnMaxPageWidth;
nMaxWidth -= GetItemsOffset().X();
long nX = nOffsetX;
long nY = nOffsetY;
sal_uInt16 nLines = 0;
sal_uInt16 nCurLine = 0;
long nLineWidthAry[100];
sal_uInt16 nLinePosAry[101];
nLineWidthAry[0] = 0;
nLinePosAry[0] = 0;
size_t nIndex = 0;
sal_uInt16 nPos = 0;
for( std::vector<ImplTabItem>::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it, ++nIndex )
{
Size aSize = ImplGetItemSize( &(*it), 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] = nPos;
}
Rectangle aNewRect( Point( nX, nY ), aSize );
if ( mbSmallInvalidate && (it->maRect != aNewRect) )
mbSmallInvalidate = false;
it->maRect = aNewRect;
it->mnLine = nLines;
it->mbFullVisible = true;
nLineWidthAry[nLines] += aSize.Width();
nX += aSize.Width();
if ( it->mnId == mnCurPageId )
nCurLine = nLines;
nPos++;
}
if ( nLines )
{ // two or more lines
long nLineHeightAry[100];
long nIH = mpTabCtrlData->maItemList[0].maRect.Bottom()-2;
for ( sal_uInt16 i = 0; i < nLines+1; i++ )
{
if ( i <= nCurLine )
nLineHeightAry[i] = nIH*(nLines-(nCurLine-i)) + GetItemsOffset().Y();
else
nLineHeightAry[i] = nIH*(i-nCurLine-1) + GetItemsOffset().Y();
}
nLinePosAry[nLines+1] = (sal_uInt16)mpTabCtrlData->maItemList.size();
long nDX = 0;
long nModDX = 0;
long nIDX = 0;
sal_uInt16 i = 0;
sal_uInt16 n = 0;
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
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++;
}
it->maRect.Left() += nIDX;
it->maRect.Right() += nIDX + nDX;
it->maRect.Top() = nLineHeightAry[n-1];
it->maRect.Bottom() = nLineHeightAry[n-1] + nIH;
nIDX += nDX;
if ( nModDX )
{
nIDX++;
it->maRect.Right()++;
nModDX--;
}
i++;
}
}
else
{ // only one line
if(ImplGetSVData()->maNWFData.mbCenteredTabs)
{
int nRightSpace = nMaxWidth;//space left on the right by the tabs
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
nRightSpace -= it->maRect.Right()-it->maRect.Left();
}
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
it->maRect.Left() += nRightSpace / 2;
it->maRect.Right() += nRightSpace / 2;
}
}
}
return true;
}
Rectangle TabControl::ImplGetTabRect( sal_uInt16 nItemPos, long nWidth, long nHeight )
{
Size aWinSize = Control::GetOutputSizePixel();
if ( nWidth < 0 )
nWidth = aWinSize.Width();
if ( nHeight < 0 )
nHeight = aWinSize.Height();
if ( mpTabCtrlData->maItemList.empty() )
{
long nW = nWidth-TAB_OFFSET*2;
long nH = nHeight-TAB_OFFSET*2;
return (nW > 0 && nH > 0)
? Rectangle( Point( TAB_OFFSET, TAB_OFFSET ), Size( nW, nH ) )
: Rectangle();
}
if ( nItemPos == TAB_PAGERECT )
{
sal_uInt16 nLastPos;
if ( mnCurPageId )
nLastPos = GetPagePos( mnCurPageId );
else
nLastPos = 0;
Rectangle aRect = ImplGetTabRect( nLastPos, nWidth, nHeight );
long nW = nWidth-TAB_OFFSET*2;
long nH = nHeight-aRect.Bottom()-TAB_OFFSET*2;
aRect = (nW > 0 && nH > 0)
? Rectangle( Point( TAB_OFFSET, aRect.Bottom()+TAB_OFFSET ), Size( nW, nH ) )
: Rectangle();
return aRect;
}
nWidth -= 1;
if ( (nWidth <= 0) || (nHeight <= 0) )
return Rectangle();
if ( mbFormat || (mnLastWidth != nWidth) || (mnLastHeight != nHeight) )
{
vcl::Font aFont( GetFont() );
aFont.SetTransparent( true );
SetFont( aFont );
bool bRet = ImplPlaceTabs( nWidth );
if ( !bRet )
return Rectangle();
mnLastWidth = nWidth;
mnLastHeight = nHeight;
mbFormat = false;
}
return size_t(nItemPos) < mpTabCtrlData->maItemList.size() ? mpTabCtrlData->maItemList[nItemPos].maRect : Rectangle();
}
void TabControl::ImplChangeTabPage( sal_uInt16 nId, sal_uInt16 nOldId )
{
ImplFreeLayoutData();
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 );
Rectangle aRect = ImplGetTabRect( nPos );
if ( !pOldItem || !pItem || (pOldItem->mnLine != pItem->mnLine) )
{
aRect.Left() = 0;
aRect.Top() = 0;
aRect.Right() = Control::GetOutputSizePixel().Width();
}
else
{
aRect.Left() -= 3;
aRect.Top() -= 2;
aRect.Right() += 3;
Invalidate( aRect );
nPos = GetPagePos( nOldId );
aRect = ImplGetTabRect( nPos );
aRect.Left() -= 3;
aRect.Top() -= 2;
aRect.Right() += 3;
}
Invalidate( aRect );
}
if ( pOldPage == pPage )
return;
Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
if ( pOldPage )
{
if ( mbRestoreHelpId )
pCtrlParent->SetHelpId( OString() );
pOldPage->DeactivatePage();
}
if ( pPage )
{
if ( ( GetStyle() & WB_NOBORDER ) )
{
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->ActivatePage();
pPage->Show();
if ( pOldPage && pOldPage->HasChildPathFocus() )
{
sal_uInt16 n = 0;
vcl::Window* pFirstChild = pPage->ImplGetDlgWindow( n, 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.Left() -= TAB_OFFSET;
aRect.Top() -= TAB_OFFSET;
aRect.Right() += TAB_OFFSET;
aRect.Bottom() += TAB_OFFSET;
}
Invalidate( aRect );
}
bool TabControl::ImplPosCurTabPage()
{
// resize/position current TabPage
ImplTabItem* pItem = ImplGetItem( GetCurPageId() );
if ( pItem && pItem->mpTabPage )
{
if ( ( GetStyle() & WB_NOBORDER ) )
{
Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
pItem->mpTabPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
return true;
}
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 );
Rectangle aRect = ImplGetTabRect( nCurPos );
const ImplTabItem& rItem = mpTabCtrlData->maItemList[ nCurPos ];
Size aTabSize = aRect.GetSize();
Size aImageSize( 0, 0 );
long nTextHeight = GetTextHeight();
long nTextWidth = 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.Width() += GetTextHeight()/4;
}
if( !rItem.maFormatText.isEmpty() )
{
// show focus around text
aRect.Left() = aRect.Left()+aImageSize.Width()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1-1;
aRect.Top() = aRect.Top()+((aTabSize.Height()-nTextHeight)/2)-1-1;
aRect.Right() = aRect.Left()+nTextWidth+2;
aRect.Bottom() = aRect.Top()+nTextHeight+2;
}
else
{
// show focus around image
long nXPos = aRect.Left()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1;
long nYPos = aRect.Top();
if( aImageSize.Height() < aRect.GetHeight() )
nYPos += (aRect.GetHeight() - aImageSize.Height())/2;
aRect.Left() = nXPos - 2;
aRect.Top() = nYPos - 2;
aRect.Right() = aRect.Left() + aImageSize.Width() + 4;
aRect.Bottom() = aRect.Top() + aImageSize.Height() + 4;
}
ShowFocus( aRect );
}
void TabControl::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplTabItem* pItem, const Rectangle& rCurRect,
bool bFirstInGroup, bool bLastInGroup, bool /* bIsCurrentItem */ )
{
if (pItem->maRect.IsEmpty())
return;
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
Rectangle aRect = pItem->maRect;
long nLeftBottom = aRect.Bottom();
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->mnId == mnCurPageId)
{
nOff2 = 2;
if (!ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise)
nOff3 = 1;
}
else
{
Point aLeftTestPos = aRect.BottomLeft();
Point aRightTestPos = aRect.BottomRight();
if (aLeftTestPos.Y() == rCurRect.Bottom())
{
aLeftTestPos.X() -= 2;
if (rCurRect.IsInside(aLeftTestPos))
bLeftBorder = false;
aRightTestPos.X() += 2;
if (rCurRect.IsInside(aRightTestPos))
bRightBorder = false;
}
else
{
if (rCurRect.IsInside(aLeftTestPos))
nLeftBottom -= 2;
if (rCurRect.IsInside(aRightTestPos))
nRightBottom -= 2;
}
}
ControlState nState = ControlState::NONE;
if (pItem->mnId == mnCurPageId)
{
nState |= ControlState::SELECTED;
// only the selected item can be focussed
if (HasFocus())
nState |= ControlState::FOCUSED;
}
if (IsEnabled())
nState |= ControlState::ENABLED;
if (IsMouseOver() && pItem->maRect.IsInside(GetPointerPosPixel()))
{
nState |= ControlState::ROLLOVER;
for (std::vector<ImplTabItem>::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it)
{
if( (&(*it) != pItem) && (it->maRect.IsInside(GetPointerPosPixel())))
{
nState &= ~ControlState::ROLLOVER; // avoid multiple highlighted tabs
break;
}
}
}
bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire);
if ( bNativeOK )
{
TabitemValue tiValue(Rectangle(pItem->maRect.Left() + TAB_TABOFFSET_X,
pItem->maRect.Top() + TAB_TABOFFSET_Y,
pItem->maRect.Right() - TAB_TABOFFSET_X,
pItem->maRect.Bottom() - TAB_TABOFFSET_Y));
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;
Rectangle aCtrlRegion( pItem->maRect );
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(Color(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);
long nTextHeight = rRenderContext.GetTextHeight();
long nTextWidth = rRenderContext.GetCtrlTextWidth(pItem->maFormatText);
if (!!pItem->maTabImage)
{
aImageSize = pItem->maTabImage.GetSizePixel();
if (!pItem->maFormatText.isEmpty())
aImageSize.Width() += GetTextHeight() / 4;
}
long nXPos = aRect.Left() + ((aTabSize.Width() - nTextWidth - aImageSize.Width()) / 2) - nOff - nOff3;
long nYPos = aRect.Top() + ((aTabSize.Height() - nTextHeight) / 2) - nOff3;
if (!pItem->maFormatText.isEmpty())
{
DrawTextFlags nStyle = DrawTextFlags::Mnemonic;
if (!pItem->mbEnabled)
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 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.Y() += (aRect.GetHeight() - aImageSize.Height()) / 2;
rRenderContext.DrawImage(aImgTL, pItem->maTabImage, pItem->mbEnabled ? 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->GetSelectEntryPos() ) );
}
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.get() == nullptr )
{
if( rMEvt.IsLeft() )
{
sal_uInt16 nPageId = GetPageId( rMEvt.GetPosPixel() );
ImplTabItem* pItem = ImplGetItem( nPageId );
if( pItem && pItem->mbEnabled )
SelectTabPage( nPageId );
}
}
}
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 );
}
void TabControl::Paint( vcl::RenderContext& rRenderContext, const Rectangle& rRect)
{
if (!(GetStyle() & WB_NOBORDER))
ImplPaint(rRenderContext, rRect);
}
void TabControl::ImplPaint(vcl::RenderContext& rRenderContext, const Rectangle& rRect)
{
HideFocus();
// reformat if needed
Rectangle aRect = ImplGetTabRect(TAB_PAGERECT);
// find current item
ImplTabItem* pCurItem = nullptr;
for (std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if (it->mnId == mnCurPageId)
{
pCurItem = &(*it);
break;
}
}
// Draw the TabPage border
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
Rectangle aCurRect;
aRect.Left() -= TAB_OFFSET;
aRect.Top() -= TAB_OFFSET;
aRect.Right() += TAB_OFFSET;
aRect.Bottom() += 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.Left() -= 10;
aRect.Right() += 10;
}
if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
{
const ImplControlValue aControlValue;
ControlState nState = ControlState::ENABLED;
if (!IsEnabled())
nState &= ~ControlState::ENABLED;
if (HasFocus())
nState |= ControlState::FOCUSED;
vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
aClipRgn.Intersect(aRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
aRect, nState, aControlValue, OUString());
}
if (rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire))
{
Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
aClipRgn = rRenderContext.GetActiveClipRegion();
aClipRgn.Intersect(aHeaderRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
aHeaderRect, nState, aControlValue, OUString());
}
}
}
else
{
long nTopOff = 1;
if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
else
rRenderContext.SetLineColor(Color(COL_BLACK));
if (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());
}
}
if (!mpTabCtrlData->maItemList.empty() && 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;
// Event 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 + mpTabCtrlData->maItemList.size();
idx = mpTabCtrlData->maItemList.size() - 1;
}
else
{
pLastTab = mpTabCtrlData->maItemList.data();
pFirstTab = pLastTab + mpTabCtrlData->maItemList.size();
idx = 0;
}
while (idx < mpTabCtrlData->maItemList.size())
{
ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
if (pItem != pCurItem)
{
vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
aClipRgn.Intersect(pItem->maRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
ImplDrawItem(rRenderContext, pItem, aCurRect, false/*bLayout*/,
pItem == pFirstTab, pItem == pLastTab);
}
}
if (bDrawTabsRTL)
idx--;
else
idx++;
}
if (pCurItem)
{
vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
aClipRgn.Intersect(pCurItem->maRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
ImplDrawItem(rRenderContext, pCurItem, aCurRect,
pCurItem == pFirstTab, pCurItem == pLastTab, true);
}
}
}
if (HasFocus())
ImplShowFocus();
mbSmallInvalidate = true;
}
void TabControl::setAllocation(const Size &rAllocation)
{
ImplFreeLayoutData();
if ( !IsReallyShown() )
return;
if( mpTabCtrlData->mpListBox )
{
// get the listbox' preferred size
Size aTabCtrlSize( GetSizePixel() );
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;
long nNewWidth = aNewSize.Width();
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if ( !it->mbFullVisible ||
(it->maRect.Right()-2 >= nNewWidth) )
{
mbSmallInvalidate = false;
break;
}
}
if ( mbSmallInvalidate )
{
Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
aRect.Left() -= TAB_OFFSET+TAB_BORDER_LEFT;
aRect.Top() -= TAB_OFFSET+TAB_BORDER_TOP;
aRect.Right() += TAB_OFFSET+TAB_BORDER_RIGHT;
aRect.Bottom() += 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 )
{
ImplShowFocus();
SetInputContext( InputContext( GetFont() ) );
}
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() )
{
Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
aItemRect.Left() = aPt.X();
aItemRect.Top() = aPt.Y();
aPt = OutputToScreenPixel( aItemRect.BottomRight() );
aItemRect.Right() = aPt.X();
aItemRect.Bottom() = aPt.Y();
Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
return;
}
}
else if ( rHEvt.GetMode() & HelpEventMode::EXTENDED )
{
OUString aHelpId( OStringToOUString( GetHelpId( nItemId ), RTL_TEXTENCODING_UTF8 ) );
if ( !aHelpId.isEmpty() )
{
// call Help if existing
Help* pHelp = Application::GetHelp();
if ( pHelp )
pHelp->Start( aHelpId, this );
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 )
{
Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
aItemRect.Left() = aPt.X();
aItemRect.Top() = aPt.Y();
aPt = OutputToScreenPixel( aItemRect.BottomRight() );
aItemRect.Right() = aPt.X();
aItemRect.Bottom() = 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;
// show tooltip if not text but image is set and helptext is available
if ( !rHelpText.isEmpty() && pItem->maText.isEmpty() && !!pItem->maTabImage )
{
Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
aItemRect.Left() = aPt.X();
aItemRect.Top() = aPt.Y();
aPt = OutputToScreenPixel( aItemRect.BottomRight() );
aItemRect.Right() = aPt.X();
aItemRect.Bottom() = 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( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
aMenu->InsertItem( it->mnId, it->maText, MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK );
if ( it->mnId == mnCurPageId )
aMenu->CheckItem( it->mnId );
aMenu->SetHelpId( it->mnId, it->maHelpId );
}
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( true, false, false );
Invalidate();
}
else if ( nType == StateChangedType::ControlForeground )
{
ImplInitSettings( false, true, false );
Invalidate();
}
else if ( nType == StateChangedType::ControlBackground )
{
ImplInitSettings( false, false, 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, true, true );
Invalidate();
}
}
Rectangle* TabControl::ImplFindPartRect( const Point& rPt )
{
ImplTabItem* pFoundItem = nullptr;
int nFound = 0;
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if ( it->maRect.IsInside( rPt ) )
{
// assure that only one tab is highlighted at a time
nFound++;
pFoundItem = &(*it);
}
}
// assure that only one tab is highlighted at a time
return nFound == 1 ? &pFoundItem->maRect : nullptr;
}
bool TabControl::PreNotify( NotifyEvent& rNEvt )
{
const MouseEvent* pMouseEvt = nullptr;
if( (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE) && (pMouseEvt = rNEvt.GetMouseEvent()) != nullptr )
{
if( !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
{
// trigger redraw if mouse over state has changed
if( IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) )
{
Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
if( pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) )
{
vcl::Region aClipRgn;
if( pLastRect )
{
// allow for slightly bigger tabitems
// as used by gtk
// TODO: query for the correct sizes
Rectangle aRect(*pLastRect);
aRect.Left()-=2;
aRect.Right()+=2;
aRect.Top()-=3;
aClipRgn.Union( aRect );
}
if( pRect )
{
// allow for slightly bigger tabitems
// as used by gtk
// TODO: query for the correct sizes
Rectangle aRect(*pRect);
aRect.Left()-=2;
aRect.Right()+=2;
aRect.Top()-=3;
aClipRgn.Union( aRect );
}
if( !aClipRgn.IsEmpty() )
Invalidate( aClipRgn );
}
}
}
}
return Control::PreNotify(rNEvt);
}
bool TabControl::EventNotify( NotifyEvent& rNEvt )
{
bool bRet = false;
if ( rNEvt.GetType() == MouseNotifyEvent::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 )
{
ImplFreeLayoutData();
Size aNewSize( rSize );
aNewSize.Width() += TAB_OFFSET*2;
Rectangle aRect = ImplGetTabRect( TAB_PAGERECT,
aNewSize.Width(), aNewSize.Height() );
aNewSize.Height() += aRect.Top()+TAB_OFFSET;
Window::SetOutputSizePixel( aNewSize );
}
Size TabControl::GetTabPageSizePixel() const
{
Rectangle aRect = const_cast<TabControl*>(this)->ImplGetTabRect( TAB_PAGERECT );
return aRect.GetSize();
}
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.push_back( ImplTabItem() );
pItem = &mpTabCtrlData->maItemList.back();
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->InsertEntry( rText );
}
else
{
std::vector< ImplTabItem >::iterator new_it =
mpTabCtrlData->maItemList.insert( mpTabCtrlData->maItemList.begin() + nPos, ImplTabItem() );
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->mnId = nPageId;
pItem->mpTabPage = nullptr;
pItem->maText = rText;
pItem->mbFullVisible = false;
mbFormat = true;
if ( IsUpdateMode() )
Invalidate();
ImplFreeLayoutData();
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 )
{
//remove page item
std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin() + nPos;
bool bIsCurrentPage = (it->mnId == mnCurPageId);
mpTabCtrlData->maItemList.erase( it );
if( mpTabCtrlData->mpListBox )
{
mpTabCtrlData->mpListBox->RemoveEntry( nPos );
mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
}
// If current page is removed, than first page gets the current page
if ( bIsCurrentPage )
{
mnCurPageId = 0;
if( ! mpTabCtrlData->maItemList.empty() )
{
// don't do this by simply setting mnCurPageId to pFirstItem->mnId
// 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].mnId );
}
}
mbFormat = true;
if ( IsUpdateMode() )
Invalidate();
ImplFreeLayoutData();
CallEventListeners( VclEventId::TabpageRemoved, reinterpret_cast<void*>(nPageId) );
}
}
void TabControl::Clear()
{
// clear item list
mpTabCtrlData->maItemList.clear();
mnCurPageId = 0;
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->Clear();
ImplFreeLayoutData();
mbFormat = true;
if ( IsUpdateMode() )
Invalidate();
CallEventListeners( VclEventId::TabpageRemovedAll );
}
void TabControl::EnablePage( sal_uInt16 i_nPageId, bool i_bEnable )
{
ImplTabItem* pItem = ImplGetItem( i_nPageId );
if ( pItem && pItem->mbEnabled != i_bEnable )
{
pItem->mbEnabled = i_bEnable;
mbFormat = true;
if( mpTabCtrlData->mpListBox )
mpTabCtrlData->mpListBox->SetEntryFlags( GetPagePos( i_nPageId ),
i_bEnable ? ListBoxEntryFlags::NONE : (ListBoxEntryFlags::DisableSelection | ListBoxEntryFlags::DrawDisabled) );
if( pItem->mnId == mnCurPageId )
{
// SetCurPageId will change to an enabled page
SetCurPageId( mnCurPageId );
}
else if ( IsUpdateMode() )
Invalidate();
}
}
sal_uInt16 TabControl::GetPageCount() const
{
return (sal_uInt16)mpTabCtrlData->maItemList.size();
}
sal_uInt16 TabControl::GetPageId( sal_uInt16 nPos ) const
{
if( size_t(nPos) < mpTabCtrlData->maItemList.size() )
return mpTabCtrlData->maItemList[ nPos ].mnId;
return 0;
}
sal_uInt16 TabControl::GetPagePos( sal_uInt16 nPageId ) const
{
for( std::vector< ImplTabItem >::const_iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if ( it->mnId == nPageId )
return (sal_uInt16)(it - mpTabCtrlData->maItemList.begin());
}
return TAB_PAGE_NOTFOUND;
}
sal_uInt16 TabControl::GetPageId( const Point& rPos ) const
{
for( size_t i = 0; i < mpTabCtrlData->maItemList.size(); ++i )
{
if ( const_cast<TabControl*>(this)->ImplGetTabRect( static_cast<sal_uInt16>(i) ).IsInside( rPos ) )
return mpTabCtrlData->maItemList[ i ].mnId;
}
return 0;
}
sal_uInt16 TabControl::GetPageId( const TabPage& rPage ) const
{
for( std::vector< ImplTabItem >::const_iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if ( it->mpTabPage == &rPage )
return it->mnId;
}
return 0;
}
sal_uInt16 TabControl::GetPageId( const OString& rName ) const
{
for( std::vector< ImplTabItem >::const_iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if ( it->maTabName == rName )
return it->mnId;
}
return 0;
}
void TabControl::SetCurPageId( sal_uInt16 nPageId )
{
sal_uInt16 nPos = GetPagePos( nPageId );
while( nPos != TAB_PAGE_NOTFOUND &&
! mpTabCtrlData->maItemList[nPos].mbEnabled )
{
nPos++;
if( size_t(nPos) >= mpTabCtrlData->maItemList.size() )
nPos = 0;
if( mpTabCtrlData->maItemList[nPos].mnId == nPageId )
break;
}
if( nPos != TAB_PAGE_NOTFOUND )
{
nPageId = mpTabCtrlData->maItemList[nPos].mnId;
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) )
{
ImplFreeLayoutData();
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) )
{
if ( pTabPage )
{
SAL_WARN_IF( pTabPage->IsVisible() && !isLayoutEnabled(pTabPage), "vcl",
"TabControl::SetTabPage() - Non-Layout Enabled Page is visible" );
if ( IsDefaultSize() )
SetTabPageSizePixel( pTabPage->GetSizePixel() );
// only set here, so that Resize does not reposition TabPage
pItem->mpTabPage = pTabPage;
queue_resize();
if ( pItem->mnId == mnCurPageId )
ImplChangeTabPage( pItem->mnId, 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 )
{
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();
ImplFreeLayoutData();
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 );
if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() )
{
Help* pHelp = Application::GetHelp();
if ( pHelp )
pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this );
}
return pItem->maHelpText;
}
void TabControl::SetHelpId( sal_uInt16 nPageId, const OString& rId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if ( pItem )
pItem->maHelpId = rId;
}
OString TabControl::GetHelpId( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if (pItem)
return pItem->maHelpId;
return OString();
}
void TabControl::SetPageName( sal_uInt16 nPageId, const OString& rName ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if ( pItem )
pItem->maTabName = rName;
}
OString TabControl::GetPageName( sal_uInt16 nPageId ) const
{
ImplTabItem* pItem = ImplGetItem( nPageId );
if (pItem)
return pItem->maTabName;
return OString();
}
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();
}
}
Rectangle TabControl::GetCharacterBounds( sal_uInt16 nPageId, long nIndex ) const
{
Rectangle aRet;
if( !HasLayoutData() || ! mpTabCtrlData->maLayoutPageIdToLine.size() )
FillLayoutData();
if( HasLayoutData() )
{
std::unordered_map< int, int >::const_iterator it = mpTabCtrlData->maLayoutPageIdToLine.find( (int)nPageId );
if( it != mpTabCtrlData->maLayoutPageIdToLine.end() )
{
Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( it->second );
if( (aPair.B() - aPair.A()) >= nIndex )
aRet = mpControlData->mpLayoutData->GetCharacterBounds( aPair.A() + nIndex );
}
}
return aRet;
}
long TabControl::GetIndexForPoint( const Point& rPoint, sal_uInt16& rPageId ) const
{
long nRet = -1;
if( !HasLayoutData() || ! mpTabCtrlData->maLayoutPageIdToLine.size() )
FillLayoutData();
if( HasLayoutData() )
{
int nIndex = mpControlData->mpLayoutData->GetIndexForPoint( rPoint );
if( nIndex != -1 )
{
// what line (->pageid) is this index in ?
int nLines = mpControlData->mpLayoutData->GetLineCount();
int nLine = -1;
while( ++nLine < nLines )
{
Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( nLine );
if( aPair.A() <= nIndex && aPair.B() >= nIndex )
{
nRet = nIndex - aPair.A();
rPageId = (sal_uInt16)mpTabCtrlData->maLayoutLineToPageId[ nLine ];
break;
}
}
}
}
return nRet;
}
void TabControl::FillLayoutData() const
{
mpTabCtrlData->maLayoutLineToPageId.clear();
mpTabCtrlData->maLayoutPageIdToLine.clear();
const_cast<TabControl*>(this)->Invalidate();
}
Rectangle TabControl::GetTabBounds( sal_uInt16 nPageId ) const
{
Rectangle aRet;
ImplTabItem* pItem = ImplGetItem( nPageId );
if(pItem)
aRet = pItem->maRect;
return aRet;
}
void TabControl::SetItemsOffset( const Point& rOffs )
{
if( mpTabCtrlData )
mpTabCtrlData->maItemsOffset = rOffs;
}
Point TabControl::GetItemsOffset() const
{
if( mpTabCtrlData )
return mpTabCtrlData->maItemsOffset;
else
return Point();
}
Size TabControl::calculateRequisition() const
{
Size aOptimalPageSize(0, 0);
sal_uInt16 nOrigPageId = GetCurPageId();
for( std::vector< ImplTabItem >::const_iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
const TabPage *pPage = it->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(it->mnId);
pThis->ActivatePage();
pPage = it->mpTabPage;
}
if (!pPage)
continue;
Size aPageSize(VclContainer::getLayoutRequisition(*pPage));
if (aPageSize.Width() > aOptimalPageSize.Width())
aOptimalPageSize.Width() = aPageSize.Width();
if (aPageSize.Height() > aOptimalPageSize.Height())
aOptimalPageSize.Height() = 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();
}
long nTabLabelsBottom = 0, nTabLabelsRight = 0;
for( std::vector< ImplTabItem >::const_iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
TabControl* pThis = const_cast<TabControl*>(this);
sal_uInt16 nPos = it - mpTabCtrlData->maItemList.begin();
Rectangle aTabRect = pThis->ImplGetTabRect(nPos, aOptimalPageSize.Width(), LONG_MAX);
if (aTabRect.Bottom() > nTabLabelsBottom)
nTabLabelsBottom = aTabRect.Bottom();
if (aTabRect.Right() > nTabLabelsRight)
nTabLabelsRight = aTabRect.Right();
}
Size aOptimalSize(aOptimalPageSize);
aOptimalSize.Height() += nTabLabelsBottom;
aOptimalSize.Width() = std::max(nTabLabelsRight, aOptimalSize.Width());
aOptimalSize.Width() += TAB_OFFSET * 2;
aOptimalSize.Height() += TAB_OFFSET * 2;
return aOptimalSize;
}
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 itr = mpTabCtrlData->maItemList.begin(), itrEnd = mpTabCtrlData->maItemList.end();
itr != itrEnd; ++itr)
{
aIDs.push_back(itr->mnId);
}
return aIDs;
}
FactoryFunction TabControl::GetUITestFactory() const
{
return TabControlUIObject::create;
}
sal_uInt16 NotebookbarTabControlBase::m_nHeaderHeight = 0;
NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window* pParent)
: TabControl(pParent, WB_STDTABCONTROL)
, bLastContextWasSupported(true)
, eLastContext(vcl::EnumContext::Context::Any)
{
BitmapEx aBitmap(VclResId(SV_RESID_BITMAP_NOTEBOOKBAR), Color());
InsertPage(1, "");
SetPageImage(1, Image(aBitmap));
}
NotebookbarTabControlBase::~NotebookbarTabControlBase()
{
disposeOnce();
}
void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext )
{
if (eLastContext != eContext)
{
bool bHandled = false;
for (int nChild = 0; nChild < GetChildCount(); ++nChild)
{
TabPage* pPage = static_cast<TabPage*>(GetChild(nChild));
sal_uInt16 nPageId = TabControl::GetPageId(*pPage);
if (pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any))
EnablePage(nPageId);
else
EnablePage(nPageId, false);
if (!bHandled && bLastContextWasSupported
&& pPage->HasContext(vcl::EnumContext::Context::Default))
{
SetCurPageId(nPageId);
}
if (pPage->HasContext(eContext) && eContext != vcl::EnumContext::Context::Any)
{
SetCurPageId(nPageId);
bHandled = true;
bLastContextWasSupported = true;
}
}
if (!bHandled)
bLastContextWasSupported = false;
eLastContext = eContext;
}
}
void NotebookbarTabControlBase::dispose()
{
m_pShortcuts.disposeAndClear();
TabControl::dispose();
}
void NotebookbarTabControlBase::SetToolBox( ToolBox* pToolBox )
{
m_pShortcuts.set( pToolBox );
}
void NotebookbarTabControlBase::SetIconClickHdl( Link<NotebookBar*, void> aHdl )
{
m_aIconClickHdl = aHdl;
}
sal_uInt16 NotebookbarTabControlBase::GetPageId( const Point& rPos ) const
{
for( size_t i = 0; i < mpTabCtrlData->maItemList.size(); ++i )
{
if ( const_cast<NotebookbarTabControlBase*>(this)->ImplGetTabRect( static_cast<sal_uInt16>(i) ).IsInside( rPos ) )
if ( mpTabCtrlData->maItemList[ i ].mbEnabled )
return mpTabCtrlData->maItemList[ i ].mnId;
}
return 0;
}
void NotebookbarTabControlBase::SelectTabPage( sal_uInt16 nPageId )
{
if ( nPageId == 1 )
m_aIconClickHdl.Call( static_cast<NotebookBar*>(GetParent()->GetParent()) );
else
{
TabControl::SelectTabPage( nPageId );
Resize();
}
}
void NotebookbarTabControlBase::SetCurPageId( sal_uInt16 nPageId )
{
if ( nPageId != 1 )
{
TabControl::SetCurPageId( nPageId );
Resize();
}
if ( nPageId == GetPageCount() )
ImplActivateTabPage( true );
}
void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext )
{
sal_uInt16 nCurPos = GetPagePos( GetCurPageId() );
if ( bNext && nCurPos + 1 < GetPageCount() )
{
sal_uInt16 nOldPos = nCurPos;
nCurPos++;
ImplTabItem* pItem = &mpTabCtrlData->maItemList[nCurPos];
while ( !pItem->mbEnabled && nCurPos + 1 < GetPageCount())
{
nCurPos++;
pItem = &mpTabCtrlData->maItemList[nCurPos];
}
if ( !pItem->mbEnabled )
nCurPos = nOldPos;
}
else if ( !bNext && nCurPos )
{
nCurPos--;
ImplTabItem* pItem = &mpTabCtrlData->maItemList[nCurPos];
while ( nCurPos && !pItem->mbEnabled )
{
nCurPos--;
pItem = &mpTabCtrlData->maItemList[nCurPos];
}
}
SelectTabPage( TabControl::GetPageId( nCurPos ) );
}
sal_uInt16 NotebookbarTabControlBase::GetHeaderHeight()
{
return m_nHeaderHeight;
}
bool NotebookbarTabControlBase::ImplPlaceTabs( long nWidth )
{
if ( nWidth <= 0 )
return false;
if ( mpTabCtrlData->maItemList.empty() )
return false;
long nMaxWidth = nWidth;
const long nOffsetX = 2 + GetItemsOffset().X();
const long nOffsetY = 2 + GetItemsOffset().Y();
//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( std::vector<ImplTabItem>::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
aWidths.push_back(ImplGetItemSize( &(*it), 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));
if ( (mnMaxPageWidth > 0) && (mnMaxPageWidth < nMaxWidth) )
nMaxWidth = mnMaxPageWidth;
nMaxWidth -= GetItemsOffset().X();
long nShortcutsWidth = m_pShortcuts != nullptr ? m_pShortcuts->GetSizePixel().getWidth() : 0;
long nX = nOffsetX;
long nY = nOffsetY;
sal_uInt16 nLines = 0;
sal_uInt16 nCurLine = 0;
long nLineWidthAry[100];
sal_uInt16 nLinePosAry[101];
nLineWidthAry[0] = 0;
nLinePosAry[0] = 0;
size_t nIndex = 0;
sal_uInt16 nPos = 0;
sal_uInt16 nHiddenWidth = 0;
for( std::vector<ImplTabItem>::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it, ++nIndex )
{
if( it == mpTabCtrlData->maItemList.begin() + 1 )
nX += nShortcutsWidth;
Size aSize = ImplGetItemSize( &(*it), 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] = nPos;
}
if( !it->maText.isEmpty() && aSize.getWidth() < 100)
aSize.Width() = 100;
Rectangle aNewRect( Point( nX, nY ), aSize );
if ( mbSmallInvalidate && (it->maRect != aNewRect) )
mbSmallInvalidate = false;
// don't show empty space when tab is hidden, move next tabs to the left
if ( it->mpTabPage && !it->mpTabPage->HasContext(vcl::EnumContext::Context::Any) )
{
aNewRect.setX(aNewRect.getX() - nHiddenWidth);
nHiddenWidth += aNewRect.getWidth();
}
it->maRect = aNewRect;
it->mnLine = nLines;
it->mbFullVisible = true;
nLineWidthAry[nLines] += aSize.Width();
nX += aSize.Width();
if ( it->mnId == mnCurPageId )
nCurLine = nLines;
nPos++;
}
if ( nLines )
{ // two or more lines
long nLineHeightAry[100];
long nIH = mpTabCtrlData->maItemList[0].maRect.Bottom()-2;
for ( sal_uInt16 i = 0; i < nLines+1; i++ )
{
if ( i <= nCurLine )
nLineHeightAry[i] = nIH*(nLines-(nCurLine-i)) + GetItemsOffset().Y();
else
nLineHeightAry[i] = nIH*(i-nCurLine-1) + GetItemsOffset().Y();
}
nLinePosAry[nLines+1] = (sal_uInt16)mpTabCtrlData->maItemList.size();
long nDX = 0;
long nModDX = 0;
long nIDX = 0;
sal_uInt16 i = 0;
sal_uInt16 n = 0;
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
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++;
}
it->maRect.Left() += nIDX;
it->maRect.Right() += nIDX + nDX;
it->maRect.Top() = nLineHeightAry[n-1];
it->maRect.Bottom() = nLineHeightAry[n-1] + nIH;
nIDX += nDX;
if ( nModDX )
{
nIDX++;
it->maRect.Right()++;
nModDX--;
}
i++;
}
}
else
{ // only one line
if(ImplGetSVData()->maNWFData.mbCenteredTabs)
{
int nRightSpace = nMaxWidth;//space left on the right by the tabs
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
nRightSpace -= it->maRect.Right()-it->maRect.Left();
}
for( std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
it->maRect.Left() += nRightSpace / 2;
it->maRect.Right() += nRightSpace / 2;
}
}
}
return true;
}
void NotebookbarTabControlBase::ImplPaint(vcl::RenderContext& rRenderContext, const Rectangle& rRect)
{
HideFocus();
// reformat if needed
Rectangle aRect = ImplGetTabRect(TAB_PAGERECT);
// find current item
ImplTabItem* pCurItem = nullptr;
for (std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
if (it->mnId == mnCurPageId)
{
pCurItem = &(*it);
break;
}
}
// Draw the TabPage border
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
Rectangle aCurRect;
aRect.Left() -= TAB_OFFSET;
aRect.Top() -= TAB_OFFSET;
aRect.Right() += TAB_OFFSET;
aRect.Bottom() += 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.Left() -= 10;
aRect.Right() += 10;
}
if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
{
const ImplControlValue aControlValue;
ControlState nState = ControlState::ENABLED;
if (!IsEnabled())
nState &= ~ControlState::ENABLED;
if (HasFocus())
nState |= ControlState::FOCUSED;
vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
aClipRgn.Intersect(aRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
aRect, nState, aControlValue, OUString());
}
if (rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire))
{
Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
aClipRgn = rRenderContext.GetActiveClipRegion();
aClipRgn.Intersect(aHeaderRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
aHeaderRect, nState, aControlValue, OUString());
}
}
}
else
{
long nTopOff = 1;
if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
else
rRenderContext.SetLineColor(Color(COL_BLACK));
if (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());
}
}
if (!mpTabCtrlData->maItemList.empty() && 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;
// Event 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 + mpTabCtrlData->maItemList.size();
idx = mpTabCtrlData->maItemList.size() - 1;
}
else
{
pLastTab = mpTabCtrlData->maItemList.data();
pFirstTab = pLastTab + mpTabCtrlData->maItemList.size();
idx = 0;
}
while (idx < mpTabCtrlData->maItemList.size())
{
ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
if ((pItem != pCurItem) && (pItem->mbEnabled))
{
vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
aClipRgn.Intersect(pItem->maRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
ImplDrawItem(rRenderContext, pItem, aCurRect, false/*bLayout*/,
pItem == pFirstTab, pItem == pLastTab);
}
}
if (bDrawTabsRTL)
idx--;
else
idx++;
}
if (pCurItem)
{
vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
aClipRgn.Intersect(pCurItem->maRect);
if (!rRect.IsEmpty())
aClipRgn.Intersect(rRect);
if (!aClipRgn.IsEmpty())
{
ImplDrawItem(rRenderContext, pCurItem, aCurRect,
pCurItem == pFirstTab, pCurItem == pLastTab, true);
}
}
}
if (HasFocus())
ImplShowFocus();
mbSmallInvalidate = true;
Control::Paint(rRenderContext, rRect);
}
Size NotebookbarTabControlBase::calculateRequisition() const
{
Size aOptimalPageSize(0, 0);
sal_uInt16 nOrigPageId = GetCurPageId();
for( std::vector< ImplTabItem >::const_iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
const TabPage *pPage = it->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)
{
NotebookbarTabControlBase *pThis = const_cast<NotebookbarTabControlBase*>(this);
pThis->SetCurPageId(it->mnId);
pThis->ActivatePage();
pPage = it->mpTabPage;
}
if (!pPage)
continue;
Size aPageSize(VclContainer::getLayoutRequisition(*pPage));
if (aPageSize.Width() > aOptimalPageSize.Width())
aOptimalPageSize.Width() = aPageSize.Width();
if (aPageSize.Height() > aOptimalPageSize.Height())
aOptimalPageSize.Height() = 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())
{
NotebookbarTabControlBase *pThis = const_cast<NotebookbarTabControlBase*>(this);
pThis->SetCurPageId(nOrigPageId);
pThis->ActivatePage();
}
long nTabLabelsBottom = 0, nTabLabelsRight = 0;
for( std::vector< ImplTabItem >::const_iterator it = mpTabCtrlData->maItemList.begin();
it != mpTabCtrlData->maItemList.end(); ++it )
{
NotebookbarTabControlBase* pThis = const_cast<NotebookbarTabControlBase*>(this);
sal_uInt16 nPos = it - mpTabCtrlData->maItemList.begin();
Rectangle aTabRect = pThis->ImplGetTabRect(nPos, aOptimalPageSize.Width(), LONG_MAX);
if (aTabRect.Bottom() > nTabLabelsBottom)
{
nTabLabelsBottom = aTabRect.Bottom();
m_nHeaderHeight = aTabRect.Bottom();
}
if (aTabRect.Right() > nTabLabelsRight)
nTabLabelsRight = aTabRect.Right();
}
Size aOptimalSize(aOptimalPageSize);
aOptimalSize.Height() += nTabLabelsBottom;
aOptimalSize.Width() = std::max(nTabLabelsRight, aOptimalSize.Width());
aOptimalSize.Width() += TAB_OFFSET * 2;
aOptimalSize.Height() += TAB_OFFSET * 2;
return aOptimalSize;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */