|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T s
Length: 19085 (0x4a8d)
Types: TextFile
Names: »sxNotify.c«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki
└─⟦526ad3590⟧ »EUUGD11/gnu-31mar87/X.V10.R4.tar.Z«
└─⟦2109abc41⟧
└─⟦this⟧ »./X.V10R4/Toolkit/Sx/code/sxNotify.c«
/*
* $Source: /u1/Sx.new/code/RCS/sxNotify.c,v $
* $Header: sxNotify.c,v 1.1 86/12/03 16:10:26 swick Exp $
*/
#ifndef lint
static char *rcsid_sxNotify_c = "$Header: sxNotify.c,v 1.1 86/12/03 16:10:26 swick Exp $";
#endif lint
/*
* sxNotify.c --
*
* This file provides a notification facility, whereby users
* are notified of various conditions and given a choice of
* options. Notifiers are built using the facilities of
* the X window manager and the Sx dispatcher.
*
* Copyright (C) 1986 Regents of the University of California.
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting
* documentation. The University of California makes no
* representations about the suitability of this software for
* any purpose. It is provided "as is" without express or
* implied warranty.
*/
#ifndef lint
static char rcsid[] = "$Header: sxNotify.c,v 1.1 86/12/03 16:10:26 swick Exp $ SPRITE (Berkeley)";
#endif not lint
#include <X/Xlib.h>
#include "sprite.h"
#include "char.h"
#include "io.h"
#include "string.h"
#include "sx.h"
#include "sxInt.h"
#include "varg.h"
/*
* A notifier is a window that pops up and displays a message and
* a set of options. The message is displayed at the top of the
* window, with the options spaced across the bottom of the window.
* Each option is drawn in a box, much like a button. The user
* reads the message and clicks a mouse button over one of the
* options. This file does cursor tracking to see when the mouse
* is over one of the options.
*/
typedef struct {
char *text; /* Text to display for option. */
int x, y; /* Location of option within notifier. */
int width, height; /* Dimensions of option, including border. */
} Option;
/*
* Parameters controlling layout of notifier:
*
* SPACING: spacing between lines of message.
* OPTION_MARGIN space around top and bottom of text in option buttons.
* OPTION_BORDER: thickness of borders around options.
* WINDOW_BORDER: thickness of border around notifier itself.
*/
#define SPACING 0
#define OPTION_MARGIN 2
#define OPTION_BORDER 1
#define WINDOW_BORDER 2
/*
* Cursor for notifier windows:
*/
#include "cursors/dot"
#include "cursors/dotMask"
static Cursor cursor;
/*
* Bitmap used to paint a fancy header area around the options.
*/
#include "notify.bits"
static Pixmap header;
/*
* Display dimensions:
*/
static int displayWidth, displayHeight;
static Boolean init = FALSE;
/*
* Forward declarations:
*/
extern void NotifierInit();
extern char * EndOfLine();
extern void OptionRedisplay();
\f
/*
*----------------------------------------------------------------------
*
* Sx_Notify --
*
* This procedure creates a notifier window displaying text and
* all the option strings. It waits until one of the option
* strings has been buttoned with the mouse, then returns. This
* procedure grabs the server, which prevents any other window
* activity until the user has selected an option.
*
* Results:
* The return value is the index of the option that was
* buttoned, where 0 corresponds to the first option in
* the list.
*
* Side effects:
* A window is created, then deleted.
*
*----------------------------------------------------------------------
*/
/* VARARGS7 */
int
Sx_Notify(parent, x, y, width, message, fontPtr, center, Varg_Args)
Window parent; /* Window in whose coordinates x and y are
* specified below. */
int x, y; /* Location of UL corner of notifier, in
* coords of parent. */
int width; /* Width of notifier, in pixels. If 0,
* then this procedure picks a width that
* makes the notifier look pretty. */
char *message; /* Message to be displayed in notifier.
* May contain newlines to break into
* lines explicitly. This procedure will
* automatically break lines at spaces
* to make sure that no line is longer
* than width pixels. */
FontInfo *fontPtr; /* Describes font in which to display text
* in the notifier. If NULL, then a default
* font will be chosen. */
Boolean center; /* TRUE means center each line in the window,
* FALSE means left-justify lines. */
Varg_Decl; /* Two or more options, each a "char *".
* The last option must be NULL. */
{
#define MAXOPTIONS 16
register Option *o;
Option options[MAXOPTIONS]; /* Space for options. */
int nOptions; /* Actual number of options. */
int nLines; /* Number of lines in notifier, not including
* options. */
int minOptionLength; /* Total width of all options, assuming
* optionPad is 0. */
int optionPad; /* Amount of extra space to leave between
* options (in addition to "space"). */
int optionY; /* Y-coord of top of option boxes. */
int optionHeight; /* Size of options. */
int currentOption; /* Option that the mouse is over, or -1. */
int space; /* Width of average character in font. Used
* to determine how much space to leave around
* edges of message and other places. */
WindowInfo info; /* Holds dimensions of parent. */
Pixmap savedPixmap; /* If non-zero, holds image of what was
* underneath notifier window (to save
* redrawing). */
Window w;
int outerWidth, outerHeight;/* Outside dimensions of w, including space
* for shadowed box. */
int i, trialWidth, inc, height, tmp;
Varg_List args;
char *p;
if (!init) {
NotifierInit();
}
if (fontPtr == NULL) {
fontPtr = Sx_GetDefaultFont();
}
/*
* Fill in most of the info in the options array, and compute
* how much screen space is needed for all the options if they're
* placed side-to-side. If I get to choose how wide the window
* is, this will be a lower bound.
*/
minOptionLength = 0;
optionHeight = fontPtr->height + 2*OPTION_BORDER + 2*OPTION_MARGIN;
space = fontPtr->width;
Varg_Start(args);
for (nOptions = 0; nOptions < MAXOPTIONS; nOptions++) {
o = &options[nOptions];
o->text = Varg_Next(args, char *);
if (o->text == NULL) {
break;
}
o->width = XStringWidth(o->text, fontPtr, 0, 0) + 2*space
+ 2*OPTION_BORDER;
o->height = optionHeight;
minOptionLength += o->width;
}
minOptionLength += (nOptions+1) * space;
/*
* Choose the width, if the caller didn't get one. Start with
* a wide width, and narrow it down until the window is about
* 1.5 times as wide as it is high (within 10%). If the caller
* already chose a width, then just compute the height it needs.
*/
optionY = optionHeight/2 + SHADOW_TOP;
if (width <= 0) {
trialWidth = DisplayWidth()/2;
} else {
trialWidth = width;
}
for (inc = trialWidth/2; ; inc /= 2) {
p = message;
for (nLines = 0, p = message; *p != 0; nLines++) {
p = EndOfLine(p, fontPtr, trialWidth - 2*space, (int *) NULL);
while (*p == ' ') {
p++;
}
if (*p == '\n') {
p++;
}
}
height = 2*optionHeight + nLines*(fontPtr->height + SPACING)
+ 2*space;
if (width > 0) {
break;
}
if (inc < 5) {
width = trialWidth;
break;
}
if ((height + height/2) > trialWidth) {
trialWidth += inc;
} else {
trialWidth -= inc;
}
if (trialWidth < minOptionLength) {
trialWidth = minOptionLength;
}
}
outerWidth = width + SHADOW_LEFT + SHADOW_RIGHT;
outerHeight = height + SHADOW_TOP + SHADOW_BOTTOM;
/*
* Fill in the rest of the option information, distributing the
* extra space evenly between the options if there's any to spare.
*/
optionPad = (width - minOptionLength)/(nOptions+1);
if (optionPad < 0) {
optionPad = 0;
}
tmp = (width - minOptionLength - (nOptions+1)*optionPad)/2
+ optionPad + space + SHADOW_LEFT;
for (i = 0; i < nOptions; i++) {
o = &options[i];
o->x = tmp;
o->y = optionY;
tmp += o->width + optionPad + space;
}
/*
* Compute the final location of the notifier window, which will
* be in RootWindow. If the specified parent wasn't RootWindow,
* the coordinate transformation will also be needed. If the
* caller didn't give a location, center the notifier in its
* parent.
*/
XQueryWindow(parent, &info);
if (x < 0) {
x = (info.width - outerWidth)/2;
}
if (y < 0) {
y = (info.height - outerHeight)/2;
}
if (parent != RootWindow) {
int xOffset, yOffset;
Window dummy;
XInterpretLocator(parent, &xOffset, &yOffset, &dummy, 0);
x -= xOffset;
y -= yOffset;
}
displayWidth = DisplayWidth();
displayHeight = DisplayHeight();
if ((x + outerWidth) > displayWidth) {
x = displayWidth - outerWidth;
}
if ((y + outerHeight) > displayHeight) {
y = displayHeight - outerHeight;
}
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
/*
* Create a window for the notifier.
*/
w = XCreateWindow(RootWindow, x, y, outerWidth, outerHeight, 0,
BlackPixmap, WhitePixmap);
if (w == 0) {
Sx_Panic("Sx_Notify: couldn't create window.");
}
XDefineCursor(w, cursor);
XSelectInput(w, ButtonPressed|MouseMoved|ExposeWindow);
/*
* Grab the server, then save the information that's about to get
* overlayed by the notifier window, if the entire area is on-screen.
* Then map the window.
*/
Sx_GrabMouse(w, cursor, ButtonPressed|MouseMoved|ExposeWindow);
XGrabServer();
if (((x + outerWidth) <= displayWidth)
&& ((y + outerHeight) <= displayHeight)) {
savedPixmap = XPixmapSave(RootWindow, x, y, outerWidth, outerHeight);
} else {
savedPixmap = 0;
}
XMapWindow(w);
/*
* Process events until a button is pushed over an option.
*/
currentOption = -1;
while (TRUE) {
XMouseMovedEvent event;
XWindowEvent(w, MouseMoved|ButtonPressed|ExposeWindow, &event);
switch ((int) event.type) {
case MouseMoved:
/*
* See if the mouse is over an option. If so, display
* the option in reverse video.
*/
if ((event.y >= optionY) && (event.y <=
optionY + options[0].height)) {
for (i = 0; i < nOptions; i++) {
o = &options[i];
if ((event.x < o->x) || (event.x > o->x + o->width)) {
continue;
}
goto newOption;
}
}
i = -1;
newOption:
if (i != currentOption) {
if (currentOption != -1) {
OptionRedisplay(w, &options[currentOption],
fontPtr, FALSE, space);
}
if (i != -1) {
OptionRedisplay(w, &options[i], fontPtr, TRUE, space);
}
currentOption = i;
}
break;
case ButtonPressed:
if (currentOption >= 0) {
/*
* Flash the button before returning.
*/
for (i = 0; i < 2; i++) {
OptionRedisplay(w, &options[currentOption],
fontPtr, FALSE, space);
SxFlashWait();
OptionRedisplay(w, &options[currentOption],
fontPtr, TRUE, space);
SxFlashWait();
}
if (savedPixmap != 0) {
XPixmapPut(w, 0, 0, 0, 0, outerWidth, outerHeight,
savedPixmap, GXcopy, AllPlanes);
XUnmapTransparent(w);
XFreePixmap(savedPixmap);
}
XUngrabServer();
Sx_UngrabMouse();
XDestroyWindow(w);
XFlush();
return currentOption;
}
break;
case ExposeWindow:
/*
* Clear the background and draw the shadow.
*/
XPixSet(w, 0, 0, outerWidth, outerHeight, WhitePixel);
SxDrawShadow(w, 0, 0, width, height, BlackPixel, savedPixmap);
/*
* Display the message, one line at a time.
*/
p = message;
for (y = 2*optionHeight + space + SHADOW_TOP; ;
y += SPACING + fontPtr->height) {
char *next;
int extraSpace;
if (*p == 0) {
break;
}
next = EndOfLine(p, fontPtr, width - 2*space,
&extraSpace);
if (center) {
extraSpace /= 2;
} else {
extraSpace = 0;
}
if (next != p) {
XText(w, extraSpace + space + SHADOW_LEFT, y, p,
next-p, fontPtr->id, BlackPixel, WhitePixel);
}
/*
* For lines not terminated by newline, eat up any white
* space at the beginning of the next line. For lines
* terminated by newline, treat the space as significant.
*/
p = next;
while (*p == ' ') {
p++;
}
if (*p == '\n') {
p++;
}
}
/*
* Display the options.
*/
XTileSet(w, SHADOW_LEFT, SHADOW_TOP, width,
2*optionHeight, header);
XLine(w, SHADOW_LEFT, 2*optionHeight + SHADOW_TOP - 1,
width, 2*optionHeight + SHADOW_TOP - 1, 1, 1,
BlackPixel, GXcopy, AllPlanes);
for (i = 0; i < nOptions; i++) {
OptionRedisplay(w, &options[i], fontPtr,
i == currentOption, space);
}
break;
}
}
}
\f
/*
*----------------------------------------------------------------------
*
* Sx_Panic --
*
* This procedure is called when some totally unexpected and
* irremediable error has occurred. It displays a message,
* waits for the user to acknowledge it, and terminates the
* process.
*
* Results:
* None. Never returns.
*
* Side effects:
* The process dies a terrible, agonizing death.
*
*----------------------------------------------------------------------
*/
void
Sx_Panic(msg)
char *msg; /* Message explaining what happened. */
{
char realMsg[2000];
static Boolean alreadyPanicked = FALSE;
if (!alreadyPanicked) {
alreadyPanicked = TRUE;
Io_PrintString(realMsg, "PANIC!!\n\n%.1950s", msg);
(void) Sx_Notify(RootWindow, -1, -1, 0, realMsg, (FontInfo *) NULL,
TRUE, "Kill Process", NULL);
}
exit(1);
}
\f
/*
*----------------------------------------------------------------------
*
* EndOfLine --
*
* This procedure is used to figure out how much stuff will fit
* on one line of a notifier. Assuming that the first character
* of string is to be at the beginning of a line, and that the
* line may be at most width pixels wide using fontPtr to display the
* text, this procedure determines how much stuff will fit on the
* line. The end of the line occurs either when a newline character
* appears, or at the end of a word if the next word would extend
* the line wider than width. If a single word would more than fill
* the entire line, then the word is broken in the middle.
*
* Results:
* The return value is the address of the character just after the
* last one that fits on the line. It can be a newline character,
* a space character, a NULL character (end of string), or even
* a regular character if a single word overflows the line. If
* extraPtr is non-NULL, *extraPtr gets filled in with the number
* of unused pixels in this line.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static char *
EndOfLine(string, fontPtr, width, extraPtr)
register char *string; /* String to be displayed. */
register FontInfo *fontPtr; /* Describes font in which string is to
* be displayed. */
int width; /* Maximum number of pixels across line. */
int *extraPtr; /* If non-NULL, fill in the value pointed
* to with the number of pixels leftover
* in the line, or 0 if this line is
* width pixels wide. */
{
char *lastWordBreak = NULL;
int lastWidth, charSize;
register char c;
for (c = *string; c != 0; string++, c = *string) {
if (Char_IsSpace(c)) {
lastWordBreak = string;
lastWidth = width;
}
if (c == '\n') {
goto done;
}
if ((c >= fontPtr->firstchar) && (c <= fontPtr->lastchar)) {
if (fontPtr->fixedwidth) {
charSize = fontPtr->width;
} else {
charSize = fontPtr->widths[c-fontPtr->firstchar];
}
}
width -= charSize;
if (width < 0) {
if (lastWordBreak != NULL) {
goto done;
}
width += charSize;
break;
}
}
lastWordBreak = string;
lastWidth = width;
done:
if (extraPtr != NULL) {
*extraPtr = lastWidth;
}
return lastWordBreak;
}
\f
/*
*----------------------------------------------------------------------
*
* OptionRedisplay --
*
* This procedure redraws a single option button.
*
* Results:
* None.
*
* Side effects:
* Information gets drawn on the screen.
*
*----------------------------------------------------------------------
*/
static void
OptionRedisplay(w, optionPtr, fontPtr, reverse, space)
Window w; /* Window in which to draw option. */
register Option *optionPtr; /* Option to redraw. */
FontInfo *fontPtr; /* Font in which to draw. */
Boolean reverse; /* TRUE means draw option as white letters
* on black background. FALSE means
* reverse. */
int space; /* Blank space to leave on each end of text. */
{
if (reverse) {
XPixSet(w, optionPtr->x, optionPtr->y, optionPtr->width,
optionPtr->height, BlackPixel);
XText(w, optionPtr->x + OPTION_BORDER + space,
optionPtr->y + OPTION_BORDER + OPTION_MARGIN,
optionPtr->text, String_Length(optionPtr->text), fontPtr->id,
WhitePixel, BlackPixel);
} else {
XPixSet(w, optionPtr->x, optionPtr->y, optionPtr->width,
optionPtr->height, WhitePixel);
XPixSet(w, optionPtr->x, optionPtr->y, optionPtr->width,
OPTION_BORDER, BlackPixel);
XPixSet(w, optionPtr->x, optionPtr->y, OPTION_BORDER,
optionPtr->height, BlackPixel);
XPixSet(w, optionPtr->x + optionPtr->width - OPTION_BORDER,
optionPtr->y, OPTION_BORDER, optionPtr->height, BlackPixel);
XPixSet(w, optionPtr->x, optionPtr->y + optionPtr->height
- OPTION_BORDER, optionPtr->width, OPTION_BORDER, BlackPixel);
XText(w, optionPtr->x + OPTION_BORDER + space,
optionPtr->y + OPTION_BORDER + OPTION_MARGIN,
optionPtr->text, String_Length(optionPtr->text),
fontPtr->id, BlackPixel, WhitePixel);
}
}
\f
/*
*----------------------------------------------------------------------
*
* NotifierInit --
*
* This procedure is called once only to initialize shared
* variables for the module.
*
* Results:
* None.
*
* Side effects:
* Random initializations get performed. See the code for
* details. If there's any problem, the procedure panics.
*
*----------------------------------------------------------------------
*/
static void
NotifierInit()
{
Bitmap tmp;
int width, height;
cursor = XCreateCursor(dot_width, dot_height, dot_bits,
dotMask_bits, dot_x_hot, dot_y_hot,
BlackPixel, WhitePixel, GXcopy);
header = 0;
XQueryTileShape(notify_width, notify_height, &width, &height);
if ((width <= 16) && (height <= 16)) {
tmp = XStoreBitmap(width, height, notify_bits);
if (tmp != 0) {
header = XMakePixmap(tmp, BlackPixel, WhitePixel);
XFreeBitmap(tmp);
}
}
if (header == 0) {
header = WhitePixmap;
}
displayWidth = DisplayWidth();
displayHeight = DisplayHeight();
if (cursor == 0) {
Sx_Panic("Sx_Notify: couldn't initialize cursor.");
}
init = TRUE;
}