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