office-gobmx/vcl/osx/printaccessoryview.mm
Mike Kaganski 0fa827dbb2 Drop std::as_const from css::uno::Sequence iterations
Obsoleted by commit 2484de6728 (Remove
non-const Sequence::begin()/end() in internal code, 2021-10-15) and
commit fb3c04bd19 (Drop non-const
Sequence::operator[] in internal code, 2021-11-05).

Change-Id: Idbafef5d34c0d4771cbbf75b9db9712e504164cd
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162640
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
2024-01-27 12:43:48 +01:00

1264 lines
43 KiB
Text

/* -*- 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 : 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 : 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: */