1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2005 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11 
12 #include "ui2_includes.h"
13 #include "ui2_typedefs.h"
14 #include "ui2_menu.h"
15 
16 #include "ui2_display.h"
17 #include "ui2_list.h"
18 #include "ui2_main.h"
19 #include "ui2_skin.h"
20 #include "ui2_util.h"
21 #include "ui2_widget.h"
22 
23 
24 #include <gdk/gdkkeysyms.h> /* for key values */
25 
26 
27 typedef struct _MenuItemData MenuItemData;
28 struct _MenuItemData
29 {
30 	gchar *text;
31 
32 	gchar *path;		/* path to this menu (/item, /submenu/item, etc.) */
33 	gint id;		/* numerical id assigned when menu was added */
34 
35 	gint is_toggle;
36 	gint toggle_active;
37 
38 	gint sensitive;
39 
40 	MenuItemData *parent;	/* parent of this menu, top is NULL */
41 	GList *children;	/* if non NULL, this is a submenu */
42 
43 	void (*select_func)(UIMenuData *md, const gchar *path, gint id, gpointer select_data);
44 	gpointer select_data;
45 
46 	UIData *ui;		/* when submenu showing, this points to the window */
47 	gint x;
48 	gint y;
49 	GdkPixbuf *backing;
50 	gint go_left;
51 
52 	UIMenuData *md;
53 };
54 
55 
56 static void ui_menu_submenu_activate(UIMenuData *md, MenuItemData *mi,
57 				     gint x, gint y, gint button, guint32 event_time);
58 
59 static UIData *ui_menu_submenu_new(MenuItemData *mi, const gchar *class, const gchar *key);
60 
61 static void ui_menu_submenu_show(UIMenuData *md, MenuItemData *mi, gint x, gint y);
62 static void ui_menu_submenu_ungrab(MenuItemData *mi, guint32 event_time);
63 static void ui_menu_submenu_grab(MenuItemData *mi, guint32 event_time);
64 static void ui_menu_submenu_grab_focus(UIMenuData *md, MenuItemData *mi, gint button, guint32 event_time);
65 
66 
67 /*
68  *---------------------------------------------------------
69  * misc
70  *---------------------------------------------------------
71  */
72 
ui_menu_is_button_press(void)73 static gint ui_menu_is_button_press(void)
74 {
75 	GdkEvent *event;
76 	gint ret = 0;
77 
78 	event = gtk_get_current_event();
79 
80 	if (event && event->type == GDK_BUTTON_PRESS)
81 		{
82 		ret = event->button.button;
83 		}
84 	if (event) gdk_event_free(event);
85 
86 	return ret;
87 }
88 
ui_menu_event_time(void)89 static guint32 ui_menu_event_time(void)
90 {
91 	GdkEvent *event;
92 	guint32 ret = 0;
93 
94 	event = gtk_get_current_event();
95 	if (!event) return 0;
96 
97 	if (event->type == GDK_BUTTON_PRESS)
98 		{
99 		ret = event->button.time;
100 		}
101 	else if (event->type == GDK_KEY_PRESS)
102 		{
103 		ret = event->key.time;
104 		}
105 
106 	gdk_event_free(event);
107 	return ret;
108 }
109 
ui_menu_hide_all(UIMenuData * md)110 static void ui_menu_hide_all(UIMenuData *md)
111 {
112 	GList *work;
113 
114 	work = g_list_last(md->submenu_list);
115 	while (work)
116 		{
117 		MenuItemData *mi = work->data;
118 		work = work->prev;
119 
120 		if (mi->ui && GTK_WIDGET_VISIBLE(mi->ui->window))
121 			{
122 			gtk_widget_hide(mi->ui->window);
123 			}
124 		}
125 }
126 
127 /*
128  *---------------------------------------------------------
129  * menu items, etc.
130  *---------------------------------------------------------
131  */
132 
ui_menu_item_free(MenuItemData * mi)133 static void ui_menu_item_free(MenuItemData *mi)
134 {
135 	if (!mi) return;
136 
137 	if (mi->children)
138 		{
139 		GList *work;
140 
141 		work = mi->children;
142 		while (work)
143 			{
144 			MenuItemData *tmp = work->data;
145 			work = work->next;
146 
147 			ui_menu_item_free(tmp);
148 			}
149 		g_list_free(mi->children);
150 		}
151 
152 	if (mi->ui) gtk_widget_destroy(mi->ui->window);
153 	if (mi->backing) gdk_pixbuf_unref(mi->backing);
154 
155 	g_free(mi->text);
156 	g_free(mi->path);
157 	g_free(mi);
158 }
159 
simple_base_length(const gchar * path)160 static gint simple_base_length(const gchar *path)
161 {
162 	gint p;
163 
164 	if (!path) return 0;
165 
166 	p = strlen(path) - 1;
167 	if (p < 0) return 0;
168 
169 	while (path[p] != '/' && p > 0) p--;
170 	if (p == 0 && path[p] == '/') p++;
171 	return p;
172 }
173 
ui_menu_find_parent(GList * list,const gchar * path)174 static MenuItemData *ui_menu_find_parent(GList *list, const gchar *path)
175 {
176 	GList *work;
177 
178 	if (!list || !path || path[0] != '/') return NULL;
179 
180 	work = list;
181 	while (work)
182 		{
183 		MenuItemData *mi;
184 		MenuItemData *ret;
185 		gint l;
186 
187 		mi = work->data;
188 		work = work->next;
189 
190 		l = simple_base_length(path);
191 		if (l == strlen(mi->path) && strncmp(mi->path, path, l) == 0)
192 			{
193 			return mi;
194 			}
195 		ret = ui_menu_find_parent(mi->children, path);
196 		if (ret) return ret;
197 		}
198 
199 	return NULL;
200 }
201 
ui_menu_item_add_real(UIMenuData * md,const gchar * text,const gchar * path,gint id,void (* select_func)(UIMenuData * md,const gchar * path,gint id,gpointer select_data),gpointer select_data)202 static MenuItemData *ui_menu_item_add_real(UIMenuData *md, const gchar *text, const gchar *path, gint id,
203 					   void (*select_func)(UIMenuData *md, const gchar *path, gint id, gpointer select_data),
204 					   gpointer select_data)
205 {
206 	MenuItemData *mi;
207 	MenuItemData *parent;
208 
209 	if (!md || !text) return NULL;
210 
211 	mi = g_new0(MenuItemData, 1);
212 
213 	mi->text = g_strdup(text);
214 	mi->path = g_strdup((path) ? path : "/");
215 	mi->id = id;
216 
217 	mi->select_func = select_func;
218 	mi->select_data = select_data;
219 
220 	mi->ui = NULL;
221 	mi->backing = NULL;
222 	mi->go_left = FALSE;
223 
224 	mi->sensitive = TRUE;
225 
226 	parent = ui_menu_find_parent(md->list, path);
227 	mi->parent = parent;
228 	mi->md = md;
229 
230 	if (parent)
231 		{
232 		if (debug_mode) printf("menu %s is submenu of %s\n", mi->text, parent->text);
233 		parent->children = g_list_append(parent->children, mi);
234 		}
235 	else
236 		{
237 		md->list = g_list_append(md->list, mi);
238 		}
239 
240 	return mi;
241 }
242 
ui_menu_item_add(UIMenuData * md,const gchar * text,const gchar * path,gint id,void (* select_func)(UIMenuData * md,const gchar * path,gint id,gpointer select_data),gpointer select_data)243 void ui_menu_item_add(UIMenuData *md, const gchar *text, const gchar *path, gint id,
244 		      void (*select_func)(UIMenuData *md, const gchar *path, gint id, gpointer select_data),
245 		      gpointer select_data)
246 {
247 	ui_menu_item_add_real(md, text, path, id, select_func, select_data);
248 }
249 
ui_menu_item_add_check(UIMenuData * md,const gchar * text,const gchar * path,gint id,gint active,void (* select_func)(UIMenuData * md,const gchar * path,gint id,gpointer select_data),gpointer select_data)250 void ui_menu_item_add_check(UIMenuData *md, const gchar *text, const gchar *path, gint id, gint active,
251 			    void (*select_func)(UIMenuData *md, const gchar *path, gint id, gpointer select_data),
252 			    gpointer select_data)
253 {
254 	MenuItemData *mi;
255 
256 	mi = ui_menu_item_add_real(md, text, path, id, select_func, select_data);
257 	if (!mi) return;
258 	mi->is_toggle = TRUE;
259 	mi->toggle_active = active;
260 }
261 
ui_menu_item_add_sensitive(UIMenuData * md,const gchar * text,const gchar * path,gint id,gint sensitive,void (* select_func)(UIMenuData * md,const gchar * path,gint id,gpointer select_data),gpointer select_data)262 void ui_menu_item_add_sensitive(UIMenuData *md, const gchar *text, const gchar *path, gint id, gint sensitive,
263 				void (*select_func)(UIMenuData *md, const gchar *path, gint id, gpointer select_data),
264 				gpointer select_data)
265 {
266 	MenuItemData *mi;
267 
268 	mi = ui_menu_item_add_real(md, text, path, id, select_func, select_data);
269 	if (mi) mi->sensitive = sensitive;
270 }
271 
272 /*
273  *---------------------------------------------------------
274  * menu computation
275  *---------------------------------------------------------
276  */
277 
ui_menu_calc_size(MenuItemData * mi)278 static void ui_menu_calc_size(MenuItemData *mi)
279 {
280 	UIData *ui;
281 	SkinData *skin;
282 	WidgetData *wd;
283 	gint w, h;
284 	gint ww, wh;
285 	gint px, py;
286 
287 	ui = mi->ui;
288 	skin = ui->skin;
289 
290 	w = skin->width_def;
291 	h = skin->height_def;
292 
293 	wd = skin_widget_get_by_key(skin, "menu", list_type_id());
294 	if (wd)
295 		{
296 		ListData *ld;
297 		GList *work;
298 		gint rows;
299 		gint string_width;
300 
301 		ld = wd->widget;
302 
303 		rows = MAX(0, g_list_length(mi->children) - 1);
304 		string_width = 32;
305 
306 		work = mi->children;
307 		while (work)
308 			{
309 			MenuItemData *mi;
310 			gint cw;
311 
312 			mi = work->data;
313 			work = work->next;
314 
315 			cw = font_string_length(ld->font, mi->text);
316 			if (cw > string_width) string_width = cw;
317 			}
318 
319 		h += ld->row_height * rows;
320 		w += string_width;
321 
322 		mi->y -= ld->y + ld->border_top;
323 		}
324 	else
325 		{
326 		printf("warning: ui menu \"%s\" contains no list widget\n", ui->key);
327 		}
328 
329 	ww = gdk_screen_width();
330 	wh = gdk_screen_height();
331 
332 	if (mi->parent)
333 		{
334 		MenuItemData *parent;
335 		ListData *ld;
336 
337 		parent = mi->parent;
338 
339 		wd = skin_widget_get_by_key(parent->ui->skin, "menu", list_type_id());
340 		if (wd)
341 			{
342 			ld = wd->widget;
343 			}
344 		else
345 			{
346 			ld = NULL;
347 			}
348 
349 		if (parent->go_left ||
350 		    mi->x >= ww - w ||
351 		    (ld && mi->x < parent->x + ld->x + (ld->width - ld->border_right)))
352 			{
353 			mi->go_left = TRUE;
354 			}
355 
356 		if (mi->go_left)
357 			{
358 			if (ld)
359 				{
360 				px = CLAMP(parent->x - w + ld->x + ld->border_left, 0, ww - w);
361 				}
362 			else
363 				{
364 				px = CLAMP(parent->x - w, 0, ww - w);
365 				}
366 			}
367 		else
368 			{
369 			px = CLAMP(mi->x, 0, ww - w);
370 			}
371 		}
372 	else
373 		{
374 		px = CLAMP(mi->x, 0, ww - w);
375 		}
376 
377 	py = CLAMP(mi->y, 0, wh - h);
378 
379 	if (px < mi->x) mi->go_left = TRUE;
380 
381 	mi->x = px;
382 	mi->y = py;
383 
384 	skin_resize(ui, w, h);
385 }
386 
387 /*
388  *---------------------------------------------------------
389  * mouse / keyboard callbacks
390  *---------------------------------------------------------
391  */
392 
ui_menu_proximity(MenuItemData * mi,gint x,gint y,MenuItemData * second)393 static gint ui_menu_proximity(MenuItemData *mi, gint x, gint y, MenuItemData *second)
394 {
395 	if (second)
396 		{
397 		if (ui_menu_proximity(mi, x, y, NULL)) return FALSE;
398 		return ui_menu_proximity(second, mi->x - second->x + x, mi->y - second->y + y, NULL);
399 		}
400 
401 	return (x >= 0 && x <= mi->ui->skin->width &&
402 		y >= 0 && y <= mi->ui->skin->height);
403 }
404 
405 #if 0
406 static gint ui_menu_press_cb(GtkWidget *w, GdkEventButton *event, gpointer data)
407 {
408 	MenuItemData *mi = data;
409 
410 	return FALSE;
411 }
412 #endif
413 
ui_menu_release_cb(GtkWidget * w,GdkEventButton * event,gpointer data)414 static gint ui_menu_release_cb(GtkWidget *w, GdkEventButton *event, gpointer data)
415 {
416 	MenuItemData *mi = data;
417 	GList *work;
418 
419 	if (!ui_menu_proximity(mi, event->x, event->y, NULL))
420 		{
421 		if (mi->parent && ui_menu_proximity(mi, event->x, event->y, mi->parent))
422 			{
423 			gtk_widget_hide(mi->ui->window);
424 			}
425 		else
426 			{
427 			ui_menu_hide_all(mi->md);
428 			}
429 		return TRUE;
430 		}
431 
432 	if (mi->md->press_button != 0 && mi->md->press_button == event->button)
433 		{
434 		mi->md->press_button = 0;
435 		if (!mi->md->submenu_list->next && !mi->md->selection_done)
436 			{
437 			gtk_grab_add(mi->ui->display);
438 			}
439 		return FALSE;
440 		}
441 
442 	work = g_list_last(mi->md->submenu_list);
443 	if (work->data != mi) return FALSE;
444 
445 	ui_menu_hide_all(mi->md);
446 	return TRUE;
447 }
448 
ui_menu_key_cb(GtkWidget * w,GdkEventKey * event,gpointer data)449 static gint ui_menu_key_cb(GtkWidget *w, GdkEventKey *event, gpointer data)
450 {
451 	MenuItemData *mi = data;
452 	gint ret = FALSE;
453 
454 	switch (event->keyval)
455 		{
456 		case GDK_Escape:
457 			ui_menu_hide_all(mi->md);
458 			ret = TRUE;
459 			break;
460 		default:
461 			break;
462 		}
463 
464 	return ret;
465 }
466 
467 /*
468  *---------------------------------------------------------
469  * the list callbacks
470  *---------------------------------------------------------
471  */
472 
ui_menu_list_size_cb(ListData * list,const gchar * key,gpointer data)473 static gint ui_menu_list_size_cb(ListData *list, const gchar *key, gpointer data)
474 {
475 	MenuItemData *mi = data;
476 
477 	list_set_menu_style(list, TRUE);
478 
479 	return g_list_length(mi->children);
480 }
481 
ui_menu_item_info_cb(ListData * list,const gchar * key,gint row,ListRowData * rd,gpointer data)482 static gint ui_menu_item_info_cb(ListData *list, const gchar *key,
483 				 gint row, ListRowData *rd, gpointer data)
484 {
485 	MenuItemData *mi = data;
486 	MenuItemData *item;
487 	gint flag;
488 
489 	item = g_list_nth_data(mi->children, row);
490 	if (!item) return FALSE;
491 
492 	list_row_column_set_text(list, rd, "text", item->text);
493 
494 	if (item->children)
495 		{
496 		flag = 1;
497 		}
498 	else if (item->is_toggle)
499 		{
500 		flag = (item->toggle_active) ? 3 : 2;
501 		}
502 	else
503 		{
504 		flag = 0;
505 		}
506 	list_row_set_flag(rd, flag);
507 	list_row_set_sensitive(rd, item->sensitive && mi->sensitive);
508 
509 	return TRUE;
510 }
511 
ui_menu_list_activate(MenuItemData * mi,ListData * ld,gint row)512 static gint ui_menu_list_activate(MenuItemData *mi, ListData *ld, gint row)
513 {
514 	gint x, y;
515 
516 	if (!mi->children) return FALSE;
517 
518 	x = ld->x + ld->width - ld->border_right;
519 	y = ld->y + ld->border_top + (ld->row_height * (row - ld->row_start));
520 
521 	x += mi->parent->x;
522 	y += mi->parent->y;
523 
524 	ui_menu_submenu_activate(mi->md, mi, x, y, 0, ui_menu_event_time());
525 
526 	return TRUE;
527 }
528 
ui_menu_list_click_cb(ListData * list,const gchar * key,gint row,ListRowData * rd,gint button,gpointer data)529 static void ui_menu_list_click_cb(ListData *list, const gchar *key, gint row,
530 				  ListRowData *rd, gint button, gpointer data)
531 {
532 	MenuItemData *mi = data;
533 	MenuItemData *item;
534 
535 	item = g_list_nth_data(mi->children, row);
536 	if (!item)
537 		{
538 		mi->md->selection_done = TRUE;
539 		return;
540 		}
541 
542 	if (ui_menu_list_activate(item, list, row)) return;
543 
544 	item->md->selection_done = TRUE;
545 	if (item->select_func)
546 		{
547 		item->select_func(item->md, item->path, item->id, item->select_data);
548 		}
549 
550 	ui_menu_hide_all(mi->md);
551 }
552 
ui_menu_list_select_cb(ListData * list,const gchar * key,gint row,ListRowData * rd,gpointer data)553 static void ui_menu_list_select_cb(ListData *list, const gchar *key, gint row,
554 				   ListRowData *rd, gpointer data)
555 {
556 	ui_menu_list_click_cb(list, key, row, rd, 0, data);
557 }
558 
ui_menu_list_move_cb(ListData * list,const gchar * key,gint row,gint activated,gint up,gpointer data)559 static gint ui_menu_list_move_cb(ListData *list, const gchar *key,
560 				 gint row, gint activated, gint up, gpointer data)
561 {
562 	MenuItemData *mi = data;
563 	MenuItemData *item;
564 
565 	item = g_list_nth_data(mi->children, row);
566 	if (!item)
567 		{
568 		mi->md->selection_done = TRUE;
569 		return FALSE;
570 		}
571 
572 	if (activated)
573 		{
574 		if (up && ui_menu_list_activate(item, list, row)) return TRUE;
575 		if (!up && mi->parent)
576 			{
577 			gtk_widget_hide(mi->ui->window);
578 			return TRUE;
579 			}
580 		}
581 
582 	return FALSE;
583 }
584 
ui_menu_back_cb(UIData * ui,GdkPixbuf * pixbuf,gpointer data)585 static gint ui_menu_back_cb(UIData *ui, GdkPixbuf *pixbuf, gpointer data)
586 {
587 	MenuItemData *mi = data;
588 
589 	if (!GTK_WIDGET_VISIBLE(mi->ui->display) || !GTK_WIDGET_REALIZED(mi->ui->display)) return FALSE;
590 
591 	util_pixbuf_fill_from_root_window(pixbuf, mi->x, mi->y, TRUE);
592 
593 	return TRUE;
594 }
595 
596 /*
597  *---------------------------------------------------------
598  * submenu handling
599  *---------------------------------------------------------
600  */
601 
ui_menu_submenu_hide_cb(GtkWidget * widget,gpointer data)602 static void ui_menu_submenu_hide_cb(GtkWidget *widget, gpointer data)
603 {
604 	MenuItemData *mi = data;
605 	UIMenuData *md;
606 
607 	md = mi->md;
608 
609 	if (md->preview == mi)
610 		{
611 		md->preview = NULL;
612 		return;
613 		}
614 
615 	gtk_grab_remove(mi->ui->display);
616 	md->submenu_list = g_list_remove(md->submenu_list, mi);
617 
618 	if (!mi->parent)
619 		{
620 		printf("warning: backed off tree at %s\n", mi->path);
621 		return;
622 		}
623 
624 	ui_menu_submenu_grab(mi->parent, ui_menu_event_time());
625 }
626 
ui_menu_submenu_delete_cb(GtkWidget * widget,GdkEventAny * event,gpointer data)627 static gint ui_menu_submenu_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
628 {
629 	gtk_widget_hide(widget);
630 	return TRUE;
631 }
632 
ui_menu_submenu_activate(UIMenuData * md,MenuItemData * mi,gint x,gint y,gint button,guint32 event_time)633 static void ui_menu_submenu_activate(UIMenuData *md, MenuItemData *mi,
634 				     gint x, gint y, gint button, guint32 event_time)
635 {
636 	if (!mi->ui)
637 		{
638 		const gchar *key = NULL;
639 
640 		ui_menu_submenu_new(mi, md->ui->class, mi->path);
641 		g_signal_connect(G_OBJECT(mi->ui->window), "delete_event",
642 				 G_CALLBACK(ui_menu_submenu_delete_cb), mi);
643 		g_signal_connect(G_OBJECT(mi->ui->window), "hide",
644 				 G_CALLBACK(ui_menu_submenu_hide_cb), mi);
645 
646 		if (mi->parent)
647 			{
648 			MenuItemData *parent;
649 			WidgetData *wd;
650 
651 			parent = mi->parent;
652 			wd = skin_widget_get_by_key(parent->ui->skin, "menu", list_type_id());
653 			if (wd)
654 				{
655 				key = ui_widget_get_data(wd, "data");
656 				}
657 			if (!key) key = parent->ui->skin_mode_key;
658 			}
659 
660 		if (!key) key = "skindata_menu";
661 
662 		if (!ui_skin_load(mi->ui, md->ui->skin_path, key))
663 			{
664 			/* try default */
665 			if (!ui_skin_load(mi->ui, md->ui->skin_path, "skindata_menu"))
666 				{
667 				/* fall back to something */
668 				ui_skin_load(mi->ui, NULL, "skindata_menu");
669 				}
670 			}
671 		}
672 
673 	md->submenu_list = g_list_append(md->submenu_list, mi);
674 
675 	ui_menu_submenu_ungrab(mi->parent, event_time);
676 
677 	ui_menu_submenu_show(md, mi, x, y);
678 	ui_menu_submenu_grab_focus(md, mi, button, event_time);
679 }
680 
681 /*
682  *---------------------------------------------------------
683  * setup, show, etc.
684  *---------------------------------------------------------
685  */
686 
ui_menu_destroy(GtkWidget * widget,gpointer data)687 static void ui_menu_destroy(GtkWidget *widget, gpointer data)
688 {
689 	UIMenuData *md = data;
690 	GList *work;
691 
692 	work = md->list;
693 	while (work)
694 		{
695 		MenuItemData *mi;
696 
697 		mi = work->data;
698 		work = work->next;
699 
700 		ui_menu_item_free(mi);
701 		}
702 	g_list_free(md->list);
703 
704 	g_free(md);
705 }
706 
ui_menu_hide_cb(GtkWidget * widget,gpointer data)707 static void ui_menu_hide_cb(GtkWidget *widget, gpointer data)
708 {
709 	UIData *ui = data;
710 
711 	gtk_grab_remove(ui->display);
712 	ui_close(ui);
713 }
714 
ui_menu_delete_cb(GtkWidget * widget,GdkEventAny * event,gpointer data)715 static gint ui_menu_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
716 {
717 	gtk_widget_hide(widget);
718 	return TRUE;
719 }
720 
ui_menu_submenu_new(MenuItemData * mi,const gchar * class,const gchar * key)721 static UIData *ui_menu_submenu_new(MenuItemData *mi, const gchar *class, const gchar *key)
722 {
723 	UIData *ui;
724 	GtkWidget *window;
725 
726 	window = gtk_window_new(GTK_WINDOW_POPUP);
727 
728 	ui = ui_new_into_container(class, key, window);
729 	ui->window = window;
730 	mi->ui = ui;
731 
732 	ui_focus_set(ui, TRUE);
733 
734 #if 0
735 	g_signal_connect(G_OBJECT(ui->display), "button_press_event",
736 			 G_CALLBACK(ui_menu_press_cb), mi);
737 #endif
738 	g_signal_connect(G_OBJECT(ui->display), "button_release_event",
739 			 G_CALLBACK(ui_menu_release_cb), mi);
740 	g_signal_connect(G_OBJECT(ui->display), "key_press_event",
741 			 G_CALLBACK(ui_menu_key_cb), mi);
742 
743 	/* set it up */
744 	list_register_key("menu", ui,
745 			  ui_menu_list_size_cb, mi,
746 			  ui_menu_item_info_cb, mi,
747 			  ui_menu_list_click_cb, mi,
748 			  ui_menu_list_select_cb, mi,
749 			  NULL, NULL);
750 	list_register_menu_funcs("menu", ui, ui_menu_list_move_cb, mi);
751 
752 	if (mi->md->ui)
753 		{
754 		/* sub windows inheret parent functions */
755 		ui->skin_func = mi->md->ui->skin_func;
756 		ui->skin_data = mi->md->ui->skin_data;
757 
758 		/* and the ui group */
759 		ui_group_set_child(mi->md->ui, ui);
760 		}
761 	ui_set_back_callback(ui, ui_menu_back_cb, mi);
762 
763 	return ui;
764 }
765 
ui_menu_new(const gchar * class,const gchar * key)766 UIMenuData *ui_menu_new(const gchar *class, const gchar *key)
767 {
768 	UIMenuData *md;
769 	MenuItemData *mi;
770 
771 	md = g_new0(UIMenuData, 1);
772 
773 	md->list = NULL;
774 	md->preview = NULL;
775 
776 	ui_menu_item_add(md, "/", "/", 0, NULL, NULL);
777 	mi = md->list->data;
778 	md->submenu_list = g_list_append(md->submenu_list, mi);
779 	md->ui = ui_menu_submenu_new(mi, class, key);
780 
781 	g_signal_connect(G_OBJECT(mi->ui->window), "destroy",
782 			 G_CALLBACK(ui_menu_destroy), md);
783 	g_signal_connect(G_OBJECT(mi->ui->window), "delete_event",
784 			 G_CALLBACK(ui_menu_delete_cb), md);
785 	g_signal_connect(G_OBJECT(mi->ui->window), "hide",
786 			 G_CALLBACK(ui_menu_hide_cb), mi->ui);
787 
788 	return md;
789 }
790 
ui_menu_submenu_show(UIMenuData * md,MenuItemData * mi,gint x,gint y)791 static void ui_menu_submenu_show(UIMenuData *md, MenuItemData *mi, gint x, gint y)
792 {
793 	if (!mi || !mi->ui->skin) return;
794 
795 	if (x < 0 || y < 0)
796 		{
797 		GdkModifierType modmask;
798 		gdk_window_get_pointer(NULL, &x, &y, &modmask);
799 		}
800 
801 	mi->x = x;
802 	mi->y = y;
803 	ui_menu_calc_size(mi);
804 
805 	gtk_window_move(GTK_WINDOW(mi->ui->window), mi->x, mi->y);
806 	gtk_widget_show(mi->ui->window);
807 
808 	mi->md->selection_done = FALSE;
809 }
810 
ui_menu_submenu_ungrab(MenuItemData * mi,guint32 event_time)811 static void ui_menu_submenu_ungrab(MenuItemData *mi, guint32 event_time)
812 {
813 	if (GTK_WIDGET_VISIBLE(mi->ui->window))
814 		{
815 		gdk_pointer_ungrab(event_time);
816 		gdk_keyboard_ungrab(event_time);
817 		}
818 	gtk_grab_remove(mi->ui->display);
819 }
820 
ui_menu_submenu_grab(MenuItemData * mi,guint32 event_time)821 static void ui_menu_submenu_grab(MenuItemData *mi, guint32 event_time)
822 {
823 	gdk_pointer_grab(mi->ui->display->window, FALSE,
824 			 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK,
825 			 NULL, NULL, event_time);
826 	gdk_keyboard_grab(mi->ui->display->window, TRUE, event_time);
827 	gtk_grab_add(mi->ui->display);
828 }
829 
ui_menu_submenu_grab_focus(UIMenuData * md,MenuItemData * mi,gint button,guint32 event_time)830 static void ui_menu_submenu_grab_focus(UIMenuData *md, MenuItemData *mi, gint button, guint32 event_time)
831 {
832 	WidgetData *wd;
833 
834 	/* we peek at current event if -1 */
835 	if (button < 0) button = ui_menu_is_button_press();
836 
837 	/* set focus to menu list widget,
838 	 * enable it for display,
839 	 * then redraw first row to show the focus
840 	 */
841 	wd = skin_widget_get_by_key(mi->ui->skin, "menu", list_type_id());
842 	if (wd)
843 		{
844 		mi->ui->focus_widget = wd;
845 		if (button > 0)
846 			{
847 			ListData *ld = wd->widget;
848 
849 			mi->ui->active_widget = wd;
850 
851 			/* force pressed state */
852 			ld->pressed = TRUE;
853 			ld->press_row = 0;
854 			}
855 		}
856 
857 	GTK_WIDGET_SET_FLAGS(mi->ui->display, GTK_HAS_FOCUS);
858 	list_row_update("menu", mi->ui, 0);
859 
860 	ui_menu_submenu_grab(mi, event_time);
861 
862 	mi->md->press_button = button;
863 }
864 
ui_menu_show(UIMenuData * md,gint x,gint y,gint button,guint32 event_time)865 void ui_menu_show(UIMenuData *md, gint x, gint y, gint button, guint32 event_time)
866 {
867 	ui_menu_submenu_show(md, md->list->data, x, y);
868 	ui_menu_submenu_grab_focus(md, md->list->data, -1, event_time);
869 }
870 
871