bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / control / tabctrl.cxx
blob1f32f7de8c1a84aeae4b92fb3c1a1edff0537e7a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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.hxx>
24 #include <vcl/svapp.hxx>
25 #include <vcl/help.hxx>
26 #include <vcl/event.hxx>
27 #include <vcl/menu.hxx>
28 #include <vcl/button.hxx>
29 #include <vcl/tabpage.hxx>
30 #include <vcl/tabctrl.hxx>
31 #include <vcl/controllayout.hxx>
32 #include <vcl/layout.hxx>
33 #include <vcl/lstbox.hxx>
34 #include <vcl/settings.hxx>
35 #include <vcl/uitest/uiobject.hxx>
36 #include <bitmaps.hlst>
38 #include <controldata.hxx>
39 #include <svdata.hxx>
40 #include <window.h>
42 #include <unordered_map>
43 #include <vector>
45 class ImplTabItem final
47 sal_uInt16 m_nId;
49 public:
50 VclPtr<TabPage> mpTabPage;
51 OUString maText;
52 OUString maFormatText;
53 OUString maHelpText;
54 OString maTabName;
55 tools::Rectangle maRect;
56 sal_uInt16 mnLine;
57 bool mbFullVisible;
58 bool m_bEnabled; ///< the tab / page is selectable
59 bool m_bVisible; ///< the tab / page can be visible
60 Image maTabImage;
62 ImplTabItem(sal_uInt16 nId);
64 sal_uInt16 id() const { return m_nId; }
67 ImplTabItem::ImplTabItem(sal_uInt16 nId)
68 : m_nId(nId)
69 , mnLine(0)
70 , mbFullVisible(false)
71 , m_bEnabled(true)
72 , m_bVisible(true)
76 struct ImplTabCtrlData
78 std::unordered_map< int, int > maLayoutPageIdToLine;
79 std::unordered_map< int, int > maLayoutLineToPageId;
80 Point maItemsOffset; // offset of the tabitems
81 std::vector< ImplTabItem > maItemList;
82 VclPtr<ListBox> mpListBox;
85 // for the Tab positions
86 #define TAB_PAGERECT 0xFFFF
87 #define HAMBURGER_DIM 28
89 void TabControl::ImplInit( vcl::Window* pParent, WinBits nStyle )
91 mbLayoutDirty = true;
93 if ( !(nStyle & WB_NOTABSTOP) )
94 nStyle |= WB_TABSTOP;
95 if ( !(nStyle & WB_NOGROUP) )
96 nStyle |= WB_GROUP;
97 if ( !(nStyle & WB_NODIALOGCONTROL) )
98 nStyle |= WB_DIALOGCONTROL;
100 Control::ImplInit( pParent, nStyle, nullptr );
102 mnLastWidth = 0;
103 mnLastHeight = 0;
104 mnActPageId = 0;
105 mnCurPageId = 0;
106 mbFormat = true;
107 mbRestoreHelpId = false;
108 mbSmallInvalidate = false;
109 mpTabCtrlData.reset(new ImplTabCtrlData);
110 mpTabCtrlData->mpListBox = nullptr;
112 ImplInitSettings( true );
114 if( nStyle & WB_DROPDOWN )
116 mpTabCtrlData->mpListBox = VclPtr<ListBox>::Create( this, WB_DROPDOWN );
117 mpTabCtrlData->mpListBox->SetPosSizePixel( Point( 0, 0 ), Size( 200, 20 ) );
118 mpTabCtrlData->mpListBox->SetSelectHdl( LINK( this, TabControl, ImplListBoxSelectHdl ) );
119 mpTabCtrlData->mpListBox->Show();
122 // if the tabcontrol is drawn (ie filled) by a native widget, make sure all controls will have transparent background
123 // otherwise they will paint with a wrong background
124 if( IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire) )
125 EnableChildTransparentMode();
127 if (pParent && pParent->IsDialog())
128 pParent->AddChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
131 const vcl::Font& TabControl::GetCanonicalFont( const StyleSettings& _rStyle ) const
133 return _rStyle.GetTabFont();
136 const Color& TabControl::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
138 return _rStyle.GetTabTextColor();
141 void TabControl::ImplInitSettings( bool bBackground )
143 Control::ImplInitSettings();
145 if ( bBackground )
147 vcl::Window* pParent = GetParent();
148 if ( !IsControlBackground() &&
149 (pParent->IsChildTransparentModeEnabled()
150 || IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire)
151 || IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) ) )
154 // set transparent mode for NWF tabcontrols to have
155 // the background always cleared properly
156 EnableChildTransparentMode();
157 SetParentClipMode( ParentClipMode::NoClip );
158 SetPaintTransparent( true );
159 SetBackground();
160 ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
162 else
164 EnableChildTransparentMode( false );
165 SetParentClipMode();
166 SetPaintTransparent( false );
168 if ( IsControlBackground() )
169 SetBackground( GetControlBackground() );
170 else
171 SetBackground( pParent->GetBackground() );
176 void TabControl::ImplFreeLayoutData()
178 if( HasLayoutData() )
180 ImplClearLayoutData();
181 mpTabCtrlData->maLayoutPageIdToLine.clear();
182 mpTabCtrlData->maLayoutLineToPageId.clear();
186 TabControl::TabControl( vcl::Window* pParent, WinBits nStyle ) :
187 Control( WindowType::TABCONTROL )
189 ImplInit( pParent, nStyle );
190 SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER ) ? "true" : "false") );
193 TabControl::~TabControl()
195 disposeOnce();
198 void TabControl::dispose()
200 Window *pParent = GetParent();
201 if (pParent && pParent->IsDialog())
202 GetParent()->RemoveChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
204 ImplFreeLayoutData();
206 // delete TabCtrl data
207 if (mpTabCtrlData)
208 mpTabCtrlData->mpListBox.disposeAndClear();
209 mpTabCtrlData.reset();
210 Control::dispose();
213 ImplTabItem* TabControl::ImplGetItem( sal_uInt16 nId ) const
215 for (auto & item : mpTabCtrlData->maItemList)
217 if (item.id() == nId)
218 return &item;
221 return nullptr;
224 Size TabControl::ImplGetItemSize( ImplTabItem* pItem, long nMaxWidth )
226 pItem->maFormatText = pItem->maText;
227 Size aSize( GetCtrlTextWidth( pItem->maFormatText ), GetTextHeight() );
228 Size aImageSize( 0, 0 );
229 if( !!pItem->maTabImage )
231 aImageSize = pItem->maTabImage.GetSizePixel();
232 if( !pItem->maFormatText.isEmpty() )
233 aImageSize.AdjustWidth(GetTextHeight()/4 );
235 aSize.AdjustWidth(aImageSize.Width() );
236 if( aImageSize.Height() > aSize.Height() )
237 aSize.setHeight( aImageSize.Height() );
239 aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
240 aSize.AdjustHeight(TAB_TABOFFSET_Y*2 );
242 tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
243 tools::Rectangle aBoundingRgn, aContentRgn;
244 const TabitemValue aControlValue(tools::Rectangle(TAB_TABOFFSET_X, TAB_TABOFFSET_Y,
245 aSize.Width() - TAB_TABOFFSET_X * 2,
246 aSize.Height() - TAB_TABOFFSET_Y * 2));
247 if(GetNativeControlRegion( ControlType::TabItem, ControlPart::Entire, aCtrlRegion,
248 ControlState::ENABLED, aControlValue,
249 aBoundingRgn, aContentRgn ) )
251 return aContentRgn.GetSize();
254 // For languages with short names (e.g. Chinese), because the space is
255 // normally only one pixel per char
256 if ( pItem->maFormatText.getLength() < TAB_EXTRASPACE_X )
257 aSize.AdjustWidth(TAB_EXTRASPACE_X-pItem->maFormatText.getLength() );
259 // shorten Text if needed
260 if ( aSize.Width()+4 >= nMaxWidth )
262 OUString aAppendStr("...");
263 pItem->maFormatText += aAppendStr;
266 if (pItem->maFormatText.getLength() > aAppendStr.getLength())
267 pItem->maFormatText = pItem->maFormatText.replaceAt( pItem->maFormatText.getLength()-aAppendStr.getLength()-1, 1, "" );
268 aSize.setWidth( GetCtrlTextWidth( pItem->maFormatText ) );
269 aSize.AdjustWidth(aImageSize.Width() );
270 aSize.AdjustWidth(TAB_TABOFFSET_X*2 );
272 while ( (aSize.Width()+4 >= nMaxWidth) && (pItem->maFormatText.getLength() > aAppendStr.getLength()) );
273 if ( aSize.Width()+4 >= nMaxWidth )
275 pItem->maFormatText = ".";
276 aSize.setWidth( 1 );
280 if( pItem->maFormatText.isEmpty() )
282 if( aSize.Height() < aImageSize.Height()+4 ) //leave space for focus rect
283 aSize.setHeight( aImageSize.Height()+4 );
286 return aSize;
289 // Feel free to move this to some more general place for reuse
290 // http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
291 // Mostly based on Alexey Frunze's nifty example at
292 // http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
293 namespace MinimumRaggednessWrap
295 static std::deque<size_t> GetEndOfLineIndexes(const std::vector<sal_Int32>& rWidthsOf, sal_Int32 nLineWidth)
297 ++nLineWidth;
299 size_t nWidthsCount = rWidthsOf.size();
300 std::vector<sal_Int32> aCosts(nWidthsCount * nWidthsCount);
302 // cost function c(i, j) that computes the cost of a line consisting of
303 // the words Word[i] to Word[j]
304 for (size_t i = 0; i < nWidthsCount; ++i)
306 for (size_t j = 0; j < nWidthsCount; ++j)
308 if (j >= i)
310 sal_Int32 c = nLineWidth - (j - i);
311 for (size_t k = i; k <= j; ++k)
312 c -= rWidthsOf[k];
313 c = (c >= 0) ? c * c : SAL_MAX_INT32;
314 aCosts[j * nWidthsCount + i] = c;
316 else
318 aCosts[j * nWidthsCount + i] = SAL_MAX_INT32;
323 std::vector<sal_Int32> aFunction(nWidthsCount);
324 std::vector<sal_Int32> aWrapPoints(nWidthsCount);
326 // f(j) in aFunction[], collect wrap points in aWrapPoints[]
327 for (size_t j = 0; j < nWidthsCount; ++j)
329 aFunction[j] = aCosts[j * nWidthsCount];
330 if (aFunction[j] == SAL_MAX_INT32)
332 for (size_t k = 0; k < j; ++k)
334 sal_Int32 s;
335 if (aFunction[k] == SAL_MAX_INT32 || aCosts[j * nWidthsCount + k + 1] == SAL_MAX_INT32)
336 s = SAL_MAX_INT32;
337 else
338 s = aFunction[k] + aCosts[j * nWidthsCount + k + 1];
339 if (aFunction[j] > s)
341 aFunction[j] = s;
342 aWrapPoints[j] = k + 1;
348 std::deque<size_t> aSolution;
350 // no solution
351 if (aFunction[nWidthsCount - 1] == SAL_MAX_INT32)
352 return aSolution;
354 // optimal solution
355 size_t j = nWidthsCount - 1;
356 while (true)
358 aSolution.push_front(j);
359 if (!aWrapPoints[j])
360 break;
361 j = aWrapPoints[j] - 1;
364 return aSolution;
368 static void lcl_AdjustSingleLineTabs(long nMaxWidth, ImplTabCtrlData *pTabCtrlData)
370 if (!ImplGetSVData()->maNWFData.mbCenteredTabs)
371 return;
373 int nRightSpace = nMaxWidth; // space left on the right by the tabs
374 for (auto const& item : pTabCtrlData->maItemList)
376 if (!item.m_bVisible)
377 continue;
378 nRightSpace -= item.maRect.Right() - item.maRect.Left();
380 nRightSpace /= 2;
382 for (auto& item : pTabCtrlData->maItemList)
384 if (!item.m_bVisible)
385 continue;
386 item.maRect.AdjustLeft(nRightSpace);
387 item.maRect.AdjustRight(nRightSpace);
391 bool TabControl::ImplPlaceTabs( long nWidth )
393 if ( nWidth <= 0 )
394 return false;
395 if ( mpTabCtrlData->maItemList.empty() )
396 return false;
398 long nMaxWidth = nWidth;
400 const long nOffsetX = 2 + GetItemsOffset().X();
401 const long nOffsetY = 2 + GetItemsOffset().Y();
403 //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
404 //of ugly bare tabs on lines of their own
406 //collect widths
407 std::vector<sal_Int32> aWidths;
408 for (auto & item : mpTabCtrlData->maItemList)
410 if (!item.m_bVisible)
411 continue;
412 aWidths.push_back(ImplGetItemSize(&item, nMaxWidth).Width());
415 //aBreakIndexes will contain the indexes of the last tab on each row
416 std::deque<size_t> aBreakIndexes(MinimumRaggednessWrap::GetEndOfLineIndexes(aWidths, nMaxWidth - nOffsetX - 2));
418 nMaxWidth -= GetItemsOffset().X();
420 long nX = nOffsetX;
421 long nY = nOffsetY;
423 sal_uInt16 nLines = 0;
424 sal_uInt16 nCurLine = 0;
426 long nLineWidthAry[100];
427 sal_uInt16 nLinePosAry[101];
428 nLineWidthAry[0] = 0;
429 nLinePosAry[0] = 0;
431 size_t nIndex = 0;
433 for (auto & item : mpTabCtrlData->maItemList)
435 if (!item.m_bVisible)
436 continue;
438 Size aSize = ImplGetItemSize( &item, nMaxWidth );
440 bool bNewLine = false;
441 if (!aBreakIndexes.empty() && nIndex > aBreakIndexes.front())
443 aBreakIndexes.pop_front();
444 bNewLine = true;
447 if ( bNewLine && (nWidth > 2+nOffsetX) )
449 if ( nLines == 99 )
450 break;
452 nX = nOffsetX;
453 nY += aSize.Height();
454 nLines++;
455 nLineWidthAry[nLines] = 0;
456 nLinePosAry[nLines] = nIndex;
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;
466 nLineWidthAry[nLines] += aSize.Width();
467 nX += aSize.Width();
469 if (item.id() == mnCurPageId)
470 nCurLine = nLines;
472 ++nIndex;
475 if (nLines) // two or more lines
477 long nLineHeightAry[100];
478 long nIH = 0;
479 for (const auto& item : mpTabCtrlData->maItemList)
481 if (!item.m_bVisible)
482 continue;
483 nIH = item.maRect.Bottom() - 1;
484 break;
487 for ( sal_uInt16 i = 0; i < nLines+1; i++ )
489 if ( i <= nCurLine )
490 nLineHeightAry[i] = nIH*(nLines-(nCurLine-i)) + GetItemsOffset().Y();
491 else
492 nLineHeightAry[i] = nIH*(i-nCurLine-1) + GetItemsOffset().Y();
495 nLinePosAry[nLines+1] = static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
497 long nDX = 0;
498 long nModDX = 0;
499 long nIDX = 0;
501 sal_uInt16 i = 0;
502 sal_uInt16 n = 0;
504 for (auto & item : mpTabCtrlData->maItemList)
506 if (!item.m_bVisible)
507 continue;
509 if ( i == nLinePosAry[n] )
511 if ( n == nLines+1 )
512 break;
514 nIDX = 0;
515 if( nLinePosAry[n+1]-i > 0 )
517 nDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) / ( nLinePosAry[n+1] - i );
518 nModDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) % ( nLinePosAry[n+1] - i );
520 else
522 // FIXME: this is a case of tabctrl way too small
523 nDX = 0;
524 nModDX = 0;
526 n++;
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;
535 if ( nModDX )
537 nIDX++;
538 item.maRect.AdjustRight( 1 );
539 nModDX--;
542 i++;
545 else // only one line
546 lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
548 return true;
551 tools::Rectangle TabControl::ImplGetTabRect( sal_uInt16 nItemPos, long nWidth, long nHeight )
553 Size aWinSize = Control::GetOutputSizePixel();
554 if ( nWidth < 0 )
555 nWidth = aWinSize.Width();
556 if ( nHeight < 0 )
557 nHeight = aWinSize.Height();
559 if ( mpTabCtrlData->maItemList.empty() )
561 long nW = nWidth-TAB_OFFSET*2;
562 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();
568 if ( nItemPos == TAB_PAGERECT )
570 sal_uInt16 nLastPos;
571 if ( mnCurPageId )
572 nLastPos = GetPagePos( mnCurPageId );
573 else
574 nLastPos = 0;
576 tools::Rectangle aRect = ImplGetTabRect( nLastPos, nWidth, nHeight );
577 if (aRect.IsEmpty())
578 return aRect;
580 long nW = nWidth-TAB_OFFSET*2;
581 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();
587 ImplTabItem* const pItem = (nItemPos < mpTabCtrlData->maItemList.size())
588 ? &mpTabCtrlData->maItemList[nItemPos] : nullptr;
589 return ImplGetTabRect(pItem, nWidth, nHeight);
592 tools::Rectangle TabControl::ImplGetTabRect(const ImplTabItem* pItem, long nWidth, long nHeight)
594 if ((nWidth <= 1) || (nHeight <= 0) || !pItem || !pItem->m_bVisible)
595 return tools::Rectangle();
597 nWidth -= 1;
599 if ( mbFormat || (mnLastWidth != nWidth) || (mnLastHeight != nHeight) )
601 vcl::Font aFont( GetFont() );
602 aFont.SetTransparent( true );
603 SetFont( aFont );
605 bool bRet = ImplPlaceTabs( nWidth );
606 if ( !bRet )
607 return tools::Rectangle();
609 mnLastWidth = nWidth;
610 mnLastHeight = nHeight;
611 mbFormat = false;
614 return pItem->maRect;
617 void TabControl::ImplChangeTabPage( sal_uInt16 nId, sal_uInt16 nOldId )
619 ImplFreeLayoutData();
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();
627 if ( IsReallyVisible() && IsUpdateMode() )
629 sal_uInt16 nPos = GetPagePos( nId );
630 tools::Rectangle aRect = ImplGetTabRect( nPos );
632 if ( !pOldItem || !pItem || (pOldItem->mnLine != pItem->mnLine) )
634 aRect.SetLeft( 0 );
635 aRect.SetTop( 0 );
636 aRect.SetRight( Control::GetOutputSizePixel().Width() );
638 else
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 );
650 Invalidate( aRect );
653 if ( pOldPage == pPage )
654 return;
656 tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
658 if ( pOldPage )
660 if ( mbRestoreHelpId )
661 pCtrlParent->SetHelpId( OString() );
664 if ( pPage )
666 if ( GetStyle() & WB_NOBORDER )
668 tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
669 pPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
671 else
672 pPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
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() )
678 mbRestoreHelpId = true;
679 pCtrlParent->SetHelpId( pPage->GetHelpId() );
682 pPage->ActivatePage();
683 pPage->Show();
685 if ( pOldPage && pOldPage->HasChildPathFocus() )
687 vcl::Window* pFirstChild = pPage->ImplGetDlgWindow( 0, GetDlgWindowType::First );
688 if ( pFirstChild )
689 pFirstChild->ImplControlFocus( GetFocusFlags::Init );
690 else
691 GrabFocus();
695 if ( pOldPage )
696 pOldPage->Hide();
698 // Invalidate the same region that will be send to NWF
699 // to always allow for bitmap caching
700 // see Window::DrawNativeControl()
701 if( IsNativeControlSupported( ControlType::TabPane, ControlPart::Entire ) )
703 aRect.AdjustLeft( -(TAB_OFFSET) );
704 aRect.AdjustTop( -(TAB_OFFSET) );
705 aRect.AdjustRight(TAB_OFFSET );
706 aRect.AdjustBottom(TAB_OFFSET );
709 Invalidate( aRect );
712 bool TabControl::ImplPosCurTabPage()
714 // resize/position current TabPage
715 ImplTabItem* pItem = ImplGetItem( GetCurPageId() );
716 if ( pItem && pItem->mpTabPage )
718 if ( GetStyle() & WB_NOBORDER )
720 tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
721 pItem->mpTabPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
722 return true;
724 tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
725 pItem->mpTabPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
726 return true;
729 return false;
732 void TabControl::ImplActivateTabPage( bool bNext )
734 sal_uInt16 nCurPos = GetPagePos( GetCurPageId() );
736 if ( bNext )
737 nCurPos = (nCurPos + 1) % GetPageCount();
738 else
740 if ( !nCurPos )
741 nCurPos = GetPageCount()-1;
742 else
743 nCurPos--;
746 SelectTabPage( GetPageId( nCurPos ) );
749 void TabControl::ImplShowFocus()
751 if ( !GetPageCount() || mpTabCtrlData->mpListBox )
752 return;
754 sal_uInt16 nCurPos = GetPagePos( mnCurPageId );
755 tools::Rectangle aRect = ImplGetTabRect( nCurPos );
756 const ImplTabItem& rItem = mpTabCtrlData->maItemList[ nCurPos ];
757 Size aTabSize = aRect.GetSize();
758 Size aImageSize( 0, 0 );
759 long nTextHeight = GetTextHeight();
760 long nTextWidth = GetCtrlTextWidth( rItem.maFormatText );
761 sal_uInt16 nOff;
763 if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::Mono) )
764 nOff = 1;
765 else
766 nOff = 0;
768 if( !! rItem.maTabImage )
770 aImageSize = rItem.maTabImage.GetSizePixel();
771 if( !rItem.maFormatText.isEmpty() )
772 aImageSize.AdjustWidth(GetTextHeight()/4 );
775 if( !rItem.maFormatText.isEmpty() )
777 // show focus around text
778 aRect.SetLeft( aRect.Left()+aImageSize.Width()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1-1 );
779 aRect.SetTop( aRect.Top()+((aTabSize.Height()-nTextHeight)/2)-1-1 );
780 aRect.SetRight( aRect.Left()+nTextWidth+2 );
781 aRect.SetBottom( aRect.Top()+nTextHeight+2 );
783 else
785 // show focus around image
786 long nXPos = aRect.Left()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1;
787 long nYPos = aRect.Top();
788 if( aImageSize.Height() < aRect.GetHeight() )
789 nYPos += (aRect.GetHeight() - aImageSize.Height())/2;
791 aRect.SetLeft( nXPos - 2 );
792 aRect.SetTop( nYPos - 2 );
793 aRect.SetRight( aRect.Left() + aImageSize.Width() + 4 );
794 aRect.SetBottom( aRect.Top() + aImageSize.Height() + 4 );
796 ShowFocus( aRect );
799 void TabControl::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplTabItem const * pItem, const tools::Rectangle& rCurRect,
800 bool bFirstInGroup, bool bLastInGroup )
802 if (!pItem->m_bVisible || pItem->maRect.IsEmpty())
803 return;
805 const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
806 tools::Rectangle aRect = pItem->maRect;
807 long nLeftBottom = aRect.Bottom();
808 long nRightBottom = aRect.Bottom();
809 bool bLeftBorder = true;
810 bool bRightBorder = true;
811 sal_uInt16 nOff;
812 bool bNativeOK = false;
814 sal_uInt16 nOff2 = 0;
815 sal_uInt16 nOff3 = 0;
817 if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
818 nOff = 1;
819 else
820 nOff = 0;
822 // if this is the active Page, we have to draw a little more
823 if (pItem->id() == mnCurPageId)
825 nOff2 = 2;
826 if (!ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise)
827 nOff3 = 1;
829 else
831 Point aLeftTestPos = aRect.BottomLeft();
832 Point aRightTestPos = aRect.BottomRight();
833 if (aLeftTestPos.Y() == rCurRect.Bottom())
835 aLeftTestPos.AdjustX( -2 );
836 if (rCurRect.IsInside(aLeftTestPos))
837 bLeftBorder = false;
838 aRightTestPos.AdjustX(2 );
839 if (rCurRect.IsInside(aRightTestPos))
840 bRightBorder = false;
842 else
844 if (rCurRect.IsInside(aLeftTestPos))
845 nLeftBottom -= 2;
846 if (rCurRect.IsInside(aRightTestPos))
847 nRightBottom -= 2;
851 ControlState nState = ControlState::NONE;
853 if (pItem->id() == mnCurPageId)
855 nState |= ControlState::SELECTED;
856 // only the selected item can be focused
857 if (HasFocus())
858 nState |= ControlState::FOCUSED;
860 if (IsEnabled())
861 nState |= ControlState::ENABLED;
862 if (IsMouseOver() && pItem->maRect.IsInside(GetPointerPosPixel()))
864 nState |= ControlState::ROLLOVER;
865 for (auto const& item : mpTabCtrlData->maItemList)
866 if ((&item != pItem) && item.m_bVisible && item.maRect.IsInside(GetPointerPosPixel()))
868 nState &= ~ControlState::ROLLOVER; // avoid multiple highlighted tabs
869 break;
871 assert(nState & ControlState::ROLLOVER);
874 bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire);
875 if ( bNativeOK )
877 TabitemValue tiValue(tools::Rectangle(pItem->maRect.Left() + TAB_TABOFFSET_X,
878 pItem->maRect.Top() + TAB_TABOFFSET_Y,
879 pItem->maRect.Right() - TAB_TABOFFSET_X,
880 pItem->maRect.Bottom() - TAB_TABOFFSET_Y));
881 if (pItem->maRect.Left() < 5)
882 tiValue.mnAlignment |= TabitemFlags::LeftAligned;
883 if (pItem->maRect.Right() > mnLastWidth - 5)
884 tiValue.mnAlignment |= TabitemFlags::RightAligned;
885 if (bFirstInGroup)
886 tiValue.mnAlignment |= TabitemFlags::FirstInGroup;
887 if (bLastInGroup)
888 tiValue.mnAlignment |= TabitemFlags::LastInGroup;
890 tools::Rectangle aCtrlRegion( pItem->maRect );
891 aCtrlRegion.AdjustBottom(TabPaneValue::m_nOverlap);
892 bNativeOK = rRenderContext.DrawNativeControl(ControlType::TabItem, ControlPart::Entire,
893 aCtrlRegion, nState, tiValue, OUString() );
896 if (!bNativeOK)
898 if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
900 rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
901 rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2)); // diagonally indented top-left pixel
902 if (bLeftBorder)
904 rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
905 Point(aRect.Left() - nOff2, nLeftBottom - 1));
907 rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2), // top line starting 2px from left border
908 Point(aRect.Right() + nOff2 - 3, aRect.Top() - nOff2)); // ending 3px from right border
910 if (bRightBorder)
912 rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
913 rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2),
914 Point(aRect.Right() + nOff2 - 2, nRightBottom - 1));
916 rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
917 rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 3 - nOff2),
918 Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
921 else
923 rRenderContext.SetLineColor(COL_BLACK);
924 rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2));
925 rRenderContext.DrawPixel(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2));
926 if (bLeftBorder)
928 rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
929 Point(aRect.Left() - nOff2, nLeftBottom - 1));
931 rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2),
932 Point(aRect.Right() - 3, aRect.Top() - nOff2));
933 if (bRightBorder)
935 rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 2 - nOff2),
936 Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
941 // set font accordingly, current item is painted bold
942 // we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
943 vcl::Font aFont(rRenderContext.GetFont());
944 aFont.SetTransparent(true);
945 rRenderContext.SetFont(aFont);
947 Size aTabSize = aRect.GetSize();
948 Size aImageSize(0, 0);
949 long nTextHeight = rRenderContext.GetTextHeight();
950 long nTextWidth = rRenderContext.GetCtrlTextWidth(pItem->maFormatText);
951 if (!!pItem->maTabImage)
953 aImageSize = pItem->maTabImage.GetSizePixel();
954 if (!pItem->maFormatText.isEmpty())
955 aImageSize.AdjustWidth(GetTextHeight() / 4 );
957 long nXPos = aRect.Left() + ((aTabSize.Width() - nTextWidth - aImageSize.Width()) / 2) - nOff - nOff3;
958 long nYPos = aRect.Top() + ((aTabSize.Height() - nTextHeight) / 2) - nOff3;
959 if (!pItem->maFormatText.isEmpty())
961 DrawTextFlags nStyle = DrawTextFlags::Mnemonic;
962 if (!pItem->m_bEnabled)
963 nStyle |= DrawTextFlags::Disable;
965 Color aColor(rStyleSettings.GetTabTextColor());
966 if (nState & ControlState::SELECTED)
967 aColor = rStyleSettings.GetTabHighlightTextColor();
968 else if (nState & ControlState::ROLLOVER)
969 aColor = rStyleSettings.GetTabRolloverTextColor();
971 Color aOldColor(rRenderContext.GetTextColor());
972 rRenderContext.SetTextColor(aColor);
974 const tools::Rectangle aOutRect(nXPos + aImageSize.Width(), nYPos,
975 nXPos + aImageSize.Width() + nTextWidth, nYPos + nTextHeight);
976 DrawControlText(rRenderContext, aOutRect, pItem->maFormatText, nStyle,
977 nullptr, nullptr);
979 rRenderContext.SetTextColor(aOldColor);
982 if (!!pItem->maTabImage)
984 Point aImgTL( nXPos, aRect.Top() );
985 if (aImageSize.Height() < aRect.GetHeight())
986 aImgTL.AdjustY((aRect.GetHeight() - aImageSize.Height()) / 2 );
987 rRenderContext.DrawImage(aImgTL, pItem->maTabImage, pItem->m_bEnabled ? DrawImageFlags::NONE : DrawImageFlags::Disable );
991 bool TabControl::ImplHandleKeyEvent( const KeyEvent& rKeyEvent )
993 bool bRet = false;
995 if ( GetPageCount() > 1 )
997 vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
998 sal_uInt16 nKeyCode = aKeyCode.GetCode();
1000 if ( aKeyCode.IsMod1() )
1002 if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
1004 if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
1006 ImplActivateTabPage( false );
1007 bRet = true;
1010 else
1012 if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
1014 ImplActivateTabPage( true );
1015 bRet = true;
1021 return bRet;
1024 IMPL_LINK_NOARG(TabControl, ImplListBoxSelectHdl, ListBox&, void)
1026 SelectTabPage( GetPageId( mpTabCtrlData->mpListBox->GetSelectedEntryPos() ) );
1029 IMPL_LINK( TabControl, ImplWindowEventListener, VclWindowEvent&, rEvent, void )
1031 if ( rEvent.GetId() == VclEventId::WindowKeyInput )
1033 // Do not handle events from TabControl or its children, which is done in Notify(), where the events can be consumed.
1034 if ( !IsWindowOrChild( rEvent.GetWindow() ) )
1036 KeyEvent* pKeyEvent = static_cast< KeyEvent* >(rEvent.GetData());
1037 ImplHandleKeyEvent( *pKeyEvent );
1042 void TabControl::MouseButtonDown( const MouseEvent& rMEvt )
1044 if (mpTabCtrlData->mpListBox.get() != nullptr || !rMEvt.IsLeft())
1045 return;
1047 ImplTabItem *pItem = ImplGetItem(rMEvt.GetPosPixel());
1048 if (pItem && pItem->m_bEnabled)
1049 SelectTabPage(pItem->id());
1052 void TabControl::KeyInput( const KeyEvent& rKEvt )
1054 if( mpTabCtrlData->mpListBox )
1055 mpTabCtrlData->mpListBox->KeyInput( rKEvt );
1056 else if ( GetPageCount() > 1 )
1058 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
1059 sal_uInt16 nKeyCode = aKeyCode.GetCode();
1061 if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_RIGHT) )
1063 bool bNext = (nKeyCode == KEY_RIGHT);
1064 ImplActivateTabPage( bNext );
1068 Control::KeyInput( rKEvt );
1071 static bool lcl_canPaint(const vcl::RenderContext& rRenderContext, const tools::Rectangle& rDrawRect,
1072 const tools::Rectangle& rItemRect)
1074 vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
1075 aClipRgn.Intersect(rItemRect);
1076 if (!rDrawRect.IsEmpty())
1077 aClipRgn.Intersect(rDrawRect);
1078 return !aClipRgn.IsEmpty();
1081 void TabControl::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
1083 if (GetStyle() & WB_NOBORDER)
1084 return;
1086 Control::Paint(rRenderContext, rRect);
1088 HideFocus();
1090 // reformat if needed
1091 tools::Rectangle aRect = ImplGetTabRect(TAB_PAGERECT);
1093 // find current item
1094 ImplTabItem* pCurItem = nullptr;
1095 for (auto & item : mpTabCtrlData->maItemList)
1097 if (item.id() == mnCurPageId)
1099 pCurItem = &item;
1100 break;
1104 // Draw the TabPage border
1105 const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
1106 tools::Rectangle aCurRect;
1107 aRect.AdjustLeft( -(TAB_OFFSET) );
1108 aRect.AdjustTop( -(TAB_OFFSET) );
1109 aRect.AdjustRight(TAB_OFFSET );
1110 aRect.AdjustBottom(TAB_OFFSET );
1112 // if we have an invisible tabpage or no tabpage at all the tabpage rect should be
1113 // increased to avoid round corners that might be drawn by a theme
1114 // in this case we're only interested in the top border of the tabpage because the tabitems are used
1115 // standalone (eg impress)
1116 bool bNoTabPage = false;
1117 TabPage* pCurPage = pCurItem ? pCurItem->mpTabPage.get() : nullptr;
1118 if (!pCurPage || !pCurPage->IsVisible())
1120 bNoTabPage = true;
1121 aRect.AdjustLeft( -10 );
1122 aRect.AdjustRight(10 );
1125 if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
1127 const bool bPaneWithHeader = rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::TabPaneWithHeader);
1128 tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
1129 if (bPaneWithHeader)
1131 aRect.SetTop(0);
1132 if (mpTabCtrlData->maItemList.size())
1134 long nRight = 0;
1135 for (const auto &item : mpTabCtrlData->maItemList)
1136 if (item.m_bVisible)
1137 nRight = item.maRect.Right();
1138 assert(nRight);
1139 aHeaderRect.SetRight(nRight);
1142 const TabPaneValue aTabPaneValue(aHeaderRect, pCurItem ? pCurItem->maRect : tools::Rectangle());
1144 ControlState nState = ControlState::ENABLED;
1145 if (!IsEnabled())
1146 nState &= ~ControlState::ENABLED;
1147 if (HasFocus())
1148 nState |= ControlState::FOCUSED;
1150 if (lcl_canPaint(rRenderContext, rRect, aRect))
1151 rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
1152 aRect, nState, aTabPaneValue, OUString());
1154 if (!bPaneWithHeader && rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire)
1155 && lcl_canPaint(rRenderContext, rRect, aHeaderRect))
1156 rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
1157 aHeaderRect, nState, aTabPaneValue, OUString());
1159 else
1161 long nTopOff = 1;
1162 if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
1163 rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
1164 else
1165 rRenderContext.SetLineColor(COL_BLACK);
1166 if (pCurItem && !pCurItem->maRect.IsEmpty())
1168 aCurRect = pCurItem->maRect;
1169 rRenderContext.DrawLine(aRect.TopLeft(), Point(aCurRect.Left() - 2, aRect.Top()));
1170 if (aCurRect.Right() + 1 < aRect.Right())
1172 rRenderContext.DrawLine(Point(aCurRect.Right(), aRect.Top()), aRect.TopRight());
1174 else
1176 nTopOff = 0;
1179 else
1180 rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
1182 rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
1184 if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
1186 // if we have not tab page the bottom line of the tab page
1187 // directly touches the tab items, so choose a color that fits seamlessly
1188 if (bNoTabPage)
1189 rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
1190 else
1191 rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
1192 rRenderContext.DrawLine(Point(1, aRect.Bottom() - 1), Point(aRect.Right() - 1, aRect.Bottom() - 1));
1193 rRenderContext.DrawLine(Point(aRect.Right() - 1, aRect.Top() + nTopOff), Point(aRect.Right() - 1, aRect.Bottom() - 1));
1194 if (bNoTabPage)
1195 rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
1196 else
1197 rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
1198 rRenderContext.DrawLine(Point(0, aRect.Bottom()), Point(aRect.Right(), aRect.Bottom()));
1199 rRenderContext.DrawLine(Point(aRect.Right(), aRect.Top() + nTopOff), Point(aRect.Right(), aRect.Bottom()));
1201 else
1203 rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
1204 rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
1208 if (!mpTabCtrlData->maItemList.empty() && mpTabCtrlData->mpListBox == nullptr)
1210 // Some native toolkits (GTK+) draw tabs right-to-left, with an
1211 // overlap between adjacent tabs
1212 bool bDrawTabsRTL = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::TabsDrawRtl);
1213 ImplTabItem* pFirstTab = nullptr;
1214 ImplTabItem* pLastTab = nullptr;
1215 size_t idx;
1217 // Even though there is a tab overlap with GTK+, the first tab is not
1218 // overlapped on the left side. Other toolkits ignore this option.
1219 if (bDrawTabsRTL)
1221 pFirstTab = mpTabCtrlData->maItemList.data();
1222 pLastTab = pFirstTab + mpTabCtrlData->maItemList.size() - 1;
1223 idx = mpTabCtrlData->maItemList.size() - 1;
1225 else
1227 pLastTab = mpTabCtrlData->maItemList.data();
1228 pFirstTab = pLastTab + mpTabCtrlData->maItemList.size() - 1;
1229 idx = 0;
1232 while (idx < mpTabCtrlData->maItemList.size())
1234 ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
1236 if (pItem != pCurItem && pItem->m_bVisible && lcl_canPaint(rRenderContext, rRect, pItem->maRect))
1237 ImplDrawItem(rRenderContext, pItem, aCurRect, pItem == pFirstTab, pItem == pLastTab);
1239 if (bDrawTabsRTL)
1240 idx--;
1241 else
1242 idx++;
1245 if (pCurItem && lcl_canPaint(rRenderContext, rRect, pCurItem->maRect))
1246 ImplDrawItem(rRenderContext, pCurItem, aCurRect, pCurItem == pFirstTab, pCurItem == pLastTab);
1249 if (HasFocus())
1250 ImplShowFocus();
1252 mbSmallInvalidate = true;
1255 void TabControl::setAllocation(const Size &rAllocation)
1257 ImplFreeLayoutData();
1259 if ( !IsReallyShown() )
1260 return;
1262 if( mpTabCtrlData->mpListBox )
1264 // get the listbox' preferred size
1265 Size aTabCtrlSize( GetSizePixel() );
1266 long nPrefWidth = mpTabCtrlData->mpListBox->get_preferred_size().Width();
1267 if( nPrefWidth > aTabCtrlSize.Width() )
1268 nPrefWidth = aTabCtrlSize.Width();
1269 Size aNewSize( nPrefWidth, LogicToPixel( Size( 12, 12 ), MapMode( MapUnit::MapAppFont ) ).Height() );
1270 Point aNewPos( (aTabCtrlSize.Width() - nPrefWidth) / 2, 0 );
1271 mpTabCtrlData->mpListBox->SetPosSizePixel( aNewPos, aNewSize );
1274 mbFormat = true;
1276 // resize/position active TabPage
1277 bool bTabPage = ImplPosCurTabPage();
1279 // check what needs to be invalidated
1280 Size aNewSize = rAllocation;
1281 long nNewWidth = aNewSize.Width();
1282 for (auto const& item : mpTabCtrlData->maItemList)
1284 if (!item.m_bVisible)
1285 continue;
1286 if (!item.mbFullVisible || (item.maRect.Right()-2 >= nNewWidth))
1288 mbSmallInvalidate = false;
1289 break;
1293 if ( mbSmallInvalidate )
1295 tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
1296 aRect.AdjustLeft( -(TAB_OFFSET+TAB_BORDER_LEFT) );
1297 aRect.AdjustTop( -(TAB_OFFSET+TAB_BORDER_TOP) );
1298 aRect.AdjustRight(TAB_OFFSET+TAB_BORDER_RIGHT );
1299 aRect.AdjustBottom(TAB_OFFSET+TAB_BORDER_BOTTOM );
1300 if ( bTabPage )
1301 Invalidate( aRect, InvalidateFlags::NoChildren );
1302 else
1303 Invalidate( aRect );
1306 else
1308 if ( bTabPage )
1309 Invalidate( InvalidateFlags::NoChildren );
1310 else
1311 Invalidate();
1314 mbLayoutDirty = false;
1317 void TabControl::SetPosSizePixel(const Point& rNewPos, const Size& rNewSize)
1319 Window::SetPosSizePixel(rNewPos, rNewSize);
1320 //if size changed, TabControl::Resize got called already
1321 if (mbLayoutDirty)
1322 setAllocation(rNewSize);
1325 void TabControl::SetSizePixel(const Size& rNewSize)
1327 Window::SetSizePixel(rNewSize);
1328 //if size changed, TabControl::Resize got called already
1329 if (mbLayoutDirty)
1330 setAllocation(rNewSize);
1333 void TabControl::SetPosPixel(const Point& rPos)
1335 Window::SetPosPixel(rPos);
1336 if (mbLayoutDirty)
1337 setAllocation(GetOutputSizePixel());
1340 void TabControl::Resize()
1342 setAllocation(Control::GetOutputSizePixel());
1345 void TabControl::GetFocus()
1347 if( ! mpTabCtrlData->mpListBox )
1349 ImplShowFocus();
1350 SetInputContext( InputContext( GetFont() ) );
1352 else
1354 if( mpTabCtrlData->mpListBox->IsReallyVisible() )
1355 mpTabCtrlData->mpListBox->GrabFocus();
1357 Control::GetFocus();
1360 void TabControl::LoseFocus()
1362 if( mpTabCtrlData && ! mpTabCtrlData->mpListBox )
1363 HideFocus();
1364 Control::LoseFocus();
1367 void TabControl::RequestHelp( const HelpEvent& rHEvt )
1369 sal_uInt16 nItemId = rHEvt.KeyboardActivated() ? mnCurPageId : GetPageId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
1371 if ( nItemId )
1373 if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
1375 OUString aStr = GetHelpText( nItemId );
1376 if ( !aStr.isEmpty() )
1378 tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
1379 Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
1380 aItemRect.SetLeft( aPt.X() );
1381 aItemRect.SetTop( aPt.Y() );
1382 aPt = OutputToScreenPixel( aItemRect.BottomRight() );
1383 aItemRect.SetRight( aPt.X() );
1384 aItemRect.SetBottom( aPt.Y() );
1385 Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
1386 return;
1390 // for Quick or Ballon Help, we show the text, if it is cut
1391 if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
1393 ImplTabItem* pItem = ImplGetItem( nItemId );
1394 const OUString& rStr = pItem->maText;
1395 if ( rStr != pItem->maFormatText )
1397 tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
1398 Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
1399 aItemRect.SetLeft( aPt.X() );
1400 aItemRect.SetTop( aPt.Y() );
1401 aPt = OutputToScreenPixel( aItemRect.BottomRight() );
1402 aItemRect.SetRight( aPt.X() );
1403 aItemRect.SetBottom( aPt.Y() );
1404 if ( !rStr.isEmpty() )
1406 if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
1407 Help::ShowBalloon( this, aItemRect.Center(), aItemRect, rStr );
1408 else
1409 Help::ShowQuickHelp( this, aItemRect, rStr );
1410 return;
1415 if ( rHEvt.GetMode() & HelpEventMode::QUICK )
1417 ImplTabItem* pItem = ImplGetItem( nItemId );
1418 const OUString& rHelpText = pItem->maHelpText;
1419 // show tooltip if not text but image is set and helptext is available
1420 if ( !rHelpText.isEmpty() && pItem->maText.isEmpty() && !!pItem->maTabImage )
1422 tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
1423 Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
1424 aItemRect.SetLeft( aPt.X() );
1425 aItemRect.SetTop( aPt.Y() );
1426 aPt = OutputToScreenPixel( aItemRect.BottomRight() );
1427 aItemRect.SetRight( aPt.X() );
1428 aItemRect.SetBottom( aPt.Y() );
1429 Help::ShowQuickHelp( this, aItemRect, rHelpText );
1430 return;
1435 Control::RequestHelp( rHEvt );
1438 void TabControl::Command( const CommandEvent& rCEvt )
1440 if( (mpTabCtrlData->mpListBox == nullptr) && (rCEvt.GetCommand() == CommandEventId::ContextMenu) && (GetPageCount() > 1) )
1442 Point aMenuPos;
1443 bool bMenu;
1444 if ( rCEvt.IsMouseEvent() )
1446 aMenuPos = rCEvt.GetMousePosPixel();
1447 bMenu = GetPageId( aMenuPos ) != 0;
1449 else
1451 aMenuPos = ImplGetTabRect( GetPagePos( mnCurPageId ) ).Center();
1452 bMenu = true;
1455 if ( bMenu )
1457 ScopedVclPtrInstance<PopupMenu> aMenu;
1458 for (auto const& item : mpTabCtrlData->maItemList)
1460 aMenu->InsertItem(item.id(), item.maText, MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK);
1461 if (item.id() == mnCurPageId)
1462 aMenu->CheckItem(item.id());
1463 aMenu->SetHelpId(item.id(), OString());
1466 sal_uInt16 nId = aMenu->Execute( this, aMenuPos );
1467 if ( nId && (nId != mnCurPageId) )
1468 SelectTabPage( nId );
1469 return;
1473 Control::Command( rCEvt );
1476 void TabControl::StateChanged( StateChangedType nType )
1478 Control::StateChanged( nType );
1480 if ( nType == StateChangedType::InitShow )
1482 ImplPosCurTabPage();
1483 if( mpTabCtrlData->mpListBox )
1484 Resize();
1486 else if ( nType == StateChangedType::UpdateMode )
1488 if ( IsUpdateMode() )
1489 Invalidate();
1491 else if ( (nType == StateChangedType::Zoom) ||
1492 (nType == StateChangedType::ControlFont) )
1494 ImplInitSettings( false );
1495 Invalidate();
1497 else if ( nType == StateChangedType::ControlForeground )
1499 ImplInitSettings( false );
1500 Invalidate();
1502 else if ( nType == StateChangedType::ControlBackground )
1504 ImplInitSettings( true );
1505 Invalidate();
1509 void TabControl::DataChanged( const DataChangedEvent& rDCEvt )
1511 Control::DataChanged( rDCEvt );
1513 if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
1514 (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
1515 ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
1516 (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
1518 ImplInitSettings( true );
1519 Invalidate();
1523 ImplTabItem* TabControl::ImplGetItem(const Point& rPt) const
1525 ImplTabItem* pFoundItem = nullptr;
1526 int nFound = 0;
1527 for (auto & item : mpTabCtrlData->maItemList)
1529 if (item.m_bVisible && item.maRect.IsInside(rPt))
1531 nFound++;
1532 pFoundItem = &item;
1536 // assure that only one tab is highlighted at a time
1537 assert(nFound <= 1);
1538 return nFound == 1 ? pFoundItem : nullptr;
1541 bool TabControl::PreNotify( NotifyEvent& rNEvt )
1543 const MouseEvent* pMouseEvt = nullptr;
1545 if( (rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE) && (pMouseEvt = rNEvt.GetMouseEvent()) != nullptr )
1547 if( !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
1549 // trigger redraw if mouse over state has changed
1550 if( IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) )
1552 ImplTabItem *pItem = ImplGetItem(GetPointerPosPixel());
1553 ImplTabItem *pLastItem = ImplGetItem(GetLastPointerPosPixel());
1554 if ((pItem != pLastItem) || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
1556 vcl::Region aClipRgn;
1557 if (pLastItem)
1559 // allow for slightly bigger tabitems
1560 // as used by gtk
1561 // TODO: query for the correct sizes
1562 tools::Rectangle aRect(pLastItem->maRect);
1563 aRect.AdjustLeft( -2 );
1564 aRect.AdjustRight(2 );
1565 aRect.AdjustTop( -3 );
1566 aClipRgn.Union( aRect );
1569 if (pItem)
1571 // allow for slightly bigger tabitems
1572 // as used by gtk
1573 // TODO: query for the correct sizes
1574 tools::Rectangle aRect(pItem->maRect);
1575 aRect.AdjustLeft( -2 );
1576 aRect.AdjustRight(2 );
1577 aRect.AdjustTop( -3 );
1578 aClipRgn.Union( aRect );
1581 if( !aClipRgn.IsEmpty() )
1582 Invalidate( aClipRgn );
1588 return Control::PreNotify(rNEvt);
1591 bool TabControl::EventNotify( NotifyEvent& rNEvt )
1593 bool bRet = false;
1595 if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
1596 bRet = ImplHandleKeyEvent( *rNEvt.GetKeyEvent() );
1598 return bRet || Control::EventNotify( rNEvt );
1601 void TabControl::ActivatePage()
1603 maActivateHdl.Call( this );
1606 bool TabControl::DeactivatePage()
1608 return !maDeactivateHdl.IsSet() || maDeactivateHdl.Call( this );
1611 void TabControl::SetTabPageSizePixel( const Size& rSize )
1613 ImplFreeLayoutData();
1615 Size aNewSize( rSize );
1616 aNewSize.AdjustWidth(TAB_OFFSET*2 );
1617 tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT,
1618 aNewSize.Width(), aNewSize.Height() );
1619 aNewSize.AdjustHeight(aRect.Top()+TAB_OFFSET );
1620 Window::SetOutputSizePixel( aNewSize );
1623 Size TabControl::GetTabPageSizePixel() const
1625 tools::Rectangle aRect = const_cast<TabControl*>(this)->ImplGetTabRect( TAB_PAGERECT );
1626 return aRect.GetSize();
1629 void TabControl::InsertPage( sal_uInt16 nPageId, const OUString& rText,
1630 sal_uInt16 nPos )
1632 SAL_WARN_IF( !nPageId, "vcl", "TabControl::InsertPage(): PageId == 0" );
1633 SAL_WARN_IF( GetPagePos( nPageId ) != TAB_PAGE_NOTFOUND, "vcl",
1634 "TabControl::InsertPage(): PageId already exists" );
1636 // insert new page item
1637 ImplTabItem* pItem = nullptr;
1638 if( nPos == TAB_APPEND || size_t(nPos) >= mpTabCtrlData->maItemList.size() )
1640 mpTabCtrlData->maItemList.emplace_back(nPageId);
1641 pItem = &mpTabCtrlData->maItemList.back();
1642 if( mpTabCtrlData->mpListBox )
1643 mpTabCtrlData->mpListBox->InsertEntry( rText );
1645 else
1647 std::vector< ImplTabItem >::iterator new_it =
1648 mpTabCtrlData->maItemList.emplace(mpTabCtrlData->maItemList.begin() + nPos, nPageId);
1649 pItem = &(*new_it);
1650 if( mpTabCtrlData->mpListBox )
1651 mpTabCtrlData->mpListBox->InsertEntry( rText, nPos);
1653 if( mpTabCtrlData->mpListBox )
1655 if( ! mnCurPageId )
1656 mpTabCtrlData->mpListBox->SelectEntryPos( 0 );
1657 mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
1660 // set current page id
1661 if ( !mnCurPageId )
1662 mnCurPageId = nPageId;
1664 // init new page item
1665 pItem->maText = rText;
1666 pItem->mbFullVisible = false;
1668 mbFormat = true;
1669 if ( IsUpdateMode() )
1670 Invalidate();
1672 ImplFreeLayoutData();
1673 if( mpTabCtrlData->mpListBox ) // reposition/resize listbox
1674 Resize();
1676 CallEventListeners( VclEventId::TabpageInserted, reinterpret_cast<void*>(nPageId) );
1679 void TabControl::RemovePage( sal_uInt16 nPageId )
1681 sal_uInt16 nPos = GetPagePos( nPageId );
1683 // does the item exist ?
1684 if ( nPos != TAB_PAGE_NOTFOUND )
1686 //remove page item
1687 std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin() + nPos;
1688 bool bIsCurrentPage = (it->id() == mnCurPageId);
1689 mpTabCtrlData->maItemList.erase( it );
1690 if( mpTabCtrlData->mpListBox )
1692 mpTabCtrlData->mpListBox->RemoveEntry( nPos );
1693 mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
1696 // If current page is removed, then first page gets the current page
1697 if ( bIsCurrentPage )
1699 mnCurPageId = 0;
1701 if( ! mpTabCtrlData->maItemList.empty() )
1703 // don't do this by simply setting mnCurPageId to pFirstItem->id()
1704 // this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
1705 // instead, call SetCurPageId
1706 // without this, the next (outside) call to SetCurPageId with the id of the first page
1707 // will result in doing nothing (as we assume that nothing changed, then), and the page
1708 // will never be shown.
1709 // 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
1711 SetCurPageId(mpTabCtrlData->maItemList[0].id());
1715 mbFormat = true;
1716 if ( IsUpdateMode() )
1717 Invalidate();
1719 ImplFreeLayoutData();
1721 CallEventListeners( VclEventId::TabpageRemoved, reinterpret_cast<void*>(nPageId) );
1725 void TabControl::Clear()
1727 // clear item list
1728 mpTabCtrlData->maItemList.clear();
1729 mnCurPageId = 0;
1730 if( mpTabCtrlData->mpListBox )
1731 mpTabCtrlData->mpListBox->Clear();
1733 ImplFreeLayoutData();
1735 mbFormat = true;
1736 if ( IsUpdateMode() )
1737 Invalidate();
1739 CallEventListeners( VclEventId::TabpageRemovedAll );
1742 void TabControl::SetPageEnabled( sal_uInt16 i_nPageId, bool i_bEnable )
1744 ImplTabItem* pItem = ImplGetItem( i_nPageId );
1746 if (pItem && pItem->m_bEnabled != i_bEnable)
1748 pItem->m_bEnabled = i_bEnable;
1749 if (!pItem->m_bVisible)
1750 return;
1752 mbFormat = true;
1753 if( mpTabCtrlData->mpListBox )
1754 mpTabCtrlData->mpListBox->SetEntryFlags( GetPagePos( i_nPageId ),
1755 i_bEnable ? ListBoxEntryFlags::NONE : (ListBoxEntryFlags::DisableSelection | ListBoxEntryFlags::DrawDisabled) );
1757 // SetCurPageId will change to a valid page
1758 if (pItem->id() == mnCurPageId)
1759 SetCurPageId( mnCurPageId );
1760 else if ( IsUpdateMode() )
1761 Invalidate();
1765 void TabControl::SetPageVisible( sal_uInt16 nPageId, bool bVisible )
1767 ImplTabItem* pItem = ImplGetItem( nPageId );
1768 if (!pItem || pItem->m_bVisible == bVisible)
1769 return;
1771 pItem->m_bVisible = bVisible;
1772 if (!bVisible)
1774 if (pItem->mbFullVisible)
1775 mbSmallInvalidate = false;
1776 pItem->mbFullVisible = false;
1777 pItem->maRect.SetEmpty();
1779 mbFormat = true;
1781 // SetCurPageId will change to a valid page
1782 if (pItem->id() == mnCurPageId)
1783 SetCurPageId(mnCurPageId);
1784 else if (IsUpdateMode())
1785 Invalidate();
1788 sal_uInt16 TabControl::GetPageCount() const
1790 return static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
1793 sal_uInt16 TabControl::GetPageId( sal_uInt16 nPos ) const
1795 if( size_t(nPos) < mpTabCtrlData->maItemList.size() )
1796 return mpTabCtrlData->maItemList[nPos].id();
1797 return 0;
1800 sal_uInt16 TabControl::GetPagePos( sal_uInt16 nPageId ) const
1802 sal_uInt16 nPos = 0;
1803 for (auto const& item : mpTabCtrlData->maItemList)
1805 if (item.id() == nPageId)
1806 return nPos;
1807 ++nPos;
1810 return TAB_PAGE_NOTFOUND;
1813 sal_uInt16 TabControl::GetPageId( const Point& rPos ) const
1815 const auto &rList = mpTabCtrlData->maItemList;
1816 const auto it = std::find_if(rList.begin(), rList.end(), [&rPos, this](const auto &item) {
1817 return const_cast<TabControl*>(this)->ImplGetTabRect(&item).IsInside(rPos); });
1818 return (it != rList.end()) ? it->id() : 0;
1821 sal_uInt16 TabControl::GetPageId( const OString& rName ) const
1823 const auto &rList = mpTabCtrlData->maItemList;
1824 const auto it = std::find_if(rList.begin(), rList.end(), [&rName](const auto &item) {
1825 return item.maTabName == rName; });
1826 return (it != rList.end()) ? it->id() : 0;
1829 void TabControl::SetCurPageId( sal_uInt16 nPageId )
1831 sal_uInt16 nPos = GetPagePos( nPageId );
1832 while (nPos != TAB_PAGE_NOTFOUND && !mpTabCtrlData->maItemList[nPos].m_bEnabled)
1834 nPos++;
1835 if( size_t(nPos) >= mpTabCtrlData->maItemList.size() )
1836 nPos = 0;
1837 if (mpTabCtrlData->maItemList[nPos].id() == nPageId)
1838 break;
1841 if( nPos != TAB_PAGE_NOTFOUND )
1843 nPageId = mpTabCtrlData->maItemList[nPos].id();
1844 if ( nPageId == mnCurPageId )
1846 if ( mnActPageId )
1847 mnActPageId = nPageId;
1848 return;
1851 if ( mnActPageId )
1852 mnActPageId = nPageId;
1853 else
1855 mbFormat = true;
1856 sal_uInt16 nOldId = mnCurPageId;
1857 mnCurPageId = nPageId;
1858 ImplChangeTabPage( nPageId, nOldId );
1863 sal_uInt16 TabControl::GetCurPageId() const
1865 if ( mnActPageId )
1866 return mnActPageId;
1867 else
1868 return mnCurPageId;
1871 void TabControl::SelectTabPage( sal_uInt16 nPageId )
1873 if ( nPageId && (nPageId != mnCurPageId) )
1875 ImplFreeLayoutData();
1877 CallEventListeners( VclEventId::TabpageDeactivate, reinterpret_cast<void*>(mnCurPageId) );
1878 if ( DeactivatePage() )
1880 mnActPageId = nPageId;
1881 ActivatePage();
1882 // Page could have been switched by the Activate handler
1883 nPageId = mnActPageId;
1884 mnActPageId = 0;
1885 SetCurPageId( nPageId );
1886 if( mpTabCtrlData->mpListBox )
1887 mpTabCtrlData->mpListBox->SelectEntryPos( GetPagePos( nPageId ) );
1888 CallEventListeners( VclEventId::TabpageActivate, reinterpret_cast<void*>(nPageId) );
1893 void TabControl::SetTabPage( sal_uInt16 nPageId, TabPage* pTabPage )
1895 ImplTabItem* pItem = ImplGetItem( nPageId );
1897 if ( pItem && (pItem->mpTabPage.get() != pTabPage) )
1899 if ( pTabPage )
1901 if ( IsDefaultSize() )
1902 SetTabPageSizePixel( pTabPage->GetSizePixel() );
1904 // only set here, so that Resize does not reposition TabPage
1905 pItem->mpTabPage = pTabPage;
1906 queue_resize();
1908 if (pItem->id() == mnCurPageId)
1909 ImplChangeTabPage(pItem->id(), 0);
1911 else
1913 pItem->mpTabPage = nullptr;
1914 queue_resize();
1919 TabPage* TabControl::GetTabPage( sal_uInt16 nPageId ) const
1921 ImplTabItem* pItem = ImplGetItem( nPageId );
1923 if ( pItem )
1924 return pItem->mpTabPage;
1925 else
1926 return nullptr;
1929 void TabControl::SetPageText( sal_uInt16 nPageId, const OUString& rText )
1931 ImplTabItem* pItem = ImplGetItem( nPageId );
1933 if ( pItem && pItem->maText != rText )
1935 pItem->maText = rText;
1936 mbFormat = true;
1937 if( mpTabCtrlData->mpListBox )
1939 sal_uInt16 nPos = GetPagePos( nPageId );
1940 mpTabCtrlData->mpListBox->RemoveEntry( nPos );
1941 mpTabCtrlData->mpListBox->InsertEntry( rText, nPos );
1943 if ( IsUpdateMode() )
1944 Invalidate();
1945 ImplFreeLayoutData();
1946 CallEventListeners( VclEventId::TabpagePageTextChanged, reinterpret_cast<void*>(nPageId) );
1950 OUString const & TabControl::GetPageText( sal_uInt16 nPageId ) const
1952 ImplTabItem* pItem = ImplGetItem( nPageId );
1954 assert( pItem );
1956 return pItem->maText;
1959 void TabControl::SetHelpText( sal_uInt16 nPageId, const OUString& rText )
1961 ImplTabItem* pItem = ImplGetItem( nPageId );
1963 assert( pItem );
1965 pItem->maHelpText = rText;
1968 const OUString& TabControl::GetHelpText( sal_uInt16 nPageId ) const
1970 ImplTabItem* pItem = ImplGetItem( nPageId );
1971 assert( pItem );
1972 return pItem->maHelpText;
1975 void TabControl::SetPageName( sal_uInt16 nPageId, const OString& rName ) const
1977 ImplTabItem* pItem = ImplGetItem( nPageId );
1979 if ( pItem )
1980 pItem->maTabName = rName;
1983 OString TabControl::GetPageName( sal_uInt16 nPageId ) const
1985 ImplTabItem* pItem = ImplGetItem( nPageId );
1987 if (pItem)
1988 return pItem->maTabName;
1990 return OString();
1993 void TabControl::SetPageImage( sal_uInt16 i_nPageId, const Image& i_rImage )
1995 ImplTabItem* pItem = ImplGetItem( i_nPageId );
1997 if ( pItem )
1999 pItem->maTabImage = i_rImage;
2000 mbFormat = true;
2001 if ( IsUpdateMode() )
2002 Invalidate();
2006 tools::Rectangle TabControl::GetCharacterBounds( sal_uInt16 nPageId, long nIndex ) const
2008 tools::Rectangle aRet;
2010 if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
2011 FillLayoutData();
2013 if( HasLayoutData() )
2015 std::unordered_map< int, int >::const_iterator it = mpTabCtrlData->maLayoutPageIdToLine.find( static_cast<int>(nPageId) );
2016 if( it != mpTabCtrlData->maLayoutPageIdToLine.end() )
2018 Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( it->second );
2019 if( (aPair.B() - aPair.A()) >= nIndex )
2020 aRet = mpControlData->mpLayoutData->GetCharacterBounds( aPair.A() + nIndex );
2024 return aRet;
2027 long TabControl::GetIndexForPoint( const Point& rPoint, sal_uInt16& rPageId ) const
2029 long nRet = -1;
2031 if( !HasLayoutData() || mpTabCtrlData->maLayoutPageIdToLine.empty() )
2032 FillLayoutData();
2034 if( HasLayoutData() )
2036 int nIndex = mpControlData->mpLayoutData->GetIndexForPoint( rPoint );
2037 if( nIndex != -1 )
2039 // what line (->pageid) is this index in ?
2040 int nLines = mpControlData->mpLayoutData->GetLineCount();
2041 int nLine = -1;
2042 while( ++nLine < nLines )
2044 Pair aPair = mpControlData->mpLayoutData->GetLineStartEnd( nLine );
2045 if( aPair.A() <= nIndex && aPair.B() >= nIndex )
2047 nRet = nIndex - aPair.A();
2048 rPageId = static_cast<sal_uInt16>(mpTabCtrlData->maLayoutLineToPageId[ nLine ]);
2049 break;
2055 return nRet;
2058 void TabControl::FillLayoutData() const
2060 mpTabCtrlData->maLayoutLineToPageId.clear();
2061 mpTabCtrlData->maLayoutPageIdToLine.clear();
2062 const_cast<TabControl*>(this)->Invalidate();
2065 tools::Rectangle TabControl::GetTabBounds( sal_uInt16 nPageId ) const
2067 tools::Rectangle aRet;
2069 ImplTabItem* pItem = ImplGetItem( nPageId );
2070 if (pItem && pItem->m_bVisible)
2071 aRet = pItem->maRect;
2073 return aRet;
2076 void TabControl::SetItemsOffset( const Point& rOffs )
2078 if( mpTabCtrlData )
2079 mpTabCtrlData->maItemsOffset = rOffs;
2082 Point TabControl::GetItemsOffset() const
2084 if( mpTabCtrlData )
2085 return mpTabCtrlData->maItemsOffset;
2086 else
2087 return Point();
2090 Size TabControl::ImplCalculateRequisition(sal_uInt16& nHeaderHeight) const
2092 Size aOptimalPageSize(0, 0);
2094 sal_uInt16 nOrigPageId = GetCurPageId();
2095 for (auto const& item : mpTabCtrlData->maItemList)
2097 const TabPage *pPage = item.mpTabPage;
2098 //it's a real nuisance if the page is not inserted yet :-(
2099 //We need to force all tabs to exist to get overall optimal size for dialog
2100 if (!pPage)
2102 TabControl *pThis = const_cast<TabControl*>(this);
2103 pThis->SetCurPageId(item.id());
2104 pThis->ActivatePage();
2105 pPage = item.mpTabPage;
2108 if (!pPage)
2109 continue;
2111 Size aPageSize(VclContainer::getLayoutRequisition(*pPage));
2113 if (aPageSize.Width() > aOptimalPageSize.Width())
2114 aOptimalPageSize.setWidth( aPageSize.Width() );
2115 if (aPageSize.Height() > aOptimalPageSize.Height())
2116 aOptimalPageSize.setHeight( aPageSize.Height() );
2119 //fdo#61940 If we were forced to activate pages in order to on-demand
2120 //create them to get their optimal size, then switch back to the original
2121 //page and re-activate it
2122 if (nOrigPageId != GetCurPageId())
2124 TabControl *pThis = const_cast<TabControl*>(this);
2125 pThis->SetCurPageId(nOrigPageId);
2126 pThis->ActivatePage();
2129 long nTabLabelsBottom = 0, nTabLabelsRight = 0;
2130 for (sal_uInt16 nPos(0), sizeList(static_cast <sal_uInt16> (mpTabCtrlData->maItemList.size()));
2131 nPos < sizeList; ++nPos)
2133 TabControl* pThis = const_cast<TabControl*>(this);
2135 tools::Rectangle aTabRect = pThis->ImplGetTabRect(nPos, aOptimalPageSize.Width(), LONG_MAX);
2136 if (aTabRect.Bottom() > nTabLabelsBottom)
2138 nTabLabelsBottom = aTabRect.Bottom();
2139 nHeaderHeight = nTabLabelsBottom;
2141 if (!aTabRect.IsEmpty() && aTabRect.Right() > nTabLabelsRight)
2142 nTabLabelsRight = aTabRect.Right();
2145 Size aOptimalSize(aOptimalPageSize);
2146 aOptimalSize.AdjustHeight(nTabLabelsBottom );
2147 aOptimalSize.setWidth( std::max(nTabLabelsRight, aOptimalSize.Width()) );
2149 aOptimalSize.AdjustWidth(TAB_OFFSET * 2 );
2150 aOptimalSize.AdjustHeight(TAB_OFFSET * 2 );
2152 return aOptimalSize;
2155 Size TabControl::calculateRequisition() const
2157 sal_uInt16 nHeaderHeight;
2158 return ImplCalculateRequisition(nHeaderHeight);
2161 Size TabControl::GetOptimalSize() const
2163 return calculateRequisition();
2166 void TabControl::queue_resize(StateChangedType eReason)
2168 mbLayoutDirty = true;
2169 Window::queue_resize(eReason);
2172 std::vector<sal_uInt16> TabControl::GetPageIDs() const
2174 std::vector<sal_uInt16> aIDs;
2175 for (auto const& item : mpTabCtrlData->maItemList)
2177 aIDs.push_back(item.id());
2180 return aIDs;
2183 FactoryFunction TabControl::GetUITestFactory() const
2185 return TabControlUIObject::create;
2188 sal_uInt16 NotebookbarTabControlBase::m_nHeaderHeight = 0;
2190 IMPL_LINK_NOARG(NotebookbarTabControlBase, OpenMenu, Button*, void)
2192 m_aIconClickHdl.Call(static_cast<NotebookBar*>(GetParent()->GetParent()));
2195 NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window* pParent)
2196 : TabControl(pParent, WB_STDTABCONTROL)
2197 , bLastContextWasSupported(true)
2198 , eLastContext(vcl::EnumContext::Context::Any)
2200 m_pOpenMenu = VclPtr<PushButton>::Create( this , WB_CENTER | WB_VCENTER );
2201 m_pOpenMenu->SetSizePixel(Size(HAMBURGER_DIM, HAMBURGER_DIM));
2202 m_pOpenMenu->SetClickHdl(LINK(this, NotebookbarTabControlBase, OpenMenu));
2203 m_pOpenMenu->SetModeImage(Image(StockImage::Yes, SV_RESID_BITMAP_NOTEBOOKBAR));
2204 m_pOpenMenu->Show();
2207 NotebookbarTabControlBase::~NotebookbarTabControlBase()
2209 disposeOnce();
2212 void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext )
2214 if (eLastContext != eContext)
2216 bool bHandled = false;
2218 for (int nChild = 0; nChild < GetPageCount(); ++nChild)
2220 sal_uInt16 nPageId = TabControl::GetPageId(nChild);
2221 TabPage* pPage = GetTabPage(nPageId);
2223 if (pPage)
2225 SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any));
2227 if (!bHandled && bLastContextWasSupported
2228 && pPage->HasContext(vcl::EnumContext::Context::Default))
2230 SetCurPageId(nPageId);
2233 if (pPage->HasContext(eContext) && eContext != vcl::EnumContext::Context::Any)
2235 SetCurPageId(nPageId);
2236 bHandled = true;
2237 bLastContextWasSupported = true;
2242 if (!bHandled)
2243 bLastContextWasSupported = false;
2244 eLastContext = eContext;
2248 void NotebookbarTabControlBase::dispose()
2250 m_pShortcuts.disposeAndClear();
2251 m_pOpenMenu.disposeAndClear();
2252 TabControl::dispose();
2255 void NotebookbarTabControlBase::SetToolBox( ToolBox* pToolBox )
2257 m_pShortcuts.set( pToolBox );
2260 void NotebookbarTabControlBase::SetIconClickHdl( Link<NotebookBar*, void> aHdl )
2262 m_aIconClickHdl = aHdl;
2265 static bool lcl_isValidPage(const ImplTabItem& rItem, bool& bFound)
2267 if (rItem.m_bVisible && rItem.m_bEnabled)
2268 bFound = true;
2269 return bFound;
2272 void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext )
2274 const sal_uInt16 nOldPos = GetPagePos(GetCurPageId());
2275 bool bFound = false;
2276 sal_Int32 nCurPos = nOldPos;
2278 if (bNext)
2280 for (nCurPos++; nCurPos < GetPageCount(); nCurPos++)
2281 if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
2282 break;
2284 else
2286 for (nCurPos--; nCurPos >= 0; nCurPos--)
2287 if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
2288 break;
2291 if (!bFound)
2292 nCurPos = nOldPos;
2293 SelectTabPage( TabControl::GetPageId( nCurPos ) );
2296 sal_uInt16 NotebookbarTabControlBase::GetHeaderHeight()
2298 return m_nHeaderHeight;
2301 bool NotebookbarTabControlBase::ImplPlaceTabs( long nWidth )
2303 if ( nWidth <= 0 )
2304 return false;
2305 if ( mpTabCtrlData->maItemList.empty() )
2306 return false;
2307 if (!m_pOpenMenu || m_pOpenMenu->isDisposed())
2308 return false;
2310 long nMaxWidth = nWidth - HAMBURGER_DIM;
2311 long nShortcutsWidth = m_pShortcuts != nullptr ? m_pShortcuts->GetSizePixel().getWidth() + 1 : 0;
2312 long nFullWidth = nShortcutsWidth;
2314 const long nOffsetX = 2 + GetItemsOffset().X() + nShortcutsWidth;
2315 const long nOffsetY = 2 + GetItemsOffset().Y();
2317 //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
2318 //of ugly bare tabs on lines of their own
2320 for (auto & item : mpTabCtrlData->maItemList)
2322 long nTabWidth = 0;
2323 if (item.m_bVisible)
2325 nTabWidth = ImplGetItemSize(&item, nMaxWidth).getWidth();
2326 if (!item.maText.isEmpty() && nTabWidth < 100)
2327 nTabWidth = 100;
2329 nFullWidth += nTabWidth;
2332 nMaxWidth -= GetItemsOffset().X();
2334 long nX = nOffsetX;
2335 long nY = nOffsetY;
2337 long nLineWidthAry[100];
2338 nLineWidthAry[0] = 0;
2340 for (auto & item : mpTabCtrlData->maItemList)
2342 if (!item.m_bVisible)
2343 continue;
2345 Size aSize = ImplGetItemSize( &item, nMaxWidth );
2347 // set minimum tab size
2348 if( nFullWidth < nMaxWidth && !item.maText.isEmpty() && aSize.getWidth() < 100)
2349 aSize.setWidth( 100 );
2351 if( !item.maText.isEmpty() && aSize.getHeight() < 28 )
2352 aSize.setHeight( 28 );
2354 tools::Rectangle aNewRect( Point( nX, nY ), aSize );
2355 if ( mbSmallInvalidate && (item.maRect != aNewRect) )
2356 mbSmallInvalidate = false;
2358 item.maRect = aNewRect;
2359 item.mnLine = 0;
2360 item.mbFullVisible = true;
2362 nLineWidthAry[0] += aSize.Width();
2363 nX += aSize.Width();
2366 // we always have only one line of tabs
2367 lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
2369 // position the shortcutbox
2370 if (m_pShortcuts)
2371 m_pShortcuts->SetPosPixel(Point(0, 0));
2373 // position the menu
2374 m_pOpenMenu->SetPosPixel(Point(nWidth - HAMBURGER_DIM, 0));
2376 return true;
2379 Size NotebookbarTabControlBase::calculateRequisition() const
2381 return TabControl::ImplCalculateRequisition(m_nHeaderHeight);
2384 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */