/* * File: xdisplay.c * Last modified on Mon Jul 25 14:08:21 1994 by eroberts * ----------------------------------------------------- * This file implements the xdisplay.h interface, which is * responsible for all of the X windows interaction. Before * you attempt to understand this implementation, you need to * understand the basics of X11 library. */ /* * General implementation notes * ---------------------------- * This implementation creates two X drawables: the graphics * window itself (mainWindow) and an offscreen window (osWindow). * All rendering is done into the offscreen window. When update * events occur, the graphics window is updated by copying bits * from the offscreen window. */ #include #include #include #include #include #include #include #include #include "genlib.h" #include "strlib.h" #include "glibrary.h" #include "xmanager.h" #include "xdisplay.h" /* * Parameters * ---------- * DesiredWidth -- Desired width of the graphics window in inches * DesiredHeight -- Desired height of the graphics window in inches * BorderPixels -- Width of the window border in pixels * MaxFontList -- Size of the font list we will accept * PStartSize -- Starting size for polygon (must be greater than 1) * DefaultFont -- Font that serves as the "Default" font */ #define DesiredWidth 7.0 #define DesiredHeight 5.0 #define BorderPixels 1 #define MaxFontList 500 #define PStartSize 50 #define DefaultFont "courier-medium" /* * Other constants * --------------- * Epsilon -- Small offset used to avoid banding/aliasing * GCFgBg -- GC mask for both foreground and background */ #define Epsilon 0.0000000001 #define GCFgBg (GCForeground | GCBackground) /* * Static table: grayList * ---------------------- * This table contains the bitmaps for the various gray-scale * values. Adding more bitmaps to this list increases * the precision at which the client can specify gray scales. */ static char grayList[][8] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22 }, { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA }, { 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD }, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, }; #define NGrays (sizeof grayList / sizeof grayList[0]) /* * Private variables * ----------------- * redraw -- TRUE if mainWindow needs redrawing * eraseMode -- TRUE if erase mode has been set * xdpi, ydpi -- Dots per inch in each coordinate * * disp -- X display containing windows * mainWindow -- Handle of graphics window * osWindow -- Handle of offscreen window bitmap * drawColor -- Color used for drawing (black) * eraseColor -- Color used for erasing (white) * mainGC -- The graphics context (GC) for mainWindow * drawGC -- The GC used for drawing on osWindow * eraseGC -- The GC used for erasing on osWindow * grayGC -- Array of GCs representing gray scales * grayStipple -- Array of stipples used for gray scales * currentFont -- Name of current font * currentSize -- Current point size * fontInfo -- Font structure pointer for current font * windowWidth -- Width of the window in inches * windowHeight -- Height of the window in inches * * argV, argC -- Used to set corresponding X window fields * * regionStarted -- TRUE is a region is in progress * regionGrayScale -- Gray scale density [0,1] * polygonPoints -- Array of points used in current region * nPolygonPoints -- Number of active points * polygonSize -- Number of allocated points */ static bool redraw = FALSE; static bool eraseMode; static double xdpi, ydpi; static Display *disp; static Window mainWindow; static Pixmap osWindow; static unsigned long drawColor, eraseColor; static GC mainGC, drawGC, eraseGC; static GC grayGC[NGrays]; static Pixmap grayStipple[NGrays]; static string currentFont; static XFontStruct *fontInfo; static int windowWidth = DesiredWidth; static int windowHeight = DesiredHeight; static string argV[] = { "xdisplay" }; static int argC = sizeof argV / sizeof argV[0]; static bool regionStarted; static double regionGrayScale; static XPoint *polygonPoints; static int nPolygonPoints; static int polygonSize; /* Private function prototypes */ static void InitGC(void); static void ForceRedraw(void); static void RedrawWindow(void); static void StartPolygon(void); static void AddSegment(int x0, int y0, int x1, int y1); static void DisplayPolygon(void); static void RenderArc(double x, double y, double rx, double ry, double start, double sweep); static int SizeFromFontName(string fontName); static double InchesX(int x); static double InchesY(int y); static int PixelsX(double x); static int PixelsY(double y); static int ScaleX(double x); static int ScaleY(double y); /* Exported entries */ /* * Function: OpenDisplay * --------------------- * The OpenDisplay function is relatively long but consists of * little more than a series of X calls to establish the * parameters of the display. */ void OpenDisplay(string title) { XWMHints xwmh; XSizeHints xsh; Colormap colormap; XFontStruct *font; Screen *screen; XSetWindowAttributes xswa; long width, height; int bitmask, scnum; char geometry[100]; if ((disp = XOpenDisplay(NULL)) == NULL) { Error("Can't open display"); } if ((font = XLoadQueryFont(disp, "fixed")) == NULL) { Error("Can't create font"); } scnum = DefaultScreen(disp); screen = ScreenOfDisplay(disp, scnum); colormap = DefaultColormap(disp, scnum); drawColor = BlackPixel(disp, scnum); eraseColor = WhitePixel(disp, scnum); xdpi = 25.4 * WidthOfScreen(screen) / WidthMMOfScreen(screen); ydpi = 25.4 * HeightOfScreen(screen) / HeightMMOfScreen(screen); width = PixelsX(DesiredWidth); height = PixelsY(DesiredHeight); sprintf(geometry, "%dx%d+%d+%d", width, height, (DisplayWidth(disp, scnum) - width) / 2, (DisplayHeight(disp, scnum) - height) / 2); bitmask = XGeometry(disp, scnum, geometry, geometry, BorderPixels, font->max_bounds.width, font->max_bounds.ascent + font->max_bounds.descent, 1, 1, &xsh.x, &xsh.y, &xsh.width, &xsh.height); if (bitmask & (XValue | YValue)) xsh.flags |= USPosition; if (bitmask & (WidthValue | HeightValue)) xsh.flags |= USSize; mainWindow = XCreateSimpleWindow(disp, DefaultRootWindow(disp), xsh.x, xsh.y, xsh.width, xsh.height, BorderPixels, drawColor, eraseColor); XSetStandardProperties(disp, mainWindow, title, title, None, argV, argC, &xsh); xwmh.flags = (InputHint | StateHint); xwmh.input = False; xwmh.initial_state = NormalState; XSetWMHints(disp, mainWindow, &xwmh); xswa.colormap = colormap; xswa.bit_gravity = CenterGravity; XChangeWindowAttributes(disp, mainWindow, CWColormap | CWBitGravity, &xswa); XSelectInput(disp, mainWindow, ExposureMask); XMapWindow(disp, mainWindow); osWindow = XCreatePixmap(disp, DefaultRootWindow(disp), xsh.width, xsh.height, 1); fontInfo = NULL; InitGC(); regionStarted = FALSE; ClearDisplay(); XFlush(disp); } /* * Function: CloseDisplay * ---------------------- * This function frees the X structures allocated by the package. */ void CloseDisplay(void) { int i; XDestroyWindow(disp, mainWindow); XFreePixmap(disp, osWindow); XFreeGC(disp, mainGC); XFreeGC(disp, drawGC); XFreeGC(disp, eraseGC); for (i = 0; i < NGrays; i++) { XFreeGC(disp, grayGC[i]); XFreePixmap(disp, grayStipple[i]); } XCloseDisplay(disp); } /* * Function: DisplayFD * ------------------- * This function simply returns the connection number and exists * only to isolate the client from any interaction with X, which * is necessary for the disp argument. */ int DisplayFD(void) { return (XConnectionNumber(disp)); } /* * Function: ProcessXEvent * ----------------------- * This function is used to read and respond to a pending X event, * although the only event considered in this implementation is the * expose event. The function returns TRUE if an event was processed * and FALSE otherwise. */ bool ProcessXEvent(void) { XEvent event; if (!XPending(disp)) return (FALSE); XNextEvent(disp, &event); if (event.xany.window == mainWindow) { if (event.type == Expose && event.xexpose.count == 0) { RedrawWindow(); } } return (TRUE); } /* * Function: CheckForRedraw * ------------------------ * This function allows the client to specify that a quiescent point * has been achieved and that it would be a good time to redraw the * window. A redraw occurs only if graphics updates have been made. */ void CheckForRedraw(void) { if (redraw) ForceRedraw(); } /* * Function: SetRedrawFlag * ----------------------- * This function allows the client to indicate that the display has * changed and that a redraw operation should be performed when the * next call to CheckForRedraw occurs. This mechanism currently * sets a single flag and should at some point be redesigned to * maintain the update region. */ void SetRedrawFlag(void) { redraw = TRUE; } /* * Function: ClearDisplay * ---------------------- * This function erases the entire display by filling with the * erase color. */ void ClearDisplay(void) { int itemp; unsigned int width, height, utemp; Window wtemp; if (XGetGeometry(disp, osWindow, &wtemp, &itemp, &itemp, &width, &height, &utemp, &utemp) == 0) return; XFillRectangle(disp, osWindow, eraseGC, 0, 0, width, height); } /* * Function: DisplayLine * --------------------- * This function draws the requested line unless a region is in * progress, in which case it adds the line segment to the polygon. */ void DisplayLine(double x, double y, double dx, double dy) { int x0, y0, x1, y1; x0 = ScaleX(x); y0 = ScaleY(y); x1 = ScaleX(x + dx); y1 = ScaleY(y + dy); if (regionStarted) { AddSegment(x0, y0, x1, y1); } else { XDrawLine(disp, osWindow, (eraseMode) ? eraseGC : drawGC, x0, y0, x1, y1); } } /* * Function: DisplayArc * -------------------- * This function ordinarily scales its arguments and uses them * to draw an arc using the standard XDrawArc call. If, however, * a region has been started, that arc must be rendered using * line segments, which is handled by RenderArc. */ void DisplayArc(double x, double y, double rx, double ry, double start, double sweep) { int ixc, iyc, irx, iry, istart, isweep; if (regionStarted) { RenderArc(x, y, rx, ry, start, sweep); } else { ixc = ScaleX(x); iyc = ScaleY(y); irx = PixelsX(rx); iry = PixelsY(ry); istart = Round(start); isweep = Round(sweep); if (isweep < 0) { isweep = -isweep; istart -= isweep; } if (istart < 0) { istart = 360 - (-istart % 360); } istart %= 360; XDrawArc(disp, osWindow, (eraseMode) ? eraseGC : drawGC, ixc - irx, iyc - iry, 2 * irx, 2 * iry, 64 * istart, 64 * isweep); } } /* * Function: DisplayText * --------------------- * This function transforms the client arguments and makes the * appropriate X call to display text on the screen. */ void DisplayText(double x, double y, string text) { int ix, iy; ix = ScaleX(x); iy = ScaleY(y); XDrawString(disp, osWindow, (eraseMode) ? eraseGC : drawGC, ix, iy, text, strlen(text)); } /* * Function: DisplayTextWidth * -------------------------- * This function simply calls the XTextWidth function to compute the * width of the displayed text and scales it to the client coordinate * system. */ double DisplayTextWidth(string text) { return (InchesX(XTextWidth(fontInfo, text, strlen(text)))); } /* * Function: DisplaySetFont * ------------------------ * This function searches the font names table for a font that matches * the argument characteristics as closely as possible. As required * by the extgraph.h client interface, the function does not change * the font if no such font exists and selects the closest existing * size. */ string DisplaySetFont(string font, int size) { char fontbuf[MaxFontName + 30]; string fontName, *fontList; int i, nFonts, bestIndex, bestSize, currentSize; bool ok, boldFlag, styleFlag; fontName = ConvertToLowerCase(font); if (StringEqual(fontName, "default")) { sprintf(fontbuf, "*-%s-*", DefaultFont); } else { sprintf(fontbuf, "*-%s-*", fontName); } boldFlag = styleFlag = FALSE; if (FindString("-bold", fontName, 0) != -1) boldFlag = TRUE; if (FindString("-i", fontName, 0) != -1) styleFlag = TRUE; if (FindString("-o", fontName, 0) != -1) styleFlag = TRUE; FreeBlock(fontName); fontList = XListFonts(disp, fontbuf, MaxFontList, &nFonts); if (nFonts != 0) { bestIndex = -1; for (i = 1; i < nFonts; i++) { fontName = fontList[i]; ok = TRUE; ok &= boldFlag || (FindString("-medium-", fontName, 0) != -1); ok &= styleFlag || (FindString("-r-", fontName, 0) != -1); if (ok) { currentSize = SizeFromFontName(fontName); if (bestIndex == -1 || abs(size - currentSize) < abs(size - bestSize)) { bestSize = currentSize; bestIndex = i; } } } if (bestIndex != -1) { if (fontInfo != NULL) XFreeFont(disp, fontInfo); fontInfo = XLoadQueryFont(disp, fontList[bestIndex]); if (fontInfo == NULL) { Error("Internal error: Can't find font"); } XSetFont(disp, drawGC, fontInfo->fid); XSetFont(disp, eraseGC, fontInfo->fid); XFreeFontNames(fontList); currentFont = CopyString(font); currentSize = bestSize; } } sprintf(fontbuf, "%d %s", currentSize, currentFont); return (CopyString(fontbuf)); } /* * Function: DisplaySetTitle * ------------------------- * This function copies the user-supplied string into the window and * icon name. */ void DisplaySetTitle(string title) { XTextProperty tp; XStringListToTextProperty(&title, 1, &tp); XSetWMIconName(disp, mainWindow, &tp); XSetWMName(disp, mainWindow, &tp); } /* * Function: DisplaySetEraseMode * ----------------------------- * This function sets the internal state of the display logic so * that it maintains the correct state of the eraseMode flag. In * the rest of the code, the eraseMode flag is used to control * which colors are used. */ void DisplaySetEraseMode(bool flag) { eraseMode = flag; } /* * Function: DisplayStartRegion * ---------------------------- * This function changes the state of the package so that subsequent * calls to DisplayLine and DisplayArc are used to add segments to a * polygonal region instead of having them appear on the display. * See the code for StartPolygon, AddSegment, and DisplayPolygon for * details. */ void DisplayStartRegion(double grayScale) { regionStarted = TRUE; regionGrayScale = grayScale; StartPolygon(); } /* * Function: DisplayEndRegion * -------------------------- * This function closes the region opened by DisplayStartRegion * and displays the assembled polygon. */ void DisplayEndRegion(void) { DisplayPolygon(); regionStarted = FALSE; } /* * Functions: DisplayGetWidth, DisplayGetHeight * -------------------------------------------- * These functions allow the client to get the width and height * of the graphics window. */ double DisplayGetWidth(void) { return (windowWidth); } double DisplayGetHeight(void) { return (windowHeight); } /* Private functions */ /* * Function: InitGC * Usage: InitGC(); * ---------------- * This function is used as part of the initialization and creates * all of the GC structures used for drawing. */ static void InitGC(void) { XGCValues gcv; Window root; int i; gcv.foreground = eraseColor; gcv.background = eraseColor; eraseGC = XCreateGC(disp, osWindow, GCFgBg, &gcv); gcv.foreground = drawColor; mainGC = XCreateGC(disp, mainWindow, GCFgBg, &gcv); drawGC = XCreateGC(disp, osWindow, GCFgBg, &gcv); gcv.fill_style = FillOpaqueStippled; root = DefaultRootWindow(disp); for (i = 0; i < NGrays; i++) { grayStipple[i] = gcv.stipple = XCreateBitmapFromData(disp, root, grayList[i], 8, 8); grayGC[i] = XCreateGC(disp, osWindow, GCFgBg | GCStipple | GCFillStyle, &gcv); } } /* * Function: ForceRedraw * Usage: ForceRedraw(); * --------------------- * This function forces the screen to update itself by sending an * Expose event for the window. */ static void ForceRedraw(void) { XEvent event; event.type = Expose; event.xany.window = mainWindow; event.xexpose.count = 0; XSendEvent(disp, mainWindow, False, 0, &event); XFlush(disp); } /* * Function: RedrawWindow * Usage: RedrawWindow(); * ---------------------- * This function redraws the active display window by copying bits * from the offscreen bitmap. */ static void RedrawWindow(void) { int itemp; unsigned int width, height, utemp; Window wtemp; if (XGetGeometry(disp, mainWindow, &wtemp, &itemp, &itemp, &width, &height, &utemp, &utemp) == 0) return; XCopyPlane(disp, osWindow, mainWindow, mainGC, 0, 0, width, height, 0, 0, 1); redraw = FALSE; } /* * Functions: StartPolygon, AddSegment, EndPolygon * Usage: StartPolygon(); * AddSegment(x0, y0, x1, y1); * AddSegment(x1, y1, x2, y2); * . . . * DisplayPolygon(); * ---------------------------------- * These functions implement the notion of a region in the X * world, where the easiest shape to fill is a polygon. Calling * StartPolygon initializes the array polygonPoints so that * subsequent calls to AddSegment will add points to it. * The points in the polygon are assumed to be contiguous, * because the client interface checks for this property. * Because polygons involving arcs can be quite large, the * AddSegment code extends the polygonPoints list if needed * by doubling the size of the array. All storage is freed * after calling DisplayPolygon, which uses the XFillPolygon * call to generate the display. */ static void StartPolygon(void) { polygonPoints = NewArray(PStartSize, XPoint); polygonSize = PStartSize; nPolygonPoints = 0; } static void AddSegment(int x0, int y0, int x1, int y1) { XPoint *newPolygon; int i; if (nPolygonPoints >= polygonSize) { polygonSize *= 2; newPolygon = NewArray(polygonSize, XPoint); for (i = 0; i < nPolygonPoints; i++) { newPolygon[i] = polygonPoints[i]; } FreeBlock(polygonPoints); polygonPoints = newPolygon; } if (nPolygonPoints == 0) { polygonPoints[nPolygonPoints].x = x0; polygonPoints[nPolygonPoints].y = y0; nPolygonPoints++; } polygonPoints[nPolygonPoints].x = x1; polygonPoints[nPolygonPoints].y = y1; nPolygonPoints++; } static void DisplayPolygon(void) { GC fillGC; int px; if (eraseMode) { fillGC = eraseGC; } else { px = regionGrayScale * (NGrays - 1) + 0.5 - Epsilon; fillGC = grayGC[px]; } XFillPolygon(disp, osWindow, fillGC, polygonPoints, nPolygonPoints, Complex, CoordModeOrigin); FreeBlock(polygonPoints); } /* * Function: RenderArc * Usage: RenderArc(x, y, rx, ry, start, sweep); * --------------------------------------------- * This function is identical to the DisplayArc function except * that the arc is rendered using line segments as part of a * polygonal region. */ static void RenderArc(double x, double y, double rx, double ry, double start, double sweep) { double t, mint, maxt, dt; int ix0, iy0, ix1, iy1; if (sweep < 0) { start += sweep; sweep = -sweep; } dt = atan2(InchesY(1), MaxF(fabs(rx), fabs(ry))); mint = Radians(start); maxt = Radians(start + sweep); ix0 = ScaleX(x + rx * cos(mint)); iy0 = ScaleY(y + ry * sin(mint)); for (t = mint + dt; t < maxt; t += dt) { if (t > maxt - dt / 2) t = maxt; ix1 = ScaleX(x + rx * cos(t)); iy1 = ScaleY(y + ry * sin(t)); AddSegment(ix0, iy0, ix1, iy1); ix0 = ix1; iy0 = iy1; } } /* * Function: SizeFromFontName * Usage: SizeFromFontName(); * -------------------------- * This function is a simple utility for DisplaySetFont that returns * the font size from an X font name. */ static int SizeFromFontName(string fontName) { char *sizePtr; sizePtr = strstr(fontName, "--"); if (sizePtr == NULL) return (-1); return (atoi(sizePtr + 2)); } /* Low-level conversion functions */ /* * Functions: InchesX, InchesY * Usage: inches = InchesX(pixels); * inches = InchesY(pixels); * -------------------------------- * These functions convert distances measured in pixels to inches. * Because the resolution may not be uniform in the horizontal and * vertical directions, the coordinates are treated separately. */ static double InchesX(int x) { return ((double) x / xdpi); } static double InchesY(int y) { return ((double) y / ydpi); } /* * Functions: PixelsX, PixelsY * Usage: pixels = PixelsX(inches); * pixels = PixelsY(inches); * -------------------------------- * These functions convert distances measured in inches to pixels. */ static int PixelsX(double x) { return (Round(x * xdpi + Epsilon)); } static int PixelsY(double y) { return (Round(y * ydpi + Epsilon)); } /* * Functions: ScaleX, ScaleY * Usage: pixels = ScaleX(inches); * pixels = ScaleY(inches); * -------------------------------- * These functions are like PixelsX and PixelsY but convert coordinates * rather than lengths. The difference is that y-coordinate values must * be inverted top to bottom to support the cartesian coordinates of * the graphics.h model. */ static int ScaleX(double x) { return (PixelsX(x)); } static int ScaleY(double y) { return (PixelsY(windowHeight - y)); }