diff --git a/gresources/nemo-shell-ui.xml b/gresources/nemo-shell-ui.xml index 50ce6d15f..e51b39a69 100644 --- a/gresources/nemo-shell-ui.xml +++ b/gresources/nemo-shell-ui.xml @@ -72,7 +72,8 @@ - + + diff --git a/libnemo-private/meson.build b/libnemo-private/meson.build index 16c061882..4607e6c72 100644 --- a/libnemo-private/meson.build +++ b/libnemo-private/meson.build @@ -55,6 +55,8 @@ nemo_private_sources = [ 'nemo-monitor.c', 'nemo-placement-grid.c', 'nemo-places-tree-view.c', + 'nemo-preview-details.c', + 'nemo-preview-image.c', 'nemo-program-choosing.c', 'nemo-progress-info-manager.c', 'nemo-progress-info.c', diff --git a/libnemo-private/nemo-global-preferences.c b/libnemo-private/nemo-global-preferences.c index f57fcbcb4..6e6ec5f97 100644 --- a/libnemo-private/nemo-global-preferences.c +++ b/libnemo-private/nemo-global-preferences.c @@ -50,6 +50,7 @@ GSettings *gtk_filechooser_preferences; GSettings *nemo_plugin_preferences; GSettings *nemo_menu_config_preferences; GSettings *nemo_search_preferences; +GSettings *nemo_preview_pane_preferences; GSettings *gnome_lockdown_preferences; GSettings *gnome_background_preferences; GSettings *gnome_media_handling_preferences; @@ -473,6 +474,7 @@ nemo_global_preferences_init (void) nemo_plugin_preferences = g_settings_new("org.nemo.plugins"); nemo_menu_config_preferences = g_settings_new("org.nemo.preferences.menu-config"); nemo_search_preferences = g_settings_new("org.nemo.search"); + nemo_preview_pane_preferences = g_settings_new("org.nemo.preview-pane"); gnome_lockdown_preferences = g_settings_new("org.cinnamon.desktop.lockdown"); gnome_background_preferences = g_settings_new("org.cinnamon.desktop.background"); gnome_media_handling_preferences = g_settings_new("org.cinnamon.desktop.media-handling"); @@ -506,6 +508,7 @@ nemo_global_preferences_finalize (void) g_object_unref (nemo_plugin_preferences); g_object_unref (nemo_menu_config_preferences); g_object_unref (nemo_search_preferences); + g_object_unref (nemo_preview_pane_preferences); g_object_unref (gnome_lockdown_preferences); g_object_unref (gnome_background_preferences); g_object_unref (gnome_media_handling_preferences); diff --git a/libnemo-private/nemo-global-preferences.h b/libnemo-private/nemo-global-preferences.h index 576a4616d..c10bf4b3d 100644 --- a/libnemo-private/nemo-global-preferences.h +++ b/libnemo-private/nemo-global-preferences.h @@ -315,6 +315,7 @@ extern GSettings *gtk_filechooser_preferences; extern GSettings *nemo_plugin_preferences; extern GSettings *nemo_menu_config_preferences; extern GSettings *nemo_search_preferences; +extern GSettings *nemo_preview_pane_preferences; extern GSettings *gnome_lockdown_preferences; extern GSettings *gnome_background_preferences; extern GSettings *gnome_media_handling_preferences; diff --git a/libnemo-private/nemo-preview-details.c b/libnemo-private/nemo-preview-details.c new file mode 100644 index 000000000..6cf26f12d --- /dev/null +++ b/libnemo-private/nemo-preview-details.c @@ -0,0 +1,239 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * nemo-preview-details.c - Widget for displaying file details in preview pane + * + * Copyright (C) 2025 Linux Mint + * + * Nemo is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nemo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#include "nemo-preview-details.h" +#include + +struct _NemoPreviewDetails { + GtkBox parent; +}; + +typedef struct { + GtkWidget *grid; + GtkWidget *name_value_label; + GtkWidget *size_value_label; + GtkWidget *type_value_label; + GtkWidget *modified_value_label; + GtkWidget *permissions_value_label; + GtkWidget *location_value_label; + + NemoFile *file; +} NemoPreviewDetailsPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (NemoPreviewDetails, nemo_preview_details, GTK_TYPE_BOX) + +static void +nemo_preview_details_finalize (GObject *object) +{ + NemoPreviewDetails *details; + NemoPreviewDetailsPrivate *priv; + + details = NEMO_PREVIEW_DETAILS (object); + priv = nemo_preview_details_get_instance_private (details); + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + priv->file = NULL; + } + + G_OBJECT_CLASS (nemo_preview_details_parent_class)->finalize (object); +} + +static GtkWidget * +create_label_pair (GtkGrid *grid, const gchar *label_text, gint row) +{ + GtkWidget *label; + GtkWidget *value; + + /* Create the label (left column) */ + label = gtk_label_new (label_text); + gtk_widget_set_halign (label, GTK_ALIGN_END); + gtk_widget_set_valign (label, GTK_ALIGN_START); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label"); + gtk_grid_attach (grid, label, 0, row, 1, 1); + gtk_widget_show (label); + + /* Create the value label (right column) */ + value = gtk_label_new (""); + gtk_widget_set_halign (value, GTK_ALIGN_START); + gtk_widget_set_valign (value, GTK_ALIGN_START); + gtk_label_set_selectable (GTK_LABEL (value), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (value), PANGO_ELLIPSIZE_MIDDLE); + gtk_grid_attach (grid, value, 1, row, 1, 1); + gtk_widget_show (value); + + return value; +} + +static void +nemo_preview_details_init (NemoPreviewDetails *details) +{ + NemoPreviewDetailsPrivate *priv; + GtkGrid *grid; + + priv = nemo_preview_details_get_instance_private (details); + + /* Create the grid for label pairs */ + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 6); + gtk_grid_set_column_spacing (grid, 12); + gtk_widget_set_margin_start (GTK_WIDGET (grid), 12); + gtk_widget_set_margin_end (GTK_WIDGET (grid), 12); + gtk_widget_set_margin_top (GTK_WIDGET (grid), 12); + gtk_widget_set_margin_bottom (GTK_WIDGET (grid), 12); + priv->grid = GTK_WIDGET (grid); + + /* Create all the label pairs */ + priv->name_value_label = create_label_pair (grid, _("Name:"), 0); + priv->size_value_label = create_label_pair (grid, _("Size:"), 1); + priv->type_value_label = create_label_pair (grid, _("Type:"), 2); + priv->modified_value_label = create_label_pair (grid, _("Modified:"), 3); + priv->permissions_value_label = create_label_pair (grid, _("Permissions:"), 4); + priv->location_value_label = create_label_pair (grid, _("Location:"), 5); + + gtk_box_pack_start (GTK_BOX (details), GTK_WIDGET (grid), FALSE, FALSE, 0); + gtk_widget_show (GTK_WIDGET (grid)); +} + +static void +nemo_preview_details_class_init (NemoPreviewDetailsClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = nemo_preview_details_finalize; +} + +GtkWidget * +nemo_preview_details_new (void) +{ + return g_object_new (NEMO_TYPE_PREVIEW_DETAILS, + "orientation", GTK_ORIENTATION_VERTICAL, + "spacing", 0, + NULL); +} + +void +nemo_preview_details_set_file (NemoPreviewDetails *widget, + NemoFile *file) +{ + NemoPreviewDetailsPrivate *priv; + gchar *str; + GFile *location; + GFile *parent; + gchar *parent_path; + + g_return_if_fail (NEMO_IS_PREVIEW_DETAILS (widget)); + + priv = nemo_preview_details_get_instance_private (widget); + + if (priv->file == file) { + return; + } + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + } + + priv->file = file; + + if (file != NULL) { + nemo_file_ref (file); + + /* Name */ + str = nemo_file_get_display_name (file); + gtk_label_set_text (GTK_LABEL (priv->name_value_label), str); + g_free (str); + + /* Size */ + str = nemo_file_get_string_attribute (file, "size"); + if (str != NULL) { + gtk_label_set_text (GTK_LABEL (priv->size_value_label), str); + g_free (str); + } else { + gtk_label_set_text (GTK_LABEL (priv->size_value_label), "—"); + } + + /* Type */ + str = nemo_file_get_string_attribute (file, "type"); + if (str != NULL) { + gtk_label_set_text (GTK_LABEL (priv->type_value_label), str); + g_free (str); + } else { + gtk_label_set_text (GTK_LABEL (priv->type_value_label), "—"); + } + + /* Modified */ + str = nemo_file_get_string_attribute (file, "date_modified"); + if (str != NULL) { + gtk_label_set_text (GTK_LABEL (priv->modified_value_label), str); + g_free (str); + } else { + gtk_label_set_text (GTK_LABEL (priv->modified_value_label), "—"); + } + + /* Permissions */ + str = nemo_file_get_string_attribute (file, "permissions"); + if (str != NULL) { + gtk_label_set_text (GTK_LABEL (priv->permissions_value_label), str); + g_free (str); + } else { + gtk_label_set_text (GTK_LABEL (priv->permissions_value_label), "—"); + } + + /* Location */ + location = nemo_file_get_location (file); + parent = g_file_get_parent (location); + if (parent != NULL) { + parent_path = g_file_get_parse_name (parent); + gtk_label_set_text (GTK_LABEL (priv->location_value_label), parent_path); + g_free (parent_path); + g_object_unref (parent); + } else { + gtk_label_set_text (GTK_LABEL (priv->location_value_label), "—"); + } + g_object_unref (location); + } +} + +void +nemo_preview_details_clear (NemoPreviewDetails *widget) +{ + NemoPreviewDetailsPrivate *priv; + + g_return_if_fail (NEMO_IS_PREVIEW_DETAILS (widget)); + + priv = nemo_preview_details_get_instance_private (widget); + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + priv->file = NULL; + } + + gtk_label_set_text (GTK_LABEL (priv->name_value_label), ""); + gtk_label_set_text (GTK_LABEL (priv->size_value_label), ""); + gtk_label_set_text (GTK_LABEL (priv->type_value_label), ""); + gtk_label_set_text (GTK_LABEL (priv->modified_value_label), ""); + gtk_label_set_text (GTK_LABEL (priv->permissions_value_label), ""); + gtk_label_set_text (GTK_LABEL (priv->location_value_label), ""); +} diff --git a/libnemo-private/nemo-preview-details.h b/libnemo-private/nemo-preview-details.h new file mode 100644 index 000000000..ccd2735b1 --- /dev/null +++ b/libnemo-private/nemo-preview-details.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * nemo-preview-details.h - Widget for displaying file details in preview pane + * + * Copyright (C) 2025 Linux Mint + * + * Nemo is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nemo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#ifndef NEMO_PREVIEW_DETAILS_H +#define NEMO_PREVIEW_DETAILS_H + +#include +#include "nemo-file.h" + +#define NEMO_TYPE_PREVIEW_DETAILS (nemo_preview_details_get_type()) + +G_DECLARE_FINAL_TYPE (NemoPreviewDetails, nemo_preview_details, NEMO, PREVIEW_DETAILS, GtkBox) +GtkWidget *nemo_preview_details_new (void); + +void nemo_preview_details_set_file (NemoPreviewDetails *widget, + NemoFile *file); +void nemo_preview_details_clear (NemoPreviewDetails *widget); + +#endif /* NEMO_PREVIEW_DETAILS_H */ diff --git a/libnemo-private/nemo-preview-image.c b/libnemo-private/nemo-preview-image.c new file mode 100644 index 000000000..5b28a81df --- /dev/null +++ b/libnemo-private/nemo-preview-image.c @@ -0,0 +1,522 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * nemo-preview-image.c - Widget for displaying image preview in preview pane + * + * Copyright (C) 2025 Linux Mint + * + * Nemo is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nemo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#include "nemo-preview-image.h" +#include "nemo-file-attributes.h" +#include + +#define RESIZE_DEBOUNCE_MS 150 +#define MIN_SIZE_CHANGE 10 + +/* Required for G_DECLARE_FINAL_TYPE */ +struct _NemoPreviewImage { + GtkBox parent; +}; + +typedef struct { + GtkWidget *drawing_area; + GtkWidget *message_label; + NemoFile *file; + + /* For resize handling */ + guint resize_timeout_id; + gint current_width; + gint current_height; + + /* Keep reference to current pixbuf for quick scaling */ + GdkPixbuf *current_pixbuf; + + /* Current surface to draw */ + cairo_surface_t *current_surface; +} NemoPreviewImagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (NemoPreviewImage, nemo_preview_image, GTK_TYPE_BOX) + +/* Forward declarations */ +static void on_size_allocate (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data); +static gboolean on_drawing_area_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data); + +static void +nemo_preview_image_finalize (GObject *object) +{ + NemoPreviewImage *preview; + NemoPreviewImagePrivate *priv; + + preview = NEMO_PREVIEW_IMAGE (object); + priv = nemo_preview_image_get_instance_private (preview); + + if (priv->resize_timeout_id != 0) { + g_source_remove (priv->resize_timeout_id); + priv->resize_timeout_id = 0; + } + + if (priv->current_pixbuf != NULL) { + g_object_unref (priv->current_pixbuf); + priv->current_pixbuf = NULL; + } + + if (priv->current_surface != NULL) { + cairo_surface_destroy (priv->current_surface); + priv->current_surface = NULL; + } + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + priv->file = NULL; + } + + G_OBJECT_CLASS (nemo_preview_image_parent_class)->finalize (object); +} + +static void +nemo_preview_image_init (NemoPreviewImage *preview) +{ + NemoPreviewImagePrivate *priv; + + priv = nemo_preview_image_get_instance_private (preview); + + /* Initialize resize tracking */ + priv->resize_timeout_id = 0; + priv->current_width = 0; + priv->current_height = 0; + priv->current_pixbuf = NULL; + priv->current_surface = NULL; + + /* Create drawing area widget */ + priv->drawing_area = gtk_drawing_area_new (); + gtk_widget_set_halign (priv->drawing_area, GTK_ALIGN_FILL); + gtk_widget_set_valign (priv->drawing_area, GTK_ALIGN_FILL); + gtk_widget_set_hexpand (priv->drawing_area, TRUE); + gtk_widget_set_vexpand (priv->drawing_area, TRUE); + g_signal_connect (priv->drawing_area, "draw", + G_CALLBACK (on_drawing_area_draw), preview); + gtk_box_pack_start (GTK_BOX (preview), priv->drawing_area, TRUE, TRUE, 0); + + /* Create message label (hidden by default) */ + priv->message_label = gtk_label_new (""); + gtk_widget_set_halign (priv->message_label, GTK_ALIGN_CENTER); + gtk_widget_set_valign (priv->message_label, GTK_ALIGN_CENTER); + gtk_style_context_add_class (gtk_widget_get_style_context (priv->message_label), + "dim-label"); + gtk_box_pack_start (GTK_BOX (preview), priv->message_label, TRUE, TRUE, 0); + + /* Connect size-allocate signal for resize handling */ + g_signal_connect (preview, "size-allocate", + G_CALLBACK (on_size_allocate), NULL); +} + +static void +nemo_preview_image_class_init (NemoPreviewImageClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = nemo_preview_image_finalize; +} + +GtkWidget * +nemo_preview_image_new (void) +{ + return g_object_new (NEMO_TYPE_PREVIEW_IMAGE, + "orientation", GTK_ORIENTATION_VERTICAL, + "spacing", 0, + NULL); +} + +static gboolean +on_drawing_area_draw (GtkWidget *widget, + cairo_t *cr, + gpointer user_data) +{ + NemoPreviewImage *preview = NEMO_PREVIEW_IMAGE (user_data); + NemoPreviewImagePrivate *priv; + gint widget_width, widget_height; + gint surface_width, surface_height; + gdouble x_offset, y_offset; + gint scale_factor; + + priv = nemo_preview_image_get_instance_private (preview); + + if (priv->current_surface == NULL) { + return FALSE; + } + + widget_width = gtk_widget_get_allocated_width (widget); + widget_height = gtk_widget_get_allocated_height (widget); + + /* Get surface dimensions - works for image surfaces created from pixbufs */ + scale_factor = gtk_widget_get_scale_factor (widget); + surface_width = cairo_image_surface_get_width (priv->current_surface) / scale_factor; + surface_height = cairo_image_surface_get_height (priv->current_surface) / scale_factor; + + /* Center the image in the drawing area */ + x_offset = (widget_width - surface_width) / 2.0; + y_offset = (widget_height - surface_height) / 2.0; + + cairo_set_source_surface (cr, priv->current_surface, x_offset, y_offset); + cairo_paint (cr); + + return TRUE; +} + +static gboolean +is_image_file (NemoFile *file) +{ + gchar *mime_type; + gboolean is_image; + + if (file == NULL || nemo_file_is_directory (file)) { + return FALSE; + } + + mime_type = nemo_file_get_mime_type (file); + is_image = mime_type != NULL && g_str_has_prefix (mime_type, "image/"); + g_free (mime_type); + + return is_image; +} + +static void +load_image_at_size (NemoPreviewImage *widget, + gint width, + gint height) +{ + NemoPreviewImagePrivate *priv; + GFile *location; + gchar *path; + GdkPixbuf *pixbuf = NULL; + cairo_surface_t *surface = NULL; + gint ui_scale; + GError *error = NULL; + + priv = nemo_preview_image_get_instance_private (widget); + + if (priv->file == NULL || !is_image_file (priv->file)) { + return; + } + + if (width <= 1 || height <= 1) { + return; + } + + location = nemo_file_get_location (priv->file); + path = g_file_get_path (location); + g_object_unref (location); + + if (path == NULL) { + return; + } + + ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); + + /* Load image directly at the exact size we need */ + pixbuf = gdk_pixbuf_new_from_file_at_scale (path, + width * ui_scale, + height * ui_scale, + TRUE, + &error); + + if (pixbuf != NULL) { + /* Save pixbuf for quick scaling during resize */ + if (priv->current_pixbuf != NULL) { + g_object_unref (priv->current_pixbuf); + } + priv->current_pixbuf = g_object_ref (pixbuf); + + surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, ui_scale, NULL); + + if (surface != NULL) { + /* Replace old surface with new one */ + if (priv->current_surface != NULL) { + cairo_surface_destroy (priv->current_surface); + } + priv->current_surface = surface; + gtk_widget_show (priv->drawing_area); + gtk_widget_queue_draw (priv->drawing_area); + } + + g_object_unref (pixbuf); + gtk_widget_hide (priv->message_label); + } else { + /* Failed to load image */ + if (error != NULL) { + g_warning ("Failed to load image: %s", error->message); + g_error_free (error); + } + gtk_label_set_text (GTK_LABEL (priv->message_label), + _("(Failed to load image)")); + gtk_widget_show (priv->message_label); + gtk_widget_hide (priv->drawing_area); + } + + g_free (path); + + priv->current_width = width; + priv->current_height = height; +} + +static void +scale_current_pixbuf_to_size (NemoPreviewImage *widget, + gint width, + gint height) +{ + NemoPreviewImagePrivate *priv; + GdkPixbuf *scaled_pixbuf; + cairo_surface_t *surface; + gint ui_scale; + gint orig_width, orig_height; + gint target_width, target_height; + gdouble scale_factor; + + priv = nemo_preview_image_get_instance_private (widget); + + if (priv->current_pixbuf == NULL) { + return; + } + + ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); + orig_width = gdk_pixbuf_get_width (priv->current_pixbuf); + orig_height = gdk_pixbuf_get_height (priv->current_pixbuf); + + /* Calculate scaled dimensions maintaining aspect ratio */ + scale_factor = MIN ((gdouble)(width * ui_scale) / orig_width, + (gdouble)(height * ui_scale) / orig_height); + + target_width = (gint)(orig_width * scale_factor); + target_height = (gint)(orig_height * scale_factor); + + if (target_width < 1 || target_height < 1) { + return; + } + + /* Scale pixbuf and display */ + scaled_pixbuf = gdk_pixbuf_scale_simple (priv->current_pixbuf, + target_width, + target_height, + GDK_INTERP_BILINEAR); + + if (scaled_pixbuf != NULL) { + surface = gdk_cairo_surface_create_from_pixbuf (scaled_pixbuf, ui_scale, NULL); + + if (surface != NULL) { + /* Replace old surface with new one */ + if (priv->current_surface != NULL) { + cairo_surface_destroy (priv->current_surface); + } + priv->current_surface = surface; + gtk_widget_queue_draw (priv->drawing_area); + } + + g_object_unref (scaled_pixbuf); + } +} + +static gboolean +on_resize_timeout (gpointer user_data) +{ + NemoPreviewImage *widget = NEMO_PREVIEW_IMAGE (user_data); + NemoPreviewImagePrivate *priv; + GtkAllocation allocation; + + priv = nemo_preview_image_get_instance_private (widget); + priv->resize_timeout_id = 0; + + gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); + load_image_at_size (widget, allocation.width, allocation.height); + + return G_SOURCE_REMOVE; +} + +static void +on_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + gpointer user_data) +{ + NemoPreviewImage *preview = NEMO_PREVIEW_IMAGE (widget); + NemoPreviewImagePrivate *priv; + gint width_diff, height_diff; + gboolean getting_smaller; + + priv = nemo_preview_image_get_instance_private (preview); + + /* Check if size changed significantly */ + width_diff = ABS (allocation->width - priv->current_width); + height_diff = ABS (allocation->height - priv->current_height); + + if (width_diff < MIN_SIZE_CHANGE && height_diff < MIN_SIZE_CHANGE) { + return; + } + + /* Check if we're getting smaller */ + getting_smaller = (allocation->width < priv->current_width || + allocation->height < priv->current_height); + + /* If getting smaller, immediately scale down the current pixbuf for responsive UI */ + if (getting_smaller && priv->current_pixbuf != NULL) { + scale_current_pixbuf_to_size (preview, allocation->width, allocation->height); + } + + /* Clear existing timeout */ + if (priv->resize_timeout_id != 0) { + g_source_remove (priv->resize_timeout_id); + } + + /* Schedule reload with debouncing to get optimal quality */ + priv->resize_timeout_id = g_timeout_add (RESIZE_DEBOUNCE_MS, + on_resize_timeout, + preview); +} + +void +nemo_preview_image_set_file (NemoPreviewImage *widget, + NemoFile *file) +{ + NemoPreviewImagePrivate *priv; + GtkAllocation allocation; + + g_return_if_fail (NEMO_IS_PREVIEW_IMAGE (widget)); + + priv = nemo_preview_image_get_instance_private (widget); + + if (priv->file == file) { + return; + } + + /* Clear any pending resize timeout */ + if (priv->resize_timeout_id != 0) { + g_source_remove (priv->resize_timeout_id); + priv->resize_timeout_id = 0; + } + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + } + + priv->file = file; + + /* Clear current image */ + if (priv->current_surface != NULL) { + cairo_surface_destroy (priv->current_surface); + priv->current_surface = NULL; + } + gtk_widget_hide (priv->message_label); + gtk_widget_hide (priv->drawing_area); + priv->current_width = 0; + priv->current_height = 0; + + if (priv->current_pixbuf != NULL) { + g_object_unref (priv->current_pixbuf); + priv->current_pixbuf = NULL; + } + + if (file != NULL) { + nemo_file_ref (file); + + if (is_image_file (file)) { + /* Load the image at current widget size */ + gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); + load_image_at_size (widget, allocation.width, allocation.height); + } else if (nemo_file_is_directory (file)) { + /* Show folder icon via nemo_file API */ + GdkPixbuf *icon_pixbuf; + cairo_surface_t *surface; + gint ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); + + icon_pixbuf = nemo_file_get_icon_pixbuf (file, 64, TRUE, ui_scale, 0); + if (icon_pixbuf != NULL) { + surface = gdk_cairo_surface_create_from_pixbuf (icon_pixbuf, ui_scale, NULL); + if (surface != NULL) { + if (priv->current_surface != NULL) { + cairo_surface_destroy (priv->current_surface); + } + priv->current_surface = surface; + } + g_object_unref (icon_pixbuf); + } + + gtk_label_set_text (GTK_LABEL (priv->message_label), _("(Folder)")); + gtk_widget_show (priv->drawing_area); + gtk_widget_queue_draw (priv->drawing_area); + gtk_widget_show (priv->message_label); + } else { + /* Non-image file: show file icon */ + GdkPixbuf *icon_pixbuf; + cairo_surface_t *surface; + gint ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); + + icon_pixbuf = nemo_file_get_icon_pixbuf (file, 64, TRUE, ui_scale, 0); + if (icon_pixbuf != NULL) { + surface = gdk_cairo_surface_create_from_pixbuf (icon_pixbuf, ui_scale, NULL); + if (surface != NULL) { + if (priv->current_surface != NULL) { + cairo_surface_destroy (priv->current_surface); + } + priv->current_surface = surface; + } + g_object_unref (icon_pixbuf); + } + + gtk_label_set_text (GTK_LABEL (priv->message_label), + _("(Not an image file)")); + gtk_widget_show (priv->drawing_area); + gtk_widget_queue_draw (priv->drawing_area); + gtk_widget_show (priv->message_label); + } + } +} + +void +nemo_preview_image_clear (NemoPreviewImage *widget) +{ + NemoPreviewImagePrivate *priv; + + g_return_if_fail (NEMO_IS_PREVIEW_IMAGE (widget)); + + priv = nemo_preview_image_get_instance_private (widget); + + /* Clear any pending resize timeout */ + if (priv->resize_timeout_id != 0) { + g_source_remove (priv->resize_timeout_id); + priv->resize_timeout_id = 0; + } + + if (priv->current_pixbuf != NULL) { + g_object_unref (priv->current_pixbuf); + priv->current_pixbuf = NULL; + } + + if (priv->current_surface != NULL) { + cairo_surface_destroy (priv->current_surface); + priv->current_surface = NULL; + } + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + priv->file = NULL; + } + + gtk_widget_hide (priv->drawing_area); + gtk_widget_hide (priv->message_label); + priv->current_width = 0; + priv->current_height = 0; +} diff --git a/libnemo-private/nemo-preview-image.h b/libnemo-private/nemo-preview-image.h new file mode 100644 index 000000000..d346c02f2 --- /dev/null +++ b/libnemo-private/nemo-preview-image.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * nemo-preview-image.h - Widget for displaying image preview in preview pane + * + * Copyright (C) 2025 Linux Mint + * + * Nemo is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nemo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#ifndef NEMO_PREVIEW_IMAGE_H +#define NEMO_PREVIEW_IMAGE_H + +#include +#include "nemo-file.h" + +#define NEMO_TYPE_PREVIEW_IMAGE (nemo_preview_image_get_type()) + +G_DECLARE_FINAL_TYPE (NemoPreviewImage, nemo_preview_image, NEMO, PREVIEW_IMAGE, GtkBox) +GtkWidget *nemo_preview_image_new (void); + +void nemo_preview_image_set_file (NemoPreviewImage *widget, + NemoFile *file); +void nemo_preview_image_clear (NemoPreviewImage *widget); + +#endif /* NEMO_PREVIEW_IMAGE_H */ diff --git a/libnemo-private/org.nemo.gschema.xml b/libnemo-private/org.nemo.gschema.xml index a43065d35..77b116aa1 100644 --- a/libnemo-private/org.nemo.gschema.xml +++ b/libnemo-private/org.nemo.gschema.xml @@ -76,6 +76,7 @@ + @@ -895,4 +896,17 @@ List of search helper filenames to skip when using content search. + + + + 400 + Width of the preview pane + The width of the preview pane in logical pixels. + + + 200 + Height of the details pane within the preview pane + The height of the NemoPreviewDetails pane in logical pixels. This is the lower part of the vertically-split preview pane showing file metadata. + + diff --git a/src/meson.build b/src/meson.build index 07a56957d..d044f4aea 100644 --- a/src/meson.build +++ b/src/meson.build @@ -53,6 +53,7 @@ nemoCommon_sources = [ 'nemo-places-sidebar.c', 'nemo-plugin-manager.c', 'nemo-previewer.c', + 'nemo-preview-pane.c', 'nemo-progress-info-widget.c', 'nemo-progress-ui-handler.c', 'nemo-properties-window.c', diff --git a/src/nemo-actions.h b/src/nemo-actions.h index 0ec1c0722..cd9a87680 100644 --- a/src/nemo-actions.h +++ b/src/nemo-actions.h @@ -46,6 +46,7 @@ #define NEMO_ACTION_SHOW_HIDE_MENUBAR "Show Hide Menubar" #define NEMO_ACTION_SHOW_HIDE_LOCATION_BAR "Show Hide Location Bar" #define NEMO_ACTION_SHOW_HIDE_EXTRA_PANE "Show Hide Extra Pane" +#define NEMO_ACTION_SHOW_HIDE_PREVIEW_PANE "Show Hide Preview Pane" #define NEMO_ACTION_GO_TO_BURN_CD "Go to Burn CD" #define NEMO_ACTION_EDIT_LOCATION "Edit Location" #define NEMO_ACTION_COMPACT_VIEW "CompactView" diff --git a/src/nemo-preview-pane.c b/src/nemo-preview-pane.c new file mode 100644 index 000000000..887934e51 --- /dev/null +++ b/src/nemo-preview-pane.c @@ -0,0 +1,291 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * nemo-preview-pane.c - Container widget for preview pane + * + * Copyright (C) 2025 Linux Mint + * + * Nemo is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nemo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#include "nemo-preview-pane.h" +#include +#include +#include +#include + +#define PREVIEW_IMAGE_HEIGHT 200 + +struct _NemoPreviewPane { + GtkBox parent; +}; + +typedef struct { + NemoWindow *window; + + GtkWidget *vpaned; + GtkWidget *image_widget; + GtkWidget *details_widget; + GtkWidget *empty_label; + + NemoFile *current_file; + gulong file_changed_id; + + gboolean initial_position_set; +} NemoPreviewPanePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (NemoPreviewPane, nemo_preview_pane, GTK_TYPE_BOX) + +static void +vpaned_size_allocate_callback (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data) +{ + NemoPreviewPane *pane = NEMO_PREVIEW_PANE (user_data); + NemoPreviewPanePrivate *priv = nemo_preview_pane_get_instance_private (pane); + gint saved_height, position; + + /* Only set initial position once */ + if (priv->initial_position_set) { + return; + } + + priv->initial_position_set = TRUE; + + /* Set position based on saved details height */ + saved_height = g_settings_get_int (nemo_preview_pane_preferences, "details-height"); + if (saved_height > 50 && allocation->height > saved_height) { + /* Position is from top, so subtract details height from total */ + position = allocation->height - saved_height; + gtk_paned_set_position (GTK_PANED (widget), position); + } else { + /* Fallback: make image section PREVIEW_IMAGE_HEIGHT */ + gtk_paned_set_position (GTK_PANED (widget), PREVIEW_IMAGE_HEIGHT); + } +} + +static void +details_pane_position_changed_callback (GObject *paned, GParamSpec *pspec, gpointer user_data) +{ + NemoPreviewPane *pane = NEMO_PREVIEW_PANE (user_data); + NemoPreviewPanePrivate *priv = nemo_preview_pane_get_instance_private (pane); + gint position, total_height, details_height; + + /* Don't save position until initial position has been set */ + if (!priv->initial_position_set) { + return; + } + + position = gtk_paned_get_position (GTK_PANED (paned)); + total_height = gtk_widget_get_allocated_height (GTK_WIDGET (paned)); + + /* Calculate height of details pane (bottom side) */ + details_height = total_height - position; + + /* Only save if details height is reasonable */ + if (details_height > 50 && total_height > 0) { + g_settings_set_int (nemo_preview_pane_preferences, "details-height", details_height); + } +} + +static void +file_changed_callback (NemoFile *file, gpointer user_data) +{ + NemoPreviewPane *pane; + NemoPreviewPanePrivate *priv; + + pane = NEMO_PREVIEW_PANE (user_data); + priv = nemo_preview_pane_get_instance_private (pane); + + /* Refresh the preview if the file has changed */ + if (file == priv->current_file) { + nemo_preview_pane_set_file (pane, file); + } +} + +static void +nemo_preview_pane_finalize (GObject *object) +{ + NemoPreviewPane *pane; + NemoPreviewPanePrivate *priv; + + pane = NEMO_PREVIEW_PANE (object); + priv = nemo_preview_pane_get_instance_private (pane); + + if (priv->current_file != NULL) { + if (priv->file_changed_id != 0) { + g_signal_handler_disconnect (priv->current_file, + priv->file_changed_id); + priv->file_changed_id = 0; + } + nemo_file_unref (priv->current_file); + priv->current_file = NULL; + } + + G_OBJECT_CLASS (nemo_preview_pane_parent_class)->finalize (object); +} + +static void +nemo_preview_pane_init (NemoPreviewPane *pane) +{ + NemoPreviewPanePrivate *priv; + GtkWidget *scrolled; + + priv = nemo_preview_pane_get_instance_private (pane); + + /* Create empty state label */ + priv->empty_label = gtk_label_new (_("No file selected")); + gtk_widget_set_halign (priv->empty_label, GTK_ALIGN_CENTER); + gtk_widget_set_valign (priv->empty_label, GTK_ALIGN_CENTER); + gtk_style_context_add_class (gtk_widget_get_style_context (priv->empty_label), + "dim-label"); + gtk_box_pack_start (GTK_BOX (pane), priv->empty_label, TRUE, TRUE, 0); + gtk_widget_show (priv->empty_label); + + /* Create vertical paned widget */ + priv->vpaned = gtk_paned_new (GTK_ORIENTATION_VERTICAL); + gtk_box_pack_start (GTK_BOX (pane), priv->vpaned, TRUE, TRUE, 0); + + /* Create image preview widget (top) */ + priv->image_widget = nemo_preview_image_new (); + gtk_paned_pack1 (GTK_PANED (priv->vpaned), + priv->image_widget, FALSE, FALSE); + gtk_widget_show (priv->image_widget); + + /* Create details widget (bottom) in a scrolled window */ + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + priv->details_widget = nemo_preview_details_new (); + gtk_container_add (GTK_CONTAINER (scrolled), priv->details_widget); + gtk_widget_show (priv->details_widget); + + gtk_paned_pack2 (GTK_PANED (priv->vpaned), + scrolled, TRUE, FALSE); + gtk_widget_show (scrolled); + + /* Initialize flag */ + priv->initial_position_set = FALSE; + + /* Connect size-allocate to set initial position from saved settings */ + g_signal_connect (priv->vpaned, "size-allocate", + G_CALLBACK (vpaned_size_allocate_callback), + pane); + + /* Connect signal to save position on resize */ + g_signal_connect (priv->vpaned, "notify::position", + G_CALLBACK (details_pane_position_changed_callback), + pane); +} + +static void +nemo_preview_pane_class_init (NemoPreviewPaneClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = nemo_preview_pane_finalize; +} + +GtkWidget * +nemo_preview_pane_new (NemoWindow *window) +{ + NemoPreviewPane *pane; + NemoPreviewPanePrivate *priv; + + pane = g_object_new (NEMO_TYPE_PREVIEW_PANE, + "orientation", GTK_ORIENTATION_VERTICAL, + "spacing", 0, + NULL); + + priv = nemo_preview_pane_get_instance_private (pane); + priv->window = window; + + return GTK_WIDGET (pane); +} + +void +nemo_preview_pane_set_file (NemoPreviewPane *pane, + NemoFile *file) +{ + NemoPreviewPanePrivate *priv; + + g_return_if_fail (NEMO_IS_PREVIEW_PANE (pane)); + + priv = nemo_preview_pane_get_instance_private (pane); + + /* Disconnect from previous file if necessary */ + if (priv->current_file != NULL) { + if (priv->file_changed_id != 0) { + g_signal_handler_disconnect (priv->current_file, + priv->file_changed_id); + priv->file_changed_id = 0; + } + nemo_file_unref (priv->current_file); + priv->current_file = NULL; + } + + priv->current_file = file; + + if (file != NULL) { + nemo_file_ref (file); + + /* Monitor file for changes */ + priv->file_changed_id = + g_signal_connect (file, "changed", + G_CALLBACK (file_changed_callback), + pane); + + /* Update child widgets */ + nemo_preview_image_set_file (NEMO_PREVIEW_IMAGE (priv->image_widget), file); + nemo_preview_details_set_file (NEMO_PREVIEW_DETAILS (priv->details_widget), file); + + /* Show preview, hide empty label */ + gtk_widget_hide (priv->empty_label); + gtk_widget_show (priv->vpaned); + } else { + /* No file selected - show empty state */ + nemo_preview_pane_clear (pane); + } +} + +void +nemo_preview_pane_clear (NemoPreviewPane *pane) +{ + NemoPreviewPanePrivate *priv; + + g_return_if_fail (NEMO_IS_PREVIEW_PANE (pane)); + + priv = nemo_preview_pane_get_instance_private (pane); + + if (priv->current_file != NULL) { + if (priv->file_changed_id != 0) { + g_signal_handler_disconnect (priv->current_file, + priv->file_changed_id); + priv->file_changed_id = 0; + } + nemo_file_unref (priv->current_file); + priv->current_file = NULL; + } + + /* Clear child widgets */ + nemo_preview_image_clear (NEMO_PREVIEW_IMAGE (priv->image_widget)); + nemo_preview_details_clear (NEMO_PREVIEW_DETAILS (priv->details_widget)); + + /* Hide preview, show empty label */ + gtk_widget_hide (priv->vpaned); + gtk_widget_show (priv->empty_label); +} diff --git a/src/nemo-preview-pane.h b/src/nemo-preview-pane.h new file mode 100644 index 000000000..4a08c3283 --- /dev/null +++ b/src/nemo-preview-pane.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * nemo-preview-pane.h - Container widget for preview pane + * + * Copyright (C) 2025 Linux Mint + * + * Nemo is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nemo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street - Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#ifndef NEMO_PREVIEW_PANE_H +#define NEMO_PREVIEW_PANE_H + +#include +#include + +/* Forward declaration */ +typedef struct NemoWindow NemoWindow; + +#define NEMO_TYPE_PREVIEW_PANE (nemo_preview_pane_get_type()) + +G_DECLARE_FINAL_TYPE (NemoPreviewPane, nemo_preview_pane, NEMO, PREVIEW_PANE, GtkBox); +GtkWidget *nemo_preview_pane_new (NemoWindow *window); + +void nemo_preview_pane_set_file (NemoPreviewPane *pane, + NemoFile *file); +void nemo_preview_pane_clear (NemoPreviewPane *pane); + +#endif /* NEMO_PREVIEW_PANE_H */ diff --git a/src/nemo-window-menus.c b/src/nemo-window-menus.c index fd374b3df..9e7ded81a 100644 --- a/src/nemo-window-menus.c +++ b/src/nemo-window-menus.c @@ -657,6 +657,36 @@ action_split_view_callback (GtkAction *action, nemo_view_update_menus (slot->content_view); } } +} + +static void +action_preview_pane_callback (GtkAction *action, + gpointer user_data) +{ + NemoWindow *window; + gboolean is_active; + + if (NEMO_IS_DESKTOP_WINDOW (user_data)) { + return; + } + + window = NEMO_WINDOW (user_data); + + is_active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + if (is_active != nemo_window_preview_pane_showing (window)) { + NemoWindowSlot *slot; + + if (is_active) { + nemo_window_preview_pane_on (window); + } else { + nemo_window_preview_pane_off (window); + } + + slot = nemo_window_get_active_slot (window); + if (slot != NULL && slot->content_view != NULL) { + nemo_view_update_menus (slot->content_view); + } + } nemo_window_update_show_hide_ui_elements (window); } @@ -1572,6 +1602,11 @@ static const GtkToggleActionEntry main_toggle_entries[] = { /* label, accelerator */ N_("E_xtra Pane"), "F3", /* tooltip */ N_("Open an extra folder view side-by-side"), G_CALLBACK (action_split_view_callback), + /* is_active */ FALSE }, + /* name, stock id */ { NEMO_ACTION_SHOW_HIDE_PREVIEW_PANE, NULL, + /* label, accelerator */ N_("_Preview Pane"), "F7", + /* tooltip */ N_("Show or hide the preview pane"), + G_CALLBACK (action_preview_pane_callback), /* is_active */ FALSE }, /* name, stock id */ { NEMO_ACTION_SHOW_THUMBNAILS, NULL, /* label, accelerator */ N_("Show _Thumbnails"), NULL, diff --git a/src/nemo-window-private.h b/src/nemo-window-private.h index b8f8e0444..8d3c1c58d 100644 --- a/src/nemo-window-private.h +++ b/src/nemo-window-private.h @@ -99,6 +99,10 @@ struct NemoWindowDetails // location changes. GFile *secondary_pane_last_location; + /* preview pane */ + GtkWidget *preview_pane; // NemoPreviewPane instance + gboolean show_preview_pane; // State flag + gboolean disable_chrome; guint sidebar_width_handler_id; diff --git a/src/nemo-window.c b/src/nemo-window.c index a9e502d72..656e28c3b 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -48,6 +48,7 @@ #include "nemo-icon-view.h" #include "nemo-list-view.h" #include "nemo-statusbar.h" +#include "nemo-preview-pane.h" #include #include @@ -1545,6 +1546,9 @@ nemo_window_sync_create_folder_button (NemoWindow *window) toolbar_set_create_folder_button (allow, slot->pane); } +/* Forward declaration for preview pane callback */ +static void preview_pane_selection_changed_callback (NemoView *view, NemoWindow *window); + static void zoom_level_changed_callback (NemoView *view, NemoWindow *window) @@ -1583,6 +1587,13 @@ nemo_window_connect_content_view (NemoWindow *window, G_CALLBACK (zoom_level_changed_callback), window); + /* Connect preview pane selection updates if preview is showing */ + if (window->details->preview_pane) { + g_signal_connect_object (view, "selection-changed", + G_CALLBACK (preview_pane_selection_changed_callback), + window, 0); + } + /* Update displayed the selected view type in the toolbar and menu. */ if (slot->pending_location == NULL) { nemo_window_sync_view_type (window); @@ -1607,6 +1618,11 @@ nemo_window_disconnect_content_view (NemoWindow *window, } g_signal_handlers_disconnect_by_func (view, G_CALLBACK (zoom_level_changed_callback), window); + + /* Disconnect preview pane selection updates if preview is showing */ + if (window->details->preview_pane) { + g_signal_handlers_disconnect_by_func (view, G_CALLBACK (preview_pane_selection_changed_callback), window); + } } /** @@ -2187,12 +2203,55 @@ nemo_window_new (GtkApplication *application, NULL); } +static void +preview_pane_selection_changed_callback (NemoView *view, NemoWindow *window) +{ + GList *selection; + NemoFile *file = NULL; + + if (!window->details->preview_pane) { + return; + } + + selection = nemo_view_get_selection (view); + + if (selection != NULL && selection->data != NULL) { + file = NEMO_FILE (selection->data); /* Show first selected file */ + } + + nemo_preview_pane_set_file (NEMO_PREVIEW_PANE (window->details->preview_pane), file); + + nemo_file_list_free (selection); +} + +static void +preview_pane_position_changed_callback (GObject *paned, GParamSpec *pspec, NemoWindow *window) +{ + gint position, total_width, preview_width; + + position = gtk_paned_get_position (GTK_PANED (paned)); + total_width = gtk_widget_get_allocated_width (GTK_WIDGET (paned)); + + /* Calculate width of preview pane (right side) */ + preview_width = total_width - position; + + /* Only save if preview width is reasonable */ + if (preview_width > 100 && total_width > 0) { + g_settings_set_int (nemo_preview_pane_preferences, "pane-width", preview_width); + } +} + void nemo_window_split_view_on (NemoWindow *window) { NemoWindowSlot *slot, *old_active_slot; GFile *location; + /* Disable preview pane if it's showing */ + if (nemo_window_preview_pane_showing (window)) { + nemo_window_preview_pane_off (window); + } + old_active_slot = nemo_window_get_active_slot (window); slot = create_extra_pane (window); @@ -2256,6 +2315,106 @@ nemo_window_split_view_showing (NemoWindow *window) return g_list_length (NEMO_WINDOW (window)->details->panes) > 1; } +void +nemo_window_preview_pane_on (NemoWindow *window) +{ + NemoWindowSlot *slot; + GList *selection; + NemoFile *file = NULL; + + /* Disable split view if it's showing */ + if (nemo_window_split_view_showing (window)) { + nemo_window_split_view_off (window); + } + + /* Create preview pane */ + window->details->preview_pane = nemo_preview_pane_new (window); + + /* Pack into split view paned */ + gtk_paned_pack2 (GTK_PANED (window->details->split_view_hpane), + window->details->preview_pane, + TRUE, FALSE); + + gtk_widget_show (window->details->preview_pane); + + /* Set position from saved settings */ + gint saved_width = g_settings_get_int (nemo_preview_pane_preferences, "pane-width"); + gint total_width = gtk_widget_get_allocated_width (window->details->split_view_hpane); + gint position; + + if (saved_width > 100 && total_width > saved_width) { + /* Position is measured from left, so subtract preview width from total */ + position = total_width - saved_width; + gtk_paned_set_position (GTK_PANED (window->details->split_view_hpane), position); + } else { + /* Fallback to 60/40 split if no saved value */ + gtk_paned_set_position (GTK_PANED (window->details->split_view_hpane), + total_width * 0.6); + } + + /* Connect signal to save position on resize */ + g_signal_connect (window->details->split_view_hpane, "notify::position", + G_CALLBACK (preview_pane_position_changed_callback), + window); + + /* Get current selection and update preview */ + slot = nemo_window_get_active_slot (window); + if (slot != NULL && slot->content_view != NULL) { + selection = nemo_view_get_selection (slot->content_view); + if (selection != NULL && selection->data != NULL) { + file = NEMO_FILE (selection->data); + } + nemo_preview_pane_set_file (NEMO_PREVIEW_PANE (window->details->preview_pane), file); + nemo_file_list_free (selection); + + /* Connect selection-changed signal for the current view */ + /* This will be reconnected automatically when view changes via nemo_window_connect_content_view() */ + g_signal_connect_object (slot->content_view, "selection-changed", + G_CALLBACK (preview_pane_selection_changed_callback), + window, 0); + } + + window->details->show_preview_pane = TRUE; + nemo_window_update_show_hide_ui_elements (window); +} + +void +nemo_window_preview_pane_off (NemoWindow *window) +{ + GtkPaned *paned; + + if (window->details->preview_pane == NULL) { + return; + } + + /* Disconnect position signal */ + g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, + G_CALLBACK (preview_pane_position_changed_callback), + window); + + paned = GTK_PANED (window->details->split_view_hpane); + + /* Remove from paned */ + gtk_container_remove (GTK_CONTAINER (paned), window->details->preview_pane); + + /* Reset paned position */ + g_object_set (G_OBJECT (paned), + "position", 0, + "position-set", FALSE, + NULL); + + window->details->preview_pane = NULL; + window->details->show_preview_pane = FALSE; + + nemo_window_update_show_hide_ui_elements (window); +} + +gboolean +nemo_window_preview_pane_showing (NemoWindow *window) +{ + return window->details->show_preview_pane; +} + void nemo_window_clear_secondary_pane_location (NemoWindow *window) { diff --git a/src/nemo-window.h b/src/nemo-window.h index bb6a4d05a..1ffb07dfb 100644 --- a/src/nemo-window.h +++ b/src/nemo-window.h @@ -157,6 +157,10 @@ void nemo_window_split_view_on (NemoWindow *window); void nemo_window_split_view_off (NemoWindow *window); gboolean nemo_window_split_view_showing (NemoWindow *window); +void nemo_window_preview_pane_on (NemoWindow *window); +void nemo_window_preview_pane_off (NemoWindow *window); +gboolean nemo_window_preview_pane_showing (NemoWindow *window); + gboolean nemo_window_disable_chrome_mapping (GValue *value, GVariant *variant, gpointer user_data);