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