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