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>
47 class ImplTabItem final
52 VclPtr
<TabPage
> mpTabPage
;
54 OUString maFormatText
;
56 OUString maAccessibleName
;
57 OUString maAccessibleDescription
;
59 tools::Rectangle maRect
;
62 bool m_bEnabled
; ///< the tab / page is selectable
63 bool m_bVisible
; ///< the tab / page can be visible
66 ImplTabItem(sal_uInt16 nId
);
68 sal_uInt16
id() const { return m_nId
; }
71 ImplTabItem::ImplTabItem(sal_uInt16 nId
)
74 , mbFullVisible(false)
80 struct ImplTabCtrlData
82 std::unordered_map
< int, int > maLayoutPageIdToLine
;
83 std::unordered_map
< int, int > maLayoutLineToPageId
;
84 std::vector
< ImplTabItem
> maItemList
;
85 VclPtr
<ListBox
> mpListBox
;
88 // for the Tab positions
89 #define TAB_PAGERECT 0xFFFF
91 void TabControl::ImplInit( vcl::Window
* pParent
, WinBits nStyle
)
95 if ( !(nStyle
& WB_NOTABSTOP
) )
97 if ( !(nStyle
& WB_NOGROUP
) )
99 if ( !(nStyle
& WB_NODIALOGCONTROL
) )
100 nStyle
|= WB_DIALOGCONTROL
;
102 Control::ImplInit( pParent
, nStyle
, nullptr );
110 mbRestoreHelpId
= false;
111 mbSmallInvalidate
= false;
112 mpTabCtrlData
.reset(new ImplTabCtrlData
);
113 mpTabCtrlData
->mpListBox
= nullptr;
115 ImplInitSettings( true );
117 if( nStyle
& WB_DROPDOWN
)
119 mpTabCtrlData
->mpListBox
= VclPtr
<ListBox
>::Create( this, WB_DROPDOWN
);
120 mpTabCtrlData
->mpListBox
->SetPosSizePixel( Point( 0, 0 ), Size( 200, 20 ) );
121 mpTabCtrlData
->mpListBox
->SetSelectHdl( LINK( this, TabControl
, ImplListBoxSelectHdl
) );
122 mpTabCtrlData
->mpListBox
->Show();
125 // if the tabcontrol is drawn (ie filled) by a native widget, make sure all controls will have transparent background
126 // otherwise they will paint with a wrong background
127 if( IsNativeControlSupported(ControlType::TabPane
, ControlPart::Entire
) )
128 EnableChildTransparentMode();
130 if (pParent
&& pParent
->IsDialog())
131 pParent
->AddChildEventListener( LINK( this, TabControl
, ImplWindowEventListener
) );
134 const vcl::Font
& TabControl::GetCanonicalFont( const StyleSettings
& _rStyle
) const
136 return _rStyle
.GetTabFont();
139 const Color
& TabControl::GetCanonicalTextColor( const StyleSettings
& _rStyle
) const
141 return _rStyle
.GetTabTextColor();
144 void TabControl::ImplInitSettings( bool bBackground
)
146 Control::ImplInitSettings();
151 vcl::Window
* pParent
= GetParent();
152 if ( !IsControlBackground() &&
153 (pParent
->IsChildTransparentModeEnabled()
154 || IsNativeControlSupported(ControlType::TabPane
, ControlPart::Entire
)
155 || IsNativeControlSupported(ControlType::TabItem
, ControlPart::Entire
) ) )
158 // set transparent mode for NWF tabcontrols to have
159 // the background always cleared properly
160 EnableChildTransparentMode();
161 SetParentClipMode( ParentClipMode::NoClip
);
162 SetPaintTransparent( true );
164 ImplGetWindowImpl()->mbUseNativeFocus
= ImplGetSVData()->maNWFData
.mbNoFocusRects
;
168 EnableChildTransparentMode( false );
170 SetPaintTransparent( false );
172 if ( IsControlBackground() )
173 SetBackground( GetControlBackground() );
175 SetBackground( pParent
->GetBackground() );
179 void TabControl::ImplFreeLayoutData()
181 if( HasLayoutData() )
183 ImplClearLayoutData();
184 mpTabCtrlData
->maLayoutPageIdToLine
.clear();
185 mpTabCtrlData
->maLayoutLineToPageId
.clear();
189 TabControl::TabControl( vcl::Window
* pParent
, WinBits nStyle
) :
190 Control( WindowType::TABCONTROL
)
192 ImplInit( pParent
, nStyle
);
193 SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER
) ? "true" : "false") );
196 TabControl::~TabControl()
201 void TabControl::dispose()
203 Window
*pParent
= GetParent();
204 if (pParent
&& pParent
->IsDialog())
205 GetParent()->RemoveChildEventListener( LINK( this, TabControl
, ImplWindowEventListener
) );
207 ImplFreeLayoutData();
209 // delete TabCtrl data
211 mpTabCtrlData
->mpListBox
.disposeAndClear();
212 mpTabCtrlData
.reset();
216 ImplTabItem
* TabControl::ImplGetItem( sal_uInt16 nId
) const
218 for (auto & item
: mpTabCtrlData
->maItemList
)
220 if (item
.id() == nId
)
227 Size
TabControl::ImplGetItemSize( ImplTabItem
* pItem
, tools::Long nMaxWidth
)
229 pItem
->maFormatText
= pItem
->maText
;
230 Size
aSize( GetOutDev()->GetCtrlTextWidth( pItem
->maFormatText
), GetTextHeight() );
231 Size
aImageSize( 0, 0 );
232 if( !!pItem
->maTabImage
)
234 aImageSize
= pItem
->maTabImage
.GetSizePixel();
235 if( !pItem
->maFormatText
.isEmpty() )
236 aImageSize
.AdjustWidth(GetTextHeight()/4 );
238 aSize
.AdjustWidth(aImageSize
.Width() );
239 if( aImageSize
.Height() > aSize
.Height() )
240 aSize
.setHeight( aImageSize
.Height() );
242 aSize
.AdjustWidth(TAB_TABOFFSET_X
*2 );
243 aSize
.AdjustHeight(TAB_TABOFFSET_Y
*2 );
245 tools::Rectangle
aCtrlRegion( Point( 0, 0 ), aSize
);
246 tools::Rectangle aBoundingRgn
, aContentRgn
;
247 const TabitemValue
aControlValue(tools::Rectangle(TAB_TABOFFSET_X
, TAB_TABOFFSET_Y
,
248 aSize
.Width() - TAB_TABOFFSET_X
* 2,
249 aSize
.Height() - TAB_TABOFFSET_Y
* 2));
250 if(GetNativeControlRegion( ControlType::TabItem
, ControlPart::Entire
, aCtrlRegion
,
251 ControlState::ENABLED
, aControlValue
,
252 aBoundingRgn
, aContentRgn
) )
254 return aContentRgn
.GetSize();
257 // For languages with short names (e.g. Chinese), because the space is
258 // normally only one pixel per char
259 if ( pItem
->maFormatText
.getLength() < TAB_EXTRASPACE_X
)
260 aSize
.AdjustWidth(TAB_EXTRASPACE_X
-pItem
->maFormatText
.getLength() );
262 // shorten Text if needed
263 if ( aSize
.Width()+4 >= nMaxWidth
)
265 OUString
aAppendStr("...");
266 pItem
->maFormatText
+= aAppendStr
;
269 if (pItem
->maFormatText
.getLength() > aAppendStr
.getLength())
270 pItem
->maFormatText
= pItem
->maFormatText
.replaceAt( pItem
->maFormatText
.getLength()-aAppendStr
.getLength()-1, 1, u
"" );
271 aSize
.setWidth( GetOutDev()->GetCtrlTextWidth( pItem
->maFormatText
) );
272 aSize
.AdjustWidth(aImageSize
.Width() );
273 aSize
.AdjustWidth(TAB_TABOFFSET_X
*2 );
275 while ( (aSize
.Width()+4 >= nMaxWidth
) && (pItem
->maFormatText
.getLength() > aAppendStr
.getLength()) );
276 if ( aSize
.Width()+4 >= nMaxWidth
)
278 pItem
->maFormatText
= ".";
283 if( pItem
->maFormatText
.isEmpty() )
285 if( aSize
.Height() < aImageSize
.Height()+4 ) //leave space for focus rect
286 aSize
.setHeight( aImageSize
.Height()+4 );
292 // Feel free to move this to some more general place for reuse
293 // http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
294 // Mostly based on Alexey Frunze's nifty example at
295 // http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
296 namespace MinimumRaggednessWrap
298 static std::deque
<size_t> GetEndOfLineIndexes(const std::vector
<sal_Int32
>& rWidthsOf
, sal_Int32 nLineWidth
)
302 size_t nWidthsCount
= rWidthsOf
.size();
303 std::vector
<sal_Int32
> aCosts(nWidthsCount
* nWidthsCount
);
305 // cost function c(i, j) that computes the cost of a line consisting of
306 // the words Word[i] to Word[j]
307 for (size_t i
= 0; i
< nWidthsCount
; ++i
)
309 for (size_t j
= 0; j
< nWidthsCount
; ++j
)
313 sal_Int32 c
= nLineWidth
- (j
- i
);
314 for (size_t k
= i
; k
<= j
; ++k
)
316 c
= (c
>= 0) ? c
* c
: SAL_MAX_INT32
;
317 aCosts
[j
* nWidthsCount
+ i
] = c
;
321 aCosts
[j
* nWidthsCount
+ i
] = SAL_MAX_INT32
;
326 std::vector
<sal_Int32
> aFunction(nWidthsCount
);
327 std::vector
<sal_Int32
> aWrapPoints(nWidthsCount
);
329 // f(j) in aFunction[], collect wrap points in aWrapPoints[]
330 for (size_t j
= 0; j
< nWidthsCount
; ++j
)
332 aFunction
[j
] = aCosts
[j
* nWidthsCount
];
333 if (aFunction
[j
] == SAL_MAX_INT32
)
335 for (size_t k
= 0; k
< j
; ++k
)
338 if (aFunction
[k
] == SAL_MAX_INT32
|| aCosts
[j
* nWidthsCount
+ k
+ 1] == SAL_MAX_INT32
)
341 s
= aFunction
[k
] + aCosts
[j
* nWidthsCount
+ k
+ 1];
342 if (aFunction
[j
] > s
)
345 aWrapPoints
[j
] = k
+ 1;
351 std::deque
<size_t> aSolution
;
354 if (aFunction
[nWidthsCount
- 1] == SAL_MAX_INT32
)
358 size_t j
= nWidthsCount
- 1;
361 aSolution
.push_front(j
);
364 j
= aWrapPoints
[j
] - 1;
371 static void lcl_AdjustSingleLineTabs(tools::Long nMaxWidth
, ImplTabCtrlData
*pTabCtrlData
)
373 if (!ImplGetSVData()->maNWFData
.mbCenteredTabs
)
376 int nRightSpace
= nMaxWidth
; // space left on the right by the tabs
377 for (auto const& item
: pTabCtrlData
->maItemList
)
379 if (!item
.m_bVisible
)
381 nRightSpace
-= item
.maRect
.GetWidth();
385 for (auto& item
: pTabCtrlData
->maItemList
)
387 if (!item
.m_bVisible
)
389 item
.maRect
.AdjustLeft(nRightSpace
);
390 item
.maRect
.AdjustRight(nRightSpace
);
394 bool TabControl::ImplPlaceTabs( tools::Long nWidth
)
398 if ( mpTabCtrlData
->maItemList
.empty() )
401 tools::Long nMaxWidth
= nWidth
;
403 const tools::Long nOffsetX
= 2;
404 const tools::Long nOffsetY
= 2;
406 //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
407 //of ugly bare tabs on lines of their own
410 std::vector
<sal_Int32
> aWidths
;
411 for (auto & item
: mpTabCtrlData
->maItemList
)
413 if (!item
.m_bVisible
)
415 aWidths
.push_back(ImplGetItemSize(&item
, nMaxWidth
).Width());
418 //aBreakIndexes will contain the indexes of the last tab on each row
419 std::deque
<size_t> aBreakIndexes(MinimumRaggednessWrap::GetEndOfLineIndexes(aWidths
, nMaxWidth
- nOffsetX
- 2));
421 tools::Long nX
= nOffsetX
;
422 tools::Long nY
= nOffsetY
;
424 sal_uInt16 nLines
= 0;
425 sal_uInt16 nCurLine
= 0;
427 tools::Long nLineWidthAry
[100];
428 sal_uInt16 nLinePosAry
[101];
429 nLineWidthAry
[0] = 0;
434 for (auto & item
: mpTabCtrlData
->maItemList
)
436 if (!item
.m_bVisible
)
439 Size aSize
= ImplGetItemSize( &item
, nMaxWidth
);
441 bool bNewLine
= false;
442 if (!aBreakIndexes
.empty() && nIndex
> aBreakIndexes
.front())
444 aBreakIndexes
.pop_front();
448 if ( bNewLine
&& (nWidth
> 2+nOffsetX
) )
454 nY
+= aSize
.Height();
456 nLineWidthAry
[nLines
] = 0;
457 nLinePosAry
[nLines
] = nIndex
;
460 tools::Rectangle
aNewRect( Point( nX
, nY
), aSize
);
461 if ( mbSmallInvalidate
&& (item
.maRect
!= aNewRect
) )
462 mbSmallInvalidate
= false;
463 item
.maRect
= aNewRect
;
464 item
.mnLine
= nLines
;
465 item
.mbFullVisible
= true;
467 nLineWidthAry
[nLines
] += aSize
.Width();
470 if (item
.id() == mnCurPageId
)
476 if (nLines
) // two or more lines
478 tools::Long nLineHeightAry
[100];
480 for (const auto& item
: mpTabCtrlData
->maItemList
)
482 if (!item
.m_bVisible
)
484 nIH
= item
.maRect
.Bottom() - 1;
488 for ( sal_uInt16 i
= 0; i
< nLines
+1; i
++ )
491 nLineHeightAry
[i
] = nIH
*(nLines
-(nCurLine
-i
));
493 nLineHeightAry
[i
] = nIH
*(i
-nCurLine
-1);
496 nLinePosAry
[nLines
+1] = static_cast<sal_uInt16
>(mpTabCtrlData
->maItemList
.size());
499 tools::Long nModDX
= 0;
500 tools::Long nIDX
= 0;
505 for (auto & item
: mpTabCtrlData
->maItemList
)
507 if (!item
.m_bVisible
)
510 if ( i
== nLinePosAry
[n
] )
516 if( nLinePosAry
[n
+1]-i
> 0 )
518 nDX
= ( nWidth
- nOffsetX
- nLineWidthAry
[n
] ) / ( nLinePosAry
[n
+1] - i
);
519 nModDX
= ( nWidth
- nOffsetX
- nLineWidthAry
[n
] ) % ( nLinePosAry
[n
+1] - i
);
523 // FIXME: this is a case of tabctrl way too small
530 item
.maRect
.AdjustLeft(nIDX
);
531 item
.maRect
.AdjustRight(nIDX
+ nDX
);
532 item
.maRect
.SetTop( nLineHeightAry
[n
-1] );
533 item
.maRect
.SetBottom(nLineHeightAry
[n
-1] + nIH
- 1);
539 item
.maRect
.AdjustRight( 1 );
546 else // only one line
547 lcl_AdjustSingleLineTabs(nMaxWidth
, mpTabCtrlData
.get());
552 tools::Rectangle
TabControl::ImplGetTabRect( sal_uInt16 nItemPos
, tools::Long nWidth
, tools::Long nHeight
)
554 Size aWinSize
= Control::GetOutputSizePixel();
556 nWidth
= aWinSize
.Width();
558 nHeight
= aWinSize
.Height();
560 if ( mpTabCtrlData
->maItemList
.empty() )
562 tools::Long nW
= nWidth
-TAB_OFFSET
*2;
563 tools::Long nH
= nHeight
-TAB_OFFSET
*2;
564 return (nW
> 0 && nH
> 0)
565 ? tools::Rectangle(Point(TAB_OFFSET
, TAB_OFFSET
), Size(nW
, nH
))
566 : tools::Rectangle();
569 if ( nItemPos
== TAB_PAGERECT
)
573 nLastPos
= GetPagePos( mnCurPageId
);
577 tools::Rectangle aRect
= ImplGetTabRect( nLastPos
, nWidth
, nHeight
);
581 // with show-tabs of true (the usual) the page rect is from under the
582 // visible tab to the bottom of the TabControl, otherwise it extends
583 // from the top of the TabControl
584 tools::Long nTabBottom
= mbShowTabs
? aRect
.Bottom() : 0;
586 tools::Long nW
= nWidth
-TAB_OFFSET
*2;
587 tools::Long nH
= nHeight
- nTabBottom
- TAB_OFFSET
*2;
588 return (nW
> 0 && nH
> 0)
589 ? tools::Rectangle( Point( TAB_OFFSET
, nTabBottom
+ TAB_OFFSET
), Size( nW
, nH
) )
590 : tools::Rectangle();
593 ImplTabItem
* const pItem
= (nItemPos
< mpTabCtrlData
->maItemList
.size())
594 ? &mpTabCtrlData
->maItemList
[nItemPos
] : nullptr;
595 return ImplGetTabRect(pItem
, nWidth
, nHeight
);
598 tools::Rectangle
TabControl::ImplGetTabRect(const ImplTabItem
* pItem
, tools::Long nWidth
, tools::Long nHeight
)
600 if ((nWidth
<= 1) || (nHeight
<= 0) || !pItem
|| !pItem
->m_bVisible
)
601 return tools::Rectangle();
605 if ( mbFormat
|| (mnLastWidth
!= nWidth
) || (mnLastHeight
!= nHeight
) )
607 vcl::Font
aFont( GetFont() );
608 aFont
.SetTransparent( true );
611 bool bRet
= ImplPlaceTabs( nWidth
);
613 return tools::Rectangle();
615 mnLastWidth
= nWidth
;
616 mnLastHeight
= nHeight
;
620 return pItem
->maRect
;
623 void TabControl::ImplChangeTabPage( sal_uInt16 nId
, sal_uInt16 nOldId
)
625 ImplFreeLayoutData();
627 ImplTabItem
* pOldItem
= ImplGetItem( nOldId
);
628 ImplTabItem
* pItem
= ImplGetItem( nId
);
629 TabPage
* pOldPage
= pOldItem
? pOldItem
->mpTabPage
.get() : nullptr;
630 TabPage
* pPage
= pItem
? pItem
->mpTabPage
.get() : nullptr;
631 vcl::Window
* pCtrlParent
= GetParent();
633 if ( IsReallyVisible() && IsUpdateMode() )
635 sal_uInt16 nPos
= GetPagePos( nId
);
636 tools::Rectangle aRect
= ImplGetTabRect( nPos
);
638 if ( !pOldItem
|| !pItem
|| (pOldItem
->mnLine
!= pItem
->mnLine
) )
642 aRect
.SetRight( Control::GetOutputSizePixel().Width() );
646 aRect
.AdjustLeft( -3 );
647 aRect
.AdjustTop( -2 );
648 aRect
.AdjustRight(3 );
650 nPos
= GetPagePos( nOldId
);
651 aRect
= ImplGetTabRect( nPos
);
652 aRect
.AdjustLeft( -3 );
653 aRect
.AdjustTop( -2 );
654 aRect
.AdjustRight(3 );
659 if ( pOldPage
== pPage
)
662 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
);
666 if ( mbRestoreHelpId
)
667 pCtrlParent
->SetHelpId({});
672 if ( GetStyle() & WB_NOBORDER
)
674 tools::Rectangle
aRectNoTab(Point(0, 0), GetSizePixel());
675 pPage
->SetPosSizePixel( aRectNoTab
.TopLeft(), aRectNoTab
.GetSize() );
678 pPage
->SetPosSizePixel( aRect
.TopLeft(), aRect
.GetSize() );
680 // activate page here so the controls can be switched
681 // also set the help id of the parent window to that of the tab page
682 if ( GetHelpId().isEmpty() )
684 mbRestoreHelpId
= true;
685 pCtrlParent
->SetHelpId( pPage
->GetHelpId() );
690 if ( pOldPage
&& pOldPage
->HasChildPathFocus() )
692 vcl::Window
* pFirstChild
= pPage
->ImplGetDlgWindow( 0, GetDlgWindowType::First
);
694 pFirstChild
->ImplControlFocus( GetFocusFlags::Init
);
703 // Invalidate the same region that will be send to NWF
704 // to always allow for bitmap caching
705 // see Window::DrawNativeControl()
706 if( IsNativeControlSupported( ControlType::TabPane
, ControlPart::Entire
) )
708 aRect
.AdjustLeft( -(TAB_OFFSET
) );
709 aRect
.AdjustTop( -(TAB_OFFSET
) );
710 aRect
.AdjustRight(TAB_OFFSET
);
711 aRect
.AdjustBottom(TAB_OFFSET
);
717 bool TabControl::ImplPosCurTabPage()
719 // resize/position current TabPage
720 ImplTabItem
* pItem
= ImplGetItem( GetCurPageId() );
721 if ( pItem
&& pItem
->mpTabPage
)
723 if ( GetStyle() & WB_NOBORDER
)
725 tools::Rectangle
aRectNoTab(Point(0, 0), GetSizePixel());
726 pItem
->mpTabPage
->SetPosSizePixel( aRectNoTab
.TopLeft(), aRectNoTab
.GetSize() );
729 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
);
730 pItem
->mpTabPage
->SetPosSizePixel( aRect
.TopLeft(), aRect
.GetSize() );
737 void TabControl::ImplActivateTabPage( bool bNext
)
739 sal_uInt16 nCurPos
= GetPagePos( GetCurPageId() );
742 nCurPos
= (nCurPos
+ 1) % GetPageCount();
746 nCurPos
= GetPageCount()-1;
751 SelectTabPage( GetPageId( nCurPos
) );
754 void TabControl::ImplShowFocus()
756 if ( !GetPageCount() || mpTabCtrlData
->mpListBox
)
759 sal_uInt16 nCurPos
= GetPagePos( mnCurPageId
);
760 tools::Rectangle aRect
= ImplGetTabRect( nCurPos
);
761 const ImplTabItem
& rItem
= mpTabCtrlData
->maItemList
[ nCurPos
];
762 Size aTabSize
= aRect
.GetSize();
763 Size
aImageSize( 0, 0 );
764 tools::Long nTextHeight
= GetTextHeight();
765 tools::Long nTextWidth
= GetOutDev()->GetCtrlTextWidth( rItem
.maFormatText
);
768 if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::Mono
) )
773 if( !! rItem
.maTabImage
)
775 aImageSize
= rItem
.maTabImage
.GetSizePixel();
776 if( !rItem
.maFormatText
.isEmpty() )
777 aImageSize
.AdjustWidth(GetTextHeight()/4 );
780 if( !rItem
.maFormatText
.isEmpty() )
782 // show focus around text
783 aRect
.SetLeft( aRect
.Left()+aImageSize
.Width()+((aTabSize
.Width()-nTextWidth
-aImageSize
.Width())/2)-nOff
-1-1 );
784 aRect
.SetTop( aRect
.Top()+((aTabSize
.Height()-nTextHeight
)/2)-1-1 );
785 aRect
.SetRight( aRect
.Left()+nTextWidth
+2 );
786 aRect
.SetBottom( aRect
.Top()+nTextHeight
+2 );
790 // show focus around image
791 tools::Long nXPos
= aRect
.Left()+((aTabSize
.Width()-nTextWidth
-aImageSize
.Width())/2)-nOff
-1;
792 tools::Long nYPos
= aRect
.Top();
793 if( aImageSize
.Height() < aRect
.GetHeight() )
794 nYPos
+= (aRect
.GetHeight() - aImageSize
.Height())/2;
796 aRect
.SetLeft( nXPos
- 2 );
797 aRect
.SetTop( nYPos
- 2 );
798 aRect
.SetRight( aRect
.Left() + aImageSize
.Width() + 4 );
799 aRect
.SetBottom( aRect
.Top() + aImageSize
.Height() + 4 );
804 void TabControl::ImplDrawItem(vcl::RenderContext
& rRenderContext
, ImplTabItem
const * pItem
, const tools::Rectangle
& rCurRect
,
805 bool bFirstInGroup
, bool bLastInGroup
)
807 if (!pItem
->m_bVisible
|| pItem
->maRect
.IsEmpty())
810 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
811 tools::Rectangle aRect
= pItem
->maRect
;
812 tools::Long nLeftBottom
= aRect
.Bottom();
813 tools::Long nRightBottom
= aRect
.Bottom();
814 bool bLeftBorder
= true;
815 bool bRightBorder
= true;
817 bool bNativeOK
= false;
819 sal_uInt16 nOff2
= 0;
820 sal_uInt16 nOff3
= 0;
822 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
827 // if this is the active Page, we have to draw a little more
828 if (pItem
->id() == mnCurPageId
)
831 if (!ImplGetSVData()->maNWFData
.mbNoActiveTabTextRaise
)
836 Point aLeftTestPos
= aRect
.BottomLeft();
837 Point aRightTestPos
= aRect
.BottomRight();
838 if (aLeftTestPos
.Y() == rCurRect
.Bottom())
840 aLeftTestPos
.AdjustX( -2 );
841 if (rCurRect
.Contains(aLeftTestPos
))
843 aRightTestPos
.AdjustX(2 );
844 if (rCurRect
.Contains(aRightTestPos
))
845 bRightBorder
= false;
849 if (rCurRect
.Contains(aLeftTestPos
))
851 if (rCurRect
.Contains(aRightTestPos
))
856 ControlState nState
= ControlState::NONE
;
858 if (pItem
->id() == mnCurPageId
)
860 nState
|= ControlState::SELECTED
;
861 // only the selected item can be focused
863 nState
|= ControlState::FOCUSED
;
866 nState
|= ControlState::ENABLED
;
867 if (IsMouseOver() && pItem
->maRect
.Contains(GetPointerPosPixel()))
869 nState
|= ControlState::ROLLOVER
;
870 for (auto const& item
: mpTabCtrlData
->maItemList
)
871 if ((&item
!= pItem
) && item
.m_bVisible
&& item
.maRect
.Contains(GetPointerPosPixel()))
873 nState
&= ~ControlState::ROLLOVER
; // avoid multiple highlighted tabs
876 assert(nState
& ControlState::ROLLOVER
);
879 bNativeOK
= rRenderContext
.IsNativeControlSupported(ControlType::TabItem
, ControlPart::Entire
);
882 TabitemValue
tiValue(tools::Rectangle(pItem
->maRect
.Left() + TAB_TABOFFSET_X
,
883 pItem
->maRect
.Top() + TAB_TABOFFSET_Y
,
884 pItem
->maRect
.Right() - TAB_TABOFFSET_X
,
885 pItem
->maRect
.Bottom() - TAB_TABOFFSET_Y
));
886 if (pItem
->maRect
.Left() < 5)
887 tiValue
.mnAlignment
|= TabitemFlags::LeftAligned
;
888 if (pItem
->maRect
.Right() > mnLastWidth
- 5)
889 tiValue
.mnAlignment
|= TabitemFlags::RightAligned
;
891 tiValue
.mnAlignment
|= TabitemFlags::FirstInGroup
;
893 tiValue
.mnAlignment
|= TabitemFlags::LastInGroup
;
895 tools::Rectangle
aCtrlRegion( pItem
->maRect
);
896 aCtrlRegion
.AdjustBottom(TabPaneValue::m_nOverlap
);
897 bNativeOK
= rRenderContext
.DrawNativeControl(ControlType::TabItem
, ControlPart::Entire
,
898 aCtrlRegion
, nState
, tiValue
, OUString() );
903 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
905 rRenderContext
.SetLineColor(rStyleSettings
.GetLightColor());
906 rRenderContext
.DrawPixel(Point(aRect
.Left() + 1 - nOff2
, aRect
.Top() + 1 - nOff2
)); // diagonally indented top-left pixel
909 rRenderContext
.DrawLine(Point(aRect
.Left() - nOff2
, aRect
.Top() + 2 - nOff2
),
910 Point(aRect
.Left() - nOff2
, nLeftBottom
- 1));
912 rRenderContext
.DrawLine(Point(aRect
.Left() + 2 - nOff2
, aRect
.Top() - nOff2
), // top line starting 2px from left border
913 Point(aRect
.Right() + nOff2
- 3, aRect
.Top() - nOff2
)); // ending 3px from right border
917 rRenderContext
.SetLineColor(rStyleSettings
.GetShadowColor());
918 rRenderContext
.DrawLine(Point(aRect
.Right() + nOff2
- 2, aRect
.Top() + 1 - nOff2
),
919 Point(aRect
.Right() + nOff2
- 2, nRightBottom
- 1));
921 rRenderContext
.SetLineColor(rStyleSettings
.GetDarkShadowColor());
922 rRenderContext
.DrawLine(Point(aRect
.Right() + nOff2
- 1, aRect
.Top() + 3 - nOff2
),
923 Point(aRect
.Right() + nOff2
- 1, nRightBottom
- 1));
928 rRenderContext
.SetLineColor(COL_BLACK
);
929 rRenderContext
.DrawPixel(Point(aRect
.Left() + 1 - nOff2
, aRect
.Top() + 1 - nOff2
));
930 rRenderContext
.DrawPixel(Point(aRect
.Right() + nOff2
- 2, aRect
.Top() + 1 - nOff2
));
933 rRenderContext
.DrawLine(Point(aRect
.Left() - nOff2
, aRect
.Top() + 2 - nOff2
),
934 Point(aRect
.Left() - nOff2
, nLeftBottom
- 1));
936 rRenderContext
.DrawLine(Point(aRect
.Left() + 2 - nOff2
, aRect
.Top() - nOff2
),
937 Point(aRect
.Right() - 3, aRect
.Top() - nOff2
));
940 rRenderContext
.DrawLine(Point(aRect
.Right() + nOff2
- 1, aRect
.Top() + 2 - nOff2
),
941 Point(aRect
.Right() + nOff2
- 1, nRightBottom
- 1));
946 // set font accordingly, current item is painted bold
947 // we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
948 vcl::Font
aFont(rRenderContext
.GetFont());
949 aFont
.SetTransparent(true);
950 rRenderContext
.SetFont(aFont
);
952 Size aTabSize
= aRect
.GetSize();
953 Size
aImageSize(0, 0);
954 tools::Long nTextHeight
= rRenderContext
.GetTextHeight();
955 tools::Long nTextWidth
= rRenderContext
.GetCtrlTextWidth(pItem
->maFormatText
);
956 if (!!pItem
->maTabImage
)
958 aImageSize
= pItem
->maTabImage
.GetSizePixel();
959 if (!pItem
->maFormatText
.isEmpty())
960 aImageSize
.AdjustWidth(GetTextHeight() / 4 );
962 tools::Long nXPos
= aRect
.Left() + ((aTabSize
.Width() - nTextWidth
- aImageSize
.Width()) / 2) - nOff
- nOff3
;
963 tools::Long nYPos
= aRect
.Top() + ((aTabSize
.Height() - nTextHeight
) / 2) - nOff3
;
964 if (!pItem
->maFormatText
.isEmpty())
966 DrawTextFlags nStyle
= DrawTextFlags::Mnemonic
;
967 if (!pItem
->m_bEnabled
)
968 nStyle
|= DrawTextFlags::Disable
;
970 Color
aColor(rStyleSettings
.GetTabTextColor());
971 if (nState
& ControlState::SELECTED
)
972 aColor
= rStyleSettings
.GetTabHighlightTextColor();
973 else if (nState
& ControlState::ROLLOVER
)
974 aColor
= rStyleSettings
.GetTabRolloverTextColor();
976 Color
aOldColor(rRenderContext
.GetTextColor());
977 rRenderContext
.SetTextColor(aColor
);
979 const tools::Rectangle
aOutRect(nXPos
+ aImageSize
.Width(), nYPos
,
980 nXPos
+ aImageSize
.Width() + nTextWidth
, nYPos
+ nTextHeight
);
981 DrawControlText(rRenderContext
, aOutRect
, pItem
->maFormatText
, nStyle
,
984 rRenderContext
.SetTextColor(aOldColor
);
987 if (!!pItem
->maTabImage
)
989 Point
aImgTL( nXPos
, aRect
.Top() );
990 if (aImageSize
.Height() < aRect
.GetHeight())
991 aImgTL
.AdjustY((aRect
.GetHeight() - aImageSize
.Height()) / 2 );
992 rRenderContext
.DrawImage(aImgTL
, pItem
->maTabImage
, pItem
->m_bEnabled
? DrawImageFlags::NONE
: DrawImageFlags::Disable
);
996 bool TabControl::ImplHandleKeyEvent( const KeyEvent
& rKeyEvent
)
1000 if ( GetPageCount() > 1 )
1002 vcl::KeyCode aKeyCode
= rKeyEvent
.GetKeyCode();
1003 sal_uInt16 nKeyCode
= aKeyCode
.GetCode();
1005 if ( aKeyCode
.IsMod1() )
1007 if ( aKeyCode
.IsShift() || (nKeyCode
== KEY_PAGEUP
) )
1009 if ( (nKeyCode
== KEY_TAB
) || (nKeyCode
== KEY_PAGEUP
) )
1011 ImplActivateTabPage( false );
1017 if ( (nKeyCode
== KEY_TAB
) || (nKeyCode
== KEY_PAGEDOWN
) )
1019 ImplActivateTabPage( true );
1029 IMPL_LINK_NOARG(TabControl
, ImplListBoxSelectHdl
, ListBox
&, void)
1031 SelectTabPage( GetPageId( mpTabCtrlData
->mpListBox
->GetSelectedEntryPos() ) );
1034 IMPL_LINK( TabControl
, ImplWindowEventListener
, VclWindowEvent
&, rEvent
, void )
1036 if ( rEvent
.GetId() == VclEventId::WindowKeyInput
)
1038 // Do not handle events from TabControl or its children, which is done in Notify(), where the events can be consumed.
1039 if ( !IsWindowOrChild( rEvent
.GetWindow() ) )
1041 KeyEvent
* pKeyEvent
= static_cast< KeyEvent
* >(rEvent
.GetData());
1042 ImplHandleKeyEvent( *pKeyEvent
);
1047 void TabControl::MouseButtonDown( const MouseEvent
& rMEvt
)
1049 if (mpTabCtrlData
->mpListBox
|| !rMEvt
.IsLeft())
1052 ImplTabItem
*pItem
= ImplGetItem(rMEvt
.GetPosPixel());
1053 if (pItem
&& pItem
->m_bEnabled
)
1054 SelectTabPage(pItem
->id());
1057 void TabControl::KeyInput( const KeyEvent
& rKEvt
)
1059 if( mpTabCtrlData
->mpListBox
)
1060 mpTabCtrlData
->mpListBox
->KeyInput( rKEvt
);
1061 else if ( GetPageCount() > 1 )
1063 vcl::KeyCode aKeyCode
= rKEvt
.GetKeyCode();
1064 sal_uInt16 nKeyCode
= aKeyCode
.GetCode();
1066 if ( (nKeyCode
== KEY_LEFT
) || (nKeyCode
== KEY_RIGHT
) )
1068 bool bNext
= (nKeyCode
== KEY_RIGHT
);
1069 ImplActivateTabPage( bNext
);
1073 Control::KeyInput( rKEvt
);
1076 static bool lcl_canPaint(const vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& rDrawRect
,
1077 const tools::Rectangle
& rItemRect
)
1079 vcl::Region
aClipRgn(rRenderContext
.GetActiveClipRegion());
1080 aClipRgn
.Intersect(rItemRect
);
1081 if (!rDrawRect
.IsEmpty())
1082 aClipRgn
.Intersect(rDrawRect
);
1083 return !aClipRgn
.IsEmpty();
1086 void TabControl::Paint( vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& rRect
)
1088 if (GetStyle() & WB_NOBORDER
)
1091 Control::Paint(rRenderContext
, rRect
);
1095 // reformat if needed
1096 tools::Rectangle aRect
= ImplGetTabRect(TAB_PAGERECT
);
1098 // find current item
1099 ImplTabItem
* pCurItem
= nullptr;
1100 for (auto & item
: mpTabCtrlData
->maItemList
)
1102 if (item
.id() == mnCurPageId
)
1109 // Draw the TabPage border
1110 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
1111 tools::Rectangle aCurRect
;
1112 aRect
.AdjustLeft( -(TAB_OFFSET
) );
1113 aRect
.AdjustTop( -(TAB_OFFSET
) );
1114 aRect
.AdjustRight(TAB_OFFSET
);
1115 aRect
.AdjustBottom(TAB_OFFSET
);
1117 // if we have an invisible tabpage or no tabpage at all the tabpage rect should be
1118 // increased to avoid round corners that might be drawn by a theme
1119 // in this case we're only interested in the top border of the tabpage because the tabitems are used
1120 // standalone (eg impress)
1121 bool bNoTabPage
= false;
1122 TabPage
* pCurPage
= pCurItem
? pCurItem
->mpTabPage
.get() : nullptr;
1123 if (!pCurPage
|| !pCurPage
->IsVisible())
1126 aRect
.AdjustLeft( -10 );
1127 aRect
.AdjustRight(10 );
1130 if (rRenderContext
.IsNativeControlSupported(ControlType::TabPane
, ControlPart::Entire
))
1132 const bool bPaneWithHeader
= mbShowTabs
&& rRenderContext
.IsNativeControlSupported(ControlType::TabPane
, ControlPart::TabPaneWithHeader
);
1133 tools::Rectangle
aHeaderRect(aRect
.Left(), 0, aRect
.Right(), aRect
.Top());
1135 if (mpTabCtrlData
->maItemList
.size())
1137 tools::Long nLeft
= LONG_MAX
;
1138 tools::Long nRight
= 0;
1139 for (const auto &item
: mpTabCtrlData
->maItemList
)
1141 if (!item
.m_bVisible
)
1143 nRight
= std::max(nRight
, item
.maRect
.Right());
1144 nLeft
= std::min(nLeft
, item
.maRect
.Left());
1146 aHeaderRect
.SetLeft(nLeft
);
1147 aHeaderRect
.SetRight(nRight
);
1150 if (bPaneWithHeader
)
1153 const TabPaneValue
aTabPaneValue(aHeaderRect
, pCurItem
? pCurItem
->maRect
: tools::Rectangle());
1155 ControlState nState
= ControlState::ENABLED
;
1157 nState
&= ~ControlState::ENABLED
;
1159 nState
|= ControlState::FOCUSED
;
1161 if (lcl_canPaint(rRenderContext
, rRect
, aRect
))
1162 rRenderContext
.DrawNativeControl(ControlType::TabPane
, ControlPart::Entire
,
1163 aRect
, nState
, aTabPaneValue
, OUString());
1165 if (!bPaneWithHeader
&& rRenderContext
.IsNativeControlSupported(ControlType::TabHeader
, ControlPart::Entire
)
1166 && lcl_canPaint(rRenderContext
, rRect
, aHeaderRect
))
1167 rRenderContext
.DrawNativeControl(ControlType::TabHeader
, ControlPart::Entire
,
1168 aHeaderRect
, nState
, aTabPaneValue
, OUString());
1172 tools::Long nTopOff
= 1;
1173 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
1174 rRenderContext
.SetLineColor(rStyleSettings
.GetLightColor());
1176 rRenderContext
.SetLineColor(COL_BLACK
);
1177 if (mbShowTabs
&& pCurItem
&& !pCurItem
->maRect
.IsEmpty())
1179 aCurRect
= pCurItem
->maRect
;
1180 rRenderContext
.DrawLine(aRect
.TopLeft(), Point(aCurRect
.Left() - 2, aRect
.Top()));
1181 if (aCurRect
.Right() + 1 < aRect
.Right())
1183 rRenderContext
.DrawLine(Point(aCurRect
.Right(), aRect
.Top()), aRect
.TopRight());
1191 rRenderContext
.DrawLine(aRect
.TopLeft(), aRect
.TopRight());
1193 rRenderContext
.DrawLine(aRect
.TopLeft(), aRect
.BottomLeft());
1195 if (!(rStyleSettings
.GetOptions() & StyleSettingsOptions::Mono
))
1197 // if we have not tab page the bottom line of the tab page
1198 // directly touches the tab items, so choose a color that fits seamlessly
1200 rRenderContext
.SetLineColor(rStyleSettings
.GetDialogColor());
1202 rRenderContext
.SetLineColor(rStyleSettings
.GetShadowColor());
1203 rRenderContext
.DrawLine(Point(1, aRect
.Bottom() - 1), Point(aRect
.Right() - 1, aRect
.Bottom() - 1));
1204 rRenderContext
.DrawLine(Point(aRect
.Right() - 1, aRect
.Top() + nTopOff
), Point(aRect
.Right() - 1, aRect
.Bottom() - 1));
1206 rRenderContext
.SetLineColor(rStyleSettings
.GetDialogColor());
1208 rRenderContext
.SetLineColor(rStyleSettings
.GetDarkShadowColor());
1209 rRenderContext
.DrawLine(Point(0, aRect
.Bottom()), Point(aRect
.Right(), aRect
.Bottom()));
1210 rRenderContext
.DrawLine(Point(aRect
.Right(), aRect
.Top() + nTopOff
), Point(aRect
.Right(), aRect
.Bottom()));
1214 rRenderContext
.DrawLine(aRect
.TopRight(), aRect
.BottomRight());
1215 rRenderContext
.DrawLine(aRect
.BottomLeft(), aRect
.BottomRight());
1219 if (mbShowTabs
&& !mpTabCtrlData
->maItemList
.empty() && mpTabCtrlData
->mpListBox
== nullptr)
1221 // Some native toolkits (GTK+) draw tabs right-to-left, with an
1222 // overlap between adjacent tabs
1223 bool bDrawTabsRTL
= rRenderContext
.IsNativeControlSupported(ControlType::TabItem
, ControlPart::TabsDrawRtl
);
1224 ImplTabItem
* pFirstTab
= nullptr;
1225 ImplTabItem
* pLastTab
= nullptr;
1228 // Even though there is a tab overlap with GTK+, the first tab is not
1229 // overlapped on the left side. Other toolkits ignore this option.
1232 pFirstTab
= mpTabCtrlData
->maItemList
.data();
1233 pLastTab
= pFirstTab
+ mpTabCtrlData
->maItemList
.size() - 1;
1234 idx
= mpTabCtrlData
->maItemList
.size() - 1;
1238 pLastTab
= mpTabCtrlData
->maItemList
.data();
1239 pFirstTab
= pLastTab
+ mpTabCtrlData
->maItemList
.size() - 1;
1243 while (idx
< mpTabCtrlData
->maItemList
.size())
1245 ImplTabItem
* pItem
= &mpTabCtrlData
->maItemList
[idx
];
1247 if (pItem
!= pCurItem
&& pItem
->m_bVisible
&& lcl_canPaint(rRenderContext
, rRect
, pItem
->maRect
))
1248 ImplDrawItem(rRenderContext
, pItem
, aCurRect
, pItem
== pFirstTab
, pItem
== pLastTab
);
1256 if (pCurItem
&& lcl_canPaint(rRenderContext
, rRect
, pCurItem
->maRect
))
1257 ImplDrawItem(rRenderContext
, pCurItem
, aCurRect
, pCurItem
== pFirstTab
, pCurItem
== pLastTab
);
1263 mbSmallInvalidate
= true;
1266 void TabControl::setAllocation(const Size
&rAllocation
)
1268 ImplFreeLayoutData();
1270 if ( !IsReallyShown() )
1273 if( mpTabCtrlData
->mpListBox
)
1275 // get the listbox' preferred size
1276 Size
aTabCtrlSize( GetSizePixel() );
1277 tools::Long nPrefWidth
= mpTabCtrlData
->mpListBox
->get_preferred_size().Width();
1278 if( nPrefWidth
> aTabCtrlSize
.Width() )
1279 nPrefWidth
= aTabCtrlSize
.Width();
1280 Size
aNewSize( nPrefWidth
, LogicToPixel( Size( 12, 12 ), MapMode( MapUnit::MapAppFont
) ).Height() );
1281 Point
aNewPos( (aTabCtrlSize
.Width() - nPrefWidth
) / 2, 0 );
1282 mpTabCtrlData
->mpListBox
->SetPosSizePixel( aNewPos
, aNewSize
);
1287 // resize/position active TabPage
1288 bool bTabPage
= ImplPosCurTabPage();
1290 // check what needs to be invalidated
1291 Size aNewSize
= rAllocation
;
1292 tools::Long nNewWidth
= aNewSize
.Width();
1293 for (auto const& item
: mpTabCtrlData
->maItemList
)
1295 if (!item
.m_bVisible
)
1297 if (!item
.mbFullVisible
|| (item
.maRect
.Right()-2 >= nNewWidth
))
1299 mbSmallInvalidate
= false;
1304 if ( mbSmallInvalidate
)
1306 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
);
1307 aRect
.AdjustLeft( -(TAB_OFFSET
+TAB_BORDER_LEFT
) );
1308 aRect
.AdjustTop( -(TAB_OFFSET
+TAB_BORDER_TOP
) );
1309 aRect
.AdjustRight(TAB_OFFSET
+TAB_BORDER_RIGHT
);
1310 aRect
.AdjustBottom(TAB_OFFSET
+TAB_BORDER_BOTTOM
);
1312 Invalidate( aRect
, InvalidateFlags::NoChildren
);
1314 Invalidate( aRect
);
1320 Invalidate( InvalidateFlags::NoChildren
);
1325 mbLayoutDirty
= false;
1328 void TabControl::SetPosSizePixel(const Point
& rNewPos
, const Size
& rNewSize
)
1330 Window::SetPosSizePixel(rNewPos
, rNewSize
);
1331 //if size changed, TabControl::Resize got called already
1333 setAllocation(rNewSize
);
1336 void TabControl::SetSizePixel(const Size
& rNewSize
)
1338 Window::SetSizePixel(rNewSize
);
1339 //if size changed, TabControl::Resize got called already
1341 setAllocation(rNewSize
);
1344 void TabControl::SetPosPixel(const Point
& rPos
)
1346 Window::SetPosPixel(rPos
);
1348 setAllocation(GetOutputSizePixel());
1351 void TabControl::Resize()
1353 setAllocation(Control::GetOutputSizePixel());
1356 void TabControl::GetFocus()
1358 if( ! mpTabCtrlData
->mpListBox
)
1363 SetInputContext( InputContext( GetFont() ) );
1367 // no tabs, focus first thing in current page
1368 ImplTabItem
* pItem
= ImplGetItem(GetCurPageId());
1369 if (pItem
&& pItem
->mpTabPage
)
1371 vcl::Window
* pFirstChild
= pItem
->mpTabPage
->ImplGetDlgWindow(0, GetDlgWindowType::First
);
1373 pFirstChild
->ImplControlFocus(GetFocusFlags::Init
);
1379 if( mpTabCtrlData
->mpListBox
->IsReallyVisible() )
1380 mpTabCtrlData
->mpListBox
->GrabFocus();
1383 Control::GetFocus();
1386 void TabControl::LoseFocus()
1388 if( mpTabCtrlData
&& ! mpTabCtrlData
->mpListBox
)
1390 Control::LoseFocus();
1393 void TabControl::RequestHelp( const HelpEvent
& rHEvt
)
1395 sal_uInt16 nItemId
= rHEvt
.KeyboardActivated() ? mnCurPageId
: GetPageId( ScreenToOutputPixel( rHEvt
.GetMousePosPixel() ) );
1399 if ( rHEvt
.GetMode() & HelpEventMode::BALLOON
)
1401 OUString aStr
= GetHelpText( nItemId
);
1402 if ( !aStr
.isEmpty() )
1404 tools::Rectangle aItemRect
= ImplGetTabRect( GetPagePos( nItemId
) );
1405 Point aPt
= OutputToScreenPixel( aItemRect
.TopLeft() );
1406 aItemRect
.SetLeft( aPt
.X() );
1407 aItemRect
.SetTop( aPt
.Y() );
1408 aPt
= OutputToScreenPixel( aItemRect
.BottomRight() );
1409 aItemRect
.SetRight( aPt
.X() );
1410 aItemRect
.SetBottom( aPt
.Y() );
1411 Help::ShowBalloon( this, aItemRect
.Center(), aItemRect
, aStr
);
1416 // for Quick or Ballon Help, we show the text, if it is cut
1417 if ( rHEvt
.GetMode() & (HelpEventMode::QUICK
| HelpEventMode::BALLOON
) )
1419 ImplTabItem
* pItem
= ImplGetItem( nItemId
);
1420 const OUString
& rStr
= pItem
->maText
;
1421 if ( rStr
!= pItem
->maFormatText
)
1423 tools::Rectangle aItemRect
= ImplGetTabRect( GetPagePos( nItemId
) );
1424 Point aPt
= OutputToScreenPixel( aItemRect
.TopLeft() );
1425 aItemRect
.SetLeft( aPt
.X() );
1426 aItemRect
.SetTop( aPt
.Y() );
1427 aPt
= OutputToScreenPixel( aItemRect
.BottomRight() );
1428 aItemRect
.SetRight( aPt
.X() );
1429 aItemRect
.SetBottom( aPt
.Y() );
1430 if ( !rStr
.isEmpty() )
1432 if ( rHEvt
.GetMode() & HelpEventMode::BALLOON
)
1433 Help::ShowBalloon( this, aItemRect
.Center(), aItemRect
, rStr
);
1435 Help::ShowQuickHelp( this, aItemRect
, rStr
);
1441 if ( rHEvt
.GetMode() & HelpEventMode::QUICK
)
1443 ImplTabItem
* pItem
= ImplGetItem( nItemId
);
1444 const OUString
& rHelpText
= pItem
->maHelpText
;
1445 if (!rHelpText
.isEmpty())
1447 tools::Rectangle aItemRect
= ImplGetTabRect( GetPagePos( nItemId
) );
1448 Point aPt
= OutputToScreenPixel( aItemRect
.TopLeft() );
1449 aItemRect
.SetLeft( aPt
.X() );
1450 aItemRect
.SetTop( aPt
.Y() );
1451 aPt
= OutputToScreenPixel( aItemRect
.BottomRight() );
1452 aItemRect
.SetRight( aPt
.X() );
1453 aItemRect
.SetBottom( aPt
.Y() );
1454 Help::ShowQuickHelp( this, aItemRect
, rHelpText
);
1460 Control::RequestHelp( rHEvt
);
1463 void TabControl::Command( const CommandEvent
& rCEvt
)
1465 if( (mpTabCtrlData
->mpListBox
== nullptr) && (rCEvt
.GetCommand() == CommandEventId::ContextMenu
) && (GetPageCount() > 1) )
1469 if ( rCEvt
.IsMouseEvent() )
1471 aMenuPos
= rCEvt
.GetMousePosPixel();
1472 bMenu
= GetPageId( aMenuPos
) != 0;
1476 aMenuPos
= ImplGetTabRect( GetPagePos( mnCurPageId
) ).Center();
1482 ScopedVclPtrInstance
<PopupMenu
> aMenu
;
1483 for (auto const& item
: mpTabCtrlData
->maItemList
)
1485 aMenu
->InsertItem(item
.id(), item
.maText
, MenuItemBits::CHECKABLE
| MenuItemBits::RADIOCHECK
);
1486 if (item
.id() == mnCurPageId
)
1487 aMenu
->CheckItem(item
.id());
1488 aMenu
->SetHelpId(item
.id(), {});
1491 sal_uInt16 nId
= aMenu
->Execute( this, aMenuPos
);
1492 if ( nId
&& (nId
!= mnCurPageId
) )
1493 SelectTabPage( nId
);
1498 Control::Command( rCEvt
);
1501 void TabControl::StateChanged( StateChangedType nType
)
1503 Control::StateChanged( nType
);
1505 if ( nType
== StateChangedType::InitShow
)
1507 ImplPosCurTabPage();
1508 if( mpTabCtrlData
->mpListBox
)
1511 else if ( nType
== StateChangedType::UpdateMode
)
1513 if ( IsUpdateMode() )
1516 else if ( (nType
== StateChangedType::Zoom
) ||
1517 (nType
== StateChangedType::ControlFont
) )
1519 ImplInitSettings( false );
1522 else if ( nType
== StateChangedType::ControlForeground
)
1524 ImplInitSettings( false );
1527 else if ( nType
== StateChangedType::ControlBackground
)
1529 ImplInitSettings( true );
1534 void TabControl::DataChanged( const DataChangedEvent
& rDCEvt
)
1536 Control::DataChanged( rDCEvt
);
1538 if ( (rDCEvt
.GetType() == DataChangedEventType::FONTS
) ||
1539 (rDCEvt
.GetType() == DataChangedEventType::FONTSUBSTITUTION
) ||
1540 ((rDCEvt
.GetType() == DataChangedEventType::SETTINGS
) &&
1541 (rDCEvt
.GetFlags() & AllSettingsFlags::STYLE
)) )
1543 ImplInitSettings( true );
1548 ImplTabItem
* TabControl::ImplGetItem(const Point
& rPt
) const
1550 ImplTabItem
* pFoundItem
= nullptr;
1552 for (auto & item
: mpTabCtrlData
->maItemList
)
1554 if (item
.m_bVisible
&& item
.maRect
.Contains(rPt
))
1561 // assure that only one tab is highlighted at a time
1562 assert(nFound
<= 1);
1563 return nFound
== 1 ? pFoundItem
: nullptr;
1566 bool TabControl::PreNotify( NotifyEvent
& rNEvt
)
1568 if( rNEvt
.GetType() == NotifyEventType::MOUSEMOVE
)
1570 const MouseEvent
* pMouseEvt
= rNEvt
.GetMouseEvent();
1571 if( pMouseEvt
&& !pMouseEvt
->GetButtons() && !pMouseEvt
->IsSynthetic() && !pMouseEvt
->IsModifierChanged() )
1573 // trigger redraw if mouse over state has changed
1574 if( IsNativeControlSupported(ControlType::TabItem
, ControlPart::Entire
) )
1576 ImplTabItem
*pItem
= ImplGetItem(GetPointerPosPixel());
1577 ImplTabItem
*pLastItem
= ImplGetItem(GetLastPointerPosPixel());
1578 if ((pItem
!= pLastItem
) || pMouseEvt
->IsLeaveWindow() || pMouseEvt
->IsEnterWindow())
1580 vcl::Region aClipRgn
;
1583 // allow for slightly bigger tabitems
1585 // TODO: query for the correct sizes
1586 tools::Rectangle
aRect(pLastItem
->maRect
);
1587 aRect
.AdjustLeft( -2 );
1588 aRect
.AdjustRight(2 );
1589 aRect
.AdjustTop( -3 );
1590 aClipRgn
.Union( aRect
);
1595 // allow for slightly bigger tabitems
1597 // TODO: query for the correct sizes
1598 tools::Rectangle
aRect(pItem
->maRect
);
1599 aRect
.AdjustLeft( -2 );
1600 aRect
.AdjustRight(2 );
1601 aRect
.AdjustTop( -3 );
1602 aClipRgn
.Union( aRect
);
1605 if( !aClipRgn
.IsEmpty() )
1606 Invalidate( aClipRgn
);
1612 return Control::PreNotify(rNEvt
);
1615 bool TabControl::EventNotify( NotifyEvent
& rNEvt
)
1619 if ( rNEvt
.GetType() == NotifyEventType::KEYINPUT
)
1620 bRet
= ImplHandleKeyEvent( *rNEvt
.GetKeyEvent() );
1622 return bRet
|| Control::EventNotify( rNEvt
);
1625 void TabControl::ActivatePage()
1627 maActivateHdl
.Call( this );
1630 bool TabControl::DeactivatePage()
1632 return !maDeactivateHdl
.IsSet() || maDeactivateHdl
.Call( this );
1635 void TabControl::SetTabPageSizePixel( const Size
& rSize
)
1637 ImplFreeLayoutData();
1639 Size
aNewSize( rSize
);
1640 aNewSize
.AdjustWidth(TAB_OFFSET
*2 );
1641 tools::Rectangle aRect
= ImplGetTabRect( TAB_PAGERECT
,
1642 aNewSize
.Width(), aNewSize
.Height() );
1643 aNewSize
.AdjustHeight(aRect
.Top()+TAB_OFFSET
);
1644 Window::SetOutputSizePixel( aNewSize
);
1647 void TabControl::InsertPage( sal_uInt16 nPageId
, const OUString
& rText
,
1650 SAL_WARN_IF( !nPageId
, "vcl", "TabControl::InsertPage(): PageId == 0" );
1651 SAL_WARN_IF( GetPagePos( nPageId
) != TAB_PAGE_NOTFOUND
, "vcl",
1652 "TabControl::InsertPage(): PageId already exists" );
1654 // insert new page item
1655 ImplTabItem
* pItem
= nullptr;
1656 if( nPos
== TAB_APPEND
|| size_t(nPos
) >= mpTabCtrlData
->maItemList
.size() )
1658 mpTabCtrlData
->maItemList
.emplace_back(nPageId
);
1659 pItem
= &mpTabCtrlData
->maItemList
.back();
1660 if( mpTabCtrlData
->mpListBox
)
1661 mpTabCtrlData
->mpListBox
->InsertEntry( rText
);
1665 std::vector
< ImplTabItem
>::iterator new_it
=
1666 mpTabCtrlData
->maItemList
.emplace(mpTabCtrlData
->maItemList
.begin() + nPos
, nPageId
);
1668 if( mpTabCtrlData
->mpListBox
)
1669 mpTabCtrlData
->mpListBox
->InsertEntry( rText
, nPos
);
1671 if( mpTabCtrlData
->mpListBox
)
1674 mpTabCtrlData
->mpListBox
->SelectEntryPos( 0 );
1675 mpTabCtrlData
->mpListBox
->SetDropDownLineCount( mpTabCtrlData
->mpListBox
->GetEntryCount() );
1678 // set current page id
1680 mnCurPageId
= nPageId
;
1682 // init new page item
1683 pItem
->maText
= rText
;
1684 pItem
->mbFullVisible
= false;
1687 if ( IsUpdateMode() )
1690 ImplFreeLayoutData();
1691 if( mpTabCtrlData
->mpListBox
) // reposition/resize listbox
1694 CallEventListeners( VclEventId::TabpageInserted
, reinterpret_cast<void*>(nPageId
) );
1697 void TabControl::RemovePage( sal_uInt16 nPageId
)
1699 sal_uInt16 nPos
= GetPagePos( nPageId
);
1701 // does the item exist ?
1702 if ( nPos
== TAB_PAGE_NOTFOUND
)
1706 std::vector
< ImplTabItem
>::iterator it
= mpTabCtrlData
->maItemList
.begin() + nPos
;
1707 bool bIsCurrentPage
= (it
->id() == mnCurPageId
);
1708 mpTabCtrlData
->maItemList
.erase( it
);
1709 if( mpTabCtrlData
->mpListBox
)
1711 mpTabCtrlData
->mpListBox
->RemoveEntry( nPos
);
1712 mpTabCtrlData
->mpListBox
->SetDropDownLineCount( mpTabCtrlData
->mpListBox
->GetEntryCount() );
1715 // If current page is removed, then first page gets the current page
1716 if ( bIsCurrentPage
)
1720 if( ! mpTabCtrlData
->maItemList
.empty() )
1722 // don't do this by simply setting mnCurPageId to pFirstItem->id()
1723 // this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
1724 // instead, call SetCurPageId
1725 // without this, the next (outside) call to SetCurPageId with the id of the first page
1726 // will result in doing nothing (as we assume that nothing changed, then), and the page
1727 // will never be shown.
1728 // 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
1730 SetCurPageId(mpTabCtrlData
->maItemList
[0].id());
1735 if ( IsUpdateMode() )
1738 ImplFreeLayoutData();
1740 CallEventListeners( VclEventId::TabpageRemoved
, reinterpret_cast<void*>(nPageId
) );
1743 void TabControl::SetPageEnabled( sal_uInt16 i_nPageId
, bool i_bEnable
)
1745 ImplTabItem
* pItem
= ImplGetItem( i_nPageId
);
1747 if (!pItem
|| pItem
->m_bEnabled
== i_bEnable
)
1750 pItem
->m_bEnabled
= i_bEnable
;
1751 if (!pItem
->m_bVisible
)
1755 if( mpTabCtrlData
->mpListBox
)
1756 mpTabCtrlData
->mpListBox
->SetEntryFlags( GetPagePos( i_nPageId
),
1757 i_bEnable
? ListBoxEntryFlags::NONE
: (ListBoxEntryFlags::DisableSelection
| ListBoxEntryFlags::DrawDisabled
) );
1759 // SetCurPageId will change to a valid page
1760 if (pItem
->id() == mnCurPageId
)
1761 SetCurPageId( mnCurPageId
);
1762 else if ( IsUpdateMode() )
1766 void TabControl::SetPageVisible( sal_uInt16 nPageId
, bool bVisible
)
1768 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1769 if (!pItem
|| pItem
->m_bVisible
== bVisible
)
1772 pItem
->m_bVisible
= bVisible
;
1775 if (pItem
->mbFullVisible
)
1776 mbSmallInvalidate
= false;
1777 pItem
->mbFullVisible
= false;
1778 pItem
->maRect
.SetEmpty();
1782 // SetCurPageId will change to a valid page
1783 if (pItem
->id() == mnCurPageId
)
1784 SetCurPageId(mnCurPageId
);
1785 else if (IsUpdateMode())
1789 sal_uInt16
TabControl::GetPageCount() const
1791 return static_cast<sal_uInt16
>(mpTabCtrlData
->maItemList
.size());
1794 sal_uInt16
TabControl::GetPageId( sal_uInt16 nPos
) const
1796 if( size_t(nPos
) < mpTabCtrlData
->maItemList
.size() )
1797 return mpTabCtrlData
->maItemList
[nPos
].id();
1801 sal_uInt16
TabControl::GetPagePos( sal_uInt16 nPageId
) const
1803 sal_uInt16 nPos
= 0;
1804 for (auto const& item
: mpTabCtrlData
->maItemList
)
1806 if (item
.id() == nPageId
)
1811 return TAB_PAGE_NOTFOUND
;
1814 sal_uInt16
TabControl::GetPageId( const Point
& rPos
) const
1816 Size winSize
= Control::GetOutputSizePixel();
1817 const auto &rList
= mpTabCtrlData
->maItemList
;
1818 const auto it
= std::find_if(rList
.begin(), rList
.end(), [&rPos
, &winSize
, this](const auto &item
) {
1819 return const_cast<TabControl
*>(this)->ImplGetTabRect(&item
, winSize
.Width(), winSize
.Height()).Contains(rPos
); });
1820 return (it
!= rList
.end()) ? it
->id() : 0;
1823 sal_uInt16
TabControl::GetPageId( const OUString
& rName
) const
1825 const auto &rList
= mpTabCtrlData
->maItemList
;
1826 const auto it
= std::find_if(rList
.begin(), rList
.end(), [&rName
](const auto &item
) {
1827 return item
.maTabName
== rName
; });
1828 return (it
!= rList
.end()) ? it
->id() : 0;
1831 void TabControl::SetCurPageId( sal_uInt16 nPageId
)
1833 sal_uInt16 nPos
= GetPagePos( nPageId
);
1834 while (nPos
!= TAB_PAGE_NOTFOUND
&& !mpTabCtrlData
->maItemList
[nPos
].m_bEnabled
)
1837 if( size_t(nPos
) >= mpTabCtrlData
->maItemList
.size() )
1839 if (mpTabCtrlData
->maItemList
[nPos
].id() == nPageId
)
1843 if( nPos
== TAB_PAGE_NOTFOUND
)
1846 nPageId
= mpTabCtrlData
->maItemList
[nPos
].id();
1847 if ( nPageId
== mnCurPageId
)
1850 mnActPageId
= nPageId
;
1855 mnActPageId
= nPageId
;
1859 sal_uInt16 nOldId
= mnCurPageId
;
1860 mnCurPageId
= nPageId
;
1861 ImplChangeTabPage( nPageId
, nOldId
);
1865 sal_uInt16
TabControl::GetCurPageId() const
1873 void TabControl::SelectTabPage( sal_uInt16 nPageId
)
1875 if ( !nPageId
|| (nPageId
== mnCurPageId
) )
1878 ImplFreeLayoutData();
1880 CallEventListeners( VclEventId::TabpageDeactivate
, reinterpret_cast<void*>(mnCurPageId
) );
1881 if ( DeactivatePage() )
1883 mnActPageId
= nPageId
;
1885 // Page could have been switched by the Activate handler
1886 nPageId
= mnActPageId
;
1888 SetCurPageId( nPageId
);
1889 if( mpTabCtrlData
->mpListBox
)
1890 mpTabCtrlData
->mpListBox
->SelectEntryPos( GetPagePos( nPageId
) );
1891 CallEventListeners( VclEventId::TabpageActivate
, reinterpret_cast<void*>(nPageId
) );
1895 void TabControl::SetTabPage( sal_uInt16 nPageId
, TabPage
* pTabPage
)
1897 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1899 if ( !pItem
|| (pItem
->mpTabPage
.get() == pTabPage
) )
1904 if ( IsDefaultSize() )
1905 SetTabPageSizePixel( pTabPage
->GetSizePixel() );
1907 // only set here, so that Resize does not reposition TabPage
1908 pItem
->mpTabPage
= pTabPage
;
1911 if (pItem
->id() == mnCurPageId
)
1912 ImplChangeTabPage(pItem
->id(), 0);
1916 pItem
->mpTabPage
= nullptr;
1921 TabPage
* TabControl::GetTabPage( sal_uInt16 nPageId
) const
1923 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1926 return pItem
->mpTabPage
;
1931 void TabControl::SetPageText( sal_uInt16 nPageId
, const OUString
& rText
)
1933 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1935 if ( !pItem
|| pItem
->maText
== rText
)
1938 pItem
->maText
= rText
;
1940 if( mpTabCtrlData
->mpListBox
)
1942 sal_uInt16 nPos
= GetPagePos( nPageId
);
1943 mpTabCtrlData
->mpListBox
->RemoveEntry( nPos
);
1944 mpTabCtrlData
->mpListBox
->InsertEntry( rText
, nPos
);
1946 if ( IsUpdateMode() )
1948 ImplFreeLayoutData();
1949 CallEventListeners( VclEventId::TabpagePageTextChanged
, reinterpret_cast<void*>(nPageId
) );
1952 OUString
const & TabControl::GetPageText( sal_uInt16 nPageId
) const
1954 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1958 return pItem
->maText
;
1961 void TabControl::SetHelpText( sal_uInt16 nPageId
, const OUString
& rText
)
1963 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1967 pItem
->maHelpText
= rText
;
1970 const OUString
& TabControl::GetHelpText( sal_uInt16 nPageId
) const
1972 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1974 return pItem
->maHelpText
;
1977 void TabControl::SetAccessibleName(sal_uInt16 nPageId
, const OUString
& rName
)
1979 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1981 pItem
->maAccessibleName
= rName
;
1984 OUString
TabControl::GetAccessibleName( sal_uInt16 nPageId
) const
1986 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1988 if (!pItem
->maAccessibleName
.isEmpty())
1989 return pItem
->maAccessibleName
;
1990 return removeMnemonicFromString(pItem
->maText
);
1993 void TabControl::SetAccessibleDescription(sal_uInt16 nPageId
, const OUString
& rDesc
)
1995 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
1997 pItem
->maAccessibleDescription
= rDesc
;
2000 OUString
TabControl::GetAccessibleDescription( sal_uInt16 nPageId
) const
2002 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
2004 if (!pItem
->maAccessibleDescription
.isEmpty())
2005 return pItem
->maAccessibleDescription
;
2006 return pItem
->maHelpText
;
2009 void TabControl::SetPageName( sal_uInt16 nPageId
, const OUString
& rName
) const
2011 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
2014 pItem
->maTabName
= rName
;
2017 OUString
TabControl::GetPageName( sal_uInt16 nPageId
) const
2019 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
2022 return pItem
->maTabName
;
2027 void TabControl::SetPageImage( sal_uInt16 i_nPageId
, const Image
& i_rImage
)
2029 ImplTabItem
* pItem
= ImplGetItem( i_nPageId
);
2033 pItem
->maTabImage
= i_rImage
;
2035 if ( IsUpdateMode() )
2040 tools::Rectangle
TabControl::GetCharacterBounds( sal_uInt16 nPageId
, tools::Long nIndex
) const
2042 tools::Rectangle aRet
;
2044 if( !HasLayoutData() || mpTabCtrlData
->maLayoutPageIdToLine
.empty() )
2047 if( HasLayoutData() )
2049 std::unordered_map
< int, int >::const_iterator it
= mpTabCtrlData
->maLayoutPageIdToLine
.find( static_cast<int>(nPageId
) );
2050 if( it
!= mpTabCtrlData
->maLayoutPageIdToLine
.end() )
2052 Pair aPair
= mxLayoutData
->GetLineStartEnd( it
->second
);
2053 if( (aPair
.B() - aPair
.A()) >= nIndex
)
2054 aRet
= mxLayoutData
->GetCharacterBounds( aPair
.A() + nIndex
);
2061 tools::Long
TabControl::GetIndexForPoint( const Point
& rPoint
, sal_uInt16
& rPageId
) const
2063 tools::Long nRet
= -1;
2065 if( !HasLayoutData() || mpTabCtrlData
->maLayoutPageIdToLine
.empty() )
2068 if( HasLayoutData() )
2070 int nIndex
= mxLayoutData
->GetIndexForPoint( rPoint
);
2073 // what line (->pageid) is this index in ?
2074 int nLines
= mxLayoutData
->GetLineCount();
2076 while( ++nLine
< nLines
)
2078 Pair aPair
= mxLayoutData
->GetLineStartEnd( nLine
);
2079 if( aPair
.A() <= nIndex
&& aPair
.B() >= nIndex
)
2081 nRet
= nIndex
- aPair
.A();
2082 rPageId
= static_cast<sal_uInt16
>(mpTabCtrlData
->maLayoutLineToPageId
[ nLine
]);
2092 void TabControl::FillLayoutData() const
2094 mpTabCtrlData
->maLayoutLineToPageId
.clear();
2095 mpTabCtrlData
->maLayoutPageIdToLine
.clear();
2096 const_cast<TabControl
*>(this)->Invalidate();
2099 tools::Rectangle
TabControl::GetTabBounds( sal_uInt16 nPageId
) const
2101 tools::Rectangle aRet
;
2103 ImplTabItem
* pItem
= ImplGetItem( nPageId
);
2104 if (pItem
&& pItem
->m_bVisible
)
2105 aRet
= pItem
->maRect
;
2110 Size
TabControl::ImplCalculateRequisition(sal_uInt16
& nHeaderHeight
) const
2112 Size
aOptimalPageSize(0, 0);
2114 sal_uInt16 nOrigPageId
= GetCurPageId();
2115 for (auto const& item
: mpTabCtrlData
->maItemList
)
2117 const TabPage
*pPage
= item
.mpTabPage
;
2118 //it's a real nuisance if the page is not inserted yet :-(
2119 //We need to force all tabs to exist to get overall optimal size for dialog
2122 TabControl
*pThis
= const_cast<TabControl
*>(this);
2123 pThis
->SetCurPageId(item
.id());
2124 pThis
->ActivatePage();
2125 pPage
= item
.mpTabPage
;
2131 Size
aPageSize(VclContainer::getLayoutRequisition(*pPage
));
2133 if (aPageSize
.Width() > aOptimalPageSize
.Width())
2134 aOptimalPageSize
.setWidth( aPageSize
.Width() );
2135 if (aPageSize
.Height() > aOptimalPageSize
.Height())
2136 aOptimalPageSize
.setHeight( aPageSize
.Height() );
2139 //fdo#61940 If we were forced to activate pages in order to on-demand
2140 //create them to get their optimal size, then switch back to the original
2141 //page and re-activate it
2142 if (nOrigPageId
!= GetCurPageId())
2144 TabControl
*pThis
= const_cast<TabControl
*>(this);
2145 pThis
->SetCurPageId(nOrigPageId
);
2146 pThis
->ActivatePage();
2149 tools::Long nTabLabelsBottom
= 0, nTabLabelsRight
= 0;
2152 for (sal_uInt16
nPos(0), sizeList(static_cast <sal_uInt16
> (mpTabCtrlData
->maItemList
.size()));
2153 nPos
< sizeList
; ++nPos
)
2155 TabControl
* pThis
= const_cast<TabControl
*>(this);
2157 tools::Rectangle aTabRect
= pThis
->ImplGetTabRect(nPos
, aOptimalPageSize
.Width(), LONG_MAX
);
2158 if (aTabRect
.Bottom() > nTabLabelsBottom
)
2160 nTabLabelsBottom
= aTabRect
.Bottom();
2161 nHeaderHeight
= nTabLabelsBottom
;
2163 if (!aTabRect
.IsEmpty() && aTabRect
.Right() > nTabLabelsRight
)
2164 nTabLabelsRight
= aTabRect
.Right();
2168 Size
aOptimalSize(aOptimalPageSize
);
2169 aOptimalSize
.AdjustHeight(nTabLabelsBottom
);
2170 aOptimalSize
.setWidth( std::max(nTabLabelsRight
, aOptimalSize
.Width()) );
2172 aOptimalSize
.AdjustWidth(TAB_OFFSET
* 2 );
2173 aOptimalSize
.AdjustHeight(TAB_OFFSET
* 2 );
2175 return aOptimalSize
;
2178 Size
TabControl::calculateRequisition() const
2180 sal_uInt16 nHeaderHeight
;
2181 return ImplCalculateRequisition(nHeaderHeight
);
2184 Size
TabControl::GetOptimalSize() const
2186 return calculateRequisition();
2189 void TabControl::queue_resize(StateChangedType eReason
)
2191 mbLayoutDirty
= true;
2192 Window::queue_resize(eReason
);
2195 std::vector
<sal_uInt16
> TabControl::GetPageIDs() const
2197 std::vector
<sal_uInt16
> aIDs
;
2198 for (auto const& item
: mpTabCtrlData
->maItemList
)
2200 aIDs
.push_back(item
.id());
2206 bool TabControl::set_property(const OUString
&rKey
, const OUString
&rValue
)
2208 if (rKey
== "show-tabs")
2210 mbShowTabs
= toBool(rValue
);
2214 return Control::set_property(rKey
, rValue
);
2218 FactoryFunction
TabControl::GetUITestFactory() const
2220 return TabControlUIObject::create
;
2223 void TabControl::DumpAsPropertyTree(tools::JsonWriter
& rJsonWriter
)
2225 rJsonWriter
.put("id", get_id());
2226 rJsonWriter
.put("type", "tabcontrol");
2227 rJsonWriter
.put("selected", GetCurPageId());
2230 auto childrenNode
= rJsonWriter
.startArray("children");
2231 for (auto id
: GetPageIDs())
2233 TabPage
* pChild
= GetTabPage(id
);
2237 auto childNode
= rJsonWriter
.startStruct();
2238 pChild
->DumpAsPropertyTree(rJsonWriter
);
2240 if (!pChild
->IsVisible())
2241 rJsonWriter
.put("hidden", true);
2246 auto tabsNode
= rJsonWriter
.startArray("tabs");
2247 for(auto id
: GetPageIDs())
2249 auto tabNode
= rJsonWriter
.startStruct();
2250 rJsonWriter
.put("text", GetPageText(id
));
2251 rJsonWriter
.put("id", id
);
2252 rJsonWriter
.put("name", GetPageName(id
));
2257 sal_uInt16
NotebookbarTabControlBase::m_nHeaderHeight
= 0;
2259 IMPL_LINK_NOARG(NotebookbarTabControlBase
, OpenMenu
, Button
*, void)
2261 m_aIconClickHdl
.Call(static_cast<NotebookBar
*>(GetParent()->GetParent()));
2264 NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window
* pParent
)
2265 : TabControl(pParent
, WB_STDTABCONTROL
)
2266 , bLastContextWasSupported(true)
2267 , eLastContext(vcl::EnumContext::Context::Any
)
2269 m_pOpenMenu
= VclPtr
<PushButton
>::Create( this , WB_CENTER
| WB_VCENTER
);
2270 m_pOpenMenu
->SetClickHdl(LINK(this, NotebookbarTabControlBase
, OpenMenu
));
2271 m_pOpenMenu
->SetModeImage(Image(StockImage::Yes
, SV_RESID_BITMAP_NOTEBOOKBAR
));
2272 m_pOpenMenu
->SetSizePixel(m_pOpenMenu
->GetOptimalSize());
2273 m_pOpenMenu
->Show();
2276 NotebookbarTabControlBase::~NotebookbarTabControlBase()
2281 void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext
)
2283 if (eLastContext
== eContext
)
2286 bool bHandled
= false;
2288 for (int nChild
= 0; nChild
< GetPageCount(); ++nChild
)
2290 sal_uInt16 nPageId
= TabControl::GetPageId(nChild
);
2291 TabPage
* pPage
= GetTabPage(nPageId
);
2295 SetPageVisible(nPageId
, pPage
->HasContext(eContext
) || pPage
->HasContext(vcl::EnumContext::Context::Any
));
2297 if (!bHandled
&& bLastContextWasSupported
2298 && pPage
->HasContext(vcl::EnumContext::Context::Default
))
2300 SetCurPageId(nPageId
);
2303 if (pPage
->HasContext(eContext
) && eContext
!= vcl::EnumContext::Context::Any
)
2305 SetCurPageId(nPageId
);
2307 bLastContextWasSupported
= true;
2313 bLastContextWasSupported
= false;
2314 eLastContext
= eContext
;
2316 // tdf#152908 Tabbed compact toolbar does not repaint itself when tabs getting removed
2317 // For unknown reason this is needed by the tabbed compact toolbar for other than gtk
2322 void NotebookbarTabControlBase::dispose()
2324 m_pShortcuts
.disposeAndClear();
2325 m_pOpenMenu
.disposeAndClear();
2326 TabControl::dispose();
2329 void NotebookbarTabControlBase::SetToolBox( ToolBox
* pToolBox
)
2331 m_pShortcuts
.set( pToolBox
);
2334 void NotebookbarTabControlBase::SetIconClickHdl( Link
<NotebookBar
*, void> aHdl
)
2336 m_aIconClickHdl
= aHdl
;
2339 static bool lcl_isValidPage(const ImplTabItem
& rItem
, bool& bFound
)
2341 if (rItem
.m_bVisible
&& rItem
.m_bEnabled
)
2346 void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext
)
2348 const sal_uInt16 nOldPos
= GetPagePos(GetCurPageId());
2349 bool bFound
= false;
2350 sal_Int32 nCurPos
= nOldPos
;
2354 for (nCurPos
++; nCurPos
< GetPageCount(); nCurPos
++)
2355 if (lcl_isValidPage(mpTabCtrlData
->maItemList
[nCurPos
], bFound
))
2360 for (nCurPos
--; nCurPos
>= 0; nCurPos
--)
2361 if (lcl_isValidPage(mpTabCtrlData
->maItemList
[nCurPos
], bFound
))
2367 SelectTabPage( TabControl::GetPageId( nCurPos
) );
2370 sal_uInt16
NotebookbarTabControlBase::GetHeaderHeight()
2372 return m_nHeaderHeight
;
2375 bool NotebookbarTabControlBase::ImplPlaceTabs( tools::Long nWidth
)
2379 if ( mpTabCtrlData
->maItemList
.empty() )
2381 if (!m_pOpenMenu
|| m_pOpenMenu
->isDisposed())
2384 const tools::Long nHamburgerWidth
= m_pOpenMenu
->GetSizePixel().Width();
2385 tools::Long nMaxWidth
= nWidth
- nHamburgerWidth
;
2386 tools::Long nShortcutsWidth
= m_pShortcuts
!= nullptr ? m_pShortcuts
->GetSizePixel().getWidth() + 1 : 0;
2387 tools::Long nFullWidth
= nShortcutsWidth
;
2389 const tools::Long nOffsetX
= 2 + nShortcutsWidth
;
2390 const tools::Long nOffsetY
= 2;
2392 //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
2393 //of ugly bare tabs on lines of their own
2395 for (auto & item
: mpTabCtrlData
->maItemList
)
2397 tools::Long nTabWidth
= 0;
2398 if (item
.m_bVisible
)
2400 nTabWidth
= ImplGetItemSize(&item
, nMaxWidth
).getWidth();
2401 if (!item
.maText
.isEmpty() && nTabWidth
< 100)
2404 nFullWidth
+= nTabWidth
;
2407 tools::Long nX
= nOffsetX
;
2408 tools::Long nY
= nOffsetY
;
2410 tools::Long nLineWidthAry
[100];
2411 nLineWidthAry
[0] = 0;
2413 for (auto & item
: mpTabCtrlData
->maItemList
)
2415 if (!item
.m_bVisible
)
2418 Size aSize
= ImplGetItemSize( &item
, nMaxWidth
);
2420 // set minimum tab size
2421 if( nFullWidth
< nMaxWidth
&& !item
.maText
.isEmpty() && aSize
.getWidth() < 100)
2422 aSize
.setWidth( 100 );
2424 if( !item
.maText
.isEmpty() && aSize
.getHeight() < 28 )
2425 aSize
.setHeight( 28 );
2427 tools::Rectangle
aNewRect( Point( nX
, nY
), aSize
);
2428 if ( mbSmallInvalidate
&& (item
.maRect
!= aNewRect
) )
2429 mbSmallInvalidate
= false;
2431 item
.maRect
= aNewRect
;
2433 item
.mbFullVisible
= true;
2435 nLineWidthAry
[0] += aSize
.Width();
2436 nX
+= aSize
.Width();
2439 // we always have only one line of tabs
2440 lcl_AdjustSingleLineTabs(nMaxWidth
, mpTabCtrlData
.get());
2442 // position the shortcutbox
2445 tools::Long nPosY
= (m_nHeaderHeight
- m_pShortcuts
->GetSizePixel().getHeight()) / 2;
2446 m_pShortcuts
->SetPosPixel(Point(0, nPosY
));
2449 tools::Long nPosY
= (m_nHeaderHeight
- m_pOpenMenu
->GetSizePixel().getHeight()) / 2;
2450 // position the menu
2451 m_pOpenMenu
->SetPosPixel(Point(nWidth
- nHamburgerWidth
, nPosY
));
2456 Size
NotebookbarTabControlBase::calculateRequisition() const
2458 return TabControl::ImplCalculateRequisition(m_nHeaderHeight
);
2461 Control
* NotebookbarTabControlBase::GetOpenMenu()
2466 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */