pspp-sheet-view: Support rectangular selection, column popup menus.
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 28 Jun 2011 05:17:57 +0000 (22:17 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 25 Apr 2012 05:41:41 +0000 (22:41 -0700)
A "rectangular selection" is where you can click and drag to select
a rectangular group of columns and rows, like in a spreadsheet.

In this commit, rectangular selections don't interact well with
"quick-edit" mode where the first click on a cell starts editing,
because clicking in a quick-edit cell starts editing instead of
rectangular selection.  The following commit will fix the problem.

This commit also adds a gtk_widget_queue_draw() call to
pspp_sheet_view_update_rubber_band() that should really
invalidate less sheet area.

src/ui/gui/marshaller-list
src/ui/gui/pspp-sheet-private.h
src/ui/gui/pspp-sheet-selection.c
src/ui/gui/pspp-sheet-selection.h
src/ui/gui/pspp-sheet-view-column.c
src/ui/gui/pspp-sheet-view-column.h
src/ui/gui/pspp-sheet-view.c

index 3cf5a9c87e92cf9aafead3167fd23b1086a59560..1f85ee6d7055428446408994273570feb62872ff 100644 (file)
@@ -2,6 +2,7 @@
 
 BOOLEAN:BOOLEAN
 BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN
+BOOLEAN:BOXED
 BOOLEAN:ENUM
 BOOLEAN:ENUM,INT
 BOOLEAN:BOXED,BOXED
index 4658955b0007d287f3761e431d943ac4789ba05b..9d99cbd8a5f7af2060d1ac901af37f4e957f09c8 100644 (file)
@@ -164,6 +164,7 @@ struct _PsppSheetViewPrivate
   gint n_columns;
   GList *columns;
   gint header_height;
+  gint n_selected_columns;
 
   PsppSheetViewColumnDropFunc column_drop_func;
   gpointer column_drop_func_data;
@@ -212,6 +213,9 @@ struct _PsppSheetViewPrivate
 
   int rubber_band_end_node;
 
+  /* Rectangular selection. */
+  PsppSheetViewColumn *anchor_column; /* XXX needs to be a weak pointer? */
+
   /* fixed height */
   gint fixed_height;
 
index ac75e27237c43c4d349233b756fc1cfe9470d8b0..9cd65028b49af59aa26347db702f62346573971d 100644 (file)
@@ -84,7 +84,7 @@ pspp_sheet_selection_class_init (PsppSheetSelectionClass *class)
 static void
 pspp_sheet_selection_init (PsppSheetSelection *selection)
 {
-  selection->type = GTK_SELECTION_SINGLE;
+  selection->type = PSPP_SHEET_SELECTION_SINGLE;
 }
 
 static void
@@ -158,28 +158,27 @@ _pspp_sheet_selection_set_tree_view (PsppSheetSelection *selection,
  * @type: The selection mode
  *
  * Sets the selection mode of the @selection.  If the previous type was
- * #GTK_SELECTION_MULTIPLE, then the anchor is kept selected, if it was
- * previously selected.
+ * #PSPP_SHEET_SELECTION_MULTIPLE or #PSPP_SHEET_SELECTION_RECTANGLE, then the
+ * anchor is kept selected, if it was previously selected.
  **/
 void
 pspp_sheet_selection_set_mode (PsppSheetSelection *selection,
-                            GtkSelectionMode  type)
+                            PsppSheetSelectionMode  type)
 {
   g_return_if_fail (PSPP_IS_SHEET_SELECTION (selection));
 
   if (selection->type == type)
     return;
 
-  
-  if (type == GTK_SELECTION_NONE)
+  if (type == PSPP_SHEET_SELECTION_NONE)
     {
       pspp_sheet_selection_unselect_all (selection);
 
       gtk_tree_row_reference_free (selection->tree_view->priv->anchor);
       selection->tree_view->priv->anchor = NULL;
     }
-  else if (type == GTK_SELECTION_SINGLE ||
-          type == GTK_SELECTION_BROWSE)
+  else if (type == PSPP_SHEET_SELECTION_SINGLE ||
+          type == PSPP_SHEET_SELECTION_BROWSE)
     {
       int node = -1;
       gint selected = FALSE;
@@ -214,6 +213,8 @@ pspp_sheet_selection_set_mode (PsppSheetSelection *selection,
        gtk_tree_path_free (anchor_path);
     }
 
+  /* XXX unselect all columns when switching to/from rectangular selection? */
+
   selection->type = type;
 }
 
@@ -226,10 +227,10 @@ pspp_sheet_selection_set_mode (PsppSheetSelection *selection,
  *
  * Return value: the current selection mode
  **/
-GtkSelectionMode
+PsppSheetSelectionMode
 pspp_sheet_selection_get_mode (PsppSheetSelection *selection)
 {
-  g_return_val_if_fail (PSPP_IS_SHEET_SELECTION (selection), GTK_SELECTION_SINGLE);
+  g_return_val_if_fail (PSPP_IS_SHEET_SELECTION (selection), PSPP_SHEET_SELECTION_SINGLE);
 
   return selection->type;
 }
@@ -257,10 +258,11 @@ pspp_sheet_selection_get_tree_view (PsppSheetSelection *selection)
  * @iter: (allow-none): The #GtkTreeIter, or NULL.
  *
  * Sets @iter to the currently selected node if @selection is set to
- * #GTK_SELECTION_SINGLE or #GTK_SELECTION_BROWSE.  @iter may be NULL if you
- * just want to test if @selection has any selected nodes.  @model is filled
- * with the current model as a convenience.  This function will not work if you
- * use @selection is #GTK_SELECTION_MULTIPLE.
+ * #PSPP_SHEET_SELECTION_SINGLE or #PSPP_SHEET_SELECTION_BROWSE.  @iter may be
+ * NULL if you just want to test if @selection has any selected nodes.  @model
+ * is filled with the current model as a convenience.  This function will not
+ * work if @selection's mode is #PSPP_SHEET_SELECTION_MULTIPLE or
+ * #PSPP_SHEET_SELECTION_RECTANGLE.
  *
  * Return value: TRUE, if there is a selected node.
  **/
@@ -274,7 +276,9 @@ pspp_sheet_selection_get_selected (PsppSheetSelection  *selection,
   gboolean retval;
 
   g_return_val_if_fail (PSPP_IS_SHEET_SELECTION (selection), FALSE);
-  g_return_val_if_fail (selection->type != GTK_SELECTION_MULTIPLE, FALSE);
+  g_return_val_if_fail (selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
+                        selection->type != PSPP_SHEET_SELECTION_RECTANGLE,
+                        FALSE);
   g_return_val_if_fail (selection->tree_view != NULL, FALSE);
 
   /* Clear the iter */
@@ -359,9 +363,10 @@ pspp_sheet_selection_get_selected_rows (PsppSheetSelection   *selection,
   if (selection->tree_view->priv->row_count == 0)
     return NULL;
 
-  if (selection->type == GTK_SELECTION_NONE)
+  if (selection->type == PSPP_SHEET_SELECTION_NONE)
     return NULL;
-  else if (selection->type != GTK_SELECTION_MULTIPLE)
+  else if (selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
+           selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
     {
       GtkTreeIter iter;
 
@@ -413,8 +418,8 @@ pspp_sheet_selection_count_selected_rows (PsppSheetSelection *selection)
   if (selection->tree_view->priv->row_count == 0)
     return 0;
 
-  if (selection->type == GTK_SELECTION_SINGLE ||
-      selection->type == GTK_SELECTION_BROWSE)
+  if (selection->type == PSPP_SHEET_SELECTION_SINGLE ||
+      selection->type == PSPP_SHEET_SELECTION_BROWSE)
     {
       if (pspp_sheet_selection_get_selected (selection, NULL, NULL))
        return 1;
@@ -469,8 +474,8 @@ pspp_sheet_selection_selected_foreach (PsppSheetSelection            *selection,
       selection->tree_view->priv->row_count == 0)
     return;
 
-  if (selection->type == GTK_SELECTION_SINGLE ||
-      selection->type == GTK_SELECTION_BROWSE)
+  if (selection->type == PSPP_SHEET_SELECTION_SINGLE ||
+      selection->type == PSPP_SHEET_SELECTION_BROWSE)
     {
       if (gtk_tree_row_reference_valid (selection->tree_view->priv->anchor))
        {
@@ -555,7 +560,8 @@ pspp_sheet_selection_select_path (PsppSheetSelection *selection,
   if (node < 0 || pspp_sheet_view_node_is_selected (selection->tree_view, node)) 
     return;
 
-  if (selection->type == GTK_SELECTION_MULTIPLE)
+  if (selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
+      selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
     mode = GTK_TREE_SELECT_MODE_TOGGLE;
 
   _pspp_sheet_selection_internal_select_node (selection,
@@ -737,6 +743,7 @@ pspp_sheet_selection_real_select_all (PsppSheetSelection *selection)
     return FALSE;
 
   range_tower_set1 (selection->tree_view->priv->selected, 0, row_count);
+  pspp_sheet_selection_select_all_columns (selection);
 
   /* XXX we could invalidate individual visible rows instead */
   gdk_window_invalidate_rect (selection->tree_view->priv->bin_window, NULL, TRUE);
@@ -748,8 +755,8 @@ pspp_sheet_selection_real_select_all (PsppSheetSelection *selection)
  * pspp_sheet_selection_select_all:
  * @selection: A #PsppSheetSelection.
  *
- * Selects all the nodes. @selection must be set to #GTK_SELECTION_MULTIPLE
- * mode.
+ * Selects all the nodes and column. @selection must be set to
+ * #PSPP_SHEET_SELECTION_MULTIPLE or  #PSPP_SHEET_SELECTION_RECTANGLE mode.
  **/
 void
 pspp_sheet_selection_select_all (PsppSheetSelection *selection)
@@ -760,7 +767,8 @@ pspp_sheet_selection_select_all (PsppSheetSelection *selection)
   if (selection->tree_view->priv->row_count == 0 || selection->tree_view->priv->model == NULL)
     return;
 
-  g_return_if_fail (selection->type == GTK_SELECTION_MULTIPLE);
+  g_return_if_fail (selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
+                    selection->type == PSPP_SHEET_SELECTION_RECTANGLE);
 
   if (pspp_sheet_selection_real_select_all (selection))
     g_signal_emit (selection, tree_selection_signals[CHANGED], 0);
@@ -769,8 +777,8 @@ pspp_sheet_selection_select_all (PsppSheetSelection *selection)
 static gint
 pspp_sheet_selection_real_unselect_all (PsppSheetSelection *selection)
 {
-  if (selection->type == GTK_SELECTION_SINGLE ||
-      selection->type == GTK_SELECTION_BROWSE)
+  if (selection->type == PSPP_SHEET_SELECTION_SINGLE ||
+      selection->type == PSPP_SHEET_SELECTION_BROWSE)
     {
       int node = -1;
       GtkTreePath *anchor_path;
@@ -808,6 +816,7 @@ pspp_sheet_selection_real_unselect_all (PsppSheetSelection *selection)
   else
     {
       range_tower_set0 (selection->tree_view->priv->selected, 0, ULONG_MAX);
+      pspp_sheet_selection_unselect_all_columns (selection);
 
       /* XXX we could invalidate individual visible rows instead */
       gdk_window_invalidate_rect (selection->tree_view->priv->bin_window, NULL, TRUE);
@@ -820,7 +829,7 @@ pspp_sheet_selection_real_unselect_all (PsppSheetSelection *selection)
  * pspp_sheet_selection_unselect_all:
  * @selection: A #PsppSheetSelection.
  *
- * Unselects all the nodes.
+ * Unselects all the nodes and columns.
  **/
 void
 pspp_sheet_selection_unselect_all (PsppSheetSelection *selection)
@@ -921,7 +930,8 @@ pspp_sheet_selection_real_modify_range (PsppSheetSelection *selection,
  * @end_path: The final node of the range.
  *
  * Selects a range of nodes, determined by @start_path and @end_path inclusive.
- * @selection must be set to #GTK_SELECTION_MULTIPLE mode. 
+ * @selection must be set to #PSPP_SHEET_SELECTION_MULTIPLE or
+ * #PSPP_SHEET_SELECTION_RECTANGLE mode.
  **/
 void
 pspp_sheet_selection_select_range (PsppSheetSelection *selection,
@@ -930,7 +940,8 @@ pspp_sheet_selection_select_range (PsppSheetSelection *selection,
 {
   g_return_if_fail (PSPP_IS_SHEET_SELECTION (selection));
   g_return_if_fail (selection->tree_view != NULL);
-  g_return_if_fail (selection->type == GTK_SELECTION_MULTIPLE);
+  g_return_if_fail (selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
+                    selection->type == PSPP_SHEET_SELECTION_RECTANGLE);
   g_return_if_fail (selection->tree_view->priv->model != NULL);
 
   if (pspp_sheet_selection_real_modify_range (selection, RANGE_SELECT, start_path, end_path))
@@ -996,22 +1007,22 @@ _pspp_sheet_selection_internal_select_node (PsppSheetSelection *selection,
   gint dirty = FALSE;
   GtkTreePath *anchor_path = NULL;
 
-  if (selection->type == GTK_SELECTION_NONE)
+  if (selection->type == PSPP_SHEET_SELECTION_NONE)
     return;
 
   if (selection->tree_view->priv->anchor)
     anchor_path = gtk_tree_row_reference_get_path (selection->tree_view->priv->anchor);
 
-  if (selection->type == GTK_SELECTION_SINGLE ||
-      selection->type == GTK_SELECTION_BROWSE)
+  if (selection->type == PSPP_SHEET_SELECTION_SINGLE ||
+      selection->type == PSPP_SHEET_SELECTION_BROWSE)
     {
       /* just unselect */
-      if (selection->type == GTK_SELECTION_BROWSE && override_browse_mode)
+      if (selection->type == PSPP_SHEET_SELECTION_BROWSE && override_browse_mode)
         {
          dirty = pspp_sheet_selection_real_unselect_all (selection);
        }
       /* Did we try to select the same node again? */
-      else if (selection->type == GTK_SELECTION_SINGLE &&
+      else if (selection->type == PSPP_SHEET_SELECTION_SINGLE &&
               anchor_path && gtk_tree_path_compare (path, anchor_path) == 0)
        {
          if ((mode & GTK_TREE_SELECT_MODE_TOGGLE) == GTK_TREE_SELECT_MODE_TOGGLE)
@@ -1056,7 +1067,8 @@ _pspp_sheet_selection_internal_select_node (PsppSheetSelection *selection,
            }
        }
     }
-  else if (selection->type == GTK_SELECTION_MULTIPLE)
+  else if (selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
+           selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
     {
       if ((mode & GTK_TREE_SELECT_MODE_EXTEND) == GTK_TREE_SELECT_MODE_EXTEND
           && (anchor_path == NULL))
@@ -1147,3 +1159,145 @@ pspp_sheet_selection_real_select_node (PsppSheetSelection *selection,
 
   return FALSE;
 }
+
+void
+pspp_sheet_selection_unselect_all_columns (PsppSheetSelection *selection)
+{
+  PsppSheetView *sheet_view = selection->tree_view;
+  gboolean changed;
+  GList *list;
+
+  changed = FALSE;
+  for (list = sheet_view->priv->columns; list; list = list->next)
+    {
+      PsppSheetViewColumn *column = list->data;
+      if (column->selected)
+        {
+          column->selected = FALSE;
+          changed = TRUE;
+        }
+    }
+  if (changed && selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
+    {
+      gtk_widget_queue_draw (GTK_WIDGET (selection->tree_view));
+      _pspp_sheet_selection_emit_changed (selection);
+    }
+}
+
+GList *
+pspp_sheet_selection_get_selected_columns (PsppSheetSelection *selection)
+{
+  PsppSheetView *sheet_view = selection->tree_view;
+  GList *selected_columns = NULL;
+  GList *iter;
+
+  g_return_val_if_fail (PSPP_IS_SHEET_SELECTION (selection), NULL);
+  g_return_val_if_fail (selection->tree_view != NULL, NULL);
+
+  if (selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
+    return NULL;
+
+  for (iter = sheet_view->priv->columns; iter; iter = iter->next)
+    {
+      PsppSheetViewColumn *column = iter->data;
+      if (column->selected)
+        selected_columns = g_list_prepend (selected_columns, column);
+    }
+  return g_list_reverse (selected_columns);
+}
+
+gint
+pspp_sheet_selection_count_selected_columns (PsppSheetSelection *selection)
+{
+  PsppSheetView *sheet_view = selection->tree_view;
+  GList *list;
+  gint n;
+
+  n = 0;
+  for (list = sheet_view->priv->columns; list; list = list->next)
+    {
+      PsppSheetViewColumn *column = list->data;
+      if (column->selected)
+        n++;
+    }
+  return n;
+}
+
+void
+pspp_sheet_selection_select_all_columns (PsppSheetSelection *selection)
+{
+  PsppSheetView *sheet_view = selection->tree_view;
+  gboolean changed;
+  GList *list;
+
+  changed = FALSE;
+  for (list = sheet_view->priv->columns; list; list = list->next)
+    {
+      PsppSheetViewColumn *column = list->data;
+      if (!column->selected && column->selectable)
+        {
+          /* XXX should use pspp_sheet_view_column_set_selected() here (and
+             elsewhere) but we want to call
+             _pspp_sheet_selection_emit_changed() only once for all the
+             columns. */
+          column->selected = TRUE;
+          changed = TRUE;
+        }
+    }
+  if (changed && selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
+    {
+      _pspp_sheet_selection_emit_changed (selection);
+      gtk_widget_queue_draw (GTK_WIDGET (selection->tree_view));
+    }
+}
+
+void
+pspp_sheet_selection_select_column (PsppSheetSelection        *selection,
+                                    PsppSheetViewColumn       *column)
+{
+  if (!column->selected && column->selectable)
+    {
+      column->selected = TRUE;
+      if (selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
+        {
+          _pspp_sheet_selection_emit_changed (selection);
+          gtk_widget_queue_draw (GTK_WIDGET (selection->tree_view));
+        }
+    }
+}
+
+void
+pspp_sheet_selection_select_column_range  (PsppSheetSelection        *selection,
+                                           PsppSheetViewColumn       *first,
+                                           PsppSheetViewColumn       *last)
+{
+  PsppSheetView *sheet_view = selection->tree_view;
+  gboolean in_range;
+  gboolean changed;
+  GList *list;
+
+  in_range = FALSE;
+  changed = FALSE;
+  for (list = sheet_view->priv->columns; list; list = list->next)
+    {
+      PsppSheetViewColumn *column = list->data;
+      gboolean c0 = column == first;
+      gboolean c1 = column == last;
+
+      if (in_range || c0 || c1)
+        {
+          if (!column->selected && column->selectable)
+            {
+              column->selected = TRUE;
+              changed = TRUE;
+            }
+        }
+
+      in_range = in_range ^ c0 ^ c1;
+    }
+  if (changed && selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
+    {
+      _pspp_sheet_selection_emit_changed (selection);
+      gtk_widget_queue_draw (GTK_WIDGET (selection->tree_view));
+    }
+}
index 306aa4abd2b7958b2182692731d11ffba23e57ea..4d8c50bcf190538f98afa08296a6a82f58676c9a 100644 (file)
@@ -48,6 +48,19 @@ G_BEGIN_DECLS
 #define PSPP_IS_SHEET_SELECTION_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), PSPP_TYPE_SHEET_SELECTION))
 #define PSPP_SHEET_SELECTION_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), PSPP_TYPE_SHEET_SELECTION, PsppSheetSelectionClass))
 
+typedef enum
+  {
+    /* Same as GtkSelectionMode. */
+    PSPP_SHEET_SELECTION_NONE = GTK_SELECTION_NONE,
+    PSPP_SHEET_SELECTION_SINGLE = GTK_SELECTION_SINGLE,
+    PSPP_SHEET_SELECTION_BROWSE = GTK_SELECTION_BROWSE,
+    PSPP_SHEET_SELECTION_MULTIPLE = GTK_SELECTION_MULTIPLE,
+
+    /* PsppSheetView extension. */
+    PSPP_SHEET_SELECTION_RECTANGLE = 10
+  }
+PsppSheetSelectionMode;
+
 typedef gboolean (* PsppSheetSelectionFunc)    (PsppSheetSelection  *selection,
                                              GtkTreeModel      *model,
                                              GtkTreePath       *path,
@@ -65,7 +78,7 @@ struct _PsppSheetSelection
   /*< private >*/
 
   PsppSheetView *GSEAL (tree_view);
-  GtkSelectionMode GSEAL (type);
+  PsppSheetSelectionMode GSEAL (type);
 };
 
 struct _PsppSheetSelectionClass
@@ -85,8 +98,8 @@ struct _PsppSheetSelectionClass
 GType            pspp_sheet_selection_get_type            (void) G_GNUC_CONST;
 
 void             pspp_sheet_selection_set_mode            (PsppSheetSelection            *selection,
-                                                        GtkSelectionMode             type);
-GtkSelectionMode pspp_sheet_selection_get_mode        (PsppSheetSelection            *selection);
+                                                        PsppSheetSelectionMode             type);
+PsppSheetSelectionMode pspp_sheet_selection_get_mode        (PsppSheetSelection            *selection);
 void             pspp_sheet_selection_set_select_function (PsppSheetSelection            *selection,
                                                         PsppSheetSelectionFunc         func,
                                                         gpointer                     data,
@@ -96,8 +109,9 @@ PsppSheetView*     pspp_sheet_selection_get_tree_view       (PsppSheetSelection
 
 PsppSheetSelectionFunc pspp_sheet_selection_get_select_function (PsppSheetSelection        *selection);
 
-/* Only meaningful if GTK_SELECTION_SINGLE or GTK_SELECTION_BROWSE is set */
-/* Use selected_foreach or get_selected_rows for GTK_SELECTION_MULTIPLE */
+/* Only meaningful if PSPP_SHEET_SELECTION_SINGLE or PSPP_SHEET_SELECTION_BROWSE is set */
+/* Use selected_foreach or get_selected_rows for
+   PSPP_SHEET_SELECTION_MULTIPLE */
 gboolean         pspp_sheet_selection_get_selected        (PsppSheetSelection            *selection,
                                                         GtkTreeModel               **model,
                                                         GtkTreeIter                 *iter);
@@ -127,7 +141,18 @@ void             pspp_sheet_selection_select_range        (PsppSheetSelection
 void             pspp_sheet_selection_unselect_range      (PsppSheetSelection            *selection,
                                                          GtkTreePath                 *start_path,
                                                         GtkTreePath                 *end_path);
-
+struct range_set *pspp_sheet_selection_get_range_set (PsppSheetSelection *selection);
+
+
+GList *          pspp_sheet_selection_get_selected_columns (PsppSheetSelection            *selection);
+gint             pspp_sheet_selection_count_selected_columns (PsppSheetSelection            *selection);
+void             pspp_sheet_selection_select_all_columns (PsppSheetSelection        *selection);
+void             pspp_sheet_selection_unselect_all_columns (PsppSheetSelection        *selection);
+void             pspp_sheet_selection_select_column        (PsppSheetSelection        *selection,
+                                                            PsppSheetViewColumn       *column);
+void             pspp_sheet_selection_select_column_range  (PsppSheetSelection        *selection,
+                                                            PsppSheetViewColumn       *first,
+                                                            PsppSheetViewColumn       *last);
 
 G_END_DECLS
 
index 9f88156fd265700199aec292a9ab6c27c4dc896e..88a624dd448915d4a6ea6bb440eaf7353d6a745c 100644 (file)
@@ -43,6 +43,7 @@
 #include <string.h>
 
 #include "ui/gui/psppire-marshal.h"
+#include "ui/gui/pspp-sheet-selection.h"
 
 #define P_(STRING) STRING
 #define GTK_PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
@@ -67,13 +68,18 @@ enum
   PROP_SORT_INDICATOR,
   PROP_SORT_ORDER,
   PROP_SORT_COLUMN_ID,
-  PROP_QUICK_EDIT
+  PROP_QUICK_EDIT,
+  PROP_SELECTED,
+  PROP_SELECTABLE,
+  PROP_ROW_HEAD
 };
 
 enum
 {
   CLICKED,
   QUERY_TOOLTIP,
+  POPUP_MENU,
+  BUTTON_PRESS_EVENT,
   LAST_SIGNAL
 };
 
@@ -141,9 +147,14 @@ static gint pspp_sheet_view_column_button_event                  (GtkWidget
                                                                gpointer                 data);
 static void pspp_sheet_view_column_button_clicked                (GtkWidget               *widget,
                                                                gpointer                 data);
+static void pspp_sheet_view_column_button_popup_menu (GtkWidget *widget,
+                                                      gpointer data);
 static gboolean pspp_sheet_view_column_mnemonic_activate         (GtkWidget *widget,
                                                                gboolean   group_cycling,
                                                                gpointer   data);
+static gboolean on_pspp_sheet_view_column_button_clicked (PsppSheetViewColumn *);
+static gboolean on_pspp_sheet_view_column_button_press_event (PsppSheetViewColumn *,
+                                                              GdkEventButton *);
 
 /* Property handlers */
 static void pspp_sheet_view_model_sort_column_changed            (GtkTreeSortable         *sortable,
@@ -187,7 +198,8 @@ pspp_sheet_view_column_class_init (PsppSheetViewColumnClass *class)
 
   object_class = (GObjectClass*) class;
 
-  class->clicked = NULL;
+  class->clicked = on_pspp_sheet_view_column_button_clicked;
+  class->button_press_event = on_pspp_sheet_view_column_button_press_event;
 
   object_class->finalize = pspp_sheet_view_column_finalize;
   object_class->set_property = pspp_sheet_view_column_set_property;
@@ -198,6 +210,15 @@ pspp_sheet_view_column_class_init (PsppSheetViewColumnClass *class)
                   G_OBJECT_CLASS_TYPE (object_class),
                   G_SIGNAL_RUN_LAST,
                   G_STRUCT_OFFSET (PsppSheetViewColumnClass, clicked),
+                  g_signal_accumulator_true_handled, NULL,
+                  psppire_marshal_BOOLEAN__VOID,
+                  G_TYPE_BOOLEAN, 0);
+
+  tree_column_signals[POPUP_MENU] =
+    g_signal_new ("popup-menu",
+                  G_OBJECT_CLASS_TYPE (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
                   NULL, NULL,
                   g_cclosure_marshal_VOID__VOID,
                   G_TYPE_NONE, 0);
@@ -212,6 +233,16 @@ pspp_sheet_view_column_class_init (PsppSheetViewColumnClass *class)
                   G_TYPE_BOOLEAN, 1,
                   GTK_TYPE_TOOLTIP);
 
+  tree_column_signals[BUTTON_PRESS_EVENT] =
+    g_signal_new ("button-press-event",
+                  G_OBJECT_CLASS_TYPE (object_class),
+                  G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (PsppSheetViewColumnClass, button_press_event),
+                  g_signal_accumulator_true_handled, NULL,
+                  psppire_marshal_BOOLEAN__BOXED,
+                  G_TYPE_BOOLEAN, 1,
+                  GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
   g_object_class_install_property (object_class,
                                    PROP_VISIBLE,
                                    g_param_spec_boolean ("visible",
@@ -370,6 +401,30 @@ pspp_sheet_view_column_class_init (PsppSheetViewColumnClass *class)
                                                          P_("If true, editing starts upon the first click in the column.  If false, the first click selects the column and a second click is needed to begin editing.  This has no effect on cells that are not editable."),
                                                          TRUE,
                                                          GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_SELECTED,
+                                   g_param_spec_boolean ("selected",
+                                                         P_("Selected"),
+                                                         P_("If true, this column is selected as part of a rectangular selection."),
+                                                         FALSE,
+                                                         GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_SELECTABLE,
+                                   g_param_spec_boolean ("selectable",
+                                                         P_("Selectable"),
+                                                         P_("If true, this column may be selected as part of a rectangular selection."),
+                                                         TRUE,
+                                                         GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_ROW_HEAD,
+                                   g_param_spec_boolean ("row-head",
+                                                         P_("Row head"),
+                                                         P_("If true, this column is a \"row head\", equivalent to a column head.  If rectangular selection is enabled, then shift+click and control+click in the column select row ranges and toggle row selection, respectively.  The column should ordinarily include a button cell; clicking on the button will select the row (and deselect all other rows)."),
+                                                         FALSE,
+                                                         GTK_PARAM_READWRITE));
 }
 
 static void
@@ -409,6 +464,9 @@ pspp_sheet_view_column_init (PsppSheetViewColumn *tree_column)
   tree_column->expand = FALSE;
   tree_column->clickable = FALSE;
   tree_column->dirty = TRUE;
+  tree_column->selected = FALSE;
+  tree_column->selectable = TRUE;
+  tree_column->row_head = FALSE;
   tree_column->sort_order = GTK_SORT_ASCENDING;
   tree_column->show_sort_indicator = FALSE;
   tree_column->property_changed_signal = 0;
@@ -546,6 +604,21 @@ pspp_sheet_view_column_set_property (GObject         *object,
                                              g_value_get_boolean (value));
       break;
 
+    case PROP_SELECTED:
+      pspp_sheet_view_column_set_selected (tree_column,
+                                             g_value_get_boolean (value));
+      break;
+
+    case PROP_SELECTABLE:
+      pspp_sheet_view_column_set_selectable (tree_column,
+                                             g_value_get_boolean (value));
+      break;
+
+    case PROP_ROW_HEAD:
+      pspp_sheet_view_column_set_row_head (tree_column,
+                                             g_value_get_boolean (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -649,6 +722,21 @@ pspp_sheet_view_column_get_property (GObject         *object,
                            pspp_sheet_view_column_get_quick_edit (tree_column));
       break;
 
+    case PROP_SELECTED:
+      g_value_set_boolean (value,
+                           pspp_sheet_view_column_get_selected (tree_column));
+      break;
+
+    case PROP_SELECTABLE:
+      g_value_set_boolean (value,
+                           pspp_sheet_view_column_get_selectable (tree_column));
+      break;
+
+    case PROP_ROW_HEAD:
+      g_value_set_boolean (value,
+                           pspp_sheet_view_column_get_row_head (tree_column));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -859,6 +947,19 @@ on_query_tooltip (GtkWidget  *widget,
   return handled;
 }
 
+static gboolean
+on_button_pressed (GtkWidget *widget, GdkEventButton *event,
+                   gpointer user_data)
+{
+  PsppSheetViewColumn *tree_column = user_data;
+  gboolean handled;
+
+  /* XXX See "Implement GtkWidget::popup_menu" in GTK+ reference manual. */
+  g_signal_emit (tree_column, tree_column_signals[BUTTON_PRESS_EVENT],
+                 0, event, &handled);
+  return handled;
+}
+
 /* Helper functions
  */
 
@@ -892,6 +993,11 @@ pspp_sheet_view_column_create_button (PsppSheetViewColumn *tree_column)
   g_signal_connect (tree_column->button, "clicked",
                    G_CALLBACK (pspp_sheet_view_column_button_clicked),
                    tree_column);
+  g_signal_connect (tree_column->button, "popup-menu",
+                   G_CALLBACK (pspp_sheet_view_column_button_popup_menu),
+                   tree_column);
+  g_signal_connect (tree_column->button, "button-press-event",
+                    G_CALLBACK (on_button_pressed), tree_column);
 
   g_signal_connect (tree_column->button, "query-tooltip",
                     G_CALLBACK (on_query_tooltip), tree_column);
@@ -1150,9 +1256,6 @@ pspp_sheet_view_column_button_event (GtkWidget *widget,
     {
       switch (event->type)
        {
-       case GDK_BUTTON_PRESS:
-       case GDK_2BUTTON_PRESS:
-       case GDK_3BUTTON_PRESS:
        case GDK_MOTION_NOTIFY:
        case GDK_BUTTON_RELEASE:
        case GDK_ENTER_NOTIFY:
@@ -1165,11 +1268,126 @@ pspp_sheet_view_column_button_event (GtkWidget *widget,
   return FALSE;
 }
 
+static gboolean
+all_rows_selected (PsppSheetView *sheet_view)
+{
+  PsppSheetSelection *selection = sheet_view->priv->selection;
+  gint n_rows, n_selected_rows;
+
+  n_rows = sheet_view->priv->row_count;
+  n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
+
+  return n_rows > 0 && n_selected_rows >= n_rows;
+}
+
+static gboolean
+on_pspp_sheet_view_column_button_press_event (PsppSheetViewColumn *column,
+                                              GdkEventButton *event)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (column->tree_view);
+  PsppSheetSelection *selection;
+  GSignalInvocationHint *hint;
+  guint modifiers;
+
+  /* We only want to run first, not last, but combining that with return type
+     `gboolean' makes GObject warn, so just ignore the run_last call. */
+  hint = g_signal_get_invocation_hint (column);
+  g_return_val_if_fail (hint != NULL, FALSE);
+  if (hint->run_type != G_SIGNAL_RUN_FIRST)
+    return FALSE;
+
+  g_return_val_if_fail (sheet_view != NULL, FALSE);
+
+  selection = sheet_view->priv->selection;
+  g_return_val_if_fail (selection != NULL, FALSE);
+
+  if (pspp_sheet_selection_get_mode (selection) != PSPP_SHEET_SELECTION_RECTANGLE)
+    return FALSE;
+
+  modifiers = event->state & gtk_accelerator_get_default_mod_mask ();
+  if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+    {
+      if (pspp_sheet_selection_count_selected_columns (selection) <= 1
+          || !all_rows_selected (sheet_view))
+        {
+          pspp_sheet_selection_select_all (selection);
+          pspp_sheet_selection_unselect_all_columns (selection);
+          pspp_sheet_selection_select_column (selection, column);
+          sheet_view->priv->anchor_column = column;
+        }
+      return FALSE;
+    }
+  else if (event->type == GDK_BUTTON_PRESS && event->button == 1
+           && modifiers == GDK_CONTROL_MASK)
+    {
+      gboolean is_selected;
+
+      if (!all_rows_selected (sheet_view))
+        {
+          pspp_sheet_selection_select_all (selection);
+          pspp_sheet_selection_unselect_all_columns (selection);
+        }
+      sheet_view->priv->anchor_column = column;
+
+      is_selected = pspp_sheet_view_column_get_selected (column);
+      pspp_sheet_view_column_set_selected (column, !is_selected);
+
+      return TRUE;
+    }
+  else if (event->type == GDK_BUTTON_PRESS && event->button == 1
+           && modifiers == GDK_SHIFT_MASK)
+    {
+      if (!all_rows_selected (sheet_view))
+        {
+          pspp_sheet_selection_select_all (selection);
+          pspp_sheet_selection_unselect_all_columns (selection);
+          sheet_view->priv->anchor_column = column;
+        }
+      else if (sheet_view->priv->anchor_column == NULL)
+        sheet_view->priv->anchor_column = column;
+
+      pspp_sheet_selection_unselect_all_columns (selection);
+      pspp_sheet_selection_select_column_range (selection,
+                                                sheet_view->priv->anchor_column,
+                                                column);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+on_pspp_sheet_view_column_button_clicked (PsppSheetViewColumn *column)
+{
+  PsppSheetSelection *selection;
+  PsppSheetView *sheet_view;
+
+  sheet_view = PSPP_SHEET_VIEW (pspp_sheet_view_column_get_tree_view (column));
+  selection = pspp_sheet_view_get_selection (sheet_view);
+  if (pspp_sheet_selection_get_mode (selection) == PSPP_SHEET_SELECTION_RECTANGLE)
+    {
+      pspp_sheet_selection_select_all (selection);
+      pspp_sheet_selection_unselect_all_columns (selection);
+      pspp_sheet_selection_select_column (selection, column);
+      sheet_view->priv->anchor_column = column;
+      return TRUE;
+    }
+  return FALSE;
+}
 
 static void
 pspp_sheet_view_column_button_clicked (GtkWidget *widget, gpointer data)
 {
-  g_signal_emit_by_name (data, "clicked");
+  PsppSheetViewColumn *column = data;
+  gboolean handled;
+
+  g_signal_emit (column, tree_column_signals[CLICKED], 0, &handled);
+}
+
+static void
+pspp_sheet_view_column_button_popup_menu (GtkWidget *widget, gpointer data)
+{
+  g_signal_emit_by_name (data, "popup-menu");
 }
 
 static gboolean
@@ -2387,6 +2605,132 @@ pspp_sheet_view_column_get_quick_edit (PsppSheetViewColumn *tree_column)
 }
 
 
+/**
+ * pspp_sheet_view_column_set_selected:
+ * @tree_column: A #PsppSheetViewColumn
+ * @selected: If true, the column is selected as part of a rectangular
+ * selection.
+ **/
+void
+pspp_sheet_view_column_set_selected (PsppSheetViewColumn *tree_column,
+                                     gboolean           selected)
+{
+  g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
+
+  selected = !!selected;
+  if (tree_column->selected != selected)
+    {
+      PsppSheetSelection *selection;
+      PsppSheetView *sheet_view;
+
+      if (tree_column->tree_view != NULL)
+        gtk_widget_queue_draw (GTK_WIDGET (tree_column->tree_view));
+      tree_column->selected = (selected?TRUE:FALSE);
+      g_object_notify (G_OBJECT (tree_column), "selected");
+
+      sheet_view = PSPP_SHEET_VIEW (pspp_sheet_view_column_get_tree_view (
+                                      tree_column));
+      selection = pspp_sheet_view_get_selection (sheet_view);
+      _pspp_sheet_selection_emit_changed (selection);
+    }
+}
+
+/**
+ * pspp_sheet_view_column_get_selected:
+ * @tree_column: A #PsppSheetViewColumn
+ *
+ * Returns %TRUE if the column is selected as part of a rectangular
+ * selection.
+ *
+ * Return value: %TRUE if the column is selected as part of a rectangular
+ * selection.
+ **/
+gboolean
+pspp_sheet_view_column_get_selected (PsppSheetViewColumn *tree_column)
+{
+  g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
+
+  return tree_column->selected;
+}
+
+/**
+ * pspp_sheet_view_column_set_selectable:
+ * @tree_column: A #PsppSheetViewColumn
+ * @selectable: If true, the column may be selected as part of a rectangular
+ * selection.
+ **/
+void
+pspp_sheet_view_column_set_selectable (PsppSheetViewColumn *tree_column,
+                                     gboolean           selectable)
+{
+  g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
+
+  selectable = !!selectable;
+  if (tree_column->selectable != selectable)
+    {
+      if (tree_column->tree_view != NULL)
+        gtk_widget_queue_draw (GTK_WIDGET (tree_column->tree_view));
+      tree_column->selectable = (selectable?TRUE:FALSE);
+      g_object_notify (G_OBJECT (tree_column), "selectable");
+    }
+}
+
+/**
+ * pspp_sheet_view_column_get_selectable:
+ * @tree_column: A #PsppSheetViewColumn
+ *
+ * Returns %TRUE if the column may be selected as part of a rectangular
+ * selection.
+ *
+ * Return value: %TRUE if the column may be selected as part of a rectangular
+ * selection.
+ **/
+gboolean
+pspp_sheet_view_column_get_selectable (PsppSheetViewColumn *tree_column)
+{
+  g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
+
+  return tree_column->selectable;
+}
+
+
+/**
+ * pspp_sheet_view_column_set_row_head:
+ * @tree_column: A #PsppSheetViewColumn
+ * @row_head: If true, the column is a "row head", analogous to a column head.
+ * See the description of the row-head property for more information.
+ **/
+void
+pspp_sheet_view_column_set_row_head (PsppSheetViewColumn *tree_column,
+                                     gboolean           row_head)
+{
+  g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
+
+  row_head = !!row_head;
+  if (tree_column->row_head != row_head)
+    {
+      tree_column->row_head = (row_head?TRUE:FALSE);
+      g_object_notify (G_OBJECT (tree_column), "row_head");
+    }
+}
+
+/**
+ * pspp_sheet_view_column_get_row_head:
+ * @tree_column: A #PsppSheetViewColumn
+ *
+ * Returns %TRUE if the column is a row head.
+ *
+ * Return value: %TRUE if the column is a row head.
+ **/
+gboolean
+pspp_sheet_view_column_get_row_head (PsppSheetViewColumn *tree_column)
+{
+  g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
+
+  return tree_column->row_head;
+}
+
+
 /**
  * pspp_sheet_view_column_set_sort_column_id:
  * @tree_column: a #PsppSheetViewColumn
index 419a326233f39552a7a7495210ee0a57331d80f6..8e4df86b4db821dc94c538249e33470a8f37d9a1 100644 (file)
@@ -40,7 +40,6 @@
 
 G_BEGIN_DECLS
 
-
 #define PSPP_TYPE_SHEET_VIEW_COLUMN         (pspp_sheet_view_column_get_type ())
 #define PSPP_SHEET_VIEW_COLUMN(obj)         (G_TYPE_CHECK_INSTANCE_CAST ((obj), PSPP_TYPE_SHEET_VIEW_COLUMN, PsppSheetViewColumn))
 #define PSPP_SHEET_VIEW_COLUMN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PSPP_TYPE_SHEET_VIEW_COLUMN, PsppSheetViewColumnClass))
@@ -107,13 +106,18 @@ struct _PsppSheetViewColumn
   guint GSEAL (use_resized_width)   : 1;
   guint GSEAL (expand)              : 1;
   guint GSEAL (quick_edit)          : 1;
+  guint GSEAL (selected)            : 1;
+  guint GSEAL (selectable)          : 1;
+  guint GSEAL (row_head)            : 1;
 };
 
 struct _PsppSheetViewColumnClass
 {
   GtkObjectClass parent_class;
 
-  void (*clicked) (PsppSheetViewColumn *tree_column);
+  gboolean (*clicked) (PsppSheetViewColumn *tree_column);
+  gboolean (*button_press_event) (PsppSheetViewColumn *,
+                                  GdkEventButton *);
 
   /* Padding for future expansion */
   void (*_gtk_reserved1) (void);
@@ -198,6 +202,16 @@ gboolean                pspp_sheet_view_column_get_reorderable     (PsppSheetVie
 void                    pspp_sheet_view_column_set_quick_edit     (PsppSheetViewColumn       *tree_column,
                                                                  gboolean                 quick_edit);
 gboolean                pspp_sheet_view_column_get_quick_edit     (PsppSheetViewColumn       *tree_column);
+void                    pspp_sheet_view_column_set_selected     (PsppSheetViewColumn       *tree_column,
+                                                                 gboolean                 selected);
+gboolean                pspp_sheet_view_column_get_selected     (PsppSheetViewColumn       *tree_column);
+
+void                    pspp_sheet_view_column_set_selectable     (PsppSheetViewColumn       *tree_column,
+                                                                 gboolean                 selectable);
+gboolean                pspp_sheet_view_column_get_selectable     (PsppSheetViewColumn       *tree_column);
+void                    pspp_sheet_view_column_set_row_head     (PsppSheetViewColumn       *tree_column,
+                                                                 gboolean                 row_head);
+gboolean                pspp_sheet_view_column_get_row_head     (PsppSheetViewColumn       *tree_column);
 
 
 
index a777809cfcd3a908fb5699722e9d79658978efd0..a867d425dc118c8ebf1b4a7e440a87f35c04472b 100644 (file)
@@ -385,6 +385,10 @@ static void     pspp_sheet_view_put                       (PsppSheetView      *t
                                                         gint              height);
 static gboolean pspp_sheet_view_start_editing             (PsppSheetView      *tree_view,
                                                         GtkTreePath      *cursor_path);
+static gboolean pspp_sheet_view_editable_button_press_event (GtkWidget *,
+                                                             GdkEventButton *,
+                                                             PsppSheetView *);
+static void pspp_sheet_view_editable_clicked (GtkButton *, PsppSheetView *);
 static void pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
                                              PsppSheetViewColumn *column,
                                              GtkTreePath       *path,
@@ -577,7 +581,7 @@ pspp_sheet_view_class_init (PsppSheetViewClass *class)
      * Enables of disables the hover selection mode of @tree_view.
      * Hover selection makes the selected row follow the pointer.
      * Currently, this works only for the selection modes 
-     * %GTK_SELECTION_SINGLE and %GTK_SELECTION_BROWSE.
+     * %PSPP_SHEET_SELECTION_SINGLE and %PSPP_SHEET_SELECTION_BROWSE.
      *
      * This mode is primarily intended for treeviews in popups, e.g.
      * in #GtkComboBox or #GtkEntryCompletion.
@@ -1006,6 +1010,8 @@ pspp_sheet_view_init (PsppSheetView *tree_view)
   tree_view->priv->prelight_node = -1;
   tree_view->priv->rubber_band_start_node = -1;
   tree_view->priv->rubber_band_end_node = -1;
+
+  tree_view->priv->anchor_column = NULL;
 }
 
 \f
@@ -2032,6 +2038,137 @@ pspp_sheet_view_node_prev (PsppSheetView *tree_view,
   return node > 0 ? node - 1 : -1;
 }
 
+static gboolean
+all_columns_selected (PsppSheetView *tree_view)
+{
+  GList *list;
+
+  for (list = tree_view->priv->columns; list; list = list->next)
+    {
+      PsppSheetViewColumn *column = list->data;
+      if (column->selectable && !column->selected)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+pspp_sheet_view_row_head_clicked (PsppSheetView *tree_view,
+                                  gint node,
+                                  PsppSheetViewColumn *column,
+                                  GdkEventButton *event)
+{
+  PsppSheetSelection *selection;
+  PsppSheetSelectionMode mode;
+  GtkTreePath *path;
+  gboolean update_anchor;
+  gboolean handled;
+  guint modifiers;
+
+  g_return_val_if_fail (tree_view != NULL, FALSE);
+  g_return_val_if_fail (column != NULL, FALSE);
+
+  selection = tree_view->priv->selection;
+  mode = pspp_sheet_selection_get_mode (selection);
+  if (mode != PSPP_SHEET_SELECTION_RECTANGLE)
+    return FALSE;
+
+  if (!column->row_head)
+    return FALSE;
+
+  if (event)
+    {
+      modifiers = event->state & gtk_accelerator_get_default_mod_mask ();
+      if (event->type != GDK_BUTTON_PRESS
+          || (modifiers != GDK_CONTROL_MASK && modifiers != GDK_SHIFT_MASK))
+        return FALSE;
+    }
+  else
+    modifiers = 0;
+
+  path = gtk_tree_path_new_from_indices (node, -1);
+  if (event == NULL)
+    {
+      pspp_sheet_selection_unselect_all (selection);
+      pspp_sheet_selection_select_path (selection, path);
+      pspp_sheet_selection_select_all_columns (selection);
+      update_anchor = TRUE;
+      handled = TRUE;
+    }
+  else if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+    {
+      if (pspp_sheet_selection_count_selected_rows (selection) <= 1
+          || !all_columns_selected (tree_view))
+        {
+          pspp_sheet_selection_unselect_all (selection);
+          pspp_sheet_selection_select_path (selection, path);
+          pspp_sheet_selection_select_all_columns (selection);
+          update_anchor = TRUE;
+          handled = FALSE;
+        }
+      else
+        update_anchor = handled = FALSE;
+    }
+  else if (event->type == GDK_BUTTON_PRESS && event->button == 1
+           && modifiers == GDK_CONTROL_MASK)
+    {
+      if (!all_columns_selected (tree_view))
+        {
+          pspp_sheet_selection_unselect_all (selection);
+          pspp_sheet_selection_select_all_columns (selection);
+        }
+
+      if (pspp_sheet_selection_path_is_selected (selection, path))
+        pspp_sheet_selection_unselect_path (selection, path);
+      else
+        pspp_sheet_selection_select_path (selection, path);
+      update_anchor = TRUE;
+      handled = TRUE;
+    }
+  else if (event->type == GDK_BUTTON_PRESS && event->button == 1
+           && modifiers == GDK_SHIFT_MASK)
+    {
+      GtkTreeRowReference *anchor = tree_view->priv->anchor;
+      GtkTreePath *anchor_path;
+
+      if (all_columns_selected (tree_view)
+          && gtk_tree_row_reference_valid (anchor))
+        {
+          update_anchor = FALSE;
+          anchor_path = gtk_tree_row_reference_get_path (anchor);
+        }
+      else
+        {
+          update_anchor = TRUE;
+          anchor_path = gtk_tree_path_copy (path);
+        }
+
+      pspp_sheet_selection_unselect_all (selection);
+      pspp_sheet_selection_select_range (selection, anchor_path, path);
+      pspp_sheet_selection_select_all_columns (selection);
+
+      gtk_tree_path_free (anchor_path);
+
+      handled = TRUE;
+    }
+  else
+    update_anchor = handled = FALSE;
+
+  if (update_anchor)
+    {
+      if (tree_view->priv->anchor)
+        gtk_tree_row_reference_free (tree_view->priv->anchor);
+      tree_view->priv->anchor =
+        gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
+                                          tree_view->priv->model,
+                                          path);
+    }
+
+  gtk_tree_path_free (path);
+  return handled;
+}
+
 static gboolean
 pspp_sheet_view_button_press (GtkWidget      *widget,
                            GdkEventButton *event)
@@ -2074,6 +2211,7 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
       gboolean row_double_click = FALSE;
       gboolean rtl;
       gboolean node_selected;
+      guint modifiers;
 
       /* Empty tree? */
       if (tree_view->priv->row_count == 0)
@@ -2142,8 +2280,8 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
       tree_view->priv->focus_column = column;
 
       /* decide if we edit */
-      if (event->type == GDK_BUTTON_PRESS && event->button == 1 &&
-         !(event->state & gtk_accelerator_get_default_mod_mask ()))
+      modifiers = event->state & gtk_accelerator_get_default_mod_mask ();
+      if (event->type == GDK_BUTTON_PRESS && event->button == 1 && !modifiers)
        {
          GtkTreePath *anchor;
          GtkTreeIter iter;
@@ -2159,7 +2297,7 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
            anchor = NULL;
 
          if (pspp_sheet_view_column_get_quick_edit (column)
-              || (anchor && !gtk_tree_path_compare (anchor, path))
+              //|| (anchor && !gtk_tree_path_compare (anchor, path))
               || !_pspp_sheet_view_column_has_editable_cell (column))
            {
              GtkCellEditable *cell_editable = NULL;
@@ -2181,6 +2319,10 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
                      gint left, right;
                      GdkRectangle area;
 
+                      pspp_sheet_view_real_set_cursor (tree_view, path,
+                                                       TRUE, TRUE);
+                      gtk_widget_queue_draw (GTK_WIDGET (tree_view));
+
                      area = cell_area;
                      _pspp_sheet_view_column_get_neighbor_sizes (column,       _pspp_sheet_view_column_get_edited_cell (column), &left, &right);
 
@@ -2207,6 +2349,9 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
            gtk_tree_path_free (anchor);
        }
 
+      if (pspp_sheet_view_row_head_clicked (tree_view, node, column, event))
+        return TRUE;
+
       /* select */
       node_selected = pspp_sheet_view_node_is_selected (tree_view, node);
       pre_val = tree_view->priv->vadjustment->value;
@@ -2231,7 +2376,7 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
             }
           else if (event->state & GDK_SHIFT_MASK)
             {
-              pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE);
+              pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE);
               pspp_sheet_view_real_select_cursor_row (tree_view, FALSE);
             }
           else
@@ -2239,6 +2384,14 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
               pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE);
             }
 
+          if (tree_view->priv->anchor_column == NULL ||
+              !(event->state & GDK_SHIFT_MASK))
+            tree_view->priv->anchor_column = column;
+          pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
+          pspp_sheet_selection_select_column_range (tree_view->priv->selection,
+                                                    tree_view->priv->anchor_column,
+                                                    column);
+
           tree_view->priv->ctrl_pressed = FALSE;
           tree_view->priv->shift_pressed = FALSE;
         }
@@ -2264,8 +2417,9 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
           tree_view->priv->press_start_y = event->y;
 
          if (tree_view->priv->rubber_banding_enable
-             && !node_selected
-             && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)
+             //&& !node_selected
+             && (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
+                  tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE))
            {
              tree_view->priv->press_start_y += tree_view->priv->dy;
              tree_view->priv->rubber_band_x = event->x;
@@ -2276,6 +2430,7 @@ pspp_sheet_view_button_press (GtkWidget      *widget,
                tree_view->priv->rubber_band_ctrl = TRUE;
              if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
                tree_view->priv->rubber_band_shift = TRUE;
+
            }
         }
 
@@ -2548,10 +2703,10 @@ prelight_or_select (PsppSheetView *tree_view,
                    gint         x,
                    gint         y)
 {
-  GtkSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
+  PsppSheetSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
   
   if (tree_view->priv->hover_selection &&
-      (mode == GTK_SELECTION_SINGLE || mode == GTK_SELECTION_BROWSE) &&
+      (mode == PSPP_SHEET_SELECTION_SINGLE || mode == PSPP_SHEET_SELECTION_BROWSE) &&
       !(tree_view->priv->edited_column &&
        tree_view->priv->edited_column->editable_widget))
     {
@@ -2572,7 +2727,7 @@ prelight_or_select (PsppSheetView *tree_view,
            }
        }
 
-      else if (mode == GTK_SELECTION_SINGLE)
+      else if (mode == PSPP_SHEET_SELECTION_SINGLE)
        pspp_sheet_selection_unselect_all (tree_view->priv->selection);
     }
 
@@ -3264,6 +3419,7 @@ pspp_sheet_view_update_rubber_band (PsppSheetView *tree_view)
   GdkRectangle new_area;
   GdkRectangle common;
   GdkRegion *invalid_region;
+  PsppSheetViewColumn *column;
 
   old_area.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubber_band_x);
   old_area.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y) - tree_view->priv->dy;
@@ -3306,6 +3462,14 @@ pspp_sheet_view_update_rubber_band (PsppSheetView *tree_view)
 
   tree_view->priv->rubber_band_x = x;
   tree_view->priv->rubber_band_y = y;
+  pspp_sheet_view_get_path_at_pos (tree_view, x, y, NULL, &column, NULL, NULL);
+
+  pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
+  pspp_sheet_selection_select_column_range (tree_view->priv->selection,
+                                            tree_view->priv->anchor_column,
+                                            column);
+
+  gtk_widget_queue_draw (GTK_WIDGET (tree_view));
 
   pspp_sheet_view_update_rubber_band_selection (tree_view);
 }
@@ -3318,6 +3482,7 @@ pspp_sheet_view_paint_rubber_band (PsppSheetView  *tree_view,
   GdkRectangle rect;
   GdkRectangle rubber_rect;
 
+  return;
   rubber_rect.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubber_band_x);
   rubber_rect.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y) - tree_view->priv->dy;
   rubber_rect.width = ABS (tree_view->priv->press_start_x - tree_view->priv->rubber_band_x) + 1;
@@ -3656,6 +3821,7 @@ pspp_sheet_view_bin_expose (GtkWidget      *widget,
       gboolean is_first = FALSE;
       gboolean is_last = FALSE;
       gboolean done = FALSE;
+      gboolean selected;
 
       max_height = ROW_HEIGHT (tree_view);
 
@@ -3669,8 +3835,7 @@ pspp_sheet_view_bin_expose (GtkWidget      *widget,
       if (node == tree_view->priv->prelight_node)
        flags |= GTK_CELL_RENDERER_PRELIT;
 
-      if (pspp_sheet_view_node_is_selected (tree_view, node))
-        flags |= GTK_CELL_RENDERER_SELECTED;
+      selected = pspp_sheet_view_node_is_selected (tree_view, node);
 
       parity = node % 2;
 
@@ -3696,11 +3861,17 @@ pspp_sheet_view_bin_expose (GtkWidget      *widget,
        {
          PsppSheetViewColumn *column = list->data;
          const gchar *detail = NULL;
+          gboolean selected_column;
          GtkStateType state;
 
          if (!column->visible)
             continue;
 
+          if (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
+            selected_column = column->selected && column->selectable;
+          else
+            selected_column = TRUE;
+
          if (cell_offset > event->area.x + event->area.width ||
              cell_offset + column->width < event->area.x)
            {
@@ -3708,6 +3879,11 @@ pspp_sheet_view_bin_expose (GtkWidget      *widget,
              continue;
            }
 
+          if (selected && selected_column)
+            flags |= GTK_CELL_RENDERER_SELECTED;
+          else
+            flags &= ~GTK_CELL_RENDERER_SELECTED;
+
           if (column->show_sort_indicator)
            flags |= GTK_CELL_RENDERER_SORTED;
           else
@@ -5485,6 +5661,7 @@ scroll_row_timeout (gpointer data)
 {
   PsppSheetView *tree_view = data;
 
+  pspp_sheet_view_horizontal_autoscroll (tree_view);
   pspp_sheet_view_vertical_autoscroll (tree_view);
 
   if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
@@ -6400,6 +6577,7 @@ pspp_sheet_view_header_focus (PsppSheetView      *tree_view,
 
 /* This function returns in 'path' the first focusable path, if the given path
  * is already focusable, it's the returned one.
+ *
  */
 static gboolean
 search_first_focusable_path (PsppSheetView  *tree_view,
@@ -6407,6 +6585,8 @@ search_first_focusable_path (PsppSheetView  *tree_view,
                             gboolean      search_forward,
                             int *new_node)
 {
+  /* XXX this function is trivial given that the sheetview doesn't support
+     separator rows */
   int node = -1;
 
   if (!path || !*path)
@@ -7388,8 +7568,8 @@ pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
 
   if (cursor_path == NULL)
     {
-      /* Consult the selection before defaulting to the
-       * first focusable element
+      /* There's no cursor.  Move the cursor to the first selected row, if any
+       * are selected, otherwise to the first row in the sheetview.
        */
       GList *selected_rows;
       GtkTreeModel *model;
@@ -7400,6 +7580,7 @@ pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
 
       if (selected_rows)
        {
+          /* XXX we could avoid doing O(n) work to get this result */
           cursor_path = gtk_tree_path_copy((const GtkTreePath *)(selected_rows->data));
          g_list_foreach (selected_rows, (GFunc)gtk_tree_path_free, NULL);
          g_list_free (selected_rows);
@@ -7416,7 +7597,8 @@ pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
 
       if (cursor_path)
        {
-         if (tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)
+         if (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
+              tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
            pspp_sheet_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE);
          else
            pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
@@ -7425,6 +7607,7 @@ pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
 
   if (cursor_path)
     {
+      /* Now find a column for the cursor. */
       PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
 
       pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
@@ -7438,9 +7621,12 @@ pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
              if (PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
                {
                  tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (list->data);
+                  pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
+                  pspp_sheet_selection_select_column (tree_view->priv->selection, tree_view->priv->focus_column);
                  break;
                }
            }
+
        }
     }
 }
@@ -7473,7 +7659,7 @@ pspp_sheet_view_move_cursor_up_down (PsppSheetView *tree_view,
   selection_count = pspp_sheet_selection_count_selected_rows (tree_view->priv->selection);
 
   if (selection_count == 0
-      && tree_view->priv->selection->type != GTK_SELECTION_NONE
+      && tree_view->priv->selection->type != PSPP_SHEET_SELECTION_NONE
       && !tree_view->priv->ctrl_pressed)
     {
       /* Don't move the cursor, but just select the current node */
@@ -7505,7 +7691,8 @@ pspp_sheet_view_move_cursor_up_down (PsppSheetView *tree_view,
    * If the list has only one item and multi-selection is set then select
    * the row (if not yet selected).
    */
-  if (tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE &&
+  if ((tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
+       tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE) &&
       new_cursor_node < 0)
     {
       if (count == -1)
@@ -7801,7 +7988,8 @@ pspp_sheet_view_real_select_all (PsppSheetView *tree_view)
   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
     return FALSE;
 
-  if (tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE)
+  if (tree_view->priv->selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
+      tree_view->priv->selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
     return FALSE;
 
   pspp_sheet_selection_select_all (tree_view->priv->selection);
@@ -7815,7 +8003,8 @@ pspp_sheet_view_real_unselect_all (PsppSheetView *tree_view)
   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
     return FALSE;
 
-  if (tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE)
+  if (tree_view->priv->selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
+      tree_view->priv->selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
     return FALSE;
 
   pspp_sheet_selection_unselect_all (tree_view->priv->selection);
@@ -9401,6 +9590,11 @@ pspp_sheet_view_set_reorderable (PsppSheetView *tree_view,
   g_object_notify (G_OBJECT (tree_view), "reorderable");
 }
 
+/* If CLEAR_AND_SELECT is true, then the row will be selected and, unless Shift
+   is pressed, other rows will be unselected.
+
+   If CLAMP_NODE is true, then the sheetview will scroll to make the row
+   visible. */
 static void
 pspp_sheet_view_real_set_cursor (PsppSheetView     *tree_view,
                               GtkTreePath     *path,
@@ -9595,6 +9789,10 @@ pspp_sheet_view_set_cursor_on_cell (PsppSheetView       *tree_view,
        pspp_sheet_view_column_focus_cell (focus_column, focus_cell);
       if (start_editing)
        pspp_sheet_view_start_editing (tree_view, path);
+
+      pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
+      pspp_sheet_selection_select_column (tree_view->priv->selection, focus_column);
+
     }
 }
 
@@ -11508,6 +11706,12 @@ pspp_sheet_view_remove_widget (GtkCellEditable *cell_editable,
   g_signal_handlers_disconnect_by_func (cell_editable,
                                        pspp_sheet_view_remove_widget,
                                        tree_view);
+  g_signal_handlers_disconnect_by_func (cell_editable,
+                                       pspp_sheet_view_editable_button_press_event,
+                                       tree_view);
+  g_signal_handlers_disconnect_by_func (cell_editable,
+                                       pspp_sheet_view_editable_clicked,
+                                       tree_view);
 
   gtk_container_remove (GTK_CONTAINER (tree_view),
                        GTK_WIDGET (cell_editable));  
@@ -11590,6 +11794,29 @@ pspp_sheet_view_start_editing (PsppSheetView *tree_view,
   return retval;
 }
 
+static gboolean
+pspp_sheet_view_editable_button_press_event (GtkWidget *widget,
+                                             GdkEventButton *event,
+                                             PsppSheetView *sheet_view)
+{
+  gint node;
+
+  node = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
+                                             "pspp-sheet-view-node"));
+  return pspp_sheet_view_row_head_clicked (sheet_view,
+                                           node,
+                                           sheet_view->priv->edited_column,
+                                           event);
+}
+
+static void
+pspp_sheet_view_editable_clicked (GtkButton *button,
+                                  PsppSheetView *sheet_view)
+{
+  pspp_sheet_view_editable_button_press_event (GTK_WIDGET (button), NULL,
+                                               sheet_view);
+}
+
 static void
 pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
                                  PsppSheetViewColumn *column,
@@ -11599,6 +11826,7 @@ pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
                                  GdkEvent          *event,
                                  guint              flags)
 {
+  PsppSheetSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
   gint pre_val = tree_view->priv->vadjustment->value;
   GtkRequisition requisition;
 
@@ -11608,6 +11836,10 @@ pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
   pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE);
   cell_area->y += pre_val - (int)tree_view->priv->vadjustment->value;
 
+  pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
+  pspp_sheet_selection_select_column (tree_view->priv->selection, column);
+  tree_view->priv->anchor_column = column;
+
   gtk_widget_size_request (GTK_WIDGET (cell_editable), &requisition);
 
   PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
@@ -11634,6 +11866,18 @@ pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
   gtk_widget_grab_focus (GTK_WIDGET (cell_editable));
   g_signal_connect (cell_editable, "remove-widget",
                    G_CALLBACK (pspp_sheet_view_remove_widget), tree_view);
+  if (mode == PSPP_SHEET_SELECTION_RECTANGLE && column->row_head &&
+      GTK_IS_BUTTON (cell_editable))
+    {
+      g_signal_connect (cell_editable, "button-press-event",
+                        G_CALLBACK (pspp_sheet_view_editable_button_press_event),
+                        tree_view);
+      g_object_set_data (G_OBJECT (cell_editable), "pspp-sheet-view-node",
+                         GINT_TO_POINTER (gtk_tree_path_get_indices (path)[0]));
+      g_signal_connect (cell_editable, "clicked",
+                        G_CALLBACK (pspp_sheet_view_editable_clicked),
+                        tree_view);
+    }
 }
 
 static void
@@ -11679,7 +11923,7 @@ pspp_sheet_view_stop_editing (PsppSheetView *tree_view,
  * Enables of disables the hover selection mode of @tree_view.
  * Hover selection makes the selected row follow the pointer.
  * Currently, this works only for the selection modes 
- * %GTK_SELECTION_SINGLE and %GTK_SELECTION_BROWSE.
+ * %PSPP_SHEET_SELECTION_SINGLE and %PSPP_SHEET_SELECTION_BROWSE.
  * 
  * Since: 2.6
  **/
@@ -11718,9 +11962,9 @@ pspp_sheet_view_get_hover_selection (PsppSheetView *tree_view)
  * @tree_view: a #PsppSheetView
  * @enable: %TRUE to enable rubber banding
  *
- * Enables or disables rubber banding in @tree_view.  If the selection mode
- * is #GTK_SELECTION_MULTIPLE, rubber banding will allow the user to select
- * multiple rows by dragging the mouse.
+ * Enables or disables rubber banding in @tree_view.  If the selection mode is
+ * #PSPP_SHEET_SELECTION_MULTIPLE or #PSPP_SHEET_SELECTION_RECTANGLE, rubber
+ * banding will allow the user to select multiple rows by dragging the mouse.
  * 
  * Since: 2.10
  **/
@@ -11743,8 +11987,9 @@ pspp_sheet_view_set_rubber_banding (PsppSheetView *tree_view,
  * @tree_view: a #PsppSheetView
  * 
  * Returns whether rubber banding is turned on for @tree_view.  If the
- * selection mode is #GTK_SELECTION_MULTIPLE, rubber banding will allow the
- * user to select multiple rows by dragging the mouse.
+ * selection mode is #PSPP_SHEET_SELECTION_MULTIPLE or
+ * #PSPP_SHEET_SELECTION_RECTANGLE, rubber banding will allow the user to
+ * select multiple rows by dragging the mouse.
  * 
  * Return value: %TRUE if rubber banding in @tree_view is enabled.
  *