/***************************************************************************
                            psprinter.cpp
                         -------------------
begin                : dom ene 22 2006
copyright            : (C) 2002-2006 by InfoSiAL S.L.
email                : mail@infosial.com
***************************************************************************/
/***************************************************************************
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; version 2 of the License.               *
 ***************************************************************************/
/***************************************************************************
   Este  programa es software libre. Puede redistribuirlo y/o modificarlo
   bajo  los  términos  de  la  Licencia  Pública General de GNU   en  su
   versión 2, publicada  por  la  Free  Software Foundation.
 ***************************************************************************/

#include <qpaintdevicemetrics.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qdir.h>
#include <qapplication.h>
#include <stdlib.h>

#include "psprinter.h"
#include "qpsprinter.h"

#if defined(Q_OS_WIN32)
#include <sys/stat.h>
#include <io.h>
#include <fcntl.h>
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#endif

#if defined(_WS_X11_)
#include <X11/Xlib.h>
#endif

#if defined(_OS_OS2EMX_)
#define INCL_DOSFILEMGR
#define INCL_DOSERRORS
#include <os2.h>
#endif

#if defined(_OS_QNX_)
#include <process.h>
#endif

// NOT REVISED

class PSPrinterUnixPrivate {
  public:
    bool marginsSpecified;
    uint topMargin;
    uint leftMargin;
    uint bottomMargin;
    uint rightMargin;
};


/*****************************************************************************
  PSPrinter member functions
 *****************************************************************************/

// PSPrinter states

#define PST_IDLE        0
#define PST_ACTIVE      1
#define PST_ERROR       2
#define PST_ABORTED     3


/*!
  Constructs a printer paint device.
*/

PSPrinter::PSPrinter( PrinterMode m )
    : QPaintDevice( QInternal::Printer | QInternal::ExternalDevice ) {
  pdrv = 0;
  pid = 0;
  orient = Portrait;
  page_size = A4;
  page_order = FirstPageFirst;
  color_mode = GrayScale;
  ncopies = 1;
  from_pg = to_pg = min_pg = max_pg = 0;
  state = PST_IDLE;
  output_file = FALSE;
  to_edge     = FALSE;
  paper_source = OnlyOne;
  buffer = 0;
  switch ( m ) {
    case Compatible:
    case PrinterResolution:
    case ScreenResolution:
    case HighResolution: {
      res = 72;
    }
    break;
  }

  D = new PSPrinterUnixPrivate;
  D->marginsSpecified = FALSE;
}

/*!
  Destructs the printer paint device and cleans up.
*/

PSPrinter::~PSPrinter() {
  if ( pdrv )
    delete pdrv;
  if ( buffer ) {
    buffer->close();
    delete buffer;
  }
}


/*!
  Advances to a new page on the printer.
  Returns TRUE if successful, otherwise FALSE.
*/

bool PSPrinter::newPage() {
  if ( state == PST_ACTIVE && pdrv )
    return ( ( QPSPrinter* ) pdrv ) ->cmd( QPSPrinter::NewPage, 0, 0 );
  return FALSE;
}


/*!
  Aborts the print job.
  Returns TRUE if successful, otherwise FALSE.
  \sa aborted()
*/

bool PSPrinter::abort() {
  if ( state == PST_ACTIVE && pdrv ) {
    ( ( QPSPrinter* ) pdrv ) ->cmd( QPSPrinter::AbortPrinting, 0, 0 );
    state = PST_ABORTED;
  }
  return state == PST_ABORTED;
}


bool PSPrinter::aborted() const {
  return state == PST_ABORTED;
}


void PSPrinter::setPrinterName( const QString &name ) {
  if ( state != 0 ) {
#if defined(QT_CHECK_STATE)
    qWarning( "PSPrinter::setPrinterName: Cannot do this during printing" );
#endif

    return ;
  }
  printer_name = name;
}


bool PSPrinter::setup( QWidget * /*parent*/ ) {
  return true;
  //return QPrintDialog::getPrinterSetup( this );
}

#ifdef _WS_X11_
static void closeAllOpenFds() {
  // hack time... getting the maximum number of open
  // files, if possible.  if not we assume it's the
  // larger of 256 and the fd we got
  int i;
#if defined(_OS_OS2EMX_)

  LONG req_count = 0;
  ULONG rc, handle_count;
  rc = DosSetRelMaxFH ( &req_count, &handle_count );
  /* if (rc != NO_ERROR) ... */
  i = ( int ) handle_count;
#elif defined(_SC_OPEN_MAX)

  i = ( int ) sysconf( _SC_OPEN_MAX );
#elif defined(_POSIX_OPEN_MAX)

  i = ( int ) _POSIX_OPEN_MAX;
#elif defined(OPEN_MAX)

  i = ( int ) OPEN_MAX;
#else

  i = QMAX( 256, fds[ 0 ] );
#endif // ways-to-set i

  while ( --i > 0 )
    ::close( i );
}
#endif

/*!
  \internal
  Handles painter commands to the printer.
*/

extern int qWinVersion();

bool PSPrinter::cmd( int c, QPainter * paint, QPDevCmdParam * p ) {
  if ( c == PdcBegin ) {
    if ( state == PST_IDLE ) {
      if ( output_file ) {
        int fd = 0;
#if defined(Q_OS_WIN32)

        if ( qWinVersion() & Qt::WV_NT_based )
          fd = _wopen( ( const wchar_t * ) qt_winTchar( output_filename, TRUE ), _O_CREAT | _O_BINARY | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE );
        else
          fd = _open( output_filename.ascii(), _O_CREAT | _O_BINARY | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE );
#else

        fd = ::open( output_filename.local8Bit(), O_CREAT | O_NOCTTY | O_TRUNC | O_WRONLY, 0666 );
#endif

        if ( fd >= 0 ) {
          pdrv = new QPSPrinter( this, fd );
          state = PST_ACTIVE;
        }
      } else {
        QString pr;
        if ( printer_name )
          pr = printer_name;
#if defined(Q_OS_WIN32)
        // Not implemented
        // lpr needs -Sserver argument

        if ( buffer ) {
          buffer->close();
          delete buffer;
        }
        buffer = new QBuffer();
        buffer->open( IO_WriteOnly );
        pdrv = new QPSPrinter( this, buffer );
        state = PST_ACTIVE;
#else

        QApplication::flushX();
        int fds[ 2 ];
        if ( pipe( fds ) != 0 ) {
          qWarning( "QPSPrinter: could not open pipe to print" );
          state = PST_ERROR;
          return FALSE;
        }
#if 0 && defined(_OS_OS2EMX_)
        // this code is still not used, and maybe it's not
        // usable either, any more.  if you want to use it,
        // you may need to fix it first.

        // old comment:

        // this code is usable but not in use.  spawn() is
        // preferable to fork()/exec() for very large
        // programs.  if fork()/exec() is a problem and you
        // use OS/2, remove '0 && ' from the #if.
        int tmp;
        tmp = dup( 0 );
        dup2( fds[ 0 ], 0 );
        ::close( fds[ 0 ] );
        fcntl( tmp, F_SETFD, FD_CLOEXEC );
        fcntl( fds[ 1 ], F_SETFD, FD_CLOEXEC );
        pr.prepend( option_string ? option_string : "-P" ); // ###
        if ( spawnlp( P_NOWAIT, print_prog.data(), print_prog.data(),
                      pr.data(), output->name(), 0 ) == -1 ) {
          ;                   // couldn't exec, ignored
        }
        dup2( tmp, 0 );
        ::close( tmp );
        pdrv = new QPSPrinter( this, fds[ 1 ] );
        state = PST_ACTIVE;
#else

        pid = fork();
        if ( pid == 0 ) {       // child process
          // if possible, exit quickly, so the actual lp/lpr
          // becomes a child of init, and ::waitpid() is
          // guaranteed not to wait.
          if ( fork() > 0 ) {
#ifdef _WS_X11_
            closeAllOpenFds();
#endif

            // try to replace this process with "true" - this prevents
            // global destructors from being called (that could possibly
            // do wrong things to the parent process)
            ( void ) execlp( "true", "true", 0 );
            ( void ) execl( "/bin/true", "true", 0 );
            ( void ) execl( "/usr/bin/true", "true", 0 );
            exit( 0 );
          }
          dup2( fds[ 0 ], 0 );
#if defined(_WS_X11_)

          closeAllOpenFds();
#endif // _WS_X11_

          if ( print_prog ) {
            pr.prepend( option_string ? option_string :
                        QString::fromLatin1( "-P" ) );
            ( void ) execlp( print_prog.ascii(), print_prog.ascii(),
                             pr.ascii(), 0 );
          } else {
            // if no print program has been specified, be smart
            // about the option string too.
            const char * lprarg = 0;
            QString lprhack;
            const char * lparg = 0;
            QString lphack;
            if ( pr || option_string ) {
              lprhack = pr;
              lprhack.prepend( option_string ? option_string :
                               QString::fromLatin1( "-P" ) );
              lprarg = lprhack.ascii();
              lphack = pr;
              lphack.prepend( option_string ? option_string :
                              QString::fromLatin1( "-d" ) );
              lparg = lphack.ascii();
            }
            ( void ) execlp( "lp", "lp", lparg, 0 );
            ( void ) execlp( "lpr", "lpr", lprarg, 0 );
            ( void ) execl( "/bin/lp", "lp", lparg, 0 );
            ( void ) execl( "/bin/lpr", "lpr", lprarg, 0 );
            ( void ) execl( "/usr/bin/lp", "lp", lparg, 0 );
            ( void ) execl( "/usr/bin/lpr", "lpr", lprarg, 0 );
          }
          // if we couldn't exec anything, close the fd,
          // wait for a second so the parent process (the
          // child of the GUI process) has exited.  then
          // exit.
          ::close( 0 );
          ( void ) ::sleep( 1 );
          ::exit( 0 );
        } else {                // parent process
          ::close( fds[ 0 ] );
          pdrv = new QPSPrinter( this, fds[ 1 ] );
          state = PST_ACTIVE;
        }
#endif // else part of _OS_OS2EMX_
#endif // else part for #if Q_OS_WIN32

      }
      if ( state == PST_ACTIVE && pdrv )
        return ( ( QPSPrinter* ) pdrv ) ->cmd( c, paint, p );
    } else {
      // ignore it?  I don't know
    }
  } else {
    bool r = FALSE;
    if ( state == PST_ACTIVE && pdrv ) {
      r = ( ( QPSPrinter* ) pdrv ) ->cmd( c, paint, p );
      if ( c == PdcEnd ) {
        state = PST_IDLE;
        delete pdrv;
        pdrv = 0;
        if ( buffer )
          buffer->close();
#if !defined(Q_OS_WIN32)

        if ( pid ) {
          ( void ) ::waitpid( pid, 0, 0 );
          pid = 0;
        }
#endif

      }
    }
    return r;
  }
  return TRUE;
}



#define MM(n) int((n * 720 + 127) / 254)
#ifdef IN
#undef IN
#endif
#define IN(n) int(n * 72)

struct PaperSize {
  int width, height;
};

static PaperSize paperSizes[ PSPrinter::NPageSize ] = {
  { MM( 210 ), MM( 297 ) },         // A4
  { MM( 176 ), MM( 250 ) },         // B5
  { IN( 8.5 ), IN( 11 ) },          // Letter
  { IN( 8.5 ), IN( 14 ) },          // Legal
  { IN( 7.5 ), IN( 10 ) },          // Executive
  { MM( 841 ), MM( 1189 ) },        // A0
  { MM( 594 ), MM( 841 ) },         // A1
  { MM( 420 ), MM( 594 ) },         // A2
  { MM( 297 ), MM( 420 ) },         // A3
  { MM( 148 ), MM( 210 ) },         // A5
  { MM( 105 ), MM( 148 ) },         // A6
  { MM( 74 ), MM( 105 ) },           // A7
  { MM( 52 ), MM( 74 ) },           // A8
  { MM( 37 ), MM( 52 ) },           // A9
  { MM( 1000 ), MM( 1414 ) },       // B0
  { MM( 707 ), MM( 1000 ) },        // B1
  { MM( 31 ), MM( 44 ) },           // B10
  { MM( 500 ), MM( 707 ) },         // B2
  { MM( 353 ), MM( 500 ) },         // B3
  { MM( 250 ), MM( 353 ) },         // B4
  { MM( 125 ), MM( 176 ) },         // B6
  { MM( 88 ), MM( 125 ) },          // B7
  { MM( 62 ), MM( 88 ) },           // B8
  { MM( 44 ), MM( 62 ) },           // B9
  { MM( 163 ), MM( 229 ) },         // C5E
  { MM( 105 ), MM( 241 ) },         // Comm10E
  { MM( 110 ), MM( 220 ) },         // DLE
  { MM( 210 ), MM( 330 ) },         // Folio
  { MM( 432 ), MM( 279 ) },         // Ledger
  { MM( 279 ), MM( 432 ) },         // Tabloid
};


/*!
  Internal implementation of the virtual QPaintDevice::metric() function.

  Use the QPaintDeviceMetrics class instead.

  \internal
  Hard coded return values for PostScript under X.
*/

int PSPrinter::metric( int m ) const {
  int val;
  PageSize s = pageSize();
#if defined(QT_CHECK_RANGE)
  Q_ASSERT( ( uint )s < ( uint )NPageSize );
#endif
  switch ( m ) {
    case QPaintDeviceMetrics::PdmWidth:
      val = orient == Portrait ? paperSizes[s].width : paperSizes[s].height;
      if ( res != 72 )
        val = ( val * res + 36 ) / 72;
      if ( !fullPage() ) {
        if ( D->marginsSpecified )
          val -= D->leftMargin + D->rightMargin;
        else
          val -= 2 * margins().width();
      }
      break;
    case QPaintDeviceMetrics::PdmHeight:
      val = orient == Portrait ? paperSizes[s].height : paperSizes[s].width;
      if ( res != 72 )
        val = ( val * res + 36 ) / 72;
      if ( !fullPage() ) {
        if ( D->marginsSpecified )
          val -= D->topMargin + D->bottomMargin;
        else
          val -= 2 * margins().height();
      }
      break;
    case QPaintDeviceMetrics::PdmDpiX:
      val = res;
      break;
    case QPaintDeviceMetrics::PdmDpiY:
      val = res;
      break;
    case QPaintDeviceMetrics::PdmPhysicalDpiX:
    case QPaintDeviceMetrics::PdmPhysicalDpiY:
      val = 72;
      break;
    case QPaintDeviceMetrics::PdmWidthMM:
      // double rounding error here.  hooray.
      val = metric( QPaintDeviceMetrics::PdmWidth );
      val = ( val * 254 + 5 * res ) / ( 10 * res );
      break;
    case QPaintDeviceMetrics::PdmHeightMM:
      val = metric( QPaintDeviceMetrics::PdmHeight );
      val = ( val * 254 + 5 * res ) / ( 10 * res );
      break;
    case QPaintDeviceMetrics::PdmNumColors:
      val = 16777216;
      break;
    case QPaintDeviceMetrics::PdmDepth:
      val = 24;
      break;
    default:
      val = 0;
#if defined(QT_CHECK_RANGE)
      qWarning( "QPixmap::metric: Invalid metric command" );
#endif
  }
  return val;
}


/*!  Returns the width of the left/right and top/bottom margins of the
printer.  This is a best-effort guess, not based on perfect knowledge.

If you have called setFullPage( TRUE ) (this is recommended for
high-quality printing), margins().width() may be treated as the
smallest sane left/right margin you can use, and margins().height() as
the smallest sane top/bottom margins you can use.

If you have called setFullPage( FALSE ) (this is the default),
margins() is automatically subtracted from the pageSize() by PSPrinter.

\sa setFullPage() QPaintDeviceMetrics PageSize
*/

QSize PSPrinter::margins() const {
  if ( D->marginsSpecified )
    return QSize( D->leftMargin, D->topMargin );

  if ( orient == Portrait )
    return QSize( res / 2, res / 3 );

  return QSize( res / 3, res / 2 );
}

#if defined(Q_WS_WIN)
void PSPrinter::setIdle() {}
void PSPrinter::setActive() {}
void PSPrinter::reinit() {}
void PSPrinter::readPdlg( void* ) {}
void PSPrinter::readPdlgA( void* ) {}
void PSPrinter::writeDevmode( HANDLE ) {}
void PSPrinter::writeDevmodeA( HANDLE ) {}
short PSPrinter::winPageSize() const {}
#endif

void PSPrinter::setMargins( uint top, uint left, uint bottom, uint right ) {
  D->topMargin = top;
  D->leftMargin = left;
  D->bottomMargin = bottom;
  D->rightMargin = right;
  D->marginsSpecified = TRUE;
}

void PSPrinter::margins( uint *top, uint *left, uint *bottom, uint *right ) const {
  if ( !D->marginsSpecified ) {
    int x = orient == Portrait ? res / 2 : res / 3;
    int y = orient == Portrait ? res / 3 : res / 2;
    *top = *bottom = y;
    *left = *right = x;
  } else {
    *top = D->topMargin;
    *left = D->leftMargin;
    *bottom = D->bottomMargin;
    *right = D->rightMargin;
  }
}

void PSPrinter::setOutputToFile( bool enable ) {
  if ( state != 0 ) {
#if defined(QT_CHECK_STATE)
    qWarning( "PSPrinter::setOutputToFile: Cannot do this during printing" );
#endif

    return ;
  }
  output_file = enable;
}


/*!
    \fn QString PSPrinter::outputFileName() const

    Returns the name of the output file. There is no default file
    name.

    \sa setOutputFileName(), setOutputToFile()
*/

/*!
    Sets the name of the output file to \a fileName.

    Setting a null or empty name (0 or "") disables output to a file,
    i.e. calls setOutputToFile(FALSE). Setting a non-empty name
    enables output to a file, i.e. calls setOutputToFile(TRUE).

    This function is currently only supported under X11.

    \sa outputFileName(), setOutputToFile()
*/

void PSPrinter::setOutputFileName( const QString &fileName ) {
  if ( state != 0 ) {
#if defined(QT_CHECK_STATE)
    qWarning( "PSPrinter::setOutputFileName: Cannot do this during printing" );
#endif

    return ;
  }
  output_filename = fileName;
  output_file = !output_filename.isEmpty();
}

void PSPrinter::setPrintProgram( const QString &printProg ) {
  print_prog = printProg;
}

void PSPrinter::setDocName( const QString &name ) {
  if ( state != 0 ) {
#if defined(QT_CHECK_STATE)
    qWarning( "PSPrinter::setDocName: Cannot do this during printing" );
#endif

    return ;
  }
  doc_name = name;
}

void PSPrinter::setCreator( const QString &creator ) {
  creator_name = creator;
}


void PSPrinter::setOrientation( Orientation orientation ) {
  orient = orientation;
#if defined(Q_WS_WIN)

  reinit();
#endif
}

void PSPrinter::setPageSize( PageSize newPageSize ) {
  if ( newPageSize > NPageSize ) {
#if defined(QT_CHECK_STATE)
    qWarning( "PSPrinter::SetPageSize: illegal page size %d", newPageSize );
#endif

    return ;
  }
  page_size = newPageSize;
#if defined(Q_WS_WIN)

  reinit();
#endif
}

void PSPrinter::setPageOrder( PageOrder newPageOrder ) {
  page_order = newPageOrder;
#if defined(Q_WS_WIN)

  reinit();
#endif
}


PSPrinter::PageOrder PSPrinter::pageOrder() const {
  return page_order;
}


void PSPrinter::setColorMode( ColorMode newColorMode ) {
  color_mode = newColorMode;
#if defined(Q_WS_WIN)

  reinit();
#endif
}


PSPrinter::ColorMode PSPrinter::colorMode() const {
  return color_mode;
}



void PSPrinter::setFromTo( int fromPage, int toPage ) {
  if ( state != 0 ) {
#if defined(QT_CHECK_STATE)
    qWarning( "PSPrinter::setFromTo: Cannot do this during printing" );
#endif

    return ;
  }
  from_pg = fromPage;
  to_pg = toPage;
}


void PSPrinter::setMinMax( int minPage, int maxPage ) {
  min_pg = minPage;
  max_pg = maxPage;
  if ( from_pg == 0 || from_pg < minPage )
    from_pg = minPage;
  if ( to_pg == 0 || to_pg > maxPage )
    to_pg = maxPage;
}


void PSPrinter::setCollateCopies( bool on ) {
  if ( !collateCopiesEnabled() && on )
    setCollateCopiesEnabled( on );
  usercolcopies = on;
}


void PSPrinter::setNumCopies( int numCopies ) {
  ncopies = numCopies;
#if defined(Q_WS_WIN)

  reinit();
#endif
}


QString PSPrinter::printerSelectionOption() const {
  return option_string;
}

void PSPrinter::setPrinterSelectionOption( const QString & option ) {
  option_string = option;
}


void PSPrinter::setFullPage( bool fp ) {
  to_edge = fp;
}


bool PSPrinter::fullPage() const {
  return to_edge;
}


void PSPrinter::setResolution( int /*dpi*/ ) {
  //res = dpi;
  //res_set = TRUE;
}


int PSPrinter::resolution() const {
  return res;
}

void PSPrinter::setPaperSource( PaperSource source ) {
  paper_source = source;
#if defined(Q_WS_WIN)

  reinit();
#endif
}


PSPrinter::PaperSource PSPrinter::paperSource() const {
  return paper_source;
}

QIODevice * PSPrinter::outDevice() {
  return buffer;
}
