nss: upgrade to release 3.73
[LibreOffice.git] / vcl / osx / printaccessoryview.mm
blob92ccbf686fe803185bd228a924ee97cef33bc7ef
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  */
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>
36 #include <svdata.hxx>
37 #include <strings.hrc>
38 #include <printaccessoryview.hrc>
40 #include <com/sun/star/i18n/XBreakIterator.hpp>
41 #include <com/sun/star/i18n/WordType.hpp>
43 #include <map>
44 #include <utility>
46 using namespace vcl;
47 using namespace com::sun::star;
48 using namespace com::sun::star::beans;
49 using namespace com::sun::star::uno;
51 namespace {
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;
64 -(void)dealloc;
65 @end
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;
87 @end
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 )
127     {
128         #if OSL_DEBUG_LEVEL > 1
129         SAL_INFO( "vcl.osx.print", "number of pages changed" <<
130                   " from " << pLastPageCount << " to " << nPages );
131         #endif
132         mpViewState->bNeedRestart = true;
133     }
135     NSTabView* pTabView = [[[self view] subviews] objectAtIndex:0];
136     NSTabViewItem* pItem = [pTabView selectedTabViewItem];
137     if( pItem )
138         mpViewState->nLastPage = [pTabView indexOfTabViewItem: pItem];
139     else
140         mpViewState->nLastPage = 0;
142     if( mpViewState->bNeedRestart )
143     {
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];
148         if( pNSWindow )
149             [pNSWindow cancelOperation: nil];
150         [[mpPrintOperation printInfo] setJobDisposition: NSPrintCancelJob];
151     }
153     return nPages;
156 @end
158 namespace {
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;
166     int                                 mnNextTag;
167     sal_Int32                           mnLastPageCount;
168     AquaPrintPanelAccessoryController*  mpAccessoryController;
170 public:
171     ControllerProperties( AquaPrintPanelAccessoryController* i_pAccessoryController )
172     : mnNextTag( 0 )
173     , mnLastPageCount( [i_pAccessoryController printerController]->getFilteredPageCount() )
174     , mpAccessoryController( i_pAccessoryController )
175     {
176         static_assert( SAL_N_ELEMENTS(SV_PRINT_NATIVE_STRINGS) == 5, "resources not found" );
177     }
179     static OUString getMoreString()
180     {
181         return VclResId(SV_PRINT_NATIVE_STRINGS[3]);
182     }
184     static OUString getPrintSelectionString()
185     {
186         return VclResId(SV_PRINT_NATIVE_STRINGS[4]);
187     }
189     int addNameTag( const OUString& i_rPropertyName )
190     {
191         int nNewTag = mnNextTag++;
192         maTagToPropertyName[ nNewTag ] = i_rPropertyName;
193         return nNewTag;
194     }
196     int addNameAndValueTag( const OUString& i_rPropertyName, sal_Int32 i_nValue )
197     {
198         int nNewTag = mnNextTag++;
199         maTagToPropertyName[ nNewTag ] = i_rPropertyName;
200         maTagToValueInt[ nNewTag ] = i_nValue;
201         return nNewTag;
202     }
204     void addObservedControl( NSObject* i_pView )
205     {
206         maViews.push_back( i_pView );
207     }
209     void addViewPair( NSView* i_pLeft, NSView* i_pRight )
210     {
211         maViewPairMap[ i_pLeft ] = i_pRight;
212         maViewPairMap[ i_pRight ] = i_pLeft;
213     }
215     NSView* getPair( NSView* i_pLeft ) const
216     {
217         NSView* pRight = nil;
218         std::map< NSView*, NSView* >::const_iterator it = maViewPairMap.find( i_pLeft );
219         if( it != maViewPairMap.end() )
220             pRight = it->second;
221         return pRight;
222     }
224     void changePropertyWithIntValue( int i_nTag )
225     {
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() )
229         {
230             vcl::PrinterController * mpController = [mpAccessoryController printerController];
231             PropertyValue* pVal = mpController->getValue( name_it->second );
232             if( pVal )
233             {
234                 pVal->Value <<= value_it->second;
235                 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
236             }
237         }
238     }
240     void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue )
241     {
242         std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
243         if( name_it != maTagToPropertyName.end() )
244         {
245             vcl::PrinterController * mpController = [mpAccessoryController printerController];
246             PropertyValue* pVal = mpController->getValue( name_it->second );
247             if( pVal )
248             {
249                 pVal->Value <<= i_nValue;
250                 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
251             }
252         }
253     }
255     void changePropertyWithBoolValue( int i_nTag, bool i_bValue )
256     {
257         std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
258         if( name_it != maTagToPropertyName.end() )
259         {
260             vcl::PrinterController * mpController = [mpAccessoryController printerController];
261             PropertyValue* pVal = mpController->getValue( name_it->second );
262             if( pVal )
263             {
264                 // ugly
265                 if( name_it->second == "PrintContent" )
266                    pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0);
267                 else
268                    pVal->Value <<= i_bValue;
270                 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
271             }
272         }
273     }
275     void changePropertyWithStringValue( int i_nTag, const OUString& i_rValue )
276     {
277         std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
278         if( name_it != maTagToPropertyName.end() )
279         {
280             vcl::PrinterController * mpController = [mpAccessoryController printerController];
281             PropertyValue* pVal = mpController->getValue( name_it->second );
282             if( pVal )
283             {
284                 pVal->Value <<= i_rValue;
285                 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
286             }
287         }
288     }
290     void updateEnableState()
291     {
292         for( std::vector< NSObject* >::iterator it = maViews.begin(); it != maViews.end(); ++it )
293         {
294             NSObject* pObj = *it;
295             NSControl* pCtrl = nil;
296             NSCell* pCell = 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] :
304                        -1;
306             std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( nTag );
307             if( name_it != maTagToPropertyName.end() && name_it->second != "PrintContent" )
308             {
309                 vcl::PrinterController * mpController = [mpAccessoryController printerController];
310                 bool bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO;
311                 if( pCtrl )
312                 {
313                     [pCtrl setEnabled: bEnabled];
314                     NSView* pOther = getPair( pCtrl );
315                     if( pOther && [pOther isKindOfClass: [NSControl class]] )
316                         [static_cast<NSControl*>(pOther) setEnabled: bEnabled];
317                 }
318                 else if( pCell )
319                     [pCell setEnabled: bEnabled];
320             }
321         }
322     }
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]) )
341     {
342         mpController = pController;
343     }
344     return self;
347 -(void)triggered:(id)pSender
349     if( [pSender isMemberOfClass: [NSPopUpButton class]] )
350     {
351         NSPopUpButton* pBtn = static_cast<NSPopUpButton*>(pSender);
352         NSMenuItem* pSelected = [pBtn selectedItem];
353         if( pSelected )
354         {
355             int nTag = [pSelected tag];
356             mpController->changePropertyWithIntValue( nTag );
357         }
358     }
359     else if( [pSender isMemberOfClass: [NSButton class]] )
360     {
361         NSButton* pBtn = static_cast<NSButton*>(pSender);
362         int nTag = [pBtn tag];
363         mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSControlStateValueOn );
364     }
365     else if( [pSender isMemberOfClass: [NSMatrix class]] )
366     {
367         NSObject* pObj = [static_cast<NSMatrix*>(pSender) selectedCell];
368         if( [pObj isMemberOfClass: [NSButtonCell class]] )
369         {
370             NSButtonCell* pCell = static_cast<NSButtonCell*>(pObj);
371             int nTag = [pCell tag];
372             mpController->changePropertyWithIntValue( nTag );
373         }
374     }
375     else if( [pSender isMemberOfClass: [NSTextField class]] )
376     {
377         NSTextField* pField = static_cast<NSTextField*>(pSender);
378         int nTag = [pField tag];
379         OUString aValue = GetOUString( [pSender stringValue] );
380         mpController->changePropertyWithStringValue( nTag, aValue );
381     }
382     else
383     {
384         SAL_INFO( "vcl.osx.print", "Unsupported class" <<
385                   ( [pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil" ) );
386     }
387     mpController->updateEnableState();
390 -(void)triggeredNumeric:(id)pSender
392     if( [pSender isMemberOfClass: [NSTextField class]] )
393     {
394         NSTextField* pField = static_cast<NSTextField*>(pSender);
395         int nTag = [pField tag];
396         sal_Int64 nValue = [pField intValue];
397         
398         NSView* pOther = mpController->getPair( pField );
399         if( pOther )
400             [static_cast<NSControl*>(pOther) setIntValue: nValue];
402         mpController->changePropertyWithIntValue( nTag, nValue );
403     }
404     else if( [pSender isMemberOfClass: [NSStepper class]] )
405     {
406         NSStepper* pStep = static_cast<NSStepper*>(pSender);
407         int nTag = [pStep tag];
408         sal_Int64 nValue = [pStep intValue];
410         NSView* pOther = mpController->getPair( pStep );
411         if( pOther )
412             [static_cast<NSControl*>(pOther) setIntValue: nValue];
414         mpController->changePropertyWithIntValue( nTag, nValue );
415     }
416     else
417     {
418         SAL_INFO( "vcl.osx.print", "Unsupported class" <<
419                   ([pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil") );
420     }
421     mpController->updateEnableState();
424 -(void)dealloc
426     delete mpController;
427     [super dealloc];
430 @end
432 namespace {
434 struct ColumnItem
436     NSControl*      pControl;
437     CGFloat         nOffset;
438     NSControl*      pSubControl;
439     
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 )
444     {}
445     
446     CGFloat getWidth() const
447     {
448         CGFloat nWidth = 0;
449         if( pControl )
450         {
451             NSRect aCtrlRect = [pControl frame];
452             nWidth = aCtrlRect.size.width;
453             nWidth += nOffset;
454             if( pSubControl )
455             {
456                 NSRect aSubRect = [pSubControl frame];
457                 nWidth += aSubRect.size.width;
458                 nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width);
459             }
460         }
461         return nWidth;
462     }
467 static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize,
468                                    std::vector< ColumnItem >& rLeftColumn,
469                                    std::vector< ColumnItem >& rRightColumn
470                                   )
472     // balance columns
474     // first get overall column widths
475     CGFloat nLeftWidth = 0;
476     CGFloat nRightWidth = 0;
477     for( size_t i = 0; i < rLeftColumn.size(); i++ )
478     {
479         CGFloat nW = rLeftColumn[i].getWidth();
480         if( nW > nLeftWidth )
481             nLeftWidth = nW;
482     }
483     for( size_t i = 0; i < rRightColumn.size(); i++ )
484     {
485         CGFloat nW = rRightColumn[i].getWidth();
486         if( nW > nRightWidth )
487             nRightWidth = nW;
488     }
490     // right align left column
491     for( size_t i = 0; i < rLeftColumn.size(); i++ )
492     {
493         if( rLeftColumn[i].pControl )
494         {
495             NSRect aCtrlRect = [rLeftColumn[i].pControl frame];
496             CGFloat nX = nLeftWidth - aCtrlRect.size.width;
497             if( rLeftColumn[i].pSubControl )
498             {
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];
503             }
504             aCtrlRect.origin.x = nX;
505             [rLeftColumn[i].pControl setFrame: aCtrlRect];
506         }
507     }
509     // left align right column
510     for( size_t i = 0; i < rRightColumn.size(); i++ )
511     {
512         if( rRightColumn[i].pControl )
513         {
514             NSRect aCtrlRect = [rRightColumn[i].pControl frame];
515             CGFloat nX = nLeftWidth + 3;
516             if( rRightColumn[i].pSubControl )
517             {
518                 NSRect aSubRect = [rRightColumn[i].pSubControl frame];
519                 aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x; 
520                 [rRightColumn[i].pSubControl setFrame: aSubRect];
521             }
522             aCtrlRect.origin.x = nX;
523             [rRightColumn[i].pControl setFrame: aCtrlRect];
524         }
525     }
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++ )
533     {
534         aUnion = NSUnionRect( aUnion, [[pSubViews objectAtIndex: n] frame] );
535     }
537     // move everything so it will fit
538     for( unsigned int n = 0; n < nViews; n++ )
539     {
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];
545     }
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++ )
564     {
565         NSTabViewItem* pItem = static_cast<NSTabViewItem*>([pTabbedViews objectAtIndex: i]);
566         NSView* pNSView = [pItem view];
567         if( pNSView )
568         {
569             NSRect aRect = [pNSView frame];
570             double nDiff = aTabSize.height - aRect.size.height;
571             aRect.size = aTabSize;
572             [pNSView setFrame: aRect];
573             
574             NSArray* pSubViews = [pNSView subviews];
575             unsigned int nSubViews = [pSubViews count];
577             // move everything up
578             for( unsigned int n = 0; n < nSubViews; n++ )
579             {
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]] )
586                 {
587                     aFrame.size.width = aTabSize.width - aFrame.origin.x - 10;
588                 }
589                 [pCurSubView setFrame: aFrame];
590             }
591         }
592     }
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];
607     [pText release];
608     return pTextView;
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() );
615     if( xBI.is() )
616     {
617         i18n::Boundary aBoundary =
618                 xBI->getWordBoundary( i_rText, i_nPos,
619                                       Application::GetSettings().GetLanguageTag().getLocale(),
620                                       i18n::WordType::ANYWORD_IGNOREWHITESPACES,
621                                       true );
622         nRet = aBoundary.endPos;
623     }
624     return nRet;
627 static void linebreakCell( NSCell* pBtn, const OUString& i_rText )
629     NSString* pText = CreateNSString( i_rText );
630     [pBtn setTitle: pText];
631     [pText release];
632     NSSize aSize = [pBtn cellSize];
633     if( aSize.width > 280 )
634     {
635         // need two lines
636         sal_Int32 nLen = i_rText.getLength();
637         sal_Int32 nIndex = nLen / 2;
638         nIndex = findBreak( i_rText, nIndex );
639         if( nIndex < nLen )
640         {
641             OUStringBuffer aBuf( i_rText );
642             aBuf[nIndex] = '\n';
643             pText = CreateNSString( aBuf.makeStringAndClear() );
644             [pBtn setTitle: pText];
645             [pText release];
646         }
647     }
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];
655     // move to nCurY
656     aTextRect.origin.y = rCurY - aTextRect.size.height;
657     [pTextView setFrame: aTextRect];
658     
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]];
663     
664     // update nCurY
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
674                     )
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];
680     if( ! bEnabled )
681         [pBtn setEnabled: NO];
682     linebreakCell( [pBtn cell], rText );
683     [pBtn sizeToFit];
684     
685     rRightColumn.push_back( ColumnItem( pBtn ) );
686     
687     // connect target
688     [pBtn setTarget: pCtrlTarget];
689     [pBtn setAction: @selector(triggered:)];
690     int nTag = pControllerProperties->addNameTag( rProperty );
691     pControllerProperties->addObservedControl( pBtn );
692     [pBtn setTag: nTag];
693     
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
698     // is minimally of
699     aCheckRect.size.width += 1;
700     
701     // move to rCurY
702     aCheckRect.origin.y = rCurY - aCheckRect.size.height;
703     [pBtn setFrame: aCheckRect];
705     [pCurParent addSubview: [pBtn autorelease]];
706     
707     // update rCurY
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
718                      )
720     CGFloat nOff = 0;
721     if( rText.getLength() )
722     {
723         // add a label
724         NSControl* pTextView = createLabel( rText );
725         NSRect aTextRect = [pTextView frame];
726         aTextRect.origin.x = rCurX + nAttachOffset;
727         [pCurParent addSubview: [pTextView autorelease]];
728         
729         rLeftColumn.push_back( ColumnItem( pTextView ) );
730         
731         // move to nCurY
732         aTextRect.origin.y = rCurY - aTextRect.size.height;
733         [pTextView setFrame: aTextRect];
734         
735         // update nCurY
736         rCurY = aTextRect.origin.y - 5;
737         
738         // indent the radio group relative to the text
739         // nOff = 20;
740     }
741     
742     // setup radio matrix
743     NSButtonCell* pProto = [[NSButtonCell alloc] init];
744     
745     NSRect aRadioRect = { { rCurX + nOff, 0 },
746                           { 280 - rCurX,
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()
754                                           numberOfColumns: 1];
755     // set individual titles
756     NSArray* pCells = [pMatrix cells];
757     for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
758     {
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];
770     }
771     [pMatrix sizeToFit];
772     aRadioRect = [pMatrix frame];
773     
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]];
778     
779     rRightColumn.push_back( ColumnItem( pMatrix ) );
780     
781     // update nCurY
782     rCurY = aRadioRect.origin.y - 5;
783     
784     [pProto release];
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
794                     )
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];
807     // iterate options
808     for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
809     {
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];
815         [pItemText release];
816     }
818     [pBtn selectItemAtIndex: nSelectValue];
819     
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 )];
825     [pBtn sizeToFit];
826     [pCurParent addSubview: [pBtn autorelease]];
827     
828     rRightColumn.push_back( ColumnItem( pBtn ) );
830     // connect target and action
831     [pBtn setTarget: pCtrlTarget];
832     [pBtn setAction: @selector(triggered:)];
833     
834     // move to nCurY
835     aBtnRect = [pBtn frame];
836     aBtnRect.origin.y = rCurY - aBtnRect.size.height;
837     [pBtn setFrame: aBtnRect];
838     
839     // align label
840     aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2;
841     [pTextView setFrame: aTextRect];
843     // update rCurY
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
856                     )
858     CGFloat nOff = 0;
859     if( rText.getLength() )
860     {
861         // add a label
862         NSControl* pTextView = createLabel( rText );
863         [pCurParent addSubview: [pTextView autorelease]];
864         
865         rLeftColumn.push_back( ColumnItem( pTextView ) );
866         
867         // move to nCurY
868         NSRect aTextRect = [pTextView frame];
869         aTextRect.origin.x = rCurX + nAttachOffset;
870         aTextRect.origin.y = rCurY - aTextRect.size.height;
871         [pTextView setFrame: aTextRect];
872         
873         // update nCurY
874         rCurY = aTextRect.origin.y - 5;
875         
876         // and set the offset for the real edit field
877         nOff = aTextRect.size.width + 5;
878     }
879     
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]];
887     
888     rRightColumn.push_back( ColumnItem( pFieldView ) );
889     
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 );
897     // move to nCurY
898     aFieldRect.origin.y = rCurY - aFieldRect.size.height;
899     [pFieldView setFrame: aFieldRect];
901     if( rCtrlType == "Range" )
902     {
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]];
912         
913         rRightColumn.back().pSubControl = pStep;
914         
915         pControllerProperties->addObservedControl( pStep );
916         [pStep setTarget: pCtrlTarget];
917         [pStep setAction: @selector(triggered:)];
918         
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 )
926         {
927             [pFormatter setMinimum: [[NSNumber numberWithInt: nMinValue] autorelease]];
928             [pStep setMinValue: nMinValue];
929             [pFormatter setMaximum: [[NSNumber numberWithInt: nMaxValue] autorelease]];
930             [pStep setMaxValue: nMaxValue];
931         }
932         [pFieldView setFormatter: pFormatter];
934         sal_Int64 nSelectVal = 0;
935         if( pValue && pValue->Value.hasValue() )
936             pValue->Value >>= nSelectVal;
937         
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:)];
947     }
948     else
949     {
950         // connect target and action
951         [pFieldView setTarget: pCtrlTarget];
952         [pFieldView setAction: @selector(triggered:)];
954         if( pValue && pValue->Value.hasValue() )
955         {
956             OUString aValue;
957             pValue->Value >>= aValue;
958             if( aValue.getLength() )
959             {
960                 NSString* pText = CreateNSString( aValue );
961                 [pFieldView setStringValue: pText];
962                 [pText release];
963             }
964         }
965     }
967     // update nCurY
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 )
979         return nil;
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;
997     CGFloat nCurY = 0;
998     CGFloat nCurX = 0;
999     NSSize aMaxTabSize = NSZeroSize;
1001     ControllerProperties* pControllerProperties = new ControllerProperties( pAccessoryController );
1002     ControlTarget* pCtrlTarget = [[ControlTarget alloc] initWithControllerMap: pControllerProperties];
1004     std::vector< ColumnItem > aLeftColumn, aRightColumn;
1006     // ugly:
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 )
1011     {
1012         Sequence< beans::PropertyValue > aOptProp;
1013         prop.Value >>= aOptProp;
1015         OUString aCtrlType;
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) )
1021         {
1022             if( rEntry.Name == "ControlType" )
1023             {
1024                 rEntry.Value >>= aCtrlType;
1025             }
1026             else if( rEntry.Name == "Choices" )
1027             {
1028                 rEntry.Value >>= aChoices;
1029             }
1030             else if( rEntry.Name == "ChoicesDisabled" )
1031             {
1032                 rEntry.Value >>= aChoicesDisabled;
1033             }
1034             else if( rEntry.Name == "Property" )
1035             {
1036                 PropertyValue aVal;
1037                 rEntry.Value >>= aVal;
1038                 aPropertyName = aVal.Name;
1039                 if( aPropertyName == "PrintContent" )
1040                     aVal.Value >>= aSelectionChecked;
1041             }
1042         }
1043         if( aCtrlType == "Radio" &&
1044             aPropertyName == "PrintContent" &&
1045             aChoices.getLength() > 2 )
1046         {
1047             bAddSelectionCheckBox = true;
1048             bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2];
1049             bSelectionBoxChecked = (aSelectionChecked==2);
1050             break;
1051         }
1052     }
1054     for( const PropertyValue & prop : rOptions )
1055     {
1056         Sequence< beans::PropertyValue > aOptProp;
1057         prop.Value >>= aOptProp;
1059         // extract ui element
1060         OUString aCtrlType;
1061         OUString aText;
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) )
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 == "Enabled" )
1092             {
1093                 bool bValue = true;
1094                 rEntry.Value >>= bValue;
1095                 bEnabled = bValue;
1096             }
1097             else if( rEntry.Name == "MinValue" )
1098             {
1099                 rEntry.Value >>= nMinValue;
1100             }
1101             else if( rEntry.Name == "MaxValue" )
1102             {
1103                 rEntry.Value >>= nMaxValue;
1104             }
1105             else if( rEntry.Name == "AttachToDependency" )
1106             {
1107                 nAttachOffset = 20;
1108             }
1109             else if( rEntry.Name == "InternalUIOnly" )
1110             {
1111                 bool bValue = false;
1112                 rEntry.Value >>= bValue;
1113                 bIgnore = bValue;
1114             }
1115             else if( rEntry.Name == "GroupingHint" )
1116             {
1117                 rEntry.Value >>= aGroupHint;
1118             }
1119         }
1121         if( aCtrlType == "Group" ||
1122             aCtrlType == "Subgroup" ||
1123             aCtrlType == "Radio" ||
1124             aCtrlType == "List"  ||
1125             aCtrlType == "Edit"  ||
1126             aCtrlType == "Range"  ||
1127             aCtrlType == "Bool" )
1128         {
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"
1134                 || ! pCurParent
1135                 || ( aCtrlType == "Subgroup" && nCurY < -250 && ! bIgnore )
1136                )
1137             {
1138                 OUString aGroupTitle( aText );
1139                 if( aCtrlType == "Subgroup" )
1140                     aGroupTitle = ControllerProperties::getMoreString();
1142                 // set size of current parent
1143                 if( pCurParent )
1144                     adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
1146                 // new tab item
1147                 if( ! aText.getLength() )
1148                     aText = "OOo";
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];
1155                 [pLabel release];
1157                 nCurX = 20; // reset indent
1158                 nCurY = 0;  // reset Y
1159                 // clear columns
1160                 aLeftColumn.clear();
1161                 aRightColumn.clear();
1163                 if( bAddSelectionCheckBox )
1164                 {
1165                     addBool( pCurParent, nCurX, nCurY, 0,
1166                              ControllerProperties::getPrintSelectionString(), bSelectionBoxEnabled,
1167                              "PrintContent", bSelectionBoxChecked,
1168                              aRightColumn, pControllerProperties, pCtrlTarget );
1169                     bAddSelectionCheckBox = false;
1170                 }
1171             }
1173             if( aCtrlType == "Subgroup" && pCurParent )
1174             {
1175                 bIgnoreSubgroup = bIgnore;
1176                 if( bIgnore )
1177                     continue;
1178                 
1179                 addSubgroup( pCurParent, nCurY, aText );
1180             }
1181             else if( bIgnoreSubgroup || bIgnore )
1182             {
1183                 continue;
1184             }
1185             else if( aCtrlType == "Bool" && pCurParent )
1186             {
1187                 bool bVal = false;
1188                 PropertyValue* pVal = pController->getValue( aPropertyName );
1189                 if( pVal )
1190                     pVal->Value >>= bVal;
1191                 addBool( pCurParent, nCurX, nCurY, nAttachOffset,
1192                          aText, true, aPropertyName, bVal,
1193                          aRightColumn, pControllerProperties, pCtrlTarget );
1194             }
1195             else if( aCtrlType == "Radio" && pCurParent )
1196             {
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 );
1207             }
1208             else if( aCtrlType == "List" && pCurParent )
1209             {
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 );
1219             }
1220             else if( (aCtrlType == "Edit"
1221                 || aCtrlType == "Range") && pCurParent )
1222             {
1223                 // current value
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 );
1230             }
1231         }
1232         else
1233         {
1234             SAL_INFO( "vcl.osx.print", "Unsupported UI option \"" << aCtrlType << "\"");
1235         }
1236     }
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];
1264     return pCtrlTarget;
1267 @end
1269 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */