[Gnucash-changes] Rework the widget lookup code to handle widgets from multiple glade

David Hampton hampton at cvs.gnucash.org
Fri Jul 22 22:37:06 EDT 2005


Log Message:
-----------
Rework the widget lookup code to handle widgets from multiple glade
files.  Documentation.

Tags:
----
gnucash-gnome2-dev

Modified Files:
--------------
    gnucash/src/gnome-utils:
        dialog-preferences.c
        dialog-preferences.h

Revision Data
-------------
Index: dialog-preferences.h
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome-utils/Attic/dialog-preferences.h,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/gnome-utils/dialog-preferences.h -Lsrc/gnome-utils/dialog-preferences.h -u -r1.1.2.1 -r1.1.2.2
--- src/gnome-utils/dialog-preferences.h
+++ src/gnome-utils/dialog-preferences.h
@@ -20,12 +20,84 @@
  * Boston, MA  02111-1307,  USA       gnu at gnu.org
  */
 
+/** @addtogroup GUI
+    @{ */
+/** @addtogroup Dialog Preferences Dialog
+    @{ */
+/** @file dialog-preferences.h
+    @brief Dialog for handling user preferences.
+    @author Copyright (c) 2005 David Hampton <hampton at employees.org>
+    
+    These functions are the external API available for the new user
+    preference dialog.  Preferences are now stored in GConf.  This
+    code ends up being nothing more than a pretty interface to set
+    key/value pairs in that database.  Any module may add a page (or
+    partial page) of preferences to the dialog.  These additions are
+    done by providing the name of a glade file, and a widget in that
+    file.  If a partial page is added, the widget name provided must
+    be that of a GtkTable containing four columns. If a full page is
+    added, the widget name provided to this code can be any kind of
+    widget, but for consistence it should probably be the same.
+
+    If a widget names is in the form gconf/xxx/yyy... and it is a type
+    of widget this code knows how to handle, then the callback signals
+    will be automatically wired up for the widget.  The only fields
+    that is required to be set in the glade file is the widget name.
+    This code currently know about radio buttons, check buttons, spin
+    boxes, an combo boxes.  (Combo boxes should not be used for less
+    than six choices.  Use a radio button group instead.)
+
+    The argument *is* a glade file, so if your code has special
+    requirements (e.g. make one widget insensitive until another is
+    selected) feel free to go ahead and add your own callbacks to the
+    glade file.  This code will connect any callbacks that exist in
+    the glade file.
+*/
+
+#ifndef GNC_DIALOG_PREFERENCES_H
+#define GNC_DIALOG_PREFERENCES_H
+
+/** This function adds a full page of preferences to the preferences
+ *  dialog.  When the dialog is created, the specified widget will be
+ *  pulled from the specified glade file and added to the preferences
+ *  dialog with the specified tab name.  The tab name may not be
+ *  duplicated.  For example, the Business code might have a full page
+ *  of its own preferences.
+ *  
+ *  @param filename The name of a glade file.
+ *  
+ *  @param widgetname The name of the widget to extract from the glade file.
+ *  
+ *  @param tabname The name this page of preferences should have in
+ *  the dialog notebook. */
 void gnc_preferences_add_page (const gchar *filename,
 			       const gchar *widgetname,
 			       const gchar *tabname);
 
+
+/** This function adds a partial page of preferences to the
+ *  preferences dialog.  When the dialog is created, the specified
+ *  widget will be pulled from the specified glade file and added to
+ *  the preferences dialog with the specified tab name.  The tab name
+ *  may be duplicated.  For example, the HBCI preferences may share a
+ *  "Data Import" page with QIF and other methods.
+ *  
+ *  @param filename The name of a glade file.
+ *  
+ *  @param widgetname The name of the widget to extract from the glade file.
+ *  
+ *  @param tabname The name this page of preferences should have in
+ *  the dialog notebook. */
 void gnc_preferences_add_to_page (const gchar *filename,
 				  const gchar *widgetname,
 				  const gchar *tabname);
 
+
+/** This function creates the preferences dialog and presents it to
+ *  the user.  The preferences dialog is a singletone, so if a
+ *  preferences dialog already exists it will be raised to the top of
+ *  the window stack instead of creating a new dialog. */
 void gnc_preferences_dialog (void);
+
+#endif
+/** @} */
Index: dialog-preferences.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/gnome-utils/Attic/dialog-preferences.c,v
retrieving revision 1.1.2.1
retrieving revision 1.1.2.2
diff -Lsrc/gnome-utils/dialog-preferences.c -Lsrc/gnome-utils/dialog-preferences.c -u -r1.1.2.1 -r1.1.2.2
--- src/gnome-utils/dialog-preferences.c
+++ src/gnome-utils/dialog-preferences.c
@@ -20,6 +20,40 @@
  * Boston, MA  02111-1307,  USA       gnu at gnu.org
  */
 
+/** @addtogroup GUI
+    @{ */
+/** @addtogroup Preferences Dialog
+    @{ */
+/** @file dialog-preferences.c
+    @brief Dialog for handling user preferences.
+    @author Copyright (c) 2005 David Hampton <hampton at employees.org>
+    
+    These functions are the external API available for the new user
+    preference dialog.  Preferences are now stored in GConf.  This
+    code ends up being nothing more than a pretty interface to set
+    key/value pairs in that database.  Any module may add a page (or
+    partial page) of preferences to the dialog.  These additions are
+    done by providing the name of a glade file, and a widget in that
+    file.  If a partial page is added, the widget name provided must
+    be that of a GtkTable containing four columns. If a full page is
+    added, the widget name provided to this code can be any kind of
+    widget, but for consistence it should probably be the same.
+
+    If a widget names is in the form gconf/xxx/yyy... and it is a type
+    of widget this code knows how to handle, then the callback signals
+    will be automatically wired up for the widget.  The only fields
+    that is required to be set in the glade file is the widget name.
+    This code currently know about radio buttons, check buttons, spin
+    boxes, an combo boxes.  (Combo boxes should not be used for less
+    than six choices.  Use a radio button group instead.)
+
+    The argument *is* a glade file, so if your code has special
+    requirements (e.g. make one widget insensitive until another is
+    selected) feel free to go ahead and add your own callbacks to the
+    glade file.  This code will connect any callbacks that exist in
+    the glade file.
+*/
+
 #include "config.h"
 
 #include <gtk/gtk.h>
@@ -35,6 +69,8 @@
 #define DIALOG_PREFERENCES_CM_CLASS	"dialog-newpreferences"
 #define GCONF_SECTION			"dialogs/preferences"
 #define PREFIX_LEN			sizeof("gconf/") - 1
+#define WIDGET_HASH			"widget_hash"
+#define NOTEBOOK			"notebook"
 
 /* This static indicates the debugging module that this .o belongs to.  */
 static short module = MOD_PREFS;
@@ -54,16 +90,58 @@
 
 GSList *add_ins = NULL;
 
+
+/** This function compares two add-ins to see if they specify the same
+ *  tab name.
+ *
+ *  @internal
+ *
+ *  @param a A pointer to the first add-in.
+ *  
+ *  @param b A pointer to the second add-in.
+ *  
+ *  @return Zero if the tab name is the same in both add-ins. Non-zero otherwise.
+ */
+static gint
+gnc_prefs_compare_addins (addition *a,
+			  addition *b)
+{
+  return strcmp(a->tabname, b->tabname);
+}
+
+
+/** This is the common function that adds any set of preferences to
+ *  the preferences dialog.  It allocates a data structure to remember
+ *  the passed in data and queues it for later when the dialog is
+ *  actually built.  This code does check to insure there arent any
+ *  conflicts, like multiple additions of the same tab name when the
+ *  two pages being added aren't compatible.
+ *
+ *  @internal
+ *
+ *  @param filename The name of a glade file.
+ *  
+ *  @param widgetname The name of the widget to extract from the glade file.
+ *  
+ *  @param tabname The name this page of preferences should have in
+ *  the dialog notebook.
+ *
+ *  @param full_page Is this a full page of preferences or a partial page.
+ */
 static void
 gnc_preferences_add_page_internal (const gchar *filename,
 				   const gchar *widgetname,
 				   const gchar *tabname,
 				   gboolean full_page)
 {
-  addition *add_in;
+  addition *add_in, *preexisting;
+  gboolean error = FALSE;
+  GSList *ptr;
 
   ENTER("file %s, widget %s, tab %s full page %d",
 	filename, widgetname, tabname, full_page);
+
+
   add_in = g_malloc(sizeof(addition));
   if (add_in == NULL) {
     g_critical("Unable to allocate memory.\n");
@@ -80,14 +158,50 @@
     g_free(add_in->filename);
     g_free(add_in->widgetname);
     g_free(add_in->tabname);
+    g_free(add_in);
     LEAVE("no memory");
     return;
   }
-  add_ins = g_slist_append(add_ins, add_in);
+
+  ptr = g_slist_find_custom(add_ins, add_in, (GCompareFunc)gnc_prefs_compare_addins);
+  if (ptr) {
+    /* problem? */
+    preexisting = ptr->data;
+    
+    if (preexisting->full_page) {
+      g_warning("New tab %s(%s/%s/%s) conflicts with existing tab %s(%s/%s/full)",
+		add_in->tabname, add_in->filename, add_in->widgetname,
+		add_in->full_page ? "full" : "partial",
+		preexisting->tabname, preexisting->filename, preexisting->widgetname);
+      error = TRUE;
+    } else if (add_in->full_page) {
+      g_warning("New tab %s(%s/%s/%s) conflicts with existing tab %s(%s/%s/partial)",
+		add_in->tabname, add_in->filename, add_in->widgetname,
+		add_in->full_page ? "full" : "partial",
+		preexisting->tabname, preexisting->filename, preexisting->widgetname);
+      error = TRUE;
+    }
+  }
+
+  if (error) {
+    g_free(add_in->filename);
+    g_free(add_in->widgetname);
+    g_free(add_in->tabname);
+    g_free(add_in);
+    return;
+  } else {
+    add_ins = g_slist_append(add_ins, add_in);
+  }
   LEAVE("");
 }
 
 
+/*  This function adds a full page of preferences to the preferences
+ *  dialog.  When the dialog is created, the specified widget will be
+ *  pulled from the specified glade file and added to the preferences
+ *  dialog with the specified tab name.  The tab name may not be
+ *  duplicated.  For example, the Business code might have a full page
+ *  of its own preferences. */
 void
 gnc_preferences_add_page (const gchar *filename,
 			  const gchar *widgetname,
@@ -96,6 +210,13 @@
   gnc_preferences_add_page_internal(filename, widgetname, tabname, TRUE);
 }
 
+
+/*  This function adds a partial page of preferences to the
+ *  preferences dialog.  When the dialog is created, the specified
+ *  widget will be pulled from the specified glade file and added to
+ *  the preferences dialog with the specified tab name.  The tab name
+ *  may be duplicated.  For example, the HBCI preferences may share a
+ *  "Data Import" page with QIF and other methods. */
 void
 gnc_preferences_add_to_page (const gchar *filename,
 			     const gchar *widgetname,
@@ -106,6 +227,41 @@
 
 /****************************************/
 
+/** This function builds a hash table of "interesting" widgets,
+ *  i.e. widgets whose name starts with "gconf/".  This table is
+ *  needed to perform name->widget lookups in the gconf callback
+ *  functions.  Normally glade could be used for this function, but
+ *  since the widgets come from multiple glade files that won;t work
+ *  in this dialog.
+ *
+ *  @internal
+ *
+ *  @param xml A pointer to glade xml file currently being added to
+ *  the dialog.
+ *  
+ *  @param dialog A pointer to the dialog.  The hash table is stored
+ *  as a pointer off the dialog so that it can be found in the
+ *  callback from gconf. */
+static void
+gnc_prefs_build_widget_table (GladeXML *xml,
+			      GtkWidget *dialog)
+{
+  GHashTable *table;
+  GList *interesting, *runner;
+  const gchar *name;
+  GtkWidget *widget;
+
+  table = g_object_get_data(G_OBJECT(dialog), WIDGET_HASH);
+  interesting = glade_xml_get_widget_prefix(xml, "gconf");
+  for (runner = interesting; runner; runner = g_list_next(runner)) {
+    widget = runner->data;
+    name = gtk_widget_get_name(widget);
+    g_hash_table_insert(table, (gchar *)name, widget);
+  }
+  g_list_free(interesting);
+}
+
+
 struct find_data {
   GtkNotebook *notebook;
   const gchar *tabname;
@@ -124,6 +280,21 @@
   GList *widgets;
 };
 
+
+/** This function is used while building the preferences dialog.  It
+ *  searches through the existing pages in the dialog to determin
+ *  where a new page should go alphabetically.  If a matching page
+ *  name is found, a pointer to that page will be returned.  This
+ *  function can also be called and asked to perform an exact match
+ *  instead of just determining where a page would go.
+ *
+ *  @internal
+ *
+ *  @param child A pointer to one page from the GtkNotebook contained
+ *  in the preferences dialog.
+ *  
+ *  @param data A pointer to a data structure passed in by the caller.
+ */
 static void
 gnc_prefs_find_page (GtkWidget *child,
 		     gpointer data)
@@ -164,8 +335,21 @@
   LEAVE("insert at offset %d", index);
 }
 
+
+/** This function moves a GtkWidget from one GtkTable to another,
+ *  preserving its attachment data, etc.  It is called when adding one
+ *  partial preference page to another.
+ *
+ *  @internal
+ *
+ *  @param widget A pointer to the widget to move.
+ *  
+ *  @param data A pointer to a data structure passed in by the caller.
+ *  This data structure contains pointers to the old and new tables,
+ *  plus the row offset into the new table.
+ */
 static void
-gnc_prefs_copy_table_entry (GtkWidget *child,
+gnc_prefs_move_table_entry (GtkWidget *child,
 			    gpointer data)
 {
   struct copy_data *copydata = data;
@@ -193,24 +377,33 @@
   LEAVE(" ");
 }
 
+
+/** At dialog creation time, this function will be called once per
+ *  adds-in.  It performs the work of addding the page into the main
+ *  dialog.  It handles both the case of a full page being added to
+ *  the dialog, and a partial page being added.
+ *
+ *  @internal
+ *
+ *  @param data A pointer to an addition data structure.
+ *  
+ *  @param user_data A pointer to the dialog.
+ */
 static void
 gnc_preferences_build_page (gpointer data,
 			    gpointer user_data)
 {
   GladeXML *xml;
-  GtkWidget *existing_content, *new_content, *label;
+  GtkWidget *dialog, *existing_content, *new_content, *label;
   GtkNotebook *notebook;
-  struct page_data *page_data;
   addition *add_in;
   struct find_data location;
   struct copy_data copydata;
   gint rows, cols;
-  GList *interesting;
 
-  ENTER("add_in %p, notebook %p", data, user_data);
+  ENTER("add_in %p, dialog %p", data, user_data);
   add_in = (addition *)data;
-  page_data = (struct page_data *) user_data;
-  notebook = page_data->notebook;
+  dialog = user_data;
 
   DEBUG("Opening %s to get %s:", add_in->filename, add_in->widgetname);
   xml = gnc_glade_xml_new(add_in->filename, add_in->widgetname);
@@ -218,10 +411,10 @@
   DEBUG("done");
 
   /* Add to the list of interesting widgets */
-  interesting = glade_xml_get_widget_prefix(xml, "gconf");
-  page_data->widgets = g_list_concat(page_data->widgets, interesting);
+  gnc_prefs_build_widget_table(xml, dialog);
 
   /* Prepare for recursion */
+  notebook = g_object_get_data(G_OBJECT(dialog), NOTEBOOK);
   location.notebook = notebook;
   location.index = -1;
   location.tabname = add_in->tabname;
@@ -271,7 +464,7 @@
   copydata.table_from = GTK_TABLE(new_content);
   copydata.table_to = GTK_TABLE(existing_content);
   copydata.row_offset = rows;
-  gtk_container_foreach(GTK_CONTAINER(new_content), gnc_prefs_copy_table_entry,
+  gtk_container_foreach(GTK_CONTAINER(new_content), gnc_prefs_move_table_entry,
 			&copydata);
 
   gtk_object_sink(GTK_OBJECT(new_content));
@@ -283,6 +476,18 @@
 /* Dynamically added Callbacks */
 /*******************************/
 
+
+/** The user clicked on a radio button.  Update gconf.  Radio button
+ *  group choices are stored as a string.  The last component of the
+ *  widget name is the string that will be stored.  I.E. The widet name
+ *  must be in this form "gconf/<some-key-name>/value".
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the radio button that was clicked.
+ *  
+ *  @param user_data Unused.
+ */
 static void
 gnc_prefs_radio_button_user_cb (GtkRadioButton *button,
 				gpointer user_data)
@@ -306,6 +511,14 @@
 }
 
 
+/** A radio button group choice was updated in gconf.  Update the user
+ *  visible dialog.
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the radio button that should be shown
+ *  as selected.
+ */
 static void
 gnc_prefs_radio_button_gconf_cb (GtkRadioButton *button)
 {
@@ -321,6 +534,15 @@
 }
 
 
+/** Connect a radio button widget to the user callback function.  Set
+ *  the starting state of the radio button group from its value in
+ *  gconf.
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the radio button that should be
+ *  connected.
+ */
 static void
 gnc_prefs_connect_radio_button (GtkRadioButton *button)
 {
@@ -358,6 +580,16 @@
 
 /**********/
 
+
+/** The user clicked on a check button.  Update gconf.  Check button
+ *  choices are stored as a boolean
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the check button that was clicked.
+ *  
+ *  @param user_data Unused.
+ */
 static void
 gnc_prefs_check_button_user_cb (GtkCheckButton *button,
 				gpointer user_data)
@@ -373,6 +605,15 @@
 }
 
 
+/** A check button choice was updated in gconf.  Update the user
+ *  visible dialog.
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the check button that changed.
+ *
+ *  @param active The new state of the check button.
+ */
 static void
 gnc_prefs_check_button_gconf_cb (GtkCheckButton *button,
 				 gboolean active)
@@ -389,6 +630,14 @@
 }
 
 
+/** Connect a check button widget to the user callback function.  Set
+ *  the starting state of the button from its value in gconf.
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the check button that should be
+ *  connected.
+ */
 static void
 gnc_prefs_connect_check_button (GtkCheckButton *button)
 {
@@ -405,6 +654,16 @@
 
 /**********/
 
+
+/** The user updated a spin button.  Update gconf.  Spin button
+ *  choices are stored as a float.
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the spin button that was clicked.
+ *  
+ *  @param user_data Unused.
+ */
 static void
 gnc_prefs_spin_button_user_cb (GtkSpinButton *spin,
 			       gpointer user_data)
@@ -420,6 +679,15 @@
 }
 
 
+/** A spin button choice was updated in gconf.  Update the user
+ *  visible dialog.
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the spin button that changed.
+ *
+ *  @param value The new value of the spin button.
+ */
 static void
 gnc_prefs_spin_button_gconf_cb (GtkSpinButton *spin,
 				gdouble value)
@@ -436,6 +704,14 @@
 }
 
 
+/** Connect a spin button widget to the user callback function.  Set
+ *  the starting state of the button from its value in gconf.
+ *
+ *  @internal
+ *
+ *  @param button A pointer to the spin button that should be
+ *  connected.
+ */
 static void
 gnc_prefs_connect_spin_button (GtkSpinButton *spin)
 {
@@ -454,6 +730,16 @@
 
 /**********/
 
+
+/** The user changed a combo box.  Update gconf.  Combo box
+ *  choices are stored as an int.
+ *
+ *  @internal
+ *
+ *  @param box A pointer to the combo box that was changed.
+ *  
+ *  @param user_data Unused.
+ */
 static void
 gnc_prefs_combo_box_user_cb (GtkComboBox *box,
 			     gpointer user_data)
@@ -469,6 +755,15 @@
 }
 
 
+/** A combo box choice was updated in gconf.  Update the user
+ *  visible dialog.
+ *
+ *  @internal
+ *
+ *  @param box A pointer to the combo box that changed.
+ *
+ *  @param value The new value of the combo box.
+ */
 static void
 gnc_prefs_combo_box_gconf_cb (GtkComboBox *box,
 			      gint value)
@@ -485,6 +780,13 @@
 }
 
 
+/** Connect a combo box widget to the user callback function.  Set
+ *  the starting state of the box from its value in gconf.
+ *
+ *  @internal
+ *
+ *  @param box A pointer to the combo box that should be connected.
+ */
 static void
 gnc_prefs_connect_combo_box (GtkComboBox *box)
 {
@@ -505,24 +807,34 @@
 /*    Callbacks     */
 /********************/
 
+/** Handle a user click on one of the buttons at the bottom of the
+ *  preference dialog.  Also handles delete_window events, which have
+ *  conveniently converted to a response by GtkDialog.
+ *
+ *  @internal
+ *
+ *  @param dialog A pointer to the preferences dialog.
+ *
+ *  @param response Indicates which button was pressed by the user.
+ *  The only expected values are HELP, CLOSE, and DELETE_EVENT.
+ *
+ *  @param unused
+ */
 void
 gnc_preferences_response_cb(GtkDialog *dialog, gint response, GtkDialog *unused)
 {
   switch (response) {
    case GTK_RESPONSE_HELP:
      gnc_gnome_help(HF_CUSTOM, HL_GLOBPREFS);
-    break;
+     break;
 
-   case GTK_RESPONSE_CLOSE:
+   default:
      gnc_save_window_size(GCONF_SECTION, GTK_WINDOW(dialog));
      gnc_unregister_gui_component_by_data(DIALOG_PREFERENCES_CM_CLASS,
 					  dialog);
      gnc_gconf_remove_notification(G_OBJECT(dialog), NULL);
      gtk_widget_destroy(GTK_WIDGET(dialog));
      break;
-
-   default:
-     break;
   }
 }
 
@@ -530,51 +842,82 @@
 /*    Creation      */
 /********************/
 
+
+/** Connect one dialog widget to the appropriate callback function for
+ *  its type.
+ *
+ *  @internal
+ *
+ *  @param name The name of the widget.
+ *
+ *  @param widget A pointer to the widget.
+ *
+ *  @param dialog A pointer to the dialog.
+ */
+static void
+gnc_prefs_connect_one (const gchar *name,
+		       GtkWidget *widget,
+		       gpointer user_data)
+{
+  if (GTK_IS_RADIO_BUTTON(widget)) {
+    DEBUG("  %s - radio button", name);
+    gnc_prefs_connect_radio_button(GTK_RADIO_BUTTON(widget));
+  } else if (GTK_IS_CHECK_BUTTON(widget)) {
+    DEBUG("  %s - check button", name);
+    gnc_prefs_connect_check_button(GTK_CHECK_BUTTON(widget));
+  } else if (GTK_IS_SPIN_BUTTON(widget)) {
+    DEBUG("  %s - spin button", name);
+    gnc_prefs_connect_spin_button(GTK_SPIN_BUTTON(widget));
+  } else if (GTK_IS_COMBO_BOX(widget)) {
+    DEBUG("  %s - combo box", name);
+    gnc_prefs_connect_combo_box(GTK_COMBO_BOX(widget));
+  } else {
+    DEBUG("  %s - unsupported %s", name,
+	  G_OBJECT_TYPE_NAME(G_OBJECT(widget)));
+  }
+}
+
+
+/** Create the preferences dialog.  This function first reads the
+ *  preferences.glade file to get obtain the dialog and set of common
+ *  preferences.  It then runs the list of add-ins, calling a helper
+ *  function to add each full/partial page to this dialog, Finally it
+ *  runs the list of "interesting: widgets that it has built,
+ *  connecting this widgets up to callback functions.
+ *
+ *  @internal
+ *
+ *  @return A pointer to the newly created dialog.
+ */
 static GtkWidget *
 gnc_preferences_dialog_create(void)
 {
   GladeXML *xml;
-  GtkWidget *dialog, *notebook, *widget;
-  struct page_data page_data;
-  GList *interesting, *runner;
+  GtkWidget *dialog, *notebook;
+  GHashTable *table;
 
   ENTER("");
   DEBUG("Opening preferences.glade:");
   xml = gnc_glade_xml_new("preferences.glade", "New Gnucash Preferences");
   dialog = glade_xml_get_widget(xml, "New Gnucash Preferences");
-  g_object_set_data(G_OBJECT(dialog), "xml", xml);
   DEBUG("autoconnect");
   glade_xml_signal_autoconnect_full(xml, gnc_glade_autoconnect_full_func,
 				    dialog);
   DEBUG("done");
 
   notebook = glade_xml_get_widget(xml, "notebook1");
-  interesting = glade_xml_get_widget_prefix(xml, "gconf");
+  table = g_hash_table_new(g_str_hash, g_str_equal);
+  g_object_set_data(G_OBJECT(dialog), NOTEBOOK, notebook);
+  g_object_set_data_full(G_OBJECT(dialog), WIDGET_HASH,
+			 table, (GDestroyNotify)g_hash_table_destroy);
 
-  page_data.notebook = GTK_NOTEBOOK(notebook);
-  page_data.widgets = interesting;
-  g_slist_foreach(add_ins, gnc_preferences_build_page, &page_data);
+  /* Add to the list of interesting widgets */
+  gnc_prefs_build_widget_table(xml, dialog);
+
+  g_slist_foreach(add_ins, gnc_preferences_build_page, dialog);
 
   DEBUG("We have the following interesting widgets:");
-  for (runner = page_data.widgets; runner; runner = g_list_next(runner)) {
-    widget = GTK_WIDGET(runner->data);
-    if (GTK_IS_RADIO_BUTTON(widget)) {
-      DEBUG("  %s - radio button", gtk_widget_get_name(widget));
-      gnc_prefs_connect_radio_button(GTK_RADIO_BUTTON(widget));
-    } else if (GTK_IS_CHECK_BUTTON(widget)) {
-      DEBUG("  %s - check button", gtk_widget_get_name(widget));
-      gnc_prefs_connect_check_button(GTK_CHECK_BUTTON(widget));
-    } else if (GTK_IS_SPIN_BUTTON(widget)) {
-      DEBUG("  %s - spin button", gtk_widget_get_name(widget));
-      gnc_prefs_connect_spin_button(GTK_SPIN_BUTTON(widget));
-    } else if (GTK_IS_COMBO_BOX(widget)) {
-      DEBUG("  %s - combo box", gtk_widget_get_name(widget));
-      gnc_prefs_connect_combo_box(GTK_COMBO_BOX(widget));
-    } else {
-      DEBUG("  %s - unsupported %s", gtk_widget_get_name(widget),
-	    G_OBJECT_TYPE_NAME(G_OBJECT(widget)));
-    }
-  }
+  g_hash_table_foreach(table, (GHFunc)gnc_prefs_connect_one, dialog);
   DEBUG("Done with interesting widgets.");
 
   LEAVE("dialog %p", dialog);
@@ -582,6 +925,54 @@
 }
 
 
+/*************************************/
+/*    GConf common callback code     */
+/*************************************/
+
+
+/** Find a partial match from gconf key to widget name.  This function
+ *  is needed if the user manually updates the value of a radio button
+ *  setting (a string) and types in an illegal value.  This function
+ *  is called on all the "interesting" widgets in the dialog until
+ *  there is a match.  This matched widget represents a legal value
+ *  for the radio button.  The calling function can then force this
+ *  radio button to be "on", thus insuring that Gnucash always has a
+ *  legal value for the radio group.
+ *
+ *  @internal
+ *
+ *  @param key The name of a widget.
+ *
+ *  @param widget A pointer to the widget.
+ *
+ *  @param user_data The name of the gconf key that was changed.
+ *
+ *  @return Zero if the gconf key is a sublse of this button's
+ *  name. Non-zero otherwise.
+ */
+static gboolean
+gnc_prefs_nearest_match (gpointer key,
+			 gpointer value,
+			 gpointer user_data)
+{
+  const gchar *widget_name = key;
+  const gchar *gconf_name = user_data;
+
+  return (strncmp(widget_name, gconf_name, strlen(gconf_name)) == 0);
+}
+
+
+/** Create the preferences dialog.  This function first reads the
+ *  preferences.glade file to get obtain the dialog and set of common
+ *  preferences.  It then runs the list of add-ins, calling a helper
+ *  function to add each full/partial page to this dialog, Finally it
+ *  runs the list of "interesting: widgets that it has built,
+ *  connecting this widgets up to callback functions.
+ *
+ *  @internal
+ *
+ *  @return A pointer to the newly created dialog.
+ */
 static void
 gnc_preferences_gconf_changed (GConfClient *client,
 			       guint cnxn_id,
@@ -592,8 +983,7 @@
   const gchar *key, *string_value;
   gchar  **parts, *name, *group_name = NULL;
   GtkWidget *widget;
-  GList *possibilities;
-  GladeXML *xml;
+  GHashTable *table;
 
   ENTER("key %s, value %p", entry->key, entry->value);
   key = gconf_entry_get_key(entry);
@@ -609,13 +999,16 @@
   g_strfreev(parts);
   DEBUG("proposed widget name %s", name);
 
-  widget = gnc_glade_lookup_widget(dialog, name);
+  /* Can't just do a glade lookup here because not all of the widgets
+   * came from the same xml file. That's why the extra hash table. */
+  table = g_object_get_data(G_OBJECT(dialog), WIDGET_HASH);
+  widget = g_hash_table_lookup(table, name);
   if ((widget == NULL) && (entry->value->type == GCONF_VALUE_STRING)) {
     string_value = gconf_value_get_string(entry->value);
     group_name = name;
     name = g_strjoin("/", group_name, string_value, NULL);
     DEBUG("proposed widget name %s", name);
-    widget = gnc_glade_lookup_widget(dialog, name);
+    widget = g_hash_table_lookup(table, name);
     if (widget == NULL) {
       /* Mutter, mutter. Someone must have typed a bad string into
        * gconf.  Force the value to a legal string.  Do this by
@@ -623,13 +1016,15 @@
        * ensure synchronization of Gnucash, Gconf, and the Prefs
        * Dialog. */
       DEBUG("bad value");
-      xml = g_object_get_data(G_OBJECT(dialog), "xml");
-      possibilities = glade_xml_get_widget_prefix(xml, group_name);
-      if (possibilities) {
-	DEBUG("forcing %s", gtk_widget_get_name(possibilities->data));
-	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(possibilities->data), TRUE);
-	g_list_free(possibilities);
+      widget = g_hash_table_find(table, gnc_prefs_nearest_match, group_name);
+      if (widget) {
+	DEBUG("forcing %s", gtk_widget_get_name(widget));
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
       }
+      g_free(group_name);
+      g_free(name);
+      LEAVE("no exact match");
+      return;
     }
     g_free(group_name);
   }
@@ -660,6 +1055,20 @@
 }
 
 
+/** Raise the preferences dialog to the top of the window stack.  This
+ *  function is called if the user attempts to create a second
+ *  preferences dialog.
+ *
+ *  @internal
+ *
+ *  @param class Unused.
+ *
+ *  @param component_id Unused.
+ *
+ *  @param user_data A pointer to the preferences dialog.
+ *
+ *  @param iter_data Unused.
+ */
 static gboolean
 show_handler (const char *class, gint component_id,
 	      gpointer user_data, gpointer iter_data)
@@ -674,6 +1083,12 @@
 }
 
 
+/** Close the preferences dialog.
+ *
+ *  @internal
+ *
+ *  @param user_data A pointer to the preferences dialog.
+ */
 static void
 close_handler (gpointer user_data)
 {
@@ -687,6 +1102,10 @@
 }
 
 
+/*  This function creates the preferences dialog and presents it to
+ *  the user.  The preferences dialog is a singletone, so if a
+ *  preferences dialog already exists it will be raised to the top of
+ *  the window stack instead of creating a new dialog. */
 void
 gnc_preferences_dialog (void)
 {
@@ -709,3 +1128,5 @@
 			     NULL, close_handler, dialog);
   LEAVE(" ");
 }
+
+/** @} */


More information about the gnucash-changes mailing list