Allow variables to be entered beyond the end of sheet.
[pspp-builds.git] / src / ui / gui / psppire-data-editor.c
index e08f414d9866602a0a707ccd529071dac97df6f3..b6f66403116d249b66b29d2f879affc7d169dc28 100644 (file)
@@ -109,8 +109,12 @@ psppire_data_editor_finalize (GObject *obj)
 
 
 
-static void popup_variable_menu (GtkSheet *sheet, gint column,
-                                GdkEventButton *event, gpointer data);
+static void popup_variable_column_menu (GtkSheet *sheet, gint column,
+                                       GdkEventButton *event, gpointer data);
+
+static void popup_variable_row_menu (GtkSheet *sheet, gint row,
+                                    GdkEventButton *event, gpointer data);
+
 
 static void popup_cases_menu (GtkSheet *sheet, gint row,
                              GdkEventButton *event, gpointer data);
@@ -118,27 +122,33 @@ static void popup_cases_menu (GtkSheet *sheet, gint row,
 
 
 
+
 /* Callback which occurs when the data sheet's column title
    is double clicked */
 static gboolean
 on_data_column_clicked (PsppireDataEditor *de, gint col, gpointer data)
 {
-
+  GtkSheetRange visible_range;
   gint current_row, current_column;
 
-  gtk_notebook_set_current_page (GTK_NOTEBOOK (de), PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
+                                PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
 
   gtk_sheet_get_active_cell (GTK_SHEET (de->var_sheet),
                             &current_row, &current_column);
 
   gtk_sheet_set_active_cell (GTK_SHEET (de->var_sheet), col, current_column);
 
-  return FALSE;
-}
 
+  gtk_sheet_get_visible_range (GTK_SHEET (de->var_sheet), &visible_range);
 
+  if ( col < visible_range.row0 || col > visible_range.rowi)
+    gtk_sheet_moveto (GTK_SHEET (de->var_sheet), col, current_column, 0.5, 0.5);
 
 
+  return FALSE;
+}
+
 
 /* Callback which occurs when the var sheet's row title
    button is double clicked */
@@ -159,10 +169,7 @@ on_var_row_clicked (PsppireDataEditor *de, gint row, gpointer data)
   gtk_sheet_get_visible_range (GTK_SHEET (de->data_sheet[0]), &visible_range);
 
   if ( row < visible_range.col0 || row > visible_range.coli)
-    {
-      gtk_sheet_moveto (GTK_SHEET (de->data_sheet[0]),
-                       -1, row, 0, 0);
-    }
+    gtk_sheet_moveto (GTK_SHEET (de->data_sheet[0]), -1, row, 0.5, 0.5);
 
   return FALSE;
 }
@@ -191,8 +198,9 @@ enum
     PROP_0,
     PROP_DATA_STORE,
     PROP_VAR_STORE,
-    PROP_COLUMN_MENU,
-    PROP_ROW_MENU,
+    PROP_VS_ROW_MENU,
+    PROP_DS_COLUMN_MENU,
+    PROP_DS_ROW_MENU,
     PROP_VALUE_LABELS,
     PROP_CURRENT_CASE,
     PROP_CURRENT_VAR,
@@ -201,7 +209,6 @@ enum
   };
 
 
-#define WIDTH_OF_M 10
 #define DEFAULT_ROW_HEIGHT 25
 
 static void
@@ -212,7 +219,7 @@ new_data_callback (PsppireDataStore *ds, gpointer data)
   for (i = 0 ; i < 4 ; ++i)
     {
       PsppireAxisUniform *vaxis;
-      casenumber n_cases =  psppire_case_file_get_case_count (ds->case_file);
+      casenumber n_cases =  psppire_data_store_get_case_count (ds);
 
       g_object_get (de->data_sheet[i], "vertical-axis", &vaxis, NULL);
 
@@ -220,18 +227,76 @@ new_data_callback (PsppireDataStore *ds, gpointer data)
     }
 }
 
+static void
+case_inserted_callback (PsppireDataStore *ds, gint before, gpointer data)
+{
+  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
+
+  gint i;
+
+  for (i = 0 ; i < 4 ; ++i)
+    {
+      PsppireAxisUniform *vaxis;
+      casenumber n_cases =  psppire_data_store_get_case_count (ds);
+
+      g_object_get (de->data_sheet[i], "vertical-axis", &vaxis, NULL);
+
+      psppire_axis_uniform_set_count (vaxis, n_cases + 1);
+    }
+}
+
+
+static void
+cases_deleted_callback (PsppireDataStore *ds, gint first, gint n_cases, gpointer data)
+{
+  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
+
+  gint i;
+
+  for (i = 0 ; i < 4 ; ++i)
+    {
+      PsppireAxisUniform *vaxis;
+      casenumber case_count =  psppire_data_store_get_case_count (ds);
+
+      g_object_get (de->data_sheet[i], "vertical-axis", &vaxis, NULL);
+
+      psppire_axis_uniform_set_count (vaxis, case_count - n_cases);
+    }
+}
+
+
+
+/* Return the width (in pixels) of an upper case M when rendered in the
+   current font of W
+*/
+static gint
+width_of_m (GtkWidget *w)
+{
+  PangoRectangle rect;
+  PangoLayout *layout = gtk_widget_create_pango_layout (w, "M");
+
+  pango_layout_get_pixel_extents (layout, NULL, &rect);
+
+  g_object_unref (layout);
+
+  return rect.width;
+}
+
 static void
 new_variables_callback (PsppireDict *dict, gpointer data)
 {
   gint v, i;
   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
+  gint m_width = width_of_m (GTK_WIDGET (de));
 
   PsppireAxisHetero *vaxis;
   g_object_get (de->var_sheet, "vertical-axis", &vaxis, NULL);
 
   psppire_axis_hetero_clear (vaxis);
 
-  for (v = 0 ; v < psppire_dict_get_var_cnt (dict); ++v)
+  /* NOTE: "<=" in the following line is correct.
+     There needs to be one more row than there are variables */
+  for (v = 0 ; v <= psppire_dict_get_var_cnt (dict); ++v)
     psppire_axis_hetero_append (vaxis, DEFAULT_ROW_HEIGHT);
 
   for (i = 0 ; i < 4 ; ++i)
@@ -245,7 +310,7 @@ new_variables_callback (PsppireDict *dict, gpointer data)
        {
          const struct variable *var = psppire_dict_get_variable (dict, v);
 
-         psppire_axis_hetero_append (haxis, 10 * var_get_display_width (var));
+         psppire_axis_hetero_append (haxis, m_width * var_get_display_width (var));
        }
     }
 }
@@ -254,8 +319,11 @@ static void
 insert_variable_callback (PsppireDict *dict, gint x, gpointer data)
 {
   gint i;
+
   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
 
+  gint m_width  = width_of_m (GTK_WIDGET (de));
+
   PsppireAxisHetero *var_vaxis;
   g_object_get (de->var_sheet, "vertical-axis", &var_vaxis, NULL);
 
@@ -267,7 +335,7 @@ insert_variable_callback (PsppireDict *dict, gint x, gpointer data)
       PsppireAxisHetero *haxis;
       g_object_get (de->data_sheet[i], "horizontal-axis", &haxis, NULL);
 
-      psppire_axis_hetero_insert (haxis, WIDTH_OF_M * var_get_display_width (var), x);
+      psppire_axis_hetero_insert (haxis, m_width * var_get_display_width (var), x);
     }
 }
 
@@ -299,6 +367,7 @@ rewidth_variable_callback (PsppireDict *dict, gint posn, gpointer data)
 {
   gint i;
   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
+  gint m_width = width_of_m (GTK_WIDGET (de));
 
   for (i = 0 ; i < 4 ; ++i)
     {
@@ -307,7 +376,7 @@ rewidth_variable_callback (PsppireDict *dict, gint posn, gpointer data)
       g_object_get (de->data_sheet[i], "horizontal-axis", &haxis, NULL);
 
       psppire_axis_hetero_resize_unit (haxis,
-                                      WIDTH_OF_M *
+                                      m_width *
                                       var_get_display_width (var), posn);
     }
 }
@@ -337,12 +406,27 @@ psppire_data_editor_set_property (GObject         *object,
                      "model", de->data_store,
                      NULL);
 
-      g_signal_connect (de->data_store->dict, "backend-changed",   G_CALLBACK (new_variables_callback), de);
-      g_signal_connect (de->data_store->dict, "variable-inserted", G_CALLBACK (insert_variable_callback), de);
-      g_signal_connect (de->data_store->dict, "variable-deleted",  G_CALLBACK (delete_variable_callback), de);
-      g_signal_connect (de->data_store->dict, "variable-display-width-changed",  G_CALLBACK (rewidth_variable_callback), de);
+      g_signal_connect (de->data_store->dict, "backend-changed",
+                       G_CALLBACK (new_variables_callback), de);
+
+      g_signal_connect (de->data_store->dict, "variable-inserted",
+                       G_CALLBACK (insert_variable_callback), de);
+
+      g_signal_connect (de->data_store->dict, "variable-deleted",
+                       G_CALLBACK (delete_variable_callback), de);
+
+      g_signal_connect (de->data_store->dict, "variable-display-width-changed",
+                       G_CALLBACK (rewidth_variable_callback), de);
+
+      g_signal_connect (de->data_store, "backend-changed",
+                       G_CALLBACK (new_data_callback), de);
+
+      g_signal_connect (de->data_store, "case-inserted",
+                       G_CALLBACK (case_inserted_callback), de);
+
+      g_signal_connect (de->data_store, "cases-deleted",
+                       G_CALLBACK (cases_deleted_callback), de);
 
-      g_signal_connect (de->data_store, "backend-changed", G_CALLBACK (new_data_callback), de);
       break;
     case PROP_VAR_STORE:
       if ( de->var_store) g_object_unref (de->var_store);
@@ -353,15 +437,23 @@ psppire_data_editor_set_property (GObject         *object,
                    "model", de->var_store,
                    NULL);
       break;
-    case PROP_COLUMN_MENU:
+    case PROP_VS_ROW_MENU:
+      {
+       GObject *menu = g_value_get_object (value);
+
+       g_signal_connect (de->var_sheet, "button-event-row",
+                         G_CALLBACK (popup_variable_row_menu), menu);
+      }
+      break;
+    case PROP_DS_COLUMN_MENU:
       {
        GObject *menu = g_value_get_object (value);
 
        g_signal_connect (de->data_sheet[0], "button-event-column",
-                         G_CALLBACK (popup_variable_menu), menu);
+                         G_CALLBACK (popup_variable_column_menu), menu);
       }
       break;
-    case PROP_ROW_MENU:
+    case PROP_DS_ROW_MENU:
       {
        GObject *menu = g_value_get_object (value);
 
@@ -461,7 +553,8 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
   GParamSpec *data_store_spec ;
   GParamSpec *var_store_spec ;
   GParamSpec *column_menu_spec;
-  GParamSpec *row_menu_spec;
+  GParamSpec *ds_row_menu_spec;
+  GParamSpec *vs_row_menu_spec;
   GParamSpec *value_labels_spec;
   GParamSpec *current_case_spec;
   GParamSpec *current_var_spec;
@@ -498,27 +591,40 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    var_store_spec);
 
   column_menu_spec =
-    g_param_spec_object ("column-menu",
-                        "Column Menu",
-                        "A menu to be displayed when button 3 is pressed in the column title buttons",
+    g_param_spec_object ("datasheet-column-menu",
+                        "Data Sheet Column Menu",
+                        "A menu to be displayed when button 3 is pressed in thedata sheet's column title buttons",
                         GTK_TYPE_MENU,
                         G_PARAM_WRITABLE);
 
   g_object_class_install_property (object_class,
-                                   PROP_COLUMN_MENU,
+                                   PROP_DS_COLUMN_MENU,
                                    column_menu_spec);
 
 
-  row_menu_spec =
-    g_param_spec_object ("row-menu",
-                        "Row Menu",
-                        "A menu to be displayed when button 3 is pressed in the row title buttons",
+  ds_row_menu_spec =
+    g_param_spec_object ("datasheet-row-menu",
+                        "Data Sheet Row Menu",
+                        "A menu to be displayed when button 3 is pressed in the data sheet's row title buttons",
                         GTK_TYPE_MENU,
                         G_PARAM_WRITABLE);
 
   g_object_class_install_property (object_class,
-                                   PROP_ROW_MENU,
-                                   row_menu_spec);
+                                   PROP_DS_ROW_MENU,
+                                   ds_row_menu_spec);
+
+
+  vs_row_menu_spec =
+    g_param_spec_object ("varsheet-row-menu",
+                        "Variable Sheet Row Menu",
+                        "A menu to be displayed when button 3 is pressed in the variable sheet's row title buttons",
+                        GTK_TYPE_MENU,
+                        G_PARAM_WRITABLE);
+
+  g_object_class_install_property (object_class,
+                                   PROP_VS_ROW_MENU,
+                                   vs_row_menu_spec);
+
 
   value_labels_spec =
     g_param_spec_boolean ("value-labels",
@@ -1075,7 +1181,7 @@ psppire_data_editor_clip_cut (PsppireDataEditor *de)
 /* Popup menu related stuff */
 
 static void
-popup_variable_menu (GtkSheet *sheet, gint column,
+popup_variable_column_menu (GtkSheet *sheet, gint column,
                     GdkEventButton *event, gpointer data)
 {
   GtkMenu *menu = GTK_MENU (data);
@@ -1097,6 +1203,29 @@ popup_variable_menu (GtkSheet *sheet, gint column,
 }
 
 
+static void
+popup_variable_row_menu (GtkSheet *sheet, gint row,
+                    GdkEventButton *event, gpointer data)
+{
+  GtkMenu *menu = GTK_MENU (data);
+
+  PsppireVarStore *var_store =
+    PSPPIRE_VAR_STORE (gtk_sheet_get_model (sheet));
+
+  const struct variable *v =
+    psppire_dict_get_variable (var_store->dict, row);
+
+  if ( v && event->button == 3)
+    {
+      gtk_sheet_select_row (sheet, row);
+
+      gtk_menu_popup (menu,
+                     NULL, NULL, NULL, NULL,
+                     event->button, event->time);
+    }
+}
+
+
 static void
 popup_cases_menu (GtkSheet *sheet, gint row,
                  GdkEventButton *event, gpointer data)
@@ -1173,14 +1302,26 @@ psppire_data_editor_sort_descending (PsppireDataEditor *de)
 void
 psppire_data_editor_insert_variable (PsppireDataEditor *de)
 {
-  glong posn = -1;
-
-  if ( de->data_sheet[0]->state == GTK_SHEET_COLUMN_SELECTED )
-    posn = GTK_SHEET (de->data_sheet[0])->range.col0;
-  else
-    posn = GTK_SHEET (de->data_sheet[0])->active_cell.col;
+  glong posn = 0;
 
-  if ( posn == -1 ) posn = 0;
+  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
+    {
+    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
+      if ( de->data_sheet[0]->state == GTK_SHEET_COLUMN_SELECTED )
+       posn = GTK_SHEET (de->data_sheet[0])->range.col0;
+      else
+       posn = GTK_SHEET (de->data_sheet[0])->active_cell.col;
+      break;
+    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
+      if ( de->var_sheet->state == GTK_SHEET_ROW_SELECTED )
+       posn = GTK_SHEET (de->var_sheet)->range.row0;
+      else
+       posn = GTK_SHEET (de->var_sheet)->active_cell.row;
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    };
 
   psppire_dict_insert_variable (de->data_store->dict, posn, NULL);
 }
@@ -1249,11 +1390,27 @@ psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
   gtk_sheet_show_grid (GTK_SHEET (de->data_sheet[0]), grid_visible);
 }
 
+
+static void
+set_font (GtkWidget *w, gpointer data)
+{
+  PangoFontDescription *font_desc = data;
+  GtkRcStyle *style = gtk_widget_get_modifier_style (w);
+
+  pango_font_description_free (style->font_desc);
+  style->font_desc = pango_font_description_copy (font_desc);
+
+  gtk_widget_modify_style (w, style);
+
+  if ( GTK_IS_CONTAINER (w))
+    gtk_container_foreach (GTK_CONTAINER (w), set_font, font_desc);
+}
+
 void
 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
 {
-  psppire_data_store_set_font (de->data_store, font_desc);
-  psppire_var_store_set_font (de->var_store, font_desc);
+  set_font (GTK_WIDGET (de), font_desc);
+  gtk_container_foreach (GTK_CONTAINER (de), set_font, font_desc);
 }
 
 
@@ -1465,7 +1622,7 @@ data_sheet_set_clip (GtkSheet *sheet)
     {
       struct ccase old;
 
-      if (psppire_case_file_get_case (ds->case_file, i, &old))
+      if (psppire_data_store_get_case (ds, i, &old))
         {
           struct ccase new;