diff --git a/gresources/nemo-file-management-properties.glade b/gresources/nemo-file-management-properties.glade index 7bb4fffbe..3a2f540e4 100644 --- a/gresources/nemo-file-management-properties.glade +++ b/gresources/nemo-file-management-properties.glade @@ -1207,6 +1207,22 @@ along with . If not, see . 0 + + + Enable item selection checkboxes + True + True + False + True + 0 + True + + + False + False + 1 + + Ignore per-folder view preferences @@ -1220,7 +1236,7 @@ along with . If not, see . False False - 1 + 2 @@ -1236,7 +1252,7 @@ along with . If not, see . False False - 2 + 3 @@ -1253,7 +1269,7 @@ along with . If not, see . False False 3 - 3 + 4 @@ -1270,7 +1286,7 @@ along with . If not, see . False False 3 - 4 + 5 @@ -1286,7 +1302,7 @@ along with . If not, see . False False - 5 + 6 diff --git a/libnemo-private/nemo-global-preferences.h b/libnemo-private/nemo-global-preferences.h index 91f404854..b7e1f0d4c 100644 --- a/libnemo-private/nemo-global-preferences.h +++ b/libnemo-private/nemo-global-preferences.h @@ -145,6 +145,7 @@ typedef enum #define NEMO_PREFERENCES_START_WITH_DUAL_PANE "start-with-dual-pane" #define NEMO_PREFERENCES_RESTORE_TABS_ON_STARTUP "restore-tabs-on-startup" +#define NEMO_PREFERENCES_SHOW_SELECTION_CHECKBOXES "show-selection-checkboxes" #define NEMO_PREFERENCES_IGNORE_VIEW_METADATA "ignore-view-metadata" #define NEMO_PREFERENCES_SHOW_BOOKMARKS_IN_TO_MENUS "show-bookmarks-in-to-menus" #define NEMO_PREFERENCES_SHOW_PLACES_IN_TO_MENUS "show-places-in-to-menus" diff --git a/libnemo-private/nemo-icon-canvas-item.c b/libnemo-private/nemo-icon-canvas-item.c index 665f78f15..e7adbb844 100644 --- a/libnemo-private/nemo-icon-canvas-item.c +++ b/libnemo-private/nemo-icon-canvas-item.c @@ -51,6 +51,7 @@ /* gap between bottom of icon and start of text box */ #define LABEL_OFFSET_BESIDES 3 #define LABEL_LINE_SPACING 0 +#define SELECTION_CHECKBOX_MARGIN 2 /* special text height handling @@ -174,6 +175,12 @@ static void draw_pixbuf (GdkPixbuf cairo_t *cr, int x, int y); +static void draw_selection_checkbox (NemoIconCanvasItem *item, + cairo_t *cr, + EelIRect icon_rect); +static double get_selection_checkbox_size (GtkWidget *widget); +static GdkPixbuf *get_selection_checkbox_pixbuf (GtkWidget *widget, + gboolean selected); static PangoLayout *get_label_layout (PangoLayout **layout, NemoIconCanvasItem *item, const char *text); @@ -1288,6 +1295,100 @@ draw_pixbuf (GdkPixbuf *pixbuf, cairo_restore (cr); } +static double +get_selection_checkbox_size (GtkWidget *widget) +{ + int width = 0; + int height = 0; + + if (gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget), + GTK_ICON_SIZE_MENU, + &width, + &height)) { + return MAX (width, height); + } + + return 16.0; +} + +static GdkPixbuf * +get_selection_checkbox_pixbuf (GtkWidget *widget, + gboolean selected) +{ + GtkIconTheme *icon_theme; + GtkStyleContext *context; + GtkIconInfo *icon_info; + GdkRGBA fg_color; + GdkPixbuf *pixbuf; + int icon_size; + const char *icon_name; + GError *error = NULL; + gboolean was_symbolic = FALSE; + + context = gtk_widget_get_style_context (widget); + icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + icon_size = (int) get_selection_checkbox_size (widget); + icon_name = selected ? "checkbox-checked-symbolic" : "checkbox-symbolic"; + + icon_info = gtk_icon_theme_lookup_icon (icon_theme, + icon_name, + icon_size, + GTK_ICON_LOOKUP_FORCE_SIZE); + if (icon_info == NULL) { + return NULL; + } + + gtk_style_context_get_color (context, GTK_STATE_FLAG_SELECTED, &fg_color); + pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, + context, + &was_symbolic, + &error); + g_object_unref (icon_info); + + if (pixbuf == NULL) { + g_clear_error (&error); + } + + return pixbuf; +} + +static void +draw_selection_checkbox (NemoIconCanvasItem *icon_item, + cairo_t *cr, + EelIRect icon_rect) +{ + GtkWidget *widget; + double pixels_per_unit; + gboolean selected; + gboolean show_selection_checkboxes; + GdkPixbuf *checkbox_pixbuf; + + show_selection_checkboxes = g_settings_get_boolean (nemo_preferences, + NEMO_PREFERENCES_SHOW_SELECTION_CHECKBOXES); + if (!show_selection_checkboxes) { + return; + } + + selected = icon_item->details->is_highlighted_for_selection; + if (!icon_item->details->is_prelit && !selected) { + return; + } + + widget = GTK_WIDGET (EEL_CANVAS_ITEM (icon_item)->canvas); + pixels_per_unit = EEL_CANVAS_ITEM (icon_item)->canvas->pixels_per_unit; + checkbox_pixbuf = get_selection_checkbox_pixbuf (widget, selected); + if (checkbox_pixbuf == NULL) { + return; + } + + draw_pixbuf (checkbox_pixbuf, + cr, + icon_rect.x0 + (SELECTION_CHECKBOX_MARGIN / pixels_per_unit), + icon_rect.y0 + (SELECTION_CHECKBOX_MARGIN / pixels_per_unit)); + g_object_unref (checkbox_pixbuf); + +} + /* shared code to highlight or dim the passed-in pixbuf */ static cairo_surface_t * real_map_surface (NemoIconCanvasItem *icon_item) @@ -1387,6 +1488,8 @@ nemo_icon_canvas_item_draw (EelCanvasItem *item, icon_rect.x0, icon_rect.y0); cairo_surface_destroy (temp_surface); + draw_selection_checkbox (icon_item, cr, icon_rect); + /* Draw stretching handles (if necessary). */ draw_stretch_handles (icon_item, cr, &icon_rect); @@ -1988,6 +2091,38 @@ nemo_icon_canvas_item_hit_test_rectangle (NemoIconCanvasItem *item, EelIRect can return hit_test (item, canvas_rect); } +gboolean +nemo_icon_canvas_item_hit_test_selection_checkbox (NemoIconCanvasItem *item, + gdouble world_x, + gdouble world_y) +{ + EelDRect icon_rect; + double pixels_per_unit; + double checkbox_size; + double checkbox_x; + double checkbox_y; + gboolean show_selection_checkboxes; + + g_return_val_if_fail (NEMO_IS_ICON_CANVAS_ITEM (item), FALSE); + + show_selection_checkboxes = g_settings_get_boolean (nemo_preferences, + NEMO_PREFERENCES_SHOW_SELECTION_CHECKBOXES); + if (!show_selection_checkboxes) { + return FALSE; + } + + icon_rect = nemo_icon_canvas_item_get_icon_rectangle (item); + pixels_per_unit = EEL_CANVAS_ITEM (item)->canvas->pixels_per_unit; + checkbox_size = get_selection_checkbox_size (GTK_WIDGET (EEL_CANVAS_ITEM (item)->canvas)) / pixels_per_unit; + checkbox_x = icon_rect.x0 + (SELECTION_CHECKBOX_MARGIN / pixels_per_unit); + checkbox_y = icon_rect.y0 + (SELECTION_CHECKBOX_MARGIN / pixels_per_unit); + + return world_x >= checkbox_x && + world_x <= checkbox_x + checkbox_size && + world_y >= checkbox_y && + world_y <= checkbox_y + checkbox_size; +} + const char * nemo_icon_canvas_item_get_editable_text (NemoIconCanvasItem *icon_item) { diff --git a/libnemo-private/nemo-icon-canvas-item.h b/libnemo-private/nemo-icon-canvas-item.h index 7a6c083b4..8314271e8 100644 --- a/libnemo-private/nemo-icon-canvas-item.h +++ b/libnemo-private/nemo-icon-canvas-item.h @@ -74,6 +74,9 @@ void nemo_icon_canvas_item_set_emblems (NemoIconCanvasItem GList *emblem_pixbufs); void nemo_icon_canvas_item_set_show_stretch_handles (NemoIconCanvasItem *item, gboolean show_stretch_handles); +gboolean nemo_icon_canvas_item_hit_test_selection_checkbox (NemoIconCanvasItem *item, + gdouble world_x, + gdouble world_y); double nemo_icon_canvas_item_get_max_text_width (NemoIconCanvasItem *item); const char *nemo_icon_canvas_item_get_editable_text (NemoIconCanvasItem *icon_item); void nemo_icon_canvas_item_set_renaming (NemoIconCanvasItem *icon_item, diff --git a/libnemo-private/nemo-icon-container.c b/libnemo-private/nemo-icon-container.c index b24d3da2b..78cedd0c7 100644 --- a/libnemo-private/nemo-icon-container.c +++ b/libnemo-private/nemo-icon-container.c @@ -5220,6 +5220,51 @@ handle_icon_button_press (NemoIconContainer *container, return TRUE; } +static gboolean +clicked_on_selection_checkbox (NemoIconContainer *container, + NemoIcon *icon, + GdkEventButton *event) +{ + gboolean show_selection_checkboxes; + double raw_x; + double raw_y; + double world_x; + double world_y; + EelDRect icon_rect; + double pixels_per_unit; + double checkbox_size; + double checkbox_x; + double checkbox_y; + + g_return_val_if_fail (NEMO_IS_ICON_CONTAINER (container), FALSE); + g_return_val_if_fail (icon != NULL, FALSE); + + show_selection_checkboxes = g_settings_get_boolean (nemo_preferences, + NEMO_PREFERENCES_SHOW_SELECTION_CHECKBOXES); + if (!show_selection_checkboxes) { + return FALSE; + } + + raw_x = event->x; + raw_y = event->y; + eel_canvas_window_to_world (EEL_CANVAS (container), event->x, event->y, + &world_x, &world_y); + if (nemo_icon_canvas_item_hit_test_selection_checkbox (icon->item, world_x, world_y)) { + return TRUE; + } + + icon_rect = nemo_icon_canvas_item_get_icon_rectangle (icon->item); + pixels_per_unit = EEL_CANVAS (container)->pixels_per_unit; + checkbox_size = 12 / pixels_per_unit; + checkbox_x = icon_rect.x0 + (2 / pixels_per_unit); + checkbox_y = icon_rect.y0 + (2 / pixels_per_unit); + + return raw_x >= checkbox_x && + raw_x <= checkbox_x + checkbox_size && + raw_y >= checkbox_y && + raw_y <= checkbox_y + checkbox_size; +} + static int item_event_callback (EelCanvasItem *item, GdkEvent *event, @@ -5234,6 +5279,13 @@ item_event_callback (EelCanvasItem *item, g_assert (icon != NULL); if (event->type == GDK_BUTTON_PRESS) { + if (clicked_on_selection_checkbox (container, icon, &event->button)) { + icon_toggle_selected (container, icon); + g_signal_emit (container, + signals[SELECTION_CHANGED], 0); + return TRUE; + } + if (handle_icon_button_press (container, icon, &event->button)) { /* Stop the event from being passed along further. Returning * TRUE ain't enough. diff --git a/libnemo-private/org.nemo.gschema.xml b/libnemo-private/org.nemo.gschema.xml index abc2814d4..24b7e8d67 100644 --- a/libnemo-private/org.nemo.gschema.xml +++ b/libnemo-private/org.nemo.gschema.xml @@ -351,6 +351,11 @@ Restore the previous window tabs on startup If set to true, Nemo will restore the last saved window tab state when launched without explicit locations. + + false + Enable item selection checkboxes + If set to true, Nemo will show clickable checkboxes on hovered items in icon and compact views, and beside all items in list view to make mouse-only multi-selection easier. + false Whether to ignore folder metadata for view zoom levels and layouts diff --git a/src/nemo-file-management-properties.c b/src/nemo-file-management-properties.c index 1566a467f..8610a527d 100644 --- a/src/nemo-file-management-properties.c +++ b/src/nemo-file-management-properties.c @@ -101,6 +101,7 @@ #define NEMO_FILE_MANAGEMENT_PROPERTIES_START_WITH_DUAL_PANE_WIDGET "start_with_dual_pane_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_RESTORE_TABS_ON_STARTUP_WIDGET "restore_tabs_on_startup_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_IGNORE_VIEW_METADATA_WIDGET "ignore_view_metadata_checkbutton" +#define NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_SELECTION_CHECKBOXES_WIDGET "show_selection_checkboxes_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_BOOKMARKS_IN_TO_MENUS_WIDGET "bookmarks_in_to_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_PLACES_IN_TO_MENUS_WIDGET "places_in_to_checkbutton" #define NEMO_FILE_MANAGEMENT_PROPERTIES_INHERIT_SHOW_THUMBNAILS_WIDGET "inherit_show_thumbnails_checkbutton" @@ -1076,6 +1077,10 @@ nemo_file_management_properties_dialog_setup (GtkBuilder *builder, NEMO_FILE_MANAGEMENT_PROPERTIES_IGNORE_VIEW_METADATA_WIDGET, NEMO_PREFERENCES_IGNORE_VIEW_METADATA); + bind_builder_bool (builder, nemo_preferences, + NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_SELECTION_CHECKBOXES_WIDGET, + NEMO_PREFERENCES_SHOW_SELECTION_CHECKBOXES); + bind_builder_bool (builder, nemo_preferences, NEMO_FILE_MANAGEMENT_PROPERTIES_BOOKMARKS_IN_TO_MENUS_WIDGET, NEMO_PREFERENCES_SHOW_BOOKMARKS_IN_TO_MENUS); diff --git a/src/nemo-list-view.c b/src/nemo-list-view.c index f027d22b4..2842ce346 100644 --- a/src/nemo-list-view.c +++ b/src/nemo-list-view.c @@ -76,6 +76,7 @@ struct NemoListViewDetails { GtkTreeViewColumn *file_name_column; int file_name_column_num; + GtkCellRendererToggle *selection_toggle_cell; GtkCellRendererPixbuf *pixbuf_cell; GtkCellRendererText *file_name_cell; GList *cells; @@ -187,6 +188,14 @@ static void set_columns_settings_from_metadata_and_preferences (NemoListView * static void queue_update_visible_icons (NemoListView *view, gint delay); static NemoZoomLevel nemo_list_view_get_zoom_level (NemoView *view); static void prioritize_visible_files (NemoListView *view); +static void selection_toggle_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); +static void selection_toggle_toggled_callback (GtkCellRendererToggle *cell, + gchar *path_str, + gpointer data); G_DEFINE_TYPE (NemoListView, nemo_list_view, NEMO_TYPE_VIEW); @@ -431,6 +440,85 @@ button_event_modifies_selection (GdkEventButton *event) return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; } +static gboolean +clicked_on_selection_toggle (NemoListView *view, + GtkTreePath *path, + GdkEventButton *event) +{ + GtkTreeViewColumn *column; + int x_col_offset; + int x_cell_offset; + int width; + int start_x; + GtkTreePath *event_path = NULL; + + if (!gtk_tree_view_get_path_at_pos (view->details->tree_view, + event->x, event->y, + &event_path, &column, NULL, NULL)) { + return FALSE; + } + + if (path != NULL && gtk_tree_path_compare (path, event_path) != 0) { + gtk_tree_path_free (event_path); + return FALSE; + } + + gtk_tree_path_free (event_path); + + if (column != view->details->file_name_column) { + return FALSE; + } + + gtk_tree_view_column_cell_get_position (view->details->file_name_column, + GTK_CELL_RENDERER (view->details->selection_toggle_cell), + &x_cell_offset, &width); + x_col_offset = gtk_tree_view_column_get_x_offset (view->details->file_name_column); + start_x = x_col_offset + x_cell_offset; + + return event->x >= start_x && event->x <= start_x + width; +} + +static void +selection_toggle_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + NemoListView *view = NEMO_LIST_VIEW (data); + GtkTreePath *path; + gboolean active; + + path = gtk_tree_model_get_path (model, iter); + active = gtk_tree_selection_path_is_selected (gtk_tree_view_get_selection (view->details->tree_view), path); + g_object_set (cell, + "active", active, + "activatable", TRUE, + NULL); + gtk_tree_path_free (path); +} + +static void +selection_toggle_toggled_callback (GtkCellRendererToggle *cell, + gchar *path_str, + gpointer data) +{ + NemoListView *view = NEMO_LIST_VIEW (data); + GtkTreePath *path; + GtkTreeSelection *selection; + + path = gtk_tree_path_new_from_string (path_str); + selection = gtk_tree_view_get_selection (view->details->tree_view); + + if (gtk_tree_selection_path_is_selected (selection, path)) { + gtk_tree_selection_unselect_path (selection, path); + } else { + gtk_tree_selection_select_path (selection, path); + } + + gtk_tree_path_free (path); +} + static void nemo_list_view_did_not_drag (NemoListView *view, GdkEventButton *event) @@ -1148,6 +1236,19 @@ button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callba call_parent = TRUE; if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL)) { + if (view->details->selection_toggle_cell != NULL && + clicked_on_selection_toggle (view, path, event) && + (event->button == 1 || event->button == 2)) { + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + if (gtk_tree_selection_path_is_selected (selection, path)) { + gtk_tree_selection_unselect_path (selection, path); + } else { + gtk_tree_selection_select_path (selection, path); + } + gtk_tree_path_free (path); + return GDK_EVENT_STOP; + } + if (g_settings_get_boolean (nemo_list_view_preferences, NEMO_PREFERENCES_LIST_VIEW_ENABLE_EXPANSION)) { gtk_widget_style_get (widget, @@ -2710,6 +2811,7 @@ create_and_set_up_tree_view (NemoListView *view) * has the icon in it.*/ if (!strcmp (name, "name")) { /* Create the file name column */ + GtkCellRendererToggle *toggle_cell; cell = gtk_cell_renderer_pixbuf_new (); view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell; @@ -2741,6 +2843,26 @@ create_and_set_up_tree_view (NemoListView *view) gtk_tree_view_column_set_reorderable (view->details->file_name_column, TRUE); gtk_tree_view_column_set_expand (view->details->file_name_column, TRUE); + toggle_cell = GTK_CELL_RENDERER_TOGGLE (gtk_cell_renderer_toggle_new ()); + view->details->selection_toggle_cell = toggle_cell; + g_object_set (toggle_cell, + "xpad", 0, + "ypad", 0, + "activatable", TRUE, + NULL); + gtk_tree_view_column_pack_start (view->details->file_name_column, + GTK_CELL_RENDERER (toggle_cell), + FALSE); + gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, + GTK_CELL_RENDERER (toggle_cell), + selection_toggle_cell_data_func, + view, + NULL); + g_signal_connect (toggle_cell, + "toggled", + G_CALLBACK (selection_toggle_toggled_callback), + view); + gtk_tree_view_column_pack_start (view->details->file_name_column, cell, FALSE); gtk_tree_view_column_set_attributes (view->details->file_name_column, cell,