[comp.windows.x] X11.2 win entry, exit events problem

toma@tc.fluke.COM (Tom Anderson) (04/07/88)

I recently installed X11.2 at our site, where we are running a mix of Sun-2's 
and Sun-3's. As with the previous release, most of the stuff works. However,
an experimental program that worked with X11.1 no longer works properly - it
seems that window entry and exit events have changed somehow...  I've included
the program below, which is a simple demo of how window entry and exit events
can be used to implement a popup menu facility (by mapping each menu selection
to a window). In X11.1, the entry and exit events were delivered; in X11.2, 
they never are.

If anyone running X on Suns can try this out and let me know if it works at their
site, I would appreciate hearing about the results. Better yet, if you find out
why it no longer works, please let me know. If there is some bug in the latest
release which causes this behavior, I would also appreciate finding out how to 
fix it.

BTW, the code is contributed to the public domain and can be used for any 
purpose.  As far as I know, the popup menu's look and feel does not conflict with
any known proprietary system :-).

Thanks,

Tom Anderson
John Fluke Mfg.
(206) 356-5895
toma@fluke

p.s. - While I have your attention, does anyone know when Sun is going to get
around to improving the performance of the X server for Suns? I know they have 
a bad case of NIH (Not-Invented-Here), and I also know they are planning to 
support X via NeWS, but how hard can it be to support a real X server? The one
that comes with the MIT distribution clearly needs a lot of work for performance 
tuning, and the NeWS implementation may not be much better.

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by spock!toma on Wed Apr  6 11:44:47 PDT 1988
# Contents:  Makefile mask.cursor popup.c popup.cursor
 
echo x - Makefile
sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'

CFLAGS=-g
XLIBS=-lX11

popup : popup.c
	cc $(CFLAGS) -o popup popup.c $(XLIBS)
@//E*O*F Makefile//
chmod u=rw,g=r,o=r Makefile
 
echo x - mask.cursor
sed 's/^@//' > "mask.cursor" <<'@//E*O*F mask.cursor//'
#define mask_width 16
#define mask_height 16
static char mask_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f,
   0xff, 0x07, 0xff, 0x0f, 0xff, 0x07, 0xff, 0x07, 0xff, 0x03, 0xff, 0x01,
   0xff, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0x00, 0x00};
@//E*O*F mask.cursor//
chmod u=rw,g=r,o=r mask.cursor
 
echo x - popup.c
sed 's/^@//' > "popup.c" <<'@//E*O*F popup.c//'
/*
 * experimental program to interactively create an X window, then pop up menu(s) in the window.
 */

#include <stdio.h>

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

extern char * malloc();

/*
 * include menu cursor bitmap data
 */
#include "popup.cursor"
#include "mask.cursor"

/*
 * menu button type
 */
struct menuButton {
    struct menuButton * next;		/* next button in the list */
    Window window;			/* the button window */
    int x, y;				/* origin relative to the menu frame */
    void (* proc)();			/* button selection procedure */
    char label[1];			/* button text: variable-length array */
} MenuButton;

#define MENU_BUTTON struct menuButton

/*
 * pop-up menu type
 */
typedef struct {
    Window window;			/* the menu frame window */
    MENU_BUTTON * buttonList;		/* list of menu buttons */
    MENU_BUTTON ** newButton;		/* insertion point for new buttons */
    Bool isFrameActive;			/* is the cursor currently in the menu frame? */
    Bool isButtonActive;		/* is the cursor currently in a button? */
    Window activeButtonWindow;		/* the window number of the active button */
    int activeButtonNumber;		/* the button number of the active button */
    int width, height;			/* frame geometry */
    int buttonWidth, buttonHeight;	/* button geometry */
    XFontStruct * font;			/* button font info */
    GC fontGC;				/* the graphics context for drawing button text */
    Cursor cursor;			/* the menu cursor */
} PopupMenu;

/*
 * create a pop-up menu
 */
PopupMenu * 
CreatePopupMenu(display, progName)
    Display * display;			/* the display */
    char * progName;			/* the program name */
{
    PopupMenu * pmp;
    char * fontName;			/* the button font name */

    if ((pmp = (PopupMenu *) malloc((PopupMenu *) sizeof(PopupMenu))) != (PopupMenu *) 0) {
	pmp->buttonList = (MENU_BUTTON *) 0;
	pmp->newButton = &pmp->buttonList;
	pmp->isButtonActive = pmp->isFrameActive = False;
	/* find and map the font name */
	if ((fontName = XGetDefault(display, progName, "buttonFont")) == (char *) 0) {
	    fontName = "9x15";
	}
	if((pmp->font = XLoadQueryFont(display, fontName)) == (XFontStruct *) 0) {
	    free((char *) pmp);
	    fprintf(stderr, "%s: can't load font %s\n", progName, fontName);
	    return((PopupMenu *) 0);
	}
	/* size the button height to fit the button font */
	pmp->buttonHeight = pmp->font->max_bounds.ascent + pmp->font->max_bounds.descent + 1;
	pmp->buttonWidth = pmp->width = pmp->height = 0;
    }
    return(pmp);
}

/*
 * add a button to a pop-up menu
 */
void
AddMenuItem(menu, label, proc)
    PopupMenu * menu;			/* the pop-up menu */
    char * label;			/* the button label */
    void (* proc)();			/* the button procedure (NOT IMPLEMENTED) */
{
    register MENU_BUTTON * nmbp;	/* menu button ptr. */
    int width;				/* overall button box width */

    /*
     * allocate the MenuButton struct for this button
     */
    if ((nmbp = (MENU_BUTTON *) malloc(sizeof(MENU_BUTTON) + strlen(label))) != (MENU_BUTTON *) 0) {

	/* compute the menu button parameters */
	nmbp->next = (MENU_BUTTON *) 0;
	nmbp->proc = proc;
	nmbp->x = 0;
	nmbp->y = menu->height;
	strcpy(nmbp->label, label);

	/* compute the new menu frame parameters */
	menu->height += menu->buttonHeight;
	width = XTextWidth(menu->font, label, strlen(label));
	menu->width 
	    = menu->buttonWidth 
	    = menu->buttonWidth > width ? menu->buttonWidth : width + 2 * menu->font->max_bounds.width;

	/* link the new button into the button list */
	*(menu->newButton) = nmbp;
	menu->newButton = &nmbp->next;
    }
}

/*
 * NOTE: should return status
 */
void
InstallPopupMenu(display, screen, menu)
    Display * display;			/* the display */
    int screen;				/* the screen */
    PopupMenu * menu;			/* the menu */
{
    register MENU_BUTTON * mbp;		/* the menu button descriptor */
    XSetWindowAttributes win_attrs;	/* the window's attributes */
    XWindowAttributes ret_win_attrs;	/* returned window attributes */
    XGCValues context_values;		/* context loading values */
    Pixmap cursorPixmap;		/* the cursor pixmap */
    Pixmap maskPixmap;			/* the cursor mask pixmap */
    XColor black, white;		/* ? not sure this is the best way to do this */
    
    /*
     * create the pop-up menu frame window
     */
    win_attrs.background_pixel = WhitePixel(display, screen);
    win_attrs.border_pixmap = None;
    win_attrs.border_pixel = BlackPixel(display, screen);	/* black border */
    win_attrs.backing_store = Always;
    win_attrs.save_under = True;
    win_attrs.override_redirect = True;
    win_attrs.event_mask = ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask;
    menu->width += 2;		/* allow for button border widths */
    menu->height += 2;
    menu->window = XCreateWindow(display, /* parent: */ RootWindow(display, screen),
	/* x, y, width, height, border_width, depth,  */
	0, 0, menu->width, menu->height, 0, 1,
	/* class, visual, valuemask, attributes */
	InputOutput, DefaultVisual(display, screen), 
	CWEventMask | CWBackPixel | CWBorderPixmap | CWSaveUnder 
	    | CWBackingStore | CWBorderPixel | CWOverrideRedirect, 
	&win_attrs);

    /*
     * read the popup cursor bitmap and set up the cursor
     */
    cursorPixmap = XCreateBitmapFromData(display, menu->window, popup_bits, popup_width, popup_height);
    maskPixmap = XCreateBitmapFromData(display, menu->window, mask_bits, mask_width, mask_height);
    if (cursorPixmap != NULL && maskPixmap != NULL) {
	/*
	 * find the XColors of black and white (there has to a more straightforward
	 * way to do this)
	 */
	XGetWindowAttributes(display, menu->window, &ret_win_attrs);
	black.pixel = BlackPixel(display, screen);
	XQueryColor(display, ret_win_attrs.colormap, &black);
	white.pixel = WhitePixel(display, screen);
	XQueryColor(display, ret_win_attrs.colormap, &white);
	menu->cursor = XCreatePixmapCursor(display, cursorPixmap, maskPixmap,
	    &black, &white, popup_x_hot, popup_y_hot);
	XDefineCursor(display, menu->window, menu->cursor);
    }

    /*
     * establish the graphics context used to draw button box text
     */
    context_values.function = GXset;
    context_values.foreground = BlackPixel(display,screen);
    context_values.font = menu->font->fid;
    menu->fontGC = XCreateGC(display, menu->window, 
	GCForeground | GCFunction | GCFont, &context_values);

    /*
     * create and map the menu button windows 
     *
     * NOTE:  The cursor mask is included; however, this shouldn't be
     * necessary, since it should be inherited from the menu frame window.
     * Something seems amiss in the way the server is handling this.
     */
    win_attrs.event_mask = EnterWindowMask | LeaveWindowMask;
    win_attrs.cursor = menu->cursor;
    for (mbp = menu->buttonList ; mbp != (MENU_BUTTON *) 0 ; mbp = mbp->next) {
	mbp->window = XCreateWindow(display, /* parent: */ menu->window,
	    /* x, y, width, height, border_width, depth,  */
	    mbp->x, mbp->y, menu->buttonWidth, menu->buttonHeight, 1, 1,
	    /* class, visual, valuemask, attributes */
	    InputOutput, DefaultVisual(display, screen), 
	    CWEventMask | CWBackPixel | CWBorderPixmap | CWBorderPixel | CWBackingStore | CWCursor,
	    &win_attrs);
	XMapWindow(display, mbp->window); 
	/*
	 * draw the button labels' text
	 */
	/* XDrawString(display, mbp->window, menu->fontGC, 
	    menu->font->max_bounds.width/2, menu->font->max_bounds.ascent,
	    mbp->label, strlen(mbp->label)); */
    }
}

DoPopupMenu(display, screen, menu, x, y)
    Display * display;			/* display */
    int screen;				/* screen */
    PopupMenu * menu;			/* menu */
    int x, y;				/* midpoint of the top bar of the menu */
{
    register MENU_BUTTON * mbp;		/* menu button pointer */
    XWindowChanges win_chngs;		/* window configuration arg struct */
    XWindowAttributes ret_win_attrs;	/* returned window attributes */
    GC context;				/* the graphics context (for drawing lines) */
    XGCValues context_values;		/* context loading values */
    int activeButtonNumber = 0;		/* the currently active button */
    register int i;

    /*
     * locate and pop up the menu window
     */
    XGetWindowAttributes(display, menu->window, &ret_win_attrs);
    win_chngs.x = x - ret_win_attrs.width / 2;
    win_chngs.y = y;
    XConfigureWindow(display, menu->window, CWX | CWY, &win_chngs);
    XMapRaised(display, menu->window);
    for (mbp = menu->buttonList ; mbp != (MENU_BUTTON *) 0 ; mbp = mbp->next) {
	/* draw the button labels' text */
	XDrawString(display, mbp->window, menu->fontGC, 
	    (3 * menu->font->max_bounds.width) / 2, menu->font->max_bounds.ascent,
	    mbp->label, strlen(mbp->label));
    }
    /* XAllowEvents(display, SyncPointer, CurrentTime); */
    XDefineCursor(display, RootWindow(display, screen), menu->cursor);
    XFlush(display);

    /*
     * establish the graphics context used to highlight boxes
     */
    context_values.function = GXxor;	/* draw by xor'ing src. and dest. */
    context_values.foreground = BlackPixel(display,screen);
    context = XCreateGC(display, menu->window, GCForeground | GCFunction, &context_values);

    /* 
     * main loop: track the window button box until the mouse button is released
     */
    activeButtonNumber = 0;
    while(1) {
	XEvent event;

	XNextEvent(display, &event);
	/* fprintf(stderr, "event type: %d\n", event.type);   */
	switch(event.type) {
	case ButtonPress:
	    /* if we are to (erroneously) start up the popup menu */
	    /* if (event.xbutton.button == Button1) {
		fprintf(stderr, "button press, xbutton.button = %d\n", 
		    event.xbutton.button);
	    } */
	    break;
	case ButtonRelease:
	    /* if we are releasing button 1 */
	    if (event.xbutton.button == Button1) {
		XUndefineCursor(display, RootWindow(display, screen));
		XUnmapWindow(display, menu->window);
		return(activeButtonNumber);
	    } /* else {
		 fprintf(stderr, "other button release, xbutton.button = %d\n", 
		    event.xbutton.button); 
	    } */
	    break;
	case EnterNotify:
	    /* traverse the button list */
	    for (mbp = menu->buttonList , i = 1 ; mbp != (MENU_BUTTON *) 0 ; mbp = mbp->next , i++) {
		/* if we entered this button */
		if (mbp->window == event.xcrossing.window) {
		    /* highlight the new button */
		    /* XFillRectangle(display, mbp->window, context, 0, 0, menu->buttonWidth, menu->buttonHeight); */
		    XFillRectangle(display, mbp->window, context, 0, 0, 
			menu->font->max_bounds.width, menu->buttonHeight);
		    activeButtonNumber = i;
		    break;
		}
	    }
	    /* fprintf(stderr, "entering window %d\n", event.xcrossing.window); */
	    break;
	case LeaveNotify:
	    /* traverse the button list */
	    for (mbp = menu->buttonList ; mbp != (MENU_BUTTON *) 0 ; mbp = mbp->next) {
		/* if we left this button */
		if (mbp->window == event.xcrossing.window) {
		    /* de-highlight the old button */
		    /* XFillRectangle(display, mbp->window, context, 0, 0, menu->buttonWidth, menu->buttonHeight); */
		    XFillRectangle(display, mbp->window, context, 0, 0, 
			menu->font->max_bounds.width, menu->buttonHeight);
		    activeButtonNumber = 0;
		    break;
		}
	    }
	    /* fprintf(stderr, "leaving window %d\n", event.xcrossing.window); */
	    break;
	}
    }
}

main(argc, argv)
    int argc;
    char ** argv;
{
    Display * display;			/* the display */
    int screen;				/* the screen */
    Window window;			/* the main window */
    PopupMenu * menu;			/* the pop-up menu */
    XSetWindowAttributes win_attrs;	/* the window's attributes */

    /* 
     * open the default display 
     */
    if ((display = XOpenDisplay((char *) 0)) == (Display *) 0) {
	fprintf(stderr, "%s: can't open the default display\n", argv[0]);
	exit(1);
    }

    /* 
     * create a window we can draw in 
     */
    screen = DefaultScreen(display);
    win_attrs.background_pixel = WhitePixel(display, screen);
    win_attrs.border_pixmap = None;
    win_attrs.border_pixel = BlackPixel(display, screen);	/* black border */
    win_attrs.event_mask = ButtonPressMask | ButtonReleaseMask /* | ButtonMotionMask */;
    window = XCreateWindow(display, /* parent: */ RootWindow(display, screen),
	/* x, y, width, height, border_width, depth,  */
	0, 0, 100, 100, 1, 1,
	/* class, visual, valuemask, attributes */
	InputOutput, DefaultVisual(display, screen), 
	CWEventMask | CWBackPixel | CWBorderPixmap | CWBorderPixel /* | CWOverrideRedirect */ , 
	&win_attrs);
    
    /*
     * map the main window 
     */
    XMapWindow(display, window);

    /* 
     * next, create the popup menu and install the buttons
     */
    menu = CreatePopupMenu(display, argv[0]);
    AddMenuItem(menu, "foo", (void (*)()) 0);
    AddMenuItem(menu, "bar", (void (*)()) 0);
    AddMenuItem(menu, "selection 1", (void (*)()) 0);
    AddMenuItem(menu, "agTYqj", (void (*)()) 0);
    InstallPopupMenu(display, screen, menu);

#ifdef DOGRAB
    /*
     * establish a passive, synchronous grab on the window sponsoring the menu
     */
    XGrabButton(display,  Button1, 0, window, True, 
	ButtonReleaseMask | EnterWindowMask | LeaveWindowMask,
	GrabModeSync, GrabModeAsync, None, None); 
#endif DOGRAB

    XFlush(display);
    /* 
     * main loop: map and unmap the pop-up menu
     */
    while(1) {
	XEvent event;

	XNextEvent(display, &event);
	/* fprintf(stderr, "event type: %d\n", event.type);  */
	if (event.type == ButtonPress && event.xbutton.button == Button1) {
	    fprintf(stderr, "you selected button %d\n",
		DoPopupMenu(display, screen, menu, event.xbutton.x_root, event.xbutton.y_root));
	}
    }
    /* XCloseDisplay(display);	/* not really necessary */
}
@//E*O*F popup.c//
chmod u=rw,g=r,o=r popup.c
 
echo x - popup.cursor
sed 's/^@//' > "popup.cursor" <<'@//E*O*F popup.cursor//'
#define popup_width 16
#define popup_height 16
#define popup_x_hot 15
#define popup_y_hot 4
static char popup_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x09, 0x80, 0x09, 0x78,
   0x09, 0x04, 0x09, 0x08, 0x09, 0x06, 0x09, 0x04, 0x09, 0x03, 0x09, 0x01,
   0xf9, 0x00, 0x09, 0x00, 0x0f, 0x00, 0x00, 0x00};
@//E*O*F popup.cursor//
chmod u=rw,g=r,o=r popup.cursor
 
exit 0
-- 

Tom Anderson, (206) 356-5895, ... uw-beaver!fluke!toma
John Fluke Mfg. Co., Inc., POB C9090, Everett, Wa. 98206

mlandau@bbn.com (Matt Landau) (04/07/88)

In comp.windows.x (<3335@fluke.COM>), toma@tc.fluke.COM (Tom Anderson) writes:
>p.s. - While I have your attention, does anyone know when Sun is going to get
>around to improving the performance of the X server for Suns? I know they have 
>a bad case of NIH (Not-Invented-Here), and I also know they are planning to 
>support X via NeWS, but how hard can it be to support a real X server? 

Not to start any SunView vs X11 vs NeWS flamage or anything, but from what 
I've heard and seen, the merged NeWS/X11 server will BE a "real X server."  
The plan is in some sense to layer the NeWS and X11 protocol interpreters 
on top of a common window "forest" and a common kernel of imaging code.

Given that the underlying event handling and imaging stuff should be the 
same for the NeWS and X sides of the server, the performance for NeWS and
X11 programs should be comparable, except for the additional overhead that
may be imposed by having to ship lots of X events over the server/client
connection instead of loading some PostScript into the server and having 
more of the application handled directly for you.
--
 Matt Landau			Waiting for a flash of enlightenment
 mlandau@bbn.com			  in all this blood and thunder

RWS@ZERMATT.LCS.MIT.EDU (Robert Scheifler) (04/09/88)

This isn't a problem with V11R2.  The bug was in V11R1 with sending
enter/leave events to them that shouldn't get them, and the bug is in
your code, for not asking for them properly, using OwnerGrabButton.
BTW, I can't demonstrate the cursor problem noted in your code, and you
have made an error (that I've seen many people make) in not
understanding what XOR means or how to use it.  When I ran your code on
an Apollo display, I got no highlighting.  Fixes:

187,189d186
<      * NOTE:  The cursor mask is included; however, this shouldn't be
<      * necessary, since it should be inherited from the menu frame window.
<      * Something seems amiss in the way the server is handling this.
192d188
<     win_attrs.cursor = menu->cursor;
199c195
< 	    CWEventMask | CWBackPixel | CWBorderPixmap | CWBorderPixel | CWBackingStore | CWCursor,
---
> 	    CWEventMask | CWBackPixel | CWBorderPixmap | CWBorderPixel | CWBackingStore,
247c243
<     context_values.foreground = BlackPixel(display,screen);
---
>     context_values.foreground = BlackPixel(display,screen) ^ WhitePixel(display, screen);
337c333
<     win_attrs.event_mask = ButtonPressMask | ButtonReleaseMask /* | ButtonMotionMask */;
---
>     win_attrs.event_mask = ButtonPressMask | ButtonReleaseMask | OwnerGrabButtonMask /* | ButtonMotionMask */;
366c362
< 	ButtonReleaseMask | EnterWindowMask | LeaveWindowMask,
---
> 	ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | OwnerGrabButtonMask,