xref: /core/vcl/source/control/tabctrl.cxx (revision 88ac9b0c)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 
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/toolkit/controllayout.hxx>
32 #include <vcl/layout.hxx>
33 #include <vcl/toolkit/lstbox.hxx>
34 #include <vcl/settings.hxx>
35 #include <vcl/uitest/uiobject.hxx>
36 #include <bitmaps.hlst>
37 #include <tools/json_writer.hxx>
38 
39 #include <controldata.hxx>
40 #include <svdata.hxx>
41 #include <window.h>
42 
43 #include <deque>
44 #include <unordered_map>
45 #include <vector>
46 
47 class ImplTabItem final
48 {
49     sal_uInt16 m_nId;
50 
51 public:
52     VclPtr<TabPage>     mpTabPage;
53     OUString            maText;
54     OUString            maFormatText;
55     OUString            maHelpText;
56     OUString            maAccessibleName;
57     OUString            maAccessibleDescription;
58     OString             maTabName;
59     tools::Rectangle    maRect;
60     sal_uInt16          mnLine;
61     bool                mbFullVisible;
62     bool                m_bEnabled; ///< the tab / page is selectable
63     bool                m_bVisible; ///< the tab / page can be visible
64     Image               maTabImage;
65 
66     ImplTabItem(sal_uInt16 nId);
67 
68     sal_uInt16 id() const { return m_nId; }
69 };
70 
71 ImplTabItem::ImplTabItem(sal_uInt16 nId)
72     : m_nId(nId)
73     , mnLine(0)
74     , mbFullVisible(false)
75     , m_bEnabled(true)
76     , m_bVisible(true)
77 {
78 }
79 
80 struct ImplTabCtrlData
81 {
82     std::unordered_map< int, int >        maLayoutPageIdToLine;
83     std::unordered_map< int, int >        maLayoutLineToPageId;
84     std::vector< ImplTabItem >      maItemList;
85     VclPtr<ListBox>                 mpListBox;
86 };
87 
88 // for the Tab positions
89 #define TAB_PAGERECT        0xFFFF
90 
91 void TabControl::ImplInit( vcl::Window* pParent, WinBits nStyle )
92 {
93     mbLayoutDirty = true;
94 
95     if ( !(nStyle & WB_NOTABSTOP) )
96         nStyle |= WB_TABSTOP;
97     if ( !(nStyle & WB_NOGROUP) )
98         nStyle |= WB_GROUP;
99     if ( !(nStyle & WB_NODIALOGCONTROL) )
100         nStyle |= WB_DIALOGCONTROL;
101 
102     Control::ImplInit( pParent, nStyle, nullptr );
103 
104     mnLastWidth                 = 0;
105     mnLastHeight                = 0;
106     mnActPageId                 = 0;
107     mnCurPageId                 = 0;
108     mbFormat                    = true;
109     mbRestoreHelpId             = false;
110     mbSmallInvalidate           = false;
111     mpTabCtrlData.reset(new ImplTabCtrlData);
112     mpTabCtrlData->mpListBox    = nullptr;
113 
114     ImplInitSettings( true );
115 
116     if( nStyle & WB_DROPDOWN )
117     {
118         mpTabCtrlData->mpListBox = VclPtr<ListBox>::Create( this, WB_DROPDOWN );
119         mpTabCtrlData->mpListBox->SetPosSizePixel( Point( 0, 0 ), Size( 200, 20 ) );
120         mpTabCtrlData->mpListBox->SetSelectHdl( LINK( this, TabControl, ImplListBoxSelectHdl ) );
121         mpTabCtrlData->mpListBox->Show();
122     }
123 
124     // if the tabcontrol is drawn (ie filled) by a native widget, make sure all controls will have transparent background
125     // otherwise they will paint with a wrong background
126     if( IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire) )
127         EnableChildTransparentMode();
128 
129     if (pParent && pParent->IsDialog())
130         pParent->AddChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
131 }
132 
133 const vcl::Font& TabControl::GetCanonicalFont( const StyleSettings& _rStyle ) const
134 {
135     return _rStyle.GetTabFont();
136 }
137 
138 const Color& TabControl::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
139 {
140     return _rStyle.GetTabTextColor();
141 }
142 
143 void TabControl::ImplInitSettings( bool bBackground )
144 {
145     Control::ImplInitSettings();
146 
147     if ( !bBackground )
148         return;
149 
150     vcl::Window* pParent = GetParent();
151     if ( !IsControlBackground() &&
152         (pParent->IsChildTransparentModeEnabled()
153         || IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire)
154         || IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) ) )
155 
156     {
157         // set transparent mode for NWF tabcontrols to have
158         // the background always cleared properly
159         EnableChildTransparentMode();
160         SetParentClipMode( ParentClipMode::NoClip );
161         SetPaintTransparent( true );
162         SetBackground();
163         ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
164     }
165     else
166     {
167         EnableChildTransparentMode( false );
168         SetParentClipMode();
169         SetPaintTransparent( false );
170 
171         if ( IsControlBackground() )
172             SetBackground( GetControlBackground() );
173         else
174             SetBackground( pParent->GetBackground() );
175     }
176 }
177 
178 void TabControl::ImplFreeLayoutData()
179 {
180     if( HasLayoutData() )
181     {
182         ImplClearLayoutData();
183         mpTabCtrlData->maLayoutPageIdToLine.clear();
184         mpTabCtrlData->maLayoutLineToPageId.clear();
185     }
186 }
187 
188 TabControl::TabControl( vcl::Window* pParent, WinBits nStyle ) :
189     Control( WindowType::TABCONTROL )
190 {
191     ImplInit( pParent, nStyle );
192     SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER ) ? "true" : "false") );
193 }
194 
195 TabControl::~TabControl()
196 {
197     disposeOnce();
198 }
199 
200 void TabControl::dispose()
201 {
202     Window *pParent = GetParent();
203     if (pParent && pParent->IsDialog())
204         GetParent()->RemoveChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
205 
206     ImplFreeLayoutData();
207 
208     // delete TabCtrl data
209     if (mpTabCtrlData)
210         mpTabCtrlData->mpListBox.disposeAndClear();
211     mpTabCtrlData.reset();
212     Control::dispose();
213 }
214 
215 ImplTabItem* TabControl::ImplGetItem( sal_uInt16 nId ) const
216 {
217     for (auto & item : mpTabCtrlData->maItemList)
218     {
219         if (item.id() == nId)
220             return &item;
221     }
222 
223     return nullptr;
224 }
225 
226 Size TabControl::ImplGetItemSize( ImplTabItem* pItem, tools::Long nMaxWidth )
227 {
228     pItem->maFormatText = pItem->maText;
229     Size aSize( GetCtrlTextWidth( pItem->maFormatText ), GetTextHeight() );
230     Size aImageSize( 0, 0 );
231     if( !!pItem->maTabImage )
232     {
233         aImageSize = pItem->maTabImage.GetSizePixel();
234         if( !pItem->maFormatText.isEmpty() )
235             aImageSize.AdjustWidth(GetTextHeight()/4 );
236     }
237     aSize.AdjustWidth(aImageSize.Width() );
238     if( aImageSize.Height() > aSize.Height() )
239         aSize.setHeight( aImageSize.Height() );
240 
241     aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
242     aSize.AdjustHeight(TAB_TABOFFSET_Y*2 );
243 
244     tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
245     tools::Rectangle aBoundingRgn, aContentRgn;
246     const TabitemValue aControlValue(tools::Rectangle(TAB_TABOFFSET_X, TAB_TABOFFSET_Y,
247                                                aSize.Width() - TAB_TABOFFSET_X * 2,
248                                                aSize.Height() - TAB_TABOFFSET_Y * 2));
249     if(GetNativeControlRegion( ControlType::TabItem, ControlPart::Entire, aCtrlRegion,
250                                            ControlState::ENABLED, aControlValue,
251                                            aBoundingRgn, aContentRgn ) )
252     {
253         return aContentRgn.GetSize();
254     }
255 
256     // For languages with short names (e.g. Chinese), because the space is
257     // normally only one pixel per char
258     if ( pItem->maFormatText.getLength() < TAB_EXTRASPACE_X )
259         aSize.AdjustWidth(TAB_EXTRASPACE_X-pItem->maFormatText.getLength() );
260 
261     // shorten Text if needed
262     if ( aSize.Width()+4 >= nMaxWidth )
263     {
264         OUString aAppendStr("...");
265         pItem->maFormatText += aAppendStr;
266         do
267         {
268             if (pItem->maFormatText.getLength() > aAppendStr.getLength())
269                 pItem->maFormatText = pItem->maFormatText.replaceAt( pItem->maFormatText.getLength()-aAppendStr.getLength()-1, 1, "" );
270             aSize.setWidth( GetCtrlTextWidth( pItem->maFormatText ) );
271             aSize.AdjustWidth(aImageSize.Width() );
272             aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
273         }
274         while ( (aSize.Width()+4 >= nMaxWidth) && (pItem->maFormatText.getLength() > aAppendStr.getLength()) );
275         if ( aSize.Width()+4 >= nMaxWidth )
276         {
277             pItem->maFormatText = ".";
278             aSize.setWidth( 1 );
279         }
280     }
281 
282     if( pItem->maFormatText.isEmpty() )
283     {
284         if( aSize.Height() < aImageSize.Height()+4 ) //leave space for focus rect
285             aSize.setHeight( aImageSize.Height()+4 );
286     }
287 
288     return aSize;
289 }
290 
291 // Feel free to move this to some more general place for reuse
292 // http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
293 // Mostly based on Alexey Frunze's nifty example at
294 // http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
295 namespace MinimumRaggednessWrap
296 {
297     static std::deque<size_t> GetEndOfLineIndexes(const std::vector<sal_Int32>& rWidthsOf, sal_Int32 nLineWidth)
298     {
299         ++nLineWidth;
300 
301         size_t nWidthsCount = rWidthsOf.size();
302         std::vector<sal_Int32> aCosts(nWidthsCount * nWidthsCount);
303 
304         // cost function c(i, j) that computes the cost of a line consisting of
305         // the words Word[i] to Word[j]
306         for (size_t i = 0; i < nWidthsCount; ++i)
307         {
308             for (size_t j = 0; j < nWidthsCount; ++j)
309             {
310                 if (j >= i)
311                 {
312                     sal_Int32 c = nLineWidth - (j - i);
313                     for (size_t k = i; k <= j; ++k)
314                         c -= rWidthsOf[k];
315                     c = (c >= 0) ? c * c : SAL_MAX_INT32;
316                     aCosts[j * nWidthsCount + i] = c;
317                 }
318                 else
319                 {
320                     aCosts[j * nWidthsCount + i] = SAL_MAX_INT32;
321                 }
322             }
323         }
324 
325         std::vector<sal_Int32> aFunction(nWidthsCount);
326         std::vector<sal_Int32> aWrapPoints(nWidthsCount);
327 
328         // f(j) in aFunction[], collect wrap points in aWrapPoints[]
329         for (size_t j = 0; j < nWidthsCount; ++j)
330         {
331             aFunction[j] = aCosts[j * nWidthsCount];
332             if (aFunction[j] == SAL_MAX_INT32)
333             {
334                 for (size_t k = 0; k < j; ++k)
335                 {
336                     sal_Int32 s;
337                     if (aFunction[k] == SAL_MAX_INT32 || aCosts[j * nWidthsCount + k + 1] == SAL_MAX_INT32)
338                         s = SAL_MAX_INT32;
339                     else
340                         s = aFunction[k] + aCosts[j * nWidthsCount + k + 1];
341                     if (aFunction[j] > s)
342                     {
343                         aFunction[j] = s;
344                         aWrapPoints[j] = k + 1;
345                     }
346                 }
347             }
348         }
349 
350         std::deque<size_t> aSolution;
351 
352         // no solution
353         if (aFunction[nWidthsCount - 1] == SAL_MAX_INT32)
354             return aSolution;
355 
356         // optimal solution
357         size_t j = nWidthsCount - 1;
358         while (true)
359         {
360             aSolution.push_front(j);
361             if (!aWrapPoints[j])
362                 break;
363             j = aWrapPoints[j] - 1;
364         }
365 
366         return aSolution;
367     }
368 };
369 
370 static void lcl_AdjustSingleLineTabs(tools::Long nMaxWidth, ImplTabCtrlData *pTabCtrlData)
371 {
372     if (!ImplGetSVData()->maNWFData.mbCenteredTabs)
373         return;
374 
375     int nRightSpace = nMaxWidth; // space left on the right by the tabs
376     for (auto const& item : pTabCtrlData->maItemList)
377     {
378         if (!item.m_bVisible)
379             continue;
380         nRightSpace -= item.maRect.Right() - item.maRect.Left();
381     }
382     nRightSpace /= 2;
383 
384     for (auto& item : pTabCtrlData->maItemList)
385     {
386         if (!item.m_bVisible)
387             continue;
388         item.maRect.AdjustLeft(nRightSpace);
389         item.maRect.AdjustRight(nRightSpace);
390     }
391 }
392 
393 bool TabControl::ImplPlaceTabs( tools::Long nWidth )
394 {
395     if ( nWidth <= 0 )
396         return false;
397     if ( mpTabCtrlData->maItemList.empty() )
398         return false;
399 
400     tools::Long nMaxWidth = nWidth;
401 
402     const tools::Long nOffsetX = 2;
403     const tools::Long nOffsetY = 2;
404 
405     //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
406     //of ugly bare tabs on lines of their own
407 
408     //collect widths
409     std::vector<sal_Int32> aWidths;
410     for (auto & item : mpTabCtrlData->maItemList)
411     {
412         if (!item.m_bVisible)
413             continue;
414         aWidths.push_back(ImplGetItemSize(&item, nMaxWidth).Width());
415     }
416 
417     //aBreakIndexes will contain the indexes of the last tab on each row
418     std::deque<size_t> aBreakIndexes(MinimumRaggednessWrap::GetEndOfLineIndexes(aWidths, nMaxWidth - nOffsetX - 2));
419 
420     tools::Long nX = nOffsetX;
421     tools::Long nY = nOffsetY;
422 
423     sal_uInt16 nLines = 0;
424     sal_uInt16 nCurLine = 0;
425 
426     tools::Long nLineWidthAry[100];
427     sal_uInt16 nLinePosAry[101];
428     nLineWidthAry[0] = 0;
429     nLinePosAry[0] = 0;
430 
431     size_t nIndex = 0;
432 
433     for (auto & item : mpTabCtrlData->maItemList)
434     {
435         if (!item.m_bVisible)
436             continue;
437 
438         Size aSize = ImplGetItemSize( &item, nMaxWidth );
439 
440         bool bNewLine = false;
441         if (!aBreakIndexes.empty() && nIndex > aBreakIndexes.front())
442         {
443             aBreakIndexes.pop_front();
444             bNewLine = true;
445         }
446 
447         if ( bNewLine && (nWidth > 2+nOffsetX) )
448         {
449             if ( nLines == 99 )
450                 break;
451 
452             nX = nOffsetX;
453             nY += aSize.Height();
454             nLines++;
455             nLineWidthAry[nLines] = 0;
456             nLinePosAry[nLines] = nIndex;
457         }
458 
459         tools::Rectangle aNewRect( Point( nX, nY ), aSize );
460         if ( mbSmallInvalidate && (item.maRect != aNewRect) )
461             mbSmallInvalidate = false;
462         item.maRect = aNewRect;
463         item.mnLine = nLines;
464         item.mbFullVisible = true;
465 
466         nLineWidthAry[nLines] += aSize.Width();
467         nX += aSize.Width();
468 
469         if (item.id() == mnCurPageId)
470             nCurLine = nLines;
471 
472         ++nIndex;
473     }
474 
475     if (nLines) // two or more lines
476     {
477         tools::Long nLineHeightAry[100];
478         tools::Long nIH = 0;
479         for (const auto& item : mpTabCtrlData->maItemList)
480         {
481             if (!item.m_bVisible)
482                 continue;
483             nIH = item.maRect.Bottom() - 1;
484             break;
485         }
486 
487         for ( sal_uInt16 i = 0; i < nLines+1; i++ )
488         {
489             if ( i <= nCurLine )
490                 nLineHeightAry[i] = nIH*(nLines-(nCurLine-i));
491             else
492                 nLineHeightAry[i] = nIH*(i-nCurLine-1);
493         }
494 
495         nLinePosAry[nLines+1] = static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
496 
497         tools::Long nDX = 0;
498         tools::Long nModDX = 0;
499         tools::Long nIDX = 0;
500 
501         sal_uInt16 i = 0;
502         sal_uInt16 n = 0;
503 
504         for (auto & item : mpTabCtrlData->maItemList)
505         {
506             if (!item.m_bVisible)
507                 continue;
508 
509             if ( i == nLinePosAry[n] )
510             {
511                 if ( n == nLines+1 )
512                     break;
513 
514                 nIDX = 0;
515                 if( nLinePosAry[n+1]-i > 0 )
516                 {
517                     nDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) / ( nLinePosAry[n+1] - i );
518                     nModDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) % ( nLinePosAry[n+1] - i );
519                 }
520                 else
521                 {
522                     // FIXME: this is a case of tabctrl way too small
523                     nDX = 0;
524                     nModDX = 0;
525                 }
526                 n++;
527             }
528 
529             item.maRect.AdjustLeft(nIDX );
530             item.maRect.AdjustRight(nIDX + nDX );
531             item.maRect.SetTop( nLineHeightAry[n-1] );
532             item.maRect.SetBottom(nLineHeightAry[n-1] + nIH - 1);
533             nIDX += nDX;
534 
535             if ( nModDX )
536             {
537                 nIDX++;
538                 item.maRect.AdjustRight( 1 );
539                 nModDX--;
540             }
541 
542             i++;
543         }
544     }
545     else // only one line
546         lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
547 
548     return true;
549 }
550 
551 tools::Rectangle TabControl::ImplGetTabRect( sal_uInt16 nItemPos, tools::Long nWidth, tools::Long nHeight )
552 {
553     Size aWinSize = Control::GetOutputSizePixel();
554     if ( nWidth < 0 )
555         nWidth = aWinSize.Width();
556     if ( nHeight < 0 )
557         nHeight = aWinSize.Height();
558 
559     if ( mpTabCtrlData->maItemList.empty() )
560     {
561         tools::Long nW = nWidth-TAB_OFFSET*2;
562         tools::Long nH = nHeight-TAB_OFFSET*2;
563         return (nW > 0 && nH > 0)
564             ? tools::Rectangle(Point(TAB_OFFSET, TAB_OFFSET), Size(nW, nH))
565             : tools::Rectangle();
566     }
567 
568     if ( nItemPos == TAB_PAGERECT )
569     {
570         sal_uInt16 nLastPos;
571         if ( mnCurPageId )
572             nLastPos = GetPagePos( mnCurPageId );
573         else
574             nLastPos = 0;
575 
576         tools::Rectangle aRect = ImplGetTabRect( nLastPos, nWidth, nHeight );
577         if (aRect.IsEmpty())
578             return aRect;
579 
580         tools::Long nW = nWidth-TAB_OFFSET*2;
581         tools::Long nH = nHeight-aRect.Bottom()-TAB_OFFSET*2;
582         return (nW > 0 && nH > 0)
583             ? tools::Rectangle( Point( TAB_OFFSET, aRect.Bottom()+TAB_OFFSET ), Size( nW, nH ) )
584             : tools::Rectangle();
585     }
586 
587     ImplTabItem* const pItem = (nItemPos < mpTabCtrlData->maItemList.size())
588                                ? &mpTabCtrlData->maItemList[nItemPos] : nullptr;
589     return ImplGetTabRect(pItem, nWidth, nHeight);
590 }
591 
592 tools::Rectangle TabControl::ImplGetTabRect(const ImplTabItem* pItem, tools::Long nWidth, tools::Long nHeight)
593 {
594     if ((nWidth <= 1) || (nHeight <= 0) || !pItem || !pItem->m_bVisible)
595         return tools::Rectangle();
596 
597     nWidth -= 1;
598 
599     if ( mbFormat || (mnLastWidth != nWidth) || (mnLastHeight != nHeight) )
600     {
601         vcl::Font aFont( GetFont() );
602         aFont.SetTransparent( true );
603         SetFont( aFont );
604 
605         bool bRet = ImplPlaceTabs( nWidth );
606         if ( !bRet )
607             return tools::Rectangle();
608 
609         mnLastWidth     = nWidth;
610         mnLastHeight    = nHeight;
611         mbFormat        = false;
612     }
613 
614     return pItem->maRect;
615 }
616 
617 void TabControl::ImplChangeTabPage( sal_uInt16 nId, sal_uInt16 nOldId )
618 {
619     ImplFreeLayoutData();
620 
621     ImplTabItem*    pOldItem = ImplGetItem( nOldId );
622     ImplTabItem*    pItem = ImplGetItem( nId );
623     TabPage*        pOldPage = pOldItem ? pOldItem->mpTabPage.get() : nullptr;
624     TabPage*        pPage = pItem ? pItem->mpTabPage.get() : nullptr;
625     vcl::Window*    pCtrlParent = GetParent();
626 
627     if ( IsReallyVisible() && IsUpdateMode() )
628     {
629         sal_uInt16 nPos = GetPagePos( nId );
630         tools::Rectangle aRect = ImplGetTabRect( nPos );
631 
632         if ( !pOldItem || !pItem || (pOldItem->mnLine != pItem->mnLine) )
633         {
634             aRect.SetLeft( 0 );
635             aRect.SetTop( 0 );
636             aRect.SetRight( Control::GetOutputSizePixel().Width() );
637         }
638         else
639         {
640             aRect.AdjustLeft( -3 );
641             aRect.AdjustTop( -2 );
642             aRect.AdjustRight(3 );
643             Invalidate( aRect );
644             nPos = GetPagePos( nOldId );
645             aRect = ImplGetTabRect( nPos );
646             aRect.AdjustLeft( -3 );
647             aRect.AdjustTop( -2 );
648             aRect.AdjustRight(3 );
649         }
650         Invalidate( aRect );
651     }
652 
653     if ( pOldPage == pPage )
654         return;
655 
656     tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
657 
658     if ( pOldPage )
659     {
660         if ( mbRestoreHelpId )
661             pCtrlParent->SetHelpId( OString() );
662     }
663 
664     if ( pPage )
665     {
666         if ( GetStyle() & WB_NOBORDER )
667         {
668             tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
669             pPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
670         }
671         else
672             pPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
673 
674         // activate page here so the controls can be switched
675         // also set the help id of the parent window to that of the tab page
676         if ( GetHelpId().isEmpty() )
677         {
678             mbRestoreHelpId = true;
679             pCtrlParent->SetHelpId( pPage->GetHelpId() );
680         }
681 
682         pPage->Show();
683 
684         if ( pOldPage && pOldPage->HasChildPathFocus() )
685         {
686             vcl::Window* pFirstChild = pPage->ImplGetDlgWindow( 0, GetDlgWindowType::First );
687             if ( pFirstChild )
688                 pFirstChild->ImplControlFocus( GetFocusFlags::Init );
689             else
690                 GrabFocus();
691         }
692     }
693 
694     if ( pOldPage )
695         pOldPage->Hide();
696 
697     // Invalidate the same region that will be send to NWF
698     // to always allow for bitmap caching
699     // see Window::DrawNativeControl()
700     if( IsNativeControlSupported( ControlType::TabPane, ControlPart::Entire ) )
701     {
702         aRect.AdjustLeft( -(TAB_OFFSET) );
703         aRect.AdjustTop( -(TAB_OFFSET) );
704         aRect.AdjustRight(TAB_OFFSET );
705         aRect.AdjustBottom(TAB_OFFSET );
706     }
707 
708     Invalidate( aRect );
709 }
710 
711 bool TabControl::ImplPosCurTabPage()
712 {
713     // resize/position current TabPage
714     ImplTabItem* pItem = ImplGetItem( GetCurPageId() );
715     if ( pItem && pItem->mpTabPage )
716     {
717         if (  GetStyle() & WB_NOBORDER )
718         {
719             tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
720             pItem->mpTabPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
721             return true;
722         }
723         tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
724         pItem->mpTabPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
725         return true;
726     }
727 
728     return false;
729 }
730 
731 void TabControl::ImplActivateTabPage( bool bNext )
732 {
733     sal_uInt16 nCurPos = GetPagePos( GetCurPageId() );
734 
735     if ( bNext )
736         nCurPos = (nCurPos + 1) % GetPageCount();
737     else
738     {
739         if ( !nCurPos )
740             nCurPos = GetPageCount()-1;
741         else
742             nCurPos--;
743     }
744 
745     SelectTabPage( GetPageId( nCurPos ) );
746 }
747 
748 void TabControl::ImplShowFocus()
749 {
750     if ( !GetPageCount() || mpTabCtrlData->mpListBox )
751         return;
752 
753     sal_uInt16                   nCurPos     = GetPagePos( mnCurPageId );
754     tools::Rectangle                aRect       = ImplGetTabRect( nCurPos );
755     const ImplTabItem&       rItem       = mpTabCtrlData->maItemList[ nCurPos ];
756     Size                     aTabSize    = aRect.GetSize();
757     Size aImageSize( 0, 0 );
758     tools::Long                     nTextHeight = GetTextHeight();
759     tools::Long                     nTextWidth  = GetCtrlTextWidth( rItem.maFormatText );
760     sal_uInt16                   nOff;
761 
762     if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::Mono) )
763         nOff = 1;
764     else
765         nOff = 0;
766 
767     if( !! rItem.maTabImage )
768     {
769         aImageSize = rItem.maTabImage.GetSizePixel();
770         if( !rItem.maFormatText.isEmpty() )
771             aImageSize.AdjustWidth(GetTextHeight()/4 );
772     }
773 
774     if( !rItem.maFormatText.isEmpty() )
775     {
776         // show focus around text
777         aRect.SetLeft( aRect.Left()+aImageSize.Width()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1-1 );
778         aRect.SetTop( aRect.Top()+((aTabSize.Height()-nTextHeight)/2)-1-1 );
779         aRect.SetRight( aRect.Left()+nTextWidth+2 );
780         aRect.SetBottom( aRect.Top()+nTextHeight+2 );
781     }
782     else
783     {
784         // show focus around image
785         tools::Long nXPos = aRect.Left()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1;
786         tools::Long nYPos = aRect.Top();
787         if( aImageSize.Height() < aRect.GetHeight() )
788             nYPos += (aRect.GetHeight() - aImageSize.Height())/2;
789 
790         aRect.SetLeft( nXPos - 2 );
791         aRect.SetTop( nYPos - 2 );
792         aRect.SetRight( aRect.Left() + aImageSize.Width() + 4 );
793         aRect.SetBottom( aRect.Top() + aImageSize.Height() + 4 );
794     }
795     ShowFocus( aRect );
796 }
797 
798 void TabControl::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplTabItem const * pItem, const tools::Rectangle& rCurRect,
799                               bool bFirstInGroup, bool bLastInGroup )
800 {
801     if (!pItem->m_bVisible || pItem->maRect.IsEmpty())
802         return;
803 
804     const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
805     tools::Rectangle aRect = pItem->maRect;
806     tools::Long nLeftBottom = aRect.Bottom();
807     tools::Long nRightBottom = aRect.Bottom();
808     bool bLeftBorder = true;
809     bool bRightBorder = true;
810     sal_uInt16 nOff;
811     bool bNativeOK = false;
812 
813     sal_uInt16 nOff2 = 0;
814     sal_uInt16 nOff3 = 0;
815 
816     if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
817         nOff = 1;
818     else
819         nOff = 0;
820 
821     // if this is the active Page, we have to draw a little more
822     if (pItem->id() == mnCurPageId)
823     {
824         nOff2 = 2;
825         if (!ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise)
826             nOff3 = 1;
827     }
828     else
829     {
830         Point aLeftTestPos = aRect.BottomLeft();
831         Point aRightTestPos = aRect.BottomRight();
832         if (aLeftTestPos.Y() == rCurRect.Bottom())
833         {
834             aLeftTestPos.AdjustX( -2 );
835             if (rCurRect.IsInside(aLeftTestPos))
836                 bLeftBorder = false;
837             aRightTestPos.AdjustX(2 );
838             if (rCurRect.IsInside(aRightTestPos))
839                 bRightBorder = false;
840         }
841         else
842         {
843             if (rCurRect.IsInside(aLeftTestPos))
844                 nLeftBottom -= 2;
845             if (rCurRect.IsInside(aRightTestPos))
846                 nRightBottom -= 2;
847         }
848     }
849 
850     ControlState nState = ControlState::NONE;
851 
852     if (pItem->id() == mnCurPageId)
853     {
854         nState |= ControlState::SELECTED;
855         // only the selected item can be focused
856         if (HasFocus())
857             nState |= ControlState::FOCUSED;
858     }
859     if (IsEnabled())
860         nState |= ControlState::ENABLED;
861     if (IsMouseOver() && pItem->maRect.IsInside(GetPointerPosPixel()))
862     {
863         nState |= ControlState::ROLLOVER;
864         for (auto const& item : mpTabCtrlData->maItemList)
865             if ((&item != pItem) && item.m_bVisible && item.maRect.IsInside(GetPointerPosPixel()))
866             {
867                 nState &= ~ControlState::ROLLOVER; // avoid multiple highlighted tabs
868                 break;
869             }
870         assert(nState & ControlState::ROLLOVER);
871     }
872 
873     bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire);
874     if ( bNativeOK )
875     {
876         TabitemValue tiValue(tools::Rectangle(pItem->maRect.Left() + TAB_TABOFFSET_X,
877                                        pItem->maRect.Top() + TAB_TABOFFSET_Y,
878                                        pItem->maRect.Right() - TAB_TABOFFSET_X,
879                                        pItem->maRect.Bottom() - TAB_TABOFFSET_Y));
880         if (pItem->maRect.Left() < 5)
881             tiValue.mnAlignment |= TabitemFlags::LeftAligned;
882         if (pItem->maRect.Right() > mnLastWidth - 5)
883             tiValue.mnAlignment |= TabitemFlags::RightAligned;
884         if (bFirstInGroup)
885             tiValue.mnAlignment |= TabitemFlags::FirstInGroup;
886         if (bLastInGroup)
887             tiValue.mnAlignment |= TabitemFlags::LastInGroup;
888 
889         tools::Rectangle aCtrlRegion( pItem->maRect );
890         aCtrlRegion.AdjustBottom(TabPaneValue::m_nOverlap);
891         bNativeOK = rRenderContext.DrawNativeControl(ControlType::TabItem, ControlPart::Entire,
892                                                      aCtrlRegion, nState, tiValue, OUString() );
893     }
894 
895     if (!bNativeOK)
896     {
897         if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
898         {
899             rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
900             rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2)); // diagonally indented top-left pixel
901             if (bLeftBorder)
902             {
903                 rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
904                                         Point(aRect.Left() - nOff2, nLeftBottom - 1));
905             }
906             rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2),   // top line starting 2px from left border
907                                     Point(aRect.Right() + nOff2 - 3, aRect.Top() - nOff2)); // ending 3px from right border
908 
909             if (bRightBorder)
910             {
911                 rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
912                 rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2),
913                                         Point(aRect.Right() + nOff2 - 2, nRightBottom - 1));
914 
915                 rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
916                 rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 3 - nOff2),
917                                         Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
918             }
919         }
920         else
921         {
922             rRenderContext.SetLineColor(COL_BLACK);
923             rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2));
924             rRenderContext.DrawPixel(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2));
925             if (bLeftBorder)
926             {
927                 rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
928                                         Point(aRect.Left() - nOff2, nLeftBottom - 1));
929             }
930             rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2),
931                                     Point(aRect.Right() - 3, aRect.Top() - nOff2));
932             if (bRightBorder)
933             {
934                 rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 2 - nOff2),
935                                         Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
936             }
937         }
938     }
939 
940     // set font accordingly, current item is painted bold
941     // we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
942     vcl::Font aFont(rRenderContext.GetFont());
943     aFont.SetTransparent(true);
944     rRenderContext.SetFont(aFont);
945 
946     Size aTabSize = aRect.GetSize();
947     Size aImageSize(0, 0);
948     tools::Long nTextHeight = rRenderContext.GetTextHeight();
949     tools::Long nTextWidth = rRenderContext.GetCtrlTextWidth(pItem->maFormatText);
950     if (!!pItem->maTabImage)
951     {
952         aImageSize = pItem->maTabImage.GetSizePixel();
953         if (!pItem->maFormatText.isEmpty())
954             aImageSize.AdjustWidth(GetTextHeight() / 4 );
955     }
956     tools::Long nXPos = aRect.Left() + ((aTabSize.Width() - nTextWidth - aImageSize.Width()) / 2) - nOff - nOff3;
957     tools::Long nYPos = aRect.Top() + ((aTabSize.Height() - nTextHeight) / 2) - nOff3;
958     if (!pItem->maFormatText.isEmpty())
959     {
960         DrawTextFlags nStyle = DrawTextFlags::Mnemonic;
961         if (!pItem->m_bEnabled)
962             nStyle |= DrawTextFlags::Disable;
963 
964         Color aColor(rStyleSettings.GetTabTextColor());
965         if (nState & ControlState::SELECTED)
966             aColor = rStyleSettings.GetTabHighlightTextColor();
967         else if (nState & ControlState::ROLLOVER)
968             aColor = rStyleSettings.GetTabRolloverTextColor();
969 
970         Color aOldColor(rRenderContext.GetTextColor());
971         rRenderContext.SetTextColor(aColor);
972 
973         const tools::Rectangle aOutRect(nXPos + aImageSize.Width(), nYPos,
974                                  nXPos + aImageSize.Width() + nTextWidth, nYPos + nTextHeight);
975         DrawControlText(rRenderContext, aOutRect, pItem->maFormatText, nStyle,
976                         nullptr, nullptr);
977 
978         rRenderContext.SetTextColor(aOldColor);
979     }
980 
981     if (!!pItem->maTabImage)
982     {
983         Point aImgTL( nXPos, aRect.Top() );
984         if (aImageSize.Height() < aRect.GetHeight())
985             aImgTL.AdjustY((aRect.GetHeight() - aImageSize.Height()) / 2 );
986         rRenderContext.DrawImage(aImgTL, pItem->maTabImage, pItem->m_bEnabled ? DrawImageFlags::NONE : DrawImageFlags::Disable );
987     }
988 }
989 
990 bool TabControl::ImplHandleKeyEvent( const KeyEvent& rKeyEvent )
991 {
992     bool bRet = false;
993 
994     if ( GetPageCount() > 1 )
995     {
996         vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
997         sal_uInt16 nKeyCode = aKeyCode.GetCode();
998 
999         if ( aKeyCode.IsMod1() )
1000         {
1001             if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
1002             {
1003                 if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
1004                 {
1005                     ImplActivateTabPage( false );
1006                     bRet = true;
1007                 }
1008             }
1009             else
1010             {
1011                 if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
1012                 {
1013                     ImplActivateTabPage( true );
1014                     bRet = true;
1015                 }
1016             }
1017         }
1018     }
1019 
1020     return bRet;
1021 }
1022 
1023 IMPL_LINK_NOARG(TabControl, ImplListBoxSelectHdl, ListBox&, void)
1024 {
1025     SelectTabPage( GetPageId( mpTabCtrlData->mpListBox->GetSelectedEntryPos() ) );
1026 }
1027 
1028 IMPL_LINK( TabControl, ImplWindowEventListener, VclWindowEvent&, rEvent, void )
1029 {
1030     if ( rEvent.GetId() == VclEventId::WindowKeyInput )
1031     {
1032         // Do not handle events from TabControl or its children, which is done in Notify(), where the events can be consumed.
1033         if ( !IsWindowOrChild( rEvent.GetWindow() ) )
1034         {
1035             KeyEvent* pKeyEvent = static_cast< KeyEvent* >(rEvent.GetData());
1036             ImplHandleKeyEvent( *pKeyEvent );
1037         }
1038     }
1039 }
1040 
1041 void TabControl::MouseButtonDown( const MouseEvent& rMEvt )
1042 {
1043     if (mpTabCtrlData->mpListBox || !rMEvt.IsLeft())
1044         return;
1045 
1046     ImplTabItem *pItem = ImplGetItem(rMEvt.GetPosPixel());
1047     if (pItem && pItem->m_bEnabled)
1048         SelectTabPage(pItem->id());
1049 }
1050 
1051 void TabControl::KeyInput( const KeyEvent& rKEvt )
1052 {
1053     if( mpTabCtrlData->mpListBox )
1054         mpTabCtrlData->mpListBox->KeyInput( rKEvt );
1055     else if ( GetPageCount() > 1 )
1056     {
1057         vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
1058         sal_uInt16  nKeyCode = aKeyCode.GetCode();
1059 
1060         if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_RIGHT) )
1061         {
1062             bool bNext = (nKeyCode == KEY_RIGHT);
1063             ImplActivateTabPage( bNext );
1064         }
1065     }
1066 
1067     Control::KeyInput( rKEvt );
1068 }
1069 
1070 static bool lcl_canPaint(const vcl::RenderContext& rRenderContext, const tools::Rectangle& rDrawRect,
1071                          const tools::Rectangle& rItemRect)
1072 {
1073     vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
1074     aClipRgn.Intersect(rItemRect);
1075     if (!rDrawRect.IsEmpty())
1076         aClipRgn.Intersect(rDrawRect);
1077     return !aClipRgn.IsEmpty();
1078 }
1079 
1080 void TabControl::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
1081 {
1082     if (GetStyle() & WB_NOBORDER)
1083         return;
1084 
1085     Control::Paint(rRenderContext, rRect);
1086 
1087     HideFocus();
1088 
1089     // reformat if needed
1090     tools::Rectangle aRect = ImplGetTabRect(TAB_PAGERECT);
1091 
1092     // find current item
1093     ImplTabItem* pCurItem = nullptr;
1094     for (auto & item : mpTabCtrlData->maItemList)
1095     {
1096         if (item.id() == mnCurPageId)
1097         {
1098             pCurItem = &item;
1099             break;
1100         }
1101     }
1102 
1103     // Draw the TabPage border
1104     const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
1105     tools::Rectangle aCurRect;
1106     aRect.AdjustLeft( -(TAB_OFFSET) );
1107     aRect.AdjustTop( -(TAB_OFFSET) );
1108     aRect.AdjustRight(TAB_OFFSET );
1109     aRect.AdjustBottom(TAB_OFFSET );
1110 
1111     // if we have an invisible tabpage or no tabpage at all the tabpage rect should be
1112     // increased to avoid round corners that might be drawn by a theme
1113     // in this case we're only interested in the top border of the tabpage because the tabitems are used
1114     // standalone (eg impress)
1115     bool bNoTabPage = false;
1116     TabPage* pCurPage = pCurItem ? pCurItem->mpTabPage.get() : nullptr;
1117     if (!pCurPage || !pCurPage->IsVisible())
1118     {
1119         bNoTabPage = true;
1120         aRect.AdjustLeft( -10 );
1121         aRect.AdjustRight(10 );
1122     }
1123 
1124     if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
1125     {
1126         const bool bPaneWithHeader = rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::TabPaneWithHeader);
1127         tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
1128         if (bPaneWithHeader)
1129         {
1130             aRect.SetTop(0);
1131             if (mpTabCtrlData->maItemList.size())
1132             {
1133                 tools::Long nRight = 0;
1134                 for (const auto &item : mpTabCtrlData->maItemList)
1135                     if (item.m_bVisible)
1136                         nRight = item.maRect.Right();
1137                 assert(nRight);
1138                 aHeaderRect.SetRight(nRight);
1139             }
1140         }
1141         const TabPaneValue aTabPaneValue(aHeaderRect, pCurItem ? pCurItem->maRect : tools::Rectangle());
1142 
1143         ControlState nState = ControlState::ENABLED;
1144         if (!IsEnabled())
1145             nState &= ~ControlState::ENABLED;
1146         if (HasFocus())
1147             nState |= ControlState::FOCUSED;
1148 
1149         if (lcl_canPaint(rRenderContext, rRect, aRect))
1150             rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
1151                                              aRect, nState, aTabPaneValue, OUString());
1152 
1153         if (!bPaneWithHeader && rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire)
1154                 && lcl_canPaint(rRenderContext, rRect, aHeaderRect))
1155             rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
1156                                              aHeaderRect, nState, aTabPaneValue, OUString());
1157     }
1158     else
1159     {
1160         tools::Long nTopOff = 1;
1161         if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
1162             rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
1163         else
1164             rRenderContext.SetLineColor(COL_BLACK);
1165         if (pCurItem && !pCurItem->maRect.IsEmpty())
1166         {
1167             aCurRect = pCurItem->maRect;
1168             rRenderContext.DrawLine(aRect.TopLeft(), Point(aCurRect.Left() - 2, aRect.Top()));
1169             if (aCurRect.Right() + 1 < aRect.Right())
1170             {
1171                 rRenderContext.DrawLine(Point(aCurRect.Right(), aRect.Top()), aRect.TopRight());
1172             }
1173             else
1174             {
1175                 nTopOff = 0;
1176             }
1177         }
1178         else
1179             rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
1180 
1181         rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
1182 
1183         if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
1184         {
1185             // if we have not tab page the bottom line of the tab page
1186             // directly touches the tab items, so choose a color that fits seamlessly
1187             if (bNoTabPage)
1188                 rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
1189             else
1190                 rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
1191             rRenderContext.DrawLine(Point(1, aRect.Bottom() - 1), Point(aRect.Right() - 1, aRect.Bottom() - 1));
1192             rRenderContext.DrawLine(Point(aRect.Right() - 1, aRect.Top() + nTopOff), Point(aRect.Right() - 1, aRect.Bottom() - 1));
1193             if (bNoTabPage)
1194                 rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
1195             else
1196                 rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
1197             rRenderContext.DrawLine(Point(0, aRect.Bottom()), Point(aRect.Right(), aRect.Bottom()));
1198             rRenderContext.DrawLine(Point(aRect.Right(), aRect.Top() + nTopOff), Point(aRect.Right(), aRect.Bottom()));
1199         }
1200         else
1201         {
1202             rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
1203             rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
1204         }
1205     }
1206 
1207     if (!mpTabCtrlData->maItemList.empty() && mpTabCtrlData->mpListBox == nullptr)
1208     {
1209         // Some native toolkits (GTK+) draw tabs right-to-left, with an
1210         // overlap between adjacent tabs
1211         bool bDrawTabsRTL = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::TabsDrawRtl);
1212         ImplTabItem* pFirstTab = nullptr;
1213         ImplTabItem* pLastTab = nullptr;
1214         size_t idx;
1215 
1216         // Even though there is a tab overlap with GTK+, the first tab is not
1217         // overlapped on the left side. Other toolkits ignore this option.
1218         if (bDrawTabsRTL)
1219         {
1220             pFirstTab = mpTabCtrlData->maItemList.data();
1221             pLastTab = pFirstTab + mpTabCtrlData->maItemList.size() - 1;
1222             idx = mpTabCtrlData->maItemList.size() - 1;
1223         }
1224         else
1225         {
1226             pLastTab = mpTabCtrlData->maItemList.data();
1227             pFirstTab = pLastTab + mpTabCtrlData->maItemList.size() - 1;
1228             idx = 0;
1229         }
1230 
1231         while (idx < mpTabCtrlData->maItemList.size())
1232         {
1233             ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
1234 
1235             if (pItem != pCurItem && pItem->m_bVisible && lcl_canPaint(rRenderContext, rRect, pItem->maRect))
1236                 ImplDrawItem(rRenderContext, pItem, aCurRect, pItem == pFirstTab, pItem == pLastTab);
1237 
1238             if (bDrawTabsRTL)
1239                 idx--;
1240             else
1241                 idx++;
1242         }
1243 
1244         if (pCurItem && lcl_canPaint(rRenderContext, rRect, pCurItem->maRect))
1245             ImplDrawItem(rRenderContext, pCurItem, aCurRect, pCurItem == pFirstTab, pCurItem == pLastTab);
1246     }
1247 
1248     if (HasFocus())
1249         ImplShowFocus();
1250 
1251     mbSmallInvalidate = true;
1252 }
1253 
1254 void TabControl::setAllocation(const Size &rAllocation)
1255 {
1256     ImplFreeLayoutData();
1257 
1258     if ( !IsReallyShown() )
1259         return;
1260 
1261     if( mpTabCtrlData->mpListBox )
1262     {
1263         // get the listbox' preferred size
1264         Size aTabCtrlSize( GetSizePixel() );
1265         tools::Long nPrefWidth = mpTabCtrlData->mpListBox->get_preferred_size().Width();
1266         if( nPrefWidth > aTabCtrlSize.Width() )
1267             nPrefWidth = aTabCtrlSize.Width();
1268         Size aNewSize( nPrefWidth, LogicToPixel( Size( 12, 12 ), MapMode( MapUnit::MapAppFont ) ).Height() );
1269         Point aNewPos( (aTabCtrlSize.Width() - nPrefWidth) / 2, 0 );
1270         mpTabCtrlData->mpListBox->SetPosSizePixel( aNewPos, aNewSize );
1271     }
1272 
1273     mbFormat = true;
1274 
1275     // resize/position active TabPage
1276     bool bTabPage = ImplPosCurTabPage();
1277 
1278     // check what needs to be invalidated
1279     Size aNewSize = rAllocation;
1280     tools::Long nNewWidth = aNewSize.Width();
1281     for (auto const& item : mpTabCtrlData->maItemList)
1282     {
1283         if (!item.m_bVisible)
1284             continue;
1285         if (!item.mbFullVisible || (item.maRect.Right()-2 >= nNewWidth))
1286         {
1287             mbSmallInvalidate = false;
1288             break;
1289         }
1290     }
1291 
1292     if ( mbSmallInvalidate )
1293     {
1294         tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
1295         aRect.AdjustLeft( -(TAB_OFFSET+TAB_BORDER_LEFT) );
1296         aRect.AdjustTop( -(TAB_OFFSET+TAB_BORDER_TOP) );
1297         aRect.AdjustRight(TAB_OFFSET+TAB_BORDER_RIGHT );
1298         aRect.AdjustBottom(TAB_OFFSET+TAB_BORDER_BOTTOM );
1299         if ( bTabPage )
1300             Invalidate( aRect, InvalidateFlags::NoChildren );
1301         else
1302             Invalidate( aRect );
1303 
1304     }
1305     else
1306     {
1307         if ( bTabPage )
1308             Invalidate( InvalidateFlags::NoChildren );
1309         else
1310             Invalidate();
1311     }
1312 
1313     mbLayoutDirty = false;
1314 }
1315 
1316 void TabControl::SetPosSizePixel(const Point& rNewPos, const Size& rNewSize)
1317 {
1318     Window::SetPosSizePixel(rNewPos, rNewSize);
1319     //if size changed, TabControl::Resize got called already
1320     if (mbLayoutDirty)
1321         setAllocation(rNewSize);
1322 }
1323 
1324 void TabControl::SetSizePixel(const Size& rNewSize)
1325 {
1326     Window::SetSizePixel(rNewSize);
1327     //if size changed, TabControl::Resize got called already
1328     if (mbLayoutDirty)
1329         setAllocation(rNewSize);
1330 }
1331 
1332 void TabControl::SetPosPixel(const Point& rPos)
1333 {
1334     Window::SetPosPixel(rPos);
1335     if (mbLayoutDirty)
1336         setAllocation(GetOutputSizePixel());
1337 }
1338 
1339 void TabControl::Resize()
1340 {
1341     setAllocation(Control::GetOutputSizePixel());
1342 }
1343 
1344 void TabControl::GetFocus()
1345 {
1346     if( ! mpTabCtrlData->mpListBox )
1347     {
1348         ImplShowFocus();
1349         SetInputContext( InputContext( GetFont() ) );
1350     }
1351     else
1352     {
1353         if( mpTabCtrlData->mpListBox->IsReallyVisible() )
1354             mpTabCtrlData->mpListBox->GrabFocus();
1355     }
1356     Control::GetFocus();
1357 }
1358 
1359 void TabControl::LoseFocus()
1360 {
1361     if( mpTabCtrlData && ! mpTabCtrlData->mpListBox )
1362         HideFocus();
1363     Control::LoseFocus();
1364 }
1365 
1366 void TabControl::RequestHelp( const HelpEvent& rHEvt )
1367 {
1368     sal_uInt16 nItemId = rHEvt.KeyboardActivated() ? mnCurPageId : GetPageId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
1369 
1370     if ( nItemId )
1371     {
1372         if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
1373         {
1374             OUString aStr = GetHelpText( nItemId );
1375             if ( !aStr.isEmpty() )
1376             {
1377                 tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
1378                 Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
1379                 aItemRect.SetLeft( aPt.X() );
1380                 aItemRect.SetTop( aPt.Y() );
1381                 aPt = OutputToScreenPixel( aItemRect.BottomRight() );
1382                 aItemRect.SetRight( aPt.X() );
1383                 aItemRect.SetBottom( aPt.Y() );
1384                 Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
1385                 return;
1386             }
1387         }
1388 
1389         // for Quick or Ballon Help, we show the text, if it is cut
1390         if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
1391         {
1392             ImplTabItem* pItem = ImplGetItem( nItemId );
1393             const OUString& rStr = pItem->maText;
1394             if ( rStr != pItem->maFormatText )
1395             {
1396                 tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
1397                 Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
1398                 aItemRect.SetLeft( aPt.X() );
1399                 aItemRect.SetTop( aPt.Y() );
1400                 aPt = OutputToScreenPixel( aItemRect.BottomRight() );
1401                 aItemRect.SetRight( aPt.X() );
1402                 aItemRect.SetBottom( aPt.Y() );
1403                 if ( !rStr.isEmpty() )
1404                 {
1405                     if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
1406                         Help::ShowBalloon( this, aItemRect.Center(), aItemRect, rStr );
1407                     else
1408                         Help::ShowQuickHelp( this, aItemRect, rStr );
1409                     return;
1410                 }
1411             }
1412         }
1413 
1414         if ( rHEvt.GetMode() & HelpEventMode::QUICK )
1415         {
1416             ImplTabItem* pItem = ImplGetItem( nItemId );
1417             const OUString& rHelpText = pItem->maHelpText;
1418             if (!rHelpText.isEmpty())
1419             {
1420                 tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
1421                 Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
1422                 aItemRect.SetLeft( aPt.X() );
1423                 aItemRect.SetTop( aPt.Y() );
1424                 aPt = OutputToScreenPixel( aItemRect.BottomRight() );
1425                 aItemRect.SetRight( aPt.X() );
1426                 aItemRect.SetBottom( aPt.Y() );
1427                 Help::ShowQuickHelp( this, aItemRect, rHelpText );
1428                 return;
1429             }
1430         }
1431     }
1432 
1433     Control::RequestHelp( rHEvt );
1434 }
1435 
1436 void TabControl::Command( const CommandEvent& rCEvt )
1437 {
1438     if( (mpTabCtrlData->mpListBox == nullptr) && (rCEvt.GetCommand() == CommandEventId::ContextMenu) && (GetPageCount() > 1) )
1439     {
1440         Point   aMenuPos;
1441         bool    bMenu;
1442         if ( rCEvt.IsMouseEvent() )
1443         {
1444             aMenuPos = rCEvt.GetMousePosPixel();
1445             bMenu = GetPageId( aMenuPos ) != 0;
1446         }
1447         else
1448         {
1449             aMenuPos = ImplGetTabRect( GetPagePos( mnCurPageId ) ).Center();
1450             bMenu = true;
1451         }
1452 
1453         if ( bMenu )
1454         {
1455             ScopedVclPtrInstance<PopupMenu> aMenu;
1456             for (auto const& item : mpTabCtrlData->maItemList)
1457             {
1458                 aMenu->InsertItem(item.id(), item.maText, MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK);
1459                 if (item.id() == mnCurPageId)
1460                     aMenu->CheckItem(item.id());
1461                 aMenu->SetHelpId(item.id(), OString());
1462             }
1463 
1464             sal_uInt16 nId = aMenu->Execute( this, aMenuPos );
1465             if ( nId && (nId != mnCurPageId) )
1466                 SelectTabPage( nId );
1467             return;
1468         }
1469     }
1470 
1471     Control::Command( rCEvt );
1472 }
1473 
1474 void TabControl::StateChanged( StateChangedType nType )
1475 {
1476     Control::StateChanged( nType );
1477 
1478     if ( nType == StateChangedType::InitShow )
1479     {
1480         ImplPosCurTabPage();
1481         if( mpTabCtrlData->mpListBox )
1482             Resize();
1483     }
1484     else if ( nType == StateChangedType::UpdateMode )
1485     {
1486         if ( IsUpdateMode() )
1487             Invalidate();
1488     }
1489     else if ( (nType == StateChangedType::Zoom)  ||
1490               (nType == StateChangedType::ControlFont) )
1491     {
1492         ImplInitSettings( false );
1493         Invalidate();
1494     }
1495     else if ( nType == StateChangedType::ControlForeground )
1496     {
1497         ImplInitSettings( false );
1498         Invalidate();
1499     }
1500     else if ( nType == StateChangedType::ControlBackground )
1501     {
1502         ImplInitSettings( true );
1503         Invalidate();
1504     }
1505 }
1506 
1507 void TabControl::DataChanged( const DataChangedEvent& rDCEvt )
1508 {
1509     Control::DataChanged( rDCEvt );
1510 
1511     if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
1512          (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
1513          ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
1514           (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
1515     {
1516         ImplInitSettings( true );
1517         Invalidate();
1518     }
1519 }
1520 
1521 ImplTabItem* TabControl::ImplGetItem(const Point& rPt) const
1522 {
1523     ImplTabItem* pFoundItem = nullptr;
1524     int nFound = 0;
1525     for (auto & item : mpTabCtrlData->maItemList)
1526     {
1527         if (item.m_bVisible && item.maRect.IsInside(rPt))
1528         {
1529             nFound++;
1530             pFoundItem = &item;
1531         }
1532     }
1533 
1534     // assure that only one tab is highlighted at a time
1535     assert(nFound <= 1);
1536     return nFound == 1 ? pFoundItem : nullptr;
1537 }
1538 
1539 bool TabControl::PreNotify( NotifyEvent& rNEvt )
1540 {
1541     if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE )
1542     {
1543         const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
1544         if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
1545         {
1546             // trigger redraw if mouse over state has changed
1547             if( IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) )
1548             {
1549                 ImplTabItem *pItem = ImplGetItem(GetPointerPosPixel());
1550                 ImplTabItem *pLastItem = ImplGetItem(GetLastPointerPosPixel());
1551                 if ((pItem != pLastItem) || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
1552                 {
1553                     vcl::Region aClipRgn;
1554                     if (pLastItem)
1555                     {
1556                         // allow for slightly bigger tabitems
1557                         // as used by gtk
1558                         // TODO: query for the correct sizes
1559                         tools::Rectangle aRect(pLastItem->maRect);
1560                         aRect.AdjustLeft( -2 );
1561                         aRect.AdjustRight(2 );
1562                         aRect.AdjustTop( -3 );
1563                         aClipRgn.Union( aRect );
1564                     }
1565 
1566                     if (pItem)
1567                     {
1568                         // allow for slightly bigger tabitems
1569                         // as used by gtk
1570                         // TODO: query for the correct sizes
1571                         tools::Rectangle aRect(pItem->maRect);
1572                         aRect.AdjustLeft( -2 );
1573                         aRect.AdjustRight(2 );
1574                         aRect.AdjustTop( -3 );
1575                         aClipRgn.Union( aRect );
1576                     }
1577 
1578                     if( !aClipRgn.IsEmpty() )
1579                         Invalidate( aClipRgn );
1580                 }
1581             }
1582         }
1583     }
1584 
1585     return Control::PreNotify(rNEvt);
1586 }
1587 
1588 bool TabControl::EventNotify( NotifyEvent& rNEvt )
1589 {
1590     bool bRet = false;
1591 
1592     if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
1593         bRet = ImplHandleKeyEvent( *rNEvt.GetKeyEvent() );
1594 
1595     return bRet || Control::EventNotify( rNEvt );
1596 }
1597 
1598 void TabControl::ActivatePage()
1599 {
1600     maActivateHdl.Call( this );
1601 }
1602 
1603 bool TabControl::DeactivatePage()
1604 {
1605     return !maDeactivateHdl.IsSet() || maDeactivateHdl.Call( this );
1606 }
1607 
1608 void TabControl::SetTabPageSizePixel( const Size& rSize )
1609 {
1610     ImplFreeLayoutData();
1611 
1612     Size aNewSize( rSize );
1613     aNewSize.AdjustWidth(TAB_OFFSET*2 );
1614     tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT,
1615                                       aNewSize.Width(), aNewSize.Height() );
1616     aNewSize.AdjustHeight(aRect.Top()+TAB_OFFSET );
1617     Window::SetOutputSizePixel( aNewSize );
1618 }
1619 
1620 void TabControl::InsertPage( sal_uInt16 nPageId, const OUString& rText,
1621                              sal_uInt16 nPos )
1622 {
1623     SAL_WARN_IF( !nPageId, "vcl", "TabControl::InsertPage(): PageId == 0" );
1624     SAL_WARN_IF( GetPagePos( nPageId ) != TAB_PAGE_NOTFOUND, "vcl",
1625                 "TabControl::InsertPage(): PageId already exists" );
1626 
1627     // insert new page item
1628     ImplTabItem* pItem = nullptr;
1629     if( nPos == TAB_APPEND || size_t(nPos) >= mpTabCtrlData->maItemList.size() )
1630     {
1631         mpTabCtrlData->maItemList.emplace_back(nPageId);
1632         pItem = &mpTabCtrlData->maItemList.back();
1633         if( mpTabCtrlData->mpListBox )
1634             mpTabCtrlData->mpListBox->InsertEntry( rText );
1635     }
1636     else
1637     {
1638         std::vector< ImplTabItem >::iterator new_it =
1639             mpTabCtrlData->maItemList.emplace(mpTabCtrlData->maItemList.begin() + nPos, nPageId);
1640         pItem = &(*new_it);
1641         if( mpTabCtrlData->mpListBox )
1642             mpTabCtrlData->mpListBox->InsertEntry( rText, nPos);
1643     }
1644     if( mpTabCtrlData->mpListBox )
1645     {
1646         if( ! mnCurPageId )
1647             mpTabCtrlData->mpListBox->SelectEntryPos( 0 );
1648         mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
1649     }
1650 
1651     // set current page id
1652     if ( !mnCurPageId )
1653         mnCurPageId = nPageId;
1654 
1655     // init new page item
1656     pItem->maText           = rText;
1657     pItem->mbFullVisible    = false;
1658 
1659     mbFormat = true;
1660     if ( IsUpdateMode() )
1661         Invalidate();
1662 
1663     ImplFreeLayoutData();
1664     if( mpTabCtrlData->mpListBox ) // reposition/resize listbox
1665         Resize();
1666 
1667     CallEventListeners( VclEventId::TabpageInserted, reinterpret_cast<void*>(nPageId) );
1668 }
1669 
1670 void TabControl::RemovePage( sal_uInt16 nPageId )
1671 {
1672     sal_uInt16 nPos = GetPagePos( nPageId );
1673 
1674     // does the item exist ?
1675     if ( nPos == TAB_PAGE_NOTFOUND )
1676         return;
1677 
1678     //remove page item
1679     std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin() + nPos;
1680     bool bIsCurrentPage = (it->id() == mnCurPageId);
1681     mpTabCtrlData->maItemList.erase( it );
1682     if( mpTabCtrlData->mpListBox )
1683     {
1684         mpTabCtrlData->mpListBox->RemoveEntry( nPos );
1685         mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
1686     }
1687 
1688     // If current page is removed, then first page gets the current page
1689     if ( bIsCurrentPage  )
1690     {
1691         mnCurPageId = 0;
1692 
1693         if( ! mpTabCtrlData->maItemList.empty() )
1694         {
1695             // don't do this by simply setting mnCurPageId to pFirstItem->id()
1696             // this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
1697             // instead, call SetCurPageId
1698             // without this, the next (outside) call to SetCurPageId with the id of the first page
1699             // will result in doing nothing (as we assume that nothing changed, then), and the page
1700             // will never be shown.
1701             // 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
1702 
1703             SetCurPageId(mpTabCtrlData->maItemList[0].id());
1704         }
1705     }
1706 
1707     mbFormat = true;
1708     if ( IsUpdateMode() )
1709         Invalidate();
1710 
1711     ImplFreeLayoutData();
1712 
1713     CallEventListeners( VclEventId::TabpageRemoved, reinterpret_cast<void*>(nPageId) );
1714 }
1715 
1716 void TabControl::SetPageEnabled( sal_uInt16 i_nPageId, bool i_bEnable )
1717 {
1718     ImplTabItem* pItem = ImplGetItem( i_nPageId );
1719 
1720     if (!pItem || pItem->m_bEnabled == i_bEnable)
1721         return;
1722 
1723     pItem->m_bEnabled = i_bEnable;
1724     if (!pItem->m_bVisible)
1725         return;
1726 
1727     mbFormat = true;
1728     if( mpTabCtrlData->mpListBox )
1729         mpTabCtrlData->mpListBox->SetEntryFlags( GetPagePos( i_nPageId ),
1730                                                  i_bEnable ? ListBoxEntryFlags::NONE : (ListBoxEntryFlags::DisableSelection | ListBoxEntryFlags::DrawDisabled) );
1731 
1732     // SetCurPageId will change to a valid page
1733     if (pItem->id() == mnCurPageId)
1734         SetCurPageId( mnCurPageId );
1735     else if ( IsUpdateMode() )
1736         Invalidate();
1737 }
1738 
1739 void TabControl::SetPageVisible( sal_uInt16 nPageId, bool bVisible )
1740 {
1741     ImplTabItem* pItem = ImplGetItem( nPageId );
1742     if (!pItem || pItem->m_bVisible == bVisible)
1743         return;
1744 
1745     pItem->m_bVisible = bVisible;
1746     if (!bVisible)
1747     {
1748         if (pItem->mbFullVisible)
1749             mbSmallInvalidate = false;
1750         pItem->mbFullVisible = false;
1751         pItem->maRect.SetEmpty();
1752     }
1753     mbFormat = true;
1754 
1755     // SetCurPageId will change to a valid page
1756     if (pItem->id() == mnCurPageId)
1757         SetCurPageId(mnCurPageId);
1758     else if (IsUpdateMode())
1759         Invalidate();
1760 }
1761 
1762 sal_uInt16 TabControl::GetPageCount() const
1763 {
1764     return static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
1765 }
1766 
1767 sal_uInt16 TabControl::GetPageId( sal_uInt16 nPos ) const
1768 {
1769     if( size_t(nPos) < mpTabCtrlData->maItemList.size() )
1770         return mpTabCtrlData->maItemList[nPos].id();
1771     return 0;
1772 }
1773 
1774 sal_uInt16 TabControl::GetPagePos( sal_uInt16 nPageId ) const
1775 {
1776     sal_uInt16 nPos = 0;
1777     for (auto const& item : mpTabCtrlData->maItemList)
1778     {
1779         if (item.id() == nPageId)
1780             return nPos;
1781         ++nPos;
1782     }
1783 
1784     return TAB_PAGE_NOTFOUND;
1785 }
1786 
1787 sal_uInt16 TabControl::GetPageId( const Point& rPos ) const
1788 {
1789     Size winSize = Control::GetOutputSizePixel();
1790     const auto &rList = mpTabCtrlData->maItemList;
1791     const auto it = std::find_if(rList.begin(), rList.end(), [&rPos, &winSize, this](const auto &item) {
1792         return const_cast<TabControl*>(this)->ImplGetTabRect(&item, winSize.Width(), winSize.Height()).IsInside(rPos); });
1793     return (it != rList.end()) ? it->id() : 0;
1794 }
1795 
1796 sal_uInt16 TabControl::GetPageId( const OString& rName ) const
1797 {
1798     const auto &rList = mpTabCtrlData->maItemList;
1799     const auto it = std::find_if(rList.begin(), rList.end(), [&rName](const auto &item) {
1800         return item.maTabName == rName; });
1801     return (it != rList.end()) ? it->id() : 0;
1802 }
1803 
1804 void TabControl::SetCurPageId( sal_uInt16 nPageId )
1805 {
1806     sal_uInt16 nPos = GetPagePos( nPageId );
1807     while (nPos != TAB_PAGE_NOTFOUND && !mpTabCtrlData->maItemList[nPos].m_bEnabled)
1808     {
1809         nPos++;
1810         if( size_t(nPos) >= mpTabCtrlData->maItemList.size() )
1811             nPos = 0;
1812         if (mpTabCtrlData->maItemList[nPos].id() == nPageId)
1813             break;
1814     }
1815 
1816     if( nPos == TAB_PAGE_NOTFOUND )
1817         return;
1818 
1819     nPageId = mpTabCtrlData->maItemList[nPos].id();
1820     if ( nPageId == mnCurPageId )
1821     {
1822         if ( mnActPageId )
1823             mnActPageId = nPageId;
1824         return;
1825     }
1826 
1827     if ( mnActPageId )
1828         mnActPageId = nPageId;
1829     else
1830     {
1831         mbFormat = true;
1832         sal_uInt16 nOldId = mnCurPageId;
1833         mnCurPageId = nPageId;
1834         ImplChangeTabPage( nPageId, nOldId );
1835     }
1836 }
1837 
1838 sal_uInt16 TabControl::GetCurPageId() const
1839 {
1840     if ( mnActPageId )
1841         return mnActPageId;
1842     else
1843         return mnCurPageId;
1844 }
1845 
1846 void TabControl::SelectTabPage( sal_uInt16 nPageId )
1847 {
1848     if ( !nPageId || (nPageId == mnCurPageId) )
1849         return;
1850 
1851     ImplFreeLayoutData();
1852 
1853     CallEventListeners( VclEventId::TabpageDeactivate, reinterpret_cast<void*>(mnCurPageId) );
1854     if ( DeactivatePage() )
1855     {
1856         mnActPageId = nPageId;
1857         ActivatePage();
1858         // Page could have been switched by the Activate handler
1859         nPageId = mnActPageId;
1860         mnActPageId = 0;
1861         SetCurPageId( nPageId );
1862         if( mpTabCtrlData->mpListBox )
1863             mpTabCtrlData->mpListBox->SelectEntryPos( GetPagePos( nPageId ) );
1864         CallEventListeners( VclEventId::TabpageActivate, reinterpret_cast<void*>(nPageId) );
1865     }
1866 }
1867 
1868 void TabControl::SetTabPage( sal_uInt16 nPageId, TabPage* pTabPage )
1869 {
1870     ImplTabItem* pItem = ImplGetItem( nPageId );
1871 
1872     if ( !pItem || (pItem->mpTabPage.get() == pTabPage) )
1873         return;
1874 
1875     if ( pTabPage )
1876     {
1877         if ( IsDefaultSize() )
1878             SetTabPageSizePixel( pTabPage->GetSizePixel() );
1879 
1880         // only set here, so that Resize does not reposition TabPage
1881         pItem->mpTabPage = pTabPage;
1882         queue_resize();
1883 
1884         if (pItem->id() == mnCurPageId)
1885             ImplChangeTabPage(pItem->id(), 0);
1886     }
1887     else
1888     {
1889         pItem->mpTabPage = nullptr;
1890         queue_resize();
1891     }
1892 }
1893 
1894 TabPage* TabControl::GetTabPage( sal_uInt16 nPageId ) const
1895 {
1896     ImplTabItem* pItem = ImplGetItem( nPageId );
1897 
1898     if ( pItem )
1899         return pItem->mpTabPage;
1900     else
1901         return nullptr;
1902 }
1903 
1904 void TabControl::SetPageText( sal_uInt16 nPageId, const OUString& rText )
1905 {
1906     ImplTabItem* pItem = ImplGetItem( nPageId );
1907 
1908     if ( !pItem || pItem->maText == rText )
1909         return;
1910 
1911     pItem->maText = rText;
1912     mbFormat = true;
1913     if( mpTabCtrlData->mpListBox )
1914     {
1915         sal_uInt16 nPos = GetPagePos( nPageId );
1916         mpTabCtrlData->mpListBox->RemoveEntry( nPos );
1917         mpTabCtrlData->mpListBox->InsertEntry( rText, nPos );
1918     }
1919     if ( IsUpdateMode() )
1920         Invalidate();
1921     ImplFreeLayoutData();
1922     CallEventListeners( VclEventId::TabpagePageTextChanged, reinterpret_cast<void*>(nPageId) );
1923 }
1924 
1925 OUString const & TabControl::GetPageText( sal_uInt16 nPageId ) const
1926 {
1927     ImplTabItem* pItem = ImplGetItem( nPageId );
1928 
1929     assert( pItem );
1930 
1931     return pItem->maText;
1932 }
1933 
1934 void TabControl::SetHelpText( sal_uInt16 nPageId, const OUString& rText )
1935 {
1936     ImplTabItem* pItem = ImplGetItem( nPageId );
1937 
1938     assert( pItem );
1939 
1940     pItem->maHelpText = rText;
1941 }
1942 
1943 const OUString& TabControl::GetHelpText( sal_uInt16 nPageId ) const
1944 {
1945     ImplTabItem* pItem = ImplGetItem( nPageId );
1946     assert( pItem );
1947     return pItem->maHelpText;
1948 }
1949 
1950 void TabControl::SetAccessibleName(sal_uInt16 nPageId, const OUString& rName)
1951 {
1952     ImplTabItem* pItem = ImplGetItem( nPageId );
1953     assert( pItem );
1954     pItem->maAccessibleName = rName;
1955 }
1956 
1957 OUString TabControl::GetAccessibleName( sal_uInt16 nPageId ) const
1958 {
1959     ImplTabItem* pItem = ImplGetItem( nPageId );
1960     assert( pItem );
1961     if (!pItem->maAccessibleName.isEmpty())
1962         return pItem->maAccessibleName;
1963     return OutputDevice::GetNonMnemonicString(pItem->maText);
1964 }
1965 
1966 void TabControl::SetAccessibleDescription(sal_uInt16 nPageId, const OUString& rDesc)
1967 {
1968     ImplTabItem* pItem = ImplGetItem( nPageId );
1969     assert( pItem );
1970     pItem->maAccessibleDescription = rDesc;
1971 }
1972 
1973 OUString TabControl::GetAccessibleDescription( sal_uInt16 nPageId ) const
1974 {
1975     ImplTabItem* pItem = ImplGetItem( nPageId );
1976     assert( pItem );
1977     if (!pItem->maAccessibleDescription.isEmpty())
1978         return pItem->maAccessibleDescription;
1979     return pItem->maHelpText;
1980 }
1981 
1982 void TabControl::SetPageName( sal_uInt16 nPageId, const OString& rName ) const
1983 {
1984     ImplTabItem* pItem = ImplGetItem( nPageId );
1985 
1986     if ( pItem )
1987         pItem->maTabName = rName;
1988 }
1989 
1990 OString TabControl::GetPageName( sal_uInt16 nPageId ) const
1991 {
1992     ImplTabItem* pItem = ImplGetItem( nPageId );
1993 
1994     if (pItem)
1995         return pItem->maTabName;
1996 
1997     return OString();
1998 }
1999 
2000 void TabControl::SetPageImage( sal_uInt16 i_nPageId, const Image& i_rImage )
2001 {
2002     ImplTabItem* pItem = ImplGetItem( i_nPageId );
2003 
2004     if ( pItem )
2005     {
2006         pItem->maTabImage = i_rImage;
2007         mbFormat = true;
2008         if ( IsUpdateMode() )
2009             Invalidate();
2010     }
2011 }
2012 
2013 tools::Rectangle TabControl::GetCharacterBounds( sal_uInt16 nPageId, tools::Long nIndex ) const
2014 {
2015     tools::Rectangle aRet;
2016 
2017     if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
2018         FillLayoutData();
2019 
2020     if( HasLayoutData() )
2021     {
2022         std::unordered_map< int, int >::const_iterator it = mpTabCtrlData->maLayoutPageIdToLine.find( static_cast<int>(nPageId) );
2023         if( it != mpTabCtrlData->maLayoutPageIdToLine.end() )
2024         {
2025             Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( it->second );
2026             if( (aPair.B() - aPair.A()) >= nIndex )
2027                 aRet = mpControlData->mpLayoutData->GetCharacterBounds( aPair.A() + nIndex );
2028         }
2029     }
2030 
2031     return aRet;
2032 }
2033 
2034 tools::Long TabControl::GetIndexForPoint( const Point& rPoint, sal_uInt16& rPageId ) const
2035 {
2036     tools::Long nRet = -1;
2037 
2038     if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
2039         FillLayoutData();
2040 
2041     if( HasLayoutData() )
2042     {
2043         int nIndex = mpControlData->mpLayoutData->GetIndexForPoint( rPoint );
2044         if( nIndex != -1 )
2045         {
2046             // what line (->pageid) is this index in ?
2047             int nLines = mpControlData->mpLayoutData->GetLineCount();
2048             int nLine = -1;
2049             while( ++nLine < nLines )
2050             {
2051                 Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( nLine );
2052                 if( aPair.A() <= nIndex && aPair.B() >= nIndex )
2053                 {
2054                     nRet = nIndex - aPair.A();
2055                     rPageId = static_cast<sal_uInt16>(mpTabCtrlData->maLayoutLineToPageId[ nLine ]);
2056                     break;
2057                 }
2058             }
2059         }
2060     }
2061 
2062     return nRet;
2063 }
2064 
2065 void TabControl::FillLayoutData() const
2066 {
2067     mpTabCtrlData->maLayoutLineToPageId.clear();
2068     mpTabCtrlData->maLayoutPageIdToLine.clear();
2069     const_cast<TabControl*>(this)->Invalidate();
2070 }
2071 
2072 tools::Rectangle TabControl::GetTabBounds( sal_uInt16 nPageId ) const
2073 {
2074     tools::Rectangle aRet;
2075 
2076     ImplTabItem* pItem = ImplGetItem( nPageId );
2077     if (pItem && pItem->m_bVisible)
2078         aRet = pItem->maRect;
2079 
2080     return aRet;
2081 }
2082 
2083 Size TabControl::ImplCalculateRequisition(sal_uInt16& nHeaderHeight) const
2084 {
2085     Size aOptimalPageSize(0, 0);
2086 
2087     sal_uInt16 nOrigPageId = GetCurPageId();
2088     for (auto const& item : mpTabCtrlData->maItemList)
2089     {
2090         const TabPage *pPage = item.mpTabPage;
2091         //it's a real nuisance if the page is not inserted yet :-(
2092         //We need to force all tabs to exist to get overall optimal size for dialog
2093         if (!pPage)
2094         {
2095             TabControl *pThis = const_cast<TabControl*>(this);
2096             pThis->SetCurPageId(item.id());
2097             pThis->ActivatePage();
2098             pPage = item.mpTabPage;
2099         }
2100 
2101         if (!pPage)
2102             continue;
2103 
2104         Size aPageSize(VclContainer::getLayoutRequisition(*pPage));
2105 
2106         if (aPageSize.Width() > aOptimalPageSize.Width())
2107             aOptimalPageSize.setWidth( aPageSize.Width() );
2108         if (aPageSize.Height() > aOptimalPageSize.Height())
2109             aOptimalPageSize.setHeight( aPageSize.Height() );
2110     }
2111 
2112     //fdo#61940 If we were forced to activate pages in order to on-demand
2113     //create them to get their optimal size, then switch back to the original
2114     //page and re-activate it
2115     if (nOrigPageId != GetCurPageId())
2116     {
2117         TabControl *pThis = const_cast<TabControl*>(this);
2118         pThis->SetCurPageId(nOrigPageId);
2119         pThis->ActivatePage();
2120     }
2121 
2122     tools::Long nTabLabelsBottom = 0, nTabLabelsRight = 0;
2123     for (sal_uInt16 nPos(0), sizeList(static_cast <sal_uInt16> (mpTabCtrlData->maItemList.size()));
2124             nPos < sizeList; ++nPos)
2125     {
2126         TabControl* pThis = const_cast<TabControl*>(this);
2127 
2128         tools::Rectangle aTabRect = pThis->ImplGetTabRect(nPos, aOptimalPageSize.Width(), LONG_MAX);
2129         if (aTabRect.Bottom() > nTabLabelsBottom)
2130         {
2131             nTabLabelsBottom = aTabRect.Bottom();
2132             nHeaderHeight = nTabLabelsBottom;
2133         }
2134         if (!aTabRect.IsEmpty() && aTabRect.Right() > nTabLabelsRight)
2135             nTabLabelsRight = aTabRect.Right();
2136     }
2137 
2138     Size aOptimalSize(aOptimalPageSize);
2139     aOptimalSize.AdjustHeight(nTabLabelsBottom );
2140     aOptimalSize.setWidth( std::max(nTabLabelsRight, aOptimalSize.Width()) );
2141 
2142     aOptimalSize.AdjustWidth(TAB_OFFSET * 2 );
2143     aOptimalSize.AdjustHeight(TAB_OFFSET * 2 );
2144 
2145     return aOptimalSize;
2146 }
2147 
2148 Size TabControl::calculateRequisition() const
2149 {
2150     sal_uInt16 nHeaderHeight;
2151     return ImplCalculateRequisition(nHeaderHeight);
2152 }
2153 
2154 Size TabControl::GetOptimalSize() const
2155 {
2156     return calculateRequisition();
2157 }
2158 
2159 void TabControl::queue_resize(StateChangedType eReason)
2160 {
2161     mbLayoutDirty = true;
2162     Window::queue_resize(eReason);
2163 }
2164 
2165 std::vector<sal_uInt16> TabControl::GetPageIDs() const
2166 {
2167     std::vector<sal_uInt16> aIDs;
2168     for (auto const& item : mpTabCtrlData->maItemList)
2169     {
2170         aIDs.push_back(item.id());
2171     }
2172 
2173     return aIDs;
2174 }
2175 
2176 FactoryFunction TabControl::GetUITestFactory() const
2177 {
2178     return TabControlUIObject::create;
2179 }
2180 
2181 void TabControl::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
2182 {
2183     rJsonWriter.put("id", get_id());
2184     rJsonWriter.put("type", "tabcontrol");
2185     rJsonWriter.put("selected", GetCurPageId());
2186 
2187     {
2188         auto childrenNode = rJsonWriter.startArray("children");
2189         for (int i = 0; i < GetChildCount(); i++)
2190         {
2191             vcl::Window* pChild = GetChild(i);
2192 
2193             if (pChild)
2194             {
2195                 auto childNode = rJsonWriter.startStruct();
2196                 pChild->DumpAsPropertyTree(rJsonWriter);
2197 
2198                 if (!pChild->IsVisible())
2199                     rJsonWriter.put("hidden", "true");
2200             }
2201         }
2202     }
2203     {
2204         auto tabsNode = rJsonWriter.startArray("tabs");
2205         for(auto id : GetPageIDs())
2206         {
2207             auto tabNode = rJsonWriter.startStruct();
2208             rJsonWriter.put("text", GetPageText(id));
2209             rJsonWriter.put("id", id);
2210             rJsonWriter.put("name", GetPageName(id));
2211         }
2212     }
2213 }
2214 
2215 sal_uInt16 NotebookbarTabControlBase::m_nHeaderHeight = 0;
2216 
2217 IMPL_LINK_NOARG(NotebookbarTabControlBase, OpenMenu, Button*, void)
2218 {
2219     m_aIconClickHdl.Call(static_cast<NotebookBar*>(GetParent()->GetParent()));
2220 }
2221 
2222 NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window* pParent)
2223     : TabControl(pParent, WB_STDTABCONTROL)
2224     , bLastContextWasSupported(true)
2225     , eLastContext(vcl::EnumContext::Context::Any)
2226 {
2227     m_pOpenMenu = VclPtr<PushButton>::Create( this , WB_CENTER | WB_VCENTER );
2228     m_pOpenMenu->SetClickHdl(LINK(this, NotebookbarTabControlBase, OpenMenu));
2229     m_pOpenMenu->SetModeImage(Image(StockImage::Yes, SV_RESID_BITMAP_NOTEBOOKBAR));
2230     m_pOpenMenu->SetSizePixel(m_pOpenMenu->GetOptimalSize());
2231     m_pOpenMenu->Show();
2232 }
2233 
2234 NotebookbarTabControlBase::~NotebookbarTabControlBase()
2235 {
2236     disposeOnce();
2237 }
2238 
2239 void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext )
2240 {
2241     if (eLastContext == eContext)
2242         return;
2243 
2244     bool bHandled = false;
2245 
2246     for (int nChild = 0; nChild < GetPageCount(); ++nChild)
2247     {
2248         sal_uInt16 nPageId = TabControl::GetPageId(nChild);
2249         TabPage* pPage = GetTabPage(nPageId);
2250 
2251         if (pPage)
2252         {
2253             SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any));
2254 
2255             if (!bHandled && bLastContextWasSupported
2256                 && pPage->HasContext(vcl::EnumContext::Context::Default))
2257             {
2258                 SetCurPageId(nPageId);
2259             }
2260 
2261             if (pPage->HasContext(eContext) && eContext != vcl::EnumContext::Context::Any)
2262             {
2263                 SetCurPageId(nPageId);
2264                 bHandled = true;
2265                 bLastContextWasSupported = true;
2266             }
2267         }
2268     }
2269 
2270     if (!bHandled)
2271         bLastContextWasSupported = false;
2272     eLastContext = eContext;
2273 }
2274 
2275 void NotebookbarTabControlBase::dispose()
2276 {
2277     m_pShortcuts.disposeAndClear();
2278     m_pOpenMenu.disposeAndClear();
2279     TabControl::dispose();
2280 }
2281 
2282 void NotebookbarTabControlBase::SetToolBox( ToolBox* pToolBox )
2283 {
2284     m_pShortcuts.set( pToolBox );
2285 }
2286 
2287 void NotebookbarTabControlBase::SetIconClickHdl( Link<NotebookBar*, void> aHdl )
2288 {
2289     m_aIconClickHdl = aHdl;
2290 }
2291 
2292 static bool lcl_isValidPage(const ImplTabItem& rItem, bool& bFound)
2293 {
2294     if (rItem.m_bVisible && rItem.m_bEnabled)
2295         bFound = true;
2296     return bFound;
2297 }
2298 
2299 void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext )
2300 {
2301     const sal_uInt16 nOldPos = GetPagePos(GetCurPageId());
2302     bool bFound = false;
2303     sal_Int32 nCurPos = nOldPos;
2304 
2305     if (bNext)
2306     {
2307         for (nCurPos++; nCurPos < GetPageCount(); nCurPos++)
2308             if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
2309                 break;
2310     }
2311     else
2312     {
2313         for (nCurPos--; nCurPos >= 0; nCurPos--)
2314             if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
2315                 break;
2316     }
2317 
2318     if (!bFound)
2319         nCurPos = nOldPos;
2320     SelectTabPage( TabControl::GetPageId( nCurPos ) );
2321 }
2322 
2323 sal_uInt16 NotebookbarTabControlBase::GetHeaderHeight()
2324 {
2325     return m_nHeaderHeight;
2326 }
2327 
2328 bool NotebookbarTabControlBase::ImplPlaceTabs( tools::Long nWidth )
2329 {
2330     if ( nWidth <= 0 )
2331         return false;
2332     if ( mpTabCtrlData->maItemList.empty() )
2333         return false;
2334     if (!m_pOpenMenu || m_pOpenMenu->isDisposed())
2335         return false;
2336 
2337     const tools::Long nHamburgerWidth = m_pOpenMenu->GetSizePixel().Width();
2338     tools::Long nMaxWidth = nWidth - nHamburgerWidth;
2339     tools::Long nShortcutsWidth = m_pShortcuts != nullptr ? m_pShortcuts->GetSizePixel().getWidth() + 1 : 0;
2340     tools::Long nFullWidth = nShortcutsWidth;
2341 
2342     const tools::Long nOffsetX = 2 + nShortcutsWidth;
2343     const tools::Long nOffsetY = 2;
2344 
2345     //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
2346     //of ugly bare tabs on lines of their own
2347 
2348     for (auto & item : mpTabCtrlData->maItemList)
2349     {
2350         tools::Long nTabWidth = 0;
2351         if (item.m_bVisible)
2352         {
2353             nTabWidth = ImplGetItemSize(&item, nMaxWidth).getWidth();
2354             if (!item.maText.isEmpty() && nTabWidth < 100)
2355                 nTabWidth = 100;
2356         }
2357         nFullWidth += nTabWidth;
2358     }
2359 
2360     tools::Long nX = nOffsetX;
2361     tools::Long nY = nOffsetY;
2362 
2363     tools::Long nLineWidthAry[100];
2364     nLineWidthAry[0] = 0;
2365 
2366     for (auto & item : mpTabCtrlData->maItemList)
2367     {
2368         if (!item.m_bVisible)
2369             continue;
2370 
2371         Size aSize = ImplGetItemSize( &item, nMaxWidth );
2372 
2373         // set minimum tab size
2374         if( nFullWidth < nMaxWidth && !item.maText.isEmpty() && aSize.getWidth() < 100)
2375             aSize.setWidth( 100 );
2376 
2377         if( !item.maText.isEmpty() && aSize.getHeight() < 28 )
2378             aSize.setHeight( 28 );
2379 
2380         tools::Rectangle aNewRect( Point( nX, nY ), aSize );
2381         if ( mbSmallInvalidate && (item.maRect != aNewRect) )
2382             mbSmallInvalidate = false;
2383 
2384         item.maRect = aNewRect;
2385         item.mnLine = 0;
2386         item.mbFullVisible = true;
2387 
2388         nLineWidthAry[0] += aSize.Width();
2389         nX += aSize.Width();
2390     }
2391 
2392     // we always have only one line of tabs
2393     lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
2394 
2395     // position the shortcutbox
2396     if (m_pShortcuts)
2397     {
2398         tools::Long nPosY = (m_nHeaderHeight - m_pShortcuts->GetSizePixel().getHeight()) / 2;
2399         m_pShortcuts->SetPosPixel(Point(0, nPosY));
2400     }
2401 
2402     tools::Long nPosY = (m_nHeaderHeight - m_pOpenMenu->GetSizePixel().getHeight()) / 2;
2403     // position the menu
2404     m_pOpenMenu->SetPosPixel(Point(nWidth - nHamburgerWidth, nPosY));
2405 
2406     return true;
2407 }
2408 
2409 Size NotebookbarTabControlBase::calculateRequisition() const
2410 {
2411     return TabControl::ImplCalculateRequisition(m_nHeaderHeight);
2412 }
2413 
2414 Control* NotebookbarTabControlBase::GetOpenMenu()
2415 {
2416     return m_pOpenMenu;
2417 }
2418 
2419 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2420