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