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 <i18nlangtag/languagetag.hxx> 24#include <rtl/ustrbuf.hxx> 25#include <vcl/print.hxx> 26#include <vcl/image.hxx> 27#include <vcl/virdev.hxx> 28#include <vcl/svapp.hxx> 29#include <vcl/unohelp.hxx> 30#include <vcl/settings.hxx> 31 32#include <osx/printview.h> 33#include <osx/salinst.h> 34#include <quartz/utils.h> 35 36#include <svdata.hxx> 37#include <strings.hrc> 38#include <printaccessoryview.hrc> 39 40#include <com/sun/star/i18n/XBreakIterator.hpp> 41#include <com/sun/star/i18n/WordType.hpp> 42 43#include <map> 44#include <string_view> 45#include <utility> 46 47using namespace vcl; 48using namespace com::sun::star; 49using namespace com::sun::star::beans; 50using namespace com::sun::star::uno; 51 52namespace { 53 54class ControllerProperties; 55 56} 57 58@interface ControlTarget : NSObject 59{ 60 ControllerProperties* mpController; 61} 62-(id)initWithControllerMap: (ControllerProperties*)pController; 63-(void)triggered:(id)pSender; 64-(void)triggeredNumeric:(id)pSender; 65-(void)dealloc; 66@end 67 68@interface AquaPrintPanelAccessoryController : NSViewController< NSPrintPanelAccessorizing > 69{ 70 NSPrintOperation *mpPrintOperation; 71 vcl::PrinterController *mpPrinterController; 72 PrintAccessoryViewState *mpViewState; 73} 74 75-(void)forPrintOperation:(NSPrintOperation*)pPrintOp; 76-(void)withPrinterController:(vcl::PrinterController*)pController; 77-(void)withViewState:(PrintAccessoryViewState*)pState; 78 79-(NSPrintOperation*)printOperation; 80-(vcl::PrinterController*)printerController; 81-(PrintAccessoryViewState*)viewState; 82 83-(NSSet*)keyPathsForValuesAffectingPreview; 84-(NSArray*)localizedSummaryItems; 85 86-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount; 87 88@end 89 90@implementation AquaPrintPanelAccessoryController 91 92-(void)forPrintOperation:(NSPrintOperation*)pPrintOp 93 { mpPrintOperation = pPrintOp; } 94 95-(void)withPrinterController:(vcl::PrinterController*)pController 96 { mpPrinterController = pController; } 97 98-(void)withViewState:(PrintAccessoryViewState*)pState 99 { mpViewState = pState; } 100 101-(NSPrintOperation*)printOperation 102 { return mpPrintOperation; } 103 104-(vcl::PrinterController*)printerController 105 { return mpPrinterController; } 106 107-(PrintAccessoryViewState*)viewState 108 { return mpViewState; } 109 110-(NSSet*)keyPathsForValuesAffectingPreview 111{ 112 return [ NSSet setWithObject:@"updatePrintOperation" ]; 113} 114 115-(NSArray*)localizedSummaryItems 116{ 117 return [ NSArray arrayWithObject: 118 [ NSDictionary dictionary ] ]; 119} 120 121-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount 122{ 123 // page range may be changed by option choice 124 sal_Int32 nPages = mpPrinterController->getFilteredPageCount(); 125 126 mpViewState->bNeedRestart = false; 127 if( nPages != pLastPageCount ) 128 { 129 #if OSL_DEBUG_LEVEL > 1 130 SAL_INFO( "vcl.osx.print", "number of pages changed" << 131 " from " << pLastPageCount << " to " << nPages ); 132 #endif 133 mpViewState->bNeedRestart = true; 134 } 135 136 NSTabView* pTabView = [[[self view] subviews] objectAtIndex:0]; 137 NSTabViewItem* pItem = [pTabView selectedTabViewItem]; 138 if( pItem ) 139 mpViewState->nLastPage = [pTabView indexOfTabViewItem: pItem]; 140 else 141 mpViewState->nLastPage = 0; 142 143 if( mpViewState->bNeedRestart ) 144 { 145 // AppKit does not give a chance of changing the page count 146 // and don't let cancel the dialog either 147 // hack: send a cancel message to the modal window displaying views 148 NSWindow* pNSWindow = [NSApp modalWindow]; 149 if( pNSWindow ) 150 [pNSWindow cancelOperation: nil]; 151 [[mpPrintOperation printInfo] setJobDisposition: NSPrintCancelJob]; 152 } 153 154 return nPages; 155} 156 157@end 158 159namespace { 160 161class ControllerProperties 162{ 163 std::map< int, OUString > maTagToPropertyName; 164 std::map< int, sal_Int32 > maTagToValueInt; 165 std::map< NSView*, NSView* > maViewPairMap; 166 std::vector< NSObject* > maViews; 167 int mnNextTag; 168 sal_Int32 mnLastPageCount; 169 AquaPrintPanelAccessoryController* mpAccessoryController; 170 171public: 172 ControllerProperties( AquaPrintPanelAccessoryController* i_pAccessoryController ) 173 : mnNextTag( 0 ) 174 , mnLastPageCount( [i_pAccessoryController printerController]->getFilteredPageCount() ) 175 , mpAccessoryController( i_pAccessoryController ) 176 { 177 static_assert( SAL_N_ELEMENTS(SV_PRINT_NATIVE_STRINGS) == 5, "resources not found" ); 178 } 179 180 static OUString getMoreString() 181 { 182 return VclResId(SV_PRINT_NATIVE_STRINGS[3]); 183 } 184 185 static OUString getPrintSelectionString() 186 { 187 return VclResId(SV_PRINT_NATIVE_STRINGS[4]); 188 } 189 190 int addNameTag( const OUString& i_rPropertyName ) 191 { 192 int nNewTag = mnNextTag++; 193 maTagToPropertyName[ nNewTag ] = i_rPropertyName; 194 return nNewTag; 195 } 196 197 int addNameAndValueTag( const OUString& i_rPropertyName, sal_Int32 i_nValue ) 198 { 199 int nNewTag = mnNextTag++; 200 maTagToPropertyName[ nNewTag ] = i_rPropertyName; 201 maTagToValueInt[ nNewTag ] = i_nValue; 202 return nNewTag; 203 } 204 205 void addObservedControl( NSObject* i_pView ) 206 { 207 maViews.push_back( i_pView ); 208 } 209 210 void addViewPair( NSView* i_pLeft, NSView* i_pRight ) 211 { 212 maViewPairMap[ i_pLeft ] = i_pRight; 213 maViewPairMap[ i_pRight ] = i_pLeft; 214 } 215 216 NSView* getPair( NSView* i_pLeft ) const 217 { 218 NSView* pRight = nil; 219 std::map< NSView*, NSView* >::const_iterator it = maViewPairMap.find( i_pLeft ); 220 if( it != maViewPairMap.end() ) 221 pRight = it->second; 222 return pRight; 223 } 224 225 void changePropertyWithIntValue( int i_nTag ) 226 { 227 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag ); 228 std::map< int, sal_Int32 >::const_iterator value_it = maTagToValueInt.find( i_nTag ); 229 if( name_it != maTagToPropertyName.end() && value_it != maTagToValueInt.end() ) 230 { 231 vcl::PrinterController * mpController = [mpAccessoryController printerController]; 232 PropertyValue* pVal = mpController->getValue( name_it->second ); 233 if( pVal ) 234 { 235 pVal->Value <<= value_it->second; 236 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount]; 237 } 238 } 239 } 240 241 void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue ) 242 { 243 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag ); 244 if( name_it != maTagToPropertyName.end() ) 245 { 246 vcl::PrinterController * mpController = [mpAccessoryController printerController]; 247 PropertyValue* pVal = mpController->getValue( name_it->second ); 248 if( pVal ) 249 { 250 pVal->Value <<= i_nValue; 251 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount]; 252 } 253 } 254 } 255 256 void changePropertyWithBoolValue( int i_nTag, bool i_bValue ) 257 { 258 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag ); 259 if( name_it != maTagToPropertyName.end() ) 260 { 261 vcl::PrinterController * mpController = [mpAccessoryController printerController]; 262 PropertyValue* pVal = mpController->getValue( name_it->second ); 263 if( pVal ) 264 { 265 // ugly 266 if( name_it->second == "PrintContent" ) 267 pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0); 268 else 269 pVal->Value <<= i_bValue; 270 271 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount]; 272 } 273 } 274 } 275 276 void changePropertyWithStringValue( int i_nTag, const OUString& i_rValue ) 277 { 278 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag ); 279 if( name_it != maTagToPropertyName.end() ) 280 { 281 vcl::PrinterController * mpController = [mpAccessoryController printerController]; 282 PropertyValue* pVal = mpController->getValue( name_it->second ); 283 if( pVal ) 284 { 285 pVal->Value <<= i_rValue; 286 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount]; 287 } 288 } 289 } 290 291 void updateEnableState() 292 { 293 for( std::vector< NSObject* >::iterator it = maViews.begin(); it != maViews.end(); ++it ) 294 { 295 NSObject* pObj = *it; 296 NSControl* pCtrl = nil; 297 NSCell* pCell = nil; 298 if( [pObj isKindOfClass: [NSControl class]] ) 299 pCtrl = static_cast<NSControl*>(pObj); 300 else if( [pObj isKindOfClass: [NSCell class]] ) 301 pCell = static_cast<NSCell*>(pObj); 302 303 int nTag = pCtrl ? [pCtrl tag] : 304 pCell ? [pCell tag] : 305 -1; 306 307 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( nTag ); 308 if( name_it != maTagToPropertyName.end() && name_it->second != "PrintContent" ) 309 { 310 vcl::PrinterController * mpController = [mpAccessoryController printerController]; 311 bool bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO; 312 if( pCtrl ) 313 { 314 [pCtrl setEnabled: bEnabled]; 315 NSView* pOther = getPair( pCtrl ); 316 if( pOther && [pOther isKindOfClass: [NSControl class]] ) 317 [static_cast<NSControl*>(pOther) setEnabled: bEnabled]; 318 } 319 else if( pCell ) 320 [pCell setEnabled: bEnabled]; 321 } 322 } 323 } 324 325}; 326 327} 328 329static OUString filterAccelerator( OUString const & rText ) 330{ 331 OUStringBuffer aBuf( rText.getLength() ); 332 for( sal_Int32 nIndex = 0; nIndex != -1; ) 333 aBuf.append( rText.getToken( 0, '~', nIndex ) ); 334 return aBuf.makeStringAndClear(); 335} 336 337@implementation ControlTarget 338 339-(id)initWithControllerMap: (ControllerProperties*)pController 340{ 341 if( (self = [super init]) ) 342 { 343 mpController = pController; 344 } 345 return self; 346} 347 348-(void)triggered:(id)pSender 349{ 350 if( [pSender isMemberOfClass: [NSPopUpButton class]] ) 351 { 352 NSPopUpButton* pBtn = static_cast<NSPopUpButton*>(pSender); 353 NSMenuItem* pSelected = [pBtn selectedItem]; 354 if( pSelected ) 355 { 356 int nTag = [pSelected tag]; 357 mpController->changePropertyWithIntValue( nTag ); 358 } 359 } 360 else if( [pSender isMemberOfClass: [NSButton class]] ) 361 { 362 NSButton* pBtn = static_cast<NSButton*>(pSender); 363 int nTag = [pBtn tag]; 364 mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSControlStateValueOn ); 365 } 366 else if( [pSender isMemberOfClass: [NSMatrix class]] ) 367 { 368 NSObject* pObj = [static_cast<NSMatrix*>(pSender) selectedCell]; 369 if( [pObj isMemberOfClass: [NSButtonCell class]] ) 370 { 371 NSButtonCell* pCell = static_cast<NSButtonCell*>(pObj); 372 int nTag = [pCell tag]; 373 mpController->changePropertyWithIntValue( nTag ); 374 } 375 } 376 else if( [pSender isMemberOfClass: [NSTextField class]] ) 377 { 378 NSTextField* pField = static_cast<NSTextField*>(pSender); 379 int nTag = [pField tag]; 380 OUString aValue = GetOUString( [pSender stringValue] ); 381 mpController->changePropertyWithStringValue( nTag, aValue ); 382 } 383 else 384 { 385 SAL_INFO( "vcl.osx.print", "Unsupported class" << 386 ( [pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil" ) ); 387 } 388 mpController->updateEnableState(); 389} 390 391-(void)triggeredNumeric:(id)pSender 392{ 393 if( [pSender isMemberOfClass: [NSTextField class]] ) 394 { 395 NSTextField* pField = static_cast<NSTextField*>(pSender); 396 int nTag = [pField tag]; 397 sal_Int64 nValue = [pField intValue]; 398 399 NSView* pOther = mpController->getPair( pField ); 400 if( pOther ) 401 [static_cast<NSControl*>(pOther) setIntValue: nValue]; 402 403 mpController->changePropertyWithIntValue( nTag, nValue ); 404 } 405 else if( [pSender isMemberOfClass: [NSStepper class]] ) 406 { 407 NSStepper* pStep = static_cast<NSStepper*>(pSender); 408 int nTag = [pStep tag]; 409 sal_Int64 nValue = [pStep intValue]; 410 411 NSView* pOther = mpController->getPair( pStep ); 412 if( pOther ) 413 [static_cast<NSControl*>(pOther) setIntValue: nValue]; 414 415 mpController->changePropertyWithIntValue( nTag, nValue ); 416 } 417 else 418 { 419 SAL_INFO( "vcl.osx.print", "Unsupported class" << 420 ([pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil") ); 421 } 422 mpController->updateEnableState(); 423} 424 425-(void)dealloc 426{ 427 delete mpController; 428 [super dealloc]; 429} 430 431@end 432 433namespace { 434 435struct ColumnItem 436{ 437 NSControl* pControl; 438 CGFloat nOffset; 439 NSControl* pSubControl; 440 441 ColumnItem( NSControl* i_pControl = nil, CGFloat i_nOffset = 0, NSControl* i_pSub = nil ) 442 : pControl( i_pControl ) 443 , nOffset( i_nOffset ) 444 , pSubControl( i_pSub ) 445 {} 446 447 CGFloat getWidth() const 448 { 449 CGFloat nWidth = 0; 450 if( pControl ) 451 { 452 NSRect aCtrlRect = [pControl frame]; 453 nWidth = aCtrlRect.size.width; 454 nWidth += nOffset; 455 if( pSubControl ) 456 { 457 NSRect aSubRect = [pSubControl frame]; 458 nWidth += aSubRect.size.width; 459 nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width); 460 } 461 } 462 return nWidth; 463 } 464}; 465 466} 467 468static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize, 469 std::vector< ColumnItem >& rLeftColumn, 470 std::vector< ColumnItem >& rRightColumn 471 ) 472{ 473 // balance columns 474 475 // first get overall column widths 476 CGFloat nLeftWidth = 0; 477 CGFloat nRightWidth = 0; 478 for( size_t i = 0; i < rLeftColumn.size(); i++ ) 479 { 480 CGFloat nW = rLeftColumn[i].getWidth(); 481 if( nW > nLeftWidth ) 482 nLeftWidth = nW; 483 } 484 for( size_t i = 0; i < rRightColumn.size(); i++ ) 485 { 486 CGFloat nW = rRightColumn[i].getWidth(); 487 if( nW > nRightWidth ) 488 nRightWidth = nW; 489 } 490 491 // right align left column 492 for( size_t i = 0; i < rLeftColumn.size(); i++ ) 493 { 494 if( rLeftColumn[i].pControl ) 495 { 496 NSRect aCtrlRect = [rLeftColumn[i].pControl frame]; 497 CGFloat nX = nLeftWidth - aCtrlRect.size.width; 498 if( rLeftColumn[i].pSubControl ) 499 { 500 NSRect aSubRect = [rLeftColumn[i].pSubControl frame]; 501 nX -= aSubRect.size.width + (aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width)); 502 aSubRect.origin.x = nLeftWidth - aSubRect.size.width; 503 [rLeftColumn[i].pSubControl setFrame: aSubRect]; 504 } 505 aCtrlRect.origin.x = nX; 506 [rLeftColumn[i].pControl setFrame: aCtrlRect]; 507 } 508 } 509 510 // left align right column 511 for( size_t i = 0; i < rRightColumn.size(); i++ ) 512 { 513 if( rRightColumn[i].pControl ) 514 { 515 NSRect aCtrlRect = [rRightColumn[i].pControl frame]; 516 CGFloat nX = nLeftWidth + 3; 517 if( rRightColumn[i].pSubControl ) 518 { 519 NSRect aSubRect = [rRightColumn[i].pSubControl frame]; 520 aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x; 521 [rRightColumn[i].pSubControl setFrame: aSubRect]; 522 } 523 aCtrlRect.origin.x = nX; 524 [rRightColumn[i].pControl setFrame: aCtrlRect]; 525 } 526 } 527 528 NSArray* pSubViews = [pNSView subviews]; 529 unsigned int nViews = [pSubViews count]; 530 NSRect aUnion = NSZeroRect; 531 532 // get the combined frame of all subviews 533 for( unsigned int n = 0; n < nViews; n++ ) 534 { 535 aUnion = NSUnionRect( aUnion, [[pSubViews objectAtIndex: n] frame] ); 536 } 537 538 // move everything so it will fit 539 for( unsigned int n = 0; n < nViews; n++ ) 540 { 541 NSView* pCurSubView = [pSubViews objectAtIndex: n]; 542 NSRect aFrame = [pCurSubView frame]; 543 aFrame.origin.x -= aUnion.origin.x - 5; 544 aFrame.origin.y -= aUnion.origin.y - 5; 545 [pCurSubView setFrame: aFrame]; 546 } 547 548 // resize the view itself 549 aUnion.size.height += 10; 550 aUnion.size.width += 20; 551 [pNSView setFrameSize: aUnion.size]; 552 553 if( aUnion.size.width > rMaxSize.width ) 554 rMaxSize.width = aUnion.size.width; 555 if( aUnion.size.height > rMaxSize.height ) 556 rMaxSize.height = aUnion.size.height; 557} 558 559static void adjustTabViews( NSTabView* pTabView, NSSize aTabSize ) 560{ 561 // loop over all contained tab pages 562 NSArray* pTabbedViews = [pTabView tabViewItems]; 563 int nViews = [pTabbedViews count]; 564 for( int i = 0; i < nViews; i++ ) 565 { 566 NSTabViewItem* pItem = static_cast<NSTabViewItem*>([pTabbedViews objectAtIndex: i]); 567 NSView* pNSView = [pItem view]; 568 if( pNSView ) 569 { 570 NSRect aRect = [pNSView frame]; 571 double nDiff = aTabSize.height - aRect.size.height; 572 aRect.size = aTabSize; 573 [pNSView setFrame: aRect]; 574 575 NSArray* pSubViews = [pNSView subviews]; 576 unsigned int nSubViews = [pSubViews count]; 577 578 // move everything up 579 for( unsigned int n = 0; n < nSubViews; n++ ) 580 { 581 NSView* pCurSubView = [pSubViews objectAtIndex: n]; 582 NSRect aFrame = [pCurSubView frame]; 583 aFrame.origin.y += nDiff; 584 // give separators the correct width 585 // separators are currently the only NSBoxes we use 586 if( [pCurSubView isMemberOfClass: [NSBox class]] ) 587 { 588 aFrame.size.width = aTabSize.width - aFrame.origin.x - 10; 589 } 590 [pCurSubView setFrame: aFrame]; 591 } 592 } 593 } 594} 595 596static NSControl* createLabel( const OUString& i_rText ) 597{ 598 NSString* pText = CreateNSString( i_rText ); 599 NSRect aTextRect = { NSZeroPoint, {20, 15} }; 600 NSTextField* pTextView = [[NSTextField alloc] initWithFrame: aTextRect]; 601 [pTextView setFont: [NSFont controlContentFontOfSize: 0]]; 602 [pTextView setEditable: NO]; 603 [pTextView setSelectable: NO]; 604 [pTextView setDrawsBackground: NO]; 605 [pTextView setBordered: NO]; 606 [pTextView setStringValue: pText]; 607 [pTextView sizeToFit]; 608 [pText release]; 609 return pTextView; 610} 611 612static sal_Int32 findBreak( const OUString& i_rText, sal_Int32 i_nPos ) 613{ 614 sal_Int32 nRet = i_rText.getLength(); 615 Reference< i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() ); 616 if( xBI.is() ) 617 { 618 i18n::Boundary aBoundary = 619 xBI->getWordBoundary( i_rText, i_nPos, 620 Application::GetSettings().GetLanguageTag().getLocale(), 621 i18n::WordType::ANYWORD_IGNOREWHITESPACES, 622 true ); 623 nRet = aBoundary.endPos; 624 } 625 return nRet; 626} 627 628static void linebreakCell( NSCell* pBtn, const OUString& i_rText ) 629{ 630 NSString* pText = CreateNSString( i_rText ); 631 [pBtn setTitle: pText]; 632 [pText release]; 633 NSSize aSize = [pBtn cellSize]; 634 if( aSize.width > 280 ) 635 { 636 // need two lines 637 sal_Int32 nLen = i_rText.getLength(); 638 sal_Int32 nIndex = nLen / 2; 639 nIndex = findBreak( i_rText, nIndex ); 640 if( nIndex < nLen ) 641 { 642 OUStringBuffer aBuf( i_rText ); 643 aBuf[nIndex] = '\n'; 644 pText = CreateNSString( aBuf.makeStringAndClear() ); 645 [pBtn setTitle: pText]; 646 [pText release]; 647 } 648 } 649} 650 651static void addSubgroup( NSView* pCurParent, CGFloat& rCurY, const OUString& rText ) 652{ 653 NSControl* pTextView = createLabel( rText ); 654 [pCurParent addSubview: [pTextView autorelease]]; 655 NSRect aTextRect = [pTextView frame]; 656 // move to nCurY 657 aTextRect.origin.y = rCurY - aTextRect.size.height; 658 [pTextView setFrame: aTextRect]; 659 660 NSRect aSepRect = { { aTextRect.size.width + 1, aTextRect.origin.y }, { 100, 6 } }; 661 NSBox* pBox = [[NSBox alloc] initWithFrame: aSepRect]; 662 [pBox setBoxType: NSBoxSeparator]; 663 [pCurParent addSubview: [pBox autorelease]]; 664 665 // update nCurY 666 rCurY = aTextRect.origin.y - 5; 667} 668 669static void addBool( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset, 670 const OUString& rText, bool bEnabled, 671 const OUString& rProperty, bool bValue, 672 std::vector<ColumnItem >& rRightColumn, 673 ControllerProperties* pControllerProperties, 674 ControlTarget* pCtrlTarget 675 ) 676{ 677 NSRect aCheckRect = { { rCurX + nAttachOffset, 0 }, { 0, 15 } }; 678 NSButton* pBtn = [[NSButton alloc] initWithFrame: aCheckRect]; 679 [pBtn setButtonType: NSButtonTypeSwitch]; 680 [pBtn setState: bValue ? NSControlStateValueOn : NSControlStateValueOff]; 681 if( ! bEnabled ) 682 [pBtn setEnabled: NO]; 683 linebreakCell( [pBtn cell], rText ); 684 [pBtn sizeToFit]; 685 686 rRightColumn.push_back( ColumnItem( pBtn ) ); 687 688 // connect target 689 [pBtn setTarget: pCtrlTarget]; 690 [pBtn setAction: @selector(triggered:)]; 691 int nTag = pControllerProperties->addNameTag( rProperty ); 692 pControllerProperties->addObservedControl( pBtn ); 693 [pBtn setTag: nTag]; 694 695 aCheckRect = [pBtn frame]; 696 // #i115837# add a murphy factor; it can apparently occasionally happen 697 // that sizeToFit does not a perfect job and that the button linebreaks again 698 // if - and only if - there is already a '\n' contained in the text and the width 699 // is minimally of 700 aCheckRect.size.width += 1; 701 702 // move to rCurY 703 aCheckRect.origin.y = rCurY - aCheckRect.size.height; 704 [pBtn setFrame: aCheckRect]; 705 706 [pCurParent addSubview: [pBtn autorelease]]; 707 708 // update rCurY 709 rCurY = aCheckRect.origin.y - 5; 710} 711 712static void addRadio( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset, 713 const OUString& rText, 714 const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue, 715 std::vector<ColumnItem >& rLeftColumn, 716 std::vector<ColumnItem >& rRightColumn, 717 ControllerProperties* pControllerProperties, 718 ControlTarget* pCtrlTarget 719 ) 720{ 721 CGFloat nOff = 0; 722 if( rText.getLength() ) 723 { 724 // add a label 725 NSControl* pTextView = createLabel( rText ); 726 NSRect aTextRect = [pTextView frame]; 727 aTextRect.origin.x = rCurX + nAttachOffset; 728 [pCurParent addSubview: [pTextView autorelease]]; 729 730 rLeftColumn.push_back( ColumnItem( pTextView ) ); 731 732 // move to nCurY 733 aTextRect.origin.y = rCurY - aTextRect.size.height; 734 [pTextView setFrame: aTextRect]; 735 736 // update nCurY 737 rCurY = aTextRect.origin.y - 5; 738 739 // indent the radio group relative to the text 740 // nOff = 20; 741 } 742 743 // setup radio matrix 744 NSButtonCell* pProto = [[NSButtonCell alloc] init]; 745 746 NSRect aRadioRect = { { rCurX + nOff, 0 }, 747 { 280 - rCurX, 748 static_cast<CGFloat>(5*rChoices.getLength()) } }; 749 [pProto setTitle: @"RadioButtonGroup"]; 750 [pProto setButtonType: NSButtonTypeRadio]; 751 NSMatrix* pMatrix = [[NSMatrix alloc] initWithFrame: aRadioRect 752 mode: NSRadioModeMatrix 753 prototype: static_cast<NSCell*>(pProto) 754 numberOfRows: rChoices.getLength() 755 numberOfColumns: 1]; 756 // set individual titles 757 NSArray* pCells = [pMatrix cells]; 758 for( sal_Int32 m = 0; m < rChoices.getLength(); m++ ) 759 { 760 NSCell* pCell = [pCells objectAtIndex: m]; 761 linebreakCell( pCell, filterAccelerator( rChoices[m] ) ); 762 // connect target and action 763 [pCell setTarget: pCtrlTarget]; 764 [pCell setAction: @selector(triggered:)]; 765 int nTag = pControllerProperties->addNameAndValueTag( rProperty, m ); 766 pControllerProperties->addObservedControl( pCell ); 767 [pCell setTag: nTag]; 768 // set current selection 769 if( nSelectValue == m ) 770 [pMatrix selectCellAtRow: m column: 0]; 771 } 772 [pMatrix sizeToFit]; 773 aRadioRect = [pMatrix frame]; 774 775 // move it down, so it comes to the correct position 776 aRadioRect.origin.y = rCurY - aRadioRect.size.height; 777 [pMatrix setFrame: aRadioRect]; 778 [pCurParent addSubview: [pMatrix autorelease]]; 779 780 rRightColumn.push_back( ColumnItem( pMatrix ) ); 781 782 // update nCurY 783 rCurY = aRadioRect.origin.y - 5; 784 785 [pProto release]; 786} 787 788static void addList( NSView* pCurParent, CGFloat& rCurX, CGFloat& rCurY, CGFloat /*nAttachOffset*/, 789 const OUString& rText, 790 const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue, 791 std::vector<ColumnItem >& rLeftColumn, 792 std::vector<ColumnItem >& rRightColumn, 793 ControllerProperties* pControllerProperties, 794 ControlTarget* pCtrlTarget 795 ) 796{ 797 // don't indent attached lists, looks bad in the existing cases 798 NSControl* pTextView = createLabel( rText ); 799 [pCurParent addSubview: [pTextView autorelease]]; 800 rLeftColumn.push_back( ColumnItem( pTextView ) ); 801 NSRect aTextRect = [pTextView frame]; 802 aTextRect.origin.x = rCurX /* + nAttachOffset*/; 803 804 // don't indent attached lists, looks bad in the existing cases 805 NSRect aBtnRect = { { rCurX /*+ nAttachOffset*/ + aTextRect.size.width, 0 }, { 0, 15 } }; 806 NSPopUpButton* pBtn = [[NSPopUpButton alloc] initWithFrame: aBtnRect pullsDown: NO]; 807 808 // iterate options 809 for( sal_Int32 m = 0; m < rChoices.getLength(); m++ ) 810 { 811 NSString* pItemText = CreateNSString( rChoices[m] ); 812 [pBtn addItemWithTitle: pItemText]; 813 NSMenuItem* pItem = [pBtn itemWithTitle: pItemText]; 814 int nTag = pControllerProperties->addNameAndValueTag( rProperty, m ); 815 [pItem setTag: nTag]; 816 [pItemText release]; 817 } 818 819 [pBtn selectItemAtIndex: nSelectValue]; 820 821 // add the button to observed controls for enabled state changes 822 // also add a tag just for this purpose 823 pControllerProperties->addObservedControl( pBtn ); 824 [pBtn setTag: pControllerProperties->addNameTag( rProperty )]; 825 826 [pBtn sizeToFit]; 827 [pCurParent addSubview: [pBtn autorelease]]; 828 829 rRightColumn.push_back( ColumnItem( pBtn ) ); 830 831 // connect target and action 832 [pBtn setTarget: pCtrlTarget]; 833 [pBtn setAction: @selector(triggered:)]; 834 835 // move to nCurY 836 aBtnRect = [pBtn frame]; 837 aBtnRect.origin.y = rCurY - aBtnRect.size.height; 838 [pBtn setFrame: aBtnRect]; 839 840 // align label 841 aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2; 842 [pTextView setFrame: aTextRect]; 843 844 // update rCurY 845 rCurY = aBtnRect.origin.y - 5; 846} 847 848static void addEdit( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset, 849 std::u16string_view rCtrlType, 850 const OUString& rText, 851 const OUString& rProperty, const PropertyValue* pValue, 852 sal_Int64 nMinValue, sal_Int64 nMaxValue, 853 std::vector<ColumnItem >& rLeftColumn, 854 std::vector<ColumnItem >& rRightColumn, 855 ControllerProperties* pControllerProperties, 856 ControlTarget* pCtrlTarget 857 ) 858{ 859 CGFloat nOff = 0; 860 if( rText.getLength() ) 861 { 862 // add a label 863 NSControl* pTextView = createLabel( rText ); 864 [pCurParent addSubview: [pTextView autorelease]]; 865 866 rLeftColumn.push_back( ColumnItem( pTextView ) ); 867 868 // move to nCurY 869 NSRect aTextRect = [pTextView frame]; 870 aTextRect.origin.x = rCurX + nAttachOffset; 871 aTextRect.origin.y = rCurY - aTextRect.size.height; 872 [pTextView setFrame: aTextRect]; 873 874 // update nCurY 875 rCurY = aTextRect.origin.y - 5; 876 877 // and set the offset for the real edit field 878 nOff = aTextRect.size.width + 5; 879 } 880 881 NSRect aFieldRect = { { rCurX + nOff + nAttachOffset, 0 }, { 100, 25 } }; 882 NSTextField* pFieldView = [[NSTextField alloc] initWithFrame: aFieldRect]; 883 [pFieldView setEditable: YES]; 884 [pFieldView setSelectable: YES]; 885 [pFieldView setDrawsBackground: YES]; 886 [pFieldView sizeToFit]; // FIXME: this does nothing 887 [pCurParent addSubview: [pFieldView autorelease]]; 888 889 rRightColumn.push_back( ColumnItem( pFieldView ) ); 890 891 // add the field to observed controls for enabled state changes 892 // also add a tag just for this purpose 893 pControllerProperties->addObservedControl( pFieldView ); 894 int nTag = pControllerProperties->addNameTag( rProperty ); 895 [pFieldView setTag: nTag]; 896 // pControllerProperties->addNamedView( pFieldView, aPropertyName ); 897 898 // move to nCurY 899 aFieldRect.origin.y = rCurY - aFieldRect.size.height; 900 [pFieldView setFrame: aFieldRect]; 901 902 if( rCtrlType == u"Range" ) 903 { 904 // add a stepper control 905 NSRect aStepFrame = { { aFieldRect.origin.x + aFieldRect.size.width + 5, 906 aFieldRect.origin.y }, 907 { 15, aFieldRect.size.height } }; 908 NSStepper* pStep = [[NSStepper alloc] initWithFrame: aStepFrame]; 909 [pStep setIncrement: 1]; 910 [pStep setValueWraps: NO]; 911 [pStep setTag: nTag]; 912 [pCurParent addSubview: [pStep autorelease]]; 913 914 rRightColumn.back().pSubControl = pStep; 915 916 pControllerProperties->addObservedControl( pStep ); 917 [pStep setTarget: pCtrlTarget]; 918 [pStep setAction: @selector(triggered:)]; 919 920 // constrain the text field to decimal numbers 921 NSNumberFormatter* pFormatter = [[NSNumberFormatter alloc] init]; 922 [pFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4]; 923 [pFormatter setNumberStyle: NSNumberFormatterDecimalStyle]; 924 [pFormatter setAllowsFloats: NO]; 925 [pFormatter setMaximumFractionDigits: 0]; 926 if( nMinValue != nMaxValue ) 927 { 928 [pFormatter setMinimum: [[NSNumber numberWithInt: nMinValue] autorelease]]; 929 [pStep setMinValue: nMinValue]; 930 [pFormatter setMaximum: [[NSNumber numberWithInt: nMaxValue] autorelease]]; 931 [pStep setMaxValue: nMaxValue]; 932 } 933 [pFieldView setFormatter: pFormatter]; 934 935 sal_Int64 nSelectVal = 0; 936 if( pValue && pValue->Value.hasValue() ) 937 pValue->Value >>= nSelectVal; 938 939 [pFieldView setIntValue: nSelectVal]; 940 [pStep setIntValue: nSelectVal]; 941 942 pControllerProperties->addViewPair( pFieldView, pStep ); 943 // connect target and action 944 [pFieldView setTarget: pCtrlTarget]; 945 [pFieldView setAction: @selector(triggeredNumeric:)]; 946 [pStep setTarget: pCtrlTarget]; 947 [pStep setAction: @selector(triggeredNumeric:)]; 948 } 949 else 950 { 951 // connect target and action 952 [pFieldView setTarget: pCtrlTarget]; 953 [pFieldView setAction: @selector(triggered:)]; 954 955 if( pValue && pValue->Value.hasValue() ) 956 { 957 OUString aValue; 958 pValue->Value >>= aValue; 959 if( aValue.getLength() ) 960 { 961 NSString* pText = CreateNSString( aValue ); 962 [pFieldView setStringValue: pText]; 963 [pText release]; 964 } 965 } 966 } 967 968 // update nCurY 969 rCurY = aFieldRect.origin.y - 5; 970} 971 972@implementation AquaPrintAccessoryView 973 974+(NSObject*)setupPrinterPanel: (NSPrintOperation*)pOp 975 withController: (vcl::PrinterController*)pController 976 withState: (PrintAccessoryViewState*)pState 977{ 978 const Sequence< PropertyValue >& rOptions( pController->getUIOptions() ); 979 if( rOptions.getLength() == 0 ) 980 return nil; 981 982 NSRect aViewFrame = { NSZeroPoint, { 600, 400 } }; 983 NSRect aTabViewFrame = aViewFrame; 984 985 NSView* pAccessoryView = [[NSView alloc] initWithFrame: aViewFrame]; 986 NSTabView* pTabView = [[NSTabView alloc] initWithFrame: aTabViewFrame]; 987 [pAccessoryView addSubview: [pTabView autorelease]]; 988 989 // create the accessory controller 990 AquaPrintPanelAccessoryController* pAccessoryController = 991 [[AquaPrintPanelAccessoryController alloc] initWithNibName: nil bundle: nil]; 992 [pAccessoryController setView: [pAccessoryView autorelease]]; 993 [pAccessoryController forPrintOperation: pOp]; 994 [pAccessoryController withPrinterController: pController]; 995 [pAccessoryController withViewState: pState]; 996 997 NSView* pCurParent = nullptr; 998 CGFloat nCurY = 0; 999 CGFloat nCurX = 0; 1000 NSSize aMaxTabSize = NSZeroSize; 1001 1002 ControllerProperties* pControllerProperties = new ControllerProperties( pAccessoryController ); 1003 ControlTarget* pCtrlTarget = [[ControlTarget alloc] initWithControllerMap: pControllerProperties]; 1004 1005 std::vector< ColumnItem > aLeftColumn, aRightColumn; 1006 1007 // ugly: 1008 // prepend a "selection" checkbox if the properties have such a selection in PrintContent 1009 bool bAddSelectionCheckBox = false, bSelectionBoxEnabled = false, bSelectionBoxChecked = false; 1010 1011 for( const PropertyValue & prop : rOptions ) 1012 { 1013 Sequence< beans::PropertyValue > aOptProp; 1014 prop.Value >>= aOptProp; 1015 1016 OUString aCtrlType; 1017 OUString aPropertyName; 1018 Sequence< OUString > aChoices; 1019 Sequence< sal_Bool > aChoicesDisabled; 1020 sal_Int32 aSelectionChecked = 0; 1021 for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) ) 1022 { 1023 if( rEntry.Name == "ControlType" ) 1024 { 1025 rEntry.Value >>= aCtrlType; 1026 } 1027 else if( rEntry.Name == "Choices" ) 1028 { 1029 rEntry.Value >>= aChoices; 1030 } 1031 else if( rEntry.Name == "ChoicesDisabled" ) 1032 { 1033 rEntry.Value >>= aChoicesDisabled; 1034 } 1035 else if( rEntry.Name == "Property" ) 1036 { 1037 PropertyValue aVal; 1038 rEntry.Value >>= aVal; 1039 aPropertyName = aVal.Name; 1040 if( aPropertyName == "PrintContent" ) 1041 aVal.Value >>= aSelectionChecked; 1042 } 1043 } 1044 if( aCtrlType == "Radio" && 1045 aPropertyName == "PrintContent" && 1046 aChoices.getLength() > 2 ) 1047 { 1048 bAddSelectionCheckBox = true; 1049 bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2]; 1050 bSelectionBoxChecked = (aSelectionChecked==2); 1051 break; 1052 } 1053 } 1054 1055 for( const PropertyValue & prop : rOptions ) 1056 { 1057 Sequence< beans::PropertyValue > aOptProp; 1058 prop.Value >>= aOptProp; 1059 1060 // extract ui element 1061 OUString aCtrlType; 1062 OUString aText; 1063 OUString aPropertyName; 1064 OUString aGroupHint; 1065 Sequence< OUString > aChoices; 1066 sal_Int64 nMinValue = 0, nMaxValue = 0; 1067 CGFloat nAttachOffset = 0; 1068 bool bIgnore = false; 1069 1070 for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) ) 1071 { 1072 if( rEntry.Name == "Text" ) 1073 { 1074 rEntry.Value >>= aText; 1075 aText = filterAccelerator( aText ); 1076 } 1077 else if( rEntry.Name == "ControlType" ) 1078 { 1079 rEntry.Value >>= aCtrlType; 1080 } 1081 else if( rEntry.Name == "Choices" ) 1082 { 1083 rEntry.Value >>= aChoices; 1084 } 1085 else if( rEntry.Name == "Property" ) 1086 { 1087 PropertyValue aVal; 1088 rEntry.Value >>= aVal; 1089 aPropertyName = aVal.Name; 1090 } 1091 else if( rEntry.Name == "MinValue" ) 1092 { 1093 rEntry.Value >>= nMinValue; 1094 } 1095 else if( rEntry.Name == "MaxValue" ) 1096 { 1097 rEntry.Value >>= nMaxValue; 1098 } 1099 else if( rEntry.Name == "AttachToDependency" ) 1100 { 1101 nAttachOffset = 20; 1102 } 1103 else if( rEntry.Name == "InternalUIOnly" ) 1104 { 1105 bool bValue = false; 1106 rEntry.Value >>= bValue; 1107 bIgnore = bValue; 1108 } 1109 else if( rEntry.Name == "GroupingHint" ) 1110 { 1111 rEntry.Value >>= aGroupHint; 1112 } 1113 } 1114 1115 if( aCtrlType == "Group" || 1116 aCtrlType == "Subgroup" || 1117 aCtrlType == "Radio" || 1118 aCtrlType == "List" || 1119 aCtrlType == "Edit" || 1120 aCtrlType == "Range" || 1121 aCtrlType == "Bool" ) 1122 { 1123 bool bIgnoreSubgroup = false; 1124 1125 // with `setAccessoryView' method only one accessory view can be set 1126 // so create this single accessory view as tabbed for grouping 1127 if( aCtrlType == "Group" 1128 || ! pCurParent 1129 || ( aCtrlType == "Subgroup" && nCurY < -250 && ! bIgnore ) 1130 ) 1131 { 1132 OUString aGroupTitle( aText ); 1133 if( aCtrlType == "Subgroup" ) 1134 aGroupTitle = ControllerProperties::getMoreString(); 1135 1136 // set size of current parent 1137 if( pCurParent ) 1138 adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn ); 1139 1140 // new tab item 1141 if( ! aText.getLength() ) 1142 aText = "OOo"; 1143 NSString* pLabel = CreateNSString( aGroupTitle ); 1144 NSTabViewItem* pItem = [[NSTabViewItem alloc] initWithIdentifier: pLabel ]; 1145 [pItem setLabel: pLabel]; 1146 [pTabView addTabViewItem: pItem]; 1147 pCurParent = [[NSView alloc] initWithFrame: aTabViewFrame]; 1148 [pItem setView: pCurParent]; 1149 [pLabel release]; 1150 1151 nCurX = 20; // reset indent 1152 nCurY = 0; // reset Y 1153 // clear columns 1154 aLeftColumn.clear(); 1155 aRightColumn.clear(); 1156 1157 if( bAddSelectionCheckBox ) 1158 { 1159 addBool( pCurParent, nCurX, nCurY, 0, 1160 ControllerProperties::getPrintSelectionString(), bSelectionBoxEnabled, 1161 "PrintContent", bSelectionBoxChecked, 1162 aRightColumn, pControllerProperties, pCtrlTarget ); 1163 bAddSelectionCheckBox = false; 1164 } 1165 } 1166 1167 if( aCtrlType == "Subgroup" && pCurParent ) 1168 { 1169 bIgnoreSubgroup = bIgnore; 1170 if( bIgnore ) 1171 continue; 1172 1173 addSubgroup( pCurParent, nCurY, aText ); 1174 } 1175 else if( bIgnoreSubgroup || bIgnore ) 1176 { 1177 continue; 1178 } 1179 else if( aCtrlType == "Bool" && pCurParent ) 1180 { 1181 bool bVal = false; 1182 PropertyValue* pVal = pController->getValue( aPropertyName ); 1183 if( pVal ) 1184 pVal->Value >>= bVal; 1185 addBool( pCurParent, nCurX, nCurY, nAttachOffset, 1186 aText, true, aPropertyName, bVal, 1187 aRightColumn, pControllerProperties, pCtrlTarget ); 1188 } 1189 else if( aCtrlType == "Radio" && pCurParent ) 1190 { 1191 // get currently selected value 1192 sal_Int32 nSelectVal = 0; 1193 PropertyValue* pVal = pController->getValue( aPropertyName ); 1194 if( pVal && pVal->Value.hasValue() ) 1195 pVal->Value >>= nSelectVal; 1196 1197 addRadio( pCurParent, nCurX, nCurY, nAttachOffset, 1198 aText, aPropertyName, aChoices, nSelectVal, 1199 aLeftColumn, aRightColumn, 1200 pControllerProperties, pCtrlTarget ); 1201 } 1202 else if( aCtrlType == "List" && pCurParent ) 1203 { 1204 PropertyValue* pVal = pController->getValue( aPropertyName ); 1205 sal_Int32 aSelectVal = 0; 1206 if( pVal && pVal->Value.hasValue() ) 1207 pVal->Value >>= aSelectVal; 1208 1209 addList( pCurParent, nCurX, nCurY, nAttachOffset, 1210 aText, aPropertyName, aChoices, aSelectVal, 1211 aLeftColumn, aRightColumn, 1212 pControllerProperties, pCtrlTarget ); 1213 } 1214 else if( (aCtrlType == "Edit" 1215 || aCtrlType == "Range") && pCurParent ) 1216 { 1217 // current value 1218 PropertyValue* pVal = pController->getValue( aPropertyName ); 1219 addEdit( pCurParent, nCurX, nCurY, nAttachOffset, 1220 aCtrlType, aText, aPropertyName, pVal, 1221 nMinValue, nMaxValue, 1222 aLeftColumn, aRightColumn, 1223 pControllerProperties, pCtrlTarget ); 1224 } 1225 } 1226 else 1227 { 1228 SAL_INFO( "vcl.osx.print", "Unsupported UI option \"" << aCtrlType << "\""); 1229 } 1230 } 1231 1232 pControllerProperties->updateEnableState(); 1233 adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn ); 1234 1235 // now reposition everything again so it is upper bound 1236 adjustTabViews( pTabView, aMaxTabSize ); 1237 1238 // find the minimum needed tab size 1239 NSSize aTabCtrlSize = [pTabView minimumSize]; 1240 aTabCtrlSize.height += aMaxTabSize.height + 10; 1241 if( aTabCtrlSize.width < aMaxTabSize.width + 10 ) 1242 aTabCtrlSize.width = aMaxTabSize.width + 10; 1243 [pTabView setFrameSize: aTabCtrlSize]; 1244 aViewFrame.size.width = aTabCtrlSize.width + aTabViewFrame.origin.x; 1245 aViewFrame.size.height = aTabCtrlSize.height + aTabViewFrame.origin.y; 1246 [pAccessoryView setFrameSize: aViewFrame.size]; 1247 1248 // get the print panel 1249 NSPrintPanel* pPrintPanel = [pOp printPanel]; 1250 [pPrintPanel setOptions: [pPrintPanel options] | NSPrintPanelShowsPreview]; 1251 // add the accessory controller to the panel 1252 [pPrintPanel addAccessoryController: [pAccessoryController autorelease]]; 1253 1254 // set the current selected tab item 1255 if( pState->nLastPage >= 0 && pState->nLastPage < [pTabView numberOfTabViewItems] ) 1256 [pTabView selectTabViewItemAtIndex: pState->nLastPage]; 1257 1258 return pCtrlTarget; 1259} 1260 1261@end 1262 1263/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 1264