pcb 4.1.1
An interactive printed circuit board layout editor.

ghid-main-menu.c

Go to the documentation of this file.
00001 
00035 #include <glib.h>
00036 #include <glib-object.h>
00037 #include <gtk/gtk.h>
00038 
00039 #include "gtkhid.h"
00040 #include "gui.h"
00041 #include "pcb-printf.h"
00042 
00043 #include "ghid-main-menu.h"
00044 #include "ghid-layer-selector.h"
00045 #include "ghid-route-style-selector.h"
00046 
00047 void Message (const char *, ...);
00048 
00049 static int action_counter;
00050 
00051 struct _GHidMainMenu
00052 {
00053   GtkMenuBar parent;
00054 
00055   GtkActionGroup *action_group;
00056   GtkAccelGroup *accel_group;
00057 
00058   gint layer_view_pos;
00059   gint layer_pick_pos;
00060   gint route_style_pos;
00061 
00062   GtkMenuShell *layer_view_shell;
00063   GtkMenuShell *layer_pick_shell;
00064   GtkMenuShell *route_style_shell;
00065 
00066   GList *actions;
00067   GHashTable *popup_table;
00068 
00069   gint n_layer_views;
00070   gint n_layer_picks;
00071   gint n_route_styles;
00072 
00073   GCallback action_cb;
00074   void (*special_key_cb) (const char *accel, GtkAction *action,
00075                           const Resource *node);
00076 };
00077 
00078 struct _GHidMainMenuClass
00079 {
00080   GtkMenuBarClass parent_class;
00081 };
00082 
00083 /* TODO: write finalize function */
00084 
00085 /* SIGNAL HANDLERS */
00086 
00087 /* RESOURCE HANDLER */
00088 
00100 static gchar *
00101 translate_accelerator (const char *text)
00102 {
00103   GString *ret_val = g_string_new ("");
00104   static struct { const char *in, *out; } key_table[] = 
00105   {
00106     {"Enter", "Return"},
00107     {"Alt",   "<alt>"},
00108     {"Shift", "<shift>"},
00109     {"Ctrl",  "<ctrl>"},
00110     {" ", ""},
00111     {":", "colon"},
00112     {"=", "equal"},
00113     {"/", "slash"},
00114     {"[", "bracketleft"},
00115     {"]", "bracketright"},
00116     {".", "period"},
00117     {"|", "bar"},
00118     {"+", "plus"},
00119     {"-", "minus"},
00120     {NULL, NULL}
00121   };
00122 
00123   enum {MOD, KEY} state = MOD;
00124   while (*text != '\0')
00125     {
00126       static gboolean gave_msg;
00127       gboolean found = FALSE;
00128       int i;
00129 
00130       if (state == MOD && strncmp (text, "<Key>", 5) == 0)
00131         {
00132           state = KEY;
00133           text += 5;
00134         }
00135       for (i = 0; key_table[i].in != NULL; ++i)
00136         {
00137           int len = strlen (key_table[i].in);
00138           if (strncmp (text, key_table[i].in, len) == 0)
00139             {
00140               found = TRUE;
00141               g_string_append (ret_val, key_table[i].out);
00142               text += len;
00143             }
00144         }
00145       if (found == FALSE)
00146         switch (state)
00147           {
00148           case MOD:
00149             Message (_("Don't know how to parse \"%s\" as an "
00150                        "accelerator in the menu resource file.\n"),
00151                      text);
00152             if (!gave_msg)
00153               {
00154                 gave_msg = TRUE;
00155                 Message (_("Format is:\n"
00156                            "modifiers<Key>k\n"
00157                            "where \"modifiers\" is a space "
00158                            "separated list of key modifiers\n"
00159                            "and \"k\" is the name of the key.\n"
00160                            "Allowed modifiers are:\n"
00161                            "   Ctrl\n"
00162                            "   Shift\n"
00163                            "   Alt\n"
00164                            "Please note that case is important.\n"));
00165               }
00166             break;
00167           case KEY:
00168             g_string_append_c (ret_val, *text);
00169             ++text;
00170             break;
00171           }
00172     }
00173   return g_string_free (ret_val, FALSE);
00174 }
00175 
00176 static gboolean
00177 g_str_case_equal (gconstpointer v1, gconstpointer v2)
00178 {
00179   return strcasecmp (v1, v2);
00180 }
00181 
00185 static const char *
00186 check_unique_accel (const char *accelerator)
00187 {
00188   static GHashTable *accel_table;
00189 
00190   if (!accelerator ||*accelerator)
00191     return accelerator;
00192 
00193   if (!accel_table)
00194     accel_table = g_hash_table_new (g_str_hash, g_str_case_equal);
00195 
00196   if (g_hash_table_lookup (accel_table, accelerator))
00197     {
00198        Message (_("Duplicate accelerator found: \"%s\"\n"
00199                   "The second occurrence will be dropped\n"),
00200                 accelerator);
00201         return NULL;
00202     }
00203 
00204   g_hash_table_insert (accel_table,
00205                        (gpointer) accelerator, (gpointer) accelerator);
00206 
00207   return accelerator;
00208 }
00209 
00210 
00218 void
00219 ghid_main_menu_real_add_resource (GHidMainMenu *menu, GtkMenuShell *shell,
00220                                   const Resource *res)
00221 {
00222   int i, j;
00223   const Resource *tmp_res;
00224   gchar mnemonic = 0;
00225 
00226   for (i = 0; i < res->c; ++i)
00227     {
00228       const gchar *accel = NULL;
00229       char *menu_label;
00230       const char *res_val;
00231       const Resource *sub_res = res->v[i].subres;
00232       GtkAction *action = NULL;
00233 
00234       switch (resource_type (res->v[i]))
00235         {
00236         case 101:   /* name, subres: passthrough */
00237           ghid_main_menu_real_add_resource (menu, shell, sub_res);
00238           break;
00239         case   1:   /* no name, subres */
00240           tmp_res = resource_subres (sub_res, "a");  /* accelerator */
00241           res_val = resource_value (sub_res, "m");   /* mnemonic */
00242           if (res_val)
00243             mnemonic = res_val[0];
00244           /* The accelerator resource will have two values, like 
00245            *   a={"Ctrl-Q" "Ctrl<Key>q"}
00246            * The first Gtk ignores. The second needs to be translated. */
00247           if (tmp_res)
00248             accel = check_unique_accel
00249                       (translate_accelerator (tmp_res->v[1].value));
00250 
00251           /* Now look for the first unnamed value (not a subresource) to
00252            * figure out the name of the menu or the menuitem. */
00253           res_val = "button";
00254           for (j = 0; j < sub_res->c; ++j)
00255             if (resource_type (sub_res->v[j]) == 10)
00256               {
00257                 res_val = _(sub_res->v[j].value);
00258                 break;
00259               }
00260           /* Hack '_' in based on mnemonic value */
00261           if (!mnemonic)
00262             menu_label = g_strdup (res_val);
00263           else
00264             {
00265               char *post_ = strchr (res_val, mnemonic);
00266               if (post_ == NULL)
00267                 menu_label = g_strdup (res_val);
00268               else
00269                 {
00270                   GString *tmp = g_string_new ("");
00271                   g_string_append_len (tmp, res_val, post_ - res_val);
00272                   g_string_append_c (tmp, '_');
00273                   g_string_append (tmp, post_);
00274                   menu_label = g_string_free (tmp, FALSE);
00275                 }
00276             }
00277           /* If the subresource we're processing also has unnamed
00278            * subresources, it's a submenu, not a regular menuitem. */
00279           if (sub_res->flags & FLAG_S)
00280             {
00281               /* SUBMENU */
00282               GtkWidget *submenu = gtk_menu_new ();
00283               GtkWidget *item = gtk_menu_item_new_with_mnemonic (menu_label);
00284               GtkWidget *tearoff = gtk_tearoff_menu_item_new ();
00285 
00286               gtk_menu_shell_append (shell, item);
00287               gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
00288 
00289               /* add tearoff to menu */
00290               gtk_menu_shell_append (GTK_MENU_SHELL (submenu), tearoff);
00291               /* recurse on the newly-added submenu */
00292               ghid_main_menu_real_add_resource (menu,
00293                                                 GTK_MENU_SHELL (submenu),
00294                                                 sub_res);
00295             }
00296           else
00297             {
00298               /* NON-SUBMENU: MENU ITEM */
00299               const char *checked = resource_value (sub_res, "checked");
00300               const char *label = resource_value (sub_res, "sensitive");
00301               const char *tip = resource_value (sub_res, "tip");
00302               if (checked)
00303                 {
00304                   /* TOGGLE ITEM */
00305                   gchar *name = g_strdup_printf ("MainMenuAction%d",
00306                                                  action_counter++);
00307 
00308                   action = GTK_ACTION (gtk_toggle_action_new (name, menu_label,
00309                                                               tip, NULL));
00310                   /* checked=foo       is a binary flag (checkbox)
00311                    * checked=foo,bar   is a flag compared to a value (radio) */
00312                   gtk_toggle_action_set_draw_as_radio
00313                     (GTK_TOGGLE_ACTION (action), !!strchr (checked, ','));
00314                 }
00315               else if (label && strcmp (label, "false") == 0)
00316                 {
00317                   /* INSENSITIVE ITEM */
00318                   GtkWidget *item = gtk_menu_item_new_with_label (menu_label);
00319                   gtk_widget_set_sensitive (item, FALSE);
00320                   gtk_menu_shell_append (shell, item);
00321                 }
00322               else
00323                 {
00324                   /* NORMAL ITEM */
00325                   gchar *name = g_strdup_printf ("MainMenuAction%d", action_counter++);
00326                   action = gtk_action_new (name, menu_label, tip, NULL);
00327                 }
00328             }
00329           /* Connect accelerator, if there is one */
00330           if (action)
00331             {
00332               GtkWidget *item;
00333               gtk_action_set_accel_group (action, menu->accel_group);
00334               gtk_action_group_add_action_with_accel (menu->action_group,
00335                                                       action, accel);
00336               gtk_action_connect_accelerator (action);
00337               g_signal_connect (G_OBJECT (action), "activate", menu->action_cb,
00338                                 (gpointer) sub_res);
00339               g_object_set_data (G_OBJECT (action), "resource",
00340                                  (gpointer) sub_res);
00341               item = gtk_action_create_menu_item (action);
00342               gtk_menu_shell_append (shell, item);
00343               menu->actions = g_list_append (menu->actions, action);
00344               menu->special_key_cb (accel, action, sub_res);
00345             }
00346           /* Scan rest of resource in case there is more work */
00347           for (j = 0; j < sub_res->c; j++)
00348             {
00349               const char *res_name;
00350               /* named value = X resource */
00351               if (resource_type (sub_res->v[j]) == 110)
00352                 {
00353                   res_name = sub_res->v[j].name;
00354 
00355                   /* translate bg, fg to background, foreground */
00356                   if (strcmp (res_name, "fg") == 0)   res_name = "foreground";
00357                   if (strcmp (res_name, "bg") == 0)   res_name = "background";
00358 
00359                   /* ignore special named values (m, a, sensitive) */
00360                   if (strcmp (res_name, "m") == 0
00361                       || strcmp (res_name, "a") == 0
00362                       || strcmp (res_name, "sensitive") == 0
00363                       || strcmp (res_name, "tip") == 0)
00364                     break;
00365 
00366                   /* log checked and active special values */
00367                   if (action && strcmp (res_name, "checked") == 0)
00368                     g_object_set_data (G_OBJECT (action), "checked-flag",
00369                                        sub_res->v[j].value);
00370                   else if (action && strcmp (res_name, "active") == 0)
00371                     g_object_set_data (G_OBJECT (action), "active-flag",
00372                                        sub_res->v[j].value);
00373                   else
00374                     /* if we got this far it is supposed to be an X
00375                      * resource.  For now ignore it and warn the user */
00376                     Message (_("The gtk gui currently ignores \"%s\""
00377                                "as part of a menuitem resource.\n"
00378                                "Feel free to provide patches\n"),
00379                              sub_res->v[j].value);
00380                 }
00381             }
00382           break;
00383         case  10:   /* no name, value */
00384           /* If we get here, the resource is "-" or "@foo" for some foo */
00385           if (res->v[i].value[0] == '@')
00386             {
00387               GList *children;
00388               int pos;
00389 
00390               children = gtk_container_get_children (GTK_CONTAINER (shell));
00391               pos = g_list_length (children);
00392               g_list_free (children);
00393 
00394               if (strcmp (res->v[i].value, "@layerview") == 0)
00395                 {
00396                   menu->layer_view_shell = shell;
00397                   menu->layer_view_pos = pos;
00398                 }
00399               else if (strcmp (res->v[i].value, "@layerpick") == 0)
00400                 {
00401                   menu->layer_pick_shell = shell;
00402                   menu->layer_pick_pos = pos;
00403                 }
00404               else if (strcmp (res->v[i].value, "@routestyles") == 0)
00405                 {
00406                   menu->route_style_shell = shell;
00407                   menu->route_style_pos = pos;
00408                 }
00409               else
00410                 Message (_("GTK GUI currently ignores \"%s\" in the menu\n"
00411                            "resource file.\n"), res->v[i].value);
00412             }
00413           else if (strcmp (res->v[i].value, "-") == 0)
00414             {
00415               GtkWidget *item = gtk_separator_menu_item_new ();
00416               gtk_menu_shell_append (shell, item);
00417             }
00418           else if (i > 0)
00419             {
00420               /* This is an action-less menuitem. It is really only useful
00421                * when you're starting to build a new menu and you're looking
00422                * to get the layout right. */
00423               GtkWidget *item
00424                 = gtk_menu_item_new_with_label (_(res->v[i].value));
00425               gtk_menu_shell_append (shell, item);
00426             }
00427           break;
00428       }
00429   }
00430 }
00431 
00432 /* CONSTRUCTOR */
00433 static void
00434 ghid_main_menu_init (GHidMainMenu *mm)
00435 {
00436   /* Hookup signal handlers */
00437 }
00438 
00439 static void
00440 ghid_main_menu_class_init (GHidMainMenuClass *klass)
00441 {
00442 }
00443 
00444 /* PUBLIC FUNCTIONS */
00445 GType
00446 ghid_main_menu_get_type (void)
00447 {
00448   static GType mm_type = 0;
00449 
00450   if (!mm_type)
00451     {
00452       const GTypeInfo mm_info =
00453         {
00454           sizeof (GHidMainMenuClass),
00455           NULL, /* base_init */
00456           NULL, /* base_finalize */
00457           (GClassInitFunc) ghid_main_menu_class_init,
00458           NULL, /* class_finalize */
00459           NULL, /* class_data */
00460           sizeof (GHidMainMenu),
00461           0,    /* n_preallocs */
00462           (GInstanceInitFunc) ghid_main_menu_init,
00463         };
00464 
00465       mm_type = g_type_register_static (GTK_TYPE_MENU_BAR,
00466                                         "GHidMainMenu",
00467                                         &mm_info, 0);
00468     }
00469 
00470   return mm_type;
00471 }
00472 
00478 GtkWidget *
00479 ghid_main_menu_new (GCallback action_cb,
00480                     void (*special_key_cb) (const char *accel,
00481                                             GtkAction *action,
00482                                             const Resource *node))
00483 {
00484   GHidMainMenu *mm = g_object_new (GHID_MAIN_MENU_TYPE, NULL);
00485 
00486   mm->accel_group = gtk_accel_group_new ();
00487   mm->action_group = gtk_action_group_new ("MainMenu");
00488 
00489   mm->layer_view_pos = 0;
00490   mm->layer_pick_pos = 0;
00491   mm->route_style_pos = 0;
00492   mm->n_layer_views = 0;
00493   mm->n_layer_picks = 0;
00494   mm->n_route_styles = 0;
00495   mm->layer_view_shell = NULL;
00496   mm->layer_pick_shell = NULL;
00497   mm->route_style_shell = NULL;
00498 
00499   mm->special_key_cb = special_key_cb;
00500   mm->action_cb = action_cb;
00501   mm->actions = NULL;
00502   mm->popup_table = g_hash_table_new (g_str_hash, g_str_equal);
00503 
00504   return GTK_WIDGET (mm);
00505 }
00506 
00510 void
00511 ghid_main_menu_add_resource (GHidMainMenu *menu, const Resource *res)
00512 {
00513   ghid_main_menu_real_add_resource (menu, GTK_MENU_SHELL (menu), res);
00514 }
00515 
00519 void
00520 ghid_main_menu_add_popup_resource (GHidMainMenu *menu, const char *name,
00521                                    const Resource *res)
00522 {
00523   GtkWidget *new_menu = gtk_menu_new ();
00524   g_object_ref_sink (new_menu);
00525   ghid_main_menu_real_add_resource (menu, GTK_MENU_SHELL (new_menu), res);
00526   g_hash_table_insert (menu->popup_table, (gpointer) name, new_menu);
00527   gtk_widget_show_all (new_menu);
00528 }
00529 
00533 GtkMenu *
00534 ghid_main_menu_get_popup (GHidMainMenu *menu, const char *name)
00535 {
00536   return g_hash_table_lookup (menu->popup_table, name);
00537 }
00538 
00539 
00551 void
00552 ghid_main_menu_update_toggle_state (GHidMainMenu *menu,
00553                                     void (*cb) (GtkAction *,
00554                                                 const char *toggle_flag,
00555                                                 const char *active_flag))
00556 {
00557   GList *list;
00558   for (list = menu->actions; list; list = list->next)
00559     {
00560       Resource *res = g_object_get_data (G_OBJECT (list->data), "resource");
00561       const char *tf = g_object_get_data (G_OBJECT (list->data),
00562                                           "checked-flag");
00563       const char *af = g_object_get_data (G_OBJECT (list->data),
00564                                           "active-flag");
00565       g_signal_handlers_block_by_func (G_OBJECT (list->data),
00566                                        menu->action_cb, res);
00567       cb (GTK_ACTION (list->data), tf, af);
00568       g_signal_handlers_unblock_by_func (G_OBJECT (list->data),
00569                                          menu->action_cb, res);
00570     }
00571 }
00572 
00576 void
00577 ghid_main_menu_install_layer_selector (GHidMainMenu *mm,
00578                                        GHidLayerSelector *ls)
00579 {
00580   GList *children, *iter;
00581 
00582   /* @layerview */
00583   if (mm->layer_view_shell)
00584     {
00585       /* Remove old children */
00586       children = gtk_container_get_children
00587                    (GTK_CONTAINER (mm->layer_view_shell));
00588       for (iter = g_list_nth (children, mm->layer_view_pos);
00589            iter != NULL && mm->n_layer_views > 0;
00590            iter = g_list_next (iter), mm->n_layer_views --)
00591         gtk_container_remove (GTK_CONTAINER (mm->layer_view_shell),
00592                               iter->data);
00593       g_list_free (children);
00594 
00595       /* Install new ones */
00596       mm->n_layer_views = ghid_layer_selector_install_view_items
00597                             (ls, mm->layer_view_shell, mm->layer_view_pos);
00598     }
00599 
00600   /* @layerpick */
00601   if (mm->layer_pick_shell)
00602     {
00603       /* Remove old children */
00604       children = gtk_container_get_children
00605                    (GTK_CONTAINER (mm->layer_pick_shell));
00606       for (iter = g_list_nth (children, mm->layer_pick_pos);
00607            iter != NULL && mm->n_layer_picks > 0;
00608            iter = g_list_next (iter), mm->n_layer_picks --)
00609         gtk_container_remove (GTK_CONTAINER (mm->layer_pick_shell),
00610                               iter->data);
00611       g_list_free (children);
00612 
00613       /* Install new ones */
00614       mm->n_layer_picks = ghid_layer_selector_install_pick_items
00615                             (ls, mm->layer_pick_shell, mm->layer_pick_pos);
00616     }
00617 }
00618 
00622 void
00623 ghid_main_menu_install_route_style_selector (GHidMainMenu *mm,
00624                                              GHidRouteStyleSelector *rss)
00625 {
00626   GList *children, *iter;
00627   /* @routestyles */
00628   if (mm->route_style_shell)
00629     {
00630       /* Remove old children */
00631       children = gtk_container_get_children
00632                    (GTK_CONTAINER (mm->route_style_shell));
00633       for (iter = g_list_nth (children, mm->route_style_pos);
00634            iter != NULL && mm->n_route_styles > 0;
00635            iter = g_list_next (iter), mm->n_route_styles --)
00636         gtk_container_remove (GTK_CONTAINER (mm->route_style_shell),
00637                               iter->data);
00638       g_list_free (children);
00639       /* Install new ones */
00640       mm->n_route_styles = ghid_route_style_selector_install_items
00641                              (rss, mm->route_style_shell, mm->route_style_pos);
00642     }
00643 }
00644 
00648 GtkAccelGroup *
00649 ghid_main_menu_get_accel_group (GHidMainMenu *menu)
00650 {
00651   return menu->accel_group;
00652 }
00653