1 /*!
2 * \file src/hid/gtk/ghid-main-menu.c
3 *
4 * \brief Implementation of GHidMainMenu widget.
5 *
6 * This widget is the main pcb menu.
7 *
8 * <hr>
9 *
10 * <h1><b>Copyright.</b></h1>\n
11 *
12 * PCB, interactive printed circuit board design
13 *
14 * Copyright (C) 1994,1995,1996, 2004 Thomas Nau
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 *
30 * Contact addresses for paper mail and Email:
31 * Thomas Nau, Schlehenweg 15, 88471 Baustetten, Germany
32 * Thomas.Nau@rz.uni-ulm.de
33 */
34
35 #include <glib.h>
36 #include <glib-object.h>
37 #include <gtk/gtk.h>
38
39 #include "gtkhid.h"
40 #include "gui.h"
41 #include "pcb-printf.h"
42
43 #include "ghid-main-menu.h"
44 #include "ghid-layer-selector.h"
45 #include "ghid-route-style-selector.h"
46
47 void Message (const char *, ...);
48
49 static int action_counter;
50
51 struct _GHidMainMenu
52 {
53 GtkMenuBar parent;
54
55 GtkActionGroup *action_group;
56 GtkAccelGroup *accel_group;
57
58 gint layer_view_pos;
59 gint layer_pick_pos;
60 gint route_style_pos;
61
62 GtkMenuShell *layer_view_shell;
63 GtkMenuShell *layer_pick_shell;
64 GtkMenuShell *route_style_shell;
65
66 GList *actions;
67 GHashTable *popup_table;
68
69 gint n_layer_views;
70 gint n_layer_picks;
71 gint n_route_styles;
72
73 GCallback action_cb;
74 void (*special_key_cb) (const char *accel, GtkAction *action,
75 const Resource *node);
76 };
77
78 struct _GHidMainMenuClass
79 {
80 GtkMenuBarClass parent_class;
81 };
82
83 /* TODO: write finalize function */
84
85 /* SIGNAL HANDLERS */
86
87 /* RESOURCE HANDLER */
88
89 /*!
90 * \brief Translate gpcb-menu.res accelerators to gtk ones.
91 *
92 * Some keys need to be replaced by a name for the gtk accelerators to
93 * work. This table contains the translations. The "in" character is
94 * what would appear in gpcb-menu.res and the "out" string is what we
95 * have to feed to gtk. I was able to find these by using xev to find
96 * the keycode and then looked at gtk+-2.10.9/gdk/keynames.txt (from the
97 * gtk source distribution) to figure out the names that go with the
98 * codes.
99 */
100 static gchar *
translate_accelerator(const char * text)101 translate_accelerator (const char *text)
102 {
103 GString *ret_val = g_string_new ("");
104 static struct { const char *in, *out; } key_table[] =
105 {
106 {"Enter", "Return"},
107 {"Alt", "<alt>"},
108 {"Shift", "<shift>"},
109 {"Ctrl", "<ctrl>"},
110 {" ", ""},
111 {":", "colon"},
112 {"=", "equal"},
113 {"/", "slash"},
114 {"[", "bracketleft"},
115 {"]", "bracketright"},
116 {".", "period"},
117 {"|", "bar"},
118 {"+", "plus"},
119 {"-", "minus"},
120 {NULL, NULL}
121 };
122
123 enum {MOD, KEY} state = MOD;
124 while (*text != '\0')
125 {
126 static gboolean gave_msg;
127 gboolean found = FALSE;
128 int i;
129
130 if (state == MOD && strncmp (text, "<Key>", 5) == 0)
131 {
132 state = KEY;
133 text += 5;
134 }
135 for (i = 0; key_table[i].in != NULL; ++i)
136 {
137 int len = strlen (key_table[i].in);
138 if (strncmp (text, key_table[i].in, len) == 0)
139 {
140 found = TRUE;
141 g_string_append (ret_val, key_table[i].out);
142 text += len;
143 }
144 }
145 if (found == FALSE)
146 switch (state)
147 {
148 case MOD:
149 Message (_("Don't know how to parse \"%s\" as an "
150 "accelerator in the menu resource file.\n"),
151 text);
152 if (!gave_msg)
153 {
154 gave_msg = TRUE;
155 Message (_("Format is:\n"
156 "modifiers<Key>k\n"
157 "where \"modifiers\" is a space "
158 "separated list of key modifiers\n"
159 "and \"k\" is the name of the key.\n"
160 "Allowed modifiers are:\n"
161 " Ctrl\n"
162 " Shift\n"
163 " Alt\n"
164 "Please note that case is important.\n"));
165 }
166 break;
167 case KEY:
168 g_string_append_c (ret_val, *text);
169 ++text;
170 break;
171 }
172 }
173 return g_string_free (ret_val, FALSE);
174 }
175
176 static gboolean
g_str_case_equal(gconstpointer v1,gconstpointer v2)177 g_str_case_equal (gconstpointer v1, gconstpointer v2)
178 {
179 return strcasecmp (v1, v2);
180 }
181
182 /*!
183 * \brief Check that translated accelerators are unique; warn otherwise.
184 */
185 static const char *
check_unique_accel(const char * accelerator)186 check_unique_accel (const char *accelerator)
187 {
188 static GHashTable *accel_table;
189
190 if (!accelerator ||*accelerator)
191 return accelerator;
192
193 if (!accel_table)
194 accel_table = g_hash_table_new (g_str_hash, g_str_case_equal);
195
196 if (g_hash_table_lookup (accel_table, accelerator))
197 {
198 Message (_("Duplicate accelerator found: \"%s\"\n"
199 "The second occurrence will be dropped\n"),
200 accelerator);
201 return NULL;
202 }
203
204 g_hash_table_insert (accel_table,
205 (gpointer) accelerator, (gpointer) accelerator);
206
207 return accelerator;
208 }
209
210
211 /*!
212 * \brief Translate a resource tree into a menu structure.
213 *
214 * \param [in] menu The GHidMainMenu widget to be acted on.
215 * \param [in] shall The base menu shell (a menu bar or popup menu).
216 * \param [in] res The base of the resource tree.
217 * */
218 void
ghid_main_menu_real_add_resource(GHidMainMenu * menu,GtkMenuShell * shell,const Resource * res)219 ghid_main_menu_real_add_resource (GHidMainMenu *menu, GtkMenuShell *shell,
220 const Resource *res)
221 {
222 int i, j;
223 const Resource *tmp_res;
224 gchar mnemonic = 0;
225
226 for (i = 0; i < res->c; ++i)
227 {
228 const gchar *accel = NULL;
229 char *menu_label;
230 const char *res_val;
231 const Resource *sub_res = res->v[i].subres;
232 GtkAction *action = NULL;
233
234 switch (resource_type (res->v[i]))
235 {
236 case 101: /* name, subres: passthrough */
237 ghid_main_menu_real_add_resource (menu, shell, sub_res);
238 break;
239 case 1: /* no name, subres */
240 tmp_res = resource_subres (sub_res, "a"); /* accelerator */
241 res_val = resource_value (sub_res, "m"); /* mnemonic */
242 if (res_val)
243 mnemonic = res_val[0];
244 /* The accelerator resource will have two values, like
245 * a={"Ctrl-Q" "Ctrl<Key>q"}
246 * The first Gtk ignores. The second needs to be translated. */
247 if (tmp_res)
248 accel = check_unique_accel
249 (translate_accelerator (tmp_res->v[1].value));
250
251 /* Now look for the first unnamed value (not a subresource) to
252 * figure out the name of the menu or the menuitem. */
253 res_val = "button";
254 for (j = 0; j < sub_res->c; ++j)
255 if (resource_type (sub_res->v[j]) == 10)
256 {
257 res_val = _(sub_res->v[j].value);
258 break;
259 }
260 /* Hack '_' in based on mnemonic value */
261 if (!mnemonic)
262 menu_label = g_strdup (res_val);
263 else
264 {
265 char *post_ = strchr (res_val, mnemonic);
266 if (post_ == NULL)
267 menu_label = g_strdup (res_val);
268 else
269 {
270 GString *tmp = g_string_new ("");
271 g_string_append_len (tmp, res_val, post_ - res_val);
272 g_string_append_c (tmp, '_');
273 g_string_append (tmp, post_);
274 menu_label = g_string_free (tmp, FALSE);
275 }
276 }
277 /* If the subresource we're processing also has unnamed
278 * subresources, it's a submenu, not a regular menuitem. */
279 if (sub_res->flags & FLAG_S)
280 {
281 /* SUBMENU */
282 GtkWidget *submenu = gtk_menu_new ();
283 GtkWidget *item = gtk_menu_item_new_with_mnemonic (menu_label);
284 GtkWidget *tearoff = gtk_tearoff_menu_item_new ();
285
286 gtk_menu_shell_append (shell, item);
287 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
288
289 /* add tearoff to menu */
290 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), tearoff);
291 /* recurse on the newly-added submenu */
292 ghid_main_menu_real_add_resource (menu,
293 GTK_MENU_SHELL (submenu),
294 sub_res);
295 }
296 else
297 {
298 /* NON-SUBMENU: MENU ITEM */
299 const char *checked = resource_value (sub_res, "checked");
300 const char *label = resource_value (sub_res, "sensitive");
301 const char *tip = resource_value (sub_res, "tip");
302 if (checked)
303 {
304 /* TOGGLE ITEM */
305 gchar *name = g_strdup_printf ("MainMenuAction%d",
306 action_counter++);
307
308 action = GTK_ACTION (gtk_toggle_action_new (name, menu_label,
309 tip, NULL));
310 /* checked=foo is a binary flag (checkbox)
311 * checked=foo,bar is a flag compared to a value (radio) */
312 gtk_toggle_action_set_draw_as_radio
313 (GTK_TOGGLE_ACTION (action), !!strchr (checked, ','));
314 }
315 else if (label && strcmp (label, "false") == 0)
316 {
317 /* INSENSITIVE ITEM */
318 GtkWidget *item = gtk_menu_item_new_with_label (menu_label);
319 gtk_widget_set_sensitive (item, FALSE);
320 gtk_menu_shell_append (shell, item);
321 }
322 else
323 {
324 /* NORMAL ITEM */
325 gchar *name = g_strdup_printf ("MainMenuAction%d", action_counter++);
326 action = gtk_action_new (name, menu_label, tip, NULL);
327 }
328 }
329 /* Connect accelerator, if there is one */
330 if (action)
331 {
332 GtkWidget *item;
333 gtk_action_set_accel_group (action, menu->accel_group);
334 gtk_action_group_add_action_with_accel (menu->action_group,
335 action, accel);
336 gtk_action_connect_accelerator (action);
337 g_signal_connect (G_OBJECT (action), "activate", menu->action_cb,
338 (gpointer) sub_res);
339 g_object_set_data (G_OBJECT (action), "resource",
340 (gpointer) sub_res);
341 item = gtk_action_create_menu_item (action);
342 gtk_menu_shell_append (shell, item);
343 menu->actions = g_list_append (menu->actions, action);
344 menu->special_key_cb (accel, action, sub_res);
345 }
346 /* Scan rest of resource in case there is more work */
347 for (j = 0; j < sub_res->c; j++)
348 {
349 const char *res_name;
350 /* named value = X resource */
351 if (resource_type (sub_res->v[j]) == 110)
352 {
353 res_name = sub_res->v[j].name;
354
355 /* translate bg, fg to background, foreground */
356 if (strcmp (res_name, "fg") == 0) res_name = "foreground";
357 if (strcmp (res_name, "bg") == 0) res_name = "background";
358
359 /* ignore special named values (m, a, sensitive) */
360 if (strcmp (res_name, "m") == 0
361 || strcmp (res_name, "a") == 0
362 || strcmp (res_name, "sensitive") == 0
363 || strcmp (res_name, "tip") == 0)
364 break;
365
366 /* log checked and active special values */
367 if (action && strcmp (res_name, "checked") == 0)
368 g_object_set_data (G_OBJECT (action), "checked-flag",
369 sub_res->v[j].value);
370 else if (action && strcmp (res_name, "active") == 0)
371 g_object_set_data (G_OBJECT (action), "active-flag",
372 sub_res->v[j].value);
373 else
374 /* if we got this far it is supposed to be an X
375 * resource. For now ignore it and warn the user */
376 Message (_("The gtk gui currently ignores \"%s\""
377 "as part of a menuitem resource.\n"
378 "Feel free to provide patches\n"),
379 sub_res->v[j].value);
380 }
381 }
382 break;
383 case 10: /* no name, value */
384 /* If we get here, the resource is "-" or "@foo" for some foo */
385 if (res->v[i].value[0] == '@')
386 {
387 GList *children;
388 int pos;
389
390 children = gtk_container_get_children (GTK_CONTAINER (shell));
391 pos = g_list_length (children);
392 g_list_free (children);
393
394 if (strcmp (res->v[i].value, "@layerview") == 0)
395 {
396 menu->layer_view_shell = shell;
397 menu->layer_view_pos = pos;
398 }
399 else if (strcmp (res->v[i].value, "@layerpick") == 0)
400 {
401 menu->layer_pick_shell = shell;
402 menu->layer_pick_pos = pos;
403 }
404 else if (strcmp (res->v[i].value, "@routestyles") == 0)
405 {
406 menu->route_style_shell = shell;
407 menu->route_style_pos = pos;
408 }
409 else
410 Message (_("GTK GUI currently ignores \"%s\" in the menu\n"
411 "resource file.\n"), res->v[i].value);
412 }
413 else if (strcmp (res->v[i].value, "-") == 0)
414 {
415 GtkWidget *item = gtk_separator_menu_item_new ();
416 gtk_menu_shell_append (shell, item);
417 }
418 else if (i > 0)
419 {
420 /* This is an action-less menuitem. It is really only useful
421 * when you're starting to build a new menu and you're looking
422 * to get the layout right. */
423 GtkWidget *item
424 = gtk_menu_item_new_with_label (_(res->v[i].value));
425 gtk_menu_shell_append (shell, item);
426 }
427 break;
428 }
429 }
430 }
431
432 /* CONSTRUCTOR */
433 static void
ghid_main_menu_init(GHidMainMenu * mm)434 ghid_main_menu_init (GHidMainMenu *mm)
435 {
436 /* Hookup signal handlers */
437 }
438
439 static void
ghid_main_menu_class_init(GHidMainMenuClass * klass)440 ghid_main_menu_class_init (GHidMainMenuClass *klass)
441 {
442 }
443
444 /* PUBLIC FUNCTIONS */
445 GType
ghid_main_menu_get_type(void)446 ghid_main_menu_get_type (void)
447 {
448 static GType mm_type = 0;
449
450 if (!mm_type)
451 {
452 const GTypeInfo mm_info =
453 {
454 sizeof (GHidMainMenuClass),
455 NULL, /* base_init */
456 NULL, /* base_finalize */
457 (GClassInitFunc) ghid_main_menu_class_init,
458 NULL, /* class_finalize */
459 NULL, /* class_data */
460 sizeof (GHidMainMenu),
461 0, /* n_preallocs */
462 (GInstanceInitFunc) ghid_main_menu_init,
463 };
464
465 mm_type = g_type_register_static (GTK_TYPE_MENU_BAR,
466 "GHidMainMenu",
467 &mm_info, 0);
468 }
469
470 return mm_type;
471 }
472
473 /*!
474 * \brief Create a new GHidMainMenu.
475 *
476 * \return a freshly-allocated GHidMainMenu
477 */
478 GtkWidget *
ghid_main_menu_new(GCallback action_cb,void (* special_key_cb)(const char * accel,GtkAction * action,const Resource * node))479 ghid_main_menu_new (GCallback action_cb,
480 void (*special_key_cb) (const char *accel,
481 GtkAction *action,
482 const Resource *node))
483 {
484 GHidMainMenu *mm = g_object_new (GHID_MAIN_MENU_TYPE, NULL);
485
486 mm->accel_group = gtk_accel_group_new ();
487 mm->action_group = gtk_action_group_new ("MainMenu");
488
489 mm->layer_view_pos = 0;
490 mm->layer_pick_pos = 0;
491 mm->route_style_pos = 0;
492 mm->n_layer_views = 0;
493 mm->n_layer_picks = 0;
494 mm->n_route_styles = 0;
495 mm->layer_view_shell = NULL;
496 mm->layer_pick_shell = NULL;
497 mm->route_style_shell = NULL;
498
499 mm->special_key_cb = special_key_cb;
500 mm->action_cb = action_cb;
501 mm->actions = NULL;
502 mm->popup_table = g_hash_table_new (g_str_hash, g_str_equal);
503
504 return GTK_WIDGET (mm);
505 }
506
507 /*!
508 * \brief Turn a pcb resource into the main menu.
509 */
510 void
ghid_main_menu_add_resource(GHidMainMenu * menu,const Resource * res)511 ghid_main_menu_add_resource (GHidMainMenu *menu, const Resource *res)
512 {
513 ghid_main_menu_real_add_resource (menu, GTK_MENU_SHELL (menu), res);
514 }
515
516 /*!
517 * \brief Turn a pcb resource into a popup menu.
518 */
519 void
ghid_main_menu_add_popup_resource(GHidMainMenu * menu,const char * name,const Resource * res)520 ghid_main_menu_add_popup_resource (GHidMainMenu *menu, const char *name,
521 const Resource *res)
522 {
523 GtkWidget *new_menu = gtk_menu_new ();
524 g_object_ref_sink (new_menu);
525 ghid_main_menu_real_add_resource (menu, GTK_MENU_SHELL (new_menu), res);
526 g_hash_table_insert (menu->popup_table, (gpointer) name, new_menu);
527 gtk_widget_show_all (new_menu);
528 }
529
530 /*!
531 * \brief Returns a registered popup menu by name.
532 */
533 GtkMenu *
ghid_main_menu_get_popup(GHidMainMenu * menu,const char * name)534 ghid_main_menu_get_popup (GHidMainMenu *menu, const char *name)
535 {
536 return g_hash_table_lookup (menu->popup_table, name);
537 }
538
539
540 /*!
541 * \brief Updates the toggle/active state of all items.
542 *
543 * Loops through all actions, passing the action, its toggle
544 * flag (maybe NULL), and its active flag (maybe NULL), to a
545 * callback function. It is the responsibility of the function
546 * to actually change the state of the action.
547 *
548 * \param [in] menu The menu to be acted on.
549 * \param [in] cb The callback that toggles the actions.
550 */
551 void
ghid_main_menu_update_toggle_state(GHidMainMenu * menu,void (* cb)(GtkAction *,const char * toggle_flag,const char * active_flag))552 ghid_main_menu_update_toggle_state (GHidMainMenu *menu,
553 void (*cb) (GtkAction *,
554 const char *toggle_flag,
555 const char *active_flag))
556 {
557 GList *list;
558 for (list = menu->actions; list; list = list->next)
559 {
560 Resource *res = g_object_get_data (G_OBJECT (list->data), "resource");
561 const char *tf = g_object_get_data (G_OBJECT (list->data),
562 "checked-flag");
563 const char *af = g_object_get_data (G_OBJECT (list->data),
564 "active-flag");
565 g_signal_handlers_block_by_func (G_OBJECT (list->data),
566 menu->action_cb, res);
567 cb (GTK_ACTION (list->data), tf, af);
568 g_signal_handlers_unblock_by_func (G_OBJECT (list->data),
569 menu->action_cb, res);
570 }
571 }
572
573 /*!
574 * \brief Installs or updates layer selector items.
575 */
576 void
ghid_main_menu_install_layer_selector(GHidMainMenu * mm,GHidLayerSelector * ls)577 ghid_main_menu_install_layer_selector (GHidMainMenu *mm,
578 GHidLayerSelector *ls)
579 {
580 GList *children, *iter;
581
582 /* @layerview */
583 if (mm->layer_view_shell)
584 {
585 /* Remove old children */
586 children = gtk_container_get_children
587 (GTK_CONTAINER (mm->layer_view_shell));
588 for (iter = g_list_nth (children, mm->layer_view_pos);
589 iter != NULL && mm->n_layer_views > 0;
590 iter = g_list_next (iter), mm->n_layer_views --)
591 gtk_container_remove (GTK_CONTAINER (mm->layer_view_shell),
592 iter->data);
593 g_list_free (children);
594
595 /* Install new ones */
596 mm->n_layer_views = ghid_layer_selector_install_view_items
597 (ls, mm->layer_view_shell, mm->layer_view_pos);
598 }
599
600 /* @layerpick */
601 if (mm->layer_pick_shell)
602 {
603 /* Remove old children */
604 children = gtk_container_get_children
605 (GTK_CONTAINER (mm->layer_pick_shell));
606 for (iter = g_list_nth (children, mm->layer_pick_pos);
607 iter != NULL && mm->n_layer_picks > 0;
608 iter = g_list_next (iter), mm->n_layer_picks --)
609 gtk_container_remove (GTK_CONTAINER (mm->layer_pick_shell),
610 iter->data);
611 g_list_free (children);
612
613 /* Install new ones */
614 mm->n_layer_picks = ghid_layer_selector_install_pick_items
615 (ls, mm->layer_pick_shell, mm->layer_pick_pos);
616 }
617 }
618
619 /*!
620 * \brief Installs or updates route style selector items.
621 */
622 void
ghid_main_menu_install_route_style_selector(GHidMainMenu * mm,GHidRouteStyleSelector * rss)623 ghid_main_menu_install_route_style_selector (GHidMainMenu *mm,
624 GHidRouteStyleSelector *rss)
625 {
626 GList *children, *iter;
627 /* @routestyles */
628 if (mm->route_style_shell)
629 {
630 /* Remove old children */
631 children = gtk_container_get_children
632 (GTK_CONTAINER (mm->route_style_shell));
633 for (iter = g_list_nth (children, mm->route_style_pos);
634 iter != NULL && mm->n_route_styles > 0;
635 iter = g_list_next (iter), mm->n_route_styles --)
636 gtk_container_remove (GTK_CONTAINER (mm->route_style_shell),
637 iter->data);
638 g_list_free (children);
639 /* Install new ones */
640 mm->n_route_styles = ghid_route_style_selector_install_items
641 (rss, mm->route_style_shell, mm->route_style_pos);
642 }
643 }
644
645 /*!
646 * \brief Returns the menu bar's accelerator group.
647 */
648 GtkAccelGroup *
ghid_main_menu_get_accel_group(GHidMainMenu * menu)649 ghid_main_menu_get_accel_group (GHidMainMenu *menu)
650 {
651 return menu->accel_group;
652 }
653
654