/*
 * Electric(tm) VLSI Design Systems
 *
 * File: graphqt.cpp
 * Qt Window System interface
 * Written by: Dmitry Nadezhin, Instutute for Design Problems in Microelectronics, Russian Academy of Sciences
 *
 * Copyright (c) 2001 Static Free Software.
 *
 * Electric(tm) 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.
 *
 * Electric(tm) 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 Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "graphqt.h"
#include "egraphics.h"

#include <qimage.h>
#include <X11/Xlib.h>

#define FLUSHTICKS     60						/* ticks between display flushes */

/****** fonts ******/

#define MAXCACHEDFONTS 200
#define FONTHASHSIZE   211		/* must be prime */

static QFont   *gra_fontcache[MAXCACHEDFONTS];
static INTBIG	gra_numfaces = 0;
static char   **gra_facelist;

typedef struct
{
	QFont   *font;
	INTBIG   face;
	INTBIG   italic;
	INTBIG   bold;
	INTBIG   underline;
	INTBIG   size;
} FONTHASH;

static FONTHASH gra_fonthash[FONTHASHSIZE];

static INTBIG        gra_textrotation = 0;
static char         *gra_textbitsdata;				/* buffer for converting text to bits */
static INTBIG        gra_textbitsdatasize = 0;		/* size of "gra_textbitsdata" */
static char        **gra_textbitsrowstart;			/* pointers to "gra_textbitsdata" */
static INTBIG        gra_textbitsrowstartsize = 0;	/* size of "gra_textbitsrowstart" */

static QFont *gra_createtextfont(INTBIG fontSize, QString face, INTBIG italic, INTBIG bold,
	INTBIG underline);
static QFont gra_gettextfont(WINDOWPART *win, TECHNOLOGY *tech, UINTBIG *descript);

static void gra_settextsize(WINDOWPART *win, INTBIG fnt, INTBIG face, INTBIG italic, INTBIG bold, INTBIG underline);

/****** rectangle saving ******/
#define NOSAVEDBOX ((SAVEDBOX *)-1)
typedef struct Isavedbox
{
	QPixmap     pix;
        GraphicsDraw *draw;
	QPoint      orig;
} SAVEDBOX;

/******************** GRAPHICS LINES ********************/

/*
 * Routine to draw a line in the graphics window.
 */
void screendrawline(WINDOWPART *win, INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2, GRAPHICS *desc,
	INTBIG texture)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawline( x1, y1, x2, y2, desc, texture );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/*
 * Routine to invert bits of the line in the graphics window
 */
void screeninvertline(WINDOWPART *win, INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screeninvertline( x1, y1, x2, y2 );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/******************** GRAPHICS POLYGONS ********************/

/*
 * Routine to draw a polygon in the graphics window.
 */
void screendrawpolygon(WINDOWPART *win, INTBIG *x, INTBIG *y, INTBIG count, GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawpolygon( x, y, count, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/******************** GRAPHICS BOXES ********************/

/*
 * Routine to draw a box in the graphics window.
 */
void screendrawbox(WINDOWPART *win, INTBIG lowx, INTBIG highx, INTBIG lowy, INTBIG highy,
	GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawbox( lowx, highx, lowy, highy, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/*
 * routine to invert the bits in the box from (lowx, lowy) to (highx, highy)
 */
void screeninvertbox(WINDOWPART *win, INTBIG lowx, INTBIG highx, INTBIG lowy, INTBIG highy)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screeninvertbox( lowx, highx, lowy, highy );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/*
 * routine to move bits on the display starting with the area at
 * (sx,sy) and ending at (dx,dy).  The size of the area to be
 * moved is "wid" by "hei".
 */
void screenmovebox(WINDOWPART *win, INTBIG sx, INTBIG sy, INTBIG wid, INTBIG hei,
	INTBIG dx, INTBIG dy)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screenmovebox( sx, sy, wid, hei, dx, dy );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/*
 * routine to save the contents of the box from "lx" to "hx" in X and from
 * "ly" to "hy" in Y.  A code is returned that identifies this box for
 * overwriting and restoring.  The routine returns negative if there is a error.
 */
INTBIG screensavebox(WINDOWPART *win, INTBIG lx, INTBIG hx, INTBIG ly, INTBIG hy)
{
	SAVEDBOX *box = new SAVEDBOX;
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	int revy = qwin->wf->revy;
	box->draw = qwin->draw;
	box->orig = QPoint( lx, revy - hy );
	box->pix.resize( hx - lx + 1, hy - ly + 1);
	bitBlt( &box->pix, 0, 0, &qwin->draw->pixmap, lx, revy - hy, -1, -1);
	return INTBIG(box);
}

/*
 * routine to shift the saved box "code" so that it is restored in a different
 * lcoation, offset by (dx,dy)
 */
void screenmovesavedbox(INTBIG code, INTBIG dx, INTBIG dy)
{
	if (code == -1) return;
	SAVEDBOX *box = (SAVEDBOX *)code;
	box->orig += QPoint( dx, -dy );
}

/*
 * routine to restore saved box "code" to the screen.  "destroy" is:
 *  0   restore box, do not free memory
 *  1   restore box, free memory
 * -1   free memory
 */
void screenrestorebox(INTBIG code, INTBIG destroy)
{
	/* get the box */
	if (code == -1) return;
	SAVEDBOX *box = (SAVEDBOX *)code;
	/* move the bits */
	if (destroy >= 0) box->draw->painter.drawPixmap( box->orig, box->pix );

	/* destroy this box's memory if requested */
	if (destroy != 0) delete box;
}

/******************** GRAPHICS TEXT ********************/

/*
 * Routine to return the number of typefaces available on the system
 * and to return their names in the array "list".
 */
INTBIG screengetfacelist(char ***list)
{
	*list = gra_facelist;
	return(gra_numfaces);
}
        
/*
 * Routine to return the default typeface used on the screen.
 */
char *screengetdefaultfacename(void)
{
	return(EApplication::localize(gra->defface));
}

void screensettextinfo(WINDOWPART *win, TECHNOLOGY *tech, UINTBIG *descript)
{
	gra_textrotation = TDGETROTATION(descript);
	gra->curfont = gra_gettextfont(win, tech, descript);
}

static QFont gra_gettextfont(WINDOWPART *win, TECHNOLOGY *tech, UINTBIG *descript)
{
	int font = TDGETSIZE(descript);
	if (font == TXTEDITOR) return gra->fixedfont;
	if (font == TXTMENU) return gra->font();
	/*
		GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
		gra->curfont = qwin->menuBar()->font();
	*/

	int size = truefontsize(font, win, tech);
	if (size < 4) size = 4;
	int face = TDGETFACE(descript);
	int italic = TDGETITALIC(descript);
	int bold = TDGETBOLD(descript);
	int underline = TDGETUNDERLINE(descript);
	gra_textrotation = TDGETROTATION(descript);
	if (face == 0 && italic == 0 && bold == 0 && underline == 0)
	{
		if (size >= MAXCACHEDFONTS) size = MAXCACHEDFONTS-1;
		if (gra_fontcache[size] == 0) {
			gra_fontcache[size] = gra_createtextfont(size, gra->defface, 0, 0, 0);
			if (gra_fontcache[size] == 0) return gra->font();
		}
		return(*gra_fontcache[size]);
	} else
	{
		UINTBIG hash = size + 3*italic + 5*bold + 7*underline + 11*face;
		hash %= FONTHASHSIZE;
		for(int i=0; i<FONTHASHSIZE; i++)
		{
			if (gra_fonthash[hash].font == 0) break;
			if (gra_fonthash[hash].face == face && gra_fonthash[hash].size == size &&
				gra_fonthash[hash].italic == italic && gra_fonthash[hash].bold == bold &&
				gra_fonthash[hash].underline == underline)
					return(*gra_fonthash[hash].font);
			hash++;
			if (hash >= FONTHASHSIZE) hash = 0;
		}
		QString facename = gra->defface;
		if (face > 0 || face < gra_numfaces) facename = gra->facelist[face-1];
		QFont *theFont = gra_createtextfont(size, facename, italic, bold, underline);
		if (theFont == 0) return gra->font();
		if (gra_fonthash[hash].font == 0)
		{
			gra_fonthash[hash].font = theFont;
			gra_fonthash[hash].face = face;
			gra_fonthash[hash].size = size;
			gra_fonthash[hash].italic = italic;
			gra_fonthash[hash].bold = bold;
			gra_fonthash[hash].underline = underline;
		}
		return(*theFont);
	}
}

static QFont *gra_createtextfont(INTBIG fontSize, QString face, INTBIG italic, INTBIG bold,
	INTBIG underline)
{
	QFont *font = new QFont( face, fontSize );
	if (font) {
#if QT_VERSION >= 300
		font->setStyleStrategy( QFont::NoAntialias );
#endif
		font->setItalic( italic );
		font->setBold( bold );
		font->setUnderline( underline );
	}
	return font;
}

void screengettextsize(WINDOWPART *win, char *str, INTBIG *x, INTBIG *y)
{
	INTBIG len, wid, hei;

	len = strlen(str);
	if (len == 0)
	{
		*x = *y = 0;
		return;
	}

	/* determine the size of the text */
	QFontMetrics fontMetrics( gra->curfont );
	hei = fontMetrics.height();
	QString qstr = QString::fromLocal8Bit( str );
	wid = fontMetrics.width( qstr );
	switch (gra_textrotation)
	{
		case 0:			/* normal */
			*x = wid;
			*y = hei;
			break;
		case 1:			/* 90 degrees counterclockwise */
			*x = -hei;
			*y = wid;
			break;
		case 2:			/* 180 degrees */
			*x = -wid;
			*y = -hei;
			break;
		case 3:			/* 90 degrees clockwise */
			*x = hei;
			*y = -wid;
			break;
	}
}

void screendrawtext(WINDOWPART *win, INTBIG atx, INTBIG aty, char *s, GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawtext( atx, aty, s, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/*
 * Routine to convert the string "msg" (to be drawn into window "win") into an
 * array of pixels.  The size of the array is returned in "wid" and "hei", and
 * the pixels are returned in an array of character vectors "rowstart".
 * The routine returns nonzero if the operation cannot be done.
 */
BOOLEAN gettextbits(WINDOWPART *win, char *msg, INTBIG *wid, INTBIG *hei, char ***rowstart)
{
	REGISTER INTBIG len, datasize;

	/* quit if string is null */
	*wid = *hei = 0;
	len = strlen(msg);
	if (len == 0) return(FALSE);

	/* determine the size of the text */
	QFontMetrics fontMetrics( gra->curfont );
	*hei = fontMetrics.height();
	QString qstr = QString::fromLocal8Bit( msg );
	*wid = fontMetrics.width( qstr );

	/* allocate space for this */
	datasize = *wid * *hei;
	if (datasize > gra_textbitsdatasize)
	{
		if (gra_textbitsdatasize > 0) efree((char *)gra_textbitsdata);
		gra_textbitsdatasize = 0;

		gra_textbitsdata = (char *)emalloc(datasize, us_tool->cluster);
		if (gra_textbitsdata == 0) return(TRUE);
		gra_textbitsdatasize = datasize;
	}
	if (*hei > gra_textbitsrowstartsize)
	{
		if (gra_textbitsrowstartsize > 0) efree((char *)gra_textbitsrowstart);
		gra_textbitsrowstartsize = 0;
		gra_textbitsrowstart = (char **)emalloc(*hei * (sizeof (char *)), us_tool->cluster);
		if (gra_textbitsrowstart == 0) return(TRUE);
		gra_textbitsrowstartsize = *hei;
	}

	/* load the row start array */
	for(int y=0; y < *hei; y++)
	{
		gra_textbitsrowstart[y] = &gra_textbitsdata[*wid * y];
	}
	*rowstart = gra_textbitsrowstart;

	/* load the image array */
	if (*wid > gra->textbits.width() || *hei > gra->textbits.height()) {
		gra->textbits.resize( QMAX( *wid, gra->textbits.width() ), QMAX( *hei, gra->textbits.height() ) );
	}

	QPainter p( &gra->textbits );
	p.eraseRect( 0, 0, *wid, *hei );
	p.setFont( gra->curfont );
	p.drawText( 0, p.fontMetrics().ascent(), qstr );
	p.end();

	QImage image = gra->textbits.convertToImage();
	for (int i=0; i<*hei; i++) {
		for (int j=0; j < *wid; j++) gra_textbitsrowstart[i][j] = image.pixelIndex( j, i);
	}
	return(FALSE);
}

/******************** CIRCLE DRAWING ********************/

void screendrawcircle(WINDOWPART *win, INTBIG atx, INTBIG aty, INTBIG radius, GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawcircle( atx, aty, radius, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

void screendrawthickcircle(WINDOWPART *win, INTBIG atx, INTBIG aty, INTBIG radius,
	GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawthickcircle( atx, aty, radius, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/******************** DISC DRAWING ********************/

/*
 * routine to draw a filled-in circle of radius "radius"
 */
void screendrawdisc(WINDOWPART *win, INTBIG atx, INTBIG aty, INTBIG radius, GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawdisc( atx, aty, radius, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/******************** ARC DRAWING ********************/

/*
 * draws a thin arc centered at (centerx, centery), clockwise,
 * passing by (x1,y1) and (x2,y2)
 */
void screendrawcirclearc(WINDOWPART *win, INTBIG centerx, INTBIG centery, INTBIG p1_x, INTBIG p1_y,
	INTBIG p2_x, INTBIG p2_y, GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawcirclearc( centerx, centery, p1_x, p1_y, p2_x, p2_y, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/*
 * draws a thick arc centered at (centerx, centery), clockwise,
 * passing by (x1,y1) and (x2,y2)
 */
void screendrawthickcirclearc(WINDOWPART *win, INTBIG centerx, INTBIG centery, INTBIG p1_x, INTBIG p1_y,
	INTBIG p2_x, INTBIG p2_y, GRAPHICS *desc)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	qwin->draw->painter.screendrawthickcirclearc( centerx, centery, p1_x, p1_y, p2_x, p2_y, desc );
	if (ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS) flushscreen();
}

/******************** GRID CONTROL ********************/

/*
 * fast grid drawing routine
 */
void screendrawgrid(WINDOWPART *win, POLYGON *obj)
{
	GraphicsMainWindow *qwin = (GraphicsMainWindow*)win->frame->qt;
	REGISTER INTBIG i, j, xnum, xden, ynum, yden, x0,y0, x1,y1, x2,y2, x3,y3,
		x4,y4, x5,y5, x10, y10, y10mod, xspacing, yspacing, y1base, x1base;
	REGISTER INTSML x, y, fatdots;
	REGISTER VARIABLE *var;

	x0 = obj->xv[0];   y0 = obj->yv[0];		/* screen space grid spacing */
	x1 = obj->xv[1];   y1 = obj->yv[1];		/* screen space grid start */
	x2 = obj->xv[2];   y2 = obj->yv[2];		/* display space low */
	x3 = obj->xv[3];   y3 = obj->yv[3];		/* display space high */
	x4 = obj->xv[4];   y4 = obj->yv[4];		/* screen space low */
	x5 = obj->xv[5];   y5 = obj->yv[5];		/* screen space high */

	var = getvalkey((INTBIG)us_tool, VTOOL, -1, us_gridboldspacingkey);
	if (var == NOVARIABLE) xspacing = yspacing = 10; else
	{
		if ((var->type&VISARRAY) == 0)
			xspacing = yspacing = var->addr; else
		{
			xspacing = ((INTBIG *)var->addr)[0];
			yspacing = ((INTBIG *)var->addr)[1];
		}
	}

	xnum = x3 - x2;
	xden = x5 - x4;
	ynum = y3 - y2;
	yden = y5 - y4;
	double kx = double(xnum)/double(xden);
	double ky = double(ynum)/double(yden);
	x10 = x0*xspacing;       y10 = y0*yspacing;
	y1base = y1 - (y1 / y0 * y0);
	x1base = x1 - (x1 / x0 * x0);

	/* adjust grid placement according to scale */
	fatdots = x0*kx > 75 && y0*ky > 75;
	fatdots++;
	if (x0*kx >= 5 && y0*ky >= 5)
	{
		double px = (x1 - x4)*kx + x2;
		double py = (y1 - y4)*ky + y2;
		int nx = (x5 - x1)/x0;
		int ny = (y5 - y1)/y0;
		qwin->draw->painter.screendrawgrid( fatdots, px, py, x0*kx, y0*ky, nx, ny);
	}
	x1 = x1base - (x1base - x1) / x10 * x10;   x0 = x10;
	y1 = y1base - (y1base - y1) / y10 * y10;   y0 = y10;
	if (x0*kx >= 5 || y0*ky >= 5) fatdots++;
	double px = (x1 - x4)*kx + x2;
	double py = (y1 - y4)*ky + y2;
	int nx = (x5 - x1)/x0;
	int ny = (y5 - y1)/y0;
	qwin->draw->painter.screendrawgrid( fatdots, px, py, x0*kx, y0*ky, nx, ny);
	if (qwin->draw->painter.dirty() &&
	    ticktime() - qwin->draw->painter.startTime() > FLUSHTICKS)
		flushscreen();
}

/******************** EPainter class ********************/

void EPainter::init( QStringList &facelist )
{
    int i;

    for(i=0; i<MAXCACHEDFONTS; i++) gra_fontcache[i] = 0;
    for(i=0; i<FONTHASHSIZE; i++) gra_fonthash[i].font = 0;

    gra_numfaces = facelist.count() + 1;
    gra_facelist = (char **)emalloc(gra_numfaces * (sizeof (char *)), us_tool->cluster);
    Q_CHECK_PTR( gra_facelist );
    (void)allocstring(&gra_facelist[0], _("DEFAULT FACE"), us_tool->cluster);
    for(i=0; i<facelist.count(); i++) {
	char *facename = gra->localize( facelist[i] );
	(void)allocstring(&gra_facelist[i+1], facename, us_tool->cluster);
    }
    if (gra_numfaces > VTMAXFACE) {
	ttyputerr(_("Warning: found %ld fonts, but can only keep track of %d"), gra_numfaces,
		  VTMAXFACE);
	gra_numfaces = VTMAXFACE;
    }
}

void EPainter::term()
{
    int i;

    for(i=0; i<MAXCACHEDFONTS; i++) {
	if(gra_fontcache[i]) delete gra_fontcache[i];
    }
    for(i=0; i<FONTHASHSIZE; i++) {
	if(gra_fonthash[i].font) delete gra_fonthash[i].font;
    }

    for(i=0; i<gra_numfaces; i++) efree((char *)gra_facelist[i]);
    if (gra_numfaces > 0) efree((char *)gra_facelist);

    if (gra_textbitsdatasize > 0) efree((char *)gra_textbitsdata);
    if (gra_textbitsrowstartsize > 0) efree((char *)gra_textbitsrowstart);
}

EPainter::EPainter()
    : QPainter()
{
}

bool EPainter::begin( QPaintDevice *pd )
{
    bool res = QPainter::begin( pd );

    /* draw dummy text to make painter's GC noncacheble */
    if (res) drawText(0, 0, " ");

    return res;
}

void EPainter::setPlanes( ulong planes )
{
    XSetPlaneMask(gra->dpy(), gc, planes );
}

void EPainter::setBrushPlanes( ulong planes = ~0)
{
    XSetPlaneMask(gra->dpy(), gc_brush, planes );
}

void EPainter::setBrush( GRAPHICS *desc )
{
    QColor pixel( 0, desc->col );
    if ((desc->colstyle & NATURE) == PATTERNED)
        QPainter::setBrush( QBrush( pixel, loadStipple( desc->raster ) ) );
    else
        QPainter::setBrush( pixel );
}

void EPainter::updateCopyRect( int lx, int ly, int hx, int hy )
{
    if (dirty()) {
	if (lx < cr.left()) cr.setLeft( lx );
	if (hx > cr.right()) cr.setRight( hx );
	if (ly < cr.top()) cr.setTop( ly );
	if (hy > cr.bottom()) cr.setBottom( hy );
    } else {
        cr = QRect( lx, ly, hx - lx + 1, hy - ly + 1);
	starttime = ticktime();
    }
}

void EPainter::updateAll()
{
    updateCopyRect( viewport().left(), viewport().top(), viewport().right(), viewport().bottom() );
}

QBitmap EPainter::loadStipple( UINTSML raster[] )
{
    uchar bits[32];

    for (int i=0; i < 8; i++) {
        bits[i*4] = bits[i*4+2] = raster[i] & 0xff;
        bits[i*4+1] = bits[i*4+3] = (raster[i] >> 8) & 0xff;
    }
    return QBitmap( 32, 8, bits );
}

void EPainter::screendrawline(INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2, GRAPHICS *desc, INTBIG texture)
{
    QColor pixel( 0, desc->col );
    switch (texture) {
	case 0: setPen( QPen( pixel, 0, SolidLine ) ); break;
	case 1: setPen( QPen( pixel, 0,   DotLine ) ); break;
	case 2: setPen( QPen( pixel, 0,  DashLine ) ); break;
	case 3: setPen( QPen( pixel, 3, SolidLine ) ); break;
    }
    int revy = viewport().bottom();
    y1 = revy - y1;
    y2 = revy - y2;
    setPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawLine( x1, y1, x2, y2 );
    setPlanes();
    int lx, ly, hx, hy;
    if (x1 < x2) { lx = x1; hx = x2; } else { lx = x2; hx = x1; }
    if (y1 < y2) { ly = y1; hy = y2; } else { ly = y2; hy = y1; }
    if (texture == 3) {
        lx--; ly--; hx++; hy++;
    }
    updateCopyRect( lx, ly, hx, hy );
}

void EPainter::screeninvertline(INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2)
{
    save();
    setRasterOp( NotROP );
    setPen( SolidLine );
    int revy = viewport().bottom();
    y1 = revy - y1;
    y2 = revy - y2;
    setPlanes( 0xff/*gra_maphigh*/);
    drawLine( x1, y1, x2, y2 );
    setPlanes();
    restore();
    int lx, ly, hx, hy;
    if (x1 < x2) { lx = x1; hx = x2; } else { lx = x2; hx = x1; }
    if (y1 < y2) { ly = y1; hy = y2; } else { ly = y2; hy = y1; }
    updateCopyRect( lx, ly, hx, hy );
}

void EPainter::screendrawpolygon(INTBIG *x, INTBIG *y, INTBIG count, GRAPHICS *desc)
{
    setPen( NoPen );
    setBrush( desc );
    int revy = viewport().bottom();
    int lx, ly, hx, hy;
    QPointArray points( count );
    lx = hx = x[0];
    ly = hy = revy - y[0];
    points.setPoint( 0, lx, ly );
    for(int i = 1; i < count; i++) {
      int px = x[i];
      int py = revy - y[i];
      if (px < lx) lx = px;
      if (px > hx) hx = px;
      if (py < ly) ly = py;
      if (py > hy) hy = py;
      points.setPoint( i, px, py );
    }
    setBrushPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawPolygon( points );
    setBrushPlanes();
    updateCopyRect( lx, ly, hx, hy );
}

void EPainter::screendrawbox(INTBIG lowx, INTBIG highx, INTBIG lowy, INTBIG highy, GRAPHICS *desc)
{
    setPen( NoPen );
    setBrush( desc );
    int revy = viewport().bottom();
    setBrushPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawRect( lowx, revy - highy, highx - lowx + 1, highy - lowy + 1);
    setBrushPlanes();
    updateCopyRect( lowx, revy - highy, highx, revy - lowy );
}

void EPainter::screeninvertbox(INTBIG lowx, INTBIG highx, INTBIG lowy, INTBIG highy)
{
    save();
    setRasterOp( NotROP );
    setPen( NoPen );
    QPainter::setBrush( SolidPattern );
    int revy = viewport().bottom();
    setBrushPlanes( 0xff/*gra_maphigh*/);
    drawRect( lowx, revy - highy, highx - lowx + 1, highy - lowy + 1);
    setBrushPlanes();
    restore();
    updateCopyRect( lowx, revy - highy, highx, revy - lowy );
}

void EPainter::screenmovebox(INTBIG sx, INTBIG sy, INTBIG wid, INTBIG hei, INTBIG dx, INTBIG dy)
{
    int revy = viewport().bottom();
    bitBlt( device(), dx, revy + 1 - dy - hei, device(), sx, revy + 1 - sy - hei, wid, hei );
    updateCopyRect( dx, revy + 1 - dy - hei, dx + wid - 1, revy - dy );
}

void EPainter::drawPixmap( QPoint orig, QPixmap pixmap )
{
    bitBlt( device(), orig, &pixmap );
    QRect r( orig, pixmap.size() );
    updateCopyRect( r.left(), r.top(), r.right(), r.bottom() );
}

void EPainter::screendrawtext(INTBIG atx, INTBIG aty, char *s, GRAPHICS *desc)
{
    QString qstr = QString::fromLocal8Bit( s );
    save();
    QColor pixel( 0, desc->col );
    setPen( pixel );
    setFont( gra->curfont );
    QFontMetrics fontMetrics( gra->curfont );
    int revy = viewport().bottom();
    aty = revy - aty;
    switch (gra_textrotation) {
	case 0:
	    translate( atx, aty - 1 );
	    break;
	case 1:
	    translate( atx - 1, aty - 1 );
	    rotate( 270 );
	    break;
	case 2:
	    translate( atx - 1, aty );
	    rotate( 180 );
	    break;
	case 3:
	    translate( atx, aty );
	    rotate( 90 );
	    break;
    }
    translate( 0, -fontMetrics.descent());
    setPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawText( 0, 0, qstr );
    setPlanes();
    QRect r = xForm( fontMetrics.boundingRect( qstr ) );
    restore();
    updateCopyRect( r.left(), r.top(), r.right(), r.bottom() );
}

void EPainter::screendrawcircle(INTBIG atx, INTBIG aty, INTBIG radius, GRAPHICS *desc)
{
    QColor pixel( 0, desc->col );
    setPen( pixel );
    int revy = viewport().bottom();
    aty = revy - aty;
    setPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawArc( atx - radius, aty - radius, radius*2, radius*2, 0, 360*16 );
    setPlanes();
    updateCopyRect( atx - radius, aty - radius, atx + radius - 1, aty + radius - 1 );
}

void EPainter::screendrawthickcircle(INTBIG atx, INTBIG aty, INTBIG radius, GRAPHICS *desc)
{
    QColor pixel( 0, desc->col );
    setPen( QPen( pixel, 3, SolidLine ) );
    int revy = viewport().bottom();
    aty = revy - aty;
    setPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawArc( atx - radius, aty - radius, radius*2, radius*2, 0, 360*16 );
    setPlanes();
    updateCopyRect( atx - radius, aty - radius, atx + radius - 1, aty + radius - 1 );
}

void EPainter::screendrawdisc(INTBIG atx, INTBIG aty, INTBIG radius, GRAPHICS *desc)
{
    setPen( NoPen );
    setBrush( desc );
    int revy = viewport().bottom();
    aty = revy - aty;
    setBrushPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawPie( atx - radius, aty - radius, radius*2, radius*2, 0, 360*16 );
    setBrushPlanes();
    updateCopyRect( atx - radius, aty - radius, atx + radius - 1, aty + radius - 1 );
}

void EPainter::screendrawcirclearc(INTBIG centerx, INTBIG centery, INTBIG p1_x, INTBIG p1_y,
				   INTBIG p2_x, INTBIG p2_y, GRAPHICS *desc)
{
    QColor pixel( 0, desc->col );
    setPen( pixel );
    int revy = viewport().bottom();
    centery = revy - centery;
    p1_y = revy - p1_y;
    p2_y = revy - p2_y;
    int radius = computedistance(centerx, centery, p1_x, p1_y);
    int startangle = -figureangle(centerx, centery, p1_x, p1_y);
    int endangle = -figureangle(centerx, centery, p2_x, p2_y);
    if (startangle < endangle) startangle += 3600;
    setPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawArc( centerx - radius, centery - radius, radius*2, radius*2,
	     (startangle*16 + 5) / 10,
	     ((endangle - startangle)*16 + 5) / 10);
    setPlanes();
    updateCopyRect( centerx - radius, centery - radius, centerx + radius - 1, centery + radius - 1 );
}

void EPainter::screendrawthickcirclearc(INTBIG centerx, INTBIG centery, INTBIG p1_x, INTBIG p1_y,
					INTBIG p2_x, INTBIG p2_y, GRAPHICS *desc)
{
    QColor pixel( 0, desc->col );
    setPen( QPen( pixel, 3, SolidLine ) );
    int revy = viewport().bottom();
    centery = revy - centery;
    p1_y = revy - p1_y;
    p2_y = revy - p2_y;
    int radius = computedistance(centerx, centery, p1_x, p1_y);
    int startangle = -figureangle(centerx, centery, p1_x, p1_y);
    int endangle = -figureangle(centerx, centery, p2_x, p2_y);
    if (startangle < endangle) startangle += 3600;
    setPlanes( desc->bits & 0xff/*gra_maphigh*/);
    drawArc( centerx - radius, centery - radius, radius*2, radius*2,
	     (startangle*16 + 5) / 10,
	     ((endangle - startangle)*16 + 5) / 10);
    setPlanes();
    updateCopyRect( centerx - radius - 1, centery - radius - 1, centerx + radius, centery + radius);
}

void EPainter::screendrawgrid(int pointWidth, double x0, double y0, double dx, double dy, int nx, int ny)
{
    if (nx <= 0 || ny <= 0) return;
    QColor pixel( 0, GRID );
    int revy = viewport().bottom();

    /* prepare pixmap with grid row */
    QPixmap pm( viewport().width(), 2*pointWidth - 1);
    pm.fill( color0 );
    QPainter p( &pm );
    p.setPen( QPen( pixel, pointWidth ) );
    QPointArray points;
    for(int ix = 0; ix < nx; ix++) {
        /* draw thick point */
        int x = int( x0 + ix*dx );
	int y = pointWidth - 1;
	switch (pointWidth) {
	    case 3:
	        points.setPoints( 13, x,y,  x+1,y, x,y+1, x-1,y, x,y-1,
				  x+2,y, x+1,y+1, x,y+2, x-1,y+1, x-2,y, x-1,y-1, x,y-2, x+1,y-1 );
	        break;
	    case 2:
	        points.setPoints( 5, x,y,  x+1,y, x,y+1, x-1,y, x,y-1 );
	        break;
	    default:
	        points.setPoints( 1, x,y );
	}
	p.drawPoints( points );
    }
    p.end();

    save();
    setRasterOp( OrROP );
    for(int iy = 0; iy < ny; iy++) {
        QPainter::drawPixmap( 0, int( revy - y0 - iy*dy ) - pointWidth + 1, pm );
    }
    restore();
    updateAll();
}

