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