1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
38 #include <strings.hrc>
39 #include <printaccessoryview.hrc>
41 #include <com/sun/star/i18n/XBreakIterator.hpp>
42 #include <com/sun/star/i18n/WordType.hpp>
45 #include <string_view>
49 using namespace com::sun::star;
50 using namespace com::sun::star::beans;
51 using namespace com::sun::star::uno;
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;
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;
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 )
130 #if OSL_DEBUG_LEVEL > 1
131 SAL_INFO( "vcl.osx.print", "number of pages changed" <<
132 " from " << pLastPageCount << " to " << nPages );
134 mpViewState->bNeedRestart = true;
137 NSTabView* pTabView = [[[self view] subviews] objectAtIndex:0];
138 NSTabViewItem* pItem = [pTabView selectedTabViewItem];
140 mpViewState->nLastPage = [pTabView indexOfTabViewItem: pItem];
142 mpViewState->nLastPage = 0;
144 if( mpViewState->bNeedRestart )
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];
151 [pNSWindow cancelOperation: nil];
152 [[mpPrintOperation printInfo] setJobDisposition: NSPrintCancelJob];
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;
169 sal_Int32 mnLastPageCount;
170 AquaPrintPanelAccessoryController* mpAccessoryController;
173 ControllerProperties( AquaPrintPanelAccessoryController* i_pAccessoryController )
175 , mnLastPageCount( [i_pAccessoryController printerController]->getFilteredPageCount() )
176 , mpAccessoryController( i_pAccessoryController )
178 static_assert( SAL_N_ELEMENTS(SV_PRINT_NATIVE_STRINGS) == 5, "resources not found" );
181 static OUString getMoreString()
183 return VclResId(SV_PRINT_NATIVE_STRINGS[3]);
186 static OUString getPrintSelectionString()
188 return VclResId(SV_PRINT_NATIVE_STRINGS[4]);
191 int addNameTag( const OUString& i_rPropertyName )
193 int nNewTag = mnNextTag++;
194 maTagToPropertyName[ nNewTag ] = i_rPropertyName;
198 int addNameAndValueTag( const OUString& i_rPropertyName, sal_Int32 i_nValue )
200 int nNewTag = mnNextTag++;
201 maTagToPropertyName[ nNewTag ] = i_rPropertyName;
202 maTagToValueInt[ nNewTag ] = i_nValue;
206 void addObservedControl( NSObject* i_pView )
208 maViews.push_back( i_pView );
211 void addViewPair( NSView* i_pLeft, NSView* i_pRight )
213 maViewPairMap[ i_pLeft ] = i_pRight;
214 maViewPairMap[ i_pRight ] = i_pLeft;
217 NSView* getPair( NSView* i_pLeft ) const
219 NSView* pRight = nil;
220 std::map< NSView*, NSView* >::const_iterator it = maViewPairMap.find( i_pLeft );
221 if( it != maViewPairMap.end() )
226 void changePropertyWithIntValue( int i_nTag )
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() )
232 vcl::PrinterController * mpController = [mpAccessoryController printerController];
233 PropertyValue* pVal = mpController->getValue( name_it->second );
236 pVal->Value <<= value_it->second;
237 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
242 void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue )
244 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
245 if( name_it != maTagToPropertyName.end() )
247 vcl::PrinterController * mpController = [mpAccessoryController printerController];
248 PropertyValue* pVal = mpController->getValue( name_it->second );
251 pVal->Value <<= i_nValue;
252 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
257 void changePropertyWithBoolValue( int i_nTag, bool i_bValue )
259 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
260 if( name_it != maTagToPropertyName.end() )
262 vcl::PrinterController * mpController = [mpAccessoryController printerController];
263 PropertyValue* pVal = mpController->getValue( name_it->second );
267 if( name_it->second == "PrintContent" )
268 pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0);
270 pVal->Value <<= i_bValue;
272 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
277 void changePropertyWithStringValue( int i_nTag, const OUString& i_rValue )
279 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
280 if( name_it != maTagToPropertyName.end() )
282 vcl::PrinterController * mpController = [mpAccessoryController printerController];
283 PropertyValue* pVal = mpController->getValue( name_it->second );
286 pVal->Value <<= i_rValue;
287 mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
292 void updateEnableState()
294 for( std::vector< NSObject* >::iterator it = maViews.begin(); it != maViews.end(); ++it )
296 NSObject* pObj = *it;
297 NSControl* pCtrl = 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] :
308 std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( nTag );
309 if( name_it != maTagToPropertyName.end() && name_it->second != "PrintContent" )
311 vcl::PrinterController * mpController = [mpAccessoryController printerController];
312 bool bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO;
315 [pCtrl setEnabled: bEnabled];
316 NSView* pOther = getPair( pCtrl );
317 if( pOther && [pOther isKindOfClass: [NSControl class]] )
318 [static_cast<NSControl*>(pOther) setEnabled: bEnabled];
321 [pCell setEnabled: bEnabled];
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]) )
344 mpController = pController;
349 -(void)triggered:(id)pSender
351 if( [pSender isMemberOfClass: [NSPopUpButton class]] )
353 NSPopUpButton* pBtn = static_cast<NSPopUpButton*>(pSender);
354 NSMenuItem* pSelected = [pBtn selectedItem];
357 int nTag = [pSelected tag];
358 mpController->changePropertyWithIntValue( nTag );
361 else if( [pSender isMemberOfClass: [NSButton class]] )
363 NSButton* pBtn = static_cast<NSButton*>(pSender);
364 int nTag = [pBtn tag];
365 mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSControlStateValueOn );
367 else if( [pSender isMemberOfClass: [NSMatrix class]] )
369 NSObject* pObj = [static_cast<NSMatrix*>(pSender) selectedCell];
370 if( [pObj isMemberOfClass: [NSButtonCell class]] )
372 NSButtonCell* pCell = static_cast<NSButtonCell*>(pObj);
373 int nTag = [pCell tag];
374 mpController->changePropertyWithIntValue( nTag );
377 else if( [pSender isMemberOfClass: [NSTextField class]] )
379 NSTextField* pField = static_cast<NSTextField*>(pSender);
380 int nTag = [pField tag];
381 OUString aValue = GetOUString( [pSender stringValue] );
382 mpController->changePropertyWithStringValue( nTag, aValue );
386 SAL_INFO( "vcl.osx.print", "Unsupported class" <<
387 ( [pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil" ) );
389 mpController->updateEnableState();
392 -(void)triggeredNumeric:(id)pSender
394 if( [pSender isMemberOfClass: [NSTextField class]] )
396 NSTextField* pField = static_cast<NSTextField*>(pSender);
397 int nTag = [pField tag];
398 sal_Int64 nValue = [pField intValue];
400 NSView* pOther = mpController->getPair( pField );
402 [static_cast<NSControl*>(pOther) setIntValue: nValue];
404 mpController->changePropertyWithIntValue( nTag, nValue );
406 else if( [pSender isMemberOfClass: [NSStepper class]] )
408 NSStepper* pStep = static_cast<NSStepper*>(pSender);
409 int nTag = [pStep tag];
410 sal_Int64 nValue = [pStep intValue];
412 NSView* pOther = mpController->getPair( pStep );
414 [static_cast<NSControl*>(pOther) setIntValue: nValue];
416 mpController->changePropertyWithIntValue( nTag, nValue );
420 SAL_INFO( "vcl.osx.print", "Unsupported class" <<
421 ([pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil") );
423 mpController->updateEnableState();
440 NSControl* pSubControl;
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 )
448 CGFloat getWidth() const
453 NSRect aCtrlRect = [pControl frame];
454 nWidth = aCtrlRect.size.width;
458 NSRect aSubRect = [pSubControl frame];
459 nWidth += aSubRect.size.width;
460 nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width);
469 static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize,
470 std::vector< ColumnItem >& rLeftColumn,
471 std::vector< ColumnItem >& rRightColumn
476 // first get overall column widths
477 CGFloat nLeftWidth = 0;
478 CGFloat nRightWidth = 0;
479 for( size_t i = 0; i < rLeftColumn.size(); i++ )
481 CGFloat nW = rLeftColumn[i].getWidth();
482 if( nW > nLeftWidth )
485 for( size_t i = 0; i < rRightColumn.size(); i++ )
487 CGFloat nW = rRightColumn[i].getWidth();
488 if( nW > nRightWidth )
492 // right align left column
493 for( size_t i = 0; i < rLeftColumn.size(); i++ )
495 if( rLeftColumn[i].pControl )
497 NSRect aCtrlRect = [rLeftColumn[i].pControl frame];
498 CGFloat nX = nLeftWidth - aCtrlRect.size.width;
499 if( rLeftColumn[i].pSubControl )
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];
506 aCtrlRect.origin.x = nX;
507 [rLeftColumn[i].pControl setFrame: aCtrlRect];
511 // left align right column
512 for( size_t i = 0; i < rRightColumn.size(); i++ )
514 if( rRightColumn[i].pControl )
516 NSRect aCtrlRect = [rRightColumn[i].pControl frame];
517 CGFloat nX = nLeftWidth + 3;
518 if( rRightColumn[i].pSubControl )
520 NSRect aSubRect = [rRightColumn[i].pSubControl frame];
521 aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x;
522 [rRightColumn[i].pSubControl setFrame: aSubRect];
524 aCtrlRect.origin.x = nX;
525 [rRightColumn[i].pControl setFrame: aCtrlRect];
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++ )
536 aUnion = NSUnionRect( aUnion, [[pSubViews objectAtIndex: n] frame] );
539 // move everything so it will fit
540 for( unsigned int n = 0; n < nViews; n++ )
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];
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++ )
567 NSTabViewItem* pItem = static_cast<NSTabViewItem*>([pTabbedViews objectAtIndex: i]);
568 NSView* pNSView = [pItem view];
571 NSRect aRect = [pNSView frame];
572 double nDiff = aTabSize.height - aRect.size.height;
573 aRect.size = aTabSize;
574 [pNSView setFrame: aRect];
576 NSArray* pSubViews = [pNSView subviews];
577 unsigned int nSubViews = [pSubViews count];
579 // move everything up
580 for( unsigned int n = 0; n < nSubViews; n++ )
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]] )
589 aFrame.size.width = aTabSize.width - aFrame.origin.x - 10;
591 [pCurSubView setFrame: aFrame];
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];
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() );
619 i18n::Boundary aBoundary =
620 xBI->getWordBoundary( i_rText, i_nPos,
621 Application::GetSettings().GetLanguageTag().getLocale(),
622 i18n::WordType::ANYWORD_IGNOREWHITESPACES,
624 nRet = aBoundary.endPos;
629 static void linebreakCell( NSCell* pBtn, const OUString& i_rText )
631 NSString* pText = CreateNSString( i_rText );
632 [pBtn setTitle: pText];
634 NSSize aSize = [pBtn cellSize];
635 if( aSize.width > 280 )
638 sal_Int32 nLen = i_rText.getLength();
639 sal_Int32 nIndex = nLen / 2;
640 nIndex = findBreak( i_rText, nIndex );
643 OUStringBuffer aBuf( i_rText );
645 pText = CreateNSString( aBuf.makeStringAndClear() );
646 [pBtn setTitle: pText];
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];
658 aTextRect.origin.y = rCurY - aTextRect.size.height;
659 [pTextView setFrame: aTextRect];
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]];
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
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];
683 [pBtn setEnabled: NO];
684 linebreakCell( [pBtn cell], rText );
687 rRightColumn.push_back( ColumnItem( pBtn ) );
690 [pBtn setTarget: pCtrlTarget];
691 [pBtn setAction: @selector(triggered:)];
692 int nTag = pControllerProperties->addNameTag( rProperty );
693 pControllerProperties->addObservedControl( pBtn );
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
701 aCheckRect.size.width += 1;
704 aCheckRect.origin.y = rCurY - aCheckRect.size.height;
705 [pBtn setFrame: aCheckRect];
707 [pCurParent addSubview: [pBtn autorelease]];
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
723 if( rText.getLength() )
726 NSControl* pTextView = createLabel( rText );
727 NSRect aTextRect = [pTextView frame];
728 aTextRect.origin.x = rCurX + nAttachOffset;
729 [pCurParent addSubview: [pTextView autorelease]];
731 rLeftColumn.push_back( ColumnItem( pTextView ) );
734 aTextRect.origin.y = rCurY - aTextRect.size.height;
735 [pTextView setFrame: aTextRect];
738 rCurY = aTextRect.origin.y - 5;
740 // indent the radio group relative to the text
744 // setup radio matrix
745 NSButtonCell* pProto = [[NSButtonCell alloc] init];
747 NSRect aRadioRect = { { rCurX + nOff, 0 },
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()
757 // set individual titles
758 NSArray* pCells = [pMatrix cells];
759 for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
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];
774 aRadioRect = [pMatrix frame];
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]];
781 rRightColumn.push_back( ColumnItem( pMatrix ) );
784 rCurY = aRadioRect.origin.y - 5;
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
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];
810 for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
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];
820 [pBtn selectItemAtIndex: nSelectValue];
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 )];
828 [pCurParent addSubview: [pBtn autorelease]];
830 rRightColumn.push_back( ColumnItem( pBtn ) );
832 // connect target and action
833 [pBtn setTarget: pCtrlTarget];
834 [pBtn setAction: @selector(triggered:)];
837 aBtnRect = [pBtn frame];
838 aBtnRect.origin.y = rCurY - aBtnRect.size.height;
839 [pBtn setFrame: aBtnRect];
842 aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2;
843 [pTextView setFrame: aTextRect];
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
861 if( rText.getLength() )
864 NSControl* pTextView = createLabel( rText );
865 [pCurParent addSubview: [pTextView autorelease]];
867 rLeftColumn.push_back( ColumnItem( pTextView ) );
870 NSRect aTextRect = [pTextView frame];
871 aTextRect.origin.x = rCurX + nAttachOffset;
872 aTextRect.origin.y = rCurY - aTextRect.size.height;
873 [pTextView setFrame: aTextRect];
876 rCurY = aTextRect.origin.y - 5;
878 // and set the offset for the real edit field
879 nOff = aTextRect.size.width + 5;
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]];
890 rRightColumn.push_back( ColumnItem( pFieldView ) );
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 );
900 aFieldRect.origin.y = rCurY - aFieldRect.size.height;
901 [pFieldView setFrame: aFieldRect];
903 if( rCtrlType == u"Range" )
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]];
915 rRightColumn.back().pSubControl = pStep;
917 pControllerProperties->addObservedControl( pStep );
918 [pStep setTarget: pCtrlTarget];
919 [pStep setAction: @selector(triggered:)];
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 )
929 [pFormatter setMinimum: [[NSNumber numberWithInt: nMinValue] autorelease]];
930 [pStep setMinValue: nMinValue];
931 [pFormatter setMaximum: [[NSNumber numberWithInt: nMaxValue] autorelease]];
932 [pStep setMaxValue: nMaxValue];
934 [pFieldView setFormatter: pFormatter];
936 sal_Int64 nSelectVal = 0;
937 if( pValue && pValue->Value.hasValue() )
938 pValue->Value >>= nSelectVal;
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:)];
952 // connect target and action
953 [pFieldView setTarget: pCtrlTarget];
954 [pFieldView setAction: @selector(triggered:)];
956 if( pValue && pValue->Value.hasValue() )
959 pValue->Value >>= aValue;
960 if( aValue.getLength() )
962 NSString* pText = CreateNSString( aValue );
963 [pFieldView setStringValue: pText];
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 )
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;
1001 NSSize aMaxTabSize = NSZeroSize;
1003 ControllerProperties* pControllerProperties = new ControllerProperties( pAccessoryController );
1004 ControlTarget* pCtrlTarget = [[ControlTarget alloc] initWithControllerMap: pControllerProperties];
1006 std::vector< ColumnItem > aLeftColumn, aRightColumn;
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 )
1014 Sequence< beans::PropertyValue > aOptProp;
1015 prop.Value >>= aOptProp;
1018 OUString aPropertyName;
1019 Sequence< OUString > aChoices;
1020 Sequence< sal_Bool > aChoicesDisabled;
1021 sal_Int32 aSelectionChecked = 0;
1022 for( const beans::PropertyValue& rEntry : aOptProp )
1024 if( rEntry.Name == "ControlType" )
1026 rEntry.Value >>= aCtrlType;
1028 else if( rEntry.Name == "Choices" )
1030 rEntry.Value >>= aChoices;
1032 else if( rEntry.Name == "ChoicesDisabled" )
1034 rEntry.Value >>= aChoicesDisabled;
1036 else if( rEntry.Name == "Property" )
1039 rEntry.Value >>= aVal;
1040 aPropertyName = aVal.Name;
1041 if( aPropertyName == "PrintContent" )
1042 aVal.Value >>= aSelectionChecked;
1045 if( aCtrlType == "Radio" &&
1046 aPropertyName == "PrintContent" &&
1047 aChoices.getLength() > 2 )
1049 bAddSelectionCheckBox = true;
1050 bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2];
1051 bSelectionBoxChecked = (aSelectionChecked==2);
1056 for( const PropertyValue & prop : rOptions )
1058 Sequence< beans::PropertyValue > aOptProp;
1059 prop.Value >>= aOptProp;
1061 // extract ui element
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 )
1073 if( rEntry.Name == "Text" )
1075 rEntry.Value >>= aText;
1076 aText = filterAccelerator( aText );
1078 else if( rEntry.Name == "ControlType" )
1080 rEntry.Value >>= aCtrlType;
1082 else if( rEntry.Name == "Choices" )
1084 rEntry.Value >>= aChoices;
1086 else if( rEntry.Name == "Property" )
1089 rEntry.Value >>= aVal;
1090 aPropertyName = aVal.Name;
1092 else if( rEntry.Name == "MinValue" )
1094 rEntry.Value >>= nMinValue;
1096 else if( rEntry.Name == "MaxValue" )
1098 rEntry.Value >>= nMaxValue;
1100 else if( rEntry.Name == "AttachToDependency" )
1104 else if( rEntry.Name == "InternalUIOnly" )
1106 bool bValue = false;
1107 rEntry.Value >>= bValue;
1110 else if( rEntry.Name == "GroupingHint" )
1112 rEntry.Value >>= aGroupHint;
1116 if( aCtrlType == "Group" ||
1117 aCtrlType == "Subgroup" ||
1118 aCtrlType == "Radio" ||
1119 aCtrlType == "List" ||
1120 aCtrlType == "Edit" ||
1121 aCtrlType == "Range" ||
1122 aCtrlType == "Bool" )
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"
1130 || ( aCtrlType == "Subgroup" && nCurY < -250 && ! bIgnore )
1133 OUString aGroupTitle( aText );
1134 if( aCtrlType == "Subgroup" )
1135 aGroupTitle = ControllerProperties::getMoreString();
1137 // set size of current parent
1139 adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
1142 if( ! aText.getLength() )
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];
1152 nCurX = 20; // reset indent
1153 nCurY = 0; // reset Y
1155 aLeftColumn.clear();
1156 aRightColumn.clear();
1158 if( bAddSelectionCheckBox )
1160 addBool( pCurParent, nCurX, nCurY, 0,
1161 ControllerProperties::getPrintSelectionString(), bSelectionBoxEnabled,
1162 "PrintContent", bSelectionBoxChecked,
1163 aRightColumn, pControllerProperties, pCtrlTarget );
1164 bAddSelectionCheckBox = false;
1168 if( aCtrlType == "Subgroup" && pCurParent )
1170 bIgnoreSubgroup = bIgnore;
1174 addSubgroup( pCurParent, nCurY, aText );
1176 else if( bIgnoreSubgroup || bIgnore )
1180 else if( aCtrlType == "Bool" && pCurParent )
1183 PropertyValue* pVal = pController->getValue( aPropertyName );
1185 pVal->Value >>= bVal;
1186 addBool( pCurParent, nCurX, nCurY, nAttachOffset,
1187 aText, true, aPropertyName, bVal,
1188 aRightColumn, pControllerProperties, pCtrlTarget );
1190 else if( aCtrlType == "Radio" && pCurParent )
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 );
1203 else if( aCtrlType == "List" && pCurParent )
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 );
1215 else if( (aCtrlType == "Edit"
1216 || aCtrlType == "Range") && pCurParent )
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 );
1229 SAL_INFO( "vcl.osx.print", "Unsupported UI option \"" << aCtrlType << "\"");
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];
1264 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */