Added feature to make OK and PASTE buttons insensitive until the dialog box
authorJohn Darrington <john@darrington.wattle.id.au>
Sun, 7 Oct 2007 00:25:03 +0000 (00:25 +0000)
committerJohn Darrington <john@darrington.wattle.id.au>
Sun, 7 Oct 2007 00:25:03 +0000 (00:25 +0000)
contains valid data.

src/ui/gui/ChangeLog
src/ui/gui/compute-dialog.c
src/ui/gui/descriptives-dialog.c
src/ui/gui/psppire-buttonbox.c
src/ui/gui/psppire-dialog.c
src/ui/gui/psppire-dialog.h

index 74fc10fea5a90f9437f94a0ee6f17411ca41b51a..c0c4d28388d6cd488e77c3f092c1ed0e14b1c4c8 100644 (file)
@@ -1,3 +1,17 @@
+2007-10-06  John Darrington <john@darrington.wattle.id.au>
+       
+       * psppire-dialog.c psppire-dialog.h: Added a predicate function
+       member to indicate when a dialog's state is (not) valid. Added a
+       signal "validity-changed" which gets emitted whenever this
+       predicate changes. 
+
+       * psppire-buttonbox.c: Connect to the toplevel window's
+       "validity-changed" signal (assuming it happens to be a
+       PsppireDialog) and set the OK, PASTE, GOTO and CONTINUE buttons
+       according.y. 
+
+       * descriptives-dialog.c compute-dialog.c: Add a validity predicate.
+
 2007-10-04  John Darrington <john@darrington.wattle.id.au>
 
        * compute-dialog.c goto-case-dialog.c main.c psppire-keypad.c: Added 
index 9d3e0b4671b35dc08f7c02d7a0838f17a99169c8..3386680d35163a2ad6d107e8b3c018402dad9f21 100644 (file)
@@ -327,6 +327,28 @@ on_expression_toggle (GtkToggleButton *button, gpointer data)
     }
 }
 
+
+/* Return TRUE if the dialog box's widgets' state are such that clicking OK
+   might not result in erroneous syntax being generated */
+static gboolean
+contents_plausible (gpointer data)
+{
+  struct compute_dialog *cd = data;
+
+  GtkWidget *target      = get_widget_assert (cd->xml, "compute-entry1");
+  GtkWidget *syntax_area = get_widget_assert (cd->xml, "compute-textview1");
+  GtkTextBuffer *buffer  =
+    gtk_text_view_get_buffer (GTK_TEXT_VIEW (syntax_area));
+
+  if ( 0 == strcmp ("", gtk_entry_get_text (GTK_ENTRY (target))))
+    return FALSE;
+
+  if ( gtk_text_buffer_get_char_count (buffer) == 0 )
+    return FALSE;
+
+  return TRUE;
+}
+
 /* Pops up the Compute dialog box */
 void
 compute_dialog (GObject *o, gpointer data)
@@ -359,6 +381,7 @@ compute_dialog (GObject *o, gpointer data)
 
   vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
 
+
   scd.dict = vs->dict;
   scd.use_type = FALSE;
 
@@ -389,6 +412,9 @@ compute_dialog (GObject *o, gpointer data)
 
   scd.xml = xml;
 
+  psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (dialog),
+                                     contents_plausible, &scd);
+
   g_signal_connect (target, "changed", G_CALLBACK (on_target_change), &scd);
 
   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &scd);
index 5ed92d9de90c985d5ee10d760cbad366dd6ee874..5a9a2b73c2c4be1f470b5b4cae0e657eb84dc877 100644 (file)
@@ -287,6 +287,20 @@ put_statistics_in_treeview (GtkTreeView *treeview)
   gtk_tree_view_append_column (treeview, col);
 }
 
+
+/* Dialog is valid iff at least one variable has been selected */
+static gboolean
+dialog_state_valid (gpointer data)
+{
+  struct descriptives_dialog *dd = data;
+
+  GtkTreeModel *vars = gtk_tree_view_get_model (dd->stat_vars);
+
+  GtkTreeIter notused;
+
+  return gtk_tree_model_get_iter_first (vars, &notused);
+}
+
 /* Pops up the Descriptives dialog box */
 void
 descriptives_dialog (GObject *o, gpointer data)
@@ -340,6 +354,9 @@ descriptives_dialog (GObject *o, gpointer data)
 
   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &scd);
 
+  psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (dialog),
+                                     dialog_state_valid, &scd);
+
   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
 
 
index da39e0867d3968fbf927fb6c47cafdaf634aad20..4552210729df4dd15243d30c47fcd497f567eee9 100644 (file)
@@ -225,6 +225,31 @@ refresh_clicked (GtkWidget *w, gpointer data)
 }
 
 
+
+static void
+on_validity_change (GtkWidget *toplevel, gboolean valid, gpointer data)
+{
+  PsppireButtonBox *bb = data;
+
+  /* Set the sensitivity of all the 'executive order' buttons */
+  gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_OK]), valid);
+  gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_PASTE]), valid);
+  gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_GOTO]), valid);
+  gtk_widget_set_sensitive (GTK_WIDGET (bb->button[PSPPIRE_BUTTON_CONTINUE]), valid);
+}
+
+static void
+on_realize (GtkWidget *buttonbox, gpointer data)
+{
+  GtkWidget *toplevel = gtk_widget_get_toplevel (buttonbox);
+
+  if ( PSPPIRE_IS_DIALOG (toplevel))
+    {
+      g_signal_connect (toplevel, "validity-changed",
+                       G_CALLBACK (on_validity_change), buttonbox);
+    }
+}
+
 static void
 psppire_button_box_init (PsppireButtonBox *bb)
 {
@@ -299,6 +324,8 @@ psppire_button_box_init (PsppireButtonBox *bb)
     g_value_unset (&value);
   }
 
+
+  g_signal_connect (bb, "realize", G_CALLBACK (on_realize), NULL);
 }
 
 
index d961c7dcf02c48c559c6da074ff4d24980833c20..90080094f8e2e5d4d4a22ea8ce1a09862abcca96 100644 (file)
 #include <gtk/gtk.h>
 #include <gtk/gtksignal.h>
 #include "psppire-dialog.h"
+#include "psppire-buttonbox.h"
+#include "psppire-selector.h"
 
 static void psppire_dialog_class_init          (PsppireDialogClass *);
 static void psppire_dialog_init                (PsppireDialog      *);
 
 
 enum  {DIALOG_REFRESH,
+       VALIDITY_CHANGED,
        n_SIGNALS};
 
 static guint signals [n_SIGNALS];
@@ -193,6 +196,18 @@ psppire_dialog_class_init (PsppireDialogClass *class)
                  0);
 
 
+  signals [VALIDITY_CHANGED] =
+    g_signal_new ("validity-changed",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__BOOLEAN,
+                 G_TYPE_NONE,
+                 1,
+                 G_TYPE_BOOLEAN);
+
+
   object_class->finalize = psppire_dialog_finalize;
 }
 
@@ -227,6 +242,8 @@ psppire_dialog_init (PsppireDialog *dialog)
 {
   GValue value = {0};
   dialog->box = NULL;
+  dialog->contents_are_valid = NULL;
+  dialog->validity_data = NULL;
 
   g_value_init (&value, orientation_spec->value_type);
   g_param_value_set_default (orientation_spec, &value);
@@ -259,13 +276,121 @@ psppire_dialog_new (void)
   return GTK_WIDGET (dialog) ;
 }
 
+
+static void
+notify_change (PsppireDialog *dialog)
+{
+  if ( dialog->contents_are_valid )
+    {
+      gboolean valid = dialog->contents_are_valid (dialog->validity_data);
+
+      g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, valid);
+    }
+}
+
+
+/* Descend the widget tree, connecting appropriate signals to the
+   notify_change callback */
+static void
+connect_notify_signal (GtkWidget *w, gpointer data)
+{
+  PsppireDialog *dialog = data;
+
+  if ( PSPPIRE_IS_BUTTONBOX (w))
+    return;
+
+
+
+  if ( GTK_IS_CONTAINER (w))
+    {
+      gtk_container_foreach (GTK_CONTAINER (w),
+                            connect_notify_signal,
+                            dialog);
+    }
+
+
+  /* It's unfortunate that GTK+ doesn't have a generic
+     "user-modified-state-changed" signal.  Instead, we have to try and
+     predict what widgets and signals are likely to exist in our dialogs. */
+
+  if ( GTK_IS_TOGGLE_BUTTON (w))
+    {
+      g_signal_connect_swapped (w, "toggled", G_CALLBACK (notify_change),
+                               dialog);
+    }
+
+  if ( PSPPIRE_IS_SELECTOR (w))
+    {
+      g_signal_connect_swapped (w, "selected", G_CALLBACK (notify_change),
+                               dialog);
+
+      g_signal_connect_swapped (w, "de-selected", G_CALLBACK (notify_change),
+                               dialog);
+    }
+
+  if ( GTK_IS_EDITABLE (w))
+    {
+      g_signal_connect_swapped (w, "changed", G_CALLBACK (notify_change),
+                               dialog);
+    }
+
+  if ( GTK_IS_CELL_EDITABLE (w))
+    {
+      g_signal_connect_swapped (w, "editing-done", G_CALLBACK (notify_change),
+                               dialog);
+    }
+
+  if ( GTK_IS_TEXT_VIEW (w))
+    {
+      GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
+
+      g_signal_connect_swapped (buffer, "changed", G_CALLBACK (notify_change),
+                               dialog);
+    }
+
+  if ( GTK_IS_TREE_VIEW (w))
+    {
+      gint i = 0;
+      GtkTreeView *tv = GTK_TREE_VIEW (w);
+      GtkTreeSelection *selection =
+       gtk_tree_view_get_selection (tv);
+      GtkTreeViewColumn *col;
+
+      g_signal_connect_swapped (selection, "changed",
+                               G_CALLBACK (notify_change), dialog);
+
+      while ((col = gtk_tree_view_get_column (tv, i++)))
+       {
+         GList *renderers = gtk_tree_view_column_get_cell_renderers (col);
+         GList *start = renderers;
+         while (renderers)
+           {
+             if ( GTK_IS_CELL_RENDERER_TOGGLE (renderers->data))
+               g_signal_connect_swapped (renderers->data, "toggled",
+                                         G_CALLBACK (notify_change), dialog);
+             renderers = renderers->next;
+           }
+         g_list_free (start);
+       }
+    }
+}
+
+
 gint
 psppire_dialog_run (PsppireDialog *dialog)
 {
+  if ( dialog->contents_are_valid != NULL )
+    gtk_container_foreach (GTK_CONTAINER (dialog->box),
+                          connect_notify_signal,
+                          dialog);
+
   dialog->loop = g_main_loop_new (NULL, FALSE);
 
   gtk_widget_show (GTK_WIDGET (dialog));
 
+  if ( dialog->contents_are_valid != NULL)
+    g_signal_emit (dialog, signals [VALIDITY_CHANGED], 0, FALSE);
+
   g_signal_emit (dialog, signals [DIALOG_REFRESH], 0);
 
   g_main_loop_run (dialog->loop);
@@ -302,3 +427,15 @@ psppire_orientation_get_type (void)
     }
   return etype;
 }
+
+
+void
+psppire_dialog_set_valid_predicate (PsppireDialog *dialog,
+                                   ContentsAreValid contents_are_valid,
+                                   gpointer data)
+{
+  dialog->contents_are_valid = contents_are_valid;
+  dialog->validity_data = data;
+}
+
+
index 7f064e94b76475e51c838b9445c8c6b92fddc20a..bd51a4c40da9b4ec3ebf963526121f09453a627a 100644 (file)
@@ -40,6 +40,9 @@ G_BEGIN_DECLS
 typedef struct _PsppireDialog       PsppireDialog;
 typedef struct _PsppireDialogClass  PsppireDialogClass;
 
+typedef gboolean (*ContentsAreValid) (gpointer);
+
+
 struct _PsppireDialog
 {
   GtkWindow window;
@@ -48,6 +51,9 @@ struct _PsppireDialog
   /* Private */
   GMainLoop *loop;
   gint response;
+
+  ContentsAreValid contents_are_valid;
+  gpointer validity_data;
 };
 
 struct _PsppireDialogClass
@@ -55,11 +61,15 @@ struct _PsppireDialogClass
   GtkWindowClass parent_class;
 };
 
+
 GType          psppire_dialog_get_type        (void);
 GtkWidget*     psppire_dialog_new             (void);
 void           psppire_dialog_reload          (PsppireDialog *);
 void           psppire_dialog_close           (PsppireDialog *);
 gint           psppire_dialog_run             (PsppireDialog *);
+void           psppire_dialog_set_valid_predicate (PsppireDialog *,
+                                                  ContentsAreValid,
+                                                  gpointer );
 
 
 GType psppire_orientation_get_type (void);