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