1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <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>
32 #include <osx/printview.h>
33 #include <osx/salinst.h>
34 #include <quartz/utils.h>
37 #include <strings.hrc>
38 #include <printaccessoryview.hrc>
40 #include <com/sun/star/i18n/XBreakIterator.hpp>
41 #include <com/sun/star/i18n/WordType.hpp>
47 using namespace com::sun::star;
48 using namespace com::sun::star::beans;
49 using namespace com::sun::star::uno;
53 class ControllerProperties;
57 @interface ControlTarget : NSObject
59 ControllerProperties* mpController;
61 -(id)initWithControllerMap: (ControllerProperties*)pController;
62 -(void)triggered:(id)pSender;
63 -(void)triggeredNumeric:(id)pSender;
67 @interface AquaPrintPanelAccessoryController : NSViewController< NSPrintPanelAccessorizing >
69 NSPrintOperation *mpPrintOperation;
70 vcl::PrinterController *mpPrinterController;
71 PrintAccessoryViewState *mpViewState;
74 -(void)forPrintOperation:(NSPrintOperation*)pPrintOp;
75 -(void)withPrinterController:(vcl::PrinterController*)pController;
76 -(void)withViewState:(PrintAccessoryViewState*)pState;
78 -(NSPrintOperation*)printOperation;
79 -(vcl::PrinterController*)printerController;
80 -(PrintAccessoryViewState*)viewState;
82 -(NSSet*)keyPathsForValuesAffectingPreview;
83 -(NSArray*)localizedSummaryItems;
85 -(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount;
89 @implementation AquaPrintPanelAccessoryController
91 -(void)forPrintOperation:(NSPrintOperation*)pPrintOp
92 { mpPrintOperation = pPrintOp; }
94 -(void)withPrinterController:(vcl::PrinterController*)pController
95 { mpPrinterController = pController; }
97 -(void)withViewState:(PrintAccessoryViewState*)pState
98 { mpViewState = pState; }
100 -(NSPrintOperation*)printOperation
101 { return mpPrintOperation; }
103 -(vcl::PrinterController*)printerController
104 { return mpPrinterController; }
106 -(PrintAccessoryViewState*)viewState
107 { return mpViewState; }
109 -(NSSet*)keyPathsForValuesAffectingPreview
111 return [ NSSet setWithObject:@"updatePrintOperation" ];
114 -(NSArray*)localizedSummaryItems
116 return [ NSArray arrayWithObject:
117 [ NSDictionary dictionary ] ];
120 -(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount
122 // page range may be changed by option choice
123 sal_Int32 nPages = mpPrinterController->getFilteredPageCount();
125 mpViewState->bNeedRestart = false;
126 if( nPages != pLastPageCount )
128 #if OSL_DEBUG_LEVEL > 1
129 SAL_INFO( "vcl.osx.print", "number of pages changed" <<
130 " from " << pLastPageCount << " to " << nPages );
132 mpViewState->bNeedRestart = true;
135 NSTabView* pTabView = [[[self view] subviews] objectAtIndex:0];
136 NSTabViewItem* pItem = [pTabView selectedTabViewItem];
138 mpViewState->nLastPage = [pTabView indexOfTabViewItem: pItem];
140 mpViewState->nLastPage = 0;
142 if( mpViewState->bNeedRestart )
144 // AppKit does not give a chance of changing the page count
145 // and don't let cancel the dialog either
146 // hack: send a cancel message to the modal window displaying views
147 NSWindow* pNSWindow = [NSApp modalWindow];
149 [pNSWindow cancelOperation: nil];
150 [[mpPrintOperation printInfo] setJobDisposition: NSPrintCancelJob];
160 class ControllerProperties
162 std::map< int, OUString > maTagToPropertyName;
163 std::map< int, sal_Int32 > maTagToValueInt;
164 std::map< NSView*, NSView* > maViewPairMap;
165 std::vector< NSObject* > maViews;
167 sal_Int32 mnLastPageCount;
168 AquaPrintPanelAccessoryController* mpAccessoryController;
171 ControllerProperties( AquaPrintPanelAccessoryController* i_pAccessoryController )
173 , mnLastPageCount( [i_pAccessoryController printerController]->getFilteredPageCount() )
174 , mpAccessoryController( i_pAccessoryController )
176 static_assert( SAL_N_ELEMENTS(SV_PRINT_NATIVE_STRINGS) == 5, "resources not found" );
179 static OUString getMoreString()
181 return VclResId(SV_PRINT_NATIVE_STRINGS[3]);
184 static OUString getPrintSelectionString()
186 return VclResId(SV_PRINT_NATIVE_STRINGS[4]);
189 int addNameTag( const OUString& i_rPropertyName )
191 int nNewTag = mnNextTag++;
192 maTagToPropertyName[ nNewTag ] = i_rPropertyName;
196 int addNameAndValueTag( const OUString& i_rPropertyName, sal_Int32 i_nValue )
198 int nNewTag = mnNextTag++;
199 maTagToPropertyName[ nNewTag ] = i_rPropertyName;
200 maTagToValueInt[ nNewTag ] = i_nValue;
204 void addObservedControl( NSObject* i_pView )
206 maViews.push_back( i_pView );
209 void addViewPair( NSView* i_pLeft, NSView* i_pRight )
211 maViewPairMap[ i_pLeft ] = i_pRight;
212 maViewPairMap[ i_pRight ] = i_pLeft;
215 NSView* getPair( NSView* i_pLeft ) const
217 NSView* pRight = nil;
218 std::map< NSView*, NSView* >::const_iterator it = maViewPairMap.find( i_pLeft );
219 if( it != maViewPairMap.end() )
224 void changePropertyWithIntValue( int i_nTag )
226 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
227 std::map< int, sal_Int32 >::const_iterator value_it = maTagToValueInt.find( i_nTag );
228 if( name_it != maTagToPropertyName.end() && value_it != maTagToValueInt.end() )
230 vcl::PrinterController * mpController = [mpAccessoryController printerController];
231 PropertyValue* pVal = mpController->getValue( name_it->second );
234 pVal->Value <<= value_it->second;
235 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
240 void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue )
242 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
243 if( name_it != maTagToPropertyName.end() )
245 vcl::PrinterController * mpController = [mpAccessoryController printerController];
246 PropertyValue* pVal = mpController->getValue( name_it->second );
249 pVal->Value <<= i_nValue;
250 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
255 void changePropertyWithBoolValue( int i_nTag, bool i_bValue )
257 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
258 if( name_it != maTagToPropertyName.end() )
260 vcl::PrinterController * mpController = [mpAccessoryController printerController];
261 PropertyValue* pVal = mpController->getValue( name_it->second );
265 if( name_it->second == "PrintContent" )
266 pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0);
268 pVal->Value <<= i_bValue;
270 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
275 void changePropertyWithStringValue( int i_nTag, const OUString& i_rValue )
277 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
278 if( name_it != maTagToPropertyName.end() )
280 vcl::PrinterController * mpController = [mpAccessoryController printerController];
281 PropertyValue* pVal = mpController->getValue( name_it->second );
284 pVal->Value <<= i_rValue;
285 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
290 void updateEnableState()
292 for( std::vector< NSObject* >::iterator it = maViews.begin(); it != maViews.end(); ++it )
294 NSObject* pObj = *it;
295 NSControl* pCtrl = nil;
297 if( [pObj isKindOfClass: [NSControl class]] )
298 pCtrl = static_cast<NSControl*>(pObj);
299 else if( [pObj isKindOfClass: [NSCell class]] )
300 pCell = static_cast<NSCell*>(pObj);
302 int nTag = pCtrl ? [pCtrl tag] :
303 pCell ? [pCell tag] :
306 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( nTag );
307 if( name_it != maTagToPropertyName.end() && name_it->second != "PrintContent" )
309 vcl::PrinterController * mpController = [mpAccessoryController printerController];
310 bool bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO;
313 [pCtrl setEnabled: bEnabled];
314 NSView* pOther = getPair( pCtrl );
315 if( pOther && [pOther isKindOfClass: [NSControl class]] )
316 [static_cast<NSControl*>(pOther) setEnabled: bEnabled];
319 [pCell setEnabled: bEnabled];
328 static OUString filterAccelerator( OUString const & rText )
330 OUStringBuffer aBuf( rText.getLength() );
331 for( sal_Int32 nIndex = 0; nIndex != -1; )
332 aBuf.append( rText.getToken( 0, '~', nIndex ) );
333 return aBuf.makeStringAndClear();
336 @implementation ControlTarget
338 -(id)initWithControllerMap: (ControllerProperties*)pController
340 if( (self = [super init]) )
342 mpController = pController;
347 -(void)triggered:(id)pSender
349 if( [pSender isMemberOfClass: [NSPopUpButton class]] )
351 NSPopUpButton* pBtn = static_cast<NSPopUpButton*>(pSender);
352 NSMenuItem* pSelected = [pBtn selectedItem];
355 int nTag = [pSelected tag];
356 mpController->changePropertyWithIntValue( nTag );
359 else if( [pSender isMemberOfClass: [NSButton class]] )
361 NSButton* pBtn = static_cast<NSButton*>(pSender);
362 int nTag = [pBtn tag];
363 mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSControlStateValueOn );
365 else if( [pSender isMemberOfClass: [NSMatrix class]] )
367 NSObject* pObj = [static_cast<NSMatrix*>(pSender) selectedCell];
368 if( [pObj isMemberOfClass: [NSButtonCell class]] )
370 NSButtonCell* pCell = static_cast<NSButtonCell*>(pObj);
371 int nTag = [pCell tag];
372 mpController->changePropertyWithIntValue( nTag );
375 else if( [pSender isMemberOfClass: [NSTextField class]] )
377 NSTextField* pField = static_cast<NSTextField*>(pSender);
378 int nTag = [pField tag];
379 OUString aValue = GetOUString( [pSender stringValue] );
380 mpController->changePropertyWithStringValue( nTag, aValue );
384 SAL_INFO( "vcl.osx.print", "Unsupported class" <<
385 ( [pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil" ) );
387 mpController->updateEnableState();
390 -(void)triggeredNumeric:(id)pSender
392 if( [pSender isMemberOfClass: [NSTextField class]] )
394 NSTextField* pField = static_cast<NSTextField*>(pSender);
395 int nTag = [pField tag];
396 sal_Int64 nValue = [pField intValue];
398 NSView* pOther = mpController->getPair( pField );
400 [static_cast<NSControl*>(pOther) setIntValue: nValue];
402 mpController->changePropertyWithIntValue( nTag, nValue );
404 else if( [pSender isMemberOfClass: [NSStepper class]] )
406 NSStepper* pStep = static_cast<NSStepper*>(pSender);
407 int nTag = [pStep tag];
408 sal_Int64 nValue = [pStep intValue];
410 NSView* pOther = mpController->getPair( pStep );
412 [static_cast<NSControl*>(pOther) setIntValue: nValue];
414 mpController->changePropertyWithIntValue( nTag, nValue );
418 SAL_INFO( "vcl.osx.print", "Unsupported class" <<
419 ([pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil") );
421 mpController->updateEnableState();
438 NSControl* pSubControl;
440 ColumnItem( NSControl* i_pControl = nil, CGFloat i_nOffset = 0, NSControl* i_pSub = nil )
441 : pControl( i_pControl )
442 , nOffset( i_nOffset )
443 , pSubControl( i_pSub )
446 CGFloat getWidth() const
451 NSRect aCtrlRect = [pControl frame];
452 nWidth = aCtrlRect.size.width;
456 NSRect aSubRect = [pSubControl frame];
457 nWidth += aSubRect.size.width;
458 nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width);
467 static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize,
468 std::vector< ColumnItem >& rLeftColumn,
469 std::vector< ColumnItem >& rRightColumn
474 // first get overall column widths
475 CGFloat nLeftWidth = 0;
476 CGFloat nRightWidth = 0;
477 for( size_t i = 0; i < rLeftColumn.size(); i++ )
479 CGFloat nW = rLeftColumn[i].getWidth();
480 if( nW > nLeftWidth )
483 for( size_t i = 0; i < rRightColumn.size(); i++ )
485 CGFloat nW = rRightColumn[i].getWidth();
486 if( nW > nRightWidth )
490 // right align left column
491 for( size_t i = 0; i < rLeftColumn.size(); i++ )
493 if( rLeftColumn[i].pControl )
495 NSRect aCtrlRect = [rLeftColumn[i].pControl frame];
496 CGFloat nX = nLeftWidth - aCtrlRect.size.width;
497 if( rLeftColumn[i].pSubControl )
499 NSRect aSubRect = [rLeftColumn[i].pSubControl frame];
500 nX -= aSubRect.size.width + (aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width));
501 aSubRect.origin.x = nLeftWidth - aSubRect.size.width;
502 [rLeftColumn[i].pSubControl setFrame: aSubRect];
504 aCtrlRect.origin.x = nX;
505 [rLeftColumn[i].pControl setFrame: aCtrlRect];
509 // left align right column
510 for( size_t i = 0; i < rRightColumn.size(); i++ )
512 if( rRightColumn[i].pControl )
514 NSRect aCtrlRect = [rRightColumn[i].pControl frame];
515 CGFloat nX = nLeftWidth + 3;
516 if( rRightColumn[i].pSubControl )
518 NSRect aSubRect = [rRightColumn[i].pSubControl frame];
519 aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x;
520 [rRightColumn[i].pSubControl setFrame: aSubRect];
522 aCtrlRect.origin.x = nX;
523 [rRightColumn[i].pControl setFrame: aCtrlRect];
527 NSArray* pSubViews = [pNSView subviews];
528 unsigned int nViews = [pSubViews count];
529 NSRect aUnion = NSZeroRect;
531 // get the combined frame of all subviews
532 for( unsigned int n = 0; n < nViews; n++ )
534 aUnion = NSUnionRect( aUnion, [[pSubViews objectAtIndex: n] frame] );
537 // move everything so it will fit
538 for( unsigned int n = 0; n < nViews; n++ )
540 NSView* pCurSubView = [pSubViews objectAtIndex: n];
541 NSRect aFrame = [pCurSubView frame];
542 aFrame.origin.x -= aUnion.origin.x - 5;
543 aFrame.origin.y -= aUnion.origin.y - 5;
544 [pCurSubView setFrame: aFrame];
547 // resize the view itself
548 aUnion.size.height += 10;
549 aUnion.size.width += 20;
550 [pNSView setFrameSize: aUnion.size];
552 if( aUnion.size.width > rMaxSize.width )
553 rMaxSize.width = aUnion.size.width;
554 if( aUnion.size.height > rMaxSize.height )
555 rMaxSize.height = aUnion.size.height;
558 static void adjustTabViews( NSTabView* pTabView, NSSize aTabSize )
560 // loop over all contained tab pages
561 NSArray* pTabbedViews = [pTabView tabViewItems];
562 int nViews = [pTabbedViews count];
563 for( int i = 0; i < nViews; i++ )
565 NSTabViewItem* pItem = static_cast<NSTabViewItem*>([pTabbedViews objectAtIndex: i]);
566 NSView* pNSView = [pItem view];
569 NSRect aRect = [pNSView frame];
570 double nDiff = aTabSize.height - aRect.size.height;
571 aRect.size = aTabSize;
572 [pNSView setFrame: aRect];
574 NSArray* pSubViews = [pNSView subviews];
575 unsigned int nSubViews = [pSubViews count];
577 // move everything up
578 for( unsigned int n = 0; n < nSubViews; n++ )
580 NSView* pCurSubView = [pSubViews objectAtIndex: n];
581 NSRect aFrame = [pCurSubView frame];
582 aFrame.origin.y += nDiff;
583 // give separators the correct width
584 // separators are currently the only NSBoxes we use
585 if( [pCurSubView isMemberOfClass: [NSBox class]] )
587 aFrame.size.width = aTabSize.width - aFrame.origin.x - 10;
589 [pCurSubView setFrame: aFrame];
595 static NSControl* createLabel( const OUString& i_rText )
597 NSString* pText = CreateNSString( i_rText );
598 NSRect aTextRect = { NSZeroPoint, {20, 15} };
599 NSTextField* pTextView = [[NSTextField alloc] initWithFrame: aTextRect];
600 [pTextView setFont: [NSFont controlContentFontOfSize: 0]];
601 [pTextView setEditable: NO];
602 [pTextView setSelectable: NO];
603 [pTextView setDrawsBackground: NO];
604 [pTextView setBordered: NO];
605 [pTextView setStringValue: pText];
606 [pTextView sizeToFit];
611 static sal_Int32 findBreak( const OUString& i_rText, sal_Int32 i_nPos )
613 sal_Int32 nRet = i_rText.getLength();
614 Reference< i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() );
617 i18n::Boundary aBoundary =
618 xBI->getWordBoundary( i_rText, i_nPos,
619 Application::GetSettings().GetLanguageTag().getLocale(),
620 i18n::WordType::ANYWORD_IGNOREWHITESPACES,
622 nRet = aBoundary.endPos;
627 static void linebreakCell( NSCell* pBtn, const OUString& i_rText )
629 NSString* pText = CreateNSString( i_rText );
630 [pBtn setTitle: pText];
632 NSSize aSize = [pBtn cellSize];
633 if( aSize.width > 280 )
636 sal_Int32 nLen = i_rText.getLength();
637 sal_Int32 nIndex = nLen / 2;
638 nIndex = findBreak( i_rText, nIndex );
641 OUStringBuffer aBuf( i_rText );
643 pText = CreateNSString( aBuf.makeStringAndClear() );
644 [pBtn setTitle: pText];
650 static void addSubgroup( NSView* pCurParent, CGFloat& rCurY, const OUString& rText )
652 NSControl* pTextView = createLabel( rText );
653 [pCurParent addSubview: [pTextView autorelease]];
654 NSRect aTextRect = [pTextView frame];
656 aTextRect.origin.y = rCurY - aTextRect.size.height;
657 [pTextView setFrame: aTextRect];
659 NSRect aSepRect = { { aTextRect.size.width + 1, aTextRect.origin.y }, { 100, 6 } };
660 NSBox* pBox = [[NSBox alloc] initWithFrame: aSepRect];
661 [pBox setBoxType: NSBoxSeparator];
662 [pCurParent addSubview: [pBox autorelease]];
665 rCurY = aTextRect.origin.y - 5;
668 static void addBool( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
669 const OUString& rText, bool bEnabled,
670 const OUString& rProperty, bool bValue,
671 std::vector<ColumnItem >& rRightColumn,
672 ControllerProperties* pControllerProperties,
673 ControlTarget* pCtrlTarget
676 NSRect aCheckRect = { { rCurX + nAttachOffset, 0 }, { 0, 15 } };
677 NSButton* pBtn = [[NSButton alloc] initWithFrame: aCheckRect];
678 [pBtn setButtonType: NSButtonTypeSwitch];
679 [pBtn setState: bValue ? NSControlStateValueOn : NSControlStateValueOff];
681 [pBtn setEnabled: NO];
682 linebreakCell( [pBtn cell], rText );
685 rRightColumn.push_back( ColumnItem( pBtn ) );
688 [pBtn setTarget: pCtrlTarget];
689 [pBtn setAction: @selector(triggered:)];
690 int nTag = pControllerProperties->addNameTag( rProperty );
691 pControllerProperties->addObservedControl( pBtn );
694 aCheckRect = [pBtn frame];
695 // #i115837# add a murphy factor; it can apparently occasionally happen
696 // that sizeToFit does not a perfect job and that the button linebreaks again
697 // if - and only if - there is already a '\n' contained in the text and the width
699 aCheckRect.size.width += 1;
702 aCheckRect.origin.y = rCurY - aCheckRect.size.height;
703 [pBtn setFrame: aCheckRect];
705 [pCurParent addSubview: [pBtn autorelease]];
708 rCurY = aCheckRect.origin.y - 5;
711 static void addRadio( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
712 const OUString& rText,
713 const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
714 std::vector<ColumnItem >& rLeftColumn,
715 std::vector<ColumnItem >& rRightColumn,
716 ControllerProperties* pControllerProperties,
717 ControlTarget* pCtrlTarget
721 if( rText.getLength() )
724 NSControl* pTextView = createLabel( rText );
725 NSRect aTextRect = [pTextView frame];
726 aTextRect.origin.x = rCurX + nAttachOffset;
727 [pCurParent addSubview: [pTextView autorelease]];
729 rLeftColumn.push_back( ColumnItem( pTextView ) );
732 aTextRect.origin.y = rCurY - aTextRect.size.height;
733 [pTextView setFrame: aTextRect];
736 rCurY = aTextRect.origin.y - 5;
738 // indent the radio group relative to the text
742 // setup radio matrix
743 NSButtonCell* pProto = [[NSButtonCell alloc] init];
745 NSRect aRadioRect = { { rCurX + nOff, 0 },
747 static_cast<CGFloat>(5*rChoices.getLength()) } };
748 [pProto setTitle: @"RadioButtonGroup"];
749 [pProto setButtonType: NSButtonTypeRadio];
750 NSMatrix* pMatrix = [[NSMatrix alloc] initWithFrame: aRadioRect
751 mode: NSRadioModeMatrix
752 prototype: static_cast<NSCell*>(pProto)
753 numberOfRows: rChoices.getLength()
755 // set individual titles
756 NSArray* pCells = [pMatrix cells];
757 for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
759 NSCell* pCell = [pCells objectAtIndex: m];
760 linebreakCell( pCell, filterAccelerator( rChoices[m] ) );
761 // connect target and action
762 [pCell setTarget: pCtrlTarget];
763 [pCell setAction: @selector(triggered:)];
764 int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
765 pControllerProperties->addObservedControl( pCell );
766 [pCell setTag: nTag];
767 // set current selection
768 if( nSelectValue == m )
769 [pMatrix selectCellAtRow: m column: 0];
772 aRadioRect = [pMatrix frame];
774 // move it down, so it comes to the correct position
775 aRadioRect.origin.y = rCurY - aRadioRect.size.height;
776 [pMatrix setFrame: aRadioRect];
777 [pCurParent addSubview: [pMatrix autorelease]];
779 rRightColumn.push_back( ColumnItem( pMatrix ) );
782 rCurY = aRadioRect.origin.y - 5;
787 static void addList( NSView* pCurParent, CGFloat& rCurX, CGFloat& rCurY, CGFloat /*nAttachOffset*/,
788 const OUString& rText,
789 const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
790 std::vector<ColumnItem >& rLeftColumn,
791 std::vector<ColumnItem >& rRightColumn,
792 ControllerProperties* pControllerProperties,
793 ControlTarget* pCtrlTarget
796 // don't indent attached lists, looks bad in the existing cases
797 NSControl* pTextView = createLabel( rText );
798 [pCurParent addSubview: [pTextView autorelease]];
799 rLeftColumn.push_back( ColumnItem( pTextView ) );
800 NSRect aTextRect = [pTextView frame];
801 aTextRect.origin.x = rCurX /* + nAttachOffset*/;
803 // don't indent attached lists, looks bad in the existing cases
804 NSRect aBtnRect = { { rCurX /*+ nAttachOffset*/ + aTextRect.size.width, 0 }, { 0, 15 } };
805 NSPopUpButton* pBtn = [[NSPopUpButton alloc] initWithFrame: aBtnRect pullsDown: NO];
808 for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
810 NSString* pItemText = CreateNSString( rChoices[m] );
811 [pBtn addItemWithTitle: pItemText];
812 NSMenuItem* pItem = [pBtn itemWithTitle: pItemText];
813 int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
814 [pItem setTag: nTag];
818 [pBtn selectItemAtIndex: nSelectValue];
820 // add the button to observed controls for enabled state changes
821 // also add a tag just for this purpose
822 pControllerProperties->addObservedControl( pBtn );
823 [pBtn setTag: pControllerProperties->addNameTag( rProperty )];
826 [pCurParent addSubview: [pBtn autorelease]];
828 rRightColumn.push_back( ColumnItem( pBtn ) );
830 // connect target and action
831 [pBtn setTarget: pCtrlTarget];
832 [pBtn setAction: @selector(triggered:)];
835 aBtnRect = [pBtn frame];
836 aBtnRect.origin.y = rCurY - aBtnRect.size.height;
837 [pBtn setFrame: aBtnRect];
840 aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2;
841 [pTextView setFrame: aTextRect];
844 rCurY = aBtnRect.origin.y - 5;
847 static void addEdit( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
848 const OUString& rCtrlType,
849 const OUString& rText,
850 const OUString& rProperty, const PropertyValue* pValue,
851 sal_Int64 nMinValue, sal_Int64 nMaxValue,
852 std::vector<ColumnItem >& rLeftColumn,
853 std::vector<ColumnItem >& rRightColumn,
854 ControllerProperties* pControllerProperties,
855 ControlTarget* pCtrlTarget
859 if( rText.getLength() )
862 NSControl* pTextView = createLabel( rText );
863 [pCurParent addSubview: [pTextView autorelease]];
865 rLeftColumn.push_back( ColumnItem( pTextView ) );
868 NSRect aTextRect = [pTextView frame];
869 aTextRect.origin.x = rCurX + nAttachOffset;
870 aTextRect.origin.y = rCurY - aTextRect.size.height;
871 [pTextView setFrame: aTextRect];
874 rCurY = aTextRect.origin.y - 5;
876 // and set the offset for the real edit field
877 nOff = aTextRect.size.width + 5;
880 NSRect aFieldRect = { { rCurX + nOff + nAttachOffset, 0 }, { 100, 25 } };
881 NSTextField* pFieldView = [[NSTextField alloc] initWithFrame: aFieldRect];
882 [pFieldView setEditable: YES];
883 [pFieldView setSelectable: YES];
884 [pFieldView setDrawsBackground: YES];
885 [pFieldView sizeToFit]; // FIXME: this does nothing
886 [pCurParent addSubview: [pFieldView autorelease]];
888 rRightColumn.push_back( ColumnItem( pFieldView ) );
890 // add the field to observed controls for enabled state changes
891 // also add a tag just for this purpose
892 pControllerProperties->addObservedControl( pFieldView );
893 int nTag = pControllerProperties->addNameTag( rProperty );
894 [pFieldView setTag: nTag];
895 // pControllerProperties->addNamedView( pFieldView, aPropertyName );
898 aFieldRect.origin.y = rCurY - aFieldRect.size.height;
899 [pFieldView setFrame: aFieldRect];
901 if( rCtrlType == "Range" )
903 // add a stepper control
904 NSRect aStepFrame = { { aFieldRect.origin.x + aFieldRect.size.width + 5,
905 aFieldRect.origin.y },
906 { 15, aFieldRect.size.height } };
907 NSStepper* pStep = [[NSStepper alloc] initWithFrame: aStepFrame];
908 [pStep setIncrement: 1];
909 [pStep setValueWraps: NO];
910 [pStep setTag: nTag];
911 [pCurParent addSubview: [pStep autorelease]];
913 rRightColumn.back().pSubControl = pStep;
915 pControllerProperties->addObservedControl( pStep );
916 [pStep setTarget: pCtrlTarget];
917 [pStep setAction: @selector(triggered:)];
919 // constrain the text field to decimal numbers
920 NSNumberFormatter* pFormatter = [[NSNumberFormatter alloc] init];
921 [pFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
922 [pFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
923 [pFormatter setAllowsFloats: NO];
924 [pFormatter setMaximumFractionDigits: 0];
925 if( nMinValue != nMaxValue )
927 [pFormatter setMinimum: [[NSNumber numberWithInt: nMinValue] autorelease]];
928 [pStep setMinValue: nMinValue];
929 [pFormatter setMaximum: [[NSNumber numberWithInt: nMaxValue] autorelease]];
930 [pStep setMaxValue: nMaxValue];
932 [pFieldView setFormatter: pFormatter];
934 sal_Int64 nSelectVal = 0;
935 if( pValue && pValue->Value.hasValue() )
936 pValue->Value >>= nSelectVal;
938 [pFieldView setIntValue: nSelectVal];
939 [pStep setIntValue: nSelectVal];
941 pControllerProperties->addViewPair( pFieldView, pStep );
942 // connect target and action
943 [pFieldView setTarget: pCtrlTarget];
944 [pFieldView setAction: @selector(triggeredNumeric:)];
945 [pStep setTarget: pCtrlTarget];
946 [pStep setAction: @selector(triggeredNumeric:)];
950 // connect target and action
951 [pFieldView setTarget: pCtrlTarget];
952 [pFieldView setAction: @selector(triggered:)];
954 if( pValue && pValue->Value.hasValue() )
957 pValue->Value >>= aValue;
958 if( aValue.getLength() )
960 NSString* pText = CreateNSString( aValue );
961 [pFieldView setStringValue: pText];
968 rCurY = aFieldRect.origin.y - 5;
971 @implementation AquaPrintAccessoryView
973 +(NSObject*)setupPrinterPanel: (NSPrintOperation*)pOp
974 withController: (vcl::PrinterController*)pController
975 withState: (PrintAccessoryViewState*)pState
977 const Sequence< PropertyValue >& rOptions( pController->getUIOptions() );
978 if( rOptions.getLength() == 0 )
981 NSRect aViewFrame = { NSZeroPoint, { 600, 400 } };
982 NSRect aTabViewFrame = aViewFrame;
984 NSView* pAccessoryView = [[NSView alloc] initWithFrame: aViewFrame];
985 NSTabView* pTabView = [[NSTabView alloc] initWithFrame: aTabViewFrame];
986 [pAccessoryView addSubview: [pTabView autorelease]];
988 // create the accessory controller
989 AquaPrintPanelAccessoryController* pAccessoryController =
990 [[AquaPrintPanelAccessoryController alloc] initWithNibName: nil bundle: nil];
991 [pAccessoryController setView: [pAccessoryView autorelease]];
992 [pAccessoryController forPrintOperation: pOp];
993 [pAccessoryController withPrinterController: pController];
994 [pAccessoryController withViewState: pState];
996 NSView* pCurParent = nullptr;
999 NSSize aMaxTabSize = NSZeroSize;
1001 ControllerProperties* pControllerProperties = new ControllerProperties( pAccessoryController );
1002 ControlTarget* pCtrlTarget = [[ControlTarget alloc] initWithControllerMap: pControllerProperties];
1004 std::vector< ColumnItem > aLeftColumn, aRightColumn;
1007 // prepend a "selection" checkbox if the properties have such a selection in PrintContent
1008 bool bAddSelectionCheckBox = false, bSelectionBoxEnabled = false, bSelectionBoxChecked = false;
1010 for( const PropertyValue & prop : rOptions )
1012 Sequence< beans::PropertyValue > aOptProp;
1013 prop.Value >>= aOptProp;
1016 OUString aPropertyName;
1017 Sequence< OUString > aChoices;
1018 Sequence< sal_Bool > aChoicesDisabled;
1019 sal_Int32 aSelectionChecked = 0;
1020 for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
1022 if( rEntry.Name == "ControlType" )
1024 rEntry.Value >>= aCtrlType;
1026 else if( rEntry.Name == "Choices" )
1028 rEntry.Value >>= aChoices;
1030 else if( rEntry.Name == "ChoicesDisabled" )
1032 rEntry.Value >>= aChoicesDisabled;
1034 else if( rEntry.Name == "Property" )
1037 rEntry.Value >>= aVal;
1038 aPropertyName = aVal.Name;
1039 if( aPropertyName == "PrintContent" )
1040 aVal.Value >>= aSelectionChecked;
1043 if( aCtrlType == "Radio" &&
1044 aPropertyName == "PrintContent" &&
1045 aChoices.getLength() > 2 )
1047 bAddSelectionCheckBox = true;
1048 bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2];
1049 bSelectionBoxChecked = (aSelectionChecked==2);
1054 for( const PropertyValue & prop : rOptions )
1056 Sequence< beans::PropertyValue > aOptProp;
1057 prop.Value >>= aOptProp;
1059 // extract ui element
1062 OUString aPropertyName;
1063 OUString aGroupHint;
1064 Sequence< OUString > aChoices;
1065 bool bEnabled = true;
1066 sal_Int64 nMinValue = 0, nMaxValue = 0;
1067 CGFloat nAttachOffset = 0;
1068 bool bIgnore = false;
1070 for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
1072 if( rEntry.Name == "Text" )
1074 rEntry.Value >>= aText;
1075 aText = filterAccelerator( aText );
1077 else if( rEntry.Name == "ControlType" )
1079 rEntry.Value >>= aCtrlType;
1081 else if( rEntry.Name == "Choices" )
1083 rEntry.Value >>= aChoices;
1085 else if( rEntry.Name == "Property" )
1088 rEntry.Value >>= aVal;
1089 aPropertyName = aVal.Name;
1091 else if( rEntry.Name == "Enabled" )
1094 rEntry.Value >>= bValue;
1097 else if( rEntry.Name == "MinValue" )
1099 rEntry.Value >>= nMinValue;
1101 else if( rEntry.Name == "MaxValue" )
1103 rEntry.Value >>= nMaxValue;
1105 else if( rEntry.Name == "AttachToDependency" )
1109 else if( rEntry.Name == "InternalUIOnly" )
1111 bool bValue = false;
1112 rEntry.Value >>= bValue;
1115 else if( rEntry.Name == "GroupingHint" )
1117 rEntry.Value >>= aGroupHint;
1121 if( aCtrlType == "Group" ||
1122 aCtrlType == "Subgroup" ||
1123 aCtrlType == "Radio" ||
1124 aCtrlType == "List" ||
1125 aCtrlType == "Edit" ||
1126 aCtrlType == "Range" ||
1127 aCtrlType == "Bool" )
1129 bool bIgnoreSubgroup = false;
1131 // with `setAccessoryView' method only one accessory view can be set
1132 // so create this single accessory view as tabbed for grouping
1133 if( aCtrlType == "Group"
1135 || ( aCtrlType == "Subgroup" && nCurY < -250 && ! bIgnore )
1138 OUString aGroupTitle( aText );
1139 if( aCtrlType == "Subgroup" )
1140 aGroupTitle = ControllerProperties::getMoreString();
1142 // set size of current parent
1144 adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
1147 if( ! aText.getLength() )
1149 NSString* pLabel = CreateNSString( aGroupTitle );
1150 NSTabViewItem* pItem = [[NSTabViewItem alloc] initWithIdentifier: pLabel ];
1151 [pItem setLabel: pLabel];
1152 [pTabView addTabViewItem: pItem];
1153 pCurParent = [[NSView alloc] initWithFrame: aTabViewFrame];
1154 [pItem setView: pCurParent];
1157 nCurX = 20; // reset indent
1158 nCurY = 0; // reset Y
1160 aLeftColumn.clear();
1161 aRightColumn.clear();
1163 if( bAddSelectionCheckBox )
1165 addBool( pCurParent, nCurX, nCurY, 0,
1166 ControllerProperties::getPrintSelectionString(), bSelectionBoxEnabled,
1167 "PrintContent", bSelectionBoxChecked,
1168 aRightColumn, pControllerProperties, pCtrlTarget );
1169 bAddSelectionCheckBox = false;
1173 if( aCtrlType == "Subgroup" && pCurParent )
1175 bIgnoreSubgroup = bIgnore;
1179 addSubgroup( pCurParent, nCurY, aText );
1181 else if( bIgnoreSubgroup || bIgnore )
1185 else if( aCtrlType == "Bool" && pCurParent )
1188 PropertyValue* pVal = pController->getValue( aPropertyName );
1190 pVal->Value >>= bVal;
1191 addBool( pCurParent, nCurX, nCurY, nAttachOffset,
1192 aText, true, aPropertyName, bVal,
1193 aRightColumn, pControllerProperties, pCtrlTarget );
1195 else if( aCtrlType == "Radio" && pCurParent )
1197 // get currently selected value
1198 sal_Int32 nSelectVal = 0;
1199 PropertyValue* pVal = pController->getValue( aPropertyName );
1200 if( pVal && pVal->Value.hasValue() )
1201 pVal->Value >>= nSelectVal;
1203 addRadio( pCurParent, nCurX, nCurY, nAttachOffset,
1204 aText, aPropertyName, aChoices, nSelectVal,
1205 aLeftColumn, aRightColumn,
1206 pControllerProperties, pCtrlTarget );
1208 else if( aCtrlType == "List" && pCurParent )
1210 PropertyValue* pVal = pController->getValue( aPropertyName );
1211 sal_Int32 aSelectVal = 0;
1212 if( pVal && pVal->Value.hasValue() )
1213 pVal->Value >>= aSelectVal;
1215 addList( pCurParent, nCurX, nCurY, nAttachOffset,
1216 aText, aPropertyName, aChoices, aSelectVal,
1217 aLeftColumn, aRightColumn,
1218 pControllerProperties, pCtrlTarget );
1220 else if( (aCtrlType == "Edit"
1221 || aCtrlType == "Range") && pCurParent )
1224 PropertyValue* pVal = pController->getValue( aPropertyName );
1225 addEdit( pCurParent, nCurX, nCurY, nAttachOffset,
1226 aCtrlType, aText, aPropertyName, pVal,
1227 nMinValue, nMaxValue,
1228 aLeftColumn, aRightColumn,
1229 pControllerProperties, pCtrlTarget );
1234 SAL_INFO( "vcl.osx.print", "Unsupported UI option \"" << aCtrlType << "\"");
1238 pControllerProperties->updateEnableState();
1239 adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
1241 // now reposition everything again so it is upper bound
1242 adjustTabViews( pTabView, aMaxTabSize );
1244 // find the minimum needed tab size
1245 NSSize aTabCtrlSize = [pTabView minimumSize];
1246 aTabCtrlSize.height += aMaxTabSize.height + 10;
1247 if( aTabCtrlSize.width < aMaxTabSize.width + 10 )
1248 aTabCtrlSize.width = aMaxTabSize.width + 10;
1249 [pTabView setFrameSize: aTabCtrlSize];
1250 aViewFrame.size.width = aTabCtrlSize.width + aTabViewFrame.origin.x;
1251 aViewFrame.size.height = aTabCtrlSize.height + aTabViewFrame.origin.y;
1252 [pAccessoryView setFrameSize: aViewFrame.size];
1254 // get the print panel
1255 NSPrintPanel* pPrintPanel = [pOp printPanel];
1256 [pPrintPanel setOptions: [pPrintPanel options] | NSPrintPanelShowsPreview];
1257 // add the accessory controller to the panel
1258 [pPrintPanel addAccessoryController: [pAccessoryController autorelease]];
1260 // set the current selected tab item
1261 if( pState->nLastPage >= 0 && pState->nLastPage < [pTabView numberOfTabViewItems] )
1262 [pTabView selectTabViewItemAtIndex: pState->nLastPage];
1269 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */