diff --git a/gresources/nemo-file-management-properties.glade b/gresources/nemo-file-management-properties.glade
index 02b8e4cb4..bf1783e35 100644
--- a/gresources/nemo-file-management-properties.glade
+++ b/gresources/nemo-file-management-properties.glade
@@ -609,6 +609,22 @@ along with . If not, see .
5
+
+
+
+ False
+ False
+ 6
+
+
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-file.c b/libnemo-private/nemo-file.c
index 01d23be22..738c22124 100644
--- a/libnemo-private/nemo-file.c
+++ b/libnemo-private/nemo-file.c
@@ -4555,6 +4555,18 @@ nemo_file_has_loaded_thumbnail (NemoFile *file)
return file->details->thumbnail_is_up_to_date;
}
+char *
+nemo_file_get_thumbnail_path (NemoFile *file)
+{
+ g_return_val_if_fail (NEMO_IS_FILE (file), NULL);
+
+ if (file->details->thumbnail_path != NULL) {
+ return g_strdup (file->details->thumbnail_path);
+ }
+
+ return NULL;
+}
+
static void
prepend_icon_name (const char *name,
GThemedIcon *icon)
diff --git a/libnemo-private/nemo-file.h b/libnemo-private/nemo-file.h
index 8b0f76975..73055daef 100644
--- a/libnemo-private/nemo-file.h
+++ b/libnemo-private/nemo-file.h
@@ -252,6 +252,7 @@ NemoRequestStatus nemo_file_get_deep_counts (NemoFile
gboolean nemo_file_should_show_thumbnail (NemoFile *file);
void nemo_file_delete_thumbnail (NemoFile *file);
gboolean nemo_file_has_loaded_thumbnail (NemoFile *file);
+char * nemo_file_get_thumbnail_path (NemoFile *file);
gboolean nemo_file_should_show_directory_item_count (NemoFile *file);
gboolean nemo_file_should_show_type (NemoFile *file);
GList * nemo_file_get_keywords (NemoFile *file);
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..00a6d6a34 100644
--- a/libnemo-private/nemo-global-preferences.h
+++ b/libnemo-private/nemo-global-preferences.h
@@ -42,6 +42,7 @@ G_BEGIN_DECLS
/* Display */
#define NEMO_PREFERENCES_SHOW_HIDDEN_FILES "show-hidden-files"
#define NEMO_PREFERENCES_SHOW_ADVANCED_PERMISSIONS "show-advanced-permissions"
+#define NEMO_PREFERENCES_SHOW_PREVIEW_PANE "show-preview-pane"
#define NEMO_PREFERENCES_DATE_FORMAT "date-format"
#define NEMO_PREFERENCES_DATE_FONT_CHOICE "date-font-choice"
#define NEMO_PREFERENCES_MONO_FONT_NAME "monospace-font-name"
@@ -315,6 +316,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-metadata.c b/libnemo-private/nemo-metadata.c
index c31231c03..d7613ee28 100644
--- a/libnemo-private/nemo-metadata.c
+++ b/libnemo-private/nemo-metadata.c
@@ -46,6 +46,7 @@ static char *used_metadata_names[] = {
(char *)NEMO_METADATA_KEY_WINDOW_MAXIMIZED,
(char *)NEMO_METADATA_KEY_WINDOW_STICKY,
(char *)NEMO_METADATA_KEY_WINDOW_KEEP_ABOVE,
+ (char *)NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE,
(char *)NEMO_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR,
(char *)NEMO_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE,
(char *)NEMO_METADATA_KEY_SIDEBAR_BUTTONS,
diff --git a/libnemo-private/nemo-metadata.h b/libnemo-private/nemo-metadata.h
index da4142fa1..fcbc4ee54 100644
--- a/libnemo-private/nemo-metadata.h
+++ b/libnemo-private/nemo-metadata.h
@@ -60,6 +60,7 @@
#define NEMO_METADATA_KEY_WINDOW_MAXIMIZED "nemo-window-maximized"
#define NEMO_METADATA_KEY_WINDOW_STICKY "nemo-window-sticky"
#define NEMO_METADATA_KEY_WINDOW_KEEP_ABOVE "nemo-window-keep-above"
+#define NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE "nemo-window-show-preview-pane"
#define NEMO_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR "nemo-sidebar-background-color"
#define NEMO_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE "nemo-sidebar-background-image"
diff --git a/libnemo-private/nemo-preview-details.c b/libnemo-private/nemo-preview-details.c
new file mode 100644
index 000000000..6f1bd8e90
--- /dev/null
+++ b/libnemo-private/nemo-preview-details.c
@@ -0,0 +1,257 @@
+/* -*- 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 - use activation URI to get real location for virtual folders */
+ str = nemo_file_get_activation_uri (file);
+ if (str != NULL) {
+ location = g_file_new_for_uri (str);
+ g_free (str);
+
+ 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);
+ } else {
+ /* Fallback to regular location if no activation URI */
+ 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..1212ef09f
--- /dev/null
+++ b/libnemo-private/nemo-preview-image.c
@@ -0,0 +1,695 @@
+/* -*- 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 "nemo-icon-info.h"
+#include "nemo-thumbnails.h"
+#include
+
+#define RESIZE_DEBOUNCE_MS 150
+#define MIN_SIZE_CHANGE 10
+
+struct _NemoPreviewImage {
+ GtkBox parent;
+};
+
+typedef struct _LoadImageData LoadImageData;
+
+typedef struct {
+ GtkWidget *frame;
+ GtkWidget *drawing_area;
+ GtkWidget *message_label;
+ NemoFile *file;
+
+ guint resize_timeout_id;
+ gint current_width;
+ gint current_height;
+
+ cairo_surface_t *current_surface;
+ gint surface_width;
+ gint surface_height;
+
+ gboolean showing_icon;
+
+ LoadImageData *current_load_data;
+} NemoPreviewImagePrivate;
+
+struct _LoadImageData {
+ gchar *file_path; /* For direct loading (can be NULL) */
+ gchar *thumbnail_path; /* For thumbnail loading (can be NULL) */
+ gint width;
+ gint height;
+ gint ui_scale;
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (NemoPreviewImage, nemo_preview_image, GTK_TYPE_BOX)
+
+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 LoadImageData *
+load_image_data_new (const gchar *file_path,
+ const gchar *thumbnail_path,
+ gint width,
+ gint height,
+ gint ui_scale)
+{
+ LoadImageData *data;
+
+ data = g_new0 (LoadImageData, 1);
+ data->file_path = g_strdup (file_path);
+ data->thumbnail_path = g_strdup (thumbnail_path);
+ data->width = width;
+ data->height = height;
+ data->ui_scale = ui_scale;
+ data->cancellable = g_cancellable_new ();
+
+ return data;
+}
+
+static void
+load_image_data_free (LoadImageData *data)
+{
+ if (data == NULL) {
+ return;
+ }
+
+ g_free (data->file_path);
+ g_free (data->thumbnail_path);
+
+ if (data->cancellable != NULL) {
+ g_object_unref (data->cancellable);
+ }
+
+ g_free (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->current_load_data != NULL) {
+ g_cancellable_cancel (priv->current_load_data->cancellable);
+ priv->current_load_data = NULL; // Forget about it, GTask will clean it up
+ }
+
+ if (priv->resize_timeout_id != 0) {
+ g_source_remove (priv->resize_timeout_id);
+ priv->resize_timeout_id = 0;
+ }
+
+ 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);
+
+ priv->resize_timeout_id = 0;
+ priv->current_width = 0;
+ priv->current_height = 0;
+ priv->current_surface = NULL;
+ priv->surface_width = 0;
+ priv->surface_height = 0;
+ priv->showing_icon = FALSE;
+ priv->current_load_data = NULL;
+
+ priv->frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (priv->frame), GTK_SHADOW_NONE);
+ gtk_widget_set_halign (priv->frame, GTK_ALIGN_FILL);
+ gtk_widget_set_valign (priv->frame, GTK_ALIGN_FILL);
+ gtk_widget_set_hexpand (priv->frame, TRUE);
+ gtk_widget_set_vexpand (priv->frame, TRUE);
+
+ gtk_box_pack_start (GTK_BOX (preview), priv->frame, TRUE, TRUE, 0);
+
+ 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_container_add (GTK_CONTAINER (priv->frame), priv->drawing_area);
+
+ 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);
+
+ gtk_container_set_border_width (GTK_CONTAINER (preview), 4);
+ gtk_widget_show_all (GTK_WIDGET (preview));
+ 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;
+ gdouble scale_x, scale_y, scale;
+ gdouble scaled_width, scaled_height;
+ gdouble x_offset, y_offset;
+
+ 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);
+
+ scale_x = (gdouble)widget_width / priv->surface_width;
+ scale_y = (gdouble)widget_height / priv->surface_height;
+ scale = MIN (scale_x, scale_y);
+
+ scaled_width = priv->surface_width * scale;
+ scaled_height = priv->surface_height * scale;
+
+ x_offset = (widget_width - scaled_width) / 2.0;
+ y_offset = (widget_height - scaled_height) / 2.0;
+
+ cairo_save (cr);
+ cairo_translate (cr, x_offset, y_offset);
+ cairo_scale (cr, scale, scale);
+ cairo_set_source_surface (cr, priv->current_surface, 0, 0);
+ cairo_paint (cr);
+ cairo_restore (cr);
+
+ /* If showing an image (not an icon), draw a border on top of it */
+ if (!priv->showing_icon) {
+ GtkStyleContext *style_context;
+ GdkRGBA border_color;
+ gdouble border_width = 1.0;
+ gdouble inset = 0.5;
+
+ style_context = gtk_widget_get_style_context (priv->frame);
+ gtk_style_context_get_border_color (style_context, GTK_STATE_FLAG_NORMAL, &border_color);
+
+ cairo_set_source_rgba (cr, border_color.red, border_color.green,
+ border_color.blue, border_color.alpha);
+ cairo_set_line_width (cr, border_width);
+ cairo_rectangle (cr, x_offset + inset, y_offset + inset,
+ scaled_width - (inset * 2), scaled_height - (inset * 2));
+ cairo_stroke (cr);
+ }
+
+ return TRUE;
+}
+
+static cairo_surface_t *
+create_surface_from_pixbuf (GdkPixbuf *pixbuf,
+ gint scale_factor)
+{
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ gint width, height;
+
+ g_return_val_if_fail (pixbuf != NULL, NULL);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ cairo_surface_set_device_scale (surface, scale_factor, scale_factor);
+
+ cr = cairo_create (surface);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ return surface;
+}
+
+static void
+load_image_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ LoadImageData *data = task_data;
+ GdkPixbuf *pixbuf = NULL;
+ GdkPixbuf *oriented_pixbuf = NULL;
+ cairo_surface_t *surface = NULL;
+ GError *error = NULL;
+
+ if (data->file_path != NULL) {
+ pixbuf = gdk_pixbuf_new_from_file_at_scale (data->file_path,
+ data->width * data->ui_scale,
+ data->height * data->ui_scale,
+ TRUE,
+ &error);
+ if (error != NULL) {
+ g_clear_error (&error);
+ }
+ }
+
+ if (g_cancellable_is_cancelled (cancellable)) {
+ if (pixbuf != NULL) {
+ g_object_unref (pixbuf);
+ }
+ g_task_return_error_if_cancelled (task);
+ return;
+ }
+
+ if (pixbuf == NULL && data->thumbnail_path != NULL) {
+ pixbuf = gdk_pixbuf_new_from_file_at_scale (data->thumbnail_path,
+ data->width * data->ui_scale,
+ data->height * data->ui_scale,
+ TRUE,
+ &error);
+ if (error != NULL) {
+ g_clear_error (&error);
+ }
+ }
+
+ if (pixbuf != NULL && !g_cancellable_is_cancelled (cancellable)) {
+ oriented_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ g_object_unref (pixbuf);
+ pixbuf = oriented_pixbuf;
+ }
+
+ if (pixbuf != NULL && !g_cancellable_is_cancelled (cancellable)) {
+ surface = create_surface_from_pixbuf (pixbuf, data->ui_scale);
+ g_object_unref (pixbuf);
+ }
+
+ if (!g_cancellable_is_cancelled (cancellable)) {
+ g_task_return_pointer (task, surface, surface ? (GDestroyNotify)cairo_surface_destroy : NULL);
+ } else {
+ if (surface != NULL) {
+ cairo_surface_destroy (surface);
+ }
+ g_task_return_error_if_cancelled (task);
+ }
+}
+
+static void
+load_image_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NemoPreviewImage *widget = NEMO_PREVIEW_IMAGE (source_object);
+ NemoPreviewImagePrivate *priv;
+ GTask *task;
+ LoadImageData *data;
+ cairo_surface_t *surface;
+ gint scale_factor;
+ GError *error = NULL;
+
+ priv = nemo_preview_image_get_instance_private (widget);
+ task = G_TASK (result);
+ data = g_task_get_task_data (task);
+
+ if (priv->current_load_data == data) {
+ priv->current_load_data = NULL;
+ }
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ return;
+
+ if (error != NULL) {
+ g_error_free (error);
+ return;
+ }
+
+ surface = g_task_propagate_pointer (task, &error);
+
+ if (surface != NULL) {
+ if (priv->current_surface != NULL) {
+ cairo_surface_destroy (priv->current_surface);
+ }
+ priv->current_surface = surface;
+
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (widget));
+ priv->surface_width = cairo_image_surface_get_width (surface) / scale_factor;
+ priv->surface_height = cairo_image_surface_get_height (surface) / scale_factor;
+
+ gtk_widget_show (priv->drawing_area);
+ gtk_widget_queue_draw (priv->drawing_area);
+ gtk_widget_hide (priv->message_label);
+ } else {
+ 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);
+ }
+}
+
+static void
+load_icon_at_size (NemoPreviewImage *widget,
+ gint width,
+ gint height)
+{
+ NemoPreviewImagePrivate *priv;
+ NemoIconInfo *icon_info = NULL;
+ GtkIconTheme *icon_theme;
+ GdkPixbuf *icon_pixbuf = NULL;
+ cairo_surface_t *surface = NULL;
+ gint ui_scale;
+ gint icon_size;
+ const char *icon_name;
+ GError *error = NULL;
+
+ priv = nemo_preview_image_get_instance_private (widget);
+
+ if (priv->file == NULL) {
+ return;
+ }
+
+ if (width <= 1 || height <= 1) {
+ return;
+ }
+
+ ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget));
+
+ icon_size = MIN (width, height) * ui_scale;
+ icon_info = nemo_file_get_icon (priv->file, icon_size, 0, ui_scale, 0);
+
+ if (icon_info != NULL && icon_info->icon_name != NULL) {
+ icon_name = icon_info->icon_name;
+ icon_theme = gtk_icon_theme_get_default ();
+
+ icon_pixbuf = gtk_icon_theme_load_icon_for_scale (icon_theme,
+ icon_name,
+ icon_size / ui_scale,
+ ui_scale,
+ GTK_ICON_LOOKUP_FORCE_SIZE,
+ &error);
+
+ if (icon_pixbuf != NULL) {
+ surface = create_surface_from_pixbuf (icon_pixbuf, ui_scale);
+
+ if (surface != NULL) {
+ if (priv->current_surface != NULL) {
+ cairo_surface_destroy (priv->current_surface);
+ }
+ priv->current_surface = surface;
+
+ priv->surface_width = cairo_image_surface_get_width (surface) / ui_scale;
+ priv->surface_height = cairo_image_surface_get_height (surface) / ui_scale;
+
+ gtk_widget_show (priv->drawing_area);
+ gtk_widget_queue_draw (priv->drawing_area);
+ }
+
+ g_object_unref (icon_pixbuf);
+ gtk_widget_hide (priv->message_label);
+ } else {
+ if (error != NULL) {
+ g_warning ("Failed to load icon '%s': %s", icon_name, error->message);
+ g_error_free (error);
+ }
+ gtk_label_set_text (GTK_LABEL (priv->message_label),
+ _("(Failed to load icon)"));
+ gtk_widget_show (priv->message_label);
+ gtk_widget_hide (priv->drawing_area);
+ }
+
+ nemo_icon_info_unref (icon_info);
+ } else {
+ if (icon_info != NULL) {
+ nemo_icon_info_unref (icon_info);
+ }
+ gtk_label_set_text (GTK_LABEL (priv->message_label),
+ _("(No icon available)"));
+ gtk_widget_show (priv->message_label);
+ gtk_widget_hide (priv->drawing_area);
+ }
+
+ priv->current_width = width;
+ priv->current_height = height;
+}
+
+static void
+load_image_at_size (NemoPreviewImage *widget,
+ gint width,
+ gint height)
+{
+ NemoPreviewImagePrivate *priv;
+ GTask *task;
+ LoadImageData *data;
+ gchar *file_path = NULL;
+ gchar *thumbnail_path = NULL;
+ gint ui_scale;
+
+ priv = nemo_preview_image_get_instance_private (widget);
+
+ if (priv->file == NULL) {
+ return;
+ }
+
+ if (width <= 1 || height <= 1) {
+ return;
+ }
+
+ if (priv->current_load_data != NULL) {
+ g_cancellable_cancel (priv->current_load_data->cancellable);
+ priv->current_load_data = NULL; /* Forget about it, callback will clean up */
+ }
+
+ if (nemo_can_thumbnail_internally (priv->file)) {
+ file_path = nemo_file_get_path (priv->file);
+ }
+
+ if (nemo_file_has_loaded_thumbnail (priv->file)) {
+ thumbnail_path = nemo_file_get_thumbnail_path (priv->file);
+ }
+
+ if (file_path == NULL && thumbnail_path == NULL) {
+ gtk_label_set_text (GTK_LABEL (priv->message_label),
+ _("(No preview available)"));
+ gtk_widget_show (priv->message_label);
+ gtk_widget_hide (priv->drawing_area);
+ return;
+ }
+
+ ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget));
+
+ data = load_image_data_new (file_path, thumbnail_path, width, height, ui_scale);
+ g_free (file_path);
+ g_free (thumbnail_path);
+
+ priv->current_load_data = data;
+
+ task = g_task_new (widget, data->cancellable, load_image_callback, NULL);
+ g_task_set_task_data (task, data, (GDestroyNotify) load_image_data_free);
+ g_task_run_in_thread (task, load_image_thread);
+ g_object_unref (task);
+
+ priv->current_width = width;
+ priv->current_height = height;
+}
+
+static void
+reload_at_size (NemoPreviewImage *widget,
+ gint width,
+ gint height)
+{
+ NemoPreviewImagePrivate *priv;
+
+ priv = nemo_preview_image_get_instance_private (widget);
+
+ if (priv->file == NULL) {
+ return;
+ }
+
+ if (nemo_can_thumbnail_internally (priv->file) || nemo_file_has_loaded_thumbnail (priv->file)) {
+ priv->showing_icon = FALSE;
+ load_image_at_size (widget, width, height);
+ } else {
+ priv->showing_icon = TRUE;
+ load_icon_at_size (widget, width, height);
+ }
+}
+
+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);
+ reload_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;
+
+ priv = nemo_preview_image_get_instance_private (preview);
+
+ if (priv->current_surface != NULL) {
+ gtk_widget_queue_draw (priv->drawing_area);
+ }
+
+ if (priv->resize_timeout_id != 0) {
+ g_source_remove (priv->resize_timeout_id);
+ }
+
+ 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;
+ }
+
+ if (priv->current_load_data != NULL) {
+ g_cancellable_cancel (priv->current_load_data->cancellable);
+ priv->current_load_data = NULL;
+ }
+
+ 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;
+
+ 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;
+ priv->surface_width = 0;
+ priv->surface_height = 0;
+
+ if (file != NULL) {
+ nemo_file_ref (file);
+ gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation);
+ reload_at_size (widget, allocation.width, allocation.height);
+ }
+}
+
+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);
+
+ if (priv->current_load_data != NULL) {
+ g_cancellable_cancel (priv->current_load_data->cancellable);
+ priv->current_load_data = NULL;
+ }
+
+ if (priv->resize_timeout_id != 0) {
+ g_source_remove (priv->resize_timeout_id);
+ priv->resize_timeout_id = 0;
+ }
+
+ 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;
+ priv->surface_width = 0;
+ priv->surface_height = 0;
+ priv->showing_icon = FALSE;
+}
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..39265db27 100644
--- a/libnemo-private/org.nemo.gschema.xml
+++ b/libnemo-private/org.nemo.gschema.xml
@@ -76,6 +76,7 @@
+
@@ -273,6 +274,11 @@
Show favorites first in windows
If set to true, then Nemo shows favorites prior to other files in the icon and list views.
+
+ false
+ Show the preview pane by default in newly visited folders
+ If set to true, then Nemo shows the preview pane when visiting new folders.
+
@@ -895,4 +901,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-file-management-properties.c b/src/nemo-file-management-properties.c
index d0a1958e1..c26c272b8 100644
--- a/src/nemo-file-management-properties.c
+++ b/src/nemo-file-management-properties.c
@@ -91,6 +91,7 @@
#define NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_SHOW_THUMBNAILS_ICON_TOOLBAR_WIDGET "show_show_thumbnails_icon_toolbar_togglebutton"
#define NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_TOGGLE_EXTRA_PANE_ICON_TOOLBAR_WIDGET "show_toggle_extra_pane_icon_toolbar_togglebutton"
+#define NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_PREVIEW_PANE_WIDGET "show_preview_pane_checkbutton"
#define NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_FULL_PATH_IN_TITLE_BARS_WIDGET "show_full_path_in_title_bars_checkbutton"
#define NEMO_FILE_MANAGEMENT_PROPERTIES_CLOSE_DEVICE_VIEW_ON_EJECT_WIDGET "close_device_view_on_eject_checkbutton"
#define NEMO_FILE_MANAGEMENT_PROPERTIES_AUTOMOUNT_MEDIA_WIDGET "media_automount_checkbutton"
@@ -931,6 +932,10 @@ nemo_file_management_properties_dialog_setup (GtkBuilder *builder,
NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_SHOW_THUMBNAILS_ICON_TOOLBAR_WIDGET,
NEMO_PREFERENCES_SHOW_SHOW_THUMBNAILS_TOOLBAR);
+ bind_builder_bool (builder, nemo_preferences,
+ NEMO_FILE_MANAGEMENT_PROPERTIES_SHOW_PREVIEW_PANE_WIDGET,
+ NEMO_PREFERENCES_SHOW_PREVIEW_PANE);
+
/* setup preferences */
bind_builder_bool (builder, nemo_icon_view_preferences,
NEMO_FILE_MANAGEMENT_PROPERTIES_LABELS_BESIDE_ICONS_WIDGET,
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-statusbar.c b/src/nemo-statusbar.c
index 40ff78bc3..09a98167a 100644
--- a/src/nemo-statusbar.c
+++ b/src/nemo-statusbar.c
@@ -90,6 +90,34 @@ nemo_status_bar_dispose (GObject *object)
G_OBJECT_CLASS (nemo_status_bar_parent_class)->dispose (object);
}
+static void
+action_toggle_split_view_callback (GtkButton *button, NemoStatusBar *bar)
+{
+ NemoWindow *window = bar->window;
+
+ if (!nemo_window_split_view_showing (window)) {
+ nemo_window_split_view_on (window);
+ } else {
+ nemo_window_split_view_off (window);
+ }
+
+ nemo_status_bar_sync_button_states (bar);
+}
+
+static void
+action_toggle_preview_pane_callback (GtkButton *button, NemoStatusBar *bar)
+{
+ NemoWindow *window = bar->window;
+
+ if (!nemo_window_preview_pane_showing (window)) {
+ nemo_window_preview_pane_on (window);
+ } else {
+ nemo_window_preview_pane_off (window);
+ }
+
+ nemo_status_bar_sync_button_states (bar);
+}
+
static void
action_places_toggle_callback (GtkButton *button, NemoStatusBar *bar)
{
@@ -130,6 +158,18 @@ sidebar_type_changed_cb (gpointer pointer, const gchar *sidebar_id, gpointer use
nemo_status_bar_sync_button_states (NEMO_STATUS_BAR (user_data));
}
+static void
+preview_pane_state_changed_cb (gpointer pointer, GParamSpec *pspec, gpointer user_data)
+{
+ nemo_status_bar_sync_button_states (NEMO_STATUS_BAR (user_data));
+}
+
+static void
+split_view_state_changed_cb (gpointer pointer, GParamSpec *pspec, gpointer user_data)
+{
+ nemo_status_bar_sync_button_states (NEMO_STATUS_BAR (user_data));
+}
+
static void
on_slider_changed_cb (GtkWidget *zoom_slider, gpointer user_data)
{
@@ -229,6 +269,24 @@ nemo_status_bar_constructed (GObject *object)
gtk_range_set_increments (GTK_RANGE (zoom_slider), 1.0, 1.0);
gtk_range_set_round_digits (GTK_RANGE (zoom_slider), 0);
+ button = gtk_toggle_button_new ();
+ icon = gtk_image_new_from_icon_name ("view-dual-symbolic", size);
+ gtk_button_set_image (GTK_BUTTON (button), icon);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (button), _("Show Split View (F3)"));
+ bar->split_view_button = button;
+ gtk_box_pack_start (GTK_BOX (bar), button, FALSE, FALSE, 2);
+ g_signal_connect (GTK_BUTTON (button), "clicked",
+ G_CALLBACK (action_toggle_split_view_callback), bar);
+
+ button = gtk_toggle_button_new ();
+ icon = gtk_image_new_from_icon_name ("xsi-preview-symbolic", size);
+ gtk_button_set_image (GTK_BUTTON (button), icon);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (button), _("Show the Preview pane (F7)"));
+ bar->preview_pane_button = button;
+ gtk_box_pack_start (GTK_BOX (bar), button, FALSE, FALSE, 2);
+ g_signal_connect (GTK_BUTTON (button), "clicked",
+ G_CALLBACK (action_toggle_preview_pane_callback), bar);
+
gtk_widget_show_all (GTK_WIDGET (bar));
g_signal_connect_object (NEMO_WINDOW (bar->window), "notify::show-sidebar",
@@ -237,6 +295,12 @@ nemo_status_bar_constructed (GObject *object)
g_signal_connect_object (NEMO_WINDOW (bar->window), "notify::sidebar-view-id",
G_CALLBACK (sidebar_type_changed_cb), bar, G_CONNECT_AFTER);
+ g_signal_connect_object (NEMO_WINDOW (bar->window), "notify::show-preview-pane",
+ G_CALLBACK (preview_pane_state_changed_cb), bar, G_CONNECT_AFTER);
+
+ g_signal_connect_object (NEMO_WINDOW (bar->window), "notify::show-split-view",
+ G_CALLBACK (split_view_state_changed_cb), bar, G_CONNECT_AFTER);
+
g_signal_connect (GTK_RANGE (zoom_slider), "value-changed",
G_CALLBACK (on_slider_changed_cb), bar);
@@ -332,6 +396,23 @@ nemo_status_bar_sync_button_states (NemoStatusBar *bar)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bar->places_button), FALSE);
}
g_signal_handlers_unblock_by_func (GTK_BUTTON (bar->places_button), action_places_toggle_callback, bar);
+
+ g_signal_handlers_block_by_func (GTK_BUTTON (bar->split_view_button), action_toggle_split_view_callback, bar);
+ if (nemo_window_split_view_showing (bar->window)) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bar->split_view_button), TRUE);
+ } else {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bar->split_view_button), FALSE);
+ }
+ g_signal_handlers_unblock_by_func (GTK_BUTTON (bar->split_view_button), action_toggle_split_view_callback, bar);
+
+ g_signal_handlers_block_by_func (GTK_BUTTON (bar->preview_pane_button), action_toggle_preview_pane_callback, bar);
+ if (nemo_window_preview_pane_showing (bar->window)) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bar->preview_pane_button), TRUE);
+ } else {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bar->preview_pane_button), FALSE);
+ }
+ g_signal_handlers_unblock_by_func (GTK_BUTTON (bar->preview_pane_button), action_toggle_preview_pane_callback, bar);
+
}
void
diff --git a/src/nemo-statusbar.h b/src/nemo-statusbar.h
index 5b8af4d02..8e2382843 100644
--- a/src/nemo-statusbar.h
+++ b/src/nemo-statusbar.h
@@ -54,6 +54,8 @@ struct _NemoStatusBar
GtkWidget *show_button;
GtkWidget *hide_button;
GtkWidget *separator;
+ GtkWidget *split_view_button;
+ GtkWidget *preview_pane_button;
};
struct _NemoStatusBarClass
diff --git a/src/nemo-view.c b/src/nemo-view.c
index c211ee5bf..3377e2638 100644
--- a/src/nemo-view.c
+++ b/src/nemo-view.c
@@ -528,6 +528,7 @@ nemo_view_reset_to_defaults (NemoView *view)
file = view->details->slot->viewed_file;
nemo_file_set_metadata(file, NEMO_METADATA_KEY_SHOW_THUMBNAILS, NULL, NULL);
nemo_file_set_metadata(file, NEMO_METADATA_KEY_DEFAULT_VIEW, NULL, NULL);
+ nemo_file_set_metadata (file, NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE, NULL, NULL);
gtk_action_activate (gtk_action_group_get_action (nemo_window_get_main_action_group (window), NEMO_ACTION_RELOAD));
}
diff --git a/src/nemo-window-menus.c b/src/nemo-window-menus.c
index 46f10b8e6..5d01b7e23 100644
--- a/src/nemo-window-menus.c
+++ b/src/nemo-window-menus.c
@@ -658,8 +658,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;
+ }
- nemo_window_update_show_hide_ui_elements (window);
+ 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);
+ }
+ }
}
static void
@@ -784,18 +812,33 @@ nemo_window_update_show_hide_ui_elements (NemoWindow *window)
NemoWindowPane *pane;
GtkActionGroup *action_group;
GtkAction *action;
+ gboolean active, split_view_showing, preview_showing;
+ split_view_showing = nemo_window_split_view_showing (window);
+ preview_showing = nemo_window_preview_pane_showing (window);
action_group = nemo_window_get_main_action_group (window);
action = gtk_action_group_get_action (action_group,
NEMO_ACTION_SHOW_HIDE_EXTRA_PANE);
- gtk_action_block_activate (action);
- gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
- nemo_window_split_view_showing (window));
- gtk_action_unblock_activate (action);
+ active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
- nemo_window_update_split_view_actions_sensitivity (window);
+ if (active != split_view_showing) {
+ gtk_action_block_activate (action);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), split_view_showing);
+ gtk_action_unblock_activate (action);
+ }
+
+ action = gtk_action_group_get_action (action_group,
+ NEMO_ACTION_SHOW_HIDE_PREVIEW_PANE);
+ active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ if (active != preview_showing) {
+ gtk_action_block_activate (action);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), preview_showing);
+ gtk_action_unblock_activate (action);
+ }
+ nemo_window_update_split_view_actions_sensitivity (window);
update_side_bar_radio_buttons (window);
pane = nemo_window_get_active_pane (window);
@@ -804,10 +847,13 @@ nemo_window_update_show_hide_ui_elements (NemoWindow *window)
action = gtk_action_group_get_action (action_group,
NEMO_ACTION_SHOW_HIDE_EXTRA_PANE);
- gtk_action_block_activate (action);
- gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
- nemo_window_split_view_showing (window));
- gtk_action_unblock_activate (action);
+ active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ if (active != split_view_showing) {
+ gtk_action_block_activate (action);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), split_view_showing);
+ gtk_action_unblock_activate (action);
+ }
}
}
@@ -1573,6 +1619,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..d203867f2 100644
--- a/src/nemo-window-private.h
+++ b/src/nemo-window-private.h
@@ -72,6 +72,9 @@ struct NemoWindowDetails
NemoWindowPane *active_pane;
GtkWidget *content_paned;
+ GtkWidget *base_paned;
+ GtkWidget *secondary_paned;
+
NemoNavigationState *nav_state;
/* Side Pane */
@@ -92,13 +95,18 @@ struct NemoWindowDetails
guint menu_hide_delay_id;
- /* split view */
- GtkWidget *split_view_hpane;
-
// A closed pane's location, valid until the remaining pane
// location changes.
GFile *secondary_pane_last_location;
+ /* preview pane */
+ GtkWidget *preview_pane; // NemoPreviewPane instance
+ gboolean show_preview_pane; // State flag
+ gboolean preview_pane_width_set; // Initial width has been set from settings
+
+ /* split view */
+ gboolean show_split_view; // State flag
+
gboolean disable_chrome;
guint sidebar_width_handler_id;
diff --git a/src/nemo-window.c b/src/nemo-window.c
index a9e502d72..db168eb56 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
@@ -105,6 +106,8 @@ enum {
PROP_DISABLE_CHROME = 1,
PROP_SIDEBAR_VIEW_TYPE,
PROP_SHOW_SIDEBAR,
+ PROP_SHOW_PREVIEW_PANE,
+ PROP_SHOW_SPLIT_VIEW,
NUM_PROPERTIES,
};
@@ -414,7 +417,7 @@ setup_side_pane_width (NemoWindow *window)
g_settings_get_int (nemo_window_state,
NEMO_WINDOW_STATE_SIDEBAR_WIDTH);
- gtk_paned_set_position (GTK_PANED (window->details->content_paned),
+ gtk_paned_set_position (GTK_PANED (window->details->base_paned),
window->details->side_pane_width);
}
@@ -429,7 +432,7 @@ nemo_window_set_up_sidebar (NemoWindow *window)
gtk_style_context_add_class (gtk_widget_get_style_context (window->details->sidebar),
GTK_STYLE_CLASS_SIDEBAR);
- gtk_paned_pack1 (GTK_PANED (window->details->content_paned),
+ gtk_paned_pack1 (GTK_PANED (window->details->base_paned),
GTK_WIDGET (window->details->sidebar),
FALSE, FALSE);
@@ -602,13 +605,40 @@ on_menu_selection_done (GtkMenuShell *menushell,
window->details->menu_hide_delay_id = g_timeout_add (0, (GSourceFunc) hide_menu_on_delay, window);
}
+/*
+ 3-pane layout
+
+ ------------------------------------------------------
+ | |------------------------------------------||
+ | ||--------split_view_paned--------| ||
+ | S || | | P ||
+ | I || | | R ||
+ | D || VIEW | VIEW | E ||
+ | E || | | V ||
+ | B || | | I ||
+ | A || | | E ||
+ | R || | | W ||
+ | || | | ||
+ | || | | ||
+ | ||---------------|----------------| ||
+ | |--------------secondary_paned-------------||
+ |---------------------base_paned---------------------|
+
+ base_paned:
+ - L: sidebar widget
+ - R: secondary_paned:
+ - L: content_paned:
+ - content_view
+ - content_view (when split-view on)
+ - R: preview pane
+*/
+
static void
nemo_window_constructed (GObject *self)
{
NemoWindow *window;
GtkWidget *grid;
GtkWidget *menu;
- GtkWidget *hpaned;
GtkWidget *vbox;
GtkWidget *toolbar_holder;
GtkWidget *nemo_statusbar;
@@ -685,28 +715,33 @@ nemo_window_constructed (GObject *self)
g_signal_connect_object (nemo_signaller_get_current (), "popup_menu_changed",
G_CALLBACK (nemo_window_load_extension_menus), window, G_CONNECT_SWAPPED);
- window->details->content_paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
- gtk_widget_set_hexpand (window->details->content_paned, TRUE);
- gtk_widget_set_vexpand (window->details->content_paned, TRUE);
+ /* Create base_paned: sidebar | secondary_paned */
+ window->details->base_paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_set_hexpand (window->details->base_paned, TRUE);
+ gtk_widget_set_vexpand (window->details->base_paned, TRUE);
- gtk_container_add (GTK_CONTAINER (grid), window->details->content_paned);
- gtk_widget_show (window->details->content_paned);
+ gtk_container_add (GTK_CONTAINER (grid), window->details->base_paned);
+ gtk_widget_show (window->details->base_paned);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
- gtk_paned_pack2 (GTK_PANED (window->details->content_paned), vbox,
+ gtk_paned_pack2 (GTK_PANED (window->details->base_paned), vbox,
TRUE, FALSE);
gtk_widget_show (vbox);
- hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
- gtk_box_pack_start (GTK_BOX (vbox), hpaned, TRUE, TRUE, 0);
- gtk_widget_show (hpaned);
- window->details->split_view_hpane = hpaned;
+ /* Create secondary_paned: content_paned | preview_pane */
+ window->details->secondary_paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_pack_start (GTK_BOX (vbox), window->details->secondary_paned, TRUE, TRUE, 0);
+ gtk_widget_show (window->details->secondary_paned);
+
+ /* Create content_paned: primary_view | secondary_view */
+ window->details->content_paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_paned_pack1 (GTK_PANED (window->details->secondary_paned), window->details->content_paned, TRUE, FALSE);
+ gtk_widget_show (window->details->content_paned);
pane = nemo_window_pane_new (window);
window->details->panes = g_list_prepend (window->details->panes, pane);
- gtk_paned_pack1 (GTK_PANED (hpaned), GTK_WIDGET (pane), TRUE, FALSE);
-
+ gtk_paned_pack1 (GTK_PANED (window->details->content_paned), GTK_WIDGET (pane), TRUE, FALSE);
nemo_statusbar = nemo_status_bar_new (window);
window->details->nemo_status_bar = nemo_statusbar;
@@ -785,6 +820,20 @@ nemo_window_set_property (GObject *object,
case PROP_SHOW_SIDEBAR:
nemo_window_set_show_sidebar (window, g_value_get_boolean (value));
break;
+ case PROP_SHOW_PREVIEW_PANE:
+ if (g_value_get_boolean (value)) {
+ nemo_window_preview_pane_on (window);
+ } else {
+ nemo_window_preview_pane_off (window);
+ }
+ break;
+ case PROP_SHOW_SPLIT_VIEW:
+ if (g_value_get_boolean (value)) {
+ nemo_window_split_view_on (window);
+ } else {
+ nemo_window_split_view_off (window);
+ }
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, arg_id, pspec);
break;
@@ -811,6 +860,12 @@ nemo_window_get_property (GObject *object,
case PROP_SHOW_SIDEBAR:
g_value_set_boolean (value, window->details->show_sidebar);
break;
+ case PROP_SHOW_PREVIEW_PANE:
+ g_value_set_boolean (value, window->details->show_preview_pane);
+ break;
+ case PROP_SHOW_SPLIT_VIEW:
+ g_value_set_boolean (value, window->details->show_split_view);
+ break;
default:
g_assert_not_reached ();
break;
@@ -1053,6 +1108,10 @@ nemo_window_set_active_pane (NemoWindow *window,
}
}
+/* Forward declarations for preview pane callbacks */
+static void preview_pane_selection_changed_callback (NemoView *view, NemoWindow *window);
+static void slot_location_changed_callback (NemoWindowSlot *slot, const char *from_uri, const char *to_uri, NemoWindow *window);
+
/* Make both, the given slot the active slot and its corresponding
* pane the active pane of the associated window.
* new_slot may be NULL. */
@@ -1082,6 +1141,10 @@ nemo_window_set_active_slot (NemoWindow *window, NemoWindowSlot *new_slot)
if (old_slot->content_view != NULL) {
nemo_window_disconnect_content_view (window, old_slot->content_view);
}
+ /* Disconnect from slot's location-changed signal */
+ g_signal_handlers_disconnect_by_func (old_slot,
+ G_CALLBACK (slot_location_changed_callback),
+ window);
gtk_widget_hide (GTK_WIDGET (old_slot->pane->tool_bar));
/* inform slot & view */
g_signal_emit_by_name (old_slot, "inactive");
@@ -1106,6 +1169,11 @@ nemo_window_set_active_slot (NemoWindow *window, NemoWindowSlot *new_slot)
nemo_window_connect_content_view (window, new_slot->content_view);
}
+ /* Connect to slot's location-changed signal for preview pane updates */
+ g_signal_connect (new_slot, "location-changed",
+ G_CALLBACK (slot_location_changed_callback),
+ window);
+
// Show active toolbar
gboolean show_toolbar;
show_toolbar = g_settings_get_boolean (nemo_window_state, NEMO_WINDOW_STATE_START_WITH_TOOLBAR);
@@ -1558,6 +1626,96 @@ zoom_level_changed_callback (NemoView *view,
nemo_window_sync_zoom_widgets (window);
}
+/* Forward declarations */
+static void nemo_window_preview_pane_on_internal (NemoWindow *window, gboolean write_metadata);
+static void nemo_window_preview_pane_off_internal (NemoWindow *window, gboolean write_metadata);
+
+/* Check if any slot in the window (across all panes and tabs) has
+ * preview pane enabled in its metadata, excluding the given slot.
+ */
+static gboolean
+any_other_slot_wants_preview_pane (NemoWindow *window,
+ NemoWindowSlot *exclude_slot)
+{
+ GList *pane_node;
+ GList *slot_node;
+ NemoWindowPane *pane;
+ NemoWindowSlot *slot;
+ NemoFile *directory_file;
+ gboolean show_preview;
+
+ for (pane_node = window->details->panes; pane_node != NULL; pane_node = pane_node->next) {
+ pane = NEMO_WINDOW_PANE (pane_node->data);
+
+ for (slot_node = pane->slots; slot_node != NULL; slot_node = slot_node->next) {
+ slot = NEMO_WINDOW_SLOT (slot_node->data);
+
+ /* Skip the slot we're excluding */
+ if (slot == exclude_slot) {
+ continue;
+ }
+
+ /* Check if this slot has a view with a directory */
+ if (slot->content_view != NULL) {
+ directory_file = nemo_view_get_directory_as_file (slot->content_view);
+ if (directory_file != NULL) {
+ show_preview = nemo_file_get_boolean_metadata (directory_file,
+ NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE,
+ FALSE);
+ if (show_preview) {
+ return TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/* Synchronize the preview pane state based on a slot's directory metadata.
+ * This checks the slot's current directory's saved preference and applies it,
+ * taking into account whether other slots in the window want the preview pane.
+ * If no metadata is saved for this directory, uses the default from GSettings.
+ */
+static void
+sync_preview_pane_from_slot_metadata (NemoWindow *window,
+ NemoWindowSlot *slot)
+{
+ NemoFile *directory_file;
+ gboolean show_preview;
+ gboolean default_show_preview;
+
+ if (slot == NULL || slot->content_view == NULL) {
+ return;
+ }
+
+ directory_file = nemo_view_get_directory_as_file (slot->content_view);
+ if (directory_file == NULL) {
+ return;
+ }
+
+ /* Get the default from GSettings - this is used when directory has no saved metadata */
+ default_show_preview = g_settings_get_boolean (nemo_preferences,
+ NEMO_PREFERENCES_SHOW_PREVIEW_PANE);
+
+ /* Get the preview pane preference: uses directory metadata if saved,
+ * otherwise falls back to the GSettings default */
+ show_preview = nemo_file_get_boolean_metadata (directory_file,
+ NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE,
+ default_show_preview);
+
+ /* Apply the saved state (don't write metadata - this is automatic sync, not user action) */
+ if (show_preview && !window->details->show_preview_pane) {
+ nemo_window_preview_pane_on_internal (window, FALSE);
+ } else if (!show_preview && window->details->show_preview_pane) {
+ /* Only close preview pane if no other slot wants it */
+ if (!any_other_slot_wants_preview_pane (window, slot)) {
+ nemo_window_preview_pane_off_internal (window, FALSE);
+ }
+ }
+}
+
/* These are called
* A) when switching the view within the active slot
@@ -1583,6 +1741,16 @@ nemo_window_connect_content_view (NemoWindow *window,
G_CALLBACK (zoom_level_changed_callback),
window);
+ /* Check if this directory has a saved preview pane state */
+ sync_preview_pane_from_slot_metadata (window, slot);
+
+ /* 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 +1775,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);
+ }
}
/**
@@ -1847,7 +2020,7 @@ create_extra_pane (NemoWindow *window)
pane = nemo_window_pane_new (window);
window->details->panes = g_list_append (window->details->panes, pane);
- paned = GTK_PANED (window->details->split_view_hpane);
+ paned = GTK_PANED (window->details->content_paned);
g_signal_connect_after (paned,
"notify::position",
@@ -2081,6 +2254,20 @@ nemo_window_class_init (NemoWindowClass *class)
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_SHOW_PREVIEW_PANE] =
+ g_param_spec_boolean ("show-preview-pane",
+ "Show the preview pane",
+ "Show the preview pane",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SHOW_SPLIT_VIEW] =
+ g_param_spec_boolean ("show-split-view",
+ "Show split view",
+ "Show split view",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
signals[GO_UP] =
g_signal_new ("go-up",
G_TYPE_FROM_CLASS (class),
@@ -2187,6 +2374,111 @@ nemo_window_new (GtkApplication *application,
NULL);
}
+static void
+slot_location_changed_callback (NemoWindowSlot *slot,
+ const char *from_uri,
+ const char *to_uri,
+ NemoWindow *window)
+{
+ GList *selection;
+ NemoFile *file = NULL;
+
+ /* Check if this directory has a saved preview pane state */
+ sync_preview_pane_from_slot_metadata (window, slot);
+
+ /* Update preview pane content when location changes */
+ if (window->details->preview_pane != NULL) {
+ /* Clear selection first since we're in a new location */
+ nemo_preview_pane_set_file (NEMO_PREVIEW_PANE (window->details->preview_pane), NULL);
+
+ /* If the new location has a selection, show it */
+ if (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);
+ }
+ }
+}
+
+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
+set_preview_pane_width_from_settings (NemoWindow *window)
+{
+ gint saved_width, position, total_width;
+
+ /* Only set initial position once */
+ if (window->details->preview_pane_width_set) {
+ return;
+ }
+
+ window->details->preview_pane_width_set = TRUE;
+
+ /* Set position based on saved preview pane width */
+ saved_width = g_settings_get_int (nemo_preview_pane_preferences, "pane-width");
+ total_width = gtk_widget_get_allocated_width (window->details->secondary_paned);
+
+ if (saved_width > 100 && total_width > saved_width) {
+ /* Position is from left, so subtract preview width from total */
+ position = total_width - saved_width;
+ gtk_paned_set_position (GTK_PANED (window->details->secondary_paned), position);
+ } else {
+ /* Fallback: 60/40 split */
+ gtk_paned_set_position (GTK_PANED (window->details->secondary_paned), total_width * 0.6);
+ }
+}
+
+static void
+preview_pane_realize_callback (GtkWidget *widget, gpointer user_data)
+{
+ NemoWindow *window = NEMO_WINDOW (user_data);
+ set_preview_pane_width_from_settings (window);
+}
+
+static void
+preview_pane_position_changed_callback (GObject *paned, GParamSpec *pspec, NemoWindow *window)
+{
+ gint position, total_width, preview_width;
+
+ /* Don't save position until initial width has been set */
+ if (!window->details->preview_pane_width_set) {
+ return;
+ }
+
+ 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)
{
@@ -2195,6 +2487,7 @@ nemo_window_split_view_on (NemoWindow *window)
old_active_slot = nemo_window_get_active_slot (window);
slot = create_extra_pane (window);
+ gtk_widget_show_all (GTK_WIDGET (slot));
location = window->details->secondary_pane_last_location;
@@ -2214,7 +2507,11 @@ nemo_window_split_view_on (NemoWindow *window)
nemo_window_slot_open_location (slot, location, 0);
g_object_unref (location);
+ window->details->show_split_view = TRUE;
window_set_search_action_text (window, FALSE);
+ nemo_window_update_show_hide_ui_elements (window);
+
+ g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_SPLIT_VIEW]);
}
void
@@ -2236,9 +2533,7 @@ nemo_window_split_view_off (NemoWindow *window)
}
}
- /* Reset split view pane's position so the position can be
- * caught again later */
- g_object_set (G_OBJECT (window->details->split_view_hpane),
+ g_object_set (G_OBJECT (window->details->content_paned),
"position", 0,
"position-set", FALSE,
NULL);
@@ -2247,7 +2542,14 @@ nemo_window_split_view_off (NemoWindow *window)
nemo_navigation_state_set_master (window->details->nav_state,
active_pane->action_group);
+ window->details->show_split_view = FALSE;
nemo_window_update_show_hide_ui_elements (window);
+
+ /* After closing split view, check if the remaining active slot
+ * wants the preview pane based on its directory metadata */
+ sync_preview_pane_from_slot_metadata (window, active_pane->active_slot);
+
+ g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_SPLIT_VIEW]);
}
gboolean
@@ -2256,6 +2558,157 @@ nemo_window_split_view_showing (NemoWindow *window)
return g_list_length (NEMO_WINDOW (window)->details->panes) > 1;
}
+static void
+nemo_window_preview_pane_on_internal (NemoWindow *window,
+ gboolean write_metadata)
+{
+ NemoWindowSlot *slot;
+ GList *selection;
+ NemoFile *file = NULL;
+
+ if (nemo_window_is_desktop (window)) {
+ return;
+ }
+
+ /* Reset flag so position can be set */
+ window->details->preview_pane_width_set = FALSE;
+
+ /* Create preview pane */
+ window->details->preview_pane = nemo_preview_pane_new (window);
+
+ /* Pack into right side of secondary_paned */
+ gtk_paned_pack2 (GTK_PANED (window->details->secondary_paned),
+ window->details->preview_pane,
+ TRUE, FALSE);
+
+ gtk_widget_show (window->details->preview_pane);
+
+ /* Set position from settings - check if paned is already realized */
+ if (gtk_widget_get_realized (window->details->secondary_paned)) {
+ /* Already realized, set position immediately */
+ set_preview_pane_width_from_settings (window);
+ } else {
+ /* Not realized yet, wait for realize signal */
+ g_signal_connect (window->details->secondary_paned, "realize",
+ G_CALLBACK (preview_pane_realize_callback),
+ window);
+ }
+
+ /* Connect signal to save position on resize */
+ g_signal_connect (window->details->secondary_paned, "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);
+
+ /* Save preview pane state to directory metadata (only if explicitly requested) */
+ if (write_metadata && slot != NULL && slot->content_view != NULL) {
+ NemoFile *directory_file;
+
+ directory_file = nemo_view_get_directory_as_file (slot->content_view);
+ if (directory_file != NULL) {
+ nemo_file_set_boolean_metadata (directory_file,
+ NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE,
+ FALSE,
+ TRUE);
+ }
+ }
+ nemo_window_update_show_hide_ui_elements (window);
+
+ g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_PREVIEW_PANE]);
+}
+
+void
+nemo_window_preview_pane_on (NemoWindow *window)
+{
+ /* User-triggered action: write metadata to remember this choice */
+ nemo_window_preview_pane_on_internal (window, TRUE);
+}
+
+static void
+nemo_window_preview_pane_off_internal (NemoWindow *window,
+ gboolean write_metadata)
+{
+ GtkPaned *paned;
+ NemoWindowSlot *slot;
+
+ if (window->details->preview_pane == NULL) {
+ return;
+ }
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (window->details->secondary_paned,
+ G_CALLBACK (preview_pane_realize_callback),
+ window);
+ g_signal_handlers_disconnect_by_func (window->details->secondary_paned,
+ G_CALLBACK (preview_pane_position_changed_callback),
+ window);
+
+ paned = GTK_PANED (window->details->secondary_paned);
+
+ /* 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);
+
+ /* Save preview pane state to directory metadata (only if explicitly requested) */
+ slot = nemo_window_get_active_slot (window);
+ if (write_metadata && slot != NULL && slot->content_view != NULL) {
+ NemoFile *directory_file;
+
+ directory_file = nemo_view_get_directory_as_file (slot->content_view);
+ if (directory_file != NULL) {
+ nemo_file_set_boolean_metadata (directory_file,
+ NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE,
+ FALSE,
+ FALSE);
+ }
+ }
+ nemo_window_update_show_hide_ui_elements (window);
+
+ g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_PREVIEW_PANE]);
+}
+
+void
+nemo_window_preview_pane_off (NemoWindow *window)
+{
+ /* User-triggered action: write metadata to remember this choice */
+ nemo_window_preview_pane_off_internal (window, TRUE);
+}
+
+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);