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