blob: e375ff1a2f649078fa5d7fd6bf86f223b5da39b7 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
/**
this file implements the sal printer interface ( SalPrinter, SalInfoPrinter
and some printer relevant methods of SalInstance and SalGraphicsData )
as aunderlying library the printer features of psprint are used.
The query methods of a SalInfoPrinter are implemented by querying psprint
The job methods of a SalPrinter are implemented by calling psprint
printer job functions.
*/
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include "rtl/ustring.hxx"
#include "osl/module.h"
#include "vcl/svapp.hxx"
#include "vcl/print.hxx"
#include "vcl/pdfwriter.hxx"
#include "vcl/printerinfomanager.hxx"
#include <unx/salunx.h>
#include "unx/saldisp.hxx"
#include "unx/salinst.h"
#include "unx/salprn.h"
#include "unx/salframe.h"
#include "unx/pspgraphics.h"
#include "unx/saldata.hxx"
#include "jobset.h"
#include "print.h"
#include "salptype.hxx"
using namespace psp;
using namespace rtl;
using namespace com::sun::star;
/*
* static helpers
*/
static oslModule driverLib = NULL;
extern "C"
{
typedef int(*setupFunction)(PrinterInfo&);
static setupFunction pSetupFunction = NULL;
typedef int(*faxFunction)(String&);
static faxFunction pFaxNrFunction = NULL;
}
static String getPdfDir( const PrinterInfo& rInfo )
{
String aDir;
sal_Int32 nIndex = 0;
while( nIndex != -1 )
{
OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
if( ! aToken.compareToAscii( "pdf=", 4 ) )
{
sal_Int32 nPos = 0;
aDir = aToken.getToken( 1, '=', nPos );
if( ! aDir.Len() )
aDir = String( ByteString( getenv( "HOME" ) ), osl_getThreadTextEncoding() );
break;
}
}
return aDir;
}
static void getPaLib()
{
if( ! driverLib )
{
driverLib = osl_loadAsciiModuleRelative( (oslGenericFunction)getPaLib, _XSALSET_LIBNAME, SAL_LOADMODULE_DEFAULT );
if ( !driverLib )
{
return;
}
pSetupFunction = (setupFunction)osl_getAsciiFunctionSymbol( driverLib, "Sal_SetupPrinterDriver" );
if ( !pSetupFunction )
fprintf( stderr, "could not resolve Sal_SetupPrinterDriver\n" );
pFaxNrFunction = (faxFunction)osl_getAsciiFunctionSymbol( driverLib, "Sal_queryFaxNumber" );
if ( !pFaxNrFunction )
fprintf( stderr, "could not resolve Sal_queryFaxNumber\n" );
}
}
inline int PtTo10Mu( int nPoints ) { return (int)((((double)nPoints)*35.27777778)+0.5); }
inline int TenMuToPt( int nUnits ) { return (int)((((double)nUnits)/35.27777778)+0.5); }
static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData )
{
pJobSetup->meOrientation = (Orientation)(rData.m_eOrientation == orientation::Landscape ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT);
// copy page size
String aPaper;
int width, height;
rData.m_aContext.getPageSize( aPaper, width, height );
pJobSetup->mePaperFormat = PaperInfo::fromPSName(OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 ));
pJobSetup->mnPaperWidth = 0;
pJobSetup->mnPaperHeight = 0;
if( pJobSetup->mePaperFormat == PAPER_USER )
{
// transform to 100dth mm
width = PtTo10Mu( width );
height = PtTo10Mu( height );
if( rData.m_eOrientation == psp::orientation::Portrait )
{
pJobSetup->mnPaperWidth = width;
pJobSetup->mnPaperHeight= height;
}
else
{
pJobSetup->mnPaperWidth = height;
pJobSetup->mnPaperHeight= width;
}
}
// copy input slot
const PPDKey* pKey = NULL;
const PPDValue* pValue = NULL;
pJobSetup->mnPaperBin = 0;
if( rData.m_pParser )
pKey = rData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "InputSlot" ) ) );
if( pKey )
pValue = rData.m_aContext.getValue( pKey );
if( pKey && pValue )
{
for( pJobSetup->mnPaperBin = 0;
pValue != pKey->getValue( pJobSetup->mnPaperBin ) &&
pJobSetup->mnPaperBin < pKey->countValues();
pJobSetup->mnPaperBin++ )
;
if( pJobSetup->mnPaperBin >= pKey->countValues() )
pJobSetup->mnPaperBin = 0;
}
// copy duplex
pKey = NULL;
pValue = NULL;
pJobSetup->meDuplexMode = DUPLEX_UNKNOWN;
if( rData.m_pParser )
pKey = rData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "Duplex" ) ) );
if( pKey )
pValue = rData.m_aContext.getValue( pKey );
if( pKey && pValue )
{
if( pValue->m_aOption.EqualsIgnoreCaseAscii( "None" ) ||
pValue->m_aOption.EqualsIgnoreCaseAscii( "Simplex", 0, 7 )
)
{
pJobSetup->meDuplexMode = DUPLEX_OFF;
}
else if( pValue->m_aOption.EqualsIgnoreCaseAscii( "DuplexNoTumble" ) )
{
pJobSetup->meDuplexMode = DUPLEX_LONGEDGE;
}
else if( pValue->m_aOption.EqualsIgnoreCaseAscii( "DuplexTumble" ) )
{
pJobSetup->meDuplexMode = DUPLEX_SHORTEDGE;
}
}
// copy the whole context
if( pJobSetup->mpDriverData )
rtl_freeMemory( pJobSetup->mpDriverData );
int nBytes;
void* pBuffer = NULL;
if( rData.getStreamBuffer( pBuffer, nBytes ) )
{
pJobSetup->mnDriverDataLen = nBytes;
pJobSetup->mpDriverData = (sal_uInt8*)pBuffer;
}
else
{
pJobSetup->mnDriverDataLen = 0;
pJobSetup->mpDriverData = NULL;
}
}
static bool passFileToCommandLine( const String& rFilename, const String& rCommandLine, bool bRemoveFile = true )
{
bool bSuccess = false;
rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
ByteString aCmdLine( rCommandLine, aEncoding );
ByteString aFilename( rFilename, aEncoding );
bool bPipe = aCmdLine.Search( "(TMP)" ) != STRING_NOTFOUND ? false : true;
// setup command line for exec
if( ! bPipe )
while( aCmdLine.SearchAndReplace( "(TMP)", aFilename ) != STRING_NOTFOUND )
;
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "%s commandline: \"%s\"\n",
bPipe ? "piping to" : "executing",
aCmdLine.GetBuffer() );
struct stat aStat;
if( stat( aFilename.GetBuffer(), &aStat ) )
fprintf( stderr, "stat( %s ) failed\n", aFilename.GetBuffer() );
fprintf( stderr, "Tmp file %s has modes: 0%03lo\n", aFilename.GetBuffer(), (long)aStat.st_mode );
#endif
const char* argv[4];
if( ! ( argv[ 0 ] = getenv( "SHELL" ) ) )
argv[ 0 ] = "/bin/sh";
argv[ 1 ] = "-c";
argv[ 2 ] = aCmdLine.GetBuffer();
argv[ 3 ] = 0;
bool bHavePipes = false;
int pid, fd[2];
if( bPipe )
bHavePipes = pipe( fd ) ? false : true;
if( ( pid = fork() ) > 0 )
{
if( bPipe && bHavePipes )
{
close( fd[0] );
char aBuffer[ 2048 ];
FILE* fp = fopen( aFilename.GetBuffer(), "r" );
while( fp && ! feof( fp ) )
{
int nBytes = fread( aBuffer, 1, sizeof( aBuffer ), fp );
if( nBytes )
write( fd[ 1 ], aBuffer, nBytes );
}
fclose( fp );
close( fd[ 1 ] );
}
int status = 0;
waitpid( pid, &status, 0 );
if( ! status )
bSuccess = true;
}
else if( ! pid )
{
if( bPipe && bHavePipes )
{
close( fd[1] );
if( fd[0] != STDIN_FILENO ) // not probable, but who knows :)
dup2( fd[0], STDIN_FILENO );
}
execv( argv[0], const_cast<char**>(argv) );
fprintf( stderr, "failed to execute \"%s\"\n", aCmdLine.GetBuffer() );
_exit( 1 );
}
else
fprintf( stderr, "failed to fork\n" );
// clean up the mess
if( bRemoveFile )
unlink( aFilename.GetBuffer() );
return bSuccess;
}
static bool sendAFax( const String& rFaxNumber, const String& rFileName, const String& rCommand )
{
std::list< OUString > aFaxNumbers;
if( ! rFaxNumber.Len() )
{
getPaLib();
if( pFaxNrFunction )
{
String aNewNr;
if( pFaxNrFunction( aNewNr ) )
aFaxNumbers.push_back( OUString( aNewNr ) );
}
}
else
{
sal_Int32 nIndex = 0;
OUString aFaxes( rFaxNumber );
OUString aBeginToken( RTL_CONSTASCII_USTRINGPARAM("<Fax#>") );
OUString aEndToken( RTL_CONSTASCII_USTRINGPARAM("</Fax#>") );
while( nIndex != -1 )
{
nIndex = aFaxes.indexOf( aBeginToken, nIndex );
if( nIndex != -1 )
{
sal_Int32 nBegin = nIndex + aBeginToken.getLength();
nIndex = aFaxes.indexOf( aEndToken, nIndex );
if( nIndex != -1 )
{
aFaxNumbers.push_back( aFaxes.copy( nBegin, nIndex-nBegin ) );
nIndex += aEndToken.getLength();
}
}
}
}
bool bSuccess = true;
if( aFaxNumbers.begin() != aFaxNumbers.end() )
{
while( aFaxNumbers.begin() != aFaxNumbers.end() && bSuccess )
{
String aCmdLine( rCommand );
String aFaxNumber( aFaxNumbers.front() );
aFaxNumbers.pop_front();
while( aCmdLine.SearchAndReplace( String( RTL_CONSTASCII_USTRINGPARAM( "(PHONE)" ) ), aFaxNumber ) != STRING_NOTFOUND )
;
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "sending fax to \"%s\"\n", OUStringToOString( aFaxNumber, osl_getThreadTextEncoding() ).getStr() );
#endif
bSuccess = passFileToCommandLine( rFileName, aCmdLine, false );
}
}
else
bSuccess = false;
// clean up temp file
unlink( ByteString( rFileName, osl_getThreadTextEncoding() ).GetBuffer() );
return bSuccess;
}
static bool createPdf( const String& rToFile, const String& rFromFile, const String& rCommandLine )
{
String aCommandLine( rCommandLine );
while( aCommandLine.SearchAndReplace( String( RTL_CONSTASCII_USTRINGPARAM( "(OUTFILE)" ) ), rToFile ) != STRING_NOTFOUND )
;
return passFileToCommandLine( rFromFile, aCommandLine );
}
/*
* SalInstance
*/
// -----------------------------------------------------------------------
SalInfoPrinter* X11SalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
ImplJobSetup* pJobSetup )
{
mbPrinterInit = true;
// create and initialize SalInfoPrinter
PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
if( pJobSetup )
{
PrinterInfoManager& rManager( PrinterInfoManager::get() );
PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) );
pPrinter->m_aJobData = aInfo;
pPrinter->m_aPrinterGfx.Init( pPrinter->m_aJobData );
if( pJobSetup->mpDriverData )
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aInfo );
pJobSetup->mnSystem = JOBSETUP_SYSTEM_UNIX;
pJobSetup->maPrinterName = pQueueInfo->maPrinterName;
pJobSetup->maDriver = aInfo.m_aDriverName;
copyJobDataToJobSetup( pJobSetup, aInfo );
// set/clear backwards compatibility flag
bool bStrictSO52Compatibility = false;
std::hash_map<rtl::OUString, rtl::OUString, rtl::OUStringHash >::const_iterator compat_it =
pJobSetup->maValueMap.find( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "StrictSO52Compatibility" ) ) );
if( compat_it != pJobSetup->maValueMap.end() )
{
if( compat_it->second.equalsIgnoreAsciiCaseAscii( "true" ) )
bStrictSO52Compatibility = true;
}
pPrinter->m_aPrinterGfx.setStrictSO52Compatibility( bStrictSO52Compatibility );
}
return pPrinter;
}
// -----------------------------------------------------------------------
void X11SalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
{
delete pPrinter;
}
// -----------------------------------------------------------------------
SalPrinter* X11SalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
mbPrinterInit = true;
// create and initialize SalPrinter
PspSalPrinter* pPrinter = new PspSalPrinter( pInfoPrinter );
pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
return pPrinter;
}
// -----------------------------------------------------------------------
void X11SalInstance::DestroyPrinter( SalPrinter* pPrinter )
{
delete pPrinter;
}
// -----------------------------------------------------------------------
void X11SalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
{
mbPrinterInit = true;
PrinterInfoManager& rManager( PrinterInfoManager::get() );
static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
if( ! pNoSyncDetection || ! *pNoSyncDetection )
{
// #i62663# synchronize possible asynchronouse printer detection now
rManager.checkPrintersChanged( true );
}
::std::list< OUString > aPrinters;
rManager.listPrinters( aPrinters );
for( ::std::list< OUString >::iterator it = aPrinters.begin(); it != aPrinters.end(); ++it )
{
const PrinterInfo& rInfo( rManager.getPrinterInfo( *it ) );
// Neuen Eintrag anlegen
SalPrinterQueueInfo* pInfo = new SalPrinterQueueInfo;
pInfo->maPrinterName = *it;
pInfo->maDriver = rInfo.m_aDriverName;
pInfo->maLocation = rInfo.m_aLocation;
pInfo->maComment = rInfo.m_aComment;
pInfo->mpSysData = NULL;
sal_Int32 nIndex = 0;
while( nIndex != -1 )
{
String aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
if( aToken.CompareToAscii( "pdf=", 4 ) == COMPARE_EQUAL )
{
pInfo->maLocation = getPdfDir( rInfo );
break;
}
}
pList->Add( pInfo );
}
}
// -----------------------------------------------------------------------
void X11SalInstance::DeletePrinterQueueInfo( SalPrinterQueueInfo* pInfo )
{
delete pInfo;
}
// -----------------------------------------------------------------------
void X11SalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
{
mbPrinterInit = true;
}
// -----------------------------------------------------------------------
String X11SalInstance::GetDefaultPrinter()
{
mbPrinterInit = true;
PrinterInfoManager& rManager( PrinterInfoManager::get() );
return rManager.getDefaultPrinter();
}
// =======================================================================
PspSalInfoPrinter::PspSalInfoPrinter()
{
m_pGraphics = NULL;
m_bPapersInit = false;
}
// -----------------------------------------------------------------------
PspSalInfoPrinter::~PspSalInfoPrinter()
{
if( m_pGraphics )
{
delete m_pGraphics;
m_pGraphics = NULL;
}
}
// -----------------------------------------------------------------------
void PspSalInfoPrinter::InitPaperFormats( const ImplJobSetup* )
{
m_aPaperFormats.clear();
m_bPapersInit = true;
if( m_aJobData.m_pParser )
{
const PPDKey* pKey = m_aJobData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "PageSize" ) ) );
if( pKey )
{
int nValues = pKey->countValues();
for( int i = 0; i < nValues; i++ )
{
const PPDValue* pValue = pKey->getValue( i );
int nWidth = 0, nHeight = 0;
m_aJobData.m_pParser->getPaperDimension( pValue->m_aOption, nWidth, nHeight );
PaperInfo aInfo(PtTo10Mu( nWidth ), PtTo10Mu( nHeight ));
m_aPaperFormats.push_back( aInfo );
}
}
}
}
// -----------------------------------------------------------------------
int PspSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* )
{
return 900;
}
// -----------------------------------------------------------------------
SalGraphics* PspSalInfoPrinter::GetGraphics()
{
// return a valid pointer only once
// the reasoning behind this is that we could have different
// SalGraphics that can run in multiple threads
// (future plans)
SalGraphics* pRet = NULL;
if( ! m_pGraphics )
{
m_pGraphics = new PspGraphics( &m_aJobData, &m_aPrinterGfx, NULL, false, this );
m_pGraphics->SetLayout( 0 );
pRet = m_pGraphics;
}
return pRet;
}
// -----------------------------------------------------------------------
void PspSalInfoPrinter::ReleaseGraphics( SalGraphics* pGraphics )
{
if( pGraphics == m_pGraphics )
{
delete pGraphics;
m_pGraphics = NULL;
}
return;
}
// -----------------------------------------------------------------------
sal_Bool PspSalInfoPrinter::Setup( SalFrame* pFrame, ImplJobSetup* pJobSetup )
{
if( ! pFrame || ! pJobSetup )
return sal_False;
getPaLib();
if( ! pSetupFunction )
return sal_False;
PrinterInfoManager& rManager = PrinterInfoManager::get();
PrinterInfo aInfo( rManager.getPrinterInfo( pJobSetup->maPrinterName ) );
if ( pJobSetup->mpDriverData )
{
SetData( ~0, pJobSetup );
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aInfo );
}
if( pSetupFunction( aInfo ) )
{
rtl_freeMemory( pJobSetup->mpDriverData );
pJobSetup->mpDriverData = NULL;
int nBytes;
void* pBuffer = NULL;
aInfo.getStreamBuffer( pBuffer, nBytes );
pJobSetup->mnDriverDataLen = nBytes;
pJobSetup->mpDriverData = (sal_uInt8*)pBuffer;
// copy everything to job setup
copyJobDataToJobSetup( pJobSetup, aInfo );
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, m_aJobData );
return sal_True;
}
return sal_False;
}
// -----------------------------------------------------------------------
// This function gets the driver data and puts it into pJobSetup
// If pJobSetup->mpDriverData is NOT NULL, then the independend
// data should be merged into the driver data
// If pJobSetup->mpDriverData IS NULL, then the driver defaults
// should be merged into the independent data
sal_Bool PspSalInfoPrinter::SetPrinterData( ImplJobSetup* pJobSetup )
{
// set/clear backwards compatibility flag
bool bStrictSO52Compatibility = false;
std::hash_map<rtl::OUString, rtl::OUString, rtl::OUStringHash >::const_iterator compat_it =
pJobSetup->maValueMap.find( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "StrictSO52Compatibility" ) ) );
if( compat_it != pJobSetup->maValueMap.end() )
{
if( compat_it->second.equalsIgnoreAsciiCaseAscii( "true" ) )
bStrictSO52Compatibility = true;
}
m_aPrinterGfx.setStrictSO52Compatibility( bStrictSO52Compatibility );
if( pJobSetup->mpDriverData )
return SetData( ~0, pJobSetup );
copyJobDataToJobSetup( pJobSetup, m_aJobData );
return sal_True;
}
// -----------------------------------------------------------------------
// This function merges the independ driver data
// and sets the new independ data in pJobSetup
// Only the data must be changed, where the bit
// in nGetDataFlags is set
sal_Bool PspSalInfoPrinter::SetData(
sal_uLong nSetDataFlags,
ImplJobSetup* pJobSetup )
{
JobData aData;
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aData );
if( aData.m_pParser )
{
const PPDKey* pKey;
const PPDValue* pValue;
// merge papersize if necessary
if( nSetDataFlags & SAL_JOBSET_PAPERSIZE )
{
int nWidth, nHeight;
if( pJobSetup->meOrientation == ORIENTATION_PORTRAIT )
{
nWidth = pJobSetup->mnPaperWidth;
nHeight = pJobSetup->mnPaperHeight;
}
else
{
nWidth = pJobSetup->mnPaperHeight;
nHeight = pJobSetup->mnPaperWidth;
}
String aPaper;
if( pJobSetup->mePaperFormat == PAPER_USER )
aPaper = aData.m_pParser->matchPaper(
TenMuToPt( pJobSetup->mnPaperWidth ),
TenMuToPt( pJobSetup->mnPaperHeight ) );
else
aPaper = rtl::OStringToOUString(PaperInfo::toPSName(pJobSetup->mePaperFormat), RTL_TEXTENCODING_ISO_8859_1);
pKey = aData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "PageSize" ) ) );
pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : NULL;
// some PPD files do not specify the standard paper names (e.g. C5 instead of EnvC5)
// try to find the correct paper anyway using the size
if( pKey && ! pValue && pJobSetup->mePaperFormat != PAPER_USER )
{
PaperInfo aInfo( pJobSetup->mePaperFormat );
aPaper = aData.m_pParser->matchPaper(
TenMuToPt( aInfo.getWidth() ),
TenMuToPt( aInfo.getHeight() ) );
pValue = pKey->getValueCaseInsensitive( aPaper );
}
if( ! ( pKey && pValue && aData.m_aContext.setValue( pKey, pValue, false ) == pValue ) )
return sal_False;
}
// merge paperbin if necessary
if( nSetDataFlags & SAL_JOBSET_PAPERBIN )
{
pKey = aData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "InputSlot" ) ) );
if( pKey )
{
int nPaperBin = pJobSetup->mnPaperBin;
if( nPaperBin >= pKey->countValues() )
pValue = pKey->getDefaultValue();
else
pValue = pKey->getValue( pJobSetup->mnPaperBin );
// may fail due to constraints;
// real paper bin is copied back to jobsetup in that case
aData.m_aContext.setValue( pKey, pValue );
}
// if printer has no InputSlot key simply ignore this setting
// (e.g. SGENPRT has no InputSlot)
}
// merge orientation if necessary
if( nSetDataFlags & SAL_JOBSET_ORIENTATION )
aData.m_eOrientation = pJobSetup->meOrientation == ORIENTATION_LANDSCAPE ? orientation::Landscape : orientation::Portrait;
// merge duplex if necessary
if( nSetDataFlags & SAL_JOBSET_DUPLEXMODE )
{
pKey = aData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "Duplex" ) ) );
if( pKey )
{
pValue = NULL;
switch( pJobSetup->meDuplexMode )
{
case DUPLEX_OFF:
pValue = pKey->getValue( String( RTL_CONSTASCII_USTRINGPARAM( "None" ) ) );
if( pValue == NULL )
pValue = pKey->getValue( String( RTL_CONSTASCII_USTRINGPARAM( "SimplexNoTumble" ) ) );
break;
case DUPLEX_SHORTEDGE:
pValue = pKey->getValue( String( RTL_CONSTASCII_USTRINGPARAM( "DuplexTumble" ) ) );
break;
case DUPLEX_LONGEDGE:
pValue = pKey->getValue( String( RTL_CONSTASCII_USTRINGPARAM( "DuplexNoTumble" ) ) );
break;
case DUPLEX_UNKNOWN:
default:
pValue = 0;
break;
}
if( ! pValue )
pValue = pKey->getDefaultValue();
aData.m_aContext.setValue( pKey, pValue );
}
}
m_aJobData = aData;
copyJobDataToJobSetup( pJobSetup, aData );
return sal_True;
}
return sal_False;
}
// -----------------------------------------------------------------------
void PspSalInfoPrinter::GetPageInfo(
const ImplJobSetup* pJobSetup,
long& rOutWidth, long& rOutHeight,
long& rPageOffX, long& rPageOffY,
long& rPageWidth, long& rPageHeight )
{
if( ! pJobSetup )
return;
JobData aData;
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aData );
// get the selected page size
if( aData.m_pParser )
{
String aPaper;
int width, height;
int left = 0, top = 0, right = 0, bottom = 0;
int nDPI = aData.m_aContext.getRenderResolution();
if( aData.m_eOrientation == psp::orientation::Portrait )
{
aData.m_aContext.getPageSize( aPaper, width, height );
aData.m_pParser->getMargins( aPaper, left, right, top, bottom );
}
else
{
aData.m_aContext.getPageSize( aPaper, height, width );
aData.m_pParser->getMargins( aPaper, top, bottom, right, left );
}
rPageWidth = width * nDPI / 72;
rPageHeight = height * nDPI / 72;
rPageOffX = left * nDPI / 72;
rPageOffY = top * nDPI / 72;
rOutWidth = ( width - left - right ) * nDPI / 72;
rOutHeight = ( height - top - bottom ) * nDPI / 72;
}
}
// -----------------------------------------------------------------------
sal_uLong PspSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pJobSetup )
{
if( ! pJobSetup )
return 0;
JobData aData;
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aData );
const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "InputSlot" ) ) ): NULL;
return pKey ? pKey->countValues() : 0;
}
// -----------------------------------------------------------------------
String PspSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pJobSetup, sal_uLong nPaperBin )
{
JobData aData;
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aData );
String aRet;
if( aData.m_pParser )
{
const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "InputSlot" ) ) ): NULL;
if( ! pKey || nPaperBin >= (sal_uLong)pKey->countValues() )
aRet = aData.m_pParser->getDefaultInputSlot();
else
{
const PPDValue* pValue = pKey->getValue( nPaperBin );
if( pValue )
aRet = aData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption );
}
}
return aRet;
}
// -----------------------------------------------------------------------
sal_uLong PspSalInfoPrinter::GetCapabilities( const ImplJobSetup* pJobSetup, sal_uInt16 nType )
{
switch( nType )
{
case PRINTER_CAPABILITIES_SUPPORTDIALOG:
return 1;
case PRINTER_CAPABILITIES_COPIES:
return 0xffff;
case PRINTER_CAPABILITIES_COLLATECOPIES:
{
// see if the PPD contains a value to set Collate to True
JobData aData;
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aData );
const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( String( RTL_CONSTASCII_USTRINGPARAM( "Collate" ) ) ) : NULL;
const PPDValue* pVal = pKey ? pKey->getValue( String( RTL_CONSTASCII_USTRINGPARAM( "True" ) ) ) : NULL;
// PPDs don't mention the number of possible collated copies.
// so let's guess as many as we want ?
return pVal ? 0xffff : 0;
}
case PRINTER_CAPABILITIES_SETORIENTATION:
return 1;
case PRINTER_CAPABILITIES_SETDUPLEX:
return 1;
case PRINTER_CAPABILITIES_SETPAPERBIN:
return 1;
case PRINTER_CAPABILITIES_SETPAPERSIZE:
return 1;
case PRINTER_CAPABILITIES_SETPAPER:
return 0;
case PRINTER_CAPABILITIES_FAX:
return PrinterInfoManager::get().checkFeatureToken( pJobSetup->maPrinterName, "fax" ) ? 1 : 0;
case PRINTER_CAPABILITIES_PDF:
if( PrinterInfoManager::get().checkFeatureToken( pJobSetup->maPrinterName, "pdf" ) )
return 1;
else
{
// see if the PPD contains a value to set Collate to True
JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->maPrinterName );
if( pJobSetup->mpDriverData )
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aData );
return aData.m_nPDFDevice > 0 ? 1 : 0;
}
case PRINTER_CAPABILITIES_EXTERNALDIALOG:
return PrinterInfoManager::get().checkFeatureToken( pJobSetup->maPrinterName, "external_dialog" ) ? 1 : 0;
case PRINTER_CAPABILITIES_USEPULLMODEL:
{
// see if the PPD contains a value to set Collate to True
JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->maPrinterName );
if( pJobSetup->mpDriverData )
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, aData );
return aData.m_nPDFDevice > 0 ? 1 : 0;
}
default: break;
};
return 0;
}
// =======================================================================
/*
* SalPrinter
*/
PspSalPrinter::PspSalPrinter( SalInfoPrinter* pInfoPrinter )
: m_bFax( false ),
m_bPdf( false ),
m_bSwallowFaxNo( false ),
m_bIsPDFWriterJob( false ),
m_pGraphics( NULL ),
m_nCopies( 1 ),
m_bCollate( false ),
m_pInfoPrinter( pInfoPrinter )
{
}
// -----------------------------------------------------------------------
PspSalPrinter::~PspSalPrinter()
{
}
// -----------------------------------------------------------------------
static String getTmpName()
{
rtl::OUString aTmp, aSys;
osl_createTempFile( NULL, NULL, &aTmp.pData );
osl_getSystemPathFromFileURL( aTmp.pData, &aSys.pData );
return aSys;
}
sal_Bool PspSalPrinter::StartJob(
const XubString* pFileName,
const XubString& rJobName,
const XubString& rAppName,
sal_uLong nCopies,
bool bCollate,
bool bDirect,
ImplJobSetup* pJobSetup )
{
vcl_sal::PrinterUpdate::jobStarted();
m_bFax = false;
m_bPdf = false;
m_aFileName = pFileName ? *pFileName : String();
m_aTmpFile = String();
m_nCopies = nCopies;
m_bCollate = bCollate;
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, m_aJobData );
if( m_nCopies > 1 )
{
// in case user did not do anything (m_nCopies=1)
// take the default from jobsetup
m_aJobData.m_nCopies = m_nCopies;
m_aJobData.setCollate( bCollate );
}
// check wether this printer is configured as fax
int nMode = 0;
const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
sal_Int32 nIndex = 0;
while( nIndex != -1 )
{
OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
if( ! aToken.compareToAscii( "fax", 3 ) )
{
m_bFax = true;
m_aTmpFile = getTmpName();
nMode = S_IRUSR | S_IWUSR;
::std::hash_map< ::rtl::OUString, ::rtl::OUString, ::rtl::OUStringHash >::const_iterator it;
it = pJobSetup->maValueMap.find( ::rtl::OUString::createFromAscii( "FAX#" ) );
if( it != pJobSetup->maValueMap.end() )
m_aFaxNr = it->second;
sal_Int32 nPos = 0;
m_bSwallowFaxNo = ! aToken.getToken( 1, '=', nPos ).compareToAscii( "swallow", 7 ) ? true : false;
break;
}
if( ! aToken.compareToAscii( "pdf=", 4 ) )
{
m_bPdf = true;
m_aTmpFile = getTmpName();
nMode = S_IRUSR | S_IWUSR;
if( ! m_aFileName.Len() )
{
m_aFileName = getPdfDir( rInfo );
m_aFileName.Append( '/' );
m_aFileName.Append( rJobName );
m_aFileName.AppendAscii( ".pdf" );
}
break;
}
}
m_aPrinterGfx.Init( m_aJobData );
// set/clear backwards compatibility flag
bool bStrictSO52Compatibility = false;
std::hash_map<rtl::OUString, rtl::OUString, rtl::OUStringHash >::const_iterator compat_it =
pJobSetup->maValueMap.find( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "StrictSO52Compatibility" ) ) );
if( compat_it != pJobSetup->maValueMap.end() )
{
if( compat_it->second.equalsIgnoreAsciiCaseAscii( "true" ) )
bStrictSO52Compatibility = true;
}
m_aPrinterGfx.setStrictSO52Compatibility( bStrictSO52Compatibility );
return m_aPrintJob.StartJob( m_aTmpFile.Len() ? m_aTmpFile : m_aFileName, nMode, rJobName, rAppName, m_aJobData, &m_aPrinterGfx, bDirect ) ? sal_True : sal_False;
}
// -----------------------------------------------------------------------
sal_Bool PspSalPrinter::EndJob()
{
sal_Bool bSuccess = sal_False;
if( m_bIsPDFWriterJob )
bSuccess = sal_True;
else
{
bSuccess = m_aPrintJob.EndJob();
if( bSuccess )
{
// check for fax
if( m_bFax )
{
const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
// sendAFax removes the file after use
bSuccess = sendAFax( m_aFaxNr, m_aTmpFile, rInfo.m_aCommand );
}
else if( m_bPdf )
{
const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
bSuccess = createPdf( m_aFileName, m_aTmpFile, rInfo.m_aCommand );
}
}
}
vcl_sal::PrinterUpdate::jobEnded();
return bSuccess;
}
// -----------------------------------------------------------------------
sal_Bool PspSalPrinter::AbortJob()
{
sal_Bool bAbort = m_aPrintJob.AbortJob() ? sal_True : sal_False;
vcl_sal::PrinterUpdate::jobEnded();
return bAbort;
}
// -----------------------------------------------------------------------
SalGraphics* PspSalPrinter::StartPage( ImplJobSetup* pJobSetup, sal_Bool )
{
JobData::constructFromStreamBuffer( pJobSetup->mpDriverData, pJobSetup->mnDriverDataLen, m_aJobData );
m_pGraphics = new PspGraphics( &m_aJobData, &m_aPrinterGfx, m_bFax ? &m_aFaxNr : NULL, m_bSwallowFaxNo, m_pInfoPrinter );
m_pGraphics->SetLayout( 0 );
if( m_nCopies > 1 )
{
// in case user did not do anything (m_nCopies=1)
// take the default from jobsetup
m_aJobData.m_nCopies = m_nCopies;
m_aJobData.setCollate( m_nCopies > 1 && m_bCollate );
}
m_aPrintJob.StartPage( m_aJobData );
m_aPrinterGfx.Init( m_aPrintJob );
return m_pGraphics;
}
// -----------------------------------------------------------------------
sal_Bool PspSalPrinter::EndPage()
{
sal_Bool bResult = m_aPrintJob.EndPage();
m_aPrinterGfx.Clear();
return bResult ? sal_True : sal_False;
}
// -----------------------------------------------------------------------
sal_uLong PspSalPrinter::GetErrorCode()
{
return 0;
}
// -----------------------------------------------------------------------
struct PDFNewJobParameters
{
Size maPageSize;
sal_uInt16 mnPaperBin;
PDFNewJobParameters( const Size& i_rSize = Size(),
sal_uInt16 i_nPaperBin = 0xffff )
: maPageSize( i_rSize ), mnPaperBin( i_nPaperBin ) {}
bool operator!=(const PDFNewJobParameters& rComp ) const
{
Size aCompLSSize( rComp.maPageSize.Height(), rComp.maPageSize.Width() );
return
(maPageSize != rComp.maPageSize && maPageSize != aCompLSSize)
|| mnPaperBin != rComp.mnPaperBin
;
}
bool operator==(const PDFNewJobParameters& rComp) const
{
return ! this->operator!=(rComp);
}
};
struct PDFPrintFile
{
rtl::OUString maTmpURL;
PDFNewJobParameters maParameters;
PDFPrintFile( const rtl::OUString& i_rURL, const PDFNewJobParameters& i_rNewParameters )
: maTmpURL( i_rURL )
, maParameters( i_rNewParameters ) {}
};
sal_Bool PspSalPrinter::StartJob( const String* i_pFileName, const String& i_rJobName, const String& i_rAppName,
ImplJobSetup* i_pSetupData, vcl::PrinterController& i_rController )
{
OSL_TRACE( "StartJob with controller: pFilename = %s", i_pFileName ? rtl::OUStringToOString( *i_pFileName, RTL_TEXTENCODING_UTF8 ).getStr() : "<nil>" );
// mark for endjob
m_bIsPDFWriterJob = true;
// reset IsLastPage
i_rController.setLastPage( sal_False );
// update job data
if( i_pSetupData )
JobData::constructFromStreamBuffer( i_pSetupData->mpDriverData, i_pSetupData->mnDriverDataLen, m_aJobData );
OSL_ASSERT( m_aJobData.m_nPDFDevice > 0 );
m_aJobData.m_nPDFDevice = 1;
// possibly create one job for collated output
sal_Bool bSinglePrintJobs = sal_False;
beans::PropertyValue* pSingleValue = i_rController.getValue( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "PrintCollateAsSingleJobs" ) ) );
if( pSingleValue )
{
pSingleValue->Value >>= bSinglePrintJobs;
}
int nCopies = i_rController.getPrinter()->GetCopyCount();
bool bCollate = i_rController.getPrinter()->IsCollateCopy();
// notify start of real print job
i_rController.jobStarted();
// setup PDFWriter context
vcl::PDFWriter::PDFWriterContext aContext;
aContext.Version = vcl::PDFWriter::PDF_1_4;
aContext.Tagged = false;
aContext.EmbedStandardFonts = true;
aContext.DocumentLocale = Application::GetSettings().GetLocale();
aContext.ColorMode = i_rController.getPrinter()->GetPrinterOptions().IsConvertToGreyscales()
? vcl::PDFWriter::DrawGreyscale : vcl::PDFWriter::DrawColor;
// prepare doc info
aContext.DocumentInfo.Title = i_rJobName;
aContext.DocumentInfo.Creator = i_rAppName;
aContext.DocumentInfo.Producer = i_rAppName;
// define how we handle metafiles in PDFWriter
vcl::PDFWriter::PlayMetafileContext aMtfContext;
aMtfContext.m_bOnlyLosslessCompression = true;
boost::shared_ptr<vcl::PDFWriter> pWriter;
std::vector< PDFPrintFile > aPDFFiles;
boost::shared_ptr<Printer> pPrinter( i_rController.getPrinter() );
int nAllPages = i_rController.getFilteredPageCount();
i_rController.createProgressDialog();
bool bAborted = false;
PDFNewJobParameters aLastParm;
aContext.DPIx = pPrinter->ImplGetDPIX();
aContext.DPIy = pPrinter->ImplGetDPIY();
for( int nPage = 0; nPage < nAllPages && ! bAborted; nPage++ )
{
if( nPage == nAllPages-1 )
i_rController.setLastPage( sal_True );
// get the page's metafile
GDIMetaFile aPageFile;
vcl::PrinterController::PageSize aPageSize = i_rController.getFilteredPageFile( nPage, aPageFile );
if( i_rController.isProgressCanceled() )
{
bAborted = true;
if( nPage != nAllPages-1 )
{
i_rController.createProgressDialog();
i_rController.setLastPage( sal_True );
i_rController.getFilteredPageFile( nPage, aPageFile );
}
}
else
{
pPrinter->SetMapMode( MapMode( MAP_100TH_MM ) );
pPrinter->SetPaperSizeUser( aPageSize.aSize, true );
PDFNewJobParameters aNewParm( pPrinter->GetPaperSize(), pPrinter->GetPaperBin() );
// create PDF writer on demand
// either on first page
// or on paper format change - cups does not support multiple paper formats per job (yet?)
// so we need to start a new job to get a new paper format from the printer
// orientation switches (that is switch of height and width) is handled transparently by CUPS
if( ! pWriter ||
(aNewParm != aLastParm && ! i_pFileName ) )
{
if( pWriter )
{
pWriter->Emit();
}
// produce PDF file
OUString aPDFUrl;
if( i_pFileName )
aPDFUrl = *i_pFileName;
else
osl_createTempFile( NULL, NULL, &aPDFUrl.pData );
// normalize to file URL
if( aPDFUrl.compareToAscii( "file:", 5 ) != 0 )
{
// this is not a file URL, but it should
// form it into a osl friendly file URL
rtl::OUString aTmp;
osl_getFileURLFromSystemPath( aPDFUrl.pData, &aTmp.pData );
aPDFUrl = aTmp;
}
// save current file and paper format
aLastParm = aNewParm;
aPDFFiles.push_back( PDFPrintFile( aPDFUrl, aNewParm ) );
// update context
aContext.URL = aPDFUrl;
// create and initialize PDFWriter
#if defined __SUNPRO_CC
#pragma disable_warn
#endif
pWriter.reset( new vcl::PDFWriter( aContext, uno::Reference< beans::XMaterialHolder >() ) );
#if defined __SUNPRO_CC
#pragma enable_warn
#endif
}
pWriter->NewPage( TenMuToPt( aNewParm.maPageSize.Width() ),
TenMuToPt( aNewParm.maPageSize.Height() ),
vcl::PDFWriter::Portrait );
pWriter->PlayMetafile( aPageFile, aMtfContext, NULL );
}
}
// emit the last file
if( pWriter )
pWriter->Emit();
// handle collate, copy count and multiple jobs correctly
int nOuterJobs = 1;
if( bSinglePrintJobs )
{
nOuterJobs = nCopies;
m_aJobData.m_nCopies = 1;
}
else
{
if( bCollate )
{
if( aPDFFiles.size() == 1 && pPrinter->HasSupport( SUPPORT_COLLATECOPY ) )
{
m_aJobData.setCollate( true );
m_aJobData.m_nCopies = nCopies;
}
else
{
nOuterJobs = nCopies;
m_aJobData.m_nCopies = 1;
}
}
else
{
m_aJobData.setCollate( false );
m_aJobData.m_nCopies = nCopies;
}
}
// spool files
if( ! i_pFileName && ! bAborted )
{
bool bFirstJob = true;
for( int nCurJob = 0; nCurJob < nOuterJobs; nCurJob++ )
{
for( size_t i = 0; i < aPDFFiles.size(); i++ )
{
oslFileHandle pFile = NULL;
osl_openFile( aPDFFiles[i].maTmpURL.pData, &pFile, osl_File_OpenFlag_Read );
if( pFile )
{
osl_setFilePos( pFile, osl_Pos_Absolut, 0 );
std::vector< char > buffer( 0x10000, 0 );
// update job data with current page size
Size aPageSize( aPDFFiles[i].maParameters.maPageSize );
m_aJobData.setPaper( TenMuToPt( aPageSize.Width() ), TenMuToPt( aPageSize.Height() ) );
// update job data with current paperbin
m_aJobData.setPaperBin( aPDFFiles[i].maParameters.mnPaperBin );
// spool current file
FILE* fp = PrinterInfoManager::get().startSpool( pPrinter->GetName(), i_rController.isDirectPrint() );
if( fp )
{
sal_uInt64 nBytesRead = 0;
do
{
osl_readFile( pFile, &buffer[0], buffer.size(), &nBytesRead );
if( nBytesRead > 0 )
fwrite( &buffer[0], 1, nBytesRead, fp );
} while( nBytesRead == buffer.size() );
rtl::OUStringBuffer aBuf( i_rJobName.Len() + 8 );
aBuf.append( i_rJobName );
if( i > 0 || nCurJob > 0 )
{
aBuf.append( sal_Unicode(' ') );
aBuf.append( sal_Int32( i + nCurJob * aPDFFiles.size() ) );
}
PrinterInfoManager::get().endSpool( pPrinter->GetName(), aBuf.makeStringAndClear(), fp, m_aJobData, bFirstJob );
bFirstJob = false;
}
}
osl_closeFile( pFile );
}
}
}
// job has been spooled
i_rController.setJobState( bAborted ? view::PrintableState_JOB_ABORTED : view::PrintableState_JOB_SPOOLED );
// clean up the temporary PDF files
if( ! i_pFileName || bAborted )
{
for( size_t i = 0; i < aPDFFiles.size(); i++ )
{
osl_removeFile( aPDFFiles[i].maTmpURL.pData );
OSL_TRACE( "removed print PDF file %s\n", rtl::OUStringToOString( aPDFFiles[i].maTmpURL, RTL_TEXTENCODING_UTF8 ).getStr() );
}
}
return sal_True;
}
/*
* vcl::PrinterUpdate
*/
Timer* vcl_sal::PrinterUpdate::pPrinterUpdateTimer = NULL;
int vcl_sal::PrinterUpdate::nActiveJobs = 0;
void vcl_sal::PrinterUpdate::doUpdate()
{
::psp::PrinterInfoManager& rManager( ::psp::PrinterInfoManager::get() );
if( rManager.checkPrintersChanged( false ) )
{
SalDisplay* pDisp = GetX11SalData()->GetDisplay();
const std::list< SalFrame* >& rList = pDisp->getFrames();
for( std::list< SalFrame* >::const_iterator it = rList.begin();
it != rList.end(); ++it )
pDisp->SendInternalEvent( *it, NULL, SALEVENT_PRINTERCHANGED );
}
}
// -----------------------------------------------------------------------
IMPL_STATIC_LINK_NOINSTANCE( vcl_sal::PrinterUpdate, UpdateTimerHdl, void*, EMPTYARG )
{
if( nActiveJobs < 1 )
{
doUpdate();
delete pPrinterUpdateTimer;
pPrinterUpdateTimer = NULL;
}
else
pPrinterUpdateTimer->Start();
return 0;
}
// -----------------------------------------------------------------------
void vcl_sal::PrinterUpdate::update()
{
if( Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
return;
if( ! static_cast< X11SalInstance* >(GetSalData()->m_pInstance)->isPrinterInit() )
{
// #i45389# start background printer detection
psp::PrinterInfoManager::get();
return;
}
if( nActiveJobs < 1 )
doUpdate();
else if( ! pPrinterUpdateTimer )
{
pPrinterUpdateTimer = new Timer();
pPrinterUpdateTimer->SetTimeout( 500 );
pPrinterUpdateTimer->SetTimeoutHdl( STATIC_LINK( NULL, vcl_sal::PrinterUpdate, UpdateTimerHdl ) );
pPrinterUpdateTimer->Start();
}
}
// -----------------------------------------------------------------------
void vcl_sal::PrinterUpdate::jobEnded()
{
nActiveJobs--;
if( nActiveJobs < 1 )
{
if( pPrinterUpdateTimer )
{
pPrinterUpdateTimer->Stop();
delete pPrinterUpdateTimer;
pPrinterUpdateTimer = NULL;
doUpdate();
}
}
}