summaryrefslogtreecommitdiffstats
path: root/vcl/osx/printaccessoryview.mm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/osx/printaccessoryview.mm
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/osx/printaccessoryview.mm')
-rw-r--r--vcl/osx/printaccessoryview.mm1264
1 files changed, 1264 insertions, 0 deletions
diff --git a/vcl/osx/printaccessoryview.mm b/vcl/osx/printaccessoryview.mm
new file mode 100644
index 000000000..95ace78d2
--- /dev/null
+++ b/vcl/osx/printaccessoryview.mm
@@ -0,0 +1,1264 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <vcl/print.hxx>
+#include <vcl/image.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/settings.hxx>
+
+#include <osx/printview.h>
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <printaccessoryview.hrc>
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+
+#include <map>
+#include <string_view>
+#include <utility>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::uno;
+
+namespace {
+
+class ControllerProperties;
+
+}
+
+@interface ControlTarget : NSObject
+{
+ ControllerProperties* mpController;
+}
+-(id)initWithControllerMap: (ControllerProperties*)pController;
+-(void)triggered:(id)pSender;
+-(void)triggeredNumeric:(id)pSender;
+-(void)dealloc;
+@end
+
+@interface AquaPrintPanelAccessoryController : NSViewController< NSPrintPanelAccessorizing >
+{
+ NSPrintOperation *mpPrintOperation;
+ vcl::PrinterController *mpPrinterController;
+ PrintAccessoryViewState *mpViewState;
+}
+
+-(void)forPrintOperation:(NSPrintOperation*)pPrintOp;
+-(void)withPrinterController:(vcl::PrinterController*)pController;
+-(void)withViewState:(PrintAccessoryViewState*)pState;
+
+-(NSPrintOperation*)printOperation;
+-(vcl::PrinterController*)printerController;
+-(PrintAccessoryViewState*)viewState;
+
+-(NSSet*)keyPathsForValuesAffectingPreview;
+-(NSArray*)localizedSummaryItems;
+
+-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount;
+
+@end
+
+@implementation AquaPrintPanelAccessoryController
+
+-(void)forPrintOperation:(NSPrintOperation*)pPrintOp
+ { mpPrintOperation = pPrintOp; }
+
+-(void)withPrinterController:(vcl::PrinterController*)pController
+ { mpPrinterController = pController; }
+
+-(void)withViewState:(PrintAccessoryViewState*)pState
+ { mpViewState = pState; }
+
+-(NSPrintOperation*)printOperation
+ { return mpPrintOperation; }
+
+-(vcl::PrinterController*)printerController
+ { return mpPrinterController; }
+
+-(PrintAccessoryViewState*)viewState
+ { return mpViewState; }
+
+-(NSSet*)keyPathsForValuesAffectingPreview
+{
+ return [ NSSet setWithObject:@"updatePrintOperation" ];
+}
+
+-(NSArray*)localizedSummaryItems
+{
+ return [ NSArray arrayWithObject:
+ [ NSDictionary dictionary ] ];
+}
+
+-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount
+{
+ // page range may be changed by option choice
+ sal_Int32 nPages = mpPrinterController->getFilteredPageCount();
+
+ mpViewState->bNeedRestart = false;
+ if( nPages != pLastPageCount )
+ {
+ #if OSL_DEBUG_LEVEL > 1
+ SAL_INFO( "vcl.osx.print", "number of pages changed" <<
+ " from " << pLastPageCount << " to " << nPages );
+ #endif
+ mpViewState->bNeedRestart = true;
+ }
+
+ NSTabView* pTabView = [[[self view] subviews] objectAtIndex:0];
+ NSTabViewItem* pItem = [pTabView selectedTabViewItem];
+ if( pItem )
+ mpViewState->nLastPage = [pTabView indexOfTabViewItem: pItem];
+ else
+ mpViewState->nLastPage = 0;
+
+ if( mpViewState->bNeedRestart )
+ {
+ // AppKit does not give a chance of changing the page count
+ // and don't let cancel the dialog either
+ // hack: send a cancel message to the modal window displaying views
+ NSWindow* pNSWindow = [NSApp modalWindow];
+ if( pNSWindow )
+ [pNSWindow cancelOperation: nil];
+ [[mpPrintOperation printInfo] setJobDisposition: NSPrintCancelJob];
+ }
+
+ return nPages;
+}
+
+@end
+
+namespace {
+
+class ControllerProperties
+{
+ std::map< int, OUString > maTagToPropertyName;
+ std::map< int, sal_Int32 > maTagToValueInt;
+ std::map< NSView*, NSView* > maViewPairMap;
+ std::vector< NSObject* > maViews;
+ int mnNextTag;
+ sal_Int32 mnLastPageCount;
+ AquaPrintPanelAccessoryController* mpAccessoryController;
+
+public:
+ ControllerProperties( AquaPrintPanelAccessoryController* i_pAccessoryController )
+ : mnNextTag( 0 )
+ , mnLastPageCount( [i_pAccessoryController printerController]->getFilteredPageCount() )
+ , mpAccessoryController( i_pAccessoryController )
+ {
+ static_assert( SAL_N_ELEMENTS(SV_PRINT_NATIVE_STRINGS) == 5, "resources not found" );
+ }
+
+ static OUString getMoreString()
+ {
+ return VclResId(SV_PRINT_NATIVE_STRINGS[3]);
+ }
+
+ static OUString getPrintSelectionString()
+ {
+ return VclResId(SV_PRINT_NATIVE_STRINGS[4]);
+ }
+
+ int addNameTag( const OUString& i_rPropertyName )
+ {
+ int nNewTag = mnNextTag++;
+ maTagToPropertyName[ nNewTag ] = i_rPropertyName;
+ return nNewTag;
+ }
+
+ int addNameAndValueTag( const OUString& i_rPropertyName, sal_Int32 i_nValue )
+ {
+ int nNewTag = mnNextTag++;
+ maTagToPropertyName[ nNewTag ] = i_rPropertyName;
+ maTagToValueInt[ nNewTag ] = i_nValue;
+ return nNewTag;
+ }
+
+ void addObservedControl( NSObject* i_pView )
+ {
+ maViews.push_back( i_pView );
+ }
+
+ void addViewPair( NSView* i_pLeft, NSView* i_pRight )
+ {
+ maViewPairMap[ i_pLeft ] = i_pRight;
+ maViewPairMap[ i_pRight ] = i_pLeft;
+ }
+
+ NSView* getPair( NSView* i_pLeft ) const
+ {
+ NSView* pRight = nil;
+ std::map< NSView*, NSView* >::const_iterator it = maViewPairMap.find( i_pLeft );
+ if( it != maViewPairMap.end() )
+ pRight = it->second;
+ return pRight;
+ }
+
+ void changePropertyWithIntValue( int i_nTag )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ std::map< int, sal_Int32 >::const_iterator value_it = maTagToValueInt.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() && value_it != maTagToValueInt.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= value_it->second;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= i_nValue;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithBoolValue( int i_nTag, bool i_bValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ // ugly
+ if( name_it->second == "PrintContent" )
+ pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0);
+ else
+ pVal->Value <<= i_bValue;
+
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithStringValue( int i_nTag, const OUString& i_rValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= i_rValue;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void updateEnableState()
+ {
+ for( std::vector< NSObject* >::iterator it = maViews.begin(); it != maViews.end(); ++it )
+ {
+ NSObject* pObj = *it;
+ NSControl* pCtrl = nil;
+ NSCell* pCell = nil;
+ if( [pObj isKindOfClass: [NSControl class]] )
+ pCtrl = static_cast<NSControl*>(pObj);
+ else if( [pObj isKindOfClass: [NSCell class]] )
+ pCell = static_cast<NSCell*>(pObj);
+
+ int nTag = pCtrl ? [pCtrl tag] :
+ pCell ? [pCell tag] :
+ -1;
+
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( nTag );
+ if( name_it != maTagToPropertyName.end() && name_it->second != "PrintContent" )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ bool bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO;
+ if( pCtrl )
+ {
+ [pCtrl setEnabled: bEnabled];
+ NSView* pOther = getPair( pCtrl );
+ if( pOther && [pOther isKindOfClass: [NSControl class]] )
+ [static_cast<NSControl*>(pOther) setEnabled: bEnabled];
+ }
+ else if( pCell )
+ [pCell setEnabled: bEnabled];
+ }
+ }
+ }
+
+};
+
+}
+
+static OUString filterAccelerator( OUString const & rText )
+{
+ OUStringBuffer aBuf( rText.getLength() );
+ for( sal_Int32 nIndex = 0; nIndex != -1; )
+ aBuf.append( o3tl::getToken( rText, 0, '~', nIndex ) );
+ return aBuf.makeStringAndClear();
+}
+
+@implementation ControlTarget
+
+-(id)initWithControllerMap: (ControllerProperties*)pController
+{
+ if( (self = [super init]) )
+ {
+ mpController = pController;
+ }
+ return self;
+}
+
+-(void)triggered:(id)pSender
+{
+ if( [pSender isMemberOfClass: [NSPopUpButton class]] )
+ {
+ NSPopUpButton* pBtn = static_cast<NSPopUpButton*>(pSender);
+ NSMenuItem* pSelected = [pBtn selectedItem];
+ if( pSelected )
+ {
+ int nTag = [pSelected tag];
+ mpController->changePropertyWithIntValue( nTag );
+ }
+ }
+ else if( [pSender isMemberOfClass: [NSButton class]] )
+ {
+ NSButton* pBtn = static_cast<NSButton*>(pSender);
+ int nTag = [pBtn tag];
+ mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSControlStateValueOn );
+ }
+ else if( [pSender isMemberOfClass: [NSMatrix class]] )
+ {
+ NSObject* pObj = [static_cast<NSMatrix*>(pSender) selectedCell];
+ if( [pObj isMemberOfClass: [NSButtonCell class]] )
+ {
+ NSButtonCell* pCell = static_cast<NSButtonCell*>(pObj);
+ int nTag = [pCell tag];
+ mpController->changePropertyWithIntValue( nTag );
+ }
+ }
+ else if( [pSender isMemberOfClass: [NSTextField class]] )
+ {
+ NSTextField* pField = static_cast<NSTextField*>(pSender);
+ int nTag = [pField tag];
+ OUString aValue = GetOUString( [pSender stringValue] );
+ mpController->changePropertyWithStringValue( nTag, aValue );
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported class" <<
+ ( [pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil" ) );
+ }
+ mpController->updateEnableState();
+}
+
+-(void)triggeredNumeric:(id)pSender
+{
+ if( [pSender isMemberOfClass: [NSTextField class]] )
+ {
+ NSTextField* pField = static_cast<NSTextField*>(pSender);
+ int nTag = [pField tag];
+ sal_Int64 nValue = [pField intValue];
+
+ NSView* pOther = mpController->getPair( pField );
+ if( pOther )
+ [static_cast<NSControl*>(pOther) setIntValue: nValue];
+
+ mpController->changePropertyWithIntValue( nTag, nValue );
+ }
+ else if( [pSender isMemberOfClass: [NSStepper class]] )
+ {
+ NSStepper* pStep = static_cast<NSStepper*>(pSender);
+ int nTag = [pStep tag];
+ sal_Int64 nValue = [pStep intValue];
+
+ NSView* pOther = mpController->getPair( pStep );
+ if( pOther )
+ [static_cast<NSControl*>(pOther) setIntValue: nValue];
+
+ mpController->changePropertyWithIntValue( nTag, nValue );
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported class" <<
+ ([pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil") );
+ }
+ mpController->updateEnableState();
+}
+
+-(void)dealloc
+{
+ delete mpController;
+ [super dealloc];
+}
+
+@end
+
+namespace {
+
+struct ColumnItem
+{
+ NSControl* pControl;
+ CGFloat nOffset;
+ NSControl* pSubControl;
+
+ ColumnItem( NSControl* i_pControl = nil, CGFloat i_nOffset = 0, NSControl* i_pSub = nil )
+ : pControl( i_pControl )
+ , nOffset( i_nOffset )
+ , pSubControl( i_pSub )
+ {}
+
+ CGFloat getWidth() const
+ {
+ CGFloat nWidth = 0;
+ if( pControl )
+ {
+ NSRect aCtrlRect = [pControl frame];
+ nWidth = aCtrlRect.size.width;
+ nWidth += nOffset;
+ if( pSubControl )
+ {
+ NSRect aSubRect = [pSubControl frame];
+ nWidth += aSubRect.size.width;
+ nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width);
+ }
+ }
+ return nWidth;
+ }
+};
+
+}
+
+static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize,
+ std::vector< ColumnItem >& rLeftColumn,
+ std::vector< ColumnItem >& rRightColumn
+ )
+{
+ // balance columns
+
+ // first get overall column widths
+ CGFloat nLeftWidth = 0;
+ CGFloat nRightWidth = 0;
+ for( size_t i = 0; i < rLeftColumn.size(); i++ )
+ {
+ CGFloat nW = rLeftColumn[i].getWidth();
+ if( nW > nLeftWidth )
+ nLeftWidth = nW;
+ }
+ for( size_t i = 0; i < rRightColumn.size(); i++ )
+ {
+ CGFloat nW = rRightColumn[i].getWidth();
+ if( nW > nRightWidth )
+ nRightWidth = nW;
+ }
+
+ // right align left column
+ for( size_t i = 0; i < rLeftColumn.size(); i++ )
+ {
+ if( rLeftColumn[i].pControl )
+ {
+ NSRect aCtrlRect = [rLeftColumn[i].pControl frame];
+ CGFloat nX = nLeftWidth - aCtrlRect.size.width;
+ if( rLeftColumn[i].pSubControl )
+ {
+ NSRect aSubRect = [rLeftColumn[i].pSubControl frame];
+ nX -= aSubRect.size.width + (aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width));
+ aSubRect.origin.x = nLeftWidth - aSubRect.size.width;
+ [rLeftColumn[i].pSubControl setFrame: aSubRect];
+ }
+ aCtrlRect.origin.x = nX;
+ [rLeftColumn[i].pControl setFrame: aCtrlRect];
+ }
+ }
+
+ // left align right column
+ for( size_t i = 0; i < rRightColumn.size(); i++ )
+ {
+ if( rRightColumn[i].pControl )
+ {
+ NSRect aCtrlRect = [rRightColumn[i].pControl frame];
+ CGFloat nX = nLeftWidth + 3;
+ if( rRightColumn[i].pSubControl )
+ {
+ NSRect aSubRect = [rRightColumn[i].pSubControl frame];
+ aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x;
+ [rRightColumn[i].pSubControl setFrame: aSubRect];
+ }
+ aCtrlRect.origin.x = nX;
+ [rRightColumn[i].pControl setFrame: aCtrlRect];
+ }
+ }
+
+ NSArray* pSubViews = [pNSView subviews];
+ unsigned int nViews = [pSubViews count];
+ NSRect aUnion = NSZeroRect;
+
+ // get the combined frame of all subviews
+ for( unsigned int n = 0; n < nViews; n++ )
+ {
+ aUnion = NSUnionRect( aUnion, [[pSubViews objectAtIndex: n] frame] );
+ }
+
+ // move everything so it will fit
+ for( unsigned int n = 0; n < nViews; n++ )
+ {
+ NSView* pCurSubView = [pSubViews objectAtIndex: n];
+ NSRect aFrame = [pCurSubView frame];
+ aFrame.origin.x -= aUnion.origin.x - 5;
+ aFrame.origin.y -= aUnion.origin.y - 5;
+ [pCurSubView setFrame: aFrame];
+ }
+
+ // resize the view itself
+ aUnion.size.height += 10;
+ aUnion.size.width += 20;
+ [pNSView setFrameSize: aUnion.size];
+
+ if( aUnion.size.width > rMaxSize.width )
+ rMaxSize.width = aUnion.size.width;
+ if( aUnion.size.height > rMaxSize.height )
+ rMaxSize.height = aUnion.size.height;
+}
+
+static void adjustTabViews( NSTabView* pTabView, NSSize aTabSize )
+{
+ // loop over all contained tab pages
+ NSArray* pTabbedViews = [pTabView tabViewItems];
+ int nViews = [pTabbedViews count];
+ for( int i = 0; i < nViews; i++ )
+ {
+ NSTabViewItem* pItem = static_cast<NSTabViewItem*>([pTabbedViews objectAtIndex: i]);
+ NSView* pNSView = [pItem view];
+ if( pNSView )
+ {
+ NSRect aRect = [pNSView frame];
+ double nDiff = aTabSize.height - aRect.size.height;
+ aRect.size = aTabSize;
+ [pNSView setFrame: aRect];
+
+ NSArray* pSubViews = [pNSView subviews];
+ unsigned int nSubViews = [pSubViews count];
+
+ // move everything up
+ for( unsigned int n = 0; n < nSubViews; n++ )
+ {
+ NSView* pCurSubView = [pSubViews objectAtIndex: n];
+ NSRect aFrame = [pCurSubView frame];
+ aFrame.origin.y += nDiff;
+ // give separators the correct width
+ // separators are currently the only NSBoxes we use
+ if( [pCurSubView isMemberOfClass: [NSBox class]] )
+ {
+ aFrame.size.width = aTabSize.width - aFrame.origin.x - 10;
+ }
+ [pCurSubView setFrame: aFrame];
+ }
+ }
+ }
+}
+
+static NSControl* createLabel( const OUString& i_rText )
+{
+ NSString* pText = CreateNSString( i_rText );
+ NSRect aTextRect = { NSZeroPoint, {20, 15} };
+ NSTextField* pTextView = [[NSTextField alloc] initWithFrame: aTextRect];
+ [pTextView setFont: [NSFont controlContentFontOfSize: 0]];
+ [pTextView setEditable: NO];
+ [pTextView setSelectable: NO];
+ [pTextView setDrawsBackground: NO];
+ [pTextView setBordered: NO];
+ [pTextView setStringValue: pText];
+ [pTextView sizeToFit];
+ [pText release];
+ return pTextView;
+}
+
+static sal_Int32 findBreak( const OUString& i_rText, sal_Int32 i_nPos )
+{
+ sal_Int32 nRet = i_rText.getLength();
+ Reference< i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() );
+ if( xBI.is() )
+ {
+ i18n::Boundary aBoundary =
+ xBI->getWordBoundary( i_rText, i_nPos,
+ Application::GetSettings().GetLanguageTag().getLocale(),
+ i18n::WordType::ANYWORD_IGNOREWHITESPACES,
+ true );
+ nRet = aBoundary.endPos;
+ }
+ return nRet;
+}
+
+static void linebreakCell( NSCell* pBtn, const OUString& i_rText )
+{
+ NSString* pText = CreateNSString( i_rText );
+ [pBtn setTitle: pText];
+ [pText release];
+ NSSize aSize = [pBtn cellSize];
+ if( aSize.width > 280 )
+ {
+ // need two lines
+ sal_Int32 nLen = i_rText.getLength();
+ sal_Int32 nIndex = nLen / 2;
+ nIndex = findBreak( i_rText, nIndex );
+ if( nIndex < nLen )
+ {
+ OUStringBuffer aBuf( i_rText );
+ aBuf[nIndex] = '\n';
+ pText = CreateNSString( aBuf.makeStringAndClear() );
+ [pBtn setTitle: pText];
+ [pText release];
+ }
+ }
+}
+
+static void addSubgroup( NSView* pCurParent, CGFloat& rCurY, const OUString& rText )
+{
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+ NSRect aTextRect = [pTextView frame];
+ // move to nCurY
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ NSRect aSepRect = { { aTextRect.size.width + 1, aTextRect.origin.y }, { 100, 6 } };
+ NSBox* pBox = [[NSBox alloc] initWithFrame: aSepRect];
+ [pBox setBoxType: NSBoxSeparator];
+ [pCurParent addSubview: [pBox autorelease]];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+}
+
+static void addBool( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ const OUString& rText, bool bEnabled,
+ const OUString& rProperty, bool bValue,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ NSRect aCheckRect = { { rCurX + nAttachOffset, 0 }, { 0, 15 } };
+ NSButton* pBtn = [[NSButton alloc] initWithFrame: aCheckRect];
+ [pBtn setButtonType: NSButtonTypeSwitch];
+ [pBtn setState: bValue ? NSControlStateValueOn : NSControlStateValueOff];
+ if( ! bEnabled )
+ [pBtn setEnabled: NO];
+ linebreakCell( [pBtn cell], rText );
+ [pBtn sizeToFit];
+
+ rRightColumn.push_back( ColumnItem( pBtn ) );
+
+ // connect target
+ [pBtn setTarget: pCtrlTarget];
+ [pBtn setAction: @selector(triggered:)];
+ int nTag = pControllerProperties->addNameTag( rProperty );
+ pControllerProperties->addObservedControl( pBtn );
+ [pBtn setTag: nTag];
+
+ aCheckRect = [pBtn frame];
+ // #i115837# add a murphy factor; it can apparently occasionally happen
+ // that sizeToFit does not a perfect job and that the button linebreaks again
+ // if - and only if - there is already a '\n' contained in the text and the width
+ // is minimally of
+ aCheckRect.size.width += 1;
+
+ // move to rCurY
+ aCheckRect.origin.y = rCurY - aCheckRect.size.height;
+ [pBtn setFrame: aCheckRect];
+
+ [pCurParent addSubview: [pBtn autorelease]];
+
+ // update rCurY
+ rCurY = aCheckRect.origin.y - 5;
+}
+
+static void addRadio( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ const OUString& rText,
+ const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ CGFloat nOff = 0;
+ if( rText.getLength() )
+ {
+ // add a label
+ NSControl* pTextView = createLabel( rText );
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX + nAttachOffset;
+ [pCurParent addSubview: [pTextView autorelease]];
+
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+
+ // move to nCurY
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+
+ // indent the radio group relative to the text
+ // nOff = 20;
+ }
+
+ // setup radio matrix
+ NSButtonCell* pProto = [[NSButtonCell alloc] init];
+
+ NSRect aRadioRect = { { rCurX + nOff, 0 },
+ { 280 - rCurX,
+ static_cast<CGFloat>(5*rChoices.getLength()) } };
+ [pProto setTitle: @"RadioButtonGroup"];
+ [pProto setButtonType: NSButtonTypeRadio];
+ NSMatrix* pMatrix = [[NSMatrix alloc] initWithFrame: aRadioRect
+ mode: NSRadioModeMatrix
+ prototype: static_cast<NSCell*>(pProto)
+ numberOfRows: rChoices.getLength()
+ numberOfColumns: 1];
+ // set individual titles
+ NSArray* pCells = [pMatrix cells];
+ for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
+ {
+ NSCell* pCell = [pCells objectAtIndex: m];
+ linebreakCell( pCell, filterAccelerator( rChoices[m] ) );
+ // connect target and action
+ [pCell setTarget: pCtrlTarget];
+ [pCell setAction: @selector(triggered:)];
+ int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
+ pControllerProperties->addObservedControl( pCell );
+ [pCell setTag: nTag];
+ // set current selection
+ if( nSelectValue == m )
+ [pMatrix selectCellAtRow: m column: 0];
+ }
+ [pMatrix sizeToFit];
+ aRadioRect = [pMatrix frame];
+
+ // move it down, so it comes to the correct position
+ aRadioRect.origin.y = rCurY - aRadioRect.size.height;
+ [pMatrix setFrame: aRadioRect];
+ [pCurParent addSubview: [pMatrix autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pMatrix ) );
+
+ // update nCurY
+ rCurY = aRadioRect.origin.y - 5;
+
+ [pProto release];
+}
+
+static void addList( NSView* pCurParent, CGFloat& rCurX, CGFloat& rCurY, CGFloat /*nAttachOffset*/,
+ const OUString& rText,
+ const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ // don't indent attached lists, looks bad in the existing cases
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX /* + nAttachOffset*/;
+
+ // don't indent attached lists, looks bad in the existing cases
+ NSRect aBtnRect = { { rCurX /*+ nAttachOffset*/ + aTextRect.size.width, 0 }, { 0, 15 } };
+ NSPopUpButton* pBtn = [[NSPopUpButton alloc] initWithFrame: aBtnRect pullsDown: NO];
+
+ // iterate options
+ for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
+ {
+ NSString* pItemText = CreateNSString( rChoices[m] );
+ [pBtn addItemWithTitle: pItemText];
+ NSMenuItem* pItem = [pBtn itemWithTitle: pItemText];
+ int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
+ [pItem setTag: nTag];
+ [pItemText release];
+ }
+
+ [pBtn selectItemAtIndex: nSelectValue];
+
+ // add the button to observed controls for enabled state changes
+ // also add a tag just for this purpose
+ pControllerProperties->addObservedControl( pBtn );
+ [pBtn setTag: pControllerProperties->addNameTag( rProperty )];
+
+ [pBtn sizeToFit];
+ [pCurParent addSubview: [pBtn autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pBtn ) );
+
+ // connect target and action
+ [pBtn setTarget: pCtrlTarget];
+ [pBtn setAction: @selector(triggered:)];
+
+ // move to nCurY
+ aBtnRect = [pBtn frame];
+ aBtnRect.origin.y = rCurY - aBtnRect.size.height;
+ [pBtn setFrame: aBtnRect];
+
+ // align label
+ aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2;
+ [pTextView setFrame: aTextRect];
+
+ // update rCurY
+ rCurY = aBtnRect.origin.y - 5;
+}
+
+static void addEdit( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ std::u16string_view rCtrlType,
+ const OUString& rText,
+ const OUString& rProperty, const PropertyValue* pValue,
+ sal_Int64 nMinValue, sal_Int64 nMaxValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ CGFloat nOff = 0;
+ if( rText.getLength() )
+ {
+ // add a label
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+
+ // move to nCurY
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX + nAttachOffset;
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+
+ // and set the offset for the real edit field
+ nOff = aTextRect.size.width + 5;
+ }
+
+ NSRect aFieldRect = { { rCurX + nOff + nAttachOffset, 0 }, { 100, 25 } };
+ NSTextField* pFieldView = [[NSTextField alloc] initWithFrame: aFieldRect];
+ [pFieldView setEditable: YES];
+ [pFieldView setSelectable: YES];
+ [pFieldView setDrawsBackground: YES];
+ [pFieldView sizeToFit]; // FIXME: this does nothing
+ [pCurParent addSubview: [pFieldView autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pFieldView ) );
+
+ // add the field to observed controls for enabled state changes
+ // also add a tag just for this purpose
+ pControllerProperties->addObservedControl( pFieldView );
+ int nTag = pControllerProperties->addNameTag( rProperty );
+ [pFieldView setTag: nTag];
+ // pControllerProperties->addNamedView( pFieldView, aPropertyName );
+
+ // move to nCurY
+ aFieldRect.origin.y = rCurY - aFieldRect.size.height;
+ [pFieldView setFrame: aFieldRect];
+
+ if( rCtrlType == u"Range" )
+ {
+ // add a stepper control
+ NSRect aStepFrame = { { aFieldRect.origin.x + aFieldRect.size.width + 5,
+ aFieldRect.origin.y },
+ { 15, aFieldRect.size.height } };
+ NSStepper* pStep = [[NSStepper alloc] initWithFrame: aStepFrame];
+ [pStep setIncrement: 1];
+ [pStep setValueWraps: NO];
+ [pStep setTag: nTag];
+ [pCurParent addSubview: [pStep autorelease]];
+
+ rRightColumn.back().pSubControl = pStep;
+
+ pControllerProperties->addObservedControl( pStep );
+ [pStep setTarget: pCtrlTarget];
+ [pStep setAction: @selector(triggered:)];
+
+ // constrain the text field to decimal numbers
+ NSNumberFormatter* pFormatter = [[NSNumberFormatter alloc] init];
+ [pFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
+ [pFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
+ [pFormatter setAllowsFloats: NO];
+ [pFormatter setMaximumFractionDigits: 0];
+ if( nMinValue != nMaxValue )
+ {
+ [pFormatter setMinimum: [[NSNumber numberWithInt: nMinValue] autorelease]];
+ [pStep setMinValue: nMinValue];
+ [pFormatter setMaximum: [[NSNumber numberWithInt: nMaxValue] autorelease]];
+ [pStep setMaxValue: nMaxValue];
+ }
+ [pFieldView setFormatter: pFormatter];
+
+ sal_Int64 nSelectVal = 0;
+ if( pValue && pValue->Value.hasValue() )
+ pValue->Value >>= nSelectVal;
+
+ [pFieldView setIntValue: nSelectVal];
+ [pStep setIntValue: nSelectVal];
+
+ pControllerProperties->addViewPair( pFieldView, pStep );
+ // connect target and action
+ [pFieldView setTarget: pCtrlTarget];
+ [pFieldView setAction: @selector(triggeredNumeric:)];
+ [pStep setTarget: pCtrlTarget];
+ [pStep setAction: @selector(triggeredNumeric:)];
+ }
+ else
+ {
+ // connect target and action
+ [pFieldView setTarget: pCtrlTarget];
+ [pFieldView setAction: @selector(triggered:)];
+
+ if( pValue && pValue->Value.hasValue() )
+ {
+ OUString aValue;
+ pValue->Value >>= aValue;
+ if( aValue.getLength() )
+ {
+ NSString* pText = CreateNSString( aValue );
+ [pFieldView setStringValue: pText];
+ [pText release];
+ }
+ }
+ }
+
+ // update nCurY
+ rCurY = aFieldRect.origin.y - 5;
+}
+
+@implementation AquaPrintAccessoryView
+
++(NSObject*)setupPrinterPanel: (NSPrintOperation*)pOp
+ withController: (vcl::PrinterController*)pController
+ withState: (PrintAccessoryViewState*)pState
+{
+ const Sequence< PropertyValue >& rOptions( pController->getUIOptions() );
+ if( rOptions.getLength() == 0 )
+ return nil;
+
+ NSRect aViewFrame = { NSZeroPoint, { 600, 400 } };
+ NSRect aTabViewFrame = aViewFrame;
+
+ NSView* pAccessoryView = [[NSView alloc] initWithFrame: aViewFrame];
+ NSTabView* pTabView = [[NSTabView alloc] initWithFrame: aTabViewFrame];
+ [pAccessoryView addSubview: [pTabView autorelease]];
+
+ // create the accessory controller
+ AquaPrintPanelAccessoryController* pAccessoryController =
+ [[AquaPrintPanelAccessoryController alloc] initWithNibName: nil bundle: nil];
+ [pAccessoryController setView: [pAccessoryView autorelease]];
+ [pAccessoryController forPrintOperation: pOp];
+ [pAccessoryController withPrinterController: pController];
+ [pAccessoryController withViewState: pState];
+
+ NSView* pCurParent = nullptr;
+ CGFloat nCurY = 0;
+ CGFloat nCurX = 0;
+ NSSize aMaxTabSize = NSZeroSize;
+
+ ControllerProperties* pControllerProperties = new ControllerProperties( pAccessoryController );
+ ControlTarget* pCtrlTarget = [[ControlTarget alloc] initWithControllerMap: pControllerProperties];
+
+ std::vector< ColumnItem > aLeftColumn, aRightColumn;
+
+ // ugly:
+ // prepend a "selection" checkbox if the properties have such a selection in PrintContent
+ bool bAddSelectionCheckBox = false, bSelectionBoxEnabled = false, bSelectionBoxChecked = false;
+
+ for( const PropertyValue & prop : rOptions )
+ {
+ Sequence< beans::PropertyValue > aOptProp;
+ prop.Value >>= aOptProp;
+
+ OUString aCtrlType;
+ OUString aPropertyName;
+ Sequence< OUString > aChoices;
+ Sequence< sal_Bool > aChoicesDisabled;
+ sal_Int32 aSelectionChecked = 0;
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if( rEntry.Name == "ControlType" )
+ {
+ rEntry.Value >>= aCtrlType;
+ }
+ else if( rEntry.Name == "Choices" )
+ {
+ rEntry.Value >>= aChoices;
+ }
+ else if( rEntry.Name == "ChoicesDisabled" )
+ {
+ rEntry.Value >>= aChoicesDisabled;
+ }
+ else if( rEntry.Name == "Property" )
+ {
+ PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ if( aPropertyName == "PrintContent" )
+ aVal.Value >>= aSelectionChecked;
+ }
+ }
+ if( aCtrlType == "Radio" &&
+ aPropertyName == "PrintContent" &&
+ aChoices.getLength() > 2 )
+ {
+ bAddSelectionCheckBox = true;
+ bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2];
+ bSelectionBoxChecked = (aSelectionChecked==2);
+ break;
+ }
+ }
+
+ for( const PropertyValue & prop : rOptions )
+ {
+ Sequence< beans::PropertyValue > aOptProp;
+ prop.Value >>= aOptProp;
+
+ // extract ui element
+ OUString aCtrlType;
+ OUString aText;
+ OUString aPropertyName;
+ OUString aGroupHint;
+ Sequence< OUString > aChoices;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ CGFloat nAttachOffset = 0;
+ bool bIgnore = false;
+
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if( rEntry.Name == "Text" )
+ {
+ rEntry.Value >>= aText;
+ aText = filterAccelerator( aText );
+ }
+ else if( rEntry.Name == "ControlType" )
+ {
+ rEntry.Value >>= aCtrlType;
+ }
+ else if( rEntry.Name == "Choices" )
+ {
+ rEntry.Value >>= aChoices;
+ }
+ else if( rEntry.Name == "Property" )
+ {
+ PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ }
+ else if( rEntry.Name == "MinValue" )
+ {
+ rEntry.Value >>= nMinValue;
+ }
+ else if( rEntry.Name == "MaxValue" )
+ {
+ rEntry.Value >>= nMaxValue;
+ }
+ else if( rEntry.Name == "AttachToDependency" )
+ {
+ nAttachOffset = 20;
+ }
+ else if( rEntry.Name == "InternalUIOnly" )
+ {
+ bool bValue = false;
+ rEntry.Value >>= bValue;
+ bIgnore = bValue;
+ }
+ else if( rEntry.Name == "GroupingHint" )
+ {
+ rEntry.Value >>= aGroupHint;
+ }
+ }
+
+ if( aCtrlType == "Group" ||
+ aCtrlType == "Subgroup" ||
+ aCtrlType == "Radio" ||
+ aCtrlType == "List" ||
+ aCtrlType == "Edit" ||
+ aCtrlType == "Range" ||
+ aCtrlType == "Bool" )
+ {
+ bool bIgnoreSubgroup = false;
+
+ // with `setAccessoryView' method only one accessory view can be set
+ // so create this single accessory view as tabbed for grouping
+ if( aCtrlType == "Group"
+ || ! pCurParent
+ || ( aCtrlType == "Subgroup" && nCurY < -250 && ! bIgnore )
+ )
+ {
+ OUString aGroupTitle( aText );
+ if( aCtrlType == "Subgroup" )
+ aGroupTitle = ControllerProperties::getMoreString();
+
+ // set size of current parent
+ if( pCurParent )
+ adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
+
+ // new tab item
+ if( ! aText.getLength() )
+ aText = "OOo";
+ NSString* pLabel = CreateNSString( aGroupTitle );
+ NSTabViewItem* pItem = [[NSTabViewItem alloc] initWithIdentifier: pLabel ];
+ [pItem setLabel: pLabel];
+ [pTabView addTabViewItem: pItem];
+ pCurParent = [[NSView alloc] initWithFrame: aTabViewFrame];
+ [pItem setView: pCurParent];
+ [pLabel release];
+
+ nCurX = 20; // reset indent
+ nCurY = 0; // reset Y
+ // clear columns
+ aLeftColumn.clear();
+ aRightColumn.clear();
+
+ if( bAddSelectionCheckBox )
+ {
+ addBool( pCurParent, nCurX, nCurY, 0,
+ ControllerProperties::getPrintSelectionString(), bSelectionBoxEnabled,
+ "PrintContent", bSelectionBoxChecked,
+ aRightColumn, pControllerProperties, pCtrlTarget );
+ bAddSelectionCheckBox = false;
+ }
+ }
+
+ if( aCtrlType == "Subgroup" && pCurParent )
+ {
+ bIgnoreSubgroup = bIgnore;
+ if( bIgnore )
+ continue;
+
+ addSubgroup( pCurParent, nCurY, aText );
+ }
+ else if( bIgnoreSubgroup || bIgnore )
+ {
+ continue;
+ }
+ else if( aCtrlType == "Bool" && pCurParent )
+ {
+ bool bVal = false;
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ if( pVal )
+ pVal->Value >>= bVal;
+ addBool( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, true, aPropertyName, bVal,
+ aRightColumn, pControllerProperties, pCtrlTarget );
+ }
+ else if( aCtrlType == "Radio" && pCurParent )
+ {
+ // get currently selected value
+ sal_Int32 nSelectVal = 0;
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nSelectVal;
+
+ addRadio( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, aPropertyName, aChoices, nSelectVal,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ else if( aCtrlType == "List" && pCurParent )
+ {
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ sal_Int32 aSelectVal = 0;
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= aSelectVal;
+
+ addList( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, aPropertyName, aChoices, aSelectVal,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ else if( (aCtrlType == "Edit"
+ || aCtrlType == "Range") && pCurParent )
+ {
+ // current value
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ addEdit( pCurParent, nCurX, nCurY, nAttachOffset,
+ aCtrlType, aText, aPropertyName, pVal,
+ nMinValue, nMaxValue,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported UI option \"" << aCtrlType << "\"");
+ }
+ }
+
+ pControllerProperties->updateEnableState();
+ adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
+
+ // now reposition everything again so it is upper bound
+ adjustTabViews( pTabView, aMaxTabSize );
+
+ // find the minimum needed tab size
+ NSSize aTabCtrlSize = [pTabView minimumSize];
+ aTabCtrlSize.height += aMaxTabSize.height + 10;
+ if( aTabCtrlSize.width < aMaxTabSize.width + 10 )
+ aTabCtrlSize.width = aMaxTabSize.width + 10;
+ [pTabView setFrameSize: aTabCtrlSize];
+ aViewFrame.size.width = aTabCtrlSize.width + aTabViewFrame.origin.x;
+ aViewFrame.size.height = aTabCtrlSize.height + aTabViewFrame.origin.y;
+ [pAccessoryView setFrameSize: aViewFrame.size];
+
+ // get the print panel
+ NSPrintPanel* pPrintPanel = [pOp printPanel];
+ [pPrintPanel setOptions: [pPrintPanel options] | NSPrintPanelShowsPreview];
+ // add the accessory controller to the panel
+ [pPrintPanel addAccessoryController: [pAccessoryController autorelease]];
+
+ // set the current selected tab item
+ if( pState->nLastPage >= 0 && pState->nLastPage < [pTabView numberOfTabViewItems] )
+ [pTabView selectTabViewItemAtIndex: pState->nLastPage];
+
+ return pCtrlTarget;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */