1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <vcl/notebookbar/notebookbar.hxx>
24 #include <vcl/svapp.hxx>
25 #include <vcl/help.hxx>
26 #include <vcl/event.hxx>
27 #include <vcl/menu.hxx>
28 #include <vcl/toolkit/button.hxx>
29 #include <vcl/tabpage.hxx>
30 #include <vcl/tabctrl.hxx>
31 #include <vcl/toolbox.hxx>
32 #include <vcl/layout.hxx>
33 #include <vcl/mnemonic.hxx>
34 #include <vcl/toolkit/lstbox.hxx>
35 #include <vcl/settings.hxx>
36 #include <vcl/uitest/uiobject.hxx>
37 #include <bitmaps.hlst>
38 #include <tools/json_writer.hxx>
44 #include <unordered_map>
48 /// Space to the left and right of the tabitem
49 #define TAB_ITEM_OFFSET_X 10
50 /// Space to the top and bottom of the tabitem
51 #define TAB_ITEM_OFFSET_Y 3
52 #define TAB_EXTRASPACE_X 6
53 #define TAB_BORDER_LEFT 1
54 #define TAB_BORDER_TOP 1
55 #define TAB_BORDER_RIGHT 2
56 #define TAB_BORDER_BOTTOM 2
58 class ImplTabItem final
63 VclPtr
<TabPage
> mpTabPage
;
65 OUString maFormatText
;
67 OUString maAccessibleName
;
68 OUString maAccessibleDescription
;
70 tools::Rectangle maRect
;
73 bool m_bEnabled
; ///< the tab / page is selectable
74 bool m_bVisible
; ///< the tab / page can be visible
77 ImplTabItem(sal_uInt16 nId
);
79 sal_uInt16
id() const { return m_nId
; }
82 ImplTabItem::ImplTabItem(sal_uInt16 nId
)
85 , mbFullVisible(false)
91 struct ImplTabCtrlData
93 std::vector
< ImplTabItem
> maItemList
;
94 VclPtr
<ListBox
> mpListBox
;
97 // for the Tab positions
98 #define TAB_PAGERECT 0xFFFF
100 void TabControl::ImplInit( vcl::Window
* pParent
, WinBits nStyle
)
102 mbLayoutDirty
= true;
104 if ( !(nStyle
& WB_NOTABSTOP
) )
105 nStyle
|= WB_TABSTOP
;
106 if ( !(nStyle
& WB_NOGROUP
) )
108 if ( !(nStyle
& WB_NODIALOGCONTROL
) )
109 nStyle
|= WB_DIALOGCONTROL
;
111 Control::ImplInit( pParent
, nStyle
, nullptr );
119 mbRestoreHelpId
= false;
120 mbSmallInvalidate
= false;
121 mpTabCtrlData
.reset(new ImplTabCtrlData
);
122 mpTabCtrlData
->mpListBox
= nullptr;
124 ImplInitSettings( true );
126 if( nStyle
& WB_DROPDOWN
)
128 mpTabCtrlData
->mpListBox
= VclPtr
<ListBox
>::Create( this, WB_DROPDOWN
);
129 mpTabCtrlData
->mpListBox
->SetPosSizePixel( Point( 0, 0 ), Size( 200, 20 ) );
130 mpTabCtrlData
->mpListBox
->SetSelectHdl( LINK( this, TabControl
, ImplListBoxSelectHdl
) );
131 mpTabCtrlData
->mpListBox
->Show();
134 // if the tabcontrol is drawn (ie filled) by a native widget, make sure all controls will have transparent background
135 // otherwise they will paint with a wrong background
136 if( IsNativeControlSupported(ControlType::TabPane
, ControlPart::Entire
) )
137 EnableChildTransparentMode();
139 if (pParent
&& pParent
->IsDialog())
140 pParent
->AddChildEventListener( LINK( this, TabControl
, ImplWindowEventListener
) );
143 const vcl::Font
& TabControl::GetCanonicalFont( const StyleSettings
& _rStyle
) const
145 return _rStyle
.GetTabFont();
148 const Color
& TabControl::GetCanonicalTextColor( const StyleSettings
& _rStyle
) const
150 return _rStyle
.GetTabTextColor();
153 void TabControl::ImplInitSettings( bool bBackground
)
155 Control::ImplInitSettings();
160 vcl::Window
* pParent
= GetParent();
161 if ( !IsControlBackground() &&
162 (pParent
->IsChildTransparentModeEnabled()
163 || IsNativeControlSupported(ControlType::TabPane
, ControlPart::Entire
)
164 || IsNativeControlSupported(ControlType::TabItem
, ControlPart::Entire
) ) )
167 // set transparent mode for NWF tabcontrols to have
168 // the background always cleared properly
169 EnableChildTransparentMode();
170 SetParentClipMode( ParentClipMode::NoClip
);
171 SetPaintTransparent( true );
173 ImplGetWindowImpl()->mbUseNativeFocus
= ImplGetSVData()->maNWFData
.mbNoFocusRects
;
177 EnableChildTransparentMode( false );
179 SetPaintTransparent( false );
181 if ( IsControlBackground() )
182 SetBackground( GetControlBackground() );
184 SetBackground( pParent
->GetBackground() );
188 TabControl::TabControl( vcl::Window
* pParent
, WinBits nStyle
) :
189 Control( WindowType::TABCONTROL
)
191 ImplInit( pParent
, nStyle
);
192 SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER
) ? "true" : "false") );
195 TabControl::~TabControl()
200 void TabControl::dispose()
202 Window
*pParent
= GetParent();
203 if (pParent
&& pParent
->IsDialog())
204 GetParent()->RemoveChildEventListener( LINK( this, TabControl
, ImplWindowEventListener
) );
206 // delete TabCtrl data
208 mpTabCtrlData
->mpListBox
.disposeAndClear();
209 mpTabCtrlData
.reset();
213 ImplTabItem
* TabControl::ImplGetItem( sal_uInt16 nId
) const
215 for (auto & item
: mpTabCtrlData
->maItemList
)
217 if (item
.id() == nId
)
224 Size
TabControl::ImplGetItemSize( ImplTabItem
* pItem
, tools::Long nMaxWidth
)
226 pItem
->maFormatText
= pItem
->maText
;
227 Size
aSize( GetOutDev()->GetCtrlTextWidth( pItem
->maFormatText
), GetTextHeight() );
228 Size
aImageSize( 0, 0 );
229 if( !!pItem
->maTabImage
)
231 aImageSize
= pItem
->maTabImage
.GetSizePixel();
232 if( !pItem
->maFormatText
.isEmpty() )
233 aImageSize
.AdjustWidth(GetTextHeight()/4 );
235 aSize
.AdjustWidth(aImageSize
.Width() );
236 if( aImageSize
.Height() > aSize
.Height() )
237 aSize
.setHeight( aImageSize
.Height() );
239 aSize
.AdjustWidth(TAB_ITEM_OFFSET_X
*2 );
240 aSize
.AdjustHeight(TAB_ITEM_OFFSET_Y
*2 );
242 tools::Rectangle
aCtrlRegion( Point( 0, 0 ), aSize
);
243 tools::Rectangle aBoundingRgn
, aContentRgn
;
244 const TabitemValue
aControlValue(tools::Rectangle(TAB_ITEM_OFFSET_X
, TAB_ITEM_OFFSET_Y
,
245 aSize
.Width() - TAB_ITEM_OFFSET_X
* 2,
246 aSize
.Height() - TAB_ITEM_OFFSET_Y
* 2),
247 TabBarPosition::Top
);
248 if(GetNativeControlRegion( ControlType::TabItem
, ControlPart::Entire
, aCtrlRegion
,
249 ControlState::ENABLED
, aControlValue
,
250 aBoundingRgn
, aContentRgn
) )
252 return aContentRgn
.GetSize();
255 // For languages with short names (e.g. Chinese), because the space is
256 // normally only one pixel per char
257 if ( pItem
->maFormatText
.getLength() < TAB_EXTRASPACE_X
)
258 aSize
.AdjustWidth(TAB_EXTRASPACE_X
-pItem
->maFormatText
.getLength() );
260 // shorten Text if needed
261 if ( aSize
.Width()+4 >= nMaxWidth
)
263 OUString
aAppendStr(u
"..."_ustr
);
264 pItem
->maFormatText
+= aAppendStr
;
267 if (pItem
->maFormatText
.getLength() > aAppendStr
.getLength())
268 pItem
->maFormatText
= pItem
->maFormatText
.replaceAt( pItem
->maFormatText
.getLength()-aAppendStr
.getLength()-1, 1, u
"" );
269 aSize
.setWidth( GetOutDev()->GetCtrlTextWidth( pItem
->maFormatText
) );
270 aSize
.AdjustWidth(aImageSize
.Width() );
271 aSize
.AdjustWidth(TAB_ITEM_OFFSET_X
*2 );
273 while ( (aSize
.Width()+4 >= nMaxWidth
) && (pItem
->maFormatText
.getLength() > aAppendStr
.getLength()) );
274 if ( aSize
.Width()+4 >= nMaxWidth
)
276 pItem
->maFormatText
= ".";
281 if( pItem
->maFormatText
.isEmpty() )
283 if( aSize
.Height() < aImageSize
.Height()+4 ) //leave space for focus rect
284 aSize
.setHeight( aImageSize
.Height()+4 );
290 // Feel free to move this to some more general place for reuse
291 // http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
292 // Mostly based on Alexey Frunze's nifty example at
293 // http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
294 namespace MinimumRaggednessWrap
296 static std::deque
<size_t> GetEndOfLineIndexes(const std::vector
<sal_Int32
>& rWidthsOf
, sal_Int32 nLineWidth
)
300 size_t nWidthsCount
= rWidthsOf
.size();
301 std::vector
<sal_Int32
> aCosts(nWidthsCount
* nWidthsCount
);
303 // cost function c(i, j) that computes the cost of a line consisting of
304 // the words Word[i] to Word[j]
305 for (size_t i
= 0; i
< nWidthsCount
; ++i
)
307 for (size_t j
= 0; j
< nWidthsCount
; ++j
)
311 sal_Int32 c
= nLineWidth
- (j
- i
);
312 for (size_t k
= i
; k
<= j
; ++k
)
314 c
= (c
>= 0) ? c
* c
: SAL_MAX_INT32
;
315 aCosts
[j
* nWidthsCount
+ i
] = c
;
319 aCosts
[j
* nWidthsCount
+ i
] = SAL_MAX_INT32
;
324 std::vector
<sal_Int32
> aFunction(nWidthsCount
);
325 std::vector
<sal_Int32
> aWrapPoints(nWidthsCount
);
327 // f(j) in aFunction[], collect wrap points in aWrapPoints[]
328 for (size_t j
= 0; j
< nWidthsCount
; ++j
)
330 aFunction
[j
] = aCosts
[j
* nWidthsCount
];
331 if (aFunction
[j
] == SAL_MAX_INT32
)
333 for (size_t k
= 0; k
< j
; ++k
)
336 if (aFunction
[k
] == SAL_MAX_INT32
|| aCosts
[j
* nWidthsCount
+ k
+ 1] == SAL_MAX_INT32
)
339 s
= aFunction
[k
] + aCosts
[j
* nWidthsCount
+ k
+ 1];
340 if (aFunction
[j
] > s
)
343 aWrapPoints
[j
] = k
+ 1;
349 std::deque
<size_t> aSolution
;
352 if (aFunction
[nWidthsCount
- 1] == SAL_MAX_INT32
)
356 size_t j
= nWidthsCount
- 1;
359 aSolution
.push_front(j
);
362 j
= aWrapPoints
[j
] - 1;
369 static void lcl_AdjustSingleLineTabs(tools::Long nMaxWidth
, ImplTabCtrlData
*pTabCtrlData
)
371 if (!ImplGetSVData()->maNWFData
.mbCenteredTabs
)
374 int nRightSpace
= nMaxWidth
; // space left on the right by the tabs
375 for (auto const& item
: pTabCtrlData
->maItemList
)
377 if (!item
.m_bVisible
)
379 nRightSpace
-= item
.maRect
.GetWidth();
383 for (auto& item
: pTabCtrlData
->maItemList
)
385 if (!item
.m_bVisible
)
387 item
.maRect
.AdjustLeft(nRightSpace
);
388 item
.maRect
.AdjustRight(nRightSpace
);
392 bool TabControl::ImplPlaceTabs( tools::Long nWidth
)
396 if ( mpTabCtrlData
->maItemList
.empty() )
399 tools::Long nMaxWidth
= nWidth
;
401 const tools::Long nOffsetX
= 2;
402 const tools::Long nOffsetY
= 2;
404 //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
405 //of ugly bare tabs on lines of their own
408 std::vector
<sal_Int32
> aWidths
;
409 for (auto & item
: mpTabCtrlData
->maItemList
)
411 if (!item
.m_bVisible
)
413 aWidths
.push_back(ImplGetItemSize(&item
, nMaxWidth
).Width());
416 //aBreakIndexes will contain the indexes of the last tab on each row
417 std::deque
<size_t> aBreakIndexes(MinimumRaggednessWrap::GetEndOfLineIndexes(aWidths
, nMaxWidth
- nOffsetX
- 2));
419 tools::Long nX
= nOffsetX
;
420 tools::Long nY
= nOffsetY
;
422 sal_uInt16 nLines
= 0;
423 sal_uInt16 nCurLine
= 0;
425 tools::Long nLineWidthAry
[100];
426 sal_uInt16 nLinePosAry
[101];
427 nLineWidthAry
[0] = 0;
432 for (auto & item
: mpTabCtrlData
->maItemList
)
434 if (!item
.m_bVisible
)
437 Size aSize
= ImplGetItemSize( &item
, nMaxWidth
);
439 bool bNewLine
= false;
440 if (!aBreakIndexes
.empty() && nIndex
> aBreakIndexes
.front())
442 aBreakIndexes
.pop_front();
446 if ( bNewLine
&& (nWidth
> 2+nOffsetX
) )
452 nY
+= aSize
.Height();
454 nLineWidthAry
[nLines
] = 0;
455 nLinePosAry
[nLines
] = nIndex
;
458 tools::Rectangle
aNewRect( Point( nX
, nY
), aSize
);
459 if ( mbSmallInvalidate
&& (item
.maRect
!= aNewRect
) )
460 mbSmallInvalidate
= false;
461 item
.maRect
= aNewRect
;
462 item
.mnLine
= nLines
;
463 item
.mbFullVisible
= true;
465 nLineWidthAry
[nLines
] += aSize
.Width();
468 if (item
.id() == mnCurPageId
)
474 if (nLines
) // two or more lines
476 tools::Long nLineHeightAry
[100];
478 for (const auto& item
: mpTabCtrlData
->maItemList
)
480 if (!item
.m_bVisible
)
482 nIH
= item
.maRect
.Bottom() - 1;
486 for ( sal_uInt16 i
= 0; i
< nLines
+1; i
++ )
489 nLineHeightAry
[i
] = nIH
*(nLines
-(nCurLine
-i
));
491 nLineHeightAry
[i
] = nIH
*(i
-nCurLine
-1);
494 nLinePosAry
[nLines
+1] = static_cast<sal_uInt16
>(mpTabCtrlData
->maItemList
.size());
497 tools::Long nModDX
= 0;
498 tools::Long nIDX
= 0;
503 for (auto & item
: mpTabCtrlData
->maItemList
)
505 if (!item
.m_bVisible
)
508 if ( i
== nLinePosAry
[n
] )
514 if( nLinePosAry
[n
+1]-i
> 0 )
516 nDX
= ( nWidth
- nOffsetX
- nLineWidthAry
[n
] ) / ( nLinePosAry
[n
+1] - i
);
517 nModDX
= ( nWidth
- nOffsetX
- nLineWidthAry
[n
] ) % ( nLinePosAry
[n
+1] - i
);
521 // FIXME: this is a case of tabctrl way too small
528 item
.maRect
.AdjustLeft(nIDX
);
529 item
.maRect
.AdjustRight(nIDX
+ nDX
);
530 item
.maRect
.SetTop( nLineHeightAry
[n
-1] );
531 item
.maRect
.SetBottom(nLineHeightAry
[n
-1] + nIH
- 1);
537 item
.maRect
.AdjustRight( 1 );
544 else // only one line
545 lcl_AdjustSingleLineTabs(nMaxWidth
, mpTabCtrlData
.get());
550 tools::Rectangle
TabControl::ImplGetTabRect( sal_uInt16 nItemPos
, tools::Long nWidth
, tools::Long nHeight
)
552 Size aWinSize
= Control::GetOutputSizePixel();
554 nWidth
= aWinSize
.Width();
556 nHeight
= aWinSize
.Height();
558 if ( mpTabCtrlData
->maItemList
.empty() )
560 tools::Long nW
= nWidth
-TAB_OFFSET
*2;
561 tools::Long nH
= nHeight
-TAB_OFFSET
*2;
562 return (nW
> 0 && nH
> 0)
563 ? tools::Rectangle(Point(TAB_OFFSET
, TAB_OFFSET
), Size(nW
, nH
))
564 : tools::Rectangle();
567 if ( nItemPos
== TAB_PAGERECT
)
571 nLastPos
= GetPagePos( mnCurPageId
);
575 tools::Rectangle aRect
= ImplGetTabRect( nLastPos
, nWidth
, nHeight
);
579 // with show-tabs of true (the usual) the page rect is from under the
580 // visible tab to the bottom of the TabControl, otherwise it extends
581 // from the top of the TabControl
582 tools::Long nTabBottom
= mbShowTabs
? aRect
.Bottom() : 0;
584 tools::Long nW
= nWidth
-TAB_OFFSET
*2;
585 tools::Long nH
= nHeight
- nTabBottom
- TAB_OFFSET
*2;
586 return (nW
> 0 && nH
> 0)
587 ? tools::Rectangle( Point( TAB_OFFSET
, nTabBottom
+ TAB_OFFSET
), Size( nW
, nH
) )
588 : tools::Rectangle();
591 ImplTabItem
* const pItem
= (nItemPos
< mpTabCtrlData
->maItemList
.size())
592 ? &mpTabCtrlData
->maItemList
[nItemPos
] : nullptr;
593 return ImplGetTabRect(pItem
, nWidth
, nHeight
);
596 tools::Rectangle
TabControl::ImplGetTabRect(const ImplTabItem
* pItem
, tools::Long nWidth
, tools::Long nHeight
)
598 if ((nWidth
<= 1) || (nHeight
<= 0) || !pItem
|| !pItem
->m_bVisible
)
599 return tools::Rectangle();
603 if ( mbFormat
|| (mnLastWidth
!= nWidth
) || (mnLastHeight
!= nHeight
) )
605 vcl::Font
aFont( GetFont() );
606 aFont
.SetTransparent( true );
609 bool bRet
= ImplPlaceTabs( nWidth
);
611 return tools::Rectangle();
613 mnLastWidth
= nWidth
;
614 mnLastHeight
= nHeight
;
618 return pItem
->maRect
;
621 void TabControl::ImplChangeTabPage( sal_uInt16 nId
, sal_uInt16 nOldId
)
623 ImplTabItem
* pOldItem
= ImplGetItem( nOldId
);
624 ImplTabItem
* pItem
= ImplGetItem( nId
);
625 TabPage
* pOldPage
= pOldItem
? pOldItem
->mpTabPage
.get() : nullptr;
626 TabPage
* pPage
= pItem
? pItem
->mpTabPage
.get() : nullptr;
627 vcl::Window
* pCtrlParent
= GetParent();
629 if ( IsReallyVisible() && IsUpdateMode() )
631 sal_uInt16 nPos
= GetPagePos( nId
);
632 tools::Rectangle aRect
= ImplGetTabRect( nPos
);
634 if ( !pOldItem
|| !pItem
|| (pOldItem
->mnLine
!= pItem
->mnLine
) )
638 aRect
.SetRight( Control::GetOutputSizePixel().Width() );
642 aRect
.AdjustLeft( -3 );
643 aRect
.AdjustTop( -2 );
644 aRect
.AdjustRight(3 );
646 nPos
= GetPagePos( nOldId
);
647 aRect
= ImplGetTabRect( nPos
);
648 aRect
.AdjustLeft( -3 );
649 aRect
.AdjustTop( -2 );
650 aRect
.AdjustRight(3 );
655 if ( pOldPage
== pPage
)
658 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
);
662 if ( mbRestoreHelpId
)
663 pCtrlParent
->SetHelpId({});
668 if ( GetStyle() & WB_NOBORDER
)
670 tools::Rectangle
aRectNoTab(Point(0, 0), GetSizePixel());
671 pPage
->SetPosSizePixel( aRectNoTab
.TopLeft(), aRectNoTab
.GetSize() );
674 pPage
->SetPosSizePixel( aRect
.TopLeft(), aRect
.GetSize() );
676 // activate page here so the controls can be switched
677 // also set the help id of the parent window to that of the tab page
678 if ( GetHelpId().isEmpty() )
680 mbRestoreHelpId
= true;
681 pCtrlParent
->SetHelpId( pPage
->GetHelpId() );
686 if ( pOldPage
&& pOldPage
->HasChildPathFocus() )
688 vcl::Window
* pFirstChild
= pPage
->ImplGetDlgWindow( 0, GetDlgWindowType::First
);
690 pFirstChild
->ImplControlFocus( GetFocusFlags::Init
);
699 // Invalidate the same region that will be send to NWF
700 // to always allow for bitmap caching
701 // see Window::DrawNativeControl()
702 if( IsNativeControlSupported( ControlType::TabPane
, ControlPart::Entire
) )
704 aRect
.AdjustLeft( -(TAB_OFFSET
) );
705 aRect
.AdjustTop( -(TAB_OFFSET
) );
706 aRect
.AdjustRight(TAB_OFFSET
);
707 aRect
.AdjustBottom(TAB_OFFSET
);
713 bool TabControl::ImplPosCurTabPage()
715 // resize/position current TabPage
716 ImplTabItem
* pItem
= ImplGetItem( GetCurPageId() );
717 if ( pItem
&& pItem
->mpTabPage
)
719 if ( GetStyle() & WB_NOBORDER
)
721 tools::Rectangle
aRectNoTab(Point(0, 0), GetSizePixel());
722 pItem
->mpTabPage
->SetPosSizePixel( aRectNoTab
.TopLeft(), aRectNoTab
.GetSize() );
725 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
);
726 pItem
->mpTabPage
->SetPosSizePixel( aRect
.TopLeft(), aRect
.GetSize() );
733 void TabControl::ImplActivateTabPage( bool bNext
)
735 sal_uInt16 nCurPos
= GetPagePos( GetCurPageId() );
738 nCurPos
= (nCurPos
+ 1) % GetPageCount();
742 nCurPos
= GetPageCount()-1;
747 SelectTabPage( GetPageId( nCurPos
) );
750 void TabControl::ImplShowFocus()
752 if ( !GetPageCount() || mpTabCtrlData
->mpListBox
)
755 sal_uInt16 nCurPos
= GetPagePos( mnCurPageId
);
756 tools::Rectangle aRect
= ImplGetTabRect( nCurPos
);
757 const ImplTabItem
& rItem
= mpTabCtrlData
->maItemList
[ nCurPos
];
758 Size aTabSize
= aRect
.GetSize();
759 Size
aImageSize( 0, 0 );
760 tools::Long nTextHeight
= GetTextHeight();
761 tools::Long nTextWidth
= GetOutDev()->GetCtrlTextWidth( rItem
.maFormatText
);
764 if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::Mono
) )
769 if( !! rItem
.maTabImage
)
771 aImageSize
= rItem
.maTabImage
.GetSizePixel();
772 if( !rItem
.maFormatText
.isEmpty() )
773 aImageSize
.AdjustWidth(GetTextHeight()/4 );
776 if( !rItem
.maFormatText
.isEmpty() )
778 // show focus around text
779 aRect
.SetLeft( aRect
.Left()+aImageSize
.Width()+((aTabSize
.Width()-nTextWidth
-aImageSize
.Width())/2)-nOff
-1-1 );
780 aRect
.SetTop( aRect
.Top()+((aTabSize
.Height()-nTextHeight
)/2)-1-1 );
781 aRect
.SetRight( aRect
.Left()+nTextWidth
+2 );
782 aRect
.SetBottom( aRect
.Top()+nTextHeight
+2 );
786 // show focus around image
787 tools::Long nXPos
= aRect
.Left()+((aTabSize
.Width()-nTextWidth
-aImageSize
.Width())/2)-nOff
-1;
788 tools::Long nYPos
= aRect
.Top();
789 if( aImageSize
.Height() < aRect
.GetHeight() )
790 nYPos
+= (aRect
.GetHeight() - aImageSize
.Height())/2;
792 aRect
.SetLeft( nXPos
- 2 );
793 aRect
.SetTop( nYPos
- 2 );
794 aRect
.SetRight( aRect
.Left() + aImageSize
.Width() + 4 );
795 aRect
.SetBottom( aRect
.Top() + aImageSize
.Height() + 4 );
800 void TabControl::ImplDrawItem(vcl::RenderContext
& rRenderContext
, ImplTabItem
const * pItem
, const tools::Rectangle
& rCurRect
,
801 bool bFirstInGroup
, bool bLastInGroup
)
803 if (!pItem
->m_bVisible
|| pItem
->maRect
.IsEmpty())
806 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
807 tools::Rectangle aRect
= pItem
->maRect
;
808 tools::Long nLeftBottom
= aRect
.Bottom();
809 tools::Long nRightBottom
= aRect
.Bottom();
810 bool bLeftBorder
= true;
811 bool bRightBorder
= true;
813 bool bNativeOK
= false;
815 sal_uInt16 nOff2
= 0;
816 sal_uInt16 nOff3
= 0;
818 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
823 // if this is the active Page, we have to draw a little more
824 if (pItem
->id() == mnCurPageId
)
827 if (!ImplGetSVData()->maNWFData
.mbNoActiveTabTextRaise
)
832 Point aLeftTestPos
= aRect
.BottomLeft();
833 Point aRightTestPos
= aRect
.BottomRight();
834 if (aLeftTestPos
.Y() == rCurRect
.Bottom())
836 aLeftTestPos
.AdjustX( -2 );
837 if (rCurRect
.Contains(aLeftTestPos
))
839 aRightTestPos
.AdjustX(2 );
840 if (rCurRect
.Contains(aRightTestPos
))
841 bRightBorder
= false;
845 if (rCurRect
.Contains(aLeftTestPos
))
847 if (rCurRect
.Contains(aRightTestPos
))
852 ControlState nState
= ControlState::NONE
;
854 if (pItem
->id() == mnCurPageId
)
856 nState
|= ControlState::SELECTED
;
857 // only the selected item can be focused
859 nState
|= ControlState::FOCUSED
;
862 nState
|= ControlState::ENABLED
;
863 if (IsMouseOver() && pItem
->maRect
.Contains(GetPointerPosPixel()))
865 nState
|= ControlState::ROLLOVER
;
866 for (auto const& item
: mpTabCtrlData
->maItemList
)
867 if ((&item
!= pItem
) && item
.m_bVisible
&& item
.maRect
.Contains(GetPointerPosPixel()))
869 nState
&= ~ControlState::ROLLOVER
; // avoid multiple highlighted tabs
872 assert(nState
& ControlState::ROLLOVER
);
875 bNativeOK
= rRenderContext
.IsNativeControlSupported(ControlType::TabItem
, ControlPart::Entire
);
878 TabitemValue
tiValue(tools::Rectangle(pItem
->maRect
.Left() + TAB_ITEM_OFFSET_X
,
879 pItem
->maRect
.Top() + TAB_ITEM_OFFSET_Y
,
880 pItem
->maRect
.Right() - TAB_ITEM_OFFSET_X
,
881 pItem
->maRect
.Bottom() - TAB_ITEM_OFFSET_Y
),
882 TabBarPosition::Top
);
883 if (pItem
->maRect
.Left() < 5)
884 tiValue
.mnAlignment
|= TabitemFlags::LeftAligned
;
885 if (pItem
->maRect
.Right() > mnLastWidth
- 5)
886 tiValue
.mnAlignment
|= TabitemFlags::RightAligned
;
888 tiValue
.mnAlignment
|= TabitemFlags::FirstInGroup
;
890 tiValue
.mnAlignment
|= TabitemFlags::LastInGroup
;
892 tools::Rectangle
aCtrlRegion( pItem
->maRect
);
893 aCtrlRegion
.AdjustBottom(TabPaneValue::m_nOverlap
);
894 bNativeOK
= rRenderContext
.DrawNativeControl(ControlType::TabItem
, ControlPart::Entire
,
895 aCtrlRegion
, nState
, tiValue
, OUString() );
900 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
902 rRenderContext
.SetLineColor(rStyleSettings
.GetLightColor());
903 rRenderContext
.DrawPixel(Point(aRect
.Left() + 1 - nOff2
, aRect
.Top() + 1 - nOff2
)); // diagonally indented top-left pixel
906 rRenderContext
.DrawLine(Point(aRect
.Left() - nOff2
, aRect
.Top() + 2 - nOff2
),
907 Point(aRect
.Left() - nOff2
, nLeftBottom
- 1));
909 rRenderContext
.DrawLine(Point(aRect
.Left() + 2 - nOff2
, aRect
.Top() - nOff2
), // top line starting 2px from left border
910 Point(aRect
.Right() + nOff2
- 3, aRect
.Top() - nOff2
)); // ending 3px from right border
914 rRenderContext
.SetLineColor(rStyleSettings
.GetShadowColor());
915 rRenderContext
.DrawLine(Point(aRect
.Right() + nOff2
- 2, aRect
.Top() + 1 - nOff2
),
916 Point(aRect
.Right() + nOff2
- 2, nRightBottom
- 1));
918 rRenderContext
.SetLineColor(rStyleSettings
.GetDarkShadowColor());
919 rRenderContext
.DrawLine(Point(aRect
.Right() + nOff2
- 1, aRect
.Top() + 3 - nOff2
),
920 Point(aRect
.Right() + nOff2
- 1, nRightBottom
- 1));
925 rRenderContext
.SetLineColor(COL_BLACK
);
926 rRenderContext
.DrawPixel(Point(aRect
.Left() + 1 - nOff2
, aRect
.Top() + 1 - nOff2
));
927 rRenderContext
.DrawPixel(Point(aRect
.Right() + nOff2
- 2, aRect
.Top() + 1 - nOff2
));
930 rRenderContext
.DrawLine(Point(aRect
.Left() - nOff2
, aRect
.Top() + 2 - nOff2
),
931 Point(aRect
.Left() - nOff2
, nLeftBottom
- 1));
933 rRenderContext
.DrawLine(Point(aRect
.Left() + 2 - nOff2
, aRect
.Top() - nOff2
),
934 Point(aRect
.Right() - 3, aRect
.Top() - nOff2
));
937 rRenderContext
.DrawLine(Point(aRect
.Right() + nOff2
- 1, aRect
.Top() + 2 - nOff2
),
938 Point(aRect
.Right() + nOff2
- 1, nRightBottom
- 1));
943 // set font accordingly, current item is painted bold
944 // we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
945 vcl::Font
aFont(rRenderContext
.GetFont());
946 aFont
.SetTransparent(true);
947 rRenderContext
.SetFont(aFont
);
949 Size aTabSize
= aRect
.GetSize();
950 Size
aImageSize(0, 0);
951 tools::Long nTextHeight
= rRenderContext
.GetTextHeight();
952 tools::Long nTextWidth
= rRenderContext
.GetCtrlTextWidth(pItem
->maFormatText
);
953 if (!!pItem
->maTabImage
)
955 aImageSize
= pItem
->maTabImage
.GetSizePixel();
956 if (!pItem
->maFormatText
.isEmpty())
957 aImageSize
.AdjustWidth(GetTextHeight() / 4 );
959 tools::Long nXPos
= aRect
.Left() + ((aTabSize
.Width() - nTextWidth
- aImageSize
.Width()) / 2) - nOff
- nOff3
;
960 tools::Long nYPos
= aRect
.Top() + ((aTabSize
.Height() - nTextHeight
) / 2) - nOff3
;
961 if (!pItem
->maFormatText
.isEmpty())
963 DrawTextFlags nStyle
= DrawTextFlags::Mnemonic
;
964 if (!pItem
->m_bEnabled
)
965 nStyle
|= DrawTextFlags::Disable
;
967 Color
aColor(rStyleSettings
.GetTabTextColor());
968 if (nState
& ControlState::SELECTED
)
969 aColor
= rStyleSettings
.GetTabHighlightTextColor();
970 else if (nState
& ControlState::ROLLOVER
)
971 aColor
= rStyleSettings
.GetTabRolloverTextColor();
973 Color
aOldColor(rRenderContext
.GetTextColor());
974 rRenderContext
.SetTextColor(aColor
);
976 const tools::Rectangle
aOutRect(nXPos
+ aImageSize
.Width(), nYPos
,
977 nXPos
+ aImageSize
.Width() + nTextWidth
, nYPos
+ nTextHeight
);
978 DrawControlText(rRenderContext
, aOutRect
, pItem
->maFormatText
, nStyle
,
981 rRenderContext
.SetTextColor(aOldColor
);
984 if (!!pItem
->maTabImage
)
986 Point
aImgTL( nXPos
, aRect
.Top() );
987 if (aImageSize
.Height() < aRect
.GetHeight())
988 aImgTL
.AdjustY((aRect
.GetHeight() - aImageSize
.Height()) / 2 );
989 rRenderContext
.DrawImage(aImgTL
, pItem
->maTabImage
, pItem
->m_bEnabled
? DrawImageFlags::NONE
: DrawImageFlags::Disable
);
993 bool TabControl::ImplHandleKeyEvent( const KeyEvent
& rKeyEvent
)
997 if ( GetPageCount() > 1 )
999 vcl::KeyCode aKeyCode
= rKeyEvent
.GetKeyCode();
1000 sal_uInt16 nKeyCode
= aKeyCode
.GetCode();
1002 if ( aKeyCode
.IsMod1() )
1004 if ( aKeyCode
.IsShift() || (nKeyCode
== KEY_PAGEUP
) )
1006 if ( (nKeyCode
== KEY_TAB
) || (nKeyCode
== KEY_PAGEUP
) )
1008 ImplActivateTabPage( false );
1014 if ( (nKeyCode
== KEY_TAB
) || (nKeyCode
== KEY_PAGEDOWN
) )
1016 ImplActivateTabPage( true );
1026 IMPL_LINK_NOARG(TabControl
, ImplListBoxSelectHdl
, ListBox
&, void)
1028 SelectTabPage( GetPageId( mpTabCtrlData
->mpListBox
->GetSelectedEntryPos() ) );
1031 IMPL_LINK( TabControl
, ImplWindowEventListener
, VclWindowEvent
&, rEvent
, void )
1033 if ( rEvent
.GetId() == VclEventId::WindowKeyInput
)
1035 // Do not handle events from TabControl or its children, which is done in Notify(), where the events can be consumed.
1036 if ( !IsWindowOrChild( rEvent
.GetWindow() ) )
1038 KeyEvent
* pKeyEvent
= static_cast< KeyEvent
* >(rEvent
.GetData());
1039 ImplHandleKeyEvent( *pKeyEvent
);
1044 void TabControl::MouseButtonDown( const MouseEvent
& rMEvt
)
1046 if (mpTabCtrlData
->mpListBox
|| !rMEvt
.IsLeft())
1049 ImplTabItem
*pItem
= ImplGetItem(rMEvt
.GetPosPixel());
1050 if (pItem
&& pItem
->m_bEnabled
)
1051 SelectTabPage(pItem
->id());
1054 void TabControl::KeyInput( const KeyEvent
& rKEvt
)
1056 if( mpTabCtrlData
->mpListBox
)
1057 mpTabCtrlData
->mpListBox
->KeyInput( rKEvt
);
1058 else if ( GetPageCount() > 1 )
1060 vcl::KeyCode aKeyCode
= rKEvt
.GetKeyCode();
1061 sal_uInt16 nKeyCode
= aKeyCode
.GetCode();
1063 if ( (nKeyCode
== KEY_LEFT
) || (nKeyCode
== KEY_RIGHT
) )
1065 bool bNext
= (nKeyCode
== KEY_RIGHT
);
1066 ImplActivateTabPage( bNext
);
1070 Control::KeyInput( rKEvt
);
1073 static bool lcl_canPaint(const vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& rDrawRect
,
1074 const tools::Rectangle
& rItemRect
)
1076 vcl::Region
aClipRgn(rRenderContext
.GetActiveClipRegion());
1077 aClipRgn
.Intersect(rItemRect
);
1078 if (!rDrawRect
.IsEmpty())
1079 aClipRgn
.Intersect(rDrawRect
);
1080 return !aClipRgn
.IsEmpty();
1083 void TabControl::Paint( vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& rRect
)
1085 if (GetStyle() & WB_NOBORDER
)
1088 Control::Paint(rRenderContext
, rRect
);
1092 // reformat if needed
1093 tools::Rectangle aRect
= ImplGetTabRect(TAB_PAGERECT
);
1095 // find current item
1096 ImplTabItem
* pCurItem
= nullptr;
1097 for (auto & item
: mpTabCtrlData
->maItemList
)
1099 if (item
.id() == mnCurPageId
)
1106 // Draw the TabPage border
1107 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
1108 tools::Rectangle aCurRect
;
1109 aRect
.AdjustLeft( -(TAB_OFFSET
) );
1110 aRect
.AdjustTop( -(TAB_OFFSET
) );
1111 aRect
.AdjustRight(TAB_OFFSET
);
1112 aRect
.AdjustBottom(TAB_OFFSET
);
1114 // if we have an invisible tabpage or no tabpage at all the tabpage rect should be
1115 // increased to avoid round corners that might be drawn by a theme
1116 // in this case we're only interested in the top border of the tabpage because the tabitems are used
1117 // standalone (eg impress)
1118 bool bNoTabPage
= false;
1119 TabPage
* pCurPage
= pCurItem
? pCurItem
->mpTabPage
.get() : nullptr;
1120 if (!pCurPage
|| !pCurPage
->IsVisible())
1123 aRect
.AdjustLeft( -10 );
1124 aRect
.AdjustRight(10 );
1127 if (rRenderContext
.IsNativeControlSupported(ControlType::TabPane
, ControlPart::Entire
))
1129 const bool bPaneWithHeader
= mbShowTabs
&& rRenderContext
.IsNativeControlSupported(ControlType::TabPane
, ControlPart::TabPaneWithHeader
);
1130 tools::Rectangle
aHeaderRect(aRect
.Left(), 0, aRect
.Right(), aRect
.Top());
1132 if (!mpTabCtrlData
->maItemList
.empty())
1134 tools::Long nLeft
= LONG_MAX
;
1135 tools::Long nRight
= 0;
1136 for (const auto &item
: mpTabCtrlData
->maItemList
)
1138 if (!item
.m_bVisible
)
1140 nRight
= std::max(nRight
, item
.maRect
.Right());
1141 nLeft
= std::min(nLeft
, item
.maRect
.Left());
1143 aHeaderRect
.SetLeft(nLeft
);
1144 aHeaderRect
.SetRight(nRight
);
1147 if (bPaneWithHeader
)
1150 const TabPaneValue
aTabPaneValue(aHeaderRect
, pCurItem
? pCurItem
->maRect
: tools::Rectangle());
1152 ControlState nState
= ControlState::ENABLED
;
1154 nState
&= ~ControlState::ENABLED
;
1156 nState
|= ControlState::FOCUSED
;
1158 if (lcl_canPaint(rRenderContext
, rRect
, aRect
))
1159 rRenderContext
.DrawNativeControl(ControlType::TabPane
, ControlPart::Entire
,
1160 aRect
, nState
, aTabPaneValue
, OUString());
1162 if (!bPaneWithHeader
&& rRenderContext
.IsNativeControlSupported(ControlType::TabHeader
, ControlPart::Entire
)
1163 && lcl_canPaint(rRenderContext
, rRect
, aHeaderRect
))
1164 rRenderContext
.DrawNativeControl(ControlType::TabHeader
, ControlPart::Entire
,
1165 aHeaderRect
, nState
, aTabPaneValue
, OUString());
1169 tools::Long nTopOff
= 1;
1170 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
1171 rRenderContext
.SetLineColor(rStyleSettings
.GetLightColor());
1173 rRenderContext
.SetLineColor(COL_BLACK
);
1174 if (mbShowTabs
&& pCurItem
&& !pCurItem
->maRect
.IsEmpty())
1176 aCurRect
= pCurItem
->maRect
;
1177 rRenderContext
.DrawLine(aRect
.TopLeft(), Point(aCurRect
.Left() - 2, aRect
.Top()));
1178 if (aCurRect
.Right() + 1 < aRect
.Right())
1180 rRenderContext
.DrawLine(Point(aCurRect
.Right(), aRect
.Top()), aRect
.TopRight());
1188 rRenderContext
.DrawLine(aRect
.TopLeft(), aRect
.TopRight());
1190 rRenderContext
.DrawLine(aRect
.TopLeft(), aRect
.BottomLeft());
1192 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
1194 // if we have not tab page the bottom line of the tab page
1195 // directly touches the tab items, so choose a color that fits seamlessly
1197 rRenderContext
.SetLineColor(rStyleSettings
.GetDialogColor());
1199 rRenderContext
.SetLineColor(rStyleSettings
.GetShadowColor());
1200 rRenderContext
.DrawLine(Point(1, aRect
.Bottom() - 1), Point(aRect
.Right() - 1, aRect
.Bottom() - 1));
1201 rRenderContext
.DrawLine(Point(aRect
.Right() - 1, aRect
.Top() + nTopOff
), Point(aRect
.Right() - 1, aRect
.Bottom() - 1));
1203 rRenderContext
.SetLineColor(rStyleSettings
.GetDialogColor());
1205 rRenderContext
.SetLineColor(rStyleSettings
.GetDarkShadowColor());
1206 rRenderContext
.DrawLine(Point(0, aRect
.Bottom()), Point(aRect
.Right(), aRect
.Bottom()));
1207 rRenderContext
.DrawLine(Point(aRect
.Right(), aRect
.Top() + nTopOff
), Point(aRect
.Right(), aRect
.Bottom()));
1211 rRenderContext
.DrawLine(aRect
.TopRight(), aRect
.BottomRight());
1212 rRenderContext
.DrawLine(aRect
.BottomLeft(), aRect
.BottomRight());
1216 const size_t nItemListSize
= mpTabCtrlData
->maItemList
.size();
1217 if (mbShowTabs
&& nItemListSize
> 0 && mpTabCtrlData
->mpListBox
== nullptr)
1219 // Some native toolkits (GTK+) draw tabs right-to-left, with an
1220 // overlap between adjacent tabs
1221 bool bDrawTabsRTL
= rRenderContext
.IsNativeControlSupported(ControlType::TabItem
, ControlPart::TabsDrawRtl
);
1222 ImplTabItem
* pFirstTab
= nullptr;
1223 ImplTabItem
* pLastTab
= nullptr;
1226 // Even though there is a tab overlap with GTK+, the first tab is not
1227 // overlapped on the left side. Other toolkits ignore this option.
1230 pFirstTab
= mpTabCtrlData
->maItemList
.data();
1231 pLastTab
= pFirstTab
+ nItemListSize
- 1;
1232 idx
= nItemListSize
- 1;
1236 pLastTab
= mpTabCtrlData
->maItemList
.data();
1237 pFirstTab
= pLastTab
+ nItemListSize
- 1;
1241 while (idx
< nItemListSize
)
1243 ImplTabItem
* pItem
= &mpTabCtrlData
->maItemList
[idx
];
1245 if (pItem
!= pCurItem
&& pItem
->m_bVisible
&& lcl_canPaint(rRenderContext
, rRect
, pItem
->maRect
))
1246 ImplDrawItem(rRenderContext
, pItem
, aCurRect
, pItem
== pFirstTab
, pItem
== pLastTab
);
1254 if (pCurItem
&& lcl_canPaint(rRenderContext
, rRect
, pCurItem
->maRect
))
1255 ImplDrawItem(rRenderContext
, pCurItem
, aCurRect
, pCurItem
== pFirstTab
, pCurItem
== pLastTab
);
1261 mbSmallInvalidate
= true;
1264 void TabControl::setAllocation(const Size
&rAllocation
)
1266 if ( !IsReallyShown() )
1269 if( mpTabCtrlData
->mpListBox
)
1271 // get the listbox' preferred size
1272 Size
aTabCtrlSize( GetSizePixel() );
1273 tools::Long nPrefWidth
= mpTabCtrlData
->mpListBox
->get_preferred_size().Width();
1274 if( nPrefWidth
> aTabCtrlSize
.Width() )
1275 nPrefWidth
= aTabCtrlSize
.Width();
1276 Size
aNewSize( nPrefWidth
, LogicToPixel( Size( 12, 12 ), MapMode( MapUnit::MapAppFont
) ).Height() );
1277 Point
aNewPos( (aTabCtrlSize
.Width() - nPrefWidth
) / 2, 0 );
1278 mpTabCtrlData
->mpListBox
->SetPosSizePixel( aNewPos
, aNewSize
);
1283 // resize/position active TabPage
1284 bool bTabPage
= ImplPosCurTabPage();
1286 // check what needs to be invalidated
1287 Size aNewSize
= rAllocation
;
1288 tools::Long nNewWidth
= aNewSize
.Width();
1289 for (auto const& item
: mpTabCtrlData
->maItemList
)
1291 if (!item
.m_bVisible
)
1293 if (!item
.mbFullVisible
|| (item
.maRect
.Right()-2 >= nNewWidth
))
1295 mbSmallInvalidate
= false;
1300 if ( mbSmallInvalidate
)
1302 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
);
1303 aRect
.AdjustLeft( -(TAB_OFFSET
+TAB_BORDER_LEFT
) );
1304 aRect
.AdjustTop( -(TAB_OFFSET
+TAB_BORDER_TOP
) );
1305 aRect
.AdjustRight(TAB_OFFSET
+TAB_BORDER_RIGHT
);
1306 aRect
.AdjustBottom(TAB_OFFSET
+TAB_BORDER_BOTTOM
);
1308 Invalidate( aRect
, InvalidateFlags::NoChildren
);
1310 Invalidate( aRect
);
1316 Invalidate( InvalidateFlags::NoChildren
);
1321 mbLayoutDirty
= false;
1324 void TabControl::SetPosSizePixel(const Point
& rNewPos
, const Size
& rNewSize
)
1326 Window::SetPosSizePixel(rNewPos
, rNewSize
);
1327 //if size changed, TabControl::Resize got called already
1329 setAllocation(rNewSize
);
1332 void TabControl::SetSizePixel(const Size
& rNewSize
)
1334 Window::SetSizePixel(rNewSize
);
1335 //if size changed, TabControl::Resize got called already
1337 setAllocation(rNewSize
);
1340 void TabControl::SetPosPixel(const Point
& rPos
)
1342 Window::SetPosPixel(rPos
);
1344 setAllocation(GetOutputSizePixel());
1347 void TabControl::Resize()
1349 setAllocation(Control::GetOutputSizePixel());
1352 void TabControl::GetFocus()
1354 if( ! mpTabCtrlData
->mpListBox
)
1359 SetInputContext( InputContext( GetFont() ) );
1363 // no tabs, focus first thing in current page
1364 ImplTabItem
* pItem
= ImplGetItem(GetCurPageId());
1365 if (pItem
&& pItem
->mpTabPage
)
1367 vcl::Window
* pFirstChild
= pItem
->mpTabPage
->ImplGetDlgWindow(0, GetDlgWindowType::First
);
1369 pFirstChild
->ImplControlFocus(GetFocusFlags::Init
);
1375 if( mpTabCtrlData
->mpListBox
->IsReallyVisible() )
1376 mpTabCtrlData
->mpListBox
->GrabFocus();
1379 Control::GetFocus();
1382 void TabControl::LoseFocus()
1384 if( mpTabCtrlData
&& ! mpTabCtrlData
->mpListBox
)
1386 Control::LoseFocus();
1389 void TabControl::RequestHelp( const HelpEvent
& rHEvt
)
1391 sal_uInt16 nItemId
= rHEvt
.KeyboardActivated() ? mnCurPageId
: GetPageId( ScreenToOutputPixel( rHEvt
.GetMousePosPixel() ) );
1395 if ( rHEvt
.GetMode() & HelpEventMode::BALLOON
)
1397 OUString aStr
= GetHelpText( nItemId
);
1398 if ( !aStr
.isEmpty() )
1400 tools::Rectangle aItemRect
= ImplGetTabRect( GetPagePos( nItemId
) );
1401 Point aPt
= OutputToScreenPixel( aItemRect
.TopLeft() );
1402 aItemRect
.SetLeft( aPt
.X() );
1403 aItemRect
.SetTop( aPt
.Y() );
1404 aPt
= OutputToScreenPixel( aItemRect
.BottomRight() );
1405 aItemRect
.SetRight( aPt
.X() );
1406 aItemRect
.SetBottom( aPt
.Y() );
1407 Help::ShowBalloon( this, aItemRect
.Center(), aItemRect
, aStr
);
1412 // for Quick or Ballon Help, we show the text, if it is cut
1413 if ( rHEvt
.GetMode() & (HelpEventMode::QUICK
| HelpEventMode::BALLOON
) )
1415 ImplTabItem
* pItem
= ImplGetItem( nItemId
);
1416 const OUString
& rStr
= pItem
->maText
;
1417 if ( rStr
!= pItem
->maFormatText
)
1419 tools::Rectangle aItemRect
= ImplGetTabRect( GetPagePos( nItemId
) );
1420 Point aPt
= OutputToScreenPixel( aItemRect
.TopLeft() );
1421 aItemRect
.SetLeft( aPt
.X() );
1422 aItemRect
.SetTop( aPt
.Y() );
1423 aPt
= OutputToScreenPixel( aItemRect
.BottomRight() );
1424 aItemRect
.SetRight( aPt
.X() );
1425 aItemRect
.SetBottom( aPt
.Y() );
1426 if ( !rStr
.isEmpty() )
1428 if ( rHEvt
.GetMode() & HelpEventMode::BALLOON
)
1429 Help::ShowBalloon( this, aItemRect
.Center(), aItemRect
, rStr
);
1431 Help::ShowQuickHelp( this, aItemRect
, rStr
);
1437 if ( rHEvt
.GetMode() & HelpEventMode::QUICK
)
1439 ImplTabItem
* pItem
= ImplGetItem( nItemId
);
1440 const OUString
& rHelpText
= pItem
->maHelpText
;
1441 if (!rHelpText
.isEmpty())
1443 tools::Rectangle aItemRect
= ImplGetTabRect( GetPagePos( nItemId
) );
1444 Point aPt
= OutputToScreenPixel( aItemRect
.TopLeft() );
1445 aItemRect
.SetLeft( aPt
.X() );
1446 aItemRect
.SetTop( aPt
.Y() );
1447 aPt
= OutputToScreenPixel( aItemRect
.BottomRight() );
1448 aItemRect
.SetRight( aPt
.X() );
1449 aItemRect
.SetBottom( aPt
.Y() );
1450 Help::ShowQuickHelp( this, aItemRect
, rHelpText
);
1456 Control::RequestHelp( rHEvt
);
1459 void TabControl::Command( const CommandEvent
& rCEvt
)
1461 if( (mpTabCtrlData
->mpListBox
== nullptr) && (rCEvt
.GetCommand() == CommandEventId::ContextMenu
) && (GetPageCount() > 1) )
1465 if ( rCEvt
.IsMouseEvent() )
1467 aMenuPos
= rCEvt
.GetMousePosPixel();
1468 bMenu
= GetPageId( aMenuPos
) != 0;
1472 aMenuPos
= ImplGetTabRect( GetPagePos( mnCurPageId
) ).Center();
1478 ScopedVclPtrInstance
<PopupMenu
> aMenu
;
1479 for (auto const& item
: mpTabCtrlData
->maItemList
)
1481 aMenu
->InsertItem(item
.id(), item
.maText
, MenuItemBits::CHECKABLE
| MenuItemBits::RADIOCHECK
);
1482 if (item
.id() == mnCurPageId
)
1483 aMenu
->CheckItem(item
.id());
1484 aMenu
->SetHelpId(item
.id(), {});
1487 sal_uInt16 nId
= aMenu
->Execute( this, aMenuPos
);
1488 if ( nId
&& (nId
!= mnCurPageId
) )
1489 SelectTabPage( nId
);
1494 Control::Command( rCEvt
);
1497 void TabControl::StateChanged( StateChangedType nType
)
1499 Control::StateChanged( nType
);
1501 if ( nType
== StateChangedType::InitShow
)
1503 ImplPosCurTabPage();
1504 if( mpTabCtrlData
->mpListBox
)
1507 else if ( nType
== StateChangedType::UpdateMode
)
1509 if ( IsUpdateMode() )
1512 else if ( (nType
== StateChangedType::Zoom
) ||
1513 (nType
== StateChangedType::ControlFont
) )
1515 ImplInitSettings( false );
1518 else if ( nType
== StateChangedType::ControlForeground
)
1520 ImplInitSettings( false );
1523 else if ( nType
== StateChangedType::ControlBackground
)
1525 ImplInitSettings( true );
1530 void TabControl::DataChanged( const DataChangedEvent
& rDCEvt
)
1532 Control::DataChanged( rDCEvt
);
1534 if ( (rDCEvt
.GetType() == DataChangedEventType::FONTS
) ||
1535 (rDCEvt
.GetType() == DataChangedEventType::FONTSUBSTITUTION
) ||
1536 ((rDCEvt
.GetType() == DataChangedEventType::SETTINGS
) &&
1537 (rDCEvt
.GetFlags() & AllSettingsFlags::STYLE
)) )
1539 ImplInitSettings( true );
1544 ImplTabItem
* TabControl::ImplGetItem(const Point
& rPt
) const
1546 ImplTabItem
* pFoundItem
= nullptr;
1548 for (auto & item
: mpTabCtrlData
->maItemList
)
1550 if (item
.m_bVisible
&& item
.maRect
.Contains(rPt
))
1557 // assure that only one tab is highlighted at a time
1558 assert(nFound
<= 1);
1559 return nFound
== 1 ? pFoundItem
: nullptr;
1562 bool TabControl::PreNotify( NotifyEvent
& rNEvt
)
1564 if( rNEvt
.GetType() == NotifyEventType::MOUSEMOVE
)
1566 const MouseEvent
* pMouseEvt
= rNEvt
.GetMouseEvent();
1567 if( pMouseEvt
&& !pMouseEvt
->GetButtons() && !pMouseEvt
->IsSynthetic() && !pMouseEvt
->IsModifierChanged() )
1569 // trigger redraw if mouse over state has changed
1570 if( IsNativeControlSupported(ControlType::TabItem
, ControlPart::Entire
) )
1572 ImplTabItem
*pItem
= ImplGetItem(GetPointerPosPixel());
1573 ImplTabItem
*pLastItem
= ImplGetItem(GetLastPointerPosPixel());
1574 if ((pItem
!= pLastItem
) || pMouseEvt
->IsLeaveWindow() || pMouseEvt
->IsEnterWindow())
1576 vcl::Region aClipRgn
;
1579 // allow for slightly bigger tabitems
1581 // TODO: query for the correct sizes
1582 tools::Rectangle
aRect(pLastItem
->maRect
);
1583 aRect
.AdjustLeft( -2 );
1584 aRect
.AdjustRight(2 );
1585 aRect
.AdjustTop( -3 );
1586 aClipRgn
.Union( aRect
);
1591 // allow for slightly bigger tabitems
1593 // TODO: query for the correct sizes
1594 tools::Rectangle
aRect(pItem
->maRect
);
1595 aRect
.AdjustLeft( -2 );
1596 aRect
.AdjustRight(2 );
1597 aRect
.AdjustTop( -3 );
1598 aClipRgn
.Union( aRect
);
1601 if( !aClipRgn
.IsEmpty() )
1602 Invalidate( aClipRgn
);
1608 return Control::PreNotify(rNEvt
);
1611 bool TabControl::EventNotify( NotifyEvent
& rNEvt
)
1615 if ( rNEvt
.GetType() == NotifyEventType::KEYINPUT
)
1616 bRet
= ImplHandleKeyEvent( *rNEvt
.GetKeyEvent() );
1618 return bRet
|| Control::EventNotify( rNEvt
);
1621 void TabControl::ActivatePage()
1623 maActivateHdl
.Call( this );
1626 bool TabControl::DeactivatePage()
1628 return !maDeactivateHdl
.IsSet() || maDeactivateHdl
.Call( this );
1631 void TabControl::SetTabPageSizePixel( const Size
& rSize
)
1633 Size
aNewSize( rSize
);
1634 aNewSize
.AdjustWidth(TAB_OFFSET
*2 );
1635 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
,
1636 aNewSize
.Width(), aNewSize
.Height() );
1637 aNewSize
.AdjustHeight(aRect
.Top()+TAB_OFFSET
);
1638 Window::SetOutputSizePixel( aNewSize
);
1641 void TabControl::InsertPage( sal_uInt16 nPageId
, const OUString
& rText
,
1644 SAL_WARN_IF( !nPageId
, "vcl", "TabControl::InsertPage(): PageId == 0" );
1645 SAL_WARN_IF( GetPagePos( nPageId
) != TAB_PAGE_NOTFOUND
, "vcl",
1646 "TabControl::InsertPage(): PageId already exists" );
1648 // insert new page item
1649 ImplTabItem
* pItem
= nullptr;
1650 if( nPos
== TAB_APPEND
|| size_t(nPos
) >= mpTabCtrlData
->maItemList
.size() )
1652 mpTabCtrlData
->maItemList
.emplace_back(nPageId
);
1653 pItem
= &mpTabCtrlData
->maItemList
.back();
1654 if( mpTabCtrlData
->mpListBox
)
1655 mpTabCtrlData
->mpListBox
->InsertEntry( rText
);
1659 std::vector
< ImplTabItem
>::iterator new_it
=
1660 mpTabCtrlData
->maItemList
.emplace(mpTabCtrlData
->maItemList
.begin() + nPos
, nPageId
);
1662 if( mpTabCtrlData
->mpListBox
)
1663 mpTabCtrlData
->mpListBox
->InsertEntry( rText
, nPos
);
1665 if( mpTabCtrlData
->mpListBox
)
1668 mpTabCtrlData
->mpListBox
->SelectEntryPos( 0 );
1669 mpTabCtrlData
->mpListBox
->SetDropDownLineCount( mpTabCtrlData
->mpListBox
->GetEntryCount() );
1672 // set current page id
1674 mnCurPageId
= nPageId
;
1676 // init new page item
1677 pItem
->maText
= rText
;
1678 pItem
->mbFullVisible
= false;
1681 if ( IsUpdateMode() )
1684 if( mpTabCtrlData
->mpListBox
) // reposition/resize listbox
1687 CallEventListeners( VclEventId::TabpageInserted
, reinterpret_cast<void*>(nPageId
) );
1690 void TabControl::RemovePage( sal_uInt16 nPageId
)
1692 sal_uInt16 nPos
= GetPagePos( nPageId
);
1694 // does the item exist ?
1695 if ( nPos
== TAB_PAGE_NOTFOUND
)
1699 std::vector
< ImplTabItem
>::iterator it
= mpTabCtrlData
->maItemList
.begin() + nPos
;
1700 bool bIsCurrentPage
= (it
->id() == mnCurPageId
);
1701 mpTabCtrlData
->maItemList
.erase( it
);
1702 if( mpTabCtrlData
->mpListBox
)
1704 mpTabCtrlData
->mpListBox
->RemoveEntry( nPos
);
1705 mpTabCtrlData
->mpListBox
->SetDropDownLineCount( mpTabCtrlData
->mpListBox
->GetEntryCount() );
1708 // If current page is removed, then first page gets the current page
1709 if ( bIsCurrentPage
)
1713 if( ! mpTabCtrlData
->maItemList
.empty() )
1715 // don't do this by simply setting mnCurPageId to pFirstItem->id()
1716 // this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
1717 // instead, call SetCurPageId
1718 // without this, the next (outside) call to SetCurPageId with the id of the first page
1719 // will result in doing nothing (as we assume that nothing changed, then), and the page
1720 // will never be shown.
1721 // 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
1723 SetCurPageId(mpTabCtrlData
->maItemList
[0].id());
1728 if ( IsUpdateMode() )
1731 CallEventListeners( VclEventId::TabpageRemoved
, reinterpret_cast<void*>(nPageId
) );
1734 void TabControl::SetPageEnabled( sal_uInt16 i_nPageId
, bool i_bEnable
)
1736 ImplTabItem
* pItem
= ImplGetItem( i_nPageId
);
1738 if (!pItem
|| pItem
->m_bEnabled
== i_bEnable
)
1741 pItem
->m_bEnabled
= i_bEnable
;
1742 if (!pItem
->m_bVisible
)
1746 if( mpTabCtrlData
->mpListBox
)
1747 mpTabCtrlData
->mpListBox
->SetEntryFlags( GetPagePos( i_nPageId
),
1748 i_bEnable
? ListBoxEntryFlags::NONE
: (ListBoxEntryFlags::DisableSelection
| ListBoxEntryFlags::DrawDisabled
) );
1750 // SetCurPageId will change to a valid page
1751 if (pItem
->id() == mnCurPageId
)
1752 SetCurPageId( mnCurPageId
);
1753 else if ( IsUpdateMode() )
1757 void TabControl::SetPageVisible( sal_uInt16 nPageId
, bool bVisible
)
1759 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1760 if (!pItem
|| pItem
->m_bVisible
== bVisible
)
1763 pItem
->m_bVisible
= bVisible
;
1766 if (pItem
->mbFullVisible
)
1767 mbSmallInvalidate
= false;
1768 pItem
->mbFullVisible
= false;
1769 pItem
->maRect
.SetEmpty();
1773 // SetCurPageId will change to a valid page
1774 if (pItem
->id() == mnCurPageId
)
1775 SetCurPageId(mnCurPageId
);
1776 else if (IsUpdateMode())
1780 sal_uInt16
TabControl::GetPageCount() const
1782 return static_cast<sal_uInt16
>(mpTabCtrlData
->maItemList
.size());
1785 sal_uInt16
TabControl::GetPageId( sal_uInt16 nPos
) const
1787 if( size_t(nPos
) < mpTabCtrlData
->maItemList
.size() )
1788 return mpTabCtrlData
->maItemList
[nPos
].id();
1792 sal_uInt16
TabControl::GetPagePos( sal_uInt16 nPageId
) const
1794 sal_uInt16 nPos
= 0;
1795 for (auto const& item
: mpTabCtrlData
->maItemList
)
1797 if (item
.id() == nPageId
)
1802 return TAB_PAGE_NOTFOUND
;
1805 sal_uInt16
TabControl::GetPageId( const Point
& rPos
) const
1807 Size winSize
= Control::GetOutputSizePixel();
1808 const auto &rList
= mpTabCtrlData
->maItemList
;
1809 const auto it
= std::find_if(rList
.begin(), rList
.end(), [&rPos
, &winSize
, this](const auto &item
) {
1810 return const_cast<TabControl
*>(this)->ImplGetTabRect(&item
, winSize
.Width(), winSize
.Height()).Contains(rPos
); });
1811 return (it
!= rList
.end()) ? it
->id() : 0;
1814 sal_uInt16
TabControl::GetPageId( const OUString
& rName
) const
1816 const auto &rList
= mpTabCtrlData
->maItemList
;
1817 const auto it
= std::find_if(rList
.begin(), rList
.end(), [&rName
](const auto &item
) {
1818 return item
.maTabName
== rName
; });
1819 return (it
!= rList
.end()) ? it
->id() : 0;
1822 void TabControl::SetCurPageId( sal_uInt16 nPageId
)
1824 sal_uInt16 nPos
= GetPagePos( nPageId
);
1825 while (nPos
!= TAB_PAGE_NOTFOUND
&& !mpTabCtrlData
->maItemList
[nPos
].m_bEnabled
)
1828 if( size_t(nPos
) >= mpTabCtrlData
->maItemList
.size() )
1830 if (mpTabCtrlData
->maItemList
[nPos
].id() == nPageId
)
1834 if( nPos
== TAB_PAGE_NOTFOUND
)
1837 nPageId
= mpTabCtrlData
->maItemList
[nPos
].id();
1838 if ( nPageId
== mnCurPageId
)
1841 mnActPageId
= nPageId
;
1846 mnActPageId
= nPageId
;
1850 sal_uInt16 nOldId
= mnCurPageId
;
1851 mnCurPageId
= nPageId
;
1852 ImplChangeTabPage( nPageId
, nOldId
);
1856 sal_uInt16
TabControl::GetCurPageId() const
1864 void TabControl::SelectTabPage( sal_uInt16 nPageId
)
1866 if ( !nPageId
|| (nPageId
== mnCurPageId
) )
1869 CallEventListeners( VclEventId::TabpageDeactivate
, reinterpret_cast<void*>(mnCurPageId
) );
1870 if ( DeactivatePage() )
1872 mnActPageId
= nPageId
;
1874 // Page could have been switched by the Activate handler
1875 nPageId
= mnActPageId
;
1877 SetCurPageId( nPageId
);
1878 if( mpTabCtrlData
->mpListBox
)
1879 mpTabCtrlData
->mpListBox
->SelectEntryPos( GetPagePos( nPageId
) );
1880 CallEventListeners( VclEventId::TabpageActivate
, reinterpret_cast<void*>(nPageId
) );
1884 void TabControl::SetTabPage( sal_uInt16 nPageId
, TabPage
* pTabPage
)
1886 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1888 if ( !pItem
|| (pItem
->mpTabPage
.get() == pTabPage
) )
1893 if ( IsDefaultSize() )
1894 SetTabPageSizePixel( pTabPage
->GetSizePixel() );
1896 // only set here, so that Resize does not reposition TabPage
1897 pItem
->mpTabPage
= pTabPage
;
1900 if (pItem
->id() == mnCurPageId
)
1901 ImplChangeTabPage(pItem
->id(), 0);
1905 pItem
->mpTabPage
= nullptr;
1910 TabPage
* TabControl::GetTabPage( sal_uInt16 nPageId
) const
1912 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1915 return pItem
->mpTabPage
;
1920 void TabControl::SetPageText( sal_uInt16 nPageId
, const OUString
& rText
)
1922 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1924 if ( !pItem
|| pItem
->maText
== rText
)
1927 pItem
->maText
= rText
;
1929 if( mpTabCtrlData
->mpListBox
)
1931 sal_uInt16 nPos
= GetPagePos( nPageId
);
1932 mpTabCtrlData
->mpListBox
->RemoveEntry( nPos
);
1933 mpTabCtrlData
->mpListBox
->InsertEntry( rText
, nPos
);
1935 if ( IsUpdateMode() )
1937 CallEventListeners( VclEventId::TabpagePageTextChanged
, reinterpret_cast<void*>(nPageId
) );
1940 OUString
const & TabControl::GetPageText( sal_uInt16 nPageId
) const
1942 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1946 return pItem
->maText
;
1949 void TabControl::SetHelpText( sal_uInt16 nPageId
, const OUString
& rText
)
1951 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1955 pItem
->maHelpText
= rText
;
1958 const OUString
& TabControl::GetHelpText( sal_uInt16 nPageId
) const
1960 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1962 return pItem
->maHelpText
;
1965 void TabControl::SetAccessibleName(sal_uInt16 nPageId
, const OUString
& rName
)
1967 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1969 pItem
->maAccessibleName
= rName
;
1972 OUString
TabControl::GetAccessibleName( sal_uInt16 nPageId
) const
1974 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1976 if (!pItem
->maAccessibleName
.isEmpty())
1977 return pItem
->maAccessibleName
;
1978 return removeMnemonicFromString(pItem
->maText
);
1981 void TabControl::SetAccessibleDescription(sal_uInt16 nPageId
, const OUString
& rDesc
)
1983 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1985 pItem
->maAccessibleDescription
= rDesc
;
1988 OUString
TabControl::GetAccessibleDescription( sal_uInt16 nPageId
) const
1990 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1992 if (!pItem
->maAccessibleDescription
.isEmpty())
1993 return pItem
->maAccessibleDescription
;
1994 return pItem
->maHelpText
;
1997 void TabControl::SetPageName( sal_uInt16 nPageId
, const OUString
& rName
) const
1999 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
2002 pItem
->maTabName
= rName
;
2005 OUString
TabControl::GetPageName( sal_uInt16 nPageId
) const
2007 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
2010 return pItem
->maTabName
;
2015 void TabControl::SetPageImage( sal_uInt16 i_nPageId
, const Image
& i_rImage
)
2017 ImplTabItem
* pItem
= ImplGetItem( i_nPageId
);
2021 pItem
->maTabImage
= i_rImage
;
2023 if ( IsUpdateMode() )
2028 tools::Rectangle
TabControl::GetTabBounds( sal_uInt16 nPageId
) const
2030 tools::Rectangle aRet
;
2032 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
2033 if (pItem
&& pItem
->m_bVisible
)
2034 aRet
= pItem
->maRect
;
2039 Size
TabControl::ImplCalculateRequisition(sal_uInt16
& nHeaderHeight
) const
2041 Size
aOptimalPageSize(0, 0);
2043 sal_uInt16 nOrigPageId
= GetCurPageId();
2044 for (auto const& item
: mpTabCtrlData
->maItemList
)
2046 const TabPage
*pPage
= item
.mpTabPage
;
2047 //it's a real nuisance if the page is not inserted yet :-(
2048 //We need to force all tabs to exist to get overall optimal size for dialog
2051 TabControl
*pThis
= const_cast<TabControl
*>(this);
2052 pThis
->SetCurPageId(item
.id());
2053 pThis
->ActivatePage();
2054 pPage
= item
.mpTabPage
;
2060 Size
aPageSize(VclContainer::getLayoutRequisition(*pPage
));
2062 if (aPageSize
.Width() > aOptimalPageSize
.Width())
2063 aOptimalPageSize
.setWidth( aPageSize
.Width() );
2064 if (aPageSize
.Height() > aOptimalPageSize
.Height())
2065 aOptimalPageSize
.setHeight( aPageSize
.Height() );
2068 //fdo#61940 If we were forced to activate pages in order to on-demand
2069 //create them to get their optimal size, then switch back to the original
2070 //page and re-activate it
2071 if (nOrigPageId
!= GetCurPageId())
2073 TabControl
*pThis
= const_cast<TabControl
*>(this);
2074 pThis
->SetCurPageId(nOrigPageId
);
2075 pThis
->ActivatePage();
2078 tools::Long nTabLabelsBottom
= 0, nTabLabelsRight
= 0;
2081 for (sal_uInt16
nPos(0), sizeList(static_cast <sal_uInt16
> (mpTabCtrlData
->maItemList
.size()));
2082 nPos
< sizeList
; ++nPos
)
2084 TabControl
* pThis
= const_cast<TabControl
*>(this);
2086 tools::Rectangle aTabRect
= pThis
->ImplGetTabRect(nPos
, aOptimalPageSize
.Width(), LONG_MAX
);
2087 if (aTabRect
.Bottom() > nTabLabelsBottom
)
2089 nTabLabelsBottom
= aTabRect
.Bottom();
2090 nHeaderHeight
= nTabLabelsBottom
;
2092 if (!aTabRect
.IsEmpty() && aTabRect
.Right() > nTabLabelsRight
)
2093 nTabLabelsRight
= aTabRect
.Right();
2097 Size
aOptimalSize(aOptimalPageSize
);
2098 aOptimalSize
.AdjustHeight(nTabLabelsBottom
);
2099 aOptimalSize
.setWidth( std::max(nTabLabelsRight
, aOptimalSize
.Width()) );
2101 aOptimalSize
.AdjustWidth(TAB_OFFSET
* 2 );
2102 aOptimalSize
.AdjustHeight(TAB_OFFSET
* 2 );
2104 return aOptimalSize
;
2107 Size
TabControl::calculateRequisition() const
2109 sal_uInt16 nHeaderHeight
;
2110 return ImplCalculateRequisition(nHeaderHeight
);
2113 Size
TabControl::GetOptimalSize() const
2115 return calculateRequisition();
2118 void TabControl::queue_resize(StateChangedType eReason
)
2120 mbLayoutDirty
= true;
2121 Window::queue_resize(eReason
);
2124 std::vector
<sal_uInt16
> TabControl::GetPageIDs() const
2126 std::vector
<sal_uInt16
> aIDs
;
2127 for (auto const& item
: mpTabCtrlData
->maItemList
)
2129 aIDs
.push_back(item
.id());
2135 bool TabControl::set_property(const OUString
&rKey
, const OUString
&rValue
)
2137 if (rKey
== "show-tabs")
2139 mbShowTabs
= toBool(rValue
);
2143 return Control::set_property(rKey
, rValue
);
2147 FactoryFunction
TabControl::GetUITestFactory() const
2149 return TabControlUIObject::create
;
2152 void TabControl::DumpAsPropertyTree(tools::JsonWriter
& rJsonWriter
)
2154 rJsonWriter
.put("id", get_id());
2155 rJsonWriter
.put("type", "tabcontrol");
2156 rJsonWriter
.put("selected", GetCurPageId());
2159 auto childrenNode
= rJsonWriter
.startArray("children");
2160 for (auto id
: GetPageIDs())
2162 TabPage
* pChild
= GetTabPage(id
);
2166 auto childNode
= rJsonWriter
.startStruct();
2167 pChild
->DumpAsPropertyTree(rJsonWriter
);
2169 if (!pChild
->IsVisible())
2170 rJsonWriter
.put("hidden", true);
2175 auto tabsNode
= rJsonWriter
.startArray("tabs");
2176 for(auto id
: GetPageIDs())
2178 auto tabNode
= rJsonWriter
.startStruct();
2179 rJsonWriter
.put("text", GetPageText(id
));
2180 rJsonWriter
.put("id", id
);
2181 rJsonWriter
.put("name", GetPageName(id
));
2186 sal_uInt16
NotebookbarTabControlBase::m_nHeaderHeight
= 0;
2188 IMPL_LINK_NOARG(NotebookbarTabControlBase
, OpenMenu
, Button
*, void)
2190 m_aIconClickHdl
.Call(static_cast<NotebookBar
*>(GetParent()->GetParent()));
2193 NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window
* pParent
)
2194 : TabControl(pParent
, WB_STDTABCONTROL
)
2195 , bLastContextWasSupported(true)
2196 , eLastContext(vcl::EnumContext::Context::Any
)
2198 m_pOpenMenu
= VclPtr
<PushButton
>::Create( this , WB_CENTER
| WB_VCENTER
);
2199 m_pOpenMenu
->SetClickHdl(LINK(this, NotebookbarTabControlBase
, OpenMenu
));
2200 m_pOpenMenu
->SetModeImage(Image(StockImage::Yes
, SV_RESID_BITMAP_NOTEBOOKBAR
));
2201 m_pOpenMenu
->SetSizePixel(m_pOpenMenu
->GetOptimalSize());
2202 m_pOpenMenu
->Show();
2205 NotebookbarTabControlBase::~NotebookbarTabControlBase()
2210 void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext
)
2212 if (eLastContext
== eContext
)
2215 bool bHandled
= false;
2217 TabPage
* pPage
= GetTabPage(mnCurPageId
);
2218 // Try to stay on the current tab (unless the new context has a special tab)
2219 if (pPage
&& eLastContext
!= vcl::EnumContext::Context::Any
2220 && pPage
->HasContext(vcl::EnumContext::Context::Any
) && pPage
->IsEnabled())
2225 for (int nChild
= 0; nChild
< GetPageCount(); ++nChild
)
2227 sal_uInt16 nPageId
= TabControl::GetPageId(nChild
);
2228 pPage
= GetTabPage(nPageId
);
2233 SetPageVisible(nPageId
, pPage
->HasContext(eContext
) || pPage
->HasContext(vcl::EnumContext::Context::Any
));
2235 if (eContext
!= vcl::EnumContext::Context::Any
2236 && (!bHandled
|| !pPage
->HasContext(vcl::EnumContext::Context::Any
))
2237 && pPage
->HasContext(eContext
))
2239 SetCurPageId(nPageId
);
2241 bLastContextWasSupported
= true;
2244 if (!bHandled
&& bLastContextWasSupported
2245 && pPage
->HasContext(vcl::EnumContext::Context::Default
))
2247 SetCurPageId(nPageId
);
2252 bLastContextWasSupported
= false;
2253 eLastContext
= eContext
;
2255 // tdf#152908 Tabbed compact toolbar does not repaint itself when tabs getting removed
2256 // For unknown reason this is needed by the tabbed compact toolbar for other than gtk
2261 void NotebookbarTabControlBase::dispose()
2263 m_pShortcuts
.disposeAndClear();
2264 m_pOpenMenu
.disposeAndClear();
2265 TabControl::dispose();
2268 void NotebookbarTabControlBase::SetToolBox( ToolBox
* pToolBox
)
2270 m_pShortcuts
.set( pToolBox
);
2273 void NotebookbarTabControlBase::SetIconClickHdl( Link
<NotebookBar
*, void> aHdl
)
2275 m_aIconClickHdl
= aHdl
;
2278 static bool lcl_isValidPage(const ImplTabItem
& rItem
)
2280 return rItem
.m_bVisible
&& rItem
.m_bEnabled
;
2283 void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext
)
2285 sal_Int32 nCurPos
= GetPagePos(GetCurPageId());
2289 for (sal_Int32 nPos
= nCurPos
+ 1; nPos
< GetPageCount(); nPos
++)
2290 if (lcl_isValidPage(mpTabCtrlData
->maItemList
[nPos
]))
2298 for (sal_Int32 nPos
= nCurPos
- 1; nPos
>= 0; nPos
--)
2299 if (lcl_isValidPage(mpTabCtrlData
->maItemList
[nPos
]))
2306 SelectTabPage( TabControl::GetPageId( nCurPos
) );
2309 bool NotebookbarTabControlBase::ImplPlaceTabs( tools::Long nWidth
)
2313 if ( mpTabCtrlData
->maItemList
.empty() )
2315 if (!m_pOpenMenu
|| m_pOpenMenu
->isDisposed())
2318 const tools::Long nHamburgerWidth
= m_pOpenMenu
->GetSizePixel().Width();
2319 tools::Long nMaxWidth
= nWidth
- nHamburgerWidth
;
2320 tools::Long nShortcutsWidth
= m_pShortcuts
!= nullptr ? m_pShortcuts
->GetSizePixel().getWidth() + 1 : 0;
2321 tools::Long nFullWidth
= nShortcutsWidth
;
2323 const tools::Long nOffsetX
= 2 + nShortcutsWidth
;
2324 const tools::Long nOffsetY
= 2;
2326 //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
2327 //of ugly bare tabs on lines of their own
2329 for (auto & item
: mpTabCtrlData
->maItemList
)
2331 tools::Long nTabWidth
= 0;
2332 if (item
.m_bVisible
)
2334 nTabWidth
= ImplGetItemSize(&item
, nMaxWidth
).getWidth();
2335 if (!item
.maText
.isEmpty() && nTabWidth
< 100)
2338 nFullWidth
+= nTabWidth
;
2341 tools::Long nX
= nOffsetX
;
2342 tools::Long nY
= nOffsetY
;
2344 tools::Long nLineWidthAry
[100];
2345 nLineWidthAry
[0] = 0;
2347 for (auto & item
: mpTabCtrlData
->maItemList
)
2349 if (!item
.m_bVisible
)
2352 Size aSize
= ImplGetItemSize( &item
, nMaxWidth
);
2354 // set minimum tab size
2355 if( nFullWidth
< nMaxWidth
&& !item
.maText
.isEmpty() && aSize
.getWidth() < 100)
2356 aSize
.setWidth( 100 );
2358 if( !item
.maText
.isEmpty() && aSize
.getHeight() < 28 )
2359 aSize
.setHeight( 28 );
2361 tools::Rectangle
aNewRect( Point( nX
, nY
), aSize
);
2362 if ( mbSmallInvalidate
&& (item
.maRect
!= aNewRect
) )
2363 mbSmallInvalidate
= false;
2365 item
.maRect
= aNewRect
;
2367 item
.mbFullVisible
= true;
2369 nLineWidthAry
[0] += aSize
.Width();
2370 nX
+= aSize
.Width();
2373 // we always have only one line of tabs
2374 // tdf#127610 subtract width of shortcuts from width available for tab items
2375 lcl_AdjustSingleLineTabs(nMaxWidth
- nShortcutsWidth
, mpTabCtrlData
.get());
2377 // position the shortcutbox
2380 tools::Long nPosY
= (m_nHeaderHeight
- m_pShortcuts
->GetSizePixel().getHeight()) / 2;
2381 m_pShortcuts
->SetPosPixel(Point(0, nPosY
));
2384 tools::Long nPosY
= (m_nHeaderHeight
- m_pOpenMenu
->GetSizePixel().getHeight()) / 2;
2385 // position the menu
2386 m_pOpenMenu
->SetPosPixel(Point(nWidth
- nHamburgerWidth
, nPosY
));
2391 Size
NotebookbarTabControlBase::calculateRequisition() const
2393 return TabControl::ImplCalculateRequisition(m_nHeaderHeight
);
2396 Control
* NotebookbarTabControlBase::GetOpenMenu()
2401 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */