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
+
+
+
+ False
+ False
+ 1
+
+
@@ -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,