1/* Copyright    Massachusetts Institute of Technology    1985	*/
2
3#include "copyright.h"
4
5/*
6Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007
7  Free Software Foundation, Inc.
8
9This program is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2, or (at your option)
12any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; see the file COPYING.  If not, write to the
21Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22Boston, MA 02110-1301, USA. */
23
24
25/*
26 * XMenu:	MIT Project Athena, X Window system menu package
27 *
28 *	XMenuActivate -	Maps a given menu to the display and activates
29 *			the menu for user selection.  The user is allowed to
30 *			specify which pane and selection will be current,
31 *			the X and Y location of the menu (relative to the
32 *			parent window) and the mouse button event mask that
33 *			will be used to identify a selection request.
34 *
35 *			A menu selection is shown to be current by placing
36 *			a highlight box around the selection as the mouse
37 *			cursor enters its active region.  Inactive selections
38 *			will not be highlighted.  As the mouse cursor moved
39 *			from one menu pane to another menu pane the pane being
40 *			entered is raised and made current and the pane being
41 *			left is lowered.
42 *
43 *			Anytime XMenuActivate returns, the p_num and
44 *			s_num are left at their last known values (i.e.,
45 *			the last known current pane and selection indices).
46 *			The following are the defined return states:
47 *
48 *			1)	If at any time an error occurs the data
49 *				pointer is left untouched and XM_FAILURE
50 *				is returned.
51 *
52 *			2)	When a selection request is received (i.e.,
53 *				when the specified mouse event occurs) the
54 *				data pointer will be set to the data
55 *				associated with the particular selection
56 *				current at the time of the selection request
57 *				and XM_SUCCESS is returned.
58 *
59 *			3)	If no selection was current at the time a
60 *				selection request is made the data pointer
61 *				will be left untouched and XM_NO_SELECT will
62 *				be returned.
63 *
64 *			4)	If the selection that was current at the time
65 *				a selection request is made is not an active
66 *				selection the data pointer will be left
67 *				untouched and XM_IA_SELECT will be returned.
68 *
69 *			Since X processes events in an asynchronous manner
70 *			it is likely that XMenuActivate will encounter
71 *			a "foreign event" while it is executing.  Foreign
72 *			events are handled in one of three ways:
73 *
74 *			1)	The event is discarded.  This is the default
75 *				mode and requires no action on the part of the
76 *				application.
77 *
78 *			2)	The application has identified an asynchronous
79 *				event handler that will be called and the
80 *				foreign event handed off to it.  Note:
81 *				AEQ mode disables this mode temporarily.
82 *
83 *			3)	The application has enabled asynchronous event
84 *				queuing mode.  In this mode all foreign events
85 *				will be	queued up untill XMenuActivate
86 *				terminates; at which time they will be
87 *				returned to the	X event queue.  As long as
88 *				AEQ mode is enabled any asynchronous event
89 *				handler as temporarily disabled.
90 *
91 *			Any events encountered while taking down the menu
92 *			(i.e., exposure events from occluded windows) will
93 *			automatically be returned to the X event queue after
94 *			XMenuActivate has cleaned the queue of any of its own
95 *			events that are no longer needed.
96 *
97 *	Author:		Tony Della Fera, DEC
98 *			March 12, 1986
99 *
100 */
101
102#include <config.h>
103#include "XMenuInt.h"
104#include <X11/keysym.h>
105
106/* For debug, set this to 0 to not grab the keyboard on menu popup */
107int x_menu_grab_keyboard = 1;
108
109typedef void (*Wait_func)();
110
111static Wait_func wait_func;
112static void* wait_data;
113
114void
115XMenuActivateSetWaitFunction (func, data)
116     Wait_func func;
117     void *data;
118{
119  wait_func = func;
120  wait_data = data;
121}
122
123int
124XMenuActivate(display, menu, p_num, s_num, x_pos, y_pos, event_mask, data,
125	      help_callback)
126    register Display *display;		/* Display to put menu on. */
127    register XMenu *menu;		/* Menu to activate. */
128    int *p_num;				/* Pane number selected. */
129    int *s_num;				/* Selection number selected. */
130    int x_pos;				/* X coordinate of menu position. */
131    int y_pos;				/* Y coordinate of menu position. */
132    unsigned int event_mask;		/* Mouse button event mask. */
133    char **data;			/* Pointer to return data value. */
134    void (* help_callback) ();		/* Help callback.  */
135{
136    int status;				/* X routine call status. */
137    int orig_x;				/* Upper left menu origin X coord. */
138    int orig_y;				/* Upper left menu origin Y coord. */
139    int ret_val;			/* Return value. */
140
141    register XMPane *p_ptr;		/* Current XMPane. */
142    register XMPane *event_xmp;		/* Event XMPane pointer. */
143    register XMPane *cur_p;		/* Current pane. */
144    register XMSelect *cur_s;		/* Current selection. */
145    XMWindow *event_xmw;		/* Event XMWindow pointer. */
146    XEvent event;			/* X input event. */
147    XEvent peek_event;			/* X input peek ahead event. */
148
149    Bool selection = False;		/* Selection has been made. */
150    Bool forward = True;		/* Moving forward in the pane list. */
151
152    Window root, child;
153    int root_x, root_y, win_x, win_y;
154    unsigned int mask;
155    KeySym keysym;
156
157    /*
158     * Define and allocate a foreign event queue to hold events
159     * that don't belong to XMenu.  These events are later restored
160     * to the X event queue.
161     */
162    typedef struct _xmeventque {
163	XEvent event;
164	struct _xmeventque *next;
165    } XMEventQue;
166
167    XMEventQue *feq = NULL;    		/* Foreign event queue. */
168    XMEventQue *feq_tmp;		/* Foreign event queue temporary. */
169
170    /*
171     * If there are no panes in the menu then return failure
172     * because the menu is not initialized.
173     */
174    if (menu->p_count == 0) {
175	_XMErrorCode = XME_NOT_INIT;
176	return(XM_FAILURE);
177    }
178
179    /*
180     * Find the desired current pane.
181     */
182    cur_p = _XMGetPanePtr(menu, *p_num);
183    if (cur_p == NULL) {
184	return(XM_FAILURE);
185    }
186    cur_p->activated = cur_p->active;
187
188    /*
189     * Find the desired current selection.
190     * If the current selection index is out of range a null current selection
191     * will be assumed and the cursor will be placed in the current pane
192     * header.
193     */
194    cur_s = _XMGetSelectionPtr(cur_p, *s_num);
195
196    /*
197     * Compute origin of menu so that cursor is in
198     * Correct pane and selection.
199     */
200    _XMTransToOrigin(display,
201		     menu,
202		     cur_p, cur_s,
203		     x_pos, y_pos,
204		     &orig_x, &orig_y);
205    menu->x_pos = orig_x;	/* Store X and Y coords of menu. */
206    menu->y_pos = orig_y;
207
208    if (XMenuRecompute(display, menu) == XM_FAILURE) {
209	return(XM_FAILURE);
210    }
211
212    /*
213     * Flush the window creation queue.
214     * This batches all window creates since lazy evaluation
215     * is more efficient than individual evaluation.
216     * This routine also does an XFlush().
217     */
218    if (_XMWinQueFlush(display, menu, cur_p, cur_s) == _FAILURE) {
219	return(XM_FAILURE);
220    }
221
222    /*
223     * Make sure windows are in correct order (in case we were passed
224     * an already created menu in incorrect order.)
225     */
226    for(p_ptr = menu->p_list->next; p_ptr != cur_p; p_ptr = p_ptr->next)
227	XRaiseWindow(display, p_ptr->window);
228    for(p_ptr = menu->p_list->prev; p_ptr != cur_p->prev; p_ptr = p_ptr->prev)
229	XRaiseWindow(display, p_ptr->window);
230
231    /*
232     * Make sure all selection windows are mapped.
233     */
234    for (
235	p_ptr = menu->p_list->next;
236	p_ptr != menu->p_list;
237	p_ptr = p_ptr->next
238    ){
239	XMapSubwindows(display, p_ptr->window);
240    }
241
242    /*
243     * Synchronize the X buffers and the event queue.
244     * From here on, all events in the queue that don't belong to
245     * XMenu are sent back to the application via an application
246     * provided event handler or discarded if the application has
247     * not provided an event handler.
248     */
249    XSync(display, 0);
250
251    /*
252     * Grab the mouse for menu input.
253     */
254
255    status = XGrabPointer(
256			  display,
257			  menu->parent,
258			  True,
259			  event_mask,
260			  GrabModeAsync,
261			  GrabModeAsync,
262			  None,
263			  menu->mouse_cursor,
264			  CurrentTime
265			  );
266    if (status == Success && x_menu_grab_keyboard)
267      {
268        status = XGrabKeyboard (display,
269                                menu->parent,
270                                False,
271                                GrabModeAsync,
272                                GrabModeAsync,
273                                CurrentTime);
274        if (status != Success)
275          XUngrabPointer(display, CurrentTime);
276      }
277
278    if (status == _X_FAILURE) {
279	_XMErrorCode = XME_GRAB_MOUSE;
280	return(XM_FAILURE);
281    }
282
283    /*
284     * Map the menu panes.
285     */
286    XMapWindow(display, cur_p->window);
287    for (p_ptr = menu->p_list->next;
288	 p_ptr != cur_p;
289	 p_ptr = p_ptr->next)
290      XMapWindow(display, p_ptr->window);
291    for (p_ptr = cur_p->next;
292	 p_ptr != menu->p_list;
293	 p_ptr = p_ptr->next)
294      XMapWindow(display, p_ptr->window);
295
296    XRaiseWindow(display, cur_p->window);	/* Make sure current */
297						/* pane is on top. */
298
299    cur_s = NULL;			/* Clear current selection. */
300
301    /*
302     * Begin event processing loop.
303     */
304    while (1) {
305        if (wait_func) (*wait_func) (wait_data);
306	XNextEvent(display, &event);	/* Get next event. */
307	switch (event.type) {		/* Dispatch on the event type. */
308    case Expose:
309	    event_xmp = (XMPane *)XLookUpAssoc(display,
310					       menu->assoc_tab,
311					       event.xexpose.window);
312	    if (event_xmp == NULL) {
313		/*
314		 * If AEQ mode is enabled then queue the event.
315		 */
316		if (menu->aeq) {
317		    feq_tmp = (XMEventQue *)malloc(sizeof(XMEventQue));
318		    if (feq_tmp == NULL) {
319			_XMErrorCode = XME_CALLOC;
320			return(XM_FAILURE);
321		    }
322		    feq_tmp->event = event;
323		    feq_tmp->next = feq;
324		    feq = feq_tmp;
325		}
326		else if (_XMEventHandler) (*_XMEventHandler)(&event);
327		break;
328	    }
329	    if (event_xmp->activated) {
330		XSetWindowBackground(display,
331				     event_xmp->window,
332				     menu->bkgnd_color);
333	    }
334	    else {
335		XSetWindowBackgroundPixmap(display,
336					   event_xmp->window,
337					   menu->inact_pixmap);
338	    }
339	    _XMRefreshPane(display, menu, event_xmp);
340	    break;
341    case EnterNotify:
342	    /*
343	     * First wait a small period of time, and see
344	     * if another EnterNotify event follows hard on the
345	     * heels of this one. i.e., the user is simply
346	     * "passing through". If so, ignore this one.
347	     */
348
349	    event_xmw = (XMWindow *)XLookUpAssoc(display,
350						 menu->assoc_tab,
351						 event.xcrossing.window);
352	    if (event_xmw == NULL) break;
353	    if (event_xmw->type == SELECTION) {
354		/*
355		 * We have entered a selection.
356		 */
357		/* if (XPending(display) == 0) usleep(150000); */
358		if (XPending(display) != 0) {
359		    XPeekEvent(display, &peek_event);
360		    if(peek_event.type == LeaveNotify) {
361			break;
362		    }
363		}
364		cur_s = (XMSelect *)event_xmw;
365		help_callback (cur_s->help_string,
366			       cur_p->serial, cur_s->serial);
367
368		/*
369		 * If the pane we are in is active and the
370		 * selection entered is active then activate
371		 * the selection.
372		 */
373		if (cur_p->active && cur_s->active > 0) {
374		    cur_s->activated = 1;
375		    _XMRefreshSelection(display, menu, cur_s);
376		}
377	    }
378	    else {
379		/*
380		 * We have entered a pane.
381		 */
382		/* if (XPending(display) == 0) usleep(150000); */
383		if (XPending(display) != 0) {
384		    XPeekEvent(display, &peek_event);
385		    if (peek_event.type == EnterNotify) break;
386		}
387		XQueryPointer(display,
388			      menu->parent,
389			      &root, &child,
390			      &root_x, &root_y,
391			      &win_x, &win_y,
392			      &mask);
393		event_xmp = (XMPane *)XLookUpAssoc(display,
394						   menu->assoc_tab,
395						   child);
396		if (event_xmp == NULL) break;
397		if (event_xmp == cur_p) break;
398		if (event_xmp->serial > cur_p->serial) forward = True;
399		else forward = False;
400		p_ptr = cur_p;
401		while (p_ptr != event_xmp) {
402		    if (forward) p_ptr = p_ptr->next;
403		    else p_ptr = p_ptr->prev;
404		    XRaiseWindow(display, p_ptr->window);
405		}
406		if (cur_p->activated) {
407		    cur_p->activated = False;
408		    XSetWindowBackgroundPixmap(display,
409					       cur_p->window,
410					       menu->inact_pixmap);
411		    _XMRefreshPane(display, menu, cur_p);
412		}
413		if (event_xmp->active) event_xmp->activated = True;
414#if 1
415		/*
416		 * i suspect the we don't get an EXPOSE event when backing
417		 * store is enabled; the menu windows content is probably
418		 * not drawn in when it should be in that case.
419		 * in that case, this is probably an ugly fix!
420		 * i hope someone more familiar with this code would
421		 * take it from here.  -- caveh@eng.sun.com.
422		 */
423		XSetWindowBackground(display,
424				     event_xmp->window,
425				     menu->bkgnd_color);
426		_XMRefreshPane(display, menu, event_xmp);
427#endif
428		cur_p = event_xmp;
429	    }
430	    break;
431    case LeaveNotify:
432	    event_xmw = (XMWindow *)XLookUpAssoc(
433						 display,
434						 menu->assoc_tab,
435						 event.xcrossing.window
436						 );
437	    if (event_xmw == NULL) break;
438	    if(cur_s == NULL) break;
439
440	    /*
441	     * If the current selection was activated then
442	     * deactivate it.
443	     */
444	    if (cur_s->activated) {
445		cur_s->activated = False;
446		_XMRefreshSelection(display, menu, cur_s);
447	    }
448	    cur_s = NULL;
449	    break;
450
451    case ButtonPress:
452    case ButtonRelease:
453		*p_num = cur_p->serial;
454		/*
455		 * Check to see if there is a current selection.
456		 */
457		if (cur_s != NULL) {
458		    /*
459		     * Set the selection number to the current selection.
460		     */
461		    *s_num = cur_s->serial;
462		    /*
463		     * If the current selection was activated then
464		     * we have a valid selection otherwise we have
465		     * an inactive selection.
466		     */
467		    if (cur_s->activated) {
468			*data = cur_s->data;
469			ret_val = XM_SUCCESS;
470		    }
471		    else {
472			ret_val = XM_IA_SELECT;
473		    }
474		}
475		else {
476		    /*
477		     * No selection was current.
478		     */
479		    ret_val = XM_NO_SELECT;
480		}
481		selection = True;
482		break;
483        case KeyPress:
484        case KeyRelease:
485                keysym = XLookupKeysym (&event.xkey, 0);
486
487                /* Pop down on C-g and Escape.  */
488                if ((keysym == XK_g && (event.xkey.state & ControlMask) != 0)
489                    || keysym == XK_Escape) /* Any escape, ignore modifiers.  */
490                  {
491                    ret_val = XM_NO_SELECT;
492                    selection = True;
493                  }
494               break;
495	    default:
496		/*
497		 * If AEQ mode is enabled then queue the event.
498		 */
499		if (menu->aeq) {
500		    feq_tmp = (XMEventQue *)malloc(sizeof(XMEventQue));
501		    if (feq_tmp == NULL) {
502			_XMErrorCode = XME_CALLOC;
503			return(XM_FAILURE);
504		    }
505		    feq_tmp->event = event;
506		    feq_tmp->next = feq;
507		    feq = feq_tmp;
508		}
509		else if (_XMEventHandler) (*_XMEventHandler)(&event);
510	}
511	/*
512	 * If a selection has been made, break out of the event loop.
513	 */
514	if (selection == True) break;
515    }
516
517    /*
518     * Unmap the menu.
519     */
520    for ( p_ptr = menu->p_list->next;
521	 p_ptr != menu->p_list;
522	 p_ptr = p_ptr->next)
523      {
524	  XUnmapWindow(display, p_ptr->window);
525      }
526
527    /*
528     * Ungrab the mouse.
529     */
530    XUngrabPointer(display, CurrentTime);
531    XUngrabKeyboard(display, CurrentTime);
532
533    /*
534     * Restore bits under where the menu was if we managed
535     * to save them and free the pixmap.
536     */
537
538    /*
539     * If there is a current selection deactivate it.
540     */
541    if (cur_s != NULL) cur_s->activated = 0;
542
543    /*
544     * Deactivate the current pane.
545     */
546    cur_p->activated = 0;
547    XSetWindowBackgroundPixmap(display, cur_p->window, menu->inact_pixmap);
548
549    /*
550     * Synchronize the X buffers and the X event queue.
551     */
552    XSync(display, 0);
553
554    /*
555     * Dispatch any events remaining on the queue.
556     */
557    while (QLength(display)) {
558	/*
559	 * Fetch the next event.
560	 */
561	XNextEvent(display, &event);
562
563	/*
564	 * Discard any events left on the queue that belong to XMenu.
565	 * All others are held and then returned to the event queue.
566	 */
567	switch (event.type) {
568	    case Expose:
569	    case EnterNotify:
570	    case LeaveNotify:
571	    case ButtonPress:
572	    case ButtonRelease:
573		/*
574		 * Does this event belong to one of XMenu's windows?
575		 * If so, discard it and process the next event.
576		 * If not fall through and treat it as a foreign event.
577		 */
578		event_xmp = (XMPane *)XLookUpAssoc(
579						   display,
580						   menu->assoc_tab,
581						   event.xbutton.window
582						   );
583		if (event_xmp != NULL) continue;
584	    default:
585		/*
586		 * This is a foreign event.
587		 * Queue it for later return to the X event queue.
588		 */
589		feq_tmp = (XMEventQue *)malloc(sizeof(XMEventQue));
590		if (feq_tmp == NULL) {
591		    _XMErrorCode = XME_CALLOC;
592		    return(XM_FAILURE);
593		}
594		feq_tmp->event = event;
595		feq_tmp->next = feq;
596		feq = feq_tmp;
597	    }
598    }
599    /*
600     * Return any foreign events that were queued to the X event queue.
601     */
602    while (feq != NULL) {
603	feq_tmp = feq;
604	XPutBackEvent(display, &feq_tmp->event);
605	feq = feq_tmp->next;
606	free((char *)feq_tmp);
607    }
608
609    wait_func = 0;
610
611    /*
612     * Return successfully.
613     */
614    _XMErrorCode = XME_NO_ERROR;
615    return(ret_val);
616
617}
618
619/* arch-tag: 6b90b578-ecea-4328-b460-a0c96963f872
620   (do not change this comment) */
621