From: Ben Pfaff Date: Tue, 28 Jun 2011 05:17:57 +0000 (-0700) Subject: pspp-sheet-view: Support rectangular selection, column popup menus. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?p=pspp;a=commitdiff_plain;h=3bc8bee4dbb8a204fb61b0dfe97c4468899dd1c3 pspp-sheet-view: Support rectangular selection, column popup menus. 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. --- diff --git a/src/ui/gui/marshaller-list b/src/ui/gui/marshaller-list index 3cf5a9c87e..1f85ee6d70 100644 --- a/src/ui/gui/marshaller-list +++ b/src/ui/gui/marshaller-list @@ -2,6 +2,7 @@ BOOLEAN:BOOLEAN BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN +BOOLEAN:BOXED BOOLEAN:ENUM BOOLEAN:ENUM,INT BOOLEAN:BOXED,BOXED diff --git a/src/ui/gui/pspp-sheet-private.h b/src/ui/gui/pspp-sheet-private.h index 4658955b00..9d99cbd8a5 100644 --- a/src/ui/gui/pspp-sheet-private.h +++ b/src/ui/gui/pspp-sheet-private.h @@ -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; diff --git a/src/ui/gui/pspp-sheet-selection.c b/src/ui/gui/pspp-sheet-selection.c index ac75e27237..9cd65028b4 100644 --- a/src/ui/gui/pspp-sheet-selection.c +++ b/src/ui/gui/pspp-sheet-selection.c @@ -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)); + } +} diff --git a/src/ui/gui/pspp-sheet-selection.h b/src/ui/gui/pspp-sheet-selection.h index 306aa4abd2..4d8c50bcf1 100644 --- a/src/ui/gui/pspp-sheet-selection.h +++ b/src/ui/gui/pspp-sheet-selection.h @@ -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 diff --git a/src/ui/gui/pspp-sheet-view-column.c b/src/ui/gui/pspp-sheet-view-column.c index 9f88156fd2..88a624dd44 100644 --- a/src/ui/gui/pspp-sheet-view-column.c +++ b/src/ui/gui/pspp-sheet-view-column.c @@ -43,6 +43,7 @@ #include #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 diff --git a/src/ui/gui/pspp-sheet-view-column.h b/src/ui/gui/pspp-sheet-view-column.h index 419a326233..8e4df86b4d 100644 --- a/src/ui/gui/pspp-sheet-view-column.h +++ b/src/ui/gui/pspp-sheet-view-column.h @@ -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); diff --git a/src/ui/gui/pspp-sheet-view.c b/src/ui/gui/pspp-sheet-view.c index a777809cfc..a867d425dc 100644 --- a/src/ui/gui/pspp-sheet-view.c +++ b/src/ui/gui/pspp-sheet-view.c @@ -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; } @@ -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. *