diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/control/tabctrl.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | vcl/source/control/tabctrl.cxx | 2459 |
1 files changed, 2459 insertions, 0 deletions
diff --git a/vcl/source/control/tabctrl.cxx b/vcl/source/control/tabctrl.cxx new file mode 100644 index 000000000..278fd59cd --- /dev/null +++ b/vcl/source/control/tabctrl.cxx @@ -0,0 +1,2459 @@ +/* -*- 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/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> + +class ImplTabItem final +{ + sal_uInt16 m_nId; + +public: + VclPtr<TabPage> mpTabPage; + OUString maText; + OUString maFormatText; + OUString maHelpText; + OUString maAccessibleName; + OUString maAccessibleDescription; + OString 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::unordered_map< int, int > maLayoutPageIdToLine; + std::unordered_map< int, int > maLayoutLineToPageId; + 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() ); + } +} + +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(); + 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_TABOFFSET_X*2 ); + aSize.AdjustHeight(TAB_TABOFFSET_Y*2 ); + + tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize ); + tools::Rectangle aBoundingRgn, aContentRgn; + const TabitemValue aControlValue(tools::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, + 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("..."); + 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_TABOFFSET_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.Right() - item.maRect.Left(); + } + 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 ) +{ + 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 ); + 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( OString() ); + } + + 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_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; + + 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 (bPaneWithHeader) + { + aRect.SetTop(0); + if (mpTabCtrlData->maItemList.size()) + { + tools::Long nRight = 0; + for (const auto &item : mpTabCtrlData->maItemList) + if (item.m_bVisible) + nRight = item.maRect.Right(); + assert(nRight); + aHeaderRect.SetRight(nRight); + } + } + 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()); + } + } + + if (mbShowTabs && !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; + + // 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 + mpTabCtrlData->maItemList.size() - 1; + idx = mpTabCtrlData->maItemList.size() - 1; + } + else + { + pLastTab = mpTabCtrlData->maItemList.data(); + pFirstTab = pLastTab + mpTabCtrlData->maItemList.size() - 1; + idx = 0; + } + + while (idx < mpTabCtrlData->maItemList.size()) + { + 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) +{ + ImplFreeLayoutData(); + + 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(), OString()); + } + + 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() == MouseNotifyEvent::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() == 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.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(); + + 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 ) + 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(); + + ImplFreeLayoutData(); + + 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 OString& 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; + + 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) ) + 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(); + 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 ); + 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 OutputDevice::GetNonMnemonicString(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 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(); + } +} + +tools::Rectangle TabControl::GetCharacterBounds( sal_uInt16 nPageId, tools::Long nIndex ) const +{ + tools::Rectangle aRet; + + if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() ) + FillLayoutData(); + + if( HasLayoutData() ) + { + std::unordered_map< int, int >::const_iterator it = mpTabCtrlData->maLayoutPageIdToLine.find( static_cast<int>(nPageId) ); + if( it != mpTabCtrlData->maLayoutPageIdToLine.end() ) + { + Pair aPair = mxLayoutData->GetLineStartEnd( it->second ); + if( (aPair.B() - aPair.A()) >= nIndex ) + aRet = mxLayoutData->GetCharacterBounds( aPair.A() + nIndex ); + } + } + + return aRet; +} + +tools::Long TabControl::GetIndexForPoint( const Point& rPoint, sal_uInt16& rPageId ) const +{ + tools::Long nRet = -1; + + if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() ) + FillLayoutData(); + + if( HasLayoutData() ) + { + int nIndex = mxLayoutData->GetIndexForPoint( rPoint ); + if( nIndex != -1 ) + { + // what line (->pageid) is this index in ? + int nLines = mxLayoutData->GetLineCount(); + int nLine = -1; + while( ++nLine < nLines ) + { + Pair aPair = mxLayoutData->GetLineStartEnd( nLine ); + if( aPair.A() <= nIndex && aPair.B() >= nIndex ) + { + nRet = nIndex - aPair.A(); + rPageId = static_cast<sal_uInt16>(mpTabCtrlData->maLayoutLineToPageId[ nLine ]); + break; + } + } + } + } + + return nRet; +} + +void TabControl::FillLayoutData() const +{ + mpTabCtrlData->maLayoutLineToPageId.clear(); + mpTabCtrlData->maLayoutPageIdToLine.clear(); + const_cast<TabControl*>(this)->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 OString &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 (int i = 0; i < GetChildCount(); i++) + { + vcl::Window* pChild = GetChild(i); + + 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; + + for (int nChild = 0; nChild < GetPageCount(); ++nChild) + { + sal_uInt16 nPageId = TabControl::GetPageId(nChild); + TabPage* pPage = GetTabPage(nPageId); + + if (pPage) + { + SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any)); + + 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; + + // 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, bool& bFound) +{ + if (rItem.m_bVisible && rItem.m_bEnabled) + bFound = true; + return bFound; +} + +void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext ) +{ + const sal_uInt16 nOldPos = GetPagePos(GetCurPageId()); + bool bFound = false; + sal_Int32 nCurPos = nOldPos; + + if (bNext) + { + for (nCurPos++; nCurPos < GetPageCount(); nCurPos++) + if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound)) + break; + } + else + { + for (nCurPos--; nCurPos >= 0; nCurPos--) + if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound)) + break; + } + + if (!bFound) + nCurPos = nOldPos; + SelectTabPage( TabControl::GetPageId( nCurPos ) ); +} + +sal_uInt16 NotebookbarTabControlBase::GetHeaderHeight() +{ + return m_nHeaderHeight; +} + +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 + lcl_AdjustSingleLineTabs(nMaxWidth, 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: */ |