1 /* menu.c - proper 3D menus
2 Copyright (C) 1996-2017 Paul Sheer
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17 02111-1307, USA.
18 */
19
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <my_string.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26
27 #include <X11/Intrinsic.h>
28 #include "lkeysym.h"
29
30 #include "stringtools.h"
31 #include "app_glob.c"
32 #include "coolwidget.h"
33 #include "coollocal.h"
34
35 #include "mad.h"
36
37 extern struct look *look;
38
39 CWidget *current_pulled_button = 0;
40
41 int menu_grabbed = 0;
42
43 int eh_menu (CWidget * w, XEvent * xevent, CEvent * cwevent);
44 void CMenuSelectionDialog (CWidget * button);
45
46 static void render_menu_button (CWidget * wdt);
47
destroy_menu(CWidget * w)48 void destroy_menu (CWidget * w)
49 {
50 int i;
51 if (!w)
52 return;
53 if (!w->menu)
54 return;
55 for (i = 0; i < w->numlines; i++)
56 if (w->menu[i].text)
57 free (w->menu[i].text);
58 free (w->menu);
59 }
60
61 /*
62 Gets y1 and y2 of the entry of a menu (i.e. a menu item). x1, y1,
63 x2, y2 would describe the rectangle that encloses that entry,
64 relative to the top left corner of the pulled-down menu window.
65 */
get_menu_item_extents(int n,int j,struct menu_item m[],int * border,int * relief,int * y1,int * y2)66 void get_menu_item_extents (int n, int j, struct menu_item m[], int *border, int *relief, int *y1, int *y2)
67 {
68 (*look->get_menu_item_extents) (n, j, m, border, relief, y1, y2);
69 }
70
71 /*
72 Returns 0-(n-1) where n is the number of entries in that menu. Returns
73 which menu item the pointer is in if (x,y) is the pointer pos. Returns
74 -1 if out of bounds or between menu items.
75 */
whereis_pointer(int x,int y,int w,int n,struct menu_item m[])76 int whereis_pointer (int x, int y, int w, int n, struct menu_item m[])
77 {
78 int i, y1, y2, border, relief;
79 for (i = 0; i < n; i++) {
80 if (!m[i].text[2])
81 continue;
82 get_menu_item_extents (n, i, m, &border, &relief, &y1, &y2);
83 if (y >= y1) {
84 if (y < y2 && x >= border && x < w - border)
85 return i;
86 } else
87 break;
88 }
89 return -1;
90 }
91
find_menu_hotkey(struct menu_item m[],int this,int num)92 int find_menu_hotkey (struct menu_item m[], int this, int num)
93 {
94 unsigned char used_keys[256];
95 int n = 0, j;
96 if (!num)
97 return 0;
98 for (j = 0; j < num; j++)
99 if (m[j].hot_key && j != this)
100 used_keys[n++] = my_lower_case (m[j].hot_key);
101 return find_letter_at_word_start ((unsigned char *) m[this].text + 1, used_keys, n);
102 }
103
menu_draw(Window win,int w,int h,struct menu_item m[],int n,int light)104 void menu_draw (Window win, int w, int h, struct menu_item m[], int n, int light)
105 {
106 (*look->menu_draw) (win, w, h, m, n, light);
107 }
108
render_menu(CWidget * w)109 void render_menu (CWidget * w)
110 {
111 int n, border, relief, y1, y2, i;
112 unsigned new_width, new_height;
113 if (!w)
114 return;
115 n = w->numlines;
116 get_menu_item_extents (n, n - 1, w->menu, &border, &relief, &y1, &y2);
117 new_height = y2 + border;
118 new_width = 0;
119 for (i = 0; i < n; i++) {
120 int t;
121 t = CImageStringWidth (w->menu[i].text);
122 t += CImageStringWidth ("W");
123 if (new_width < t)
124 new_width = t;
125 }
126 new_width += (border + relief) * 2;
127 if (w->width != new_width || w->height != new_height) {
128 w->width = new_width;
129 w->height = new_height;
130 XResizeWindow (CDisplay, w->winid, w->width, w->height);
131 }
132 #if 0
133 if (w->y + w->height > HeightOfScreen (DefaultScreenOfDisplay (CDisplay))) {
134 int y;
135 y = HeightOfScreen (DefaultScreenOfDisplay (CDisplay)) - w->height;
136 if (y < 0)
137 y = 0;
138 XMoveWindow (CDisplay, w->winid, w->x, y);
139 }
140 #endif
141 #define BOUND_LIMITS 50
142 get_menu_item_extents (n, w->current, w->menu, &border, &relief, &y1, &y2);
143 if (w->current >= 0) {
144 if (y2 + w->y + BOUND_LIMITS >= HeightOfScreen (DefaultScreenOfDisplay (CDisplay)))
145 CSetWidgetPosition (w->ident, w->x, HeightOfScreen (DefaultScreenOfDisplay (CDisplay)) - y2 - BOUND_LIMITS);
146 if (y1 + w->y < BOUND_LIMITS)
147 CSetWidgetPosition (w->ident, w->x, BOUND_LIMITS - y1);
148 }
149 w->droppedmenu->current = w->current;
150 menu_draw (w->winid, w->width, w->height, w->menu, w->numlines, w->current);
151 }
152
153 /* gets a windows position relative to the origin if some ancestor windows,
154 window can be of a widget or not */
CGetWindowPosition(Window win,Window ancestor,int * x_return,int * y_return)155 void CGetWindowPosition (Window win, Window ancestor, int *x_return, int *y_return)
156 {
157 CWidget *w = (CWidget *) 1;
158 int x = 0, y = 0;
159 Window root, parent, *children;
160 unsigned int nchildren, width, height, bd, depth;
161 *x_return = *y_return = 0;
162 if (win == ancestor)
163 return;
164 for (;;) {
165 if (w)
166 w = CWidgetOfWindow (win);
167 if (w)
168 if (w->parentid == CRoot)
169 w = 0;
170 if (w) {
171 parent = w->parentid;
172 x = w->x;
173 y = w->y;
174 } else {
175 if (!XQueryTree (CDisplay, win, &root, &parent, &children, &nchildren))
176 return;
177 if (children)
178 XFree ((char *) children);
179 XGetGeometry (CDisplay, win, &root, &x, &y, &width, &height, &bd, &depth);
180 }
181 *x_return += x;
182 *y_return += y;
183 if (parent == ancestor || parent == CRoot)
184 break;
185 win = parent;
186 }
187 }
188
189 void focus_stack_remove_window (Window w);
190
pull_up(CWidget * button)191 void pull_up (CWidget * button)
192 {
193 if (!button)
194 return;
195 if (button->kind != C_MENU_BUTTON_WIDGET)
196 return;
197 if (button->droppedmenu) {
198 current_pulled_button = 0;
199 CDestroyWidget (button->droppedmenu->ident);
200 button->droppedmenu = 0;
201 }
202 focus_stack_remove_window (button->winid);
203 render_menu_button (button);
204 }
205
206 static CWidget *last_menu = 0;
207
CSetLastMenu(CWidget * button)208 void CSetLastMenu (CWidget *button)
209 {
210 last_menu = button;
211 }
212
CGetLastMenu(void)213 CWidget *CGetLastMenu (void)
214 {
215 return last_menu;
216 }
217
218 void menu_hand_cursor (Window win);
219
pull_down(CWidget * button)220 static CWidget *pull_down (CWidget * button) /* must create a new widget */
221 {
222 CWidget *menu;
223 CWidget *sib;
224 int width, height, n;
225 int x, y;
226
227 if(button->droppedmenu)
228 return 0;
229
230 sib = CGetLastMenu ();
231 if (sib)
232 if (strcmp (button->ident, sib->ident))
233 pull_up (sib); /* pull up last menu if different */
234
235 sib = button;
236 while((sib = CNextFocus (sib)) != button) /* pull up any other sibling menus */
237 pull_up (sib);
238
239 CSetLastMenu (button);
240
241 n = button->numlines;
242
243 CGetWindowPosition (button->winid, CRoot, &x, &y);
244
245 height = 2; /* don't know what these are yet */
246 width = 2;
247
248 x += button->firstcolumn;
249
250 menu = CSetupWidget (catstrs (button->ident, ".pull", NULL), CRoot, x,
251 y + button->height, width, height, C_MENU_WIDGET, INPUT_KEY, COLOR_FLAT, 0);
252 menu->options |= (button->options & MENU_AUTO_PULL_UP);
253
254 menu_hand_cursor (menu->winid);
255
256 /* no destroy 'cos gets ->menu gets destroyed by other menu-button-widget */
257 menu->numlines = n;
258 menu->menu = button->menu;
259 menu->eh = eh_menu;
260 menu->droppedmenu = button;
261 button->droppedmenu = menu;
262 current_pulled_button = button;
263 render_menu_button (button);
264 return menu;
265 }
266
CPullDown(CWidget * button)267 void CPullDown (CWidget * button)
268 {
269 pull_down (button);
270 }
271
get_pulled_menu(void)272 CWidget *get_pulled_menu (void)
273 {
274 return current_pulled_button;
275 }
276
CPullUp(CWidget * button)277 void CPullUp (CWidget * button)
278 {
279 pull_up (button);
280 }
281
execute_item(CWidget * menu,int item)282 int execute_item (CWidget * menu, int item)
283 {
284 int r = 0;
285 char ident[33];
286 strcpy (ident, menu->ident);
287 menu->droppedmenu->current = item;
288 XUngrabPointer (CDisplay, CurrentTime);
289 XUnmapWindow (CDisplay, menu->winid);
290 if (item >= 0 && item < menu->numlines)
291 if (menu->menu[item].call_back) {
292 menu->droppedmenu->current = item;
293 (*(menu->menu[item].call_back)) (menu->menu[item].data);
294 r = 1;
295 }
296 menu = CIdent (ident); /* menu may not exists anymore */
297 if (menu)
298 pull_up (menu->droppedmenu);
299 CFocusLast ();
300 return r;
301 }
302
render_menu_button(CWidget * wdt)303 static void render_menu_button (CWidget * wdt)
304 {
305 (*look->render_menu_button) (wdt);
306 }
307
308
309 int is_focus_change_key (KeySym k, int command);
310 int is_focus_prev_key (KeySym k, int command, unsigned int state);
311
eh_menubutton(CWidget * w,XEvent * xevent,CEvent * cwevent)312 int eh_menubutton (CWidget * w, XEvent * xevent, CEvent * cwevent)
313 {
314 int c;
315 CWidget *f;
316 switch (xevent->type) {
317 case FocusOut:
318 case FocusIn:
319 render_menu_button (w);
320 CExposeWindowArea (w->parentid, 0, w->x - WIDGET_FOCUS_RING, w->y - WIDGET_FOCUS_RING, w->width + WIDGET_FOCUS_RING * 2, w->height + WIDGET_FOCUS_RING * 2);
321 break;
322 case KeyPress:
323 c = CKeySym (xevent);
324 if (!w->droppedmenu) {
325 if (c == XK_space || c == XK_Return || c == XK_KP_Enter || cwevent->command == CK_Down) {
326 CMenuSelectionDialog (w);
327 return 1;
328 }
329 }
330 if (c == XK_Escape) {
331 pull_up (w);
332 CFocusLast ();
333 return 1;
334 }
335 if (cwevent->command == CK_Up && w->droppedmenu) {
336 if (w->droppedmenu->numlines < 1)
337 return 1;
338 if (w->droppedmenu->current == -1)
339 w->droppedmenu->current = 0;
340 do {
341 w->droppedmenu->current = (w->droppedmenu->current + w->droppedmenu->numlines - 1) % w->droppedmenu->numlines;
342 } while (w->droppedmenu->menu[w->droppedmenu->current].text[2] == 0);
343 render_menu (w->droppedmenu);
344 return 1;
345 }
346 if (cwevent->command == CK_Down && w->droppedmenu) {
347 if (w->droppedmenu->numlines < 1)
348 return 1;
349 do {
350 w->droppedmenu->current = (w->droppedmenu->current + 1) % w->droppedmenu->numlines;
351 } while (w->droppedmenu->menu[w->droppedmenu->current].text[2] == 0);
352 render_menu (w->droppedmenu);
353 return 1;
354 }
355 if (is_focus_prev_key (c, cwevent->command, xevent->xkey.state)) {
356 f = CPreviousFocus (w);
357 while (f->kind != C_MENU_BUTTON_WIDGET && (unsigned long) f != (unsigned long) w)
358 f = CPreviousFocus (f);
359 if (f) {
360 CFocus (f);
361 if (w->droppedmenu)
362 CMenuSelectionDialog (f);
363 }
364 return 1;
365 }
366 if (is_focus_change_key (c, cwevent->command)) {
367 f = CNextFocus (w);
368 while (f->kind != C_MENU_BUTTON_WIDGET && (unsigned long) f != (unsigned long) w)
369 f = CNextFocus (f);
370 if (f) {
371 CFocus (f);
372 if (w->droppedmenu)
373 CMenuSelectionDialog (f);
374 }
375 return 1;
376 }
377 if (w->droppedmenu && c) {
378 int i;
379 if (c == XK_Return || c == XK_KP_Enter || c == XK_space) {
380 return execute_item (w->droppedmenu, w->droppedmenu->current);
381 } else {
382 for (i = 0; i < w->droppedmenu->numlines; i++)
383 if (match_hotkey (c, w->droppedmenu->menu[i].hot_key))
384 return execute_item (w->droppedmenu, i);
385 }
386 }
387 if (cwevent->command != CK_Up && cwevent->command != CK_Down)
388 return 0;
389 case ButtonPress:
390 if (xevent->type == ButtonPress) {
391 w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
392 w->options |= BUTTON_PRESSED;
393 }
394 render_menu_button (w);
395 if (!w->droppedmenu)
396 CMenuSelectionDialog (w);
397 return 1;
398 break;
399 case ButtonRelease:
400 w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
401 w->options |= BUTTON_HIGHLIGHT;
402 render_menu_button (w);
403 return 1;
404 case MotionNotify:
405 if (!w->droppedmenu && menu_grabbed) {
406 pull_down (w);
407 CFocus (w);
408 }
409 return 1;
410 case EnterNotify:
411 w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
412 w->options |= BUTTON_HIGHLIGHT;
413 render_menu_button (w);
414 break;
415 case Expose:
416 if (xevent->xexpose.count)
417 break;
418 case LeaveNotify:
419 w->options &= 0xFFFFFFFFUL - BUTTON_PRESSED - BUTTON_HIGHLIGHT;
420 render_menu_button (w);
421 break;
422 }
423 return 0;
424 }
425
426
427
eh_menu(CWidget * w,XEvent * xevent,CEvent * cwevent)428 int eh_menu (CWidget * w, XEvent * xevent, CEvent * cwevent)
429 {
430 static Window win = 0;
431 static int current = -30000;
432 switch (xevent->type) {
433 case MotionNotify:
434 w->current = whereis_pointer (xevent->xmotion.x, xevent->xmotion.y, w->width, w->numlines, w->menu);
435 if (w->current == current && w->winid == win)
436 break;
437 current = w->current;
438 win = w->winid;
439 render_menu (w);
440 break;
441 case ButtonRelease:
442 return execute_item (w, whereis_pointer (xevent->xmotion.x, xevent->xmotion.y, w->width, w->numlines, w->menu));
443 case ButtonPress:
444 w->current = whereis_pointer (xevent->xmotion.x, xevent->xmotion.y, w->width, w->numlines, w->menu);
445 render_menu (w);
446 break;
447 case Expose:
448 if (xevent->xexpose.count)
449 break;
450 case LeaveNotify:
451 current = w->current = w->droppedmenu->current;
452 render_menu (w);
453 break;
454 }
455 return 0;
456 }
457
CDrawMenuButton(const char * ident,Window parent,Window focus_return,int x,int y,int width,int height,int num_items,const char * label,...)458 CWidget *CDrawMenuButton (const char *ident, Window parent, Window focus_return,
459 int x, int y, int width, int height, int num_items, const char *label,...)
460 {
461 va_list ap;
462 CWidget *wdt;
463 struct menu_item *m;
464 int i;
465 int w, h;
466
467 if (width == AUTO_WIDTH || height == AUTO_HEIGHT)
468 CTextSize (&w, &h, label);
469 if (width == AUTO_WIDTH)
470 width = w + 4 + BUTTON_RELIEF * 2;
471 if (height == AUTO_HEIGHT)
472 height = h + 4 + BUTTON_RELIEF * 2;
473
474 wdt = CSetupWidget (ident, parent, x, y,
475 width, height, C_MENU_BUTTON_WIDGET, INPUT_KEY | OwnerGrabButtonMask, COLOR_FLAT, 1);
476 wdt->options |= MENU_AUTO_PULL_UP; /* default */
477
478 set_hint_pos (x + width, y + height + WIDGET_SPACING);
479 wdt->label = (char *) strdup (label);
480 wdt->hotkey = find_hotkey (wdt);
481 wdt->options |= WIDGET_HOTKEY_ACTIVATES;
482
483 m = CMalloc ((num_items ? num_items : 1) * sizeof (struct menu_item));
484
485 va_start (ap, label);
486 for (i = 0; i < num_items; i++) {
487 char *text;
488 text = va_arg (ap, char *);
489 text = text ? text : "";
490 m[i].text = (char *) strdup (catstrs (" ", text, " ", NULL));
491 m[i].hot_key = va_arg (ap, int);
492 m[i].call_back = va_arg (ap, callfn);
493 m[i].data = va_arg (ap, unsigned long);
494 }
495 va_end (ap);
496
497 wdt->destroy = destroy_menu;
498 wdt->numlines = num_items;
499 wdt->menu = m;
500 wdt->eh = eh_menubutton;
501
502 return wdt;
503 }
504
insert_menu_item(CWidget * w,int i,const char * text,int hot_key,callfn call_back,unsigned long data)505 void insert_menu_item (CWidget * w, int i, const char *text, int hot_key, callfn call_back, unsigned long data)
506 {
507 struct menu_item *m;
508
509 m = CMalloc ((w->numlines + 1) * sizeof (struct menu_item));
510 memcpy (m, w->menu, i * sizeof (struct menu_item));
511 memcpy (m + i + 1, w->menu + i, (w->numlines - i) * sizeof (struct menu_item));
512 free (w->menu);
513 w->menu = m;
514 m[i].text = (char *) strdup (catstrs (" ", text, " ", NULL));
515 m[i].hot_key = hot_key;
516 m[i].call_back = call_back;
517 m[i].data = data;
518
519 w->numlines++;
520
521 if (w->droppedmenu != 0) {
522 w->droppedmenu->menu = m;
523 w->droppedmenu->numlines = w->numlines;
524 w->droppedmenu->current = w->current;
525 render_menu (w->droppedmenu);
526 }
527 }
528
CAddMenuItem(const char * ident,const char * text,int hot_key,callfn call_back,unsigned long data)529 void CAddMenuItem (const char *ident, const char *text, int hot_key, callfn call_back, unsigned long data)
530 {
531 CWidget *w;
532 w = CIdent (ident);
533 if (!w) {
534 CErrorDialog (0, 0, 0, _ (" Add Menu Item "), " %s: %s ", _ ("No such menu"), ident);
535 return;
536 }
537 insert_menu_item (w, w->numlines, text, hot_key, call_back, data);
538 }
539
CInsertMenuItem(const char * ident,const char * after,const char * text,int hot_key,callfn call_back,unsigned long data)540 void CInsertMenuItem (const char *ident, const char *after, const char *text, int hot_key, callfn call_back, unsigned long data)
541 {
542 int i;
543 CWidget *w;
544 w = CIdent (ident);
545 if (!w) {
546 CErrorDialog (0, 0, 0, _ (" Insert Menu Item "), " %s: %s ", _ ("No such menu"), ident);
547 return;
548 }
549 i = CHasMenuItem (ident, after);
550 if (i < 0) {
551 CErrorDialog (0, 0, 0, _ (" Insert Menu Item "), " %s: %s ", _ ("No such item"), after);
552 return;
553 }
554 insert_menu_item (w, i, text, hot_key, call_back, data);
555 }
556
CInsertMenuItemAfter(const char * ident,const char * after,const char * text,int hot_key,callfn call_back,unsigned long data)557 void CInsertMenuItemAfter (const char *ident, const char *after, const char *text, int hot_key, callfn call_back, unsigned long data)
558 {
559 int i;
560 CWidget *w;
561 w = CIdent (ident);
562 if (!w) {
563 CErrorDialog (0, 0, 0, _ (" Insert Menu Item "), " %s: %s ", _ ("No such menu"), ident);
564 return;
565 }
566 i = CHasMenuItem (ident, after);
567 if (i < 0) {
568 CErrorDialog (0, 0, 0, _ (" Insert Menu Item "), " %s: %s ", _ ("No such item"), after);
569 return;
570 }
571 insert_menu_item (w, i + 1, text, hot_key, call_back, data);
572 }
573
remove_item(CWidget * w,int i)574 static void remove_item (CWidget * w, int i)
575 {
576 if (!w)
577 return;
578 if (i >= w->numlines || i < 0)
579 return;
580 if (w->menu[i].text)
581 free (w->menu[i].text);
582 w->numlines--;
583 memmove (&w->menu[i], &w->menu[i + 1], (w->numlines - i) * sizeof (struct menu_item));
584 if (w->current == i)
585 w->current = -1;
586 else if (w->current > i)
587 w->current--;
588 if (w->droppedmenu != 0) {
589 w->droppedmenu->numlines = w->numlines;
590 w->droppedmenu->current = w->current;
591 }
592 }
593
CRemoveMenuItemNumber(const char * ident,int i)594 void CRemoveMenuItemNumber (const char *ident, int i)
595 {
596 CWidget *w;
597 w = CIdent (ident);
598 remove_item (w, i);
599 }
600
601 /*
602 Starts from the bottom of the menu and searches for the first
603 menu item containing text (strstr != NULL), and returns the
604 integer.
605 */
CHasMenuItem(const char * ident,const char * text)606 int CHasMenuItem (const char *ident, const char *text)
607 {
608 CWidget *w;
609 int i;
610 w = CIdent (ident);
611 if (!w)
612 return -1;
613 if (w->numlines)
614 for (i = w->numlines - 1; i >= 0; i--)
615 if (strstr (w->menu[i].text, text) || !*text)
616 return i;
617 return -1;
618 }
619
620 /*
621 Starts from the bottom of the menu and searches for the first
622 menu item containing text (strstr != NULL), and deletes it.
623 */
CRemoveMenuItem(const char * ident,const char * text)624 void CRemoveMenuItem (const char *ident, const char *text)
625 {
626 remove_item (CIdent (ident), CHasMenuItem (ident, text));
627 }
628
CReplaceMenuItem(const char * ident,const char * old_text,const char * new_text,int hot_key,callfn call_back,unsigned long data)629 void CReplaceMenuItem (const char *ident, const char *old_text, const char *new_text, int hot_key, callfn call_back, unsigned long data)
630 {
631 struct menu_item *m;
632 CWidget *w;
633 int i;
634
635 w = CIdent (ident);
636 if (!w) {
637 CErrorDialog (0, 0, 0, _ (" Replace Menu Item "), " %s: %s ", _ ("No such menu"), ident);
638 return;
639 }
640 i = CHasMenuItem (ident, old_text);
641 if (i < 0) {
642 CErrorDialog (0, 0, 0, _ (" Replace Menu Item "), " %s: %s ", _ ("No such item"), old_text);
643 return;
644 }
645 m = w->menu;
646 free (m[i].text);
647 m[i].text = (char *) strdup (catstrs (" ", new_text, " ", NULL));
648 m[i].hot_key = hot_key;
649 m[i].call_back = call_back;
650 m[i].data = data;
651
652 if (w->droppedmenu != 0)
653 render_menu (w->droppedmenu);
654 }
655
CMenuSelectionDialog(CWidget * button)656 void CMenuSelectionDialog (CWidget * button)
657 {
658 CEvent cwevent;
659 XEvent xevent;
660
661 /* sanity check */
662 if (!button)
663 return;
664
665 /* drop the menu and focus on the button */
666 pull_down (button);
667 CFocus (button);
668
669 /* we are already inside this function */
670 if (menu_grabbed)
671 return;
672 menu_grabbed = 1;
673
674 /* grab the pointer - we want events even when pointer is over other apps windows */
675 XGrabPointer
676 (CDisplay, button->winid, True,
677 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | PointerMotionMask |
678 EnterWindowMask | LeaveWindowMask | OwnerGrabButtonMask, GrabModeAsync,
679 GrabModeAsync, None, CGetCursorID (CURSOR_MENU), CurrentTime);
680
681 /* current_pulled_button could go to zero if some handler does a pull_up of the menu */
682 while (current_pulled_button) {
683 CNextEvent (&xevent, &cwevent);
684 /* terminate the loop if we have a button press/release as the user expects */
685 if (xevent.type == ButtonRelease || xevent.type == ButtonPress) {
686 CWidget *w;
687 w = CWidgetOfWindow (xevent.xbutton.window);
688 if (!w)
689 break;
690 if (w->kind != C_MENU_BUTTON_WIDGET && w->kind != C_MENU_WIDGET)
691 break;
692 if (xevent.xbutton.x >= w->width || xevent.xbutton.x < 0 ||
693 xevent.xbutton.y >= w->height || xevent.xbutton.y < 0)
694 break;
695 }
696 }
697
698 /* check if any menu up */
699 if (current_pulled_button) {
700 pull_up (current_pulled_button);
701 current_pulled_button = 0;
702 }
703
704 /* ungrab and return focus */
705 menu_grabbed = 0;
706 XUngrabPointer (CDisplay, CurrentTime);
707 CFocusLast ();
708 }
709
710
711