/****************************************************************************
**
** A Qt PostScript widget.
**
** Copyright (C) 1997 by Mark Donohoe.
** Based on original work by Tim Theisen.
**
** This code is freely distributable under the GNU Public License.
**
*****************************************************************************/

#include <config.h>

#include <qdrawutil.h>
#include <kapp.h>
#include <kdebug.h>

#include "interpreterdialog.h"
#include "kpswidget.h"
#include "kpswidget.moc"

#define BUFFER_SIZE (8192)

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));

   //fprintf(stderr, "kghostview: %s(0x%lx): %s\n", req, e->resourceid, msg);

   return 0;
}

KPSWidget::KPSWidget( QWidget *parent, const char* name )
 : QWidget( parent, name )
{
    _mWin = None;

    proc = 0;

   XSetErrorHandler(handler);

   gs_window = winId();
   gs_display = x11Display();

   messages = new MessagesDialog( 0, "messages" );
   intConfig = new InterpreterDialog( topLevelWidget(), "intConfig" );

   // Initialise class variables
   // I usually forget a few important ones resulting in nasty seg. faults.

   background_pixmap = None;

   left_margin = 0; right_margin = 0; bottom_margin = 0; top_margin = 0;
   _xdpi=75.0; _ydpi=75.0;

   foreground = 0;
   background_pixel = 1;
   antialias=true;
   show_messages=false;
   stdin_ready = false;

   input_buffer = 0;
   bytes_left = 0;
   _inputQueue.setAutoDelete( true );
   _orientation = 1;
   busy = false;
   setCursor( arrowCursor );

    _interpreterReady	= false;
    _disableStart	= false;

    _delayedLayout  = false;
    _layoutDirty    = false;

    layout();
}

KPSWidget::~KPSWidget()
{
   delete proc; proc = 0;
   stopInterpreter();
   if ( background_pixmap != None )
      XFreePixmap( gs_display, background_pixmap );
   delete intConfig;
   delete messages;
}


// ************************************************************************
//
//	PUBLIC METHODS
//
// ************************************************************************

/**
 * Stop any running interpreter and don't allow new ones to start.
 */
void KPSWidget::disableInterpreter()
{
    _disableStart = true;
    stopInterpreter();
}

/**
 * Allow the interpreter to start, and try to start it.
 */
void KPSWidget::enableInterpreter()
{
    _disableStart = false;
    startInterpreter();
}

/**
 * Returns true if the interpreter is ready for new input.
 */
bool KPSWidget::isInterpreterReady() const
{
    return isInterpreterRunning() && _interpreterReady;
}

/**
 * Returns true if the interpreter is running.
 */
bool KPSWidget::isInterpreterRunning() const
{
    return proc && proc->isRunning();
}

/**
 * Tell ghostscript to start the next page.
 * Returns false if ghostscript is not running, or not ready to start
 * another page.
 * If another page is started, sets the _interpreterReady flag and cursor.
 */
bool KPSWidget::nextPage()
{
    XEvent ev;

    if( !isInterpreterRunning() )
	return false;
	
    if( _mWin == None ) {
	kdDebug(4500) << "kghostview: communication window unknown!" << endl;
	return false;
    }
	
    if( _interpreterReady ) {
	_interpreterReady = false;
	setCursor( waitCursor );

	ev.xclient.type = ClientMessage;
	ev.xclient.display = gs_display;
	ev.xclient.window = _mWin;
	ev.xclient.message_type = gs_next;
	ev.xclient.format = 32;
		
	XSendEvent( gs_display, _mWin, false, 0, &ev );
	XFlush( gs_display );

	return true;
    }
    else
	return false;
}

/**
 * Queue a portion of a PostScript file for output to ghostscript and start
 * processing the queue.
 *
 * fp: FILE* of the file in question. NOTE: if you have several KPSWidget's
 * reading from the same file, you must open a unique FILE* for each widget.
 * begin: position in file to start.
 * len: number of bytes to write.
 *
 * If an interpreter is not running, nothing is queued and false is returned.
 */
bool KPSWidget::sendPS( FILE* fp, const KDSC::Offset& offset, bool close )
{
    if( !isInterpreterRunning() )
	return false;

    // Create a new record to add to the queue.
    Record* ps_new = new Record;
    ps_new->fp = fp;
    ps_new->begin = offset.begin();
    ps_new->len = offset.length();
    ps_new->seek_needed = true;
    ps_new->close = close;

    // TODO: move this somewhere else.
    if( input_buffer == 0 )
	input_buffer = (char *) malloc(BUFFER_SIZE);

    if( _inputQueue.isEmpty() )
	bytes_left = offset.length();

    // Queue the record.
    _inputQueue.enqueue( ps_new );

    // Start processing the queue.
    if( stdin_ready )
	gs_input();

    return true;
}

/**
 * Sets the filename of the ghostscript input. Usually we use a pipe for
 * communication and no filename will be needed.
 */
void KPSWidget::setFileName( const QString& fileName )
{
    _fileName = fileName;
}

void KPSWidget::setOrientation( int orientation )
{
    if( _orientation != orientation ) {
	_orientation = orientation;
	relayout();
    }
}

void KPSWidget::setBoundingBox( const KDSC::BoundingBox& boundingBox )
{
    if( _boundingBox != boundingBox ) {
	_boundingBox = boundingBox;
	relayout();
    }
}

void KPSWidget::setResolution( int xdpi, int ydpi )
{
    if( _xdpi != xdpi || _ydpi != ydpi ) {
	_xdpi = xdpi;
	_ydpi = ydpi;
	relayout();
    }
}

void KPSWidget::layout()
{
    bool sizeChange = computeSize();
    if( sizeChange ) {
	resizeEvent(0); // Dutta 16/3/98
	repaint();
	setup();
	emit pageSizeChanged( QSize( width(), height() ) );
    }
}

void KPSWidget::relayout()
{
    if( _delayedLayout )
	_layoutDirty = true;
    else
	layout();
}

void KPSWidget::setDelayedLayout( bool value )
{
    _delayedLayout = value;

    if( !_delayedLayout )
	relayout();
}

/**********************************************************************************
 *
 *	SLOTS
 *
 **********************************************************************************/

bool KPSWidget::configure()
{
   intConfig->setup();
   if (intConfig->exec())
   {
      setup();
      return true;
   }

   return false;
}

void
KPSWidget::writeSettings()
{
    intConfig->writeSettings();
}

// ************************************************************************
//
//	PROTECTED METHODS
//
// ************************************************************************

bool
KPSWidget::computeSize()
{
   int newWidth=0, newHeight=0;
   bool change = false;

   int old_orient_angle = orient_angle;

   switch( _orientation )
   {
   case 1: //PORTRAIT
      orient_angle=0;
      newWidth = (int) (_boundingBox.width() / 72.0 * _xdpi + 0.5);
      newHeight = (int) (_boundingBox.height() / 72.0 * _ydpi + 0.5);
      break;
   case 2: //LANDSCAPE
      orient_angle=90;
      newWidth = (int) (_boundingBox.height() / 72.0 * _xdpi + 0.5);
      newHeight = (int) (_boundingBox.width() / 72.0 * _ydpi + 0.5);
      break;
   case 3: //UPSIDEDOWN
      orient_angle=180;
      newWidth = (int) (_boundingBox.width() / 72.0 * _xdpi + 0.5);
      newHeight = (int) (_boundingBox.height() / 72.0 * _ydpi + 0.5);
      break;
   case 4: //SEASCAPE
      orient_angle=270;
      newWidth = (int) (_boundingBox.height() / 72.0 * _xdpi + 0.5);
      newHeight = (int) (_boundingBox.width() / 72.0 * _ydpi + 0.5);
      break;
   }

   if( (newWidth != width()) ||
       (newHeight != height()) )
   {
      resize (newWidth, newHeight);
      resize (newWidth, newHeight);
      change = true;
   }

   return change | (old_orient_angle != orient_angle);
}

static bool alloc_error;
static XErrorHandler oldhandler;

static int catch_alloc (Display *dpy, XErrorEvent *err)
{
   if (err->error_code == BadAlloc) {
      alloc_error = true;
   }
   if (alloc_error) return 0;
   return oldhandler(dpy, err);
}

void
KPSWidget::setup()
{
   Pixmap bpixmap = None;

   // NO stop interpreter ?

   stopInterpreter();

   // NO test of actual change ?

   if (background_pixmap != None)
   {
      XFreePixmap(gs_display, background_pixmap);
      background_pixmap = None;
      XSetWindowBackgroundPixmap(gs_display, gs_window, None);
   }

   if( intConfig->backingStoreType() ==InterpreterDialog::PIX_BACKING )
   {
      if (background_pixmap == None)
      {
         XSync(gs_display, false);
         oldhandler = XSetErrorHandler(catch_alloc);
         alloc_error = false;
         bpixmap = XCreatePixmap(
                        gs_display, gs_window,
                        width(), height(),
                        DefaultDepth( gs_display, DefaultScreen( gs_display) ) );
         XSync(gs_display, false);
         if (alloc_error)
         {
            //printf("BadAlloc\n");
            //XtCallCallbackList(w, gvw->ghostview.message_callback,
            //         "BadAlloc");
            if (bpixmap != None)
            {
               XFreePixmap(gs_display, bpixmap);
               XSync(gs_display, false);
               bpixmap = None;
            }
         }
         oldhandler = XSetErrorHandler(oldhandler);
         if (bpixmap != None)
         {
            background_pixmap = bpixmap;
            XSetWindowBackgroundPixmap(gs_display, gs_window,
                                       background_pixmap);
         }
      }
      else
      {
         bpixmap = background_pixmap;
      }
   }

   XSetWindowAttributes xswa;

   if (bpixmap != None)
   {
      xswa.backing_store = NotUseful;
      XChangeWindowAttributes(gs_display, gs_window,
                              CWBackingStore, &xswa);
   }
   else
   {
      xswa.backing_store = Always;
      XChangeWindowAttributes(gs_display, gs_window,
                              CWBackingStore, &xswa);
   }

   ghostview = (Atom) XInternAtom(gs_display, "GHOSTVIEW", false);
   gs_colors = (Atom) XInternAtom(gs_display, "GHOSTVIEW_COLORS", false);
   gs_next = (Atom) XInternAtom(gs_display, "NEXT", false);
   gs_page = (Atom) XInternAtom(gs_display, "PAGE", false);
   gs_done = (Atom) XInternAtom(gs_display, "DONE", false);

   char buf[512];

   sprintf(buf, "%ld %d %d %d %d %d %g %g %d %d %d %d",
           background_pixmap,
           orient_angle,
	   _boundingBox.llx(), _boundingBox.lly(),
	   _boundingBox.urx(), _boundingBox.ury(),
           _xdpi, _ydpi,
           left_margin, bottom_margin,
	   right_margin, top_margin);

   XChangeProperty(gs_display, gs_window,
                   ghostview,
                   XA_STRING, 8, PropModeReplace,
                   (unsigned char *)buf, strlen(buf));

   sprintf(buf, "%s %d %d",
           intConfig->paletteType() ==
              InterpreterDialog::MONO_PALETTE  ? "Monochrome" :
           intConfig->paletteType() ==
              InterpreterDialog::GRAY_PALETTE  ? "Grayscale" :
           intConfig->paletteType() ==
              InterpreterDialog::COLOR_PALETTE ? "Color" : "?",
           (int) BlackPixel(gs_display, DefaultScreen(gs_display)),
           (int) WhitePixel(gs_display, DefaultScreen(gs_display)) );

   XChangeProperty(gs_display, gs_window,
                   gs_colors,
                   XA_STRING, 8, PropModeReplace,
                   (unsigned char *)buf, strlen(buf));

   XSync(gs_display, false);  // Be sure to update properties
}


void KPSWidget::startInterpreter()
{
    GC gc;
    XGCValues values;

    values.foreground = WhitePixel(gs_display, DefaultScreen( gs_display) );
    values.background = BlackPixel(gs_display, DefaultScreen( gs_display) );

    gc = XCreateGC( gs_display,
                    RootWindow( gs_display, DefaultScreen( gs_display ) ),
                    ( GCForeground | GCBackground ), &values );

    stopInterpreter();

    // Clear the window before starting a new interpreter.
    if( background_pixmap != None ) {
	XFillRectangle( gs_display, background_pixmap,
			gc, 0, 0, width(), height() );
    }
    erase();

    if( _disableStart )
	return;

    // Specify the commandline for the interpreter.
    proc = new KProcess;
    *proc << intConfig->interpreterPath().local8Bit().data();
    if( intConfig->antiAlias() )
	*proc << "-sDEVICE=x11alpha";
    else
	*proc << "-sDEVICE=x11";
    if( !intConfig->platformFonts() )
	*proc << "-dNOPLATFONTS";
    *proc << "-dNOPAUSE";
    *proc << "-dQUIET";
    *proc << "-dSAFER";
    if( _fileName.isEmpty() )
	*proc << "-";
    else
	*proc << _fileName;

    busy = true;
    setCursor( waitCursor );

    // WABA: Save & restore this.
    char buf[512];
    sprintf( buf, "%d", static_cast<int>( gs_window ) );
    setenv( "GHOSTVIEW", buf, true );
    setenv( "DISPLAY", XDisplayString( gs_display ), true );

    // Connect up the process.
    connect( proc, SIGNAL( processExited( KProcess* ) ),
	     this, SLOT( interpreterFailed() ) );
    connect( proc, SIGNAL( receivedStdout( KProcess*, char*, int ) ),
	     this, SLOT( gs_output( KProcess*, char*, int ) ) );
    connect( proc, SIGNAL( receivedStderr( KProcess*, char*, int ) ),
	     this, SLOT( gs_output( KProcess*, char*, int ) ) );
    connect( proc, SIGNAL( wroteStdin( KProcess*) ),
	     this, SLOT( gs_input() ) );

    kapp->flushX();

    // Finally fire up the interpreter.
    kdDebug(4500) << "KPSWidget: starting interpreter" << endl;
    proc->start( KProcess::NotifyOnExit,
		 _fileName.isEmpty() ? KProcess::All : KProcess::AllOutput );

    stdin_ready = true;
    _interpreterReady = false;
}

void KPSWidget::stopInterpreter()
{
   if( !busy) return;
   busy=false;

   if (isInterpreterRunning())
   {
      proc->kill(SIGTERM);
   }

   delete proc;
   proc = 0;

   _inputQueue.clear();

   setCursor( arrowCursor );
}

void KPSWidget::interpreterFailed()
{
   stopInterpreter();
}

/* Output - receive I/O from ghostscript's stdout and stderr.
 * Pass this to the application via the output_callback. */

void KPSWidget::gs_output( KProcess *, char *buffer, int len )
{
   QString line = QString::fromLocal8Bit(buffer, len);

   if (line.isEmpty()) return;
   if( intConfig->showMessages() )
   {
      messages->show();
      messages->cancel->setFocus();
      messages->messageBox->append( line );
      //messages->setCursorPosition( messages->numRows(), 0 );
   }
}

void KPSWidget::gs_input()
{
   stdin_ready = true;

   do
   {
      // Close old buffer
      if (!_inputQueue.isEmpty() && bytes_left == 0)
      {
         Record* ps_old = _inputQueue.dequeue();
         delete ps_old;
      }

      // Open new buffer
      if (!_inputQueue.isEmpty() && _inputQueue.head()->seek_needed)
      {
         if (_inputQueue.head()->len > 0)
            fseek(_inputQueue.head()->fp, _inputQueue.head()->begin, SEEK_SET);
         _inputQueue.head()->seek_needed = false;
         bytes_left = _inputQueue.head()->len;
      }
   }
   while(!_inputQueue.isEmpty() && (bytes_left == 0)); // Skip empty blocks.

   int buflen = 0;
   if (bytes_left > BUFFER_SIZE)
   {
      buflen = fread(input_buffer, sizeof (char), BUFFER_SIZE, _inputQueue.head()->fp);
   }
   else if (bytes_left > 0)
   {
      buflen = fread(input_buffer, sizeof (char), bytes_left, _inputQueue.head()->fp);
   }

   if (bytes_left > 0 && buflen == 0)
   {
      interpreterFailed();
      return;
   }
   bytes_left -= buflen;

   if (buflen > 0)
   {
      if (!proc->writeStdin(input_buffer, buflen))
      {
         interpreterFailed();
         return;
      }
      stdin_ready = false;
   }
   else
   {
      _interpreterReady = true;
   }
}

bool
KPSWidget::x11Event( XEvent *ev )
{
   if(ev->xany.type == ClientMessage)
   {
      _mWin = ev->xclient.data.l[0];

      XClientMessageEvent *cme = ( XClientMessageEvent * ) ev;

      if(cme->message_type == gs_page)
      {
// kdDebug(4500) << "kghostview: got gs_page" << endl;
//         kg->page->busy=False;
         setCursor( arrowCursor );
         return TRUE;
      }
      else if(cme->message_type == gs_done)
      {
// kdDebug(4500) << "kghostview: got done" << endl;
//         kg->page->disableInterpreter();
         return TRUE;
      }
   }
   return QWidget::x11Event(ev);
}



KPSView::KPSView( QWidget* parent, const char* name )
 : QScrollView( parent, name )
{
    _psWidget = new KPSWidget( viewport() );
    addChild( _psWidget );

    setFocusPolicy( QWidget::StrongFocus );
    viewport()->setFocusPolicy( QWidget::WheelFocus );
}

/**
 * Reimplemented from QScrollView. It enables scrolling by dragging the
 * PostScript widget with the mouse.
 */
bool KPSView::eventFilter( QObject* o, QEvent* e )
{
    if( o == _psWidget )
	if( e->type() == QEvent::MouseButtonPress ) {
	    QMouseEvent* me = static_cast< QMouseEvent* >( e );
	    if( me->button() & LeftButton ) {
		_dragGrabPos = me->globalPos();
	    }
	}
	else if( e->type() == QEvent::MouseMove ) {
	    QMouseEvent* me = static_cast< QMouseEvent* >( e );
	    if( me->state() & LeftButton ) {
		QPoint delta = _dragGrabPos - me->globalPos();
		scrollBy( delta.x(), delta.y() );
		_dragGrabPos = me->globalPos();
	    }
	}
	else if( e->type() == QEvent::Resize ) {
	    // We need to call QScrollView::eventFilter before centerContents,
	    // otherwise a loop will be introduced.
	    bool result = QScrollView::eventFilter( o, e );
	    centerContents();
	    return result;
	}

    return QScrollView::eventFilter( o, e );
}

bool KPSView::readDown()
{
    if( verticalScrollBar()->value() == verticalScrollBar()->maxValue() )
	return false;
    else {
	int newValue = QMIN( verticalScrollBar()->value() + height() - 50,
			     verticalScrollBar()->maxValue() );
	verticalScrollBar()->setValue( newValue );
	return true;
    }
}

void KPSView::scrollRight()
{
    horizontalScrollBar()->addLine();
}

void KPSView::scrollLeft()
{
    horizontalScrollBar()->subtractLine();
}

void KPSView::scrollDown()
{
    verticalScrollBar()->addLine();
}

void KPSView::scrollUp()
{
    verticalScrollBar()->subtractLine();
}

void KPSView::scrollTop()
{
    verticalScrollBar()->setValue( verticalScrollBar()->minValue() );
}

/**
 * Enable/Disable the scrollbars.
 */
void KPSView::enableScrollBars( bool b )
{
    setHScrollBarMode( b ? Auto : AlwaysOff );
    setVScrollBarMode( b ? Auto : AlwaysOff );
}

void KPSView::keyPressEvent( QKeyEvent* e )
{   
    switch ( e->key() ) {
    case Key_Up:
	scrollUp();
	break;
    case Key_Down:
	scrollDown();
	break;
    case Key_Left:
	scrollLeft();
	break;
    case Key_Right:
	scrollRight();
	break;
    default:
	e->ignore();
	return;
    }
    e->accept();
}

/**
 * Reimplemented to from QScrollView to make sure that the PostScript
 * widget is centered when it fits in the viewport.
 */
void KPSView::viewportResizeEvent( QResizeEvent* e )
{
    QScrollView::viewportResizeEvent( e );
    emit viewSizeChanged( viewport()->size() );
    centerContents();
}

/**
 * If the viewport is larger than the PostScript widget, center the widget
 * on the viewport.
 */
void KPSView::centerContents()
{
    int newX = 0;
    int newY = 0;

    QSize newViewportSize = viewportSize( _psWidget->width(),
					  _psWidget->height() );

    if( newViewportSize.width() > _psWidget->width() )
	newX = ( newViewportSize.width() - _psWidget->width() )/2;
    if( newViewportSize.height() > _psWidget->height() )
	newY = ( newViewportSize.height() - _psWidget->height() )/2;

    moveChild( _psWidget, newX, newY );
}
