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