Fail more gracefully when selecting cells out of range
[pspp] / src / ui / gui / psppire-data-editor.c
index 9c8f3aa93fad4204ad5c4d50bb79d469d2181737..80bf587695598398093df776f37dcaa401497188 100644 (file)
 #include "libpspp/range-set.h"
 #include "libpspp/str.h"
 #include "ui/gui/helper.h"
-#include "ui/gui/pspp-sheet-selection.h"
 #include "ui/gui/psppire-data-store.h"
 #include "ui/gui/psppire-value-entry.h"
-#include "ui/gui/psppire-var-sheet.h"
 #include "ui/gui/psppire-conf.h"
+#include "ui/gui/psppire-var-sheet-header.h"
 
 #include "ui/gui/efficient-sheet/jmd-sheet.h"
 
 #define _(msgid) gettext (msgid)
 
 
+static GtkCellRenderer *
+create_spin_renderer (GType type)
+{
+  GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
+      
+  GtkAdjustment *adj = gtk_adjustment_new (0,
+                                          0, G_MAXDOUBLE,
+                                          1, 1,
+                                          0);
+  g_object_set (r,
+               "adjustment", adj,
+               NULL);
+  
+  return r;
+}
+
+static GtkCellRenderer *
+create_combo_renderer (GType type)
+{
+  GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+  
+  GEnumClass *ec = g_type_class_ref (type);
+  
+  const GEnumValue *ev ;
+  for (ev = ec->values; ev->value_name; ++ev)
+    {
+      GtkTreeIter iter;
+
+      gtk_list_store_append (list_store, &iter);
+
+      gtk_list_store_set (list_store, &iter,
+                         0, ev->value,
+                         1, gettext (ev->value_nick),
+                         -1);
+    }
+
+  GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
+
+  g_object_set (r,
+               "model", list_store,
+               "text-column", 1,
+               "has-entry", TRUE,
+               NULL);
+
+  return r;
+}
+
+GtkCellRenderer *xx ;
+GtkCellRenderer *column_width_renderer ;
+GtkCellRenderer *measure_renderer ;
+GtkCellRenderer *alignment_renderer ;
+
+
+
+static GtkCellRenderer *
+select_renderer_func (gint col, gint row, GType type)
+{
+  if (!xx)
+    xx = create_spin_renderer (type);
+
+  if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
+    column_width_renderer = create_combo_renderer (type);
+
+  if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
+    measure_renderer = create_combo_renderer (type);
+
+  if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
+    alignment_renderer = create_combo_renderer (type);
+
+  switch  (col)
+    {
+    case DICT_TVM_COL_WIDTH:
+    case DICT_TVM_COL_DECIMAL:
+    case DICT_TVM_COL_COLUMNS:
+      return xx;
+      
+    case DICT_TVM_COL_ALIGNMENT:
+      return alignment_renderer;
+
+    case DICT_TVM_COL_MEASURE:
+      return measure_renderer;
+      
+    case DICT_TVM_COL_ROLE:
+      return column_width_renderer;
+    }
+  
+  return NULL;
+}
+
+
 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
 static void psppire_data_editor_init                (PsppireDataEditor      *de);
 
 static void disconnect_data_sheets (PsppireDataEditor *);
 static void refresh_entry (PsppireDataEditor *);
-static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
 
 GType
 psppire_data_editor_get_type (void)
@@ -99,12 +187,6 @@ psppire_data_editor_dispose (GObject *obj)
       de->font = NULL;
     }
 
-  if (de->ui_manager)
-    {
-      g_object_unref (de->ui_manager);
-      de->ui_manager = NULL;
-    }
-
   /* Chain up to the parent class */
   G_OBJECT_CLASS (parent_class)->dispose (obj);
 }
@@ -115,17 +197,61 @@ enum
     PROP_DATA_STORE,
     PROP_DICTIONARY,
     PROP_VALUE_LABELS,
-    PROP_SPLIT_WINDOW,
-    PROP_UI_MANAGER
+    PROP_SPLIT_WINDOW
   };
 
 static void
 psppire_data_editor_refresh_model (PsppireDataEditor *de)
 {
-  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
-    int i;
+}
 
-  psppire_var_sheet_set_dictionary (var_sheet, de->dict);
+static void
+change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
+{
+  /* Return the IDXth variable */
+  struct variable *var =  psppire_dict_get_variable (dict, row);
+
+  switch (col)
+    {
+    case DICT_TVM_COL_NAME:
+      dict_rename_var (dict->dict, var, g_value_get_string (value));
+      break;
+    case DICT_TVM_COL_LABEL:
+      var_set_label (var, g_value_get_string (value));
+      break;
+    case DICT_TVM_COL_COLUMNS:
+      var_set_display_width (var, g_value_get_int (value));
+      break;
+    case DICT_TVM_COL_MEASURE:
+      var_set_measure (var, g_value_get_enum (value));
+      break;
+    case DICT_TVM_COL_ALIGNMENT:
+      var_set_alignment (var, g_value_get_enum (value));
+      break;
+    case DICT_TVM_COL_ROLE:
+      var_set_role (var, g_value_get_enum (value));
+      break;
+    default:
+      g_message ("Changing col %d of var sheet not yet supported", col);
+      break;
+    }
+}
+
+static void
+change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
+{
+  const struct variable *var = psppire_dict_get_variable (store->dict, col);
+
+  if (NULL == var)
+    return;
+
+  union value v;
+  value_init (&v, var_get_width (var));
+  v.f = g_value_get_double (value);
+  
+  psppire_data_store_set_value (store, row, var, &v);
+
+  value_destroy (&v, var_get_width (var));
 }
 
 static void
@@ -136,8 +262,6 @@ psppire_data_editor_set_property (GObject         *object,
 {
   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
 
-  int i;
-
   switch (prop_id)
     {
     case PROP_SPLIT_WINDOW:
@@ -159,6 +283,9 @@ psppire_data_editor_set_property (GObject         *object,
       g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
       psppire_data_editor_refresh_model (de);
 
+      g_signal_connect_swapped (de->data_sheet, "value-changed",
+                               G_CALLBACK (change_data_value), de->data_store);
+      
       g_signal_connect_swapped (de->data_store, "case-changed",
                                 G_CALLBACK (refresh_entry), de);
 
@@ -170,13 +297,14 @@ psppire_data_editor_set_property (GObject         *object,
       g_object_ref (de->dict);
 
       g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
+      g_object_set (de->var_sheet, "data-model", de->dict, NULL);
+      g_signal_connect_swapped (de->var_sheet, "value-changed",
+                               G_CALLBACK (change_var_property), de->dict);
 
-      psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
-                                        de->dict);
       break;
     case PROP_VALUE_LABELS:
       break;
-    case PROP_UI_MANAGER:
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -204,9 +332,6 @@ psppire_data_editor_get_property (GObject         *object,
       break;
     case PROP_VALUE_LABELS:
       break;
-    case PROP_UI_MANAGER:
-      g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
-      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -219,7 +344,7 @@ psppire_data_editor_switch_page (GtkNotebook     *notebook,
                                  guint            page_num)
 {
   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
-  psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
+
 }
 
 static void
@@ -227,7 +352,7 @@ psppire_data_editor_set_focus_child (GtkContainer *container,
                                      GtkWidget    *widget)
 {
   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
-  psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
+
 }
 
 static void
@@ -237,7 +362,7 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
   GParamSpec *dict_spec ;
   GParamSpec *value_labels_spec;
   GParamSpec *split_window_spec;
-  GParamSpec *ui_manager_spec;
+
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
@@ -295,48 +420,39 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    PROP_SPLIT_WINDOW,
                                    split_window_spec);
 
-  ui_manager_spec =
-    g_param_spec_object ("ui-manager",
-                         "UI Manager",
-                         "UI manager for the active notebook tab.  The client should merge this UI manager with the active UI manager to obtain menu items and tool bar items specific to the active notebook tab.",
-                         GTK_TYPE_UI_MANAGER,
-                         G_PARAM_READABLE);
-  g_object_class_install_property (object_class,
-                                   PROP_UI_MANAGER,
-                                   ui_manager_spec);
 }
 
-static gboolean
-on_data_sheet_var_double_clicked (GtkWidget *data_sheet,
-                                  gint dict_index,
-                                  PsppireDataEditor *de)
+
+static void
+on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
+                                 PsppireDataEditor *de)
 {
   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
-                                 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
-
-  psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
-                                   dict_index);
+                                 PSPPIRE_DATA_EDITOR_DATA_VIEW);
 
-  return TRUE;
+  jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
 }
 
-static gboolean
-on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
+
+static void
+on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
                                  PsppireDataEditor *de)
 {
-
+  
   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
-                                 PSPPIRE_DATA_EDITOR_DATA_VIEW);
-
+                                 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
 
-  return TRUE;
+  jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
 }
 
+
+
 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
    active cell or cells. */
 static void
 refresh_entry (PsppireDataEditor *de)
 {
+  g_print ("%s\n", __FUNCTION__);
 }
 
 static void
@@ -344,27 +460,53 @@ on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
 {
 }
 
+
 static void
-on_data_sheet_selection_changed (PsppSheetSelection *selection,
-                                 PsppireDataEditor *de)
+disconnect_data_sheets (PsppireDataEditor *de)
 {
-  /* In a split view, ensure that only a single data sheet has a nonempty
-     selection.  */
-  if (de->split
-      && pspp_sheet_selection_count_selected_rows (selection)
-      && pspp_sheet_selection_count_selected_columns (selection))
-    {
-    }
-
-  refresh_entry (de);
 }
 
-
+/* Called when the active cell or the selection in the data sheet changes */
 static void
-disconnect_data_sheets (PsppireDataEditor *de)
+on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
 {
-  int i;
+  gchar *ref_cell_text = NULL;
 
+  gint n_cases = abs (sel->end_y - sel->start_y) + 1;
+  gint n_vars = abs (sel->end_x - sel->start_x) + 1;
+
+  if (n_cases == 1 && n_vars == 1)
+    {
+      /* A single cell is selected */
+      const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
+
+      if (var)
+       ref_cell_text = g_strdup_printf (_("%d : %s"),
+                                        sel->start_y + 1, var_get_name (var));
+    }
+  else
+    {
+      struct string s;
+
+      /* The glib string library does not understand the ' printf modifier
+        on all platforms, but the "struct string" library does (because
+        Gnulib fixes that problem), so use the latter.  */
+      ds_init_empty (&s);
+      ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
+                    n_cases);
+      ds_put_byte (&s, ' ');
+      ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
+      ds_put_byte (&s, ' ');
+      ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
+                                  n_vars),
+                    n_vars);
+      ref_cell_text = ds_steal_cstr (&s);
+    }
+  
+  gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
+                      ref_cell_text ? ref_cell_text : "");
+  
+  g_free (ref_cell_text);
 }
 
 
@@ -373,12 +515,10 @@ static void set_font_recursively (GtkWidget *w, gpointer data);
 static void
 psppire_data_editor_init (PsppireDataEditor *de)
 {
-  GtkWidget *var_sheet_scroller;
   GtkWidget *hbox;
   gchar *fontname = NULL;
 
   de->font = NULL;
-  de->ui_manager = NULL;
   de->old_vbox_widget = NULL;
 
   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
@@ -396,35 +536,46 @@ psppire_data_editor_init (PsppireDataEditor *de)
   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
 
   de->split = FALSE;
-  de->data_sheet = g_object_new (JMD_TYPE_SHEET,
-                                NULL);
-  GtkWidget *button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
-  gtk_button_set_label (GTK_BUTTON (button), _("Case"));
+  de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
+  GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
+  gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
 
+
+  g_signal_connect_swapped (de->data_sheet, "selection-changed",
+                   G_CALLBACK (on_data_selection_change), de);
+  
   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
                            gtk_label_new_with_mnemonic (_("Data View")));
 
   gtk_widget_show_all (de->vbox);
 
-  de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
+  de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
 
-  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
-                                  GTK_TREE_VIEW_GRID_LINES_BOTH);
-  var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
-  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
-                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-  gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
-  gtk_widget_show_all (var_sheet_scroller);
+  PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
+  
+  g_object_set (de->var_sheet,
+               "hmodel", vsh,
+               "select-renderer-func", select_renderer_func,
+               NULL);
 
-  gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
+  
+  GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
+  gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
+  
+  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
                            gtk_label_new_with_mnemonic (_("Variable View")));
 
-  g_signal_connect (de->var_sheet, "var-double-clicked",
+  gtk_widget_show_all (de->var_sheet);
+  
+  g_signal_connect (de->var_sheet, "row-header-double-clicked",
                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
 
+  g_signal_connect (de->data_sheet, "column-header-double-clicked",
+                    G_CALLBACK (on_data_sheet_var_double_clicked), de);
+
   g_object_set (de, "can-focus", FALSE, NULL);
 
   if (psppire_conf_get_string (psppire_conf_new (),
@@ -436,7 +587,6 @@ psppire_data_editor_init (PsppireDataEditor *de)
       set_font_recursively (GTK_WIDGET (de), de->font);
     }
 
-  psppire_data_editor_update_ui_manager (de);
 }
 
 GtkWidget*
@@ -454,14 +604,8 @@ psppire_data_editor_new (PsppireDict *dict,
 void
 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
 {
-  GtkTreeViewGridLines grid;
-  int i;
-
-  grid = (grid_visible
-          ? GTK_TREE_VIEW_GRID_LINES_BOTH
-          : GTK_TREE_VIEW_GRID_LINES_NONE);
-
-  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
+  g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
+  g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
 }
 
 
@@ -499,14 +643,9 @@ psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_
 void
 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
 {
-  GtkTreeViewGridLines grid_lines;
-  gboolean labels;
-
   if (split == de->split)
     return;
 
-
-
   disconnect_data_sheets (de);
 
   psppire_data_editor_refresh_model (de);
@@ -518,7 +657,6 @@ psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
 
   de->split = split;
   g_object_notify (G_OBJECT (de), "split");
-  psppire_data_editor_update_ui_manager (de);
 }
 
 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
@@ -526,63 +664,42 @@ psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
 void
 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
 {
-
-  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
-    {
-
-    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
-                                       dict_index);
-      break;
-    }
 }
 
-
-/* Returns the UI manager that should be merged into DE's toplevel widget's UI
-   manager to display menu items and toolbar items specific to DE's current
-   page and data sheet.
-
-   DE's toplevel widget can watch for changes by connecting to DE's
-   notify::ui-manager signal. */
-GtkUIManager *
-psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
-{
-  psppire_data_editor_update_ui_manager (de);
-  return de->ui_manager;
-}
-
-static void
-psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
+#if SHEET_MERGE
+/* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
+   is the only data sheet.  If DE is in split mode (showing four data sheets),
+   this is the focused data sheet or, if none is focused, the data sheet with
+   selected cells or, if none has selected cells, the upper-left data sheet. */
+PsppireDataSheet *
+psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
 {
-  GtkUIManager *ui_manager;
-
-  ui_manager = NULL;
-
-  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
+  if (de->split)
     {
-    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-      break;
-
-    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      ui_manager = psppire_var_sheet_get_ui_manager (
-        PSPPIRE_VAR_SHEET (de->var_sheet));
-      break;
+      PsppireDataSheet *data_sheet;
+      GtkWidget *scroller;
+      int i;
+
+      /* If one of the datasheet's scrollers is focused, choose that one. */
+      scroller = gtk_container_get_focus_child (
+        GTK_CONTAINER (de->datasheet_vbox_widget));
+      if (scroller != NULL)
+        return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
+
+      /* Otherwise if there's a nonempty selection in some data sheet, choose
+         that one. */
+      FOR_EACH_DATA_SHEET (data_sheet, i, de)
+        {
+          PsppSheetSelection *selection;
 
-    default:
-      /* This happens transiently in psppire_data_editor_init(). */
-      break;
+          selection = pspp_sheet_view_get_selection (
+            PSPP_SHEET_VIEW (data_sheet));
+          if (pspp_sheet_selection_count_selected_rows (selection)
+              && pspp_sheet_selection_count_selected_columns (selection))
+            return data_sheet;
+        }
     }
 
-  if (ui_manager != de->ui_manager)
-    {
-      if (de->ui_manager)
-        g_object_unref (de->ui_manager);
-      if (ui_manager)
-        g_object_ref (ui_manager);
-      de->ui_manager = ui_manager;
-
-      g_object_notify (G_OBJECT (de), "ui-manager");
-    }
+  return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
 }
-
-
+#endif