/** 
 * Copyright (C) 1997-2002 the KGhostView authors. See file AUTHORS.
 * 	
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/**
 * This is essentially the KGhostview widget stripped down and without all the
 * KTMainWindow dependencies (there were quite a bit with menus, toolbars,
 * etc.. accessed all over the place). It would probably make sense to make
 * that use this in the future (ported by mosfet@kde.org).
 */

#include <math.h>

#include <algorithm>
#include <memory>

#include <qdragobject.h>
#include <qlayout.h>
#include <qlistbox.h>
#include <qtimer.h>
#include <qwidget.h>

#include <kapplication.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <kfilterdev.h>
#include <kinstance.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kmimetype.h>
#include <knotifyclient.h>
#include <kprinter.h>
#include <kprocess.h>
#include <ktempfile.h>
#include <kio/netaccess.h>

#include <config.h>

#include "gotodialog.h"
#include "infodialog.h"
#include "kdscerrordialog.h"
#include "kgv_view.h"
#include "version.h"

extern "C" {
#include "ps.h"
}

#include "kgv_miniwidget.h"

#define BUFFER_SIZE (8192)

using namespace std;
using namespace KGV;

KGVMiniWidget::KGVMiniWidget( KGVPart* part, const char* name ) : 
    QObject( part, name ),
    _part( part )
{
    _marklist = part->markList();
    _psWidget = 0;

    _magnification = 1;
    _fixedMagnifications << 0.125 << 0.25 << 0.3333 << 0.5 << 0.6667 << 0.75 
                         << 1 << 1.25 << 1.50 << 2 << 3 << 4 << 6 << 8;
     
    KLocale locale( "kghostview" );
    _fallBackPageMedia = pageSizeToString( 
              static_cast< QPrinter::PageSize >( locale.pageSize() ) );
    
    _usePageLabels = true;
    
    _overrideOrientation = CDSC_ORIENT_UNKNOWN;
    
    _currentPage = -1;
    _fileName = "";
    
    _isFileOpen = false;
    _psFile = 0;
    _dsc = 0;
    
    _tmpUnzipped = 0;
    _tmpFromPDF  = 0;
    _tmpDSC      = 0;

    mGotoDialog = 0;
    mInfoDialog = 0;
    
    connect( this, SIGNAL( newPageShown( int ) ), 
             SLOT( updateStatusBarText( int ) ) );

    readSettings();
    
    _pdf2dsc = new Pdf2dsc( _interpreterPath, this );
    connect( _pdf2dsc, SIGNAL( finished( bool ) ),
             SLOT( openPDFFileContinue( bool ) ) );

}

KGVMiniWidget::~KGVMiniWidget()
{
    if( mGotoDialog != 0 )
    {
	delete mGotoDialog;
    	mGotoDialog = 0;
    }
    reset();
}

void KGVMiniWidget::readSettings()
{
    KConfigGroup ghostscript( KGVFactory::instance()->config(), "Ghostscript" );
    _interpreterPath = ghostscript.readPathEntry( "Interpreter" );
}

QString KGVMiniWidget::pageSizeToString( QPrinter::PageSize pageSize )
{
    switch( pageSize )
    {
    case QPrinter::A3:     return "A3";
    case QPrinter::A4:     return "A4";
    case QPrinter::A5:     return "A5";
    case QPrinter::B4:     return "B4";
    case QPrinter::Ledger: return "Ledger";
    case QPrinter::Legal:  return "Legal";
    case QPrinter::Letter: return "Letter";
    default:               return "Unknown";
    }
}

/*- OPENING and READING ---------------------------------------------------*/

void KGVMiniWidget::openFile( const QString& name, const QString& mimetype )
{
    kdDebug(4500) << "KGVMiniWidget::openFile" << endl;
    
    reset();
    _fileName = name;
    _mimetype = mimetype;
    
    QTimer::singleShot( 0, this, SLOT( doOpenFile() ) );
}
    
void KGVMiniWidget::doOpenFile()
{
    QFileInfo fileInfo( _fileName );
    if( !fileInfo.exists() ) 
    {
	KMessageBox::sorry( _part->widget(),
		i18n( "<qt>Could not open <nobr><strong>%1</strong></nobr>: "
		      "File does not exist.</qt>" )
		.arg( _fileName ) );
	emit canceled( QString() );
	return;
    }
    if( !fileInfo.isReadable() )
    {
    	KMessageBox::sorry( _part->widget(),
		i18n( "<qt>Could not open <nobr><strong>%1</strong></nobr>: "
		      "Permission denied.</qt>" )
		.arg( _fileName ) );
	emit canceled( QString() );
	return;
    }
   
    if( _mimetype == "application/x-gzip" )
    {
	uncompressFile();
	KMimeType::Ptr mimetype = KMimeType::findByPath( _fileName );
	kdDebug(4500) << "KGVMiniWidget::mimetype: " << mimetype->name() 
	              << endl;
	_mimetype = mimetype->name();
    }	
    
    // If the file contains a PDF document, create a DSC description file
    // of the PDF document. This can be passed to Ghostscript just like
    // an ordinary PS file.
    if( _mimetype == "application/pdf" ) 
    {
	_tmpDSC = new KTempFile( QString::null, ".ps" );
	Q_CHECK_PTR( _tmpDSC );
	if( _tmpDSC->status() != 0 ) {
	    KMessageBox::error( _part->widget(),
		    i18n( "Could not create temporary file: %1" )
		    .arg( strerror( _tmpDSC->status() ) ) );
	    emit canceled( QString() );
	    return;
	}
	
	// When pdf2dsc has finished the program will continue with 
	// openPDFFileContinue.
	_pdf2dsc->run( _fileName, _tmpDSC->name() );
	return;
    }
    else if( _mimetype == "application/postscript" 
          || _mimetype == "application/illustrator" 
          || _mimetype == "image/x-eps" 
	  || _mimetype == "text/plain" )
    {
	_format = PS;
	openPSFile();
	return;
    }
    else 
    {
	KMessageBox::sorry( _part->widget(),
	        i18n( "<qt>Could not open <nobr><strong>%1</strong></nobr> "
	              "which has type <strong>%2</strong>. KGhostview can "
	              "only load PostScript (.ps, .eps) and Portable "
	              "Document Format (.pdf) files.</qt>" )
		      .arg( _fileName )
		      .arg( _mimetype ) );
	emit canceled( QString() );
	return;
    }
}

void KGVMiniWidget::uncompressFile()
{
    // If the file is gzipped, unzip it to the temporary file _tmpUnzipped.
    kdDebug(4500) << "KGVMiniWidget: unzipping file" << endl;
    
    _tmpUnzipped = new KTempFile;
    Q_CHECK_PTR( _tmpUnzipped );
    if( _tmpUnzipped->status() != 0 ) 
    {
	KMessageBox::error( _part->widget(),
		i18n( "Could not create temporary file: %2" )
		.arg( strerror( _tmpUnzipped->status() ) ) );
	emit canceled( QString() );
	return;
    }

    auto_ptr<QIODevice> filterDev( KFilterDev::deviceForFile( _fileName ) );
    if( !filterDev->open( IO_ReadOnly ) ) 
    {
	KMessageBox::error( _part->widget(),
		i18n( "<qt>Could not unzip <nobr><strong>%1</strong></nobr>.</qt>" )
		.arg( _fileName ) );
	emit canceled( QString() );
	return;
    }

    QByteArray buf( BUFFER_SIZE );
    int read = 0, wrtn = 0;
    while( ( read = filterDev->readBlock( buf.data(), buf.size() ) )
	    > 0 ) 
    {
	wrtn = _tmpUnzipped->file()->writeBlock( buf.data(), read );
	if( read != wrtn )
	    break;
    }

    if( read != 0 ) 
    {
	KMessageBox::error( _part->widget(),
	    i18n( "<qt>Could not unzip <nobr><strong>%1</strong></nobr>.</qt>" )
	    .arg( _fileName ) );
	emit canceled( QString() );
	return;
    }

    _tmpUnzipped->close();
    _fileName = _tmpUnzipped->name();
}

void KGVMiniWidget::openPDFFileContinue( bool pdf2dscResult )
{	
    kdDebug(4500) << "KGVMiniWidget::openPDFFileContinue" << endl;
    
    if( !pdf2dscResult ) 
    {
	KMessageBox::error( _part->widget(),
		i18n( "<qt>Could not open file <nobr><strong>%1</strong></nobr>.</qt>" ) 
		.arg( _fileName ) );
	emit canceled( QString() );
	return;
    }

    _tmpDSC->close();
    _format = PDF;

    openPSFile(_tmpDSC->name());
}

void KGVMiniWidget::openPSFile(const QString &file)
{
    QString fileName = file.isEmpty() ? _fileName : file;
    kdDebug(4500) << "KGVMiniWidget::openPSFile" << endl;
    
    FILE* fp = fopen( QFile::encodeName( fileName ), "r");
    if( fp == 0 ) 
    {
	KMessageBox::error( _part->widget(),
		i18n( "<qt>Error opening file <nobr><strong>%1</strong></nobr>: %2</qt>" )
		.arg( fileName )
		.arg( strerror( errno ) ) );
	emit canceled( "" );
	return;
    }
    else 
    {
	_psFile = fp;
	_isFileOpen = true;    
	scanDSC();
	buildTOC();
	_psWidget->setFileName( _fileName, dsc()->isStructured() );
	emit completed();
    }
}

void KGVMiniWidget::fileChanged( const QString& name )
{
    kdDebug(4500) << "KGVMiniWidget: fileChanged " << name << endl;

    if( !_psFile )
	return;

    unsigned int savepage = _currentPage;

    /*
    if( openFile( name ) )
	goToPage( savepage );
    else
	emit fileChangeFailed();
    */
}

void KGVMiniWidget::scanDSC()
{
    _dsc = new KDSC();
    
    char buf[4096];
    int  count;
    /*
    QTime clock;
    clock.start();
    */
    while( ( count = fread( buf, sizeof(char), 4096, _psFile ) ) != 0 )
    {
	_dsc->scanData( buf, count );
	/*
	if( clock.elapsed() > 10 )
	{
	    kapp->processEvents();
	    clock.start();
	}
	*/
    }
    _dsc->fixup();
}

void KGVMiniWidget::reset()
{
    _pdf2dsc->kill();
    _isFileOpen = false;
  
    /*
    if( _psWidget )
	_psWidget->disableInterpreter();
    */

    // return to document defaults
    _overrideOrientation = CDSC_ORIENT_UNKNOWN;
    _overridePageMedia = QString::null;
    emit setStatusBarText( "" );

    // Reset to a known state
    _currentPage = -1;
    
    if( _dsc ) 
    {
	delete _dsc; 
	_dsc = 0;
    }
    
    if( _psFile )
    {
        fclose( _psFile );
	_psFile = 0;
    }
    
    clearTemporaryFiles();
}

void KGVMiniWidget::configureGhostscript()
{
    if( _psWidget->configure() )
	redisplay();
}

void KGVMiniWidget::setPSWidget( KPSWidget* psWidget )
{
    _psWidget = psWidget;
    setMagnification( _magnification );
    connect( _psWidget, SIGNAL( pageFinished() ), 
             this, SLOT( sendPage() ) );
}

void KGVMiniWidget::goToPage()
{
  if (mGotoDialog==0)
    {
      mGotoDialog = new GotoDialog( _part->widget(), "goto", false );
      connect( mGotoDialog, SIGNAL(gotoPage(int)), this, SLOT(goToPage(int)) );
    }

  mGotoDialog->show();

}

void KGVMiniWidget::info()
{
    if( !isFileOpen() ) 
	return;
    
    mInfoDialog = new InfoDialog( _part->widget(), "info", true );
    mInfoDialog->setup( _fileName, dsc()->dsc_title(), dsc()->dsc_date() );
    mInfoDialog->exec();
    delete mInfoDialog;
}

void KGVMiniWidget::goToPage( int page )
{
    if( _currentPage != page ) {
	_currentPage = page;
	showPage( page );
    }
}

void KGVMiniWidget::zoomIn()
{
    QValueList<double>::ConstIterator it = _fixedMagnifications.begin();
    while( it != _fixedMagnifications.end() && *it <= magnification() ) ++it;
    
    if( it != _fixedMagnifications.end() )
	setMagnification( *it );
}

void KGVMiniWidget::zoomOut()
{
    QValueList<double>::ConstIterator it = _fixedMagnifications.begin();
    while( it != _fixedMagnifications.end() && *it < magnification() ) ++it;
   
    if( it != _fixedMagnifications.begin() ) 
        setMagnification( *(--it) );
}

bool KGVMiniWidget::atMaxZoom() const
{
    QValueList<double>::ConstIterator it = _fixedMagnifications.begin();
    while( it != _fixedMagnifications.end() && *it <= magnification() ) ++it;
    
    return (it == _fixedMagnifications.end());
}

bool KGVMiniWidget::atMinZoom() const
{
    QValueList<double>::ConstIterator it = _fixedMagnifications.begin();
    while( it != _fixedMagnifications.end() && *it < magnification() ) ++it;
   
    return (it == _fixedMagnifications.begin());
}

/*
void KGVMiniWidget::fitWidth( unsigned int width )
{
    setMagnification( ( (double)width / _psWidget->logicalDpiX() ) / 
                      ( (double)boundingBox().width() / 72) );
}

void KGVMiniWidget::fitHeight( unsigned int height )
{
    setMagnification( ( (double)height / _psWidget->logicalDpiY() ) / 
                      ( (double)boundingBox().height() / 72) );
}
*/

void KGVMiniWidget::firstPage()
{
    goToPage( 0 );
}

void KGVMiniWidget::lastPage()
{
    goToPage( dsc()->page_count() - 1 );
}

bool KGVMiniWidget::prevPage()
{
    int new_page = 0;

    if( dsc()->isStructured() ) {
	new_page = _currentPage - 1;
	if( new_page < 0 )
	    return false;
    }

    goToPage( new_page );
    return true;
}

bool KGVMiniWidget::nextPage()
{
    int new_page = 0;

    if( dsc()->isStructured() ) {
	new_page = _currentPage + 1;
	if( (unsigned int)new_page >= dsc()->page_count() )
	    return false;
    }

    goToPage( new_page );
    return true;
}


void KGVMiniWidget::redisplay ()
{
    if( !_psFile )
	return;

    _psWidget->disableInterpreter();
    showPage( _currentPage );
}

void KGVMiniWidget::restoreOverrideOrientation()
{
    _overrideOrientation = CDSC_ORIENT_UNKNOWN;
    showPage( _currentPage );
}

void KGVMiniWidget::setOverrideOrientation( CDSC_ORIENTATION_ENUM orientation )
{
    _overrideOrientation = orientation;
    showPage( _currentPage );
}

CDSC_ORIENTATION_ENUM KGVMiniWidget::orientation() const
{
    kdDebug(4500) << "KGVMiniWidget::orientation()" << endl;

    if( _overrideOrientation != CDSC_ORIENT_UNKNOWN )
	return _overrideOrientation;
    else if( dsc()->page_orientation() != CDSC_ORIENT_UNKNOWN )
	return static_cast< CDSC_ORIENTATION_ENUM >( dsc()->page_orientation());
    else if( dsc()->bbox().get() != 0 
	  && dsc()->bbox()->width() > dsc()->bbox()->height() )
        return CDSC_LANDSCAPE;
    else
        return CDSC_PORTRAIT;
}

CDSC_ORIENTATION_ENUM KGVMiniWidget::orientation( int pagenumber ) const
{
    kdDebug(4500) << "KGVMiniWidget::orientation( " << pagenumber << " )" << endl;

    if( _overrideOrientation != CDSC_ORIENT_UNKNOWN )
        return _overrideOrientation;
    else if( dsc()->page()[ pagenumber ].orientation != CDSC_ORIENT_UNKNOWN )
	return static_cast< CDSC_ORIENTATION_ENUM >( dsc()->page()[ pagenumber ].orientation );
    else if( dsc()->page_orientation() != CDSC_ORIENT_UNKNOWN )
	return static_cast< CDSC_ORIENTATION_ENUM >( dsc()->page_orientation());
    else if( !dsc()->epsf() )
        return CDSC_PORTRAIT;
    else if( dsc()->bbox().get() != 0
          && dsc()->bbox()->width() > dsc()->bbox()->height() )
        return CDSC_LANDSCAPE;
    else
        return CDSC_PORTRAIT;
}

void KGVMiniWidget::restoreOverridePageMedia()
{
    _overridePageMedia = QString::null;
    showPage( _currentPage );
}

void KGVMiniWidget::setOverridePageMedia( const QString& mediaName )
{
    _overridePageMedia = mediaName;
    showPage( _currentPage );
}

QString KGVMiniWidget::pageMedia() const
{
    if( !_overridePageMedia.isNull() )
	return _overridePageMedia;
    else if( dsc()->page_media() != 0 )
	return QString( dsc()->page_media()->name );
    else if( dsc()->bbox().get() != 0 )
	return QString( "BoundingBox" );
    else
	return _fallBackPageMedia;
}

QString KGVMiniWidget::pageMedia( int pagenumber ) const
{
    if( !_overridePageMedia.isNull() )
	return _overridePageMedia;
    else if( dsc()->page()[ pagenumber ].media != 0 )
	return QString( dsc()->page()[ pagenumber ].media->name );
    else if( dsc()->page_media() != 0 )
	return QString( dsc()->page_media()->name );
    else if( dsc()->bbox().get() != 0 )
	return QString( "BoundingBox" );
    else
	return _fallBackPageMedia;
}

KDSCBBOX KGVMiniWidget::boundingBox() const
{
    QString currentMedia = pageMedia();
    if( currentMedia == "BoundingBox" )
	return KDSCBBOX( *dsc()->bbox().get() );
    else {
	QSize size = computePageSize( currentMedia );
	return KDSCBBOX( 0, 0, size.width(), size.height() );
    }
}

KDSCBBOX KGVMiniWidget::boundingBox( int pageNo ) const
{
    QString currentMedia = pageMedia( pageNo );
    if( currentMedia == "BoundingBox" )
	return KDSCBBOX( *dsc()->bbox().get() );
    else {
	QSize size = computePageSize( currentMedia );
	return KDSCBBOX( 0, 0, size.width(), size.height() );
    }
}

bool KGVMiniWidget::atFirstPage() const
{
    return ( _currentPage == 0 );
}

bool KGVMiniWidget::atLastPage() const
{
    return ( _currentPage == static_cast<int>( dsc()->page_count() ) - 1 );
}

void KGVMiniWidget::showPage( int pagenumber )
{
    if( !isFileOpen() )
	return;

    kdDebug(4500) << "KGVMiniWidget::showPage( " << pagenumber << " )" << endl;

    static_cast< QWidget* >( _psWidget->parent() )->show();

    if( dsc()->isStructured() ) 
    {
	// Coerce page number to fall in range
	if( ( unsigned int)pagenumber >= dsc()->page_count() )
	    pagenumber = dsc()->page_count() - 1;
	if( pagenumber < 0 )
	    pagenumber = 0;

	_currentPage = pagenumber;

        _psWidget->setOrientation( orientation( _currentPage ) );
	_psWidget->setBoundingBox( boundingBox( _currentPage ) );
	_psWidget->setMagnification( _magnification );
   
	if( !_psWidget->isInterpreterRunning() )
	{
	    // Start interpreter, send preamble and send the current page.
	    _psWidget->enableInterpreter();
	    _psWidget->sendPS( _psFile, dsc()->beginprolog(),
					dsc()->endprolog() );
	    _psWidget->sendPS( _psFile, dsc()->beginsetup(),
					dsc()->endsetup() );
	    _psWidget->sendPS( _psFile, dsc()->page()[ _currentPage ].begin,
					dsc()->page()[ _currentPage ].end );
	    _visiblePage = _currentPage;
	}
	else
	    sendPage();
    }
    else 
    {
	_psWidget->setOrientation( orientation() );
	_psWidget->setBoundingBox( boundingBox() );
	_psWidget->setMagnification( _magnification );
	
	if( !_psWidget->isInterpreterRunning() ) 
	{
	    // This is not a structured document -- start interpreter
	    _psWidget->enableInterpreter();
	    if( !dsc() )
		_psWidget->disableInterpreter();
	}
	else if( _psWidget->isInterpreterReady() )
	    _psWidget->nextPage();
	else 
	{
	    /*
	    KNotifyClient::userEvent
	      (i18n("KGhostview cannot load the document, \"%1\".\n"
		    "It appears to be broken.").arg( _fileName ),
	       KNotifyClient::Messagebox);
	    _psWidget->disableInterpreter();
	    _psFile=0;

	    //TODO: More to do to turn off display?
	    */
	    return;
	}
    }

    emit newPageShown( pagenumber );
}

void KGVMiniWidget::sendPage()
{
   // Send the page to the interpreter.
    if( !_psWidget->isInterpreterBusy() && _visiblePage != _currentPage ) 
    {
	// Interpreter ready - Fire off next page
	_psWidget->nextPage();
	_psWidget->sendPS( _psFile, dsc()->page()[ _currentPage ].begin,
				    dsc()->page()[ _currentPage ].end );
	_visiblePage = _currentPage;
    }
}

void KGVMiniWidget::updateStatusBarText( int pageNumber )
{
    if( !dsc() )
	return;
    
    if( dsc()->isStructured() ) 
    {
	QString text;
	
	if( pageNumber == -1 )
	    text = i18n( "Page 1" );
	else
	    if( !_usePageLabels || _format == PDF ) 
		text = i18n( "Page %1 of %2" )
		       .arg( pageNumber + 1 )
		       .arg( dsc()->page_count() );
	    else
		text = i18n( "Page %1 (%2 of %3)" )
		       .arg( dsc()->page()[ _currentPage ].label )
		       .arg( pageNumber + 1 )
		       .arg( dsc()->page_count() );

	emit setStatusBarText( text );
    }
}

void KGVMiniWidget::buildTOC()
{
    if( !dsc() ) 
	return;
    
    // Build table of contents
    // Well, that's what it used to be called !!

    int this_page, last_page=0;

    _marklist->setAutoUpdate( false );
    _marklist->clear();

    if( dsc()->isStructured() ) {
	unsigned int maxlen = 0;
	unsigned int i, j;

	// Find the longest pagelabel
	if(  _usePageLabels ) {
	    for( i = 0; i < dsc()->page_count(); i++ )
		maxlen = QMAX( maxlen, strlen( dsc()->page()[i].label ) );
	}
	else {
	    double x;
	    x = dsc()->page_count();
	    maxlen = (int)( log10(x) + 1 );
	}

	toc_entry_length = maxlen + 3;
	toc_length = dsc()->page_count() * toc_entry_length - 1;

	if( _usePageLabels )
	    for( i = 0; i < dsc()->page_count(); i++ ) {
		if( dsc()->page_order() == CDSC_DESCEND )
		    j = ( dsc()->page_count() - 1 ) - i;
		else
		    j = i;
		this_page = atoi( dsc()->page()[j].label );
		last_page = this_page;
	    }

	// finally set marked list
	QString s, tip;
	for( i = 1; i <= dsc()->page_count(); i++ ) {
	    j = dsc()->page_count() - i;
	    tip = dsc()->page()[j].label;

	    if( !_usePageLabels )
		s.setNum (j+1);
	    else
		s=tip;

	    _marklist->insertItem( s, 0, tip );
	}
    }
    else {
	toc_length = 0;
	toc_entry_length = 3;
	QString s("1");
	_marklist->insertItem( s, 0 );
    }

    _marklist->setAutoUpdate( true );
    _marklist->update();
}

void KGVMiniWidget::setMagnification( double magnification )
{
    _magnification = magnification;
    showPage( _currentPage );
}

bool KGVMiniWidget::convertFromPDF( const QString& saveFileName, 
                                    unsigned int firstPage,
                                    unsigned int lastPage )
{
    // TODO -- timeout/fail on this conversion (it can hang on a bad pdf)
    // TODO -- use output from gs (leave out -q) to drive a progress bar

    KProcess process;
    process << _interpreterPath
            << "-q"
            << "-dNOPAUSE"
            << "-dBATCH"
            << "-dSAFER"
            << "-dPARANOIDSAFER"
            << "-sDEVICE=pswrite"
            << ( QCString("-sOutputFile=")+QFile::encodeName(saveFileName) )
            << ( QString("-dFirstPage=")+QString::number( firstPage ) )
            << ( QString("-dLastPage=")+QString::number( lastPage ) )
            << "-c"
            << "save"
            << "pop"
            << "-f"
            << QFile::encodeName(_fileName);

    /*QValueList<QCString> args = process.args();
    QValueList<QCString>::Iterator it = args.begin();
    for ( ; it != args.end() ; ++it )
        kdDebug() << ( *it ) << endl;*/

    if( !process.start( KProcess::Block ) )
    {
	kdError() << "convertFromPDF: Couldn't start gs process" << endl;
	// TODO -- error message (gs not found?)
	return false;
    }
    if ( !process.normalExit() || process.exitStatus() != 0 )
    {
	kdError() << "convertFromPDF: normalExit=" << process.normalExit() << " exitStatus=" << process.exitStatus() << endl;
	// TODO -- error message (can't open, strerr())
	return false;
    }

    return true;
}

void KGVMiniWidget::enablePageLabels( bool b )
{
    if( _usePageLabels != b ) 
    {
	_usePageLabels = b;
	updateStatusBarText( _currentPage );
	buildTOC();
    }
}

void KGVMiniWidget::clearTemporaryFiles()
{
    if( _tmpUnzipped ) {
	_tmpUnzipped->setAutoDelete( true );
	delete _tmpUnzipped;
	_tmpUnzipped = 0;
    }
    if( _tmpFromPDF ) {
	_tmpFromPDF->setAutoDelete( true );
	delete _tmpFromPDF;
	_tmpFromPDF = 0;
    }
    if( _tmpDSC ) {
	_tmpDSC->setAutoDelete( true );
	delete _tmpDSC;
	_tmpDSC = 0;
    }
}


/*- DOCUMENT --------------------------------------------------------------*/

QStringList KGVMiniWidget::mediaNames() const
{   
    QStringList names;

    const CDSCMEDIA* m = dsc_known_media;
    while( m->name ) {
	names << m->name;
	m++;
    }

    if( isFileOpen() && dsc()->media() ) {
	for( unsigned int i = 0; i < dsc()->media_count(); i++ ) {
	    if( dsc()->media()[i] && dsc()->media()[i]->name )
		names << dsc()->media()[i]->name;
	}
    }

    return names;
}

const CDSCMEDIA* KGVMiniWidget::findMediaByName( const QString& mediaName ) const
{
    if( !isFileOpen() )
	return 0;
    
    if( dsc()->media() ) {
	for( unsigned int i = 0; i < dsc()->media_count(); i++ ) {
	    if( dsc()->media()[i] && dsc()->media()[i]->name
	     && qstricmp( mediaName.local8Bit(), 
	                  dsc()->media()[i]->name ) == 0 ) {
		return dsc()->media()[i];
	    }
	}
    }
    /* It didn't match %%DocumentPaperSizes: */
    /* Try our known media */
    const CDSCMEDIA *m = dsc_known_media;
    while( m->name ) {
        if( qstricmp( mediaName.local8Bit(), m->name ) == 0 ) {
	    return m;
	}
	m++;
    }

    return 0;
}

QSize KGVMiniWidget::computePageSize( const QString& mediaName ) const
{
    kdDebug(4500) << "KGVMiniWidget::computePageSize( " << mediaName << " )" << endl;

    if( mediaName == "BoundingBox" ) {
	if( dsc()->bbox().get() != 0 )
	    return dsc()->bbox()->size();
	else
	    return QSize( 0, 0 );
    }

    const CDSCMEDIA* m = findMediaByName( mediaName );
    Q_ASSERT( m );
    return QSize( static_cast<int>( m->width ), static_cast<int>( m->height ) );
}


/*- PRINTING and SAVING ---------------------------------------------------*/

QString KGVMiniWidget::pageListToRange( const PageList& pageList )
{
    QString range;

    // Iterators marking the begin and end of a successive sequence 
    // of pages.
    PageList::const_iterator bss( pageList.begin() );
    PageList::const_iterator ess;
       
    PageList::const_iterator it ( pageList.begin() );
    
    while( it != pageList.end() )
    {
	ess = it++;

	// If ess points to the end of a successive sequence of pages,
	// add the stringrepresentation of the sequence to range and
	// update bss.
	if( it == pageList.end() || *it != (*ess) + 1 )
	{
	    if( !range.isEmpty() )
		range += ",";

	    if( bss == ess )
		range += QString::number( *ess );
	    else
		range += QString( "%1-%2" ).arg( *bss ).arg( *ess );

	    bss = it;
	}
    }

    return range;
}

void KGVMiniWidget::print()
{
    if( !dsc() ) return;

    KPrinter printer;
    
    if( dsc()->isStructured() ) 
    {
	printer.setPageSelection( KPrinter::ApplicationSide );
	
	printer.setCurrentPage( _currentPage + 1 );
	printer.setMinMax( 1, dsc()->page_count() );
	printer.setOption( "kde-range", 
	                    pageListToRange( _marklist->markList() ) );
	
	if( printer.setup( _part->widget() ) ) 
	{
	    KTempFile tf( QString::null, ".ps" );
	    if( tf.status() == 0 ) 
	    {
	        savePages( tf.name(), printer.pageList() );
	        printer.printFiles( QStringList( tf.name() ), true );
	    }
	    else 
	    {
	        // TODO: Proper error handling
	        ;
	    }
	}
    }
    else 
    {
	printer.setPageSelection( KPrinter::SystemSide );
	
	if( printer.setup( _part->widget() ) )
	    printer.printFiles( _fileName );
    }
}

void KGVMiniWidget::saveAs()
{
    if( !isFileOpen() )
	return;
    
    KURL saveURL = KFileDialog::getSaveURL( 
                          _part->url().isLocalFile() 
                              ? _part->url().url() 
                              : _part->url().fileName(), 
                          QString::null, 
                          _part->widget(), 
                          QString::null );
    if( !KIO::NetAccess::upload( _fileName,
				 saveURL ) )
	; // TODO: Proper error dialog
}

bool KGVMiniWidget::savePages( const QString& saveFileName,
                               const PageList& pageList )
{
    if( pageList.empty() )
	return true;
    
    if( _format == PDF ) 
    {
	KTempFile psSaveFile( QString::null, ".ps" );
	psSaveFile.setAutoDelete( true );
	if( psSaveFile.status() != 0 )
	    return false;

	// Find the minimum and maximum pagenumber in pageList.
	int minPage = pageList.first(), maxPage = pageList.first();
	for( PageList::const_iterator ci = pageList.begin();
	     ci != pageList.end(); ++ci )
	{
	    minPage = QMIN( *ci, minPage );
	    maxPage = QMAX( *ci, maxPage );
	}
	
	// Convert the pages in the range [minPage,maxPage] from PDF to
	// PostScript.
	if( !convertFromPDF( psSaveFile.name(), 
	                     minPage, maxPage ) )
	    return false;

	// The page minPage in the original file becomes page 1 in the 
	// converted file. We still have to select the desired pages from
	// this file, so we need to take into account that difference.
	PageList normedPageList;
	transform( pageList.begin(), pageList.end(),
	           back_inserter( normedPageList ),
	           bind2nd( minus<int>(), minPage - 1 ) );
	
	// Finally select the desired pages from the converted file.
	psCopyDoc( psSaveFile.name(), saveFileName, normedPageList );
    }
    else
    {
	psCopyDoc( _fileName, saveFileName, pageList );
    }

    return true;
}

// length calculates string length at compile time
// can only be used with character constants
#define length( a ) ( sizeof( a ) - 1 )

// Copy the headers, marked pages, and trailer to fp

bool KGVMiniWidget::psCopyDoc( const QString& inputFile,
	const QString& outputFile, const PageList& pageList )
{
    FILE* from;
    FILE* to;
    char text[ PSLINELENGTH ];
    char* comment;
    bool pages_written = false;
    bool pages_atend = false;
    unsigned int i = 0;
    unsigned int pages = 0;
    long here;

    kdDebug(4500) << "KGVMiniWidget: Copying pages from " << inputFile << " to "
    << outputFile << endl;

    from = fopen( QFile::encodeName( inputFile ), "r" );
    to = fopen( QFile::encodeName( outputFile ), "w" );

    pages = pageList.size();

    if( pages == 0 ) {
	KMessageBox::sorry( 0,
		i18n( "Printing failed because the list of "
				"pages to be printed was empty." ),
			  i18n( "Error Printing" ) );
	return false;
    }

    // Hack in order to make printing of PDF files work. FIXME
    CDSC* dsc;

    if( _format == PS )
	dsc = _dsc->cdsc();
    else {
	FILE* fp = fopen( QFile::encodeName( inputFile ), "r");
	char buf[256];
	int count;
	dsc = dsc_init( 0 );
	while( ( count = fread( buf, 1, 1, fp ) ) != 0 )
	    dsc_scan_data( dsc, buf, count );
	dsc_fixup( dsc );
	fclose( fp );

	if( !dsc )
	    return false;
    }

    here = dsc->begincomments;
    while( ( comment = pscopyuntil( from, to, here,
	     dsc->endcomments, "%%Pages:" ) ) ) {
	here = ftell( from );
	if( pages_written || pages_atend ) {
	    free( comment );
	    continue;
	}
	sscanf( comment + length("%%Pages:" ), "%256s", text );
	text[256] = 0; // Just in case of an overflow
	if( strcmp( text, "(atend)" ) == 0 ) {
	    fputs( comment, to );
	    pages_atend = true;
	}
	else {
	    switch ( sscanf( comment + length( "%%Pages:" ), "%*d %d", &i ) )  {
	    case 1:
		fprintf( to, "%%%%Pages: %d %d\n", pages, i );
		break;
	    default:
		fprintf( to, "%%%%Pages: %d\n", pages );
		break;
	    }
	    pages_written = true;
	}
	free(comment);
    }
    pscopy( from, to, dsc->beginpreview, dsc->endpreview );
    pscopy( from, to, dsc->begindefaults, dsc->enddefaults );
    pscopy( from, to, dsc->beginprolog, dsc->endprolog );
    pscopy( from, to, dsc->beginsetup, dsc->endsetup );

    //TODO -- Check that a all dsc attributes are copied

    unsigned int count = 1;
    PageList::const_iterator it;
    for( it = pageList.begin(); it != pageList.end(); ++it ) {
	i = (*it) - 1;
	comment = pscopyuntil( from, to, dsc->page[i].begin,
			       dsc->page[i].end, "%%Page:" );
	free( comment );
	fprintf( to, "%%%%Page: %s %d\n", dsc->page[i].label,
		 count++ );
	pscopy( from, to, -1, dsc->page[i].end );
    }

    here = dsc->begintrailer;
    while( ( comment = pscopyuntil( from, to, here,
				  dsc->endtrailer, "%%Pages:" ) ) ) {
	here = ftell( from );
	if ( pages_written ) {
	    free( comment );
	    continue;
	}
	switch ( sscanf( comment + length( "%%Pages:" ), "%*d %d", &i ) ) {
	case 1:
	    fprintf( to, "%%%%Pages: %d %d\n", pages, i );
	    break;
	default:
	    fprintf( to, "%%%%Pages: %d\n", pages );
	    break;
	}
	pages_written = true;
	free( comment );
    }

    fclose( from );
    fclose( to );

    if( _format == PDF )
	dsc_free( dsc );

    return true;
}

#undef length


/*- Conversion stuff ------------------------------------------------------*/

/*
void KGVMiniWidget::runPdf2ps( const QString& pdfName, 
                               const QString& dscName )
{
    KProcess process;
    process << _interpreterPath
	    << "-dNODISPLAY"
	    << "-dQUIET"
	    << QString( "-sPDFname=%1" ).arg( pdfName )
	    << QString( "-sDSCnamale locale( "kghostview" );
    _fallBackPageMedia = pageSizeToString( 
              static_cast< QPrinter::PageSize >( locale.pageSize() ) );
    
    _usePageLabels = false;
e=%1" ).arg( dscName )
	    << "pdf2dsc.ps"
	    << "-c"
	    << "quit";

    connect( &process, SIGNAL( processExited( KProcess* ) ),
	     this, SLOT( pdf2psExited( KProcess* ) ) );

    kdDebug(4500) << "KGVMiniWidget: pdf2ps started" << endl;
    process.start( KProcess::NotifyOnExit );
}

void KGVMiniWidget::pdf2psExited( KProcess* process )
{
    kdDebug(4500) << "KGVMiniWidget: pdf2ps exited" << endl;
    
    emit pdf2psFinished( process.normalExit() && process.exitStatus() != 0 );
}
*/

Pdf2dsc::Pdf2dsc( const QString& ghostscriptPath, QObject* parent, const char* name ) :
    QObject( parent, name ),
    _process( 0 ),
    _ghostscriptPath( ghostscriptPath )
{}

Pdf2dsc::~Pdf2dsc()
{
    kill();
}

void Pdf2dsc::run( const QString& pdfName, const QString& dscName )
{
    kill();
    
    _process = new KProcess;
    *_process << _ghostscriptPath
              << "-dSAFER"
              << "-dPARANOIDSAFER"
              << "-dDELAYSAFER"
              << "-dNODISPLAY"
              << "-dQUIET"
              << QString( "-sPDFname=%1" ).arg( pdfName )
              << QString( "-sDSCname=%1" ).arg( dscName )
              << "-c"
              << "<< /PermitFileReading [ PDFname ] /PermitFileWriting [ DSCname ] /PermitFileControl [] >> setuserparams .locksafe"
              << "-f"
              << "pdf2dsc.ps"
              << "-c"
              << "quit";

    connect( _process, SIGNAL( processExited( KProcess* ) ),
	     this, SLOT( processExited() ) );

    kdDebug(4500) << "Pdf2dsc: started" << endl;
    _process->start( KProcess::NotifyOnExit );
}

void Pdf2dsc::kill()
{
    if( _process != 0 )
    {
	kdDebug(4500) << "Pdf2dsc: killing current process" << endl;
	delete _process;
	_process = 0;
    }
}

void Pdf2dsc::processExited()
{
    kdDebug(4500) << "Pdf2dsc: process exited" << endl;
    
    emit finished( _process->normalExit() && _process->exitStatus() == 0 );
    delete _process;
    _process = 0;
}

#include "kgv_miniwidget.moc"

