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