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);