<commit_before>/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: print3.cxx,v $
 * $Revision: 1.1.2.11 $
 *
 * 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 <hash_map>
#include <hash_set>

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 )
        {
            bool bMove = false;
            for( sal_Int32 i = nCacheSize-1; i > 0; i-- )
            {
                if( maCacheRanking[i] == nLastHit )
                    bMove = true;
                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 std::hash_map< rtl::OUString, size_t, rtl::OUStringHash > PropertyToIndexMap;
    typedef std::hash_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;

    ImplPrinterControllerData() :
        mbFirstPage( sal_True ),
        mbLastPage( sal_False ),
        mbReversePageOrder( sal_False ),
        meJobState( view::PrintableState_JOB_STARTED ),
        mpProgress( NULL )
    {}
    ~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->setLastPage( sal_True );
                i_pController->getFilteredPageFile( 0, aPageFile );
                return;
            }
            if( aDlg.isPrintToFile() )
            {
                rtl::OUString aFile = queryFile( pController->getPrinter().get() );
                if( ! aFile.getLength() )
                {
                    GDIMetaFile aPageFile;
                    i_pController->setLastPage( sal_True );
                    i_pController->getFilteredPageFile( 0, aPageFile );
                    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 nRepeatCount = bUserCopy ? mnCopyCount : 1;
        if( bSinglePrintJobs )
        {
            nJobs = mnCopyCount;
            nCopies = 1;
            nRepeatCount = 1;
        }

        for( int nJobIteration = 0; nJobIteration < nJobs; nJobIteration++ )
        {
            bool bError = 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 nIteration = 0; nIteration < nRepeatCount; nIteration++ )
                {
                    for( int nPage = 0; nPage < nPages; nPage++ )
                    {
                        if( nPage == nPages-1 && nIteration == nRepeatCount-1 && nJobIteration == nJobs-1 )
                            i_pController->setLastPage( sal_True );
                        i_pController->printFilteredPage( nPage );
                    }
                    // 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() ) ) );
}

bool PrinterController::setupPrinter( Window* i_pParent )
{
    bool bRet = false;
    if( mpImplData->mpPrinter.get() )
    {
        Size aPaperSize( mpImplData->mpPrinter->PixelToLogic(
            mpImplData->mpPrinter->GetPaperSizePixel(), MapMode( MAP_100TH_MM ) ) );
        bRet = mpImplData->mpPrinter->Setup( i_pParent );
        if( bRet )
        {
            // was the papersize overridden ? if so we need to take action
            Size aNewPaperSize( mpImplData->mpPrinter->PixelToLogic(
                mpImplData->mpPrinter->GetPaperSizePixel(), MapMode( MAP_100TH_MM ) ) );
            if( aNewPaperSize != aPaperSize )
            {
                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 ) );
            }
        }
    }
    return bRet;
}

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

    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() );
    }
    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 )<commit_msg>ppp02: #i108327# set requested paper bin<commit_after>/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: print3.cxx,v $
 * $Revision: 1.1.2.11 $
 *
 * 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 <hash_map>
#include <hash_set>

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 )
        {
            bool bMove = false;
            for( sal_Int32 i = nCacheSize-1; i > 0; i-- )
            {
                if( maCacheRanking[i] == nLastHit )
                    bMove = true;
                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 std::hash_map< rtl::OUString, size_t, rtl::OUStringHash > PropertyToIndexMap;
    typedef std::hash_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;

    ImplPrinterControllerData() :
        mbFirstPage( sal_True ),
        mbLastPage( sal_False ),
        mbReversePageOrder( sal_False ),
        meJobState( view::PrintableState_JOB_STARTED ),
        mpProgress( NULL )
    {}
    ~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->setLastPage( sal_True );
                i_pController->getFilteredPageFile( 0, aPageFile );
                return;
            }
            if( aDlg.isPrintToFile() )
            {
                rtl::OUString aFile = queryFile( pController->getPrinter().get() );
                if( ! aFile.getLength() )
                {
                    GDIMetaFile aPageFile;
                    i_pController->setLastPage( sal_True );
                    i_pController->getFilteredPageFile( 0, aPageFile );
                    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 nRepeatCount = bUserCopy ? mnCopyCount : 1;
        if( bSinglePrintJobs )
        {
            nJobs = mnCopyCount;
            nCopies = 1;
            nRepeatCount = 1;
        }

        for( int nJobIteration = 0; nJobIteration < nJobs; nJobIteration++ )
        {
            bool bError = 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 nIteration = 0; nIteration < nRepeatCount; nIteration++ )
                {
                    for( int nPage = 0; nPage < nPages; nPage++ )
                    {
                        if( nPage == nPages-1 && nIteration == nRepeatCount-1 && nJobIteration == nJobs-1 )
                            i_pController->setLastPage( sal_True );
                        i_pController->printFilteredPage( nPage );
                    }
                    // 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() ) ) );
}

bool PrinterController::setupPrinter( Window* i_pParent )
{
    bool bRet = false;
    if( mpImplData->mpPrinter.get() )
    {
        Size aPaperSize( mpImplData->mpPrinter->PixelToLogic(
            mpImplData->mpPrinter->GetPaperSizePixel(), MapMode( MAP_100TH_MM ) ) );
        bRet = mpImplData->mpPrinter->Setup( i_pParent );
        if( bRet )
        {
            // was the papersize overridden ? if so we need to take action
            Size aNewPaperSize( mpImplData->mpPrinter->PixelToLogic(
                mpImplData->mpPrinter->GetPaperSizePixel(), MapMode( MAP_100TH_MM ) ) );
            if( aNewPaperSize != aPaperSize )
            {
                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 ) );
            }
        }
    }
    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 = -1;
    for( sal_Int32 nProperty = 0, nPropertyCount = i_rProps.getLength(); nProperty < nPropertyCount; ++nProperty )
    {
        if( i_rProps[ nProperty ].Name.equalsAscii( "PreferredPageSize" ) )
        {
            i_rProps[ nProperty ].Value >>= aSetSize;
        }
        else if( i_rProps[ nProperty ].Name.equalsAscii( "PageSize" ) )
        {
            i_rProps[ nProperty ].Value >>= aIsSize;
        }
        else if( i_rProps[ nProperty ].Name.equalsAscii( "PageIncludesNonprintableArea" ) )
        {
            sal_Bool bVal = sal_False;
            i_rProps[ nProperty ].Value >>= bVal;
            aPageSize.bFullPaper = static_cast<bool>(bVal);
        }
        else if( i_rProps[ nProperty ].Name.equalsAscii( "PrinterPaperTray" ) )
        {
            i_rProps[ nProperty ].Value >>= nPaperBin;
        }
    }

    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 )
{'fraction_non_alphanumeric': 0.10146482711633452, 'fraction_numerical': 0.004632941577244081, 'mean_word_length': 1.9349630073985202, 'pattern_counts': {'":': 0, '<': 87, '<?xml version=': 0, '>': 363, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 80, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2852616', 'n_tokens_mistral': 17654, 'n_tokens_neox': 16790, 'n_words': 3655}