Fix bug where data sometimes got erroneously copied between cells
[pspp] / lib / gtk-contrib / psppire-sheet.c
index 908081e1a0abaafe0e2627e6ff8a6a9ded3e6fa2..f68ccad3d8b0e544973563216da6daccabe23ac9 100644 (file)
@@ -1,5 +1,5 @@
 /*
-   Copyright (C) 2006, 2008 Free Software Foundation
+   Copyright (C) 2006, 2008, 2009 Free Software Foundation
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -69,7 +69,9 @@
 #include "psppire-sheet.h"
 #include <ui/gui/psppire-marshal.h>
 #include <ui/gui/sheet/psppire-sheetmodel.h>
+#include <ui/gui/sheet/psppire-axis.h>
 #include <libpspp/misc.h>
+
 #include <math.h>
 
 /* sheet flags */
@@ -477,9 +479,7 @@ static void psppire_sheet_size_allocate              (GtkWidget *widget,
                                                  GtkAllocation *allocation);
 
 static gboolean psppire_sheet_focus_in               (GtkWidget     *widget,
-                                                     GdkEventFocus *event,
-                                                     gpointer       user_data);
-
+                                                     GdkEventFocus *event);
 
 /* Sheet queries */
 
@@ -699,6 +699,7 @@ enum
     PROP_0,
     PROP_VAXIS,
     PROP_HAXIS,
+    PROP_CELL_PADDING,
     PROP_MODEL
   };
 
@@ -758,6 +759,7 @@ psppire_sheet_set_vertical_axis (PsppireSheet *sheet, PsppireAxis *a)
     g_object_ref (sheet->vaxis);
 }
 
+static const GtkBorder default_cell_padding = {3, 3, 3, 3};
 
 static void
 psppire_sheet_set_property (GObject         *object,
@@ -770,11 +772,37 @@ psppire_sheet_set_property (GObject         *object,
 
   switch (prop_id)
     {
+    case PROP_CELL_PADDING:
+      if ( sheet->cell_padding)
+       g_boxed_free (GTK_TYPE_BORDER, sheet->cell_padding);
+
+      sheet->cell_padding = g_value_dup_boxed (value);
+
+     if (NULL == sheet->cell_padding)
+       sheet->cell_padding = g_boxed_copy (GTK_TYPE_BORDER,
+                                          &default_cell_padding);
+
+     if (sheet->vaxis)
+       g_object_set (sheet->vaxis, "padding",
+                    sheet->cell_padding->top + sheet->cell_padding->bottom,
+                    NULL);
+
+     if (sheet->haxis)
+       g_object_set (sheet->haxis, "padding",
+                    sheet->cell_padding->left + sheet->cell_padding->right,
+                    NULL);
+      break;
     case PROP_VAXIS:
       psppire_sheet_set_vertical_axis (sheet, g_value_get_pointer (value));
+      g_object_set (sheet->vaxis, "padding",
+                   sheet->cell_padding->top + sheet->cell_padding->bottom,
+                   NULL);
       break;
     case PROP_HAXIS:
       psppire_sheet_set_horizontal_axis (sheet, g_value_get_pointer (value));
+      g_object_set (sheet->haxis, "padding",
+                   sheet->cell_padding->left + sheet->cell_padding->right,
+                   NULL);
       break;
     case PROP_MODEL:
       psppire_sheet_set_model (sheet, g_value_get_pointer (value));
@@ -795,6 +823,9 @@ psppire_sheet_get_property (GObject         *object,
 
   switch (prop_id)
     {
+    case PROP_CELL_PADDING:
+      g_value_set_boxed (value, sheet->cell_padding);
+      break;
     case PROP_VAXIS:
       g_value_set_pointer (value, sheet->vaxis);
       break;
@@ -819,6 +850,7 @@ psppire_sheet_class_init (PsppireSheetClass *klass)
   GParamSpec *haxis_spec ;
   GParamSpec *vaxis_spec ;
   GParamSpec *model_spec ;
+  GParamSpec *cell_padding_spec ;
 
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
@@ -1015,10 +1047,17 @@ psppire_sheet_class_init (PsppireSheetClass *klass)
   container_class->add = NULL;
   container_class->remove = NULL;
   container_class->forall = psppire_sheet_forall;
+  container_class->set_focus_child = NULL;
 
   object_class->dispose = psppire_sheet_dispose;
   object_class->finalize = psppire_sheet_finalize;
 
+  cell_padding_spec =
+    g_param_spec_boxed ("cell-padding",
+                       "Cell Padding",
+                       "The space between a cell's contents and its border",
+                       GTK_TYPE_BORDER,
+                       G_PARAM_CONSTRUCT | G_PARAM_READABLE | G_PARAM_WRITABLE);
 
   vaxis_spec =
     g_param_spec_pointer ("vertical-axis",
@@ -1050,6 +1089,10 @@ psppire_sheet_class_init (PsppireSheetClass *klass)
                                    PROP_HAXIS,
                                    haxis_spec);
 
+  g_object_class_install_property (object_class,
+                                   PROP_CELL_PADDING,
+                                   cell_padding_spec);
+
   g_object_class_install_property (object_class,
                                    PROP_MODEL,
                                    model_spec);
@@ -1592,7 +1635,6 @@ psppire_sheet_select_row (PsppireSheet *sheet, gint row)
   sheet->range.col0 = 0;
   sheet->range.rowi = row;
   sheet->range.coli = psppire_axis_unit_count (sheet->haxis) - 1;
-  sheet->active_cell.row = row;
 
   g_signal_emit (sheet, sheet_signals[SELECT_ROW], 0, row);
   psppire_sheet_real_select_range (sheet, NULL);
@@ -1616,7 +1658,6 @@ psppire_sheet_select_column (PsppireSheet *sheet, gint column)
   sheet->range.col0 = column;
   sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
   sheet->range.coli = column;
-  sheet->active_cell.col = column;
 
   g_signal_emit (sheet, sheet_signals[SELECT_COLUMN], 0, column);
   psppire_sheet_real_select_range (sheet, NULL);
@@ -1753,6 +1794,9 @@ psppire_sheet_dispose  (GObject *object)
 
   sheet->dispose_has_run = TRUE;
 
+  if ( sheet->cell_padding)
+    g_boxed_free (GTK_TYPE_BORDER, sheet->cell_padding);
+
   if (sheet->model) g_object_unref (sheet->model);
   if (sheet->vaxis) g_object_unref (sheet->vaxis);
   if (sheet->haxis) g_object_unref (sheet->haxis);
@@ -1810,7 +1854,6 @@ psppire_sheet_style_set (GtkWidget *widget,
   set_entry_widget_font (sheet);
 }
 
-#define BORDER_WIDTH 3
 
 static void
 psppire_sheet_realize (GtkWidget *widget)
@@ -1918,7 +1961,10 @@ psppire_sheet_realize (GtkWidget *widget)
   values.foreground = widget->style->white;
   values.function = GDK_INVERT;
   values.subwindow_mode = GDK_INCLUDE_INFERIORS;
-  values.line_width = BORDER_WIDTH;
+  values.line_width = MAX (sheet->cell_padding->left,
+                          MAX (sheet->cell_padding->right,
+                               MAX (sheet->cell_padding->top,
+                                    sheet->cell_padding->bottom)));
 
   sheet->xor_gc = gdk_gc_new_with_values (widget->window,
                                          &values,
@@ -1928,13 +1974,15 @@ psppire_sheet_realize (GtkWidget *widget)
                                          GDK_GC_LINE_WIDTH
                                          );
 
-
   gtk_widget_set_parent_window (sheet->entry_widget, sheet->sheet_window);
   gtk_widget_set_parent (sheet->entry_widget, GTK_WIDGET (sheet));
 
   gtk_widget_set_parent_window (sheet->button, sheet->sheet_window);
   gtk_widget_set_parent (sheet->button, GTK_WIDGET (sheet));
 
+  sheet->button->style = gtk_style_attach (sheet->button->style,
+                                          sheet->sheet_window);
+
 
   sheet->cursor_drag = gdk_cursor_new_for_display (display, GDK_PLUS);
 
@@ -2092,13 +2140,18 @@ psppire_sheet_unmap (GtkWidget *widget)
     gdk_window_hide (sheet->row_title_window);
   gdk_window_hide (widget->window);
 
-  if (GTK_WIDGET_MAPPED (sheet->entry_widget))
-    gtk_widget_unmap (sheet->entry_widget);
-
-  if (GTK_WIDGET_MAPPED (sheet->button))
-    gtk_widget_unmap (sheet->button);
+  gtk_widget_unmap (sheet->entry_widget);
+  gtk_widget_unmap (sheet->button);
+  gtk_widget_unmap (sheet->hover_window->window);
 }
 
+/* get cell attributes of the given cell */
+/* TRUE means that the cell is currently allocated */
+static gboolean psppire_sheet_get_attributes (const PsppireSheet *sheet,
+                                             gint row, gint col,
+                                             PsppireSheetCellAttr *attributes);
+
+
 
 static void
 psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col)
@@ -2167,6 +2220,19 @@ psppire_sheet_cell_draw (PsppireSheet *sheet, gint row, gint col)
   if ( !pango_font_description_get_size_is_absolute (font_desc))
     font_height /= PANGO_SCALE;
 
+
+  if ( sheet->cell_padding )
+    {
+      area.x += sheet->cell_padding->left;
+      area.width -= sheet->cell_padding->right
+       + sheet->cell_padding->left;
+
+      area.y += sheet->cell_padding->top;
+      area.height -= sheet->cell_padding->bottom
+       +
+       sheet->cell_padding->top;
+    }
+
   /* Centre the text vertically */
   area.y += (area.height - font_height) / 2.0;
 
@@ -2650,11 +2716,11 @@ change_active_cell (PsppireSheet *sheet, gint row, gint col)
   old_row = sheet->active_cell.row;
   old_col = sheet->active_cell.col;
 
-  /* Erase the old cell */
-  psppire_sheet_draw_active_cell (sheet);
-
   entry_load_text (sheet);
 
+  /* Erase the old cell border */
+  psppire_sheet_draw_active_cell (sheet);
+
   sheet->range.row0 = row;
   sheet->range.col0 = col;
   sheet->range.rowi = row;
@@ -3018,11 +3084,24 @@ psppire_sheet_draw_border (PsppireSheet *sheet, PsppireSheetRange new_range)
 
   rectangle_from_range (sheet, &new_range, &area);
 
-  gdk_draw_rectangle (sheet->sheet_window,
-                     sheet->xor_gc,
+  area.width ++;
+  area.height ++;
+
+  gdk_gc_set_clip_rectangle (sheet->xor_gc, &area);
+
+  area.x += sheet->cell_padding->left / 2;
+  area.y += sheet->cell_padding->top / 2;
+  area.width -= (sheet->cell_padding->left + sheet->cell_padding->right ) / 2;
+  area.height -= (sheet->cell_padding->top + sheet->cell_padding->bottom ) / 2;
+
+  gdk_draw_rectangle (sheet->sheet_window,  sheet->xor_gc,
                      FALSE,
-                     area.x, area.y,
-                     area.width, area.height);
+                     area.x,
+                     area.y,
+                     area.width,
+                     area.height);
+
+  gdk_gc_set_clip_rectangle (sheet->xor_gc, NULL);
 }
 
 
@@ -3087,8 +3166,6 @@ psppire_sheet_select_range (PsppireSheet *sheet, const PsppireSheetRange *range)
   sheet->range.rowi = range->rowi;
   sheet->range.col0 = range->col0;
   sheet->range.coli = range->coli;
-  sheet->active_cell.row = range->row0;
-  sheet->active_cell.col = range->col0;
   sheet->selection_cell.row = range->rowi;
   sheet->selection_cell.col = range->coli;
 
@@ -3213,7 +3290,7 @@ psppire_sheet_button_press (GtkWidget *widget,
   GdkModifierType mods;
   gint x, y;
   gint  row, column;
-  gboolean veto;
+
 
   g_return_val_if_fail (widget != NULL, FALSE);
   g_return_val_if_fail (PSPPIRE_IS_SHEET (widget), FALSE);
@@ -3317,65 +3394,8 @@ psppire_sheet_button_press (GtkWidget *widget,
                        NULL, NULL, event->time);
       gtk_grab_add (GTK_WIDGET (sheet));
 
-      if (sheet->selection_mode != GTK_SELECTION_SINGLE &&
-         sheet->selection_mode != GTK_SELECTION_NONE &&
-         sheet->cursor_drag->type == GDK_SIZING &&
-         !PSPPIRE_SHEET_IN_SELECTION (sheet) && !PSPPIRE_SHEET_IN_RESIZE (sheet))
-       {
-         if (sheet->state == GTK_STATE_NORMAL)
-           {
-             row = sheet->active_cell.row;
-             column = sheet->active_cell.col;
-             sheet->active_cell.row = row;
-             sheet->active_cell.col = column;
-             sheet->drag_range = sheet->range;
-             sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
-             psppire_sheet_select_range (sheet, &sheet->drag_range);
-           }
-         sheet->x_drag = x;
-         sheet->y_drag = y;
-         if (row > sheet->range.rowi) row--;
-         if (column > sheet->range.coli) column--;
-         sheet->drag_cell.row = row;
-         sheet->drag_cell.col = column;
-         sheet->drag_range = sheet->range;
-         draw_xor_rectangle (sheet, sheet->drag_range);
-         PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_RESIZE);
-       }
-      else if (sheet->cursor_drag->type == GDK_TOP_LEFT_ARROW &&
-              !PSPPIRE_SHEET_IN_SELECTION (sheet)
-              && ! PSPPIRE_SHEET_IN_DRAG (sheet)
-              && sheet->active_cell.row >= 0
-              && sheet->active_cell.col >= 0
-              )
-       {
-         if (sheet->state == GTK_STATE_NORMAL)
-           {
-             row = sheet->active_cell.row;
-             column = sheet->active_cell.col;
-             sheet->active_cell.row = row;
-             sheet->active_cell.col = column;
-             sheet->drag_range = sheet->range;
-             sheet->state = PSPPIRE_SHEET_RANGE_SELECTED;
-             psppire_sheet_select_range (sheet, &sheet->drag_range);
-           }
-         sheet->x_drag = x;
-         sheet->y_drag = y;
-         if (row < sheet->range.row0) row++;
-         if (row > sheet->range.rowi) row--;
-         if (column < sheet->range.col0) column++;
-         if (column > sheet->range.coli) column--;
-         sheet->drag_cell.row = row;
-         sheet->drag_cell.col = column;
-         sheet->drag_range = sheet->range;
-         draw_xor_rectangle (sheet, sheet->drag_range);
-         PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_DRAG);
-       }
-      else
-       {
-         veto = psppire_sheet_click_cell (sheet, row, column);
-         if (veto) PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
-       }
+      if (psppire_sheet_click_cell (sheet, row, column))
+       PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
     }
 
   if (event->window == sheet->column_title_window)
@@ -3390,7 +3410,6 @@ psppire_sheet_button_press (GtkWidget *widget,
 
       if (psppire_sheet_model_get_column_sensitivity (sheet->model, column))
        {
-         veto = psppire_sheet_click_cell (sheet, -1, column);
          gtk_grab_add (GTK_WIDGET (sheet));
          PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
        }
@@ -3407,7 +3426,6 @@ psppire_sheet_button_press (GtkWidget *widget,
       row = row_from_ypixel (sheet, y);
       if (psppire_sheet_model_get_row_sensitivity (sheet->model, row))
        {
-         veto = psppire_sheet_click_cell (sheet, row, -1);
          gtk_grab_add (GTK_WIDGET (sheet));
          PSPPIRE_SHEET_SET_FLAGS (sheet, PSPPIRE_SHEET_IN_SELECTION);
        }
@@ -3467,8 +3485,6 @@ psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
       sheet->range.rowi = psppire_axis_unit_count (sheet->vaxis) - 1;
       sheet->range.coli =
        psppire_axis_unit_count (sheet->haxis) - 1;
-      sheet->active_cell.row = 0;
-      sheet->active_cell.col = 0;
       psppire_sheet_select_range (sheet, NULL);
       return TRUE;
     }
@@ -3483,8 +3499,6 @@ psppire_sheet_click_cell (PsppireSheet *sheet, gint row, gint column)
       change_active_cell (sheet, row, column);
     }
 
-  sheet->active_cell.row = row;
-  sheet->active_cell.col = column;
   sheet->selection_cell.row = row;
   sheet->selection_cell.col = column;
   sheet->range.row0 = row;
@@ -3553,10 +3567,6 @@ psppire_sheet_button_release (GtkWidget *widget,
 
       psppire_sheet_real_unselect_range (sheet, NULL);
 
-      sheet->active_cell.row = sheet->active_cell.row +
-       (sheet->drag_range.row0 - sheet->range.row0);
-      sheet->active_cell.col = sheet->active_cell.col +
-       (sheet->drag_range.col0 - sheet->range.col0);
       sheet->selection_cell.row = sheet->selection_cell.row +
        (sheet->drag_range.row0 - sheet->range.row0);
       sheet->selection_cell.col = sheet->selection_cell.col +
@@ -3578,10 +3588,6 @@ psppire_sheet_button_release (GtkWidget *widget,
 
       psppire_sheet_real_unselect_range (sheet, NULL);
 
-      sheet->active_cell.row = sheet->active_cell.row +
-       (sheet->drag_range.row0 - sheet->range.row0);
-      sheet->active_cell.col = sheet->active_cell.col +
-       (sheet->drag_range.col0 - sheet->range.col0);
       if (sheet->drag_range.row0 < sheet->range.row0)
        sheet->selection_cell.row = sheet->drag_range.row0;
       if (sheet->drag_range.rowi >= sheet->range.rowi)
@@ -3686,7 +3692,8 @@ create_hover_window (void)
 #define HOVER_WINDOW_Y_OFFSET 2
 
 static void
-show_subtitle (PsppireSheet *sheet, gint row, gint column, const gchar *subtitle)
+show_subtitle (PsppireSheet *sheet, gint row, gint column,
+              const gchar *subtitle)
 {
   gint x, y;
   gint px, py;
@@ -3736,6 +3743,8 @@ motion_timeout_callback (gpointer data)
   PsppireSheet *sheet = PSPPIRE_SHEET (data);
   gint x, y;
   gint row, column;
+
+  gdk_threads_enter ();
   gtk_widget_get_pointer (GTK_WIDGET (sheet), &x, &y);
 
   if ( psppire_sheet_get_pixel_info (sheet, x, y, &row, &column) )
@@ -3759,6 +3768,7 @@ motion_timeout_callback (gpointer data)
        }
     }
 
+  gdk_threads_leave ();
   return FALSE;
 }
 
@@ -4054,14 +4064,16 @@ psppire_sheet_crossing_notify (GtkWidget *widget,
   else if (event->window == sheet->row_title_window)
     sheet->row_title_under = event->type == GDK_ENTER_NOTIFY;
 
+  if (event->type == GDK_LEAVE_NOTIFY)
+    gtk_widget_hide (sheet->hover_window->window);
+
   return TRUE;
 }
 
 
 static gboolean
 psppire_sheet_focus_in (GtkWidget     *w,
-                       GdkEventFocus *event,
-                       gpointer       user_data)
+                       GdkEventFocus *event)
 {
   PsppireSheet *sheet = PSPPIRE_SHEET (w);
 
@@ -4600,10 +4612,10 @@ psppire_sheet_size_allocate_entry (PsppireSheet *sheet)
   rectangle_from_cell (sheet, sheet->active_cell.row,
                       sheet->active_cell.col, &entry_alloc);
 
-  entry_alloc.width -= BORDER_WIDTH ;
-  entry_alloc.height -= BORDER_WIDTH ;
-  entry_alloc.x += DIV_RND_UP (BORDER_WIDTH, 2);
-  entry_alloc.y += DIV_RND_UP (BORDER_WIDTH, 2);
+  entry_alloc.x += sheet->cell_padding->left;
+  entry_alloc.y += sheet->cell_padding->right;
+  entry_alloc.width -= sheet->cell_padding->left + sheet->cell_padding->right;
+  entry_alloc.height -= sheet->cell_padding->top + sheet->cell_padding->bottom;
 
 
   gtk_widget_set_size_request (sheet->entry_widget, entry_alloc.width,
@@ -4728,8 +4740,9 @@ draw_button (PsppireSheet *sheet, GdkWindow *window,
 
   gtk_paint_box (sheet->button->style, window,
                 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
-                &allocation, GTK_WIDGET (sheet->button),
-                "buttondefault",
+                &allocation,
+                GTK_WIDGET (sheet->button),
+                NULL,
                 allocation.x, allocation.y,
                 allocation.width, allocation.height);
 
@@ -4745,10 +4758,30 @@ draw_button (PsppireSheet *sheet, GdkWindow *window,
     gtk_paint_box (sheet->button->style, window,
                   button->state, shadow_type,
                   &allocation, GTK_WIDGET (sheet->button),
-                  "button",
+                  NULL,
                   allocation.x, allocation.y,
                   allocation.width, allocation.height);
 
+  if ( button->overstruck)
+    {
+      GdkPoint points[2] = {
+       {allocation.x,  allocation.y},
+       {allocation.x + allocation.width,
+        allocation.y + allocation.height}
+      };
+
+      gtk_paint_polygon (sheet->button->style,
+                        window,
+                        button->state,
+                        shadow_type,
+                        NULL,
+                        GTK_WIDGET (sheet),
+                        NULL,
+                        points,
+                        2,
+                        TRUE);
+    }
+
   if (button->label_visible)
     {
       text_height = DEFAULT_ROW_HEIGHT -
@@ -5194,7 +5227,9 @@ set_column_width (PsppireSheet *sheet,
   if ( width <= 0)
     return;
 
-  psppire_axis_resize (sheet->haxis, column, width);
+  psppire_axis_resize (sheet->haxis, column,
+                      width - sheet->cell_padding->left -
+                      sheet->cell_padding->right);
 
   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)))
     {
@@ -5219,7 +5254,9 @@ set_row_height (PsppireSheet *sheet,
   if (height <= 0)
     return;
 
-  psppire_axis_resize (sheet->vaxis, row, height);
+  psppire_axis_resize (sheet->vaxis, row,
+                      height - sheet->cell_padding->top -
+                      sheet->cell_padding->bottom);
 
   if (GTK_WIDGET_REALIZED (GTK_WIDGET (sheet)) )
     {
@@ -5230,7 +5267,7 @@ set_row_height (PsppireSheet *sheet,
     }
 }
 
-gboolean
+static gboolean
 psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
                          PsppireSheetCellAttr *attr)
 {
@@ -5253,8 +5290,6 @@ psppire_sheet_get_attributes (const PsppireSheet *sheet, gint row, gint col,
   attr->border.mask = 0;
   attr->border.color = GTK_WIDGET (sheet)->style->black;
 
-  attr->is_editable = psppire_sheet_model_is_editable (sheet->model, row, col);
-
   colormap = gtk_widget_get_colormap (GTK_WIDGET (sheet));
   fg = psppire_sheet_model_get_foreground (sheet->model, row, col);
   if ( fg )
@@ -5337,6 +5372,7 @@ psppire_sheet_button_new (void)
   button->label = NULL;
   button->label_visible = TRUE;
   button->justification = GTK_JUSTIFY_FILL;
+  button->overstruck = FALSE;
 
   return button;
 }