/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#include "precompiled_vcl.hxx"

#include "vcl/print.hxx"
#include "vcl/prndlg.hxx"
#include "vcl/svapp.hxx"
#include "vcl/svdata.hxx"
#include "vcl/salinst.hxx"
#include "vcl/salprn.hxx"
#include "vcl/svids.hrc"
#include "vcl/metaact.hxx"
#include "vcl/msgbox.hxx"
#include "vcl/configsettings.hxx"

#include "tools/urlobj.hxx"

#include "com/sun/star/ui/dialogs/XFilePicker.hpp"
#include "com/sun/star/ui/dialogs/XFilterManager.hpp"
#include "com/sun/star/ui/dialogs/TemplateDescription.hpp"
#include "com/sun/star/ui/dialogs/ExecutableDialogResults.hpp"
#include "com/sun/star/view/DuplexMode.hpp"
#include "com/sun/star/lang/XMultiServiceFactory.hpp"
#include "com/sun/star/awt/Size.hpp"
#include "comphelper/processfactory.hxx"

#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>

using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::beans;
using namespace vcl;

class ImplPageCache
{
    struct CacheEntry
    {
        GDIMetaFile                 aPage;
        PrinterController::PageSize aSize;
    };

    std::vector< CacheEntry >  maPages;
    std::vector< sal_Int32 >    maPageNumbers;
    std::vector< sal_Int32 >    maCacheRanking;

    static const sal_Int32 nCacheSize = 6;

    void updateRanking( sal_Int32 nLastHit )
    {
        if( maCacheRanking[0] != nLastHit )
        {
            for( sal_Int32 i = nCacheSize-1; i > 0; i-- )
                maCacheRanking[i] = maCacheRanking[i-1];
            maCacheRanking[0] = nLastHit;
        }
    }

public:
    ImplPageCache()
    : maPages( nCacheSize )
    , maPageNumbers( nCacheSize, -1 )
    , maCacheRanking( nCacheSize )
    {
        for( sal_Int32 i = 0; i < nCacheSize; i++ )
            maCacheRanking[i] = nCacheSize - i - 1;
    }

    // caution: does not ensure uniqueness
    void insert( sal_Int32 i_nPageNo, const GDIMetaFile& i_rPage, const PrinterController::PageSize& i_rSize )
    {
        sal_Int32 nReplacePage = maCacheRanking.back();
        maPages[ nReplacePage ].aPage = i_rPage;
        maPages[ nReplacePage ].aSize = i_rSize;
        maPageNumbers[ nReplacePage ] = i_nPageNo;
        // cache insertion means in our case, the page was just queried
        // so update the ranking
        updateRanking( nReplacePage );
    }

    // caution: bad algorithm; should there ever be reason to increase the cache size beyond 6
    // this needs to be urgently rewritten. However do NOT increase the cache size lightly,
    // whole pages can be rather memory intensive
    bool get( sal_Int32 i_nPageNo, GDIMetaFile& o_rPageFile, PrinterController::PageSize& o_rSize )
    {
        for( sal_Int32 i = 0; i < nCacheSize; ++i )
        {
            if( maPageNumbers[i] == i_nPageNo )
            {
                updateRanking( i );
                o_rPageFile = maPages[i].aPage;
                o_rSize = maPages[i].aSize;
                return true;
            }
        }
        return false;
    }

    void invalidate()
    {
        for( sal_Int32 i = 0; i < nCacheSize; ++i )
        {
            maPageNumbers[i] = -1;
            maPages[i].aPage.Clear();
            maCacheRanking[i] = nCacheSize - i - 1;
        }
    }
};

class vcl::ImplPrinterControllerData
{
public:
    struct ControlDependency
    {
        rtl::OUString       maDependsOnName;
        sal_Int32           mnDependsOnEntry;

        ControlDependency() : mnDependsOnEntry( -1 ) {}
    };

    typedef boost::unordered_map< rtl::OUString, size_t, rtl::OUStringHash > PropertyToIndexMap;
    typedef boost::unordered_map< rtl::OUString, ControlDependency, rtl::OUStringHash > ControlDependencyMap;

    boost::shared_ptr<Printer>                                  mpPrinter;
    Sequence< PropertyValue >                                   maUIOptions;
    std::vector< PropertyValue >                                maUIProperties;
    std::vector< bool >                                         maUIPropertyEnabled;
    PropertyToIndexMap                                          maPropertyToIndex;
    Link                                                        maOptionChangeHdl;
    ControlDependencyMap                                        maControlDependencies;
    sal_Bool                                                    mbFirstPage;
    sal_Bool                                                    mbLastPage;
    sal_Bool                                                    mbReversePageOrder;
    view::PrintableState                                        meJobState;

    vcl::PrinterController::MultiPageSetup                      maMultiPage;

    vcl::PrintProgressDialog*                                   mpProgress;

    ImplPageCache                                               maPageCache;

    // set by user through printer config dialog
    // if set, pages are centered and trimmed onto the fixed page
    Size                                                        maFixedPageSize;
    sal_Int32                                                   mnDefaultPaperBin;
    sal_Int32                                                   mnFixedPaperBin;

    ImplPrinterControllerData() :
        mbFirstPage( sal_True ),
        mbLastPage( sal_False ),
        mbReversePageOrder( sal_False ),
        meJobState( view::PrintableState_JOB_STARTED ),
        mpProgress( NULL ),
        mnDefaultPaperBin( -1 ),
        mnFixedPaperBin( -1 )
    {}
    ~ImplPrinterControllerData() { delete mpProgress; }

    Size getRealPaperSize( const Size& i_rPageSize ) const
    {
        if( maFixedPageSize.Width() > 0 && maFixedPageSize.Height() > 0 )
            return maFixedPageSize;
        if( maMultiPage.nRows * maMultiPage.nColumns > 1 )
            return maMultiPage.aPaperSize;
        return i_rPageSize;
    }
    bool isFixedPageSize() const
    { return maFixedPageSize.Width() != 0 && maFixedPageSize.Height() != 0; }
    PrinterController::PageSize modifyJobSetup( const Sequence< PropertyValue >& i_rProps );
};

PrinterController::PrinterController()
    : mpImplData( new ImplPrinterControllerData )
{
}

PrinterController::PrinterController( const boost::shared_ptr<Printer>& i_pPrinter )
    : mpImplData( new ImplPrinterControllerData )
{
    mpImplData->mpPrinter = i_pPrinter;
}

static rtl::OUString queryFile( Printer* pPrinter )
{
    rtl::OUString aResult;

    uno::Reference< lang::XMultiServiceFactory > xFactory( ::comphelper::getProcessServiceFactory() );
    if( xFactory.is() )
    {
        uno::Sequence< uno::Any > aTempl( 1 );
        aTempl.getArray()[0] <<= ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION;
        uno::Reference< ui::dialogs::XFilePicker > xFilePicker(
            xFactory->createInstanceWithArguments(
                ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.ui.dialogs.FilePicker" ) ),
                aTempl ), uno::UNO_QUERY );
        DBG_ASSERT( xFilePicker.is(), "could not get FilePicker service" );

        uno::Reference< ui::dialogs::XFilterManager > xFilterMgr( xFilePicker, uno::UNO_QUERY );
        if( xFilePicker.is() && xFilterMgr.is() )
        {
            try
            {
#ifdef UNX
                // add PostScript and PDF
                bool bPS = true, bPDF = true;
                if( pPrinter )
                {
                    if( pPrinter->GetCapabilities( PRINTER_CAPABILITIES_PDF ) )
                        bPS = false;
                    else
                        bPDF = false;
                }
                if( bPS )
                    xFilterMgr->appendFilter( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PostScript" ) ), ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "*.ps" ) ) );
                if( bPDF )
                    xFilterMgr->appendFilter( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Portable Document Format" ) ), ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "*.pdf" ) ) );
#elif defined WNT
        (void)pPrinter;
                xFilterMgr->appendFilter( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "*.PRN" ) ), ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "*.prn" ) ) );
#endif
                // add arbitrary files
                xFilterMgr->appendFilter( String( VclResId( SV_STDTEXT_ALLFILETYPES ) ), ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "*.*" ) ) );
            }
            catch( lang::IllegalArgumentException rExc )
            {
                DBG_ERRORFILE( "caught IllegalArgumentException when registering filter\n" );
            }

            if( xFilePicker->execute() == ui::dialogs::ExecutableDialogResults::OK )
            {
                uno::Sequence< ::rtl::OUString > aPathSeq( xFilePicker->getFiles() );
                INetURLObject aObj( aPathSeq[0] );
                aResult = aObj.PathToFileName();
            }
        }
    }
    return aResult;
}

struct PrintJobAsync
{
    boost::shared_ptr<PrinterController>  mpController;
    JobSetup                            maInitSetup;

    PrintJobAsync( const boost::shared_ptr<PrinterController>& i_pController,
                   const JobSetup& i_rInitSetup
                   )
    : mpController( i_pController ), maInitSetup( i_rInitSetup )
    {}

    DECL_LINK( ExecJob, void* );
};

IMPL_LINK( PrintJobAsync, ExecJob, void*, EMPTYARG )
{
    Printer::ImplPrintJob( mpController, maInitSetup );

    // clean up, do not access members after this
    delete this;

    return 0;
}

void Printer::PrintJob( const boost::shared_ptr<PrinterController>& i_pController,
                        const JobSetup& i_rInitSetup
                        )
{
    sal_Bool bSynchronous = sal_False;
    beans::PropertyValue* pVal = i_pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Wait" ) ) );
    if( pVal )
        pVal->Value >>= bSynchronous;

    if( bSynchronous )
        ImplPrintJob( i_pController, i_rInitSetup );
    else
    {
        PrintJobAsync* pAsync = new PrintJobAsync( i_pController, i_rInitSetup );
        Application::PostUserEvent( LINK( pAsync, PrintJobAsync, ExecJob ) );
    }
}

void Printer::ImplPrintJob( const boost::shared_ptr<PrinterController>& i_pController,
                            const JobSetup& i_rInitSetup
                            )
{
    boost::shared_ptr<PrinterController> pController( i_pController );

    // check if there is a default printer; if not, show an error box (if appropriate)
    if( GetDefaultPrinterName().Len() == 0  )
    {
        if(  pController->isShowDialogs()
             // && ! pController->isDirectPrint()
           )
        {
            ErrorBox aBox( NULL, VclResId( SV_PRINT_NOPRINTERWARNING ) );
            aBox.Execute();
        }
        pController->setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsDirect" ) ),
                               makeAny( sal_False ) );
    }

    // setup printer

    // if no specific printer is already set, create one

    // #i108686#
    // in case of a UI (platform independent or system dialog) print job, make the printer persistent over jobs
    // however if no printer was already set by the print job's originator,
    // and this is an API job, then use the system default location (because
    // this is the only sensible default available if the user has no means of changing
    // the destination
    if( ! pController->getPrinter() )
    {
        rtl::OUString aPrinterName( i_rInitSetup.GetPrinterName() );
        if( ! aPrinterName.getLength() && pController->isShowDialogs() && ! pController->isDirectPrint() )
        {
            // get printer name from configuration
            SettingsConfigItem* pItem = SettingsConfigItem::get();
            aPrinterName = pItem->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintDialog" ) ),
                                            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "LastPrinterUsed" ) ) );
        }

        boost::shared_ptr<Printer> pPrinter( new Printer( aPrinterName ) );
        pController->setPrinter( pPrinter );
    }

    // reset last page property
    i_pController->setLastPage( sal_False );

    // update "PageRange" property inferring from other properties:
    // case 1: "Pages" set from UNO API ->
    //         setup "Print Selection" and insert "PageRange" attribute
    // case 2: "All pages" is selected
    //         update "Page range" attribute to have a sensible default,
    //         but leave "All" as selected

    // "Pages" attribute from API is now equivalent to "PageRange"
    // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
    // Argh ! That sure needs cleaning up
    beans::PropertyValue* pContentVal = i_pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintRange" ) ) );
    if( ! pContentVal )
        pContentVal = i_pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintContent" ) ) );

    // case 1: UNO API has set "Pages"
    beans::PropertyValue* pPagesVal = i_pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Pages" ) ) );
    if( pPagesVal )
    {
        rtl::OUString aPagesVal;
        pPagesVal->Value >>= aPagesVal;
        if( aPagesVal.getLength() )
        {
            // "Pages" attribute from API is now equivalent to "PageRange"
            // AND "PrintContent" = 1 except calc where it is "PrintRange" = 1
            // Argh ! That sure needs cleaning up
            if( pContentVal )
            {
                pContentVal->Value = makeAny( sal_Int32( 1 ) );
                i_pController->setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PageRange" ) ), pPagesVal->Value );
            }
        }
    }
    // case 2: is "All" selected ?
    else if( pContentVal )
    {
        sal_Int32 nContent = -1;
        if( pContentVal->Value >>= nContent )
        {
            if( nContent == 0 )
            {
                sal_Int32 nPages = i_pController->getPageCount();
                if( nPages > 0 )
                {
                    rtl::OUStringBuffer aBuf( 32 );
                    aBuf.appendAscii( "1" );
                    if( nPages > 1 )
                    {
                        aBuf.appendAscii( "-" );
                        aBuf.append( nPages );
                    }
                    i_pController->setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PageRange" ) ), makeAny( aBuf.makeStringAndClear() ) );
                }
            }
        }
    }

    beans::PropertyValue* pReverseVal = i_pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintReverse" ) ) );
    if( pReverseVal )
    {
        sal_Bool bReverse = sal_False;
        pReverseVal->Value >>= bReverse;
        pController->setReversePrint( bReverse );
    }

    // in direct print case check whether there is anything to print.
    // if not, show an errorbox (if appropriate)
    if( pController->isShowDialogs() && pController->isDirectPrint() )
    {
        if( pController->getFilteredPageCount() == 0 )
        {
            ErrorBox aBox( NULL, VclResId( SV_PRINT_NOCONTENT ) );
            aBox.Execute();
            return;
        }
    }

    // check if the printer brings up its own dialog
    // in that case leave the work to that dialog
    if( ! pController->getPrinter()->GetCapabilities( PRINTER_CAPABILITIES_EXTERNALDIALOG ) &&
        ! pController->isDirectPrint() &&
        pController->isShowDialogs()
        )
    {
        try
        {
            PrintDialog aDlg( NULL, i_pController );
            if( ! aDlg.Execute() )
            {
                GDIMetaFile aPageFile;
                i_pController->abortJob();
                return;
            }
            if( aDlg.isPrintToFile() )
            {
                rtl::OUString aFile = queryFile( pController->getPrinter().get() );
                if( ! aFile.getLength() )
                {
                    i_pController->abortJob();
                    return;
                }
                pController->setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "LocalFileName" ) ),
                                       makeAny( aFile ) );
            }
            else if( aDlg.isSingleJobs() )
            {
                pController->setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintCollateAsSingleJobs" ) ),
                                       makeAny( sal_True ) );
            }
        }
        catch( std::bad_alloc& )
        {
        }
    }

    pController->pushPropertiesToPrinter();

    rtl::OUString aJobName;
    beans::PropertyValue* pJobNameVal = pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "JobName" ) ) );
    if( pJobNameVal )
        pJobNameVal->Value >>= aJobName;

    pController->getPrinter()->StartJob( String( aJobName ), pController );

    pController->jobFinished( pController->getJobState() );
}

bool Printer::StartJob( const rtl::OUString& i_rJobName, boost::shared_ptr<vcl::PrinterController>& i_pController )
{
    mnError = PRINTER_OK;

    if ( IsDisplayPrinter() )
        return FALSE;

    if ( IsJobActive() || IsPrinting() )
        return FALSE;

    ULONG   nCopies = mnCopyCount;
    bool    bCollateCopy = mbCollateCopy;
    bool    bUserCopy = FALSE;

    if ( nCopies > 1 )
    {
        ULONG nDevCopy;

        if ( bCollateCopy )
            nDevCopy = GetCapabilities( PRINTER_CAPABILITIES_COLLATECOPIES );
        else
            nDevCopy = GetCapabilities( PRINTER_CAPABILITIES_COPIES );

        // need to do copies by hand ?
        if ( nCopies > nDevCopy )
        {
            bUserCopy = TRUE;
            nCopies = 1;
            bCollateCopy = FALSE;
        }
    }
    else
        bCollateCopy = FALSE;


    ImplSVData* pSVData = ImplGetSVData();
    mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter );

    if ( !mpPrinter )
        return FALSE;

    sal_Bool bSinglePrintJobs = sal_False;
    beans::PropertyValue* pSingleValue = i_pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintCollateAsSingleJobs" ) ) );
    if( pSingleValue )
    {
        pSingleValue->Value >>= bSinglePrintJobs;
    }

    // remark: currently it is still possible to use EnablePrintFile and
    // SetPrintFileName to redirect printout into file
    // it can be argued that those methods should be removed in favor
    // of only using the LocalFileName property
    beans::PropertyValue* pFileValue = i_pController->getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "LocalFileName" ) ) );
    if( pFileValue )
    {
        rtl::OUString aFile;
        pFileValue->Value >>= aFile;
        if( aFile.getLength() )
        {
            mbPrintFile = TRUE;
            maPrintFile = aFile;
            bSinglePrintJobs = sal_False;
        }
    }

    XubString* pPrintFile = NULL;
    if ( mbPrintFile )
        pPrintFile = &maPrintFile;

    maJobName               = i_rJobName;
    mnCurPage               = 1;
    mnCurPrintPage          = 1;
    mbPrinting              = TRUE;
    if( ImplGetSVData()->maGDIData.mbPrinterPullModel )
    {
        mbJobActive             = TRUE;
        // sallayer does all necessary page printing
        // and also handles showing a dialog
        // that also means it must call jobStarted when the dialog is finished
        // it also must set the JobState of the Controller
        if( mpPrinter->StartJob( pPrintFile,
                                 i_rJobName,
                                 Application::GetDisplayName(),
                                 maJobSetup.ImplGetConstData(),
                                 *i_pController ) )
        {
            EndJob();
        }
        else
        {
            mnError = ImplSalPrinterErrorCodeToVCL( mpPrinter->GetErrorCode() );
            if ( !mnError )
                mnError = PRINTER_GENERALERROR;
            pSVData->mpDefInst->DestroyPrinter( mpPrinter );
            mnCurPage           = 0;
            mnCurPrintPage      = 0;
            mbPrinting          = FALSE;
            mpPrinter = NULL;

            return false;
        }
    }
    else
    {
        // possibly a dialog has been shown
        // now the real job starts
        i_pController->setJobState( view::PrintableState_JOB_STARTED );
        i_pController->jobStarted();

        int nJobs = 1;
        int nOuterRepeatCount = 1;
        int nInnerRepeatCount = 1;
        if( bUserCopy )
        {
            if( mbCollateCopy )
                nOuterRepeatCount = mnCopyCount;
            else
                nInnerRepeatCount = mnCopyCount;
        }
        if( bSinglePrintJobs )
        {
            nJobs = mnCopyCount;
            nCopies = 1;
            nOuterRepeatCount = nInnerRepeatCount = 1;
        }

        for( int nJobIteration = 0; nJobIteration < nJobs; nJobIteration++ )
        {
            bool bError = false, bAborted = false;
            if( mpPrinter->StartJob( pPrintFile,
                                     i_rJobName,
                                     Application::GetDisplayName(),
                                     nCopies,
                                     bCollateCopy,
                                     i_pController->isDirectPrint(),
                                     maJobSetup.ImplGetConstData() ) )
            {
                mbJobActive             = TRUE;
                i_pController->createProgressDialog();
                int nPages = i_pController->getFilteredPageCount();
                for( int nOuterIteration = 0; nOuterIteration < nOuterRepeatCount && ! bAborted; nOuterIteration++ )
                {
                    for( int nPage = 0; nPage < nPages && ! bAborted; nPage++ )
                    {
                        for( int nInnerIteration = 0; nInnerIteration < nInnerRepeatCount && ! bAborted; nInnerIteration++ )
                        {
                            if( nPage == nPages-1 &&
                                nOuterIteration == nOuterRepeatCount-1 &&
                                nInnerIteration == nInnerRepeatCount-1 &&
                                nJobIteration == nJobs-1 )
                            {
                                i_pController->setLastPage( sal_True );
                            }
                            i_pController->printFilteredPage( nPage );
                            if( i_pController->isProgressCanceled() )
                            {
                                i_pController->abortJob();
                                bAborted = true;
                            }
                        }
                    }
                    // FIXME: duplex ?
                }
                EndJob();

                if( nJobIteration < nJobs-1 )
                {
                    mpPrinter = pSVData->mpDefInst->CreatePrinter( mpInfoPrinter );

                    if ( mpPrinter )
                    {
                        maJobName               = i_rJobName;
                        mnCurPage               = 1;
                        mnCurPrintPage          = 1;
                        mbPrinting              = TRUE;
                    }
                    else
                        bError = true;
                }
            }
            else
                bError = true;

            if( bError )
            {
                mnError = ImplSalPrinterErrorCodeToVCL( mpPrinter->GetErrorCode() );
                if ( !mnError )
                    mnError = PRINTER_GENERALERROR;
                i_pController->setJobState( mnError == PRINTER_ABORT
                                            ? view::PrintableState_JOB_ABORTED
                                            : view::PrintableState_JOB_FAILED );
                if( mpPrinter )
                    pSVData->mpDefInst->DestroyPrinter( mpPrinter );
                mnCurPage           = 0;
                mnCurPrintPage      = 0;
                mbPrinting          = FALSE;
                mpPrinter = NULL;

                return false;
            }
        }

        if( i_pController->getJobState() == view::PrintableState_JOB_STARTED )
            i_pController->setJobState( view::PrintableState_JOB_SPOOLED );
    }

    // make last used printer persistent for UI jobs
    if( i_pController->isShowDialogs() && ! i_pController->isDirectPrint() )
    {
        SettingsConfigItem* pItem = SettingsConfigItem::get();
        pItem->setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintDialog" ) ),
                         rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "LastPrinterUsed" ) ),
                         GetName()
                         );
    }

    return true;
}

PrinterController::~PrinterController()
{
    delete mpImplData;
}

view::PrintableState PrinterController::getJobState() const
{
    return mpImplData->meJobState;
}

void PrinterController::setJobState( view::PrintableState i_eState )
{
    mpImplData->meJobState = i_eState;
}

const boost::shared_ptr<Printer>& PrinterController::getPrinter() const
{
    return mpImplData->mpPrinter;
}

void PrinterController::setPrinter( const boost::shared_ptr<Printer>& i_rPrinter )
{
    mpImplData->mpPrinter = i_rPrinter;
    setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Name" ) ),
              makeAny( rtl::OUString( i_rPrinter->GetName() ) ) );
    mpImplData->mnDefaultPaperBin = mpImplData->mpPrinter->GetPaperBin();
    mpImplData->mnFixedPaperBin = -1;
}

bool PrinterController::setupPrinter( Window* i_pParent )
{
    bool bRet = false;
    if( mpImplData->mpPrinter.get() )
    {
        // get old data
        Size aPaperSize( mpImplData->mpPrinter->PixelToLogic(
            mpImplData->mpPrinter->GetPaperSizePixel(), MapMode( MAP_100TH_MM ) ) );
        USHORT nPaperBin = mpImplData->mpPrinter->GetPaperBin();

        // call driver setup
        bRet = mpImplData->mpPrinter->Setup( i_pParent );
        if( bRet )
        {
            // was papersize or bin  overridden ? if so we need to take action
            Size aNewPaperSize( mpImplData->mpPrinter->PixelToLogic(
                mpImplData->mpPrinter->GetPaperSizePixel(), MapMode( MAP_100TH_MM ) ) );
            USHORT nNewPaperBin = mpImplData->mpPrinter->GetPaperBin();
            if( aNewPaperSize != aPaperSize || nNewPaperBin != nPaperBin )
            {
                mpImplData->maFixedPageSize = aNewPaperSize;
                mpImplData->maPageCache.invalidate();
                awt::Size aOverrideSize;
                aOverrideSize.Width = aNewPaperSize.Width();
                aOverrideSize.Height = aNewPaperSize.Height();
                setValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "OverridePageSize" ) ),
                          makeAny( aOverrideSize ) );
                mpImplData->mnFixedPaperBin = nNewPaperBin;
            }
        }
    }
    return bRet;
}

PrinterController::PageSize vcl::ImplPrinterControllerData::modifyJobSetup( const Sequence< PropertyValue >& i_rProps )
{
    PrinterController::PageSize aPageSize;
    aPageSize.aSize = mpPrinter->GetPaperSize();
    awt::Size aSetSize, aIsSize;
    sal_Int32 nPaperBin = mnDefaultPaperBin;
    for( sal_Int32 nProperty = 0, nPropertyCount = i_rProps.getLength(); nProperty < nPropertyCount; ++nProperty )
    {
        if( i_rProps[ nProperty ].Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "PreferredPageSize" ) ) )
        {
            i_rProps[ nProperty ].Value >>= aSetSize;
        }
        else if( i_rProps[ nProperty ].Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "PageSize" ) ) )
        {
            i_rProps[ nProperty ].Value >>= aIsSize;
        }
        else if( i_rProps[ nProperty ].Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "PageIncludesNonprintableArea" ) ) )
        {
            sal_Bool bVal = sal_False;
            i_rProps[ nProperty ].Value >>= bVal;
            aPageSize.bFullPaper = static_cast<bool>(bVal);
        }
        else if( i_rProps[ nProperty ].Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "PrinterPaperTray" ) ) )
        {
            sal_Int32 nBin = -1;
            i_rProps[ nProperty ].Value >>= nBin;
            if( nBin >= 0 && nBin < mpPrinter->GetPaperBinCount() )
                nPaperBin = nBin;
        }
    }

    Size aCurSize( mpPrinter->GetPaperSize() );
    if( aSetSize.Width && aSetSize.Height )
    {
        Size aSetPaperSize( aSetSize.Width, aSetSize.Height );
        Size aRealPaperSize( getRealPaperSize( aSetPaperSize ) );
        if( aRealPaperSize != aCurSize )
            aIsSize = aSetSize;
    }

    if( aIsSize.Width && aIsSize.Height )
    {
        aPageSize.aSize.Width() = aIsSize.Width;
        aPageSize.aSize.Height() = aIsSize.Height;

        Size aRealPaperSize( getRealPaperSize( aPageSize.aSize ) );
        if( aRealPaperSize != aCurSize )
            mpPrinter->SetPaperSizeUser( aRealPaperSize, ! isFixedPageSize() );
    }

    if( nPaperBin != -1 && nPaperBin != mpPrinter->GetPaperBin() )
        mpPrinter->SetPaperBin( nPaperBin );

    return aPageSize;
}

int PrinterController::getPageCountProtected() const
{
    const MapMode aMapMode( MAP_100TH_MM );

    mpImplData->mpPrinter->Push();
    mpImplData->mpPrinter->SetMapMode( aMapMode );
    int nPages = getPageCount();
    mpImplData->mpPrinter->Pop();
    return nPages;
}

Sequence< beans::PropertyValue > PrinterController::getPageParametersProtected( int i_nPage ) const
{
    const MapMode aMapMode( MAP_100TH_MM );

    mpImplData->mpPrinter->Push();
    mpImplData->mpPrinter->SetMapMode( aMapMode );
    Sequence< beans::PropertyValue > aResult( getPageParameters( i_nPage ) );
    mpImplData->mpPrinter->Pop();
    return aResult;
}

PrinterController::PageSize PrinterController::getPageFile( int i_nUnfilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache )
{
    // update progress if necessary
    if( mpImplData->mpProgress )
    {
        // do nothing if printing is canceled
        if( mpImplData->mpProgress->isCanceled() )
            return PrinterController::PageSize();
        mpImplData->mpProgress->tick();
        Application::Reschedule( true );
    }

    if( i_bMayUseCache )
    {
        PrinterController::PageSize aPageSize;
        if( mpImplData->maPageCache.get( i_nUnfilteredPage, o_rMtf, aPageSize ) )
        {
            return aPageSize;
        }
    }
    else
        mpImplData->maPageCache.invalidate();

    o_rMtf.Clear();

    // get page parameters
    Sequence< PropertyValue > aPageParm( getPageParametersProtected( i_nUnfilteredPage ) );
    const MapMode aMapMode( MAP_100TH_MM );

    mpImplData->mpPrinter->Push();
    mpImplData->mpPrinter->SetMapMode( aMapMode );

    // modify job setup if necessary
    PrinterController::PageSize aPageSize = mpImplData->modifyJobSetup( aPageParm );

    o_rMtf.SetPrefSize( aPageSize.aSize );
    o_rMtf.SetPrefMapMode( aMapMode );

    mpImplData->mpPrinter->EnableOutput( FALSE );

    o_rMtf.Record( mpImplData->mpPrinter.get() );

    printPage( i_nUnfilteredPage );

    o_rMtf.Stop();
    o_rMtf.WindStart();
    mpImplData->mpPrinter->Pop();

    if( i_bMayUseCache )
        mpImplData->maPageCache.insert( i_nUnfilteredPage, o_rMtf, aPageSize );

    // reset "FirstPage" property to false now we've gotten at least our first one
    mpImplData->mbFirstPage = sal_False;

    return aPageSize;
}

static void appendSubPage( GDIMetaFile& o_rMtf, const Rectangle& i_rClipRect, GDIMetaFile& io_rSubPage, bool i_bDrawBorder )
{
    // intersect all clipregion actions with our clip rect
    io_rSubPage.WindStart();
    io_rSubPage.Clip( i_rClipRect );

    // save gstate
    o_rMtf.AddAction( new MetaPushAction( PUSH_ALL ) );

    // clip to page rect
    o_rMtf.AddAction( new MetaClipRegionAction( Region( i_rClipRect ), TRUE ) );

    // append the subpage
    io_rSubPage.WindStart();
    io_rSubPage.Play( o_rMtf );

    // restore gstate
    o_rMtf.AddAction( new MetaPopAction() );

    // draw a border
    if( i_bDrawBorder )
    {
        // save gstate
        o_rMtf.AddAction( new MetaPushAction( PUSH_LINECOLOR | PUSH_FILLCOLOR | PUSH_CLIPREGION | PUSH_MAPMODE ) );
        o_rMtf.AddAction( new MetaMapModeAction( MapMode( MAP_100TH_MM ) ) );

        Rectangle aBorderRect( i_rClipRect );
        o_rMtf.AddAction( new MetaLineColorAction( Color( COL_BLACK ), TRUE ) );
        o_rMtf.AddAction( new MetaFillColorAction( Color( COL_TRANSPARENT ), FALSE ) );
        o_rMtf.AddAction( new MetaRectAction( aBorderRect ) );

        // restore gstate
        o_rMtf.AddAction( new MetaPopAction() );
    }
}

PrinterController::PageSize PrinterController::getFilteredPageFile( int i_nFilteredPage, GDIMetaFile& o_rMtf, bool i_bMayUseCache )
{
    const MultiPageSetup& rMPS( mpImplData->maMultiPage );
    int nSubPages = rMPS.nRows * rMPS.nColumns;
    if( nSubPages < 1 )
        nSubPages = 1;

    // reverse sheet order
    if( mpImplData->mbReversePageOrder )
    {
        int nDocPages = getFilteredPageCount();
        i_nFilteredPage = nDocPages - 1 - i_nFilteredPage;
    }

    // there is no filtering to be done (and possibly the page size of the
    // original page is to be set), when N-Up is "neutral" that is there is
    // only one subpage and the margins are 0
    if( nSubPages == 1 &&
        rMPS.nLeftMargin == 0 && rMPS.nRightMargin == 0 &&
        rMPS.nTopMargin == 0 && rMPS.nBottomMargin == 0 )
    {
        PrinterController::PageSize aPageSize = getPageFile( i_nFilteredPage, o_rMtf, i_bMayUseCache );
        Size aPaperSize = mpImplData->getRealPaperSize( aPageSize.aSize );
        mpImplData->mpPrinter->SetMapMode( MapMode( MAP_100TH_MM ) );
        mpImplData->mpPrinter->SetPaperSizeUser( aPaperSize, ! mpImplData->isFixedPageSize() );
        if( aPaperSize != aPageSize.aSize )
        {
            // user overridden page size, center Metafile
            o_rMtf.WindStart();
            long nDX = (aPaperSize.Width() - aPageSize.aSize.Width()) / 2;
            long nDY = (aPaperSize.Height() - aPageSize.aSize.Height()) / 2;
            o_rMtf.Move( nDX, nDY, mpImplData->mpPrinter->ImplGetDPIX(), mpImplData->mpPrinter->ImplGetDPIY() );
            o_rMtf.WindStart();
            o_rMtf.SetPrefSize( aPaperSize );
            aPageSize.aSize = aPaperSize;
        }
        return aPageSize;
    }

    // set last page property really only on the very last page to be rendered
    // that is on the last subpage of a NUp run
    sal_Bool bIsLastPage = mpImplData->mbLastPage;
    mpImplData->mbLastPage = sal_False;

    Size aPaperSize( mpImplData->getRealPaperSize( mpImplData->maMultiPage.aPaperSize ) );

    // multi page area: page size minus margins + one time spacing right and down
    // the added spacing is so each subpage can be calculated including its spacing
    Size aMPArea( aPaperSize );
    aMPArea.Width()  -= rMPS.nLeftMargin + rMPS.nRightMargin;
    aMPArea.Width()  += rMPS.nHorizontalSpacing;
    aMPArea.Height() -= rMPS.nTopMargin + rMPS.nBottomMargin;
    aMPArea.Height() += rMPS.nVerticalSpacing;

    // determine offsets
    long nAdvX = aMPArea.Width() / rMPS.nColumns;
    long nAdvY = aMPArea.Height() / rMPS.nRows;

    // determine size of a "cell" subpage, leave a little space around pages
    Size aSubPageSize( nAdvX - rMPS.nHorizontalSpacing, nAdvY - rMPS.nVerticalSpacing );

    o_rMtf.Clear();
    o_rMtf.SetPrefSize( aPaperSize );
    o_rMtf.SetPrefMapMode( MapMode( MAP_100TH_MM ) );
    o_rMtf.AddAction( new MetaMapModeAction( MapMode( MAP_100TH_MM ) ) );

    int nDocPages = getPageCountProtected();
    for( int nSubPage = 0; nSubPage < nSubPages; nSubPage++ )
    {
        // map current sub page to real page
        int nPage = (i_nFilteredPage * nSubPages + nSubPage) / rMPS.nRepeat;
        if( nSubPage == nSubPages-1 ||
            nPage == nDocPages-1 )
        {
            mpImplData->mbLastPage = bIsLastPage;
        }
        if( nPage >= 0 && nPage < nDocPages )
        {
            GDIMetaFile aPageFile;
            PrinterController::PageSize aPageSize = getPageFile( nPage, aPageFile, i_bMayUseCache );
            if( aPageSize.aSize.Width() && aPageSize.aSize.Height() )
            {
                long nCellX = 0, nCellY = 0;
                switch( rMPS.nOrder )
                {
                case PrinterController::LRTB:
                    nCellX = (nSubPage % rMPS.nColumns);
                    nCellY = (nSubPage / rMPS.nColumns);
                    break;
                case PrinterController::TBLR:
                    nCellX = (nSubPage / rMPS.nRows);
                    nCellY = (nSubPage % rMPS.nRows);
                    break;
                }
                // scale the metafile down to a sub page size
                double fScaleX = double(aSubPageSize.Width())/double(aPageSize.aSize.Width());
                double fScaleY = double(aSubPageSize.Height())/double(aPageSize.aSize.Height());
                double fScale  = std::min( fScaleX, fScaleY );
                aPageFile.Scale( fScale, fScale );
                aPageFile.WindStart();

                // move the subpage so it is centered in its "cell"
                long nOffX = (aSubPageSize.Width() - long(double(aPageSize.aSize.Width()) * fScale)) / 2;
                long nOffY = (aSubPageSize.Height() - long(double(aPageSize.aSize.Height()) * fScale)) / 2;
                long nX = rMPS.nLeftMargin + nOffX + nAdvX * nCellX;
                long nY = rMPS.nTopMargin + nOffY + nAdvY * nCellY;
                aPageFile.Move( nX, nY, mpImplData->mpPrinter->ImplGetDPIX(), mpImplData->mpPrinter->ImplGetDPIY() );
                aPageFile.WindStart();
                // calculate border rectangle
                Rectangle aSubPageRect( Point( nX, nY ),
                                        Size( long(double(aPageSize.aSize.Width())*fScale),
                                              long(double(aPageSize.aSize.Height())*fScale) ) );

                // append subpage to page
                appendSubPage( o_rMtf, aSubPageRect, aPageFile, rMPS.bDrawBorder );
            }
        }
    }
    o_rMtf.WindStart();

    // subsequent getPageFile calls have changed the paper, reset it to current value
    mpImplData->mpPrinter->SetMapMode( MapMode( MAP_100TH_MM ) );
    mpImplData->mpPrinter->SetPaperSizeUser( aPaperSize, ! mpImplData->isFixedPageSize() );

    return PrinterController::PageSize( aPaperSize, true );
}

int PrinterController::getFilteredPageCount()
{
    int nDiv = mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns;
    if( nDiv < 1 )
        nDiv = 1;
    return (getPageCountProtected() * mpImplData->maMultiPage.nRepeat + (nDiv-1)) / nDiv;
}

ULONG PrinterController::removeTransparencies( GDIMetaFile& i_rIn, GDIMetaFile& o_rOut )
{
    ULONG nRestoreDrawMode = mpImplData->mpPrinter->GetDrawMode();
    sal_Int32 nMaxBmpDPIX = mpImplData->mpPrinter->ImplGetDPIX();
    sal_Int32 nMaxBmpDPIY = mpImplData->mpPrinter->ImplGetDPIY();

    const PrinterOptions&   rPrinterOptions = mpImplData->mpPrinter->GetPrinterOptions();

    static const sal_Int32 OPTIMAL_BMP_RESOLUTION = 300;
    static const sal_Int32 NORMAL_BMP_RESOLUTION  = 200;


    if( rPrinterOptions.IsReduceBitmaps() )
    {
        // calculate maximum resolution for bitmap graphics
        if( PRINTER_BITMAP_OPTIMAL == rPrinterOptions.GetReducedBitmapMode() )
        {
            nMaxBmpDPIX = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIX );
            nMaxBmpDPIY = std::min( sal_Int32(OPTIMAL_BMP_RESOLUTION), nMaxBmpDPIY );
        }
        else if( PRINTER_BITMAP_NORMAL == rPrinterOptions.GetReducedBitmapMode() )
        {
            nMaxBmpDPIX = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIX );
            nMaxBmpDPIY = std::min( sal_Int32(NORMAL_BMP_RESOLUTION), nMaxBmpDPIY );
        }
        else
        {
            nMaxBmpDPIX = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIX );
            nMaxBmpDPIY = std::min( sal_Int32(rPrinterOptions.GetReducedBitmapResolution()), nMaxBmpDPIY );
        }
    }

    // convert to greysacles
    if( rPrinterOptions.IsConvertToGreyscales() )
    {
        mpImplData->mpPrinter->SetDrawMode( mpImplData->mpPrinter->GetDrawMode() |
                                            ( DRAWMODE_GRAYLINE | DRAWMODE_GRAYFILL | DRAWMODE_GRAYTEXT |
                                              DRAWMODE_GRAYBITMAP | DRAWMODE_GRAYGRADIENT ) );
    }

    // disable transparency output
    if( rPrinterOptions.IsReduceTransparency() && ( PRINTER_TRANSPARENCY_NONE == rPrinterOptions.GetReducedTransparencyMode() ) )
    {
        mpImplData->mpPrinter->SetDrawMode( mpImplData->mpPrinter->GetDrawMode() | DRAWMODE_NOTRANSPARENCY );
    }

    Color aBg( COL_TRANSPARENT ); // default: let RemoveTransparenciesFromMetaFile do its own background logic
    if( mpImplData->maMultiPage.nRows * mpImplData->maMultiPage.nColumns > 1 )
    {
        // in N-Up printing we have no "page" background operation
        // we also have no way to determine the paper color
        // so let's go for white, which will kill 99.9% of the real cases
        aBg = Color( COL_WHITE );
    }
    mpImplData->mpPrinter->RemoveTransparenciesFromMetaFile( i_rIn, o_rOut, nMaxBmpDPIX, nMaxBmpDPIY,
                                                             rPrinterOptions.IsReduceTransparency(),
                                                             rPrinterOptions.GetReducedTransparencyMode() == PRINTER_TRANSPARENCY_AUTO,
                                                             rPrinterOptions.IsReduceBitmaps() && rPrinterOptions.IsReducedBitmapIncludesTransparency(),
                                                             aBg
                                                             );
    return nRestoreDrawMode;
}

void PrinterController::printFilteredPage( int i_nPage )
{
    if( mpImplData->meJobState != view::PrintableState_JOB_STARTED )
        return;

    GDIMetaFile aPageFile;
    PrinterController::PageSize aPageSize = getFilteredPageFile( i_nPage, aPageFile );

    if( mpImplData->mpProgress )
    {
        // do nothing if printing is canceled
        if( mpImplData->mpProgress->isCanceled() )
        {
            setJobState( view::PrintableState_JOB_ABORTED );
            return;
        }
    }

    // in N-Up printing set the correct page size
    mpImplData->mpPrinter->SetMapMode( MAP_100TH_MM );
    // aPageSize was filtered through mpImplData->getRealPaperSize already by getFilteredPageFile()
    mpImplData->mpPrinter->SetPaperSizeUser( aPageSize.aSize, ! mpImplData->isFixedPageSize() );
    if( mpImplData->mnFixedPaperBin != -1 &&
        mpImplData->mpPrinter->GetPaperBin() != mpImplData->mnFixedPaperBin )
    {
        mpImplData->mpPrinter->SetPaperBin( mpImplData->mnFixedPaperBin );
    }

    // if full paper is meant to be used, move the output to accomodate for pageoffset
    if( aPageSize.bFullPaper )
    {
        Point aPageOffset( mpImplData->mpPrinter->GetPageOffset() );
        aPageFile.WindStart();
        aPageFile.Move( -aPageOffset.X(), -aPageOffset.Y(), mpImplData->mpPrinter->ImplGetDPIX(), mpImplData->mpPrinter->ImplGetDPIY() );
    }

    GDIMetaFile aCleanedFile;
    ULONG nRestoreDrawMode = removeTransparencies( aPageFile, aCleanedFile );

    mpImplData->mpPrinter->EnableOutput( TRUE );

    // actually print the page
    mpImplData->mpPrinter->ImplStartPage();

    mpImplData->mpPrinter->Push();
    aCleanedFile.WindStart();
    aCleanedFile.Play( mpImplData->mpPrinter.get() );
    mpImplData->mpPrinter->Pop();

    mpImplData->mpPrinter->ImplEndPage();

    mpImplData->mpPrinter->SetDrawMode( nRestoreDrawMode );
}

void PrinterController::jobStarted()
{
}

void PrinterController::jobFinished( view::PrintableState )
{
}

void PrinterController::abortJob()
{
    setJobState( view::PrintableState_JOB_ABORTED );
    // applications (well, sw) depend on a page request with "IsLastPage" = true
    // to free resources, else they (well, sw) will crash eventually
    setLastPage( sal_True );
    delete mpImplData->mpProgress;
    mpImplData->mpProgress = NULL;
    GDIMetaFile aMtf;
    getPageFile( 0, aMtf, false );
}

void PrinterController::setLastPage( sal_Bool i_bLastPage )
{
    mpImplData->mbLastPage = i_bLastPage;
}

void PrinterController::setReversePrint( sal_Bool i_bReverse )
{
    mpImplData->mbReversePageOrder = i_bReverse;
}

bool PrinterController::getReversePrint() const
{
    return mpImplData->mbReversePageOrder;
}

Sequence< PropertyValue > PrinterController::getJobProperties( const Sequence< PropertyValue >& i_rMergeList ) const
{
    boost::unordered_set< rtl::OUString, rtl::OUStringHash > aMergeSet;
    size_t nResultLen = size_t(i_rMergeList.getLength()) + mpImplData->maUIProperties.size() + 3;
    for( int i = 0; i < i_rMergeList.getLength(); i++ )
        aMergeSet.insert( i_rMergeList[i].Name );

    Sequence< PropertyValue > aResult( nResultLen );
    for( int i = 0; i < i_rMergeList.getLength(); i++ )
        aResult[i] = i_rMergeList[i];
    int nCur = i_rMergeList.getLength();
    for( size_t i = 0; i < mpImplData->maUIProperties.size(); i++ )
    {
        if( aMergeSet.find( mpImplData->maUIProperties[i].Name ) == aMergeSet.end() )
            aResult[nCur++] = mpImplData->maUIProperties[i];
    }
    // append IsFirstPage
    if( aMergeSet.find( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsFirstPage" ) ) ) == aMergeSet.end() )
    {
        PropertyValue aVal;
        aVal.Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsFirstPage" ) );
        aVal.Value <<= mpImplData->mbFirstPage;
        aResult[nCur++] = aVal;
    }
    // append IsLastPage
    if( aMergeSet.find( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsLastPage" ) ) ) == aMergeSet.end() )
    {
        PropertyValue aVal;
        aVal.Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsLastPage" ) );
        aVal.Value <<= mpImplData->mbLastPage;
        aResult[nCur++] = aVal;
    }
    // append IsPrinter
    if( aMergeSet.find( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsPrinter" ) ) ) == aMergeSet.end() )
    {
        PropertyValue aVal;
        aVal.Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsPrinter" ) );
        aVal.Value <<= sal_True;
        aResult[nCur++] = aVal;
    }
    aResult.realloc( nCur );
    return aResult;
}

const Sequence< beans::PropertyValue >& PrinterController::getUIOptions() const
{
    return mpImplData->maUIOptions;
}

beans::PropertyValue* PrinterController::getValue( const rtl::OUString& i_rProperty )
{
    boost::unordered_map< rtl::OUString, size_t, rtl::OUStringHash >::const_iterator it =
        mpImplData->maPropertyToIndex.find( i_rProperty );
    return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : NULL;
}

const beans::PropertyValue* PrinterController::getValue( const rtl::OUString& i_rProperty ) const
{
    boost::unordered_map< rtl::OUString, size_t, rtl::OUStringHash >::const_iterator it =
        mpImplData->maPropertyToIndex.find( i_rProperty );
    return it != mpImplData->maPropertyToIndex.end() ? &mpImplData->maUIProperties[it->second] : NULL;
}

Sequence< beans::PropertyValue > PrinterController::getValues( const Sequence< rtl::OUString >& i_rNames ) const
{
    Sequence< beans::PropertyValue > aRet( i_rNames.getLength() );
    sal_Int32 nFound = 0;
    for( sal_Int32 i = 0; i < i_rNames.getLength(); i++ )
    {
        const beans::PropertyValue* pVal = getValue( i_rNames[i] );
        if( pVal )
            aRet[ nFound++ ] = *pVal;
    }
    aRet.realloc( nFound );
    return aRet;
}

void PrinterController::setValue( const rtl::OUString& i_rName, const Any& i_rValue )
{
    beans::PropertyValue aVal;
    aVal.Name = i_rName;
    aVal.Value = i_rValue;

    setValue( aVal );
}

void PrinterController::setValue( const beans::PropertyValue& i_rValue )
{
    boost::unordered_map< rtl::OUString, size_t, rtl::OUStringHash >::const_iterator it =
        mpImplData->maPropertyToIndex.find( i_rValue.Name );
    if( it != mpImplData->maPropertyToIndex.end() )
        mpImplData->maUIProperties[ it->second ] = i_rValue;
    else
    {
        // insert correct index into property map
        mpImplData->maPropertyToIndex[ i_rValue.Name ] = mpImplData->maUIProperties.size();
        mpImplData->maUIProperties.push_back( i_rValue );
        mpImplData->maUIPropertyEnabled.push_back( true );
    }
}

void PrinterController::setUIOptions( const Sequence< beans::PropertyValue >& i_rOptions )
{
    DBG_ASSERT( mpImplData->maUIOptions.getLength() == 0, "setUIOptions called twice !" );

    mpImplData->maUIOptions = i_rOptions;

    for( int i = 0; i < i_rOptions.getLength(); i++ )
    {
        Sequence< beans::PropertyValue > aOptProp;
        i_rOptions[i].Value >>= aOptProp;
        bool bIsEnabled = true;
        bool bHaveProperty = false;
        rtl::OUString aPropName;
        vcl::ImplPrinterControllerData::ControlDependency aDep;
        for( int n = 0; n < aOptProp.getLength(); n++ )
        {
            const beans::PropertyValue& rEntry( aOptProp[ n ] );
            if( rEntry.Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "Property" ) ) )
            {
                PropertyValue aVal;
                rEntry.Value >>= aVal;
                DBG_ASSERT( mpImplData->maPropertyToIndex.find( aVal.Name )
                            == mpImplData->maPropertyToIndex.end(), "duplicate property entry" );
                setValue( aVal );
                aPropName = aVal.Name;
                bHaveProperty = true;
            }
            else if( rEntry.Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "Enabled" ) ) )
            {
                sal_Bool bValue = sal_True;
                rEntry.Value >>= bValue;
                bIsEnabled = bValue;
            }
            else if( rEntry.Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "DependsOnName" ) ) )
            {
                rEntry.Value >>= aDep.maDependsOnName;
            }
            else if( rEntry.Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "DependsOnEntry" ) ) )
            {
                rEntry.Value >>= aDep.mnDependsOnEntry;
            }
        }
        if( bHaveProperty )
        {
            vcl::ImplPrinterControllerData::PropertyToIndexMap::const_iterator it =
                mpImplData->maPropertyToIndex.find( aPropName );
            // sanity check
            if( it != mpImplData->maPropertyToIndex.end() )
            {
                mpImplData->maUIPropertyEnabled[ it->second ] = bIsEnabled;
            }
            if( aDep.maDependsOnName.getLength() > 0 )
                mpImplData->maControlDependencies[ aPropName ] = aDep;
        }
    }
}

void PrinterController::enableUIOption( const rtl::OUString& i_rProperty, bool i_bEnable )
{
    boost::unordered_map< rtl::OUString, size_t, rtl::OUStringHash >::const_iterator it =
        mpImplData->maPropertyToIndex.find( i_rProperty );
    if( it != mpImplData->maPropertyToIndex.end() )
    {
        // call handler only for actual changes
        if( ( mpImplData->maUIPropertyEnabled[ it->second ] && ! i_bEnable ) ||
            ( ! mpImplData->maUIPropertyEnabled[ it->second ] && i_bEnable ) )
        {
            mpImplData->maUIPropertyEnabled[ it->second ] = i_bEnable;
            rtl::OUString aPropName( i_rProperty );
            mpImplData->maOptionChangeHdl.Call( &aPropName );
        }
    }
}

bool PrinterController::isUIOptionEnabled( const rtl::OUString& i_rProperty ) const
{
    bool bEnabled = false;
    boost::unordered_map< rtl::OUString, size_t, rtl::OUStringHash >::const_iterator prop_it =
        mpImplData->maPropertyToIndex.find( i_rProperty );
    if( prop_it != mpImplData->maPropertyToIndex.end() )
    {
        bEnabled = mpImplData->maUIPropertyEnabled[prop_it->second];

        if( bEnabled )
        {
            // check control dependencies
            vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it =
                mpImplData->maControlDependencies.find( i_rProperty );
            if( it != mpImplData->maControlDependencies.end() )
            {
                // check if the dependency is enabled
                // if the dependency is disabled, we are too
                bEnabled = isUIOptionEnabled( it->second.maDependsOnName );

                if( bEnabled )
                {
                    // does the dependency have the correct value ?
                    const com::sun::star::beans::PropertyValue* pVal = getValue( it->second.maDependsOnName );
                    OSL_ENSURE( pVal, "unknown property in dependency" );
                    if( pVal )
                    {
                        sal_Int32 nDepVal = 0;
                        sal_Bool bDepVal = sal_False;
                        if( pVal->Value >>= nDepVal )
                        {
                            bEnabled = (nDepVal == it->second.mnDependsOnEntry) || (it->second.mnDependsOnEntry == -1);
                        }
                        else if( pVal->Value >>= bDepVal )
                        {
                            // could be a dependency on a checked boolean
                            // in this case the dependency is on a non zero for checked value
                            bEnabled = (   bDepVal && it->second.mnDependsOnEntry != 0) ||
                                       ( ! bDepVal && it->second.mnDependsOnEntry == 0);
                        }
                        else
                        {
                            // if the type does not match something is awry
                            OSL_ENSURE( 0, "strange type in control dependency" );
                            bEnabled = false;
                        }
                    }
                }
            }
        }
    }
    return bEnabled;
}

rtl::OUString PrinterController::getDependency( const rtl::OUString& i_rProperty ) const
{
    rtl::OUString aDependency;

    vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it =
        mpImplData->maControlDependencies.find( i_rProperty );
    if( it != mpImplData->maControlDependencies.end() )
        aDependency = it->second.maDependsOnName;

    return aDependency;
}

rtl::OUString PrinterController::makeEnabled( const rtl::OUString& i_rProperty )
{
    rtl::OUString aDependency;

    vcl::ImplPrinterControllerData::ControlDependencyMap::const_iterator it =
        mpImplData->maControlDependencies.find( i_rProperty );
    if( it != mpImplData->maControlDependencies.end() )
    {
        if( isUIOptionEnabled( it->second.maDependsOnName ) )
        {
           aDependency = it->second.maDependsOnName;
           const com::sun::star::beans::PropertyValue* pVal = getValue( aDependency );
           OSL_ENSURE( pVal, "unknown property in dependency" );
           if( pVal )
           {
               sal_Int32 nDepVal = 0;
               sal_Bool bDepVal = sal_False;
               if( pVal->Value >>= nDepVal )
               {
                   if( it->second.mnDependsOnEntry != -1 )
                   {
                       setValue( aDependency, makeAny( sal_Int32( it->second.mnDependsOnEntry ) ) );
                   }
               }
               else if( pVal->Value >>= bDepVal )
               {
                   setValue( aDependency, makeAny( sal_Bool( it->second.mnDependsOnEntry != 0 ) ) );
               }
               else
               {
                   // if the type does not match something is awry
                   OSL_ENSURE( 0, "strange type in control dependency" );
               }
           }
        }
    }

    return aDependency;
}

void PrinterController::setOptionChangeHdl( const Link& i_rHdl )
{
    mpImplData->maOptionChangeHdl = i_rHdl;
}

void PrinterController::createProgressDialog()
{
    if( ! mpImplData->mpProgress )
    {
        sal_Bool bShow = sal_True;
        beans::PropertyValue* pMonitor = getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "MonitorVisible" ) ) );
        if( pMonitor )
            pMonitor->Value >>= bShow;
        else
        {
            const com::sun::star::beans::PropertyValue* pVal = getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsApi" ) ) );
            if( pVal )
            {
                sal_Bool bApi = sal_False;
                pVal->Value >>= bApi;
                bShow = ! bApi;
            }
        }

        if( bShow && ! Application::IsHeadlessModeEnabled() )
        {
            mpImplData->mpProgress = new PrintProgressDialog( NULL, getPageCountProtected() );
            mpImplData->mpProgress->Show();
        }
    }
    else
        mpImplData->mpProgress->reset();
}

bool PrinterController::isProgressCanceled() const
{
    return mpImplData->mpProgress && mpImplData->mpProgress->isCanceled();
}

void PrinterController::setMultipage( const MultiPageSetup& i_rMPS )
{
    mpImplData->maMultiPage = i_rMPS;
}

const PrinterController::MultiPageSetup& PrinterController::getMultipage() const
{
    return mpImplData->maMultiPage;
}

void PrinterController::pushPropertiesToPrinter()
{
    sal_Int32 nCopyCount = 1;
    // set copycount and collate
    const beans::PropertyValue* pVal = getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "CopyCount" ) ) );
    if( pVal )
        pVal->Value >>= nCopyCount;
    sal_Bool bCollate = sal_False;
    pVal = getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Collate" ) ) );
    if( pVal )
        pVal->Value >>= bCollate;
    mpImplData->mpPrinter->SetCopyCount( static_cast<USHORT>(nCopyCount), bCollate );

    // duplex mode
    pVal = getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "DuplexMode" ) ) );
    if( pVal )
    {
        sal_Int16 nDuplex = view::DuplexMode::UNKNOWN;
        pVal->Value >>= nDuplex;
        switch( nDuplex )
        {
        case view::DuplexMode::OFF: mpImplData->mpPrinter->SetDuplexMode( DUPLEX_OFF ); break;
        case view::DuplexMode::LONGEDGE: mpImplData->mpPrinter->SetDuplexMode( DUPLEX_LONGEDGE ); break;
        case view::DuplexMode::SHORTEDGE: mpImplData->mpPrinter->SetDuplexMode( DUPLEX_SHORTEDGE ); break;
        }
    }
}

bool PrinterController::isShowDialogs() const
{
    sal_Bool bApi = getBoolProperty( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsApi" ) ), sal_False );
    return ! bApi && ! Application::IsHeadlessModeEnabled();
}

bool PrinterController::isDirectPrint() const
{
    sal_Bool bDirect = getBoolProperty( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "IsDirect" ) ), sal_False );
    return bDirect == sal_True;
}

sal_Bool PrinterController::getBoolProperty( const rtl::OUString& i_rProperty, sal_Bool i_bFallback ) const
{
    sal_Bool bRet = i_bFallback;
    const com::sun::star::beans::PropertyValue* pVal = getValue( i_rProperty );
    if( pVal )
        pVal->Value >>= bRet;
    return bRet;
}

/*
 * PrinterOptionsHelper
**/
Any PrinterOptionsHelper::getValue( const rtl::OUString& i_rPropertyName ) const
{
    Any aRet;
    boost::unordered_map< rtl::OUString, Any, rtl::OUStringHash >::const_iterator it =
        m_aPropertyMap.find( i_rPropertyName );
    if( it != m_aPropertyMap.end() )
        aRet = it->second;
    return aRet;
}

void PrinterOptionsHelper::setValue( const rtl::OUString& i_rPropertyName, const Any& i_rValue )
{
    m_aPropertyMap[ i_rPropertyName ] = i_rValue;
}

bool PrinterOptionsHelper::hasProperty( const rtl::OUString& i_rPropertyName ) const
{
    Any aRet;
    boost::unordered_map< rtl::OUString, Any, rtl::OUStringHash >::const_iterator it =
        m_aPropertyMap.find( i_rPropertyName );
    return it != m_aPropertyMap.end();
}

sal_Bool PrinterOptionsHelper::getBoolValue( const rtl::OUString& i_rPropertyName, sal_Bool i_bDefault ) const
{
    sal_Bool bRet = sal_False;
    Any aVal( getValue( i_rPropertyName ) );
    return (aVal >>= bRet) ? bRet : i_bDefault;
}

sal_Int64 PrinterOptionsHelper::getIntValue( const rtl::OUString& i_rPropertyName, sal_Int64 i_nDefault ) const
{
    sal_Int64 nRet = 0;
    Any aVal( getValue( i_rPropertyName ) );
    return (aVal >>= nRet) ? nRet : i_nDefault;
}

rtl::OUString PrinterOptionsHelper::getStringValue( const rtl::OUString& i_rPropertyName, const rtl::OUString& i_rDefault ) const
{
    rtl::OUString aRet;
    Any aVal( getValue( i_rPropertyName ) );
    return (aVal >>= aRet) ? aRet : i_rDefault;
}

bool PrinterOptionsHelper::processProperties( const Sequence< PropertyValue >& i_rNewProp,
                                              std::set< rtl::OUString >* o_pChangeProp )
{
    bool bChanged = false;

    // clear the changed set
    if( o_pChangeProp )
        o_pChangeProp->clear();

    sal_Int32 nElements = i_rNewProp.getLength();
    const PropertyValue* pVals = i_rNewProp.getConstArray();
    for( sal_Int32 i = 0; i < nElements; i++ )
    {
        bool bElementChanged = false;
        boost::unordered_map< rtl::OUString, Any, rtl::OUStringHash >::iterator it =
            m_aPropertyMap.find( pVals[ i ].Name );
        if( it != m_aPropertyMap.end() )
        {
            if( it->second != pVals[ i ].Value )
                bElementChanged = true;
        }
        else
            bElementChanged = true;

        if( bElementChanged )
        {
            if( o_pChangeProp )
                o_pChangeProp->insert( pVals[ i ].Name );
            m_aPropertyMap[ pVals[i].Name ] = pVals[i].Value;
            bChanged = true;
        }
    }
    return bChanged;
}

void PrinterOptionsHelper::appendPrintUIOptions( uno::Sequence< beans::PropertyValue >& io_rProps ) const
{
    if( m_aUIProperties.getLength() > 0 )
    {
        sal_Int32 nIndex = io_rProps.getLength();
        io_rProps.realloc( nIndex+1 );
        PropertyValue aVal;
        aVal.Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "ExtraPrintUIOptions" ) );
        aVal.Value = makeAny( m_aUIProperties );
        io_rProps[ nIndex ] = aVal;
    }
}

Any PrinterOptionsHelper::getUIControlOpt( const rtl::OUString& i_rTitle,
                                           const Sequence< rtl::OUString >& i_rHelpTexts,
                                           const rtl::OUString& i_rType,
                                           const PropertyValue* i_pVal,
                                           const PrinterOptionsHelper::UIControlOptions& i_rControlOptions
                                           )
{
    sal_Int32 nElements =
        1                                                               // ControlType
        + (i_rTitle.getLength() ? 1 : 0)                                // Text
        + (i_rHelpTexts.getLength() ? 1 : 0)                            // HelpText
        + (i_pVal ? 1 : 0)                                              // Property
        + i_rControlOptions.maAddProps.getLength()                      // additional props
        + (i_rControlOptions.maGroupHint.getLength() ? 1 : 0)           // grouping
        + (i_rControlOptions.mbInternalOnly ? 1 : 0)                    // internal hint
        + (i_rControlOptions.mbEnabled ? 0 : 1)                         // enabled
        ;
    if( i_rControlOptions.maDependsOnName.getLength() )
    {
        nElements += 1;
        if( i_rControlOptions.mnDependsOnEntry != -1 )
            nElements += 1;
        if( i_rControlOptions.mbAttachToDependency )
            nElements += 1;
    }

    Sequence< PropertyValue > aCtrl( nElements );
    sal_Int32 nUsed = 0;
    if( i_rTitle.getLength() )
    {
        aCtrl[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Text" ) );
        aCtrl[nUsed++].Value = makeAny( i_rTitle );
    }
    if( i_rHelpTexts.getLength() )
    {
        aCtrl[nUsed  ].Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "HelpText" ) );
        aCtrl[nUsed++].Value = makeAny( i_rHelpTexts );
    }
    aCtrl[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "ControlType" ) );
    aCtrl[nUsed++].Value = makeAny( i_rType );
    if( i_pVal )
    {
        aCtrl[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Property" ) );
        aCtrl[nUsed++].Value = makeAny( *i_pVal );
    }
    if( i_rControlOptions.maDependsOnName.getLength() )
    {
        aCtrl[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "DependsOnName" ) );
        aCtrl[nUsed++].Value = makeAny( i_rControlOptions.maDependsOnName );
        if( i_rControlOptions.mnDependsOnEntry != -1 )
        {
            aCtrl[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "DependsOnEntry" ) );
            aCtrl[nUsed++].Value = makeAny( i_rControlOptions.mnDependsOnEntry );
        }
        if( i_rControlOptions.mbAttachToDependency )
        {
            aCtrl[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "AttachToDependency" ) );
            aCtrl[nUsed++].Value = makeAny( i_rControlOptions.mbAttachToDependency );
        }
    }
    if( i_rControlOptions.maGroupHint.getLength() )
    {
        aCtrl[nUsed  ].Name    = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "GroupingHint" ) );
        aCtrl[nUsed++].Value <<= i_rControlOptions.maGroupHint;
    }
    if( i_rControlOptions.mbInternalOnly )
    {
        aCtrl[nUsed  ].Name    = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "InternalUIOnly" ) );
        aCtrl[nUsed++].Value <<= sal_True;
    }
    if( ! i_rControlOptions.mbEnabled )
    {
        aCtrl[nUsed  ].Name    = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Enabled" ) );
        aCtrl[nUsed++].Value <<= sal_False;
    }

    sal_Int32 nAddProps = i_rControlOptions.maAddProps.getLength();
    for( sal_Int32 i = 0; i < nAddProps; i++ )
        aCtrl[ nUsed++ ] = i_rControlOptions.maAddProps[i];

    DBG_ASSERT( nUsed == nElements, "nUsed != nElements, probable heap corruption" );

    return makeAny( aCtrl );
}

Any PrinterOptionsHelper::getGroupControlOpt( const rtl::OUString& i_rTitle, const rtl::OUString& i_rHelpText )
{
    Sequence< rtl::OUString > aHelpText;
    if( i_rHelpText.getLength() > 0 )
    {
        aHelpText.realloc( 1 );
        *aHelpText.getArray() = i_rHelpText;
    }
    return getUIControlOpt( i_rTitle, aHelpText, rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Group" ) ) );
}

Any PrinterOptionsHelper::getSubgroupControlOpt( const rtl::OUString& i_rTitle,
                                                 const rtl::OUString& i_rHelpText,
                                                 const PrinterOptionsHelper::UIControlOptions& i_rControlOptions
                                                 )
{
    Sequence< rtl::OUString > aHelpText;
    if( i_rHelpText.getLength() > 0 )
    {
        aHelpText.realloc( 1 );
        *aHelpText.getArray() = i_rHelpText;
    }
    return getUIControlOpt( i_rTitle, aHelpText, rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Subgroup" ) ),
                            NULL, i_rControlOptions );
}

Any PrinterOptionsHelper::getBoolControlOpt( const rtl::OUString& i_rTitle,
                                             const rtl::OUString& i_rHelpText,
                                             const rtl::OUString& i_rProperty,
                                             sal_Bool i_bValue,
                                             const PrinterOptionsHelper::UIControlOptions& i_rControlOptions
                                             )
{
    Sequence< rtl::OUString > aHelpText;
    if( i_rHelpText.getLength() > 0 )
    {
        aHelpText.realloc( 1 );
        *aHelpText.getArray() = i_rHelpText;
    }
    PropertyValue aVal;
    aVal.Name = i_rProperty;
    aVal.Value = makeAny( i_bValue );
    return getUIControlOpt( i_rTitle, aHelpText, rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Bool" ) ), &aVal, i_rControlOptions );
}

Any PrinterOptionsHelper::getChoiceControlOpt( const rtl::OUString& i_rTitle,
                                               const Sequence< rtl::OUString >& i_rHelpText,
                                               const rtl::OUString& i_rProperty,
                                               const Sequence< rtl::OUString >& i_rChoices,
                                               sal_Int32 i_nValue,
                                               const rtl::OUString& i_rType,
                                               const PrinterOptionsHelper::UIControlOptions& i_rControlOptions
                                               )
{
    UIControlOptions aOpt( i_rControlOptions );
    sal_Int32 nUsed = aOpt.maAddProps.getLength();
    aOpt.maAddProps.realloc( nUsed + 1 );
    aOpt.maAddProps[nUsed].Name = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Choices" ) );
    aOpt.maAddProps[nUsed].Value = makeAny( i_rChoices );

    PropertyValue aVal;
    aVal.Name = i_rProperty;
    aVal.Value = makeAny( i_nValue );
    return getUIControlOpt( i_rTitle, i_rHelpText, i_rType, &aVal, aOpt );
}

Any PrinterOptionsHelper::getRangeControlOpt( const rtl::OUString& i_rTitle,
                                              const rtl::OUString& i_rHelpText,
                                              const rtl::OUString& i_rProperty,
                                              sal_Int32 i_nValue,
                                              sal_Int32 i_nMinValue,
                                              sal_Int32 i_nMaxValue,
                                              const PrinterOptionsHelper::UIControlOptions& i_rControlOptions
                                            )
{
    UIControlOptions aOpt( i_rControlOptions );
    if( i_nMaxValue >= i_nMinValue )
    {
        sal_Int32 nUsed = aOpt.maAddProps.getLength();
        aOpt.maAddProps.realloc( nUsed + 2 );
        aOpt.maAddProps[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "MinValue" ) );
        aOpt.maAddProps[nUsed++].Value = makeAny( i_nMinValue );
        aOpt.maAddProps[nUsed  ].Name  = rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "MaxValue" ) );
        aOpt.maAddProps[nUsed++].Value = makeAny( i_nMaxValue );
    }

    Sequence< rtl::OUString > aHelpText;
    if( i_rHelpText.getLength() > 0 )
    {
        aHelpText.realloc( 1 );
        *aHelpText.getArray() = i_rHelpText;
    }
    PropertyValue aVal;
    aVal.Name = i_rProperty;
    aVal.Value = makeAny( i_nValue );
    return getUIControlOpt( i_rTitle,
                            aHelpText,
                            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Range" ) ),
                            &aVal,
                            aOpt
                            );
}

Any PrinterOptionsHelper::getEditControlOpt( const rtl::OUString& i_rTitle,
                                             const rtl::OUString& i_rHelpText,
                                             const rtl::OUString& i_rProperty,
                                             const rtl::OUString& i_rValue,
                                             const PrinterOptionsHelper::UIControlOptions& i_rControlOptions
                                           )
{
    Sequence< rtl::OUString > aHelpText;
    if( i_rHelpText.getLength() > 0 )
    {
        aHelpText.realloc( 1 );
        *aHelpText.getArray() = i_rHelpText;
    }
    PropertyValue aVal;
    aVal.Name = i_rProperty;
    aVal.Value = makeAny( i_rValue );
    return getUIControlOpt( i_rTitle,
                            aHelpText,
                            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Edit" ) ),
                            &aVal,
                            i_rControlOptions
                            );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */