You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/kghostview/kpswidget.cpp

531 lines
14 KiB

/**
* Copyright (C) 2000-2003 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kpswidget.h"
#include <stdlib.h>
#include <math.h>
#include <tqstringlist.h>
#include <kapplication.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kprocess.h>
#include "configuration.h"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
int handler( Display* d, XErrorEvent* e )
{
char msg[80], req[80], number[80];
XGetErrorText( d, e->error_code, msg, sizeof( msg ) );
sprintf( number, "%d", e->request_code );
XGetErrorDatabaseText( d, "XRequest", number, "<unknown>",
req, sizeof( req ) );
return 0;
}
int orientation2angle( CDSC_ORIENTATION_ENUM orientation )
{
Q_ASSERT( orientation != CDSC_ORIENT_UNKNOWN );
int angle = 0;
switch( orientation )
{
case CDSC_ORIENT_UNKNOWN: break; // Catched by Q_ASSERT
case CDSC_PORTRAIT: angle = 0; break;
case CDSC_LANDSCAPE: angle = 90; break;
case CDSC_UPSIDEDOWN: angle = 180; break;
case CDSC_SEASCAPE: angle = 270; break;
}
return angle;
}
TQCString palette2String( Configuration::EnumPalette::type palette )
{
TQCString str;
switch( palette )
{
case Configuration::EnumPalette::Color: str = "Color"; break;
case Configuration::EnumPalette::Grayscale: str = "Grayscale"; break;
case Configuration::EnumPalette::Monochrome: str = "Monochrome"; break;
default: kdWarning( 4500 ) << "palette2String(): unkown palette" << endl;
str = "Color";
}
return str;
}
KPSWidget::KPSWidget( TQWidget* tqparent, const char* name ) :
TQWidget ( tqparent, name ),
_gsWindow ( None ),
_usePipe ( false ),
_doubleBuffer ( false ),
_ghostscriptDirty ( false ),
_orientation ( CDSC_PORTRAIT ),
_magnification ( 1 ),
_palette ( Configuration::EnumPalette::Color ),
_widgetDirty ( true ),
_process ( 0 ),
_buffer ( 0 ),
_stdinReady ( false ),
_interpreterBusy ( false ),
_interpreterReady ( false )
{
XSetErrorHandler( handler );
// Create the Atoms used to communicate with Ghostscript.
const char* const atomNames[] = { "GHOSTVIEW", "GHOSTVIEW_COLORS",
"NEXT", "PAGE", "DONE" };
XInternAtoms( x11Display(), const_cast<char**>( atomNames ),
5, false, _atoms );
// readSettings() TODO
}
KPSWidget::~KPSWidget()
{
if ( _buffer ) operator delete( _buffer );
stopInterpreter();
}
bool KPSWidget::isInterpreterReady() const
{
return isInterpreterRunning() && _interpreterReady;
}
bool KPSWidget::isInterpreterBusy() const
{
return _interpreterBusy;
}
bool KPSWidget::isInterpreterRunning() const
{
return ( _process && _process->isRunning() );
}
bool KPSWidget::nextPage()
{
if( !isInterpreterReady() )
return false;
if( _gsWindow == None ) {
kdDebug(4500) << "communication window unknown!" << endl;
return false;
}
_interpreterReady = false;
_interpreterBusy = true;
setCursor( waitCursor );
XEvent e;
e.xclient.type = ClientMessage;
e.xclient.display = x11Display();
e.xclient.window = _gsWindow;
e.xclient.message_type = _atoms[NEXT];
e.xclient.format = 32;
XSendEvent( x11Display(), _gsWindow, false, 0, &e );
XFlush( x11Display() );
return true;
}
void KPSWidget::clear()
{
//_backgroundPixmap.fill();
}
bool KPSWidget::sendPS( FILE* fp, unsigned int begin, unsigned int end )
{
kdDebug(4500) << "KPSWidget::sendPS" << endl;
if( !isInterpreterRunning() )
return false;
// Create a new record to add to the queue.
_inputQueue.push( Record( fp, begin, end - begin ) );
// Start processing the queue.
if( _stdinReady )
gs_input(_process);
return true;
}
void KPSWidget::setGhostscriptPath( const TQString& path )
{
kdDebug() << "KPSWidget::setGhostscriptPath( " << path << " )" << endl;
if( _ghostscriptPath != path )
{
_ghostscriptPath = path;
stopInterpreter();
_ghostscriptDirty = true;
}
}
void KPSWidget::setGhostscriptArguments( const TQStringList& arguments )
{
if( _ghostscriptArguments != arguments )
{
_ghostscriptArguments = arguments;
stopInterpreter();
_ghostscriptDirty = true;
}
}
void KPSWidget::setFileName( const TQString& fileName, bool usePipe )
{
if(( _fileName != fileName ) || (_usePipe != usePipe))
{
_usePipe = usePipe;
_fileName = fileName;
stopInterpreter();
_ghostscriptDirty = true;
}
}
void KPSWidget::setOrientation( CDSC_ORIENTATION_ENUM orientation )
{
if( _orientation != orientation )
{
_orientation = orientation;
stopInterpreter();
_widgetDirty = true;
}
}
void KPSWidget::setBoundingBox( const KDSCBBOX& boundingBox )
{
if( _boundingBox != boundingBox )
{
_boundingBox = boundingBox;
stopInterpreter();
_widgetDirty = true;
}
}
void KPSWidget::setMagnification( double magnification )
{
if( kAbs( magnification - _magnification ) > 0.0001 )
{
_magnification = magnification;
stopInterpreter();
_widgetDirty = true;
}
}
void KPSWidget::setPalette( Configuration::EnumPalette::type palette )
{
if( _palette != palette )
{
_palette = palette;
stopInterpreter();
_widgetDirty = true;
}
}
void KPSWidget::setDoubleBuffering( bool db )
{
if( _doubleBuffer != db )
{
_doubleBuffer = db;
stopInterpreter();
_widgetDirty = true;
}
}
namespace {
/* Rounding up is better than normal rounding because it is better to have a pixel too many than one too little.
* If we have one too many, no one will notice. If we have one too little, gs complains.
*
* I have a file which isn't displayed (gs error) without this fix.
*/
inline int round_up( double x )
{
return static_cast<int>( ceil( x ) );
}
}
void KPSWidget::setupWidget()
{
if( !_widgetDirty )
return;
Q_ASSERT( orientation() != CDSC_ORIENT_UNKNOWN );
const float dpiX = _magnification * TQT_TQPAINTDEVICE(this)->x11AppDpiX();
const float dpiY = _magnification * TQT_TQPAINTDEVICE(this)->x11AppDpiY();
int newWidth = 0, newHeight = 0;
if( orientation() == CDSC_PORTRAIT || orientation() == CDSC_UPSIDEDOWN )
{
newWidth = round_up( boundingBox().width() * dpiX / 72.0 );
newHeight = round_up( boundingBox().height() * dpiY / 72.0 );
}
else
{
newWidth = round_up( boundingBox().height() * dpiX / 72.0 );
newHeight = round_up( boundingBox().width() * dpiY / 72.0 );
}
if( newWidth != width() || newHeight != height() )
{
setEraseColor( white );
setFixedSize( newWidth, newHeight );
kapp->processEvents();
_backgroundPixmap.resize( size() );
_backgroundPixmap.fill( white );
// The line below is needed to work around certain "features" of styles such as liquid
// see bug:61711 for more info (LPC, 20 Aug '03)
setBackgroundOrigin( TQWidget::WidgetOrigin );
setErasePixmap( _backgroundPixmap );
}
char data[512];
sprintf( data, "%ld %d %d %d %d %d %g %g",
( _doubleBuffer ? 0 : _backgroundPixmap.handle() ),
orientation2angle( orientation() ),
boundingBox().llx(), boundingBox().lly(),
boundingBox().urx(), boundingBox().ury(),
dpiX, dpiY );
XChangeProperty( x11Display(), winId(),
_atoms[GHOSTVIEW],
XA_STRING, 8, PropModeReplace,
(unsigned char*) data, strlen( data ) );
sprintf( data, "%s %d %d",
palette2String( _palette ).data(),
(int)BlackPixel( x11Display(), DefaultScreen( x11Display() ) ),
(int)WhitePixel( x11Display(), DefaultScreen( x11Display() ) ) );
XChangeProperty( x11Display(), winId(),
_atoms[GHOSTVIEW_COLORS],
XA_STRING, 8, PropModeReplace,
(unsigned char*) data, strlen( data ) );
// Make sure the properties are updated immediately.
XSync( x11Display(), false );
tqrepaint();
_widgetDirty = false;
}
bool KPSWidget::startInterpreter()
{
setupWidget();
_process = new KProcess;
if ( _doubleBuffer ) _process->setEnvironment( "GHOSTVIEW", TQString( "%1 %2" ).tqarg( winId() ).tqarg( _backgroundPixmap.handle() ) );
else _process->setEnvironment( "GHOSTVIEW", TQString::number( winId() ) );
*_process << _ghostscriptPath.local8Bit();
*_process << _ghostscriptArguments;
if( _usePipe )
*_process <<
// The following two lines are their to ensure that we are allowed to read _fileName
"-dDELAYSAFER" << "-sInputFile="+_fileName << "-c" <<
"<< /PermitFileReading [ InputFile ] /PermitFileWriting [] /PermitFileControl [] >> setuserparams .locksafe" <<
"-";
else
*_process << _fileName << "-c" << "quit";
connect( _process, TQT_SIGNAL( processExited( KProcess* ) ),
this, TQT_SLOT( slotProcessExited( KProcess* ) ) );
connect( _process, TQT_SIGNAL( receivedStdout( KProcess*, char*, int ) ),
this, TQT_SLOT( gs_output( KProcess*, char*, int ) ) );
connect( _process, TQT_SIGNAL( receivedStderr( KProcess*, char*, int ) ),
this, TQT_SLOT( gs_output( KProcess*, char*, int ) ) );
connect( _process, TQT_SIGNAL( wroteStdin( KProcess*) ),
this, TQT_SLOT( gs_input( KProcess* ) ) );
kapp->flushX();
// Finally fire up the interpreter.
kdDebug(4500) << "KPSWidget: starting interpreter" << endl;
if( _process->start( KProcess::NotifyOnExit,
_usePipe ? KProcess::All : KProcess::AllOutput ) )
{
_interpreterBusy = true;
setCursor( waitCursor );
_stdinReady = true;
_interpreterReady = false;
_ghostscriptDirty = false;
return true;
}
else
{
KMessageBox::error( this,
i18n( "Could not start Ghostscript. This is most likely "
"caused by an incorrectly specified interpreter." ) );
return false;
}
}
void KPSWidget::stopInterpreter()
{
kdDebug(4500) << "KPSWidget::stopInterpreter()" << endl;
// if( !_interpreterBusy ) return;
if( isInterpreterRunning() )
_process->kill( SIGHUP );
_process = 0;
while ( !_inputQueue.empty() ) _inputQueue.pop();
_interpreterBusy = false;
unsetCursor();
}
void KPSWidget::interpreterFailed()
{
stopInterpreter();
}
void KPSWidget::slotProcessExited( KProcess* process )
{
kdDebug(4500) << "KPSWidget: process exited" << endl;
if ( process == _process )
{
kdDebug( 4500 ) << "KPSWidget::slotProcessExited(): looks like it was not a clean exit." << endl;
if ( process->normalExit() ) {
emit ghostscriptError( TQString( i18n( "Exited with error code %1." ).tqarg( process->exitStatus() ) ) );
} else {
emit ghostscriptError( TQString( i18n( "Process killed or crashed." ) ) );
}
_process = 0;
stopInterpreter();
unsetCursor();
}
}
void KPSWidget::gs_output( KProcess*, char* buffer, int len )
{
emit output( buffer, len );
}
void KPSWidget::gs_input( KProcess* process )
{
kdDebug(4500) << "KPSWidget::gs_input" << endl;
if (process != _process)
{
kdDebug(4500) << "KPSWidget::gs_input(): process != _process" << endl;
return;
}
_stdinReady = true;
while( ! _inputQueue.empty() && _inputQueue.front().len == 0 ) _inputQueue.pop();
if( _inputQueue.empty() ) {
_interpreterReady = true;
return;
}
Record& current = _inputQueue.front();
if ( fseek( current.fp, current.begin, SEEK_SET ) ) {
kdDebug(4500) << "KPSWidget::gs_input(): seek failed!" << endl;
interpreterFailed();
return;
}
Q_ASSERT( current.len > 0 );
const unsigned buffer_size = 4096;
if ( !_buffer ) _buffer = static_cast<char*>( operator new( buffer_size ) );
const int bytesRead = fread( _buffer, sizeof (char),
TQMIN( buffer_size, current.len ),
current.fp );
if( bytesRead > 0 )
{
current.begin += bytesRead;
current.len -= bytesRead;
if( process && process->writeStdin( _buffer, bytesRead ) )
_stdinReady = false;
else
interpreterFailed();
}
else
interpreterFailed();
}
void KPSWidget::readSettings()
{
setGhostscriptPath( Configuration::interpreter() );
TQStringList arguments;
if( Configuration::antialiasing() )
arguments = TQStringList::split( " ", Configuration::antialiasingArguments() );
else
arguments = TQStringList::split( " ", Configuration::nonAntialiasingArguments() );
if( !Configuration::platformFonts() )
arguments << "-dNOPLATFONTS";
arguments << "-dNOPAUSE" << "-dTQUIET" << "-dSAFER" << "-dPARANOIDSAFER";
setGhostscriptArguments( arguments );
setPalette( static_cast<Configuration::EnumPalette::type>( Configuration::palette() ) );
}
bool KPSWidget::x11Event( XEvent* e )
{
if( e->type == ClientMessage )
{
_gsWindow = e->xclient.data.l[0];
if( e->xclient.message_type == _atoms[PAGE] )
{
kdDebug(4500) << "KPSWidget: received PAGE" << endl;
_interpreterBusy = false;
unsetCursor();
emit newPageImage( _backgroundPixmap );
if ( _doubleBuffer ) setErasePixmap( _backgroundPixmap );
return true;
}
else if( e->xclient.message_type == _atoms[DONE] )
{
kdDebug(4500) << "KPSWidget: received DONE" << endl;
stopInterpreter();
return true;
}
}
return TQWidget::x11Event( e );
}
#include "kpswidget.moc"
// vim:sw=4:sts=4:ts=8:noet