From ee0f9cdb56d07c084a96c2eaf2183fd4c459de2f Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Wed, 10 Dec 2025 10:57:44 -0500 Subject: [PATCH 01/19] Add a preview pane, initial run. very wip... - only connects to current view when activated, not 'any' view - does not handle multiple selection - image resolution no good (it's using thumbnails still) - pane sizing is problematic. - many image types not shown currently. - ... --- gresources/nemo-shell-ui.xml | 3 +- libnemo-private/meson.build | 2 + libnemo-private/nemo-preview-details.c | 240 +++++++++++++++++++++++++ libnemo-private/nemo-preview-details.h | 39 ++++ libnemo-private/nemo-preview-image.c | 221 +++++++++++++++++++++++ libnemo-private/nemo-preview-image.h | 39 ++++ src/meson.build | 1 + src/nemo-actions.h | 1 + src/nemo-preview-pane.c | 230 ++++++++++++++++++++++++ src/nemo-preview-pane.h | 42 +++++ src/nemo-window-menus.c | 35 ++++ src/nemo-window-private.h | 4 + src/nemo-window.c | 108 +++++++++++ src/nemo-window.h | 4 + 14 files changed, 968 insertions(+), 1 deletion(-) create mode 100644 libnemo-private/nemo-preview-details.c create mode 100644 libnemo-private/nemo-preview-details.h create mode 100644 libnemo-private/nemo-preview-image.c create mode 100644 libnemo-private/nemo-preview-image.h create mode 100644 src/nemo-preview-pane.c create mode 100644 src/nemo-preview-pane.h 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-preview-details.c b/libnemo-private/nemo-preview-details.c new file mode 100644 index 000000000..04efb4dbb --- /dev/null +++ b/libnemo-private/nemo-preview-details.c @@ -0,0 +1,240 @@ +/* -*- 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_label_set_max_width_chars (GTK_LABEL (value), 30); + 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..0753250b0 --- /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..c9a4c701b --- /dev/null +++ b/libnemo-private/nemo-preview-image.c @@ -0,0 +1,221 @@ +/* -*- 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 MAX_PREVIEW_SIZE 400 + +struct _NemoPreviewImage { + GtkBox parent; +}; + +typedef struct { + GtkWidget *image; + GtkWidget *message_label; + NemoFile *file; +} NemoPreviewImagePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (NemoPreviewImage, nemo_preview_image, GTK_TYPE_BOX) + +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->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); + + /* Create image widget */ + priv->image = gtk_image_new (); + gtk_widget_set_halign (priv->image, GTK_ALIGN_CENTER); + gtk_widget_set_valign (priv->image, GTK_ALIGN_CENTER); + gtk_box_pack_start (GTK_BOX (preview), priv->image, 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); +} + +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 GdkPixbuf * +scale_pixbuf_to_fit (GdkPixbuf *pixbuf, gint max_width, gint max_height) +{ + gint orig_width, orig_height; + gint new_width, new_height; + gdouble scale; + + if (pixbuf == NULL) { + return NULL; + } + + orig_width = gdk_pixbuf_get_width (pixbuf); + orig_height = gdk_pixbuf_get_height (pixbuf); + + /* If image is smaller than max size, don't scale up */ + if (orig_width <= max_width && orig_height <= max_height) { + return g_object_ref (pixbuf); + } + + /* Calculate scale factor to fit within max dimensions */ + scale = MIN ((gdouble) max_width / orig_width, + (gdouble) max_height / orig_height); + + new_width = (gint) (orig_width * scale); + new_height = (gint) (orig_height * scale); + + return gdk_pixbuf_scale_simple (pixbuf, new_width, new_height, + GDK_INTERP_BILINEAR); +} + +void +nemo_preview_image_set_file (NemoPreviewImage *widget, + NemoFile *file) +{ + NemoPreviewImagePrivate *priv; + GdkPixbuf *pixbuf = NULL; + GdkPixbuf *scaled_pixbuf = NULL; + cairo_surface_t *surface = NULL; + gint ui_scale; + + g_return_if_fail (NEMO_IS_PREVIEW_IMAGE (widget)); + + priv = nemo_preview_image_get_instance_private (widget); + + if (priv->file == file) { + return; + } + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + } + + priv->file = file; + + /* Clear current image */ + gtk_image_clear (GTK_IMAGE (priv->image)); + gtk_widget_hide (priv->message_label); + gtk_widget_hide (priv->image); + + if (file != NULL) { + nemo_file_ref (file); + + ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); + + /* Try to get thumbnail/icon */ + pixbuf = nemo_file_get_icon_pixbuf (file, + MAX_PREVIEW_SIZE, + TRUE, + ui_scale, + NEMO_FILE_ICON_FLAGS_USE_THUMBNAILS); + + if (pixbuf != NULL) { + /* Scale pixbuf to fit within max dimensions */ + scaled_pixbuf = scale_pixbuf_to_fit (pixbuf, MAX_PREVIEW_SIZE, MAX_PREVIEW_SIZE); + + if (scaled_pixbuf != NULL) { + surface = gdk_cairo_surface_create_from_pixbuf (scaled_pixbuf, ui_scale, NULL); + + if (surface != NULL) { + gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); + gtk_widget_show (priv->image); + cairo_surface_destroy (surface); + } + + g_object_unref (scaled_pixbuf); + } + + g_object_unref (pixbuf); + + /* Check if this is a real image file or just an icon */ + /* If it's just an icon (not a thumbnail), show a message */ + if (nemo_file_is_directory (file)) { + gtk_label_set_text (GTK_LABEL (priv->message_label), + _("(Folder)")); + gtk_widget_show (priv->message_label); + } + } else { + /* No preview available */ + gtk_label_set_text (GTK_LABEL (priv->message_label), + _("(Image preview not available)")); + 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); + + if (priv->file != NULL) { + nemo_file_unref (priv->file); + priv->file = NULL; + } + + gtk_image_clear (GTK_IMAGE (priv->image)); + gtk_widget_hide (priv->image); + gtk_widget_hide (priv->message_label); +} diff --git a/libnemo-private/nemo-preview-image.h b/libnemo-private/nemo-preview-image.h new file mode 100644 index 000000000..e5dadad7a --- /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/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..9ffde1426 --- /dev/null +++ b/src/nemo-preview-pane.c @@ -0,0 +1,230 @@ +/* -*- 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 + +#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; +} NemoPreviewPanePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (NemoPreviewPane, nemo_preview_pane, GTK_TYPE_BOX) + +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); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + + 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); + + /* Set initial position for the paned */ + gtk_paned_set_position (GTK_PANED (priv->vpaned), PREVIEW_IMAGE_HEIGHT); +} + +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_object (file, "changed", + G_CALLBACK (file_changed_callback), + pane, 0); + + /* 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 46f10b8e6..b6210b664 100644 --- a/src/nemo-window-menus.c +++ b/src/nemo-window-menus.c @@ -658,6 +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; + } + + 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); } @@ -1573,6 +1603,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..09406c081 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 @@ -2187,12 +2188,38 @@ 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); +} + 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 +2283,87 @@ 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); + + /* Set initial position (60% for file view, 40% for preview) */ + gtk_paned_set_position (GTK_PANED (window->details->split_view_hpane), + gtk_widget_get_allocated_width (window->details->split_view_hpane) * 0.6); + + gtk_widget_show (window->details->preview_pane); + + /* 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 */ + 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 signals */ + /* Note: g_signal_connect_object handles disconnection automatically */ + + 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); From dbbf100a9762a1b1c31bcd0f4d32a61b6af0c651 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Wed, 10 Dec 2025 12:59:36 -0500 Subject: [PATCH 02/19] Improve imaging, save pane positions (roughly). --- libnemo-private/nemo-global-preferences.c | 3 + libnemo-private/nemo-global-preferences.h | 1 + libnemo-private/nemo-preview-details.h | 2 +- libnemo-private/nemo-preview-image.c | 336 ++++++++++++++++++---- libnemo-private/nemo-preview-image.h | 2 +- libnemo-private/org.nemo.gschema.xml | 14 + src/nemo-preview-pane.c | 67 ++++- src/nemo-window.c | 47 ++- 8 files changed, 409 insertions(+), 63 deletions(-) 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.h b/libnemo-private/nemo-preview-details.h index 0753250b0..ccd2735b1 100644 --- a/libnemo-private/nemo-preview-details.h +++ b/libnemo-private/nemo-preview-details.h @@ -29,7 +29,7 @@ #define NEMO_TYPE_PREVIEW_DETAILS (nemo_preview_details_get_type()) -G_DECLARE_FINAL_TYPE (NemoPreviewDetails, nemo_preview_details, NEMO, PREVIEW_DETAILS, GtkBox); +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, diff --git a/libnemo-private/nemo-preview-image.c b/libnemo-private/nemo-preview-image.c index c9a4c701b..1ac539f42 100644 --- a/libnemo-private/nemo-preview-image.c +++ b/libnemo-private/nemo-preview-image.c @@ -25,8 +25,10 @@ #include "nemo-file-attributes.h" #include -#define MAX_PREVIEW_SIZE 400 +#define RESIZE_DEBOUNCE_MS 150 +#define MIN_SIZE_CHANGE 10 +/* Required for G_DECLARE_FINAL_TYPE */ struct _NemoPreviewImage { GtkBox parent; }; @@ -35,10 +37,21 @@ typedef struct { GtkWidget *image; 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; } 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 void nemo_preview_image_finalize (GObject *object) { @@ -48,6 +61,16 @@ nemo_preview_image_finalize (GObject *object) 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->file != NULL) { nemo_file_unref (priv->file); priv->file = NULL; @@ -63,6 +86,12 @@ nemo_preview_image_init (NemoPreviewImage *preview) 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; + /* Create image widget */ priv->image = gtk_image_new (); gtk_widget_set_halign (priv->image, GTK_ALIGN_CENTER); @@ -76,6 +105,10 @@ nemo_preview_image_init (NemoPreviewImage *preview) 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 @@ -96,34 +129,204 @@ nemo_preview_image_new (void) NULL); } -static GdkPixbuf * -scale_pixbuf_to_fit (GdkPixbuf *pixbuf, gint max_width, gint max_height) +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) { + gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); + gtk_widget_show (priv->image); + cairo_surface_destroy (surface); + } + + 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->image); + } + + 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 new_width, new_height; - gdouble scale; + 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 (pixbuf == NULL) { - return NULL; + if (target_width < 1 || target_height < 1) { + return; } - orig_width = gdk_pixbuf_get_width (pixbuf); - orig_height = gdk_pixbuf_get_height (pixbuf); + /* 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) { + gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); + cairo_surface_destroy (surface); + } - /* If image is smaller than max size, don't scale up */ - if (orig_width <= max_width && orig_height <= max_height) { - return g_object_ref (pixbuf); + g_object_unref (scaled_pixbuf); } +} - /* Calculate scale factor to fit within max dimensions */ - scale = MIN ((gdouble) max_width / orig_width, - (gdouble) max_height / orig_height); +static gboolean +on_resize_timeout (gpointer user_data) +{ + NemoPreviewImage *widget = NEMO_PREVIEW_IMAGE (user_data); + NemoPreviewImagePrivate *priv; + GtkAllocation allocation; - new_width = (gint) (orig_width * scale); - new_height = (gint) (orig_height * scale); + 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 gdk_pixbuf_scale_simple (pixbuf, new_width, new_height, - GDK_INTERP_BILINEAR); + 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 @@ -131,10 +334,7 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, NemoFile *file) { NemoPreviewImagePrivate *priv; - GdkPixbuf *pixbuf = NULL; - GdkPixbuf *scaled_pixbuf = NULL; - cairo_surface_t *surface = NULL; - gint ui_scale; + GtkAllocation allocation; g_return_if_fail (NEMO_IS_PREVIEW_IMAGE (widget)); @@ -144,6 +344,12 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, 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); } @@ -154,48 +360,59 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, gtk_image_clear (GTK_IMAGE (priv->image)); gtk_widget_hide (priv->message_label); gtk_widget_hide (priv->image); + 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); - ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); - - /* Try to get thumbnail/icon */ - pixbuf = nemo_file_get_icon_pixbuf (file, - MAX_PREVIEW_SIZE, - TRUE, - ui_scale, - NEMO_FILE_ICON_FLAGS_USE_THUMBNAILS); - - if (pixbuf != NULL) { - /* Scale pixbuf to fit within max dimensions */ - scaled_pixbuf = scale_pixbuf_to_fit (pixbuf, MAX_PREVIEW_SIZE, MAX_PREVIEW_SIZE); - - if (scaled_pixbuf != NULL) { - surface = gdk_cairo_surface_create_from_pixbuf (scaled_pixbuf, ui_scale, NULL); - + 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) { gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); - gtk_widget_show (priv->image); cairo_surface_destroy (surface); } - - g_object_unref (scaled_pixbuf); + g_object_unref (icon_pixbuf); } - g_object_unref (pixbuf); - - /* Check if this is a real image file or just an icon */ - /* If it's just an icon (not a thumbnail), show a message */ - if (nemo_file_is_directory (file)) { - gtk_label_set_text (GTK_LABEL (priv->message_label), - _("(Folder)")); - gtk_widget_show (priv->message_label); - } + gtk_label_set_text (GTK_LABEL (priv->message_label), _("(Folder)")); + gtk_widget_show (priv->image); + gtk_widget_show (priv->message_label); } else { - /* No preview available */ + /* 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) { + gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); + cairo_surface_destroy (surface); + } + g_object_unref (icon_pixbuf); + } + gtk_label_set_text (GTK_LABEL (priv->message_label), - _("(Image preview not available)")); + _("(Not an image file)")); + gtk_widget_show (priv->image); gtk_widget_show (priv->message_label); } } @@ -210,6 +427,17 @@ nemo_preview_image_clear (NemoPreviewImage *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->file != NULL) { nemo_file_unref (priv->file); priv->file = NULL; @@ -218,4 +446,6 @@ nemo_preview_image_clear (NemoPreviewImage *widget) gtk_image_clear (GTK_IMAGE (priv->image)); gtk_widget_hide (priv->image); 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 index e5dadad7a..d346c02f2 100644 --- a/libnemo-private/nemo-preview-image.h +++ b/libnemo-private/nemo-preview-image.h @@ -29,7 +29,7 @@ #define NEMO_TYPE_PREVIEW_IMAGE (nemo_preview_image_get_type()) -G_DECLARE_FINAL_TYPE (NemoPreviewImage, nemo_preview_image, NEMO, PREVIEW_IMAGE, GtkBox); +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, 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/nemo-preview-pane.c b/src/nemo-preview-pane.c index 9ffde1426..abfddbb6c 100644 --- a/src/nemo-preview-pane.c +++ b/src/nemo-preview-pane.c @@ -24,6 +24,7 @@ #include "nemo-preview-pane.h" #include #include +#include #include #define PREVIEW_IMAGE_HEIGHT 200 @@ -42,10 +43,62 @@ typedef struct { 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) { @@ -126,8 +179,18 @@ nemo_preview_pane_init (NemoPreviewPane *pane) scrolled, TRUE, FALSE); gtk_widget_show (scrolled); - /* Set initial position for the paned */ - gtk_paned_set_position (GTK_PANED (priv->vpaned), PREVIEW_IMAGE_HEIGHT); + /* 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 diff --git a/src/nemo-window.c b/src/nemo-window.c index 09406c081..14ba1b722 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -2209,6 +2209,23 @@ preview_pane_selection_changed_callback (NemoView *view, NemoWindow *window) 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) { @@ -2303,12 +2320,28 @@ nemo_window_preview_pane_on (NemoWindow *window) window->details->preview_pane, TRUE, FALSE); - /* Set initial position (60% for file view, 40% for preview) */ - gtk_paned_set_position (GTK_PANED (window->details->split_view_hpane), - gtk_widget_get_allocated_width (window->details->split_view_hpane) * 0.6); - 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) { @@ -2338,8 +2371,10 @@ nemo_window_preview_pane_off (NemoWindow *window) return; } - /* Disconnect signals */ - /* Note: g_signal_connect_object handles disconnection automatically */ + /* 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); From 4094099a79370083fcdd718a1179b08e64b4fee5 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Wed, 10 Dec 2025 13:43:06 -0500 Subject: [PATCH 03/19] Setup view connection better, have it change when the view does. --- src/nemo-preview-pane.c | 6 +++--- src/nemo-window.c | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/nemo-preview-pane.c b/src/nemo-preview-pane.c index abfddbb6c..d317eeeca 100644 --- a/src/nemo-preview-pane.c +++ b/src/nemo-preview-pane.c @@ -247,9 +247,9 @@ nemo_preview_pane_set_file (NemoPreviewPane *pane, /* Monitor file for changes */ priv->file_changed_id = - g_signal_connect_object (file, "changed", - G_CALLBACK (file_changed_callback), - pane, 0); + 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); diff --git a/src/nemo-window.c b/src/nemo-window.c index 14ba1b722..656e28c3b 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -1546,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) @@ -1584,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); @@ -1608,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); + } } /** @@ -2352,7 +2367,8 @@ nemo_window_preview_pane_on (NemoWindow *window) nemo_preview_pane_set_file (NEMO_PREVIEW_PANE (window->details->preview_pane), file); nemo_file_list_free (selection); - /* Connect selection-changed signal */ + /* 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); From 49211c623ef206567a4690779516340e4f1c2383 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Wed, 10 Dec 2025 13:57:07 -0500 Subject: [PATCH 04/19] Remove detail pane shadow --- src/nemo-preview-pane.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nemo-preview-pane.c b/src/nemo-preview-pane.c index d317eeeca..887934e51 100644 --- a/src/nemo-preview-pane.c +++ b/src/nemo-preview-pane.c @@ -168,8 +168,6 @@ nemo_preview_pane_init (NemoPreviewPane *pane) gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), - GTK_SHADOW_IN); priv->details_widget = nemo_preview_details_new (); gtk_container_add (GTK_CONTAINER (scrolled), priv->details_widget); From b1f274af58b9257ff400912142e6e9c47cb14a96 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Wed, 10 Dec 2025 14:35:00 -0500 Subject: [PATCH 05/19] Fix resizing image. --- libnemo-private/nemo-preview-image.c | 117 +++++++++++++++++++++------ 1 file changed, 94 insertions(+), 23 deletions(-) diff --git a/libnemo-private/nemo-preview-image.c b/libnemo-private/nemo-preview-image.c index 1ac539f42..5b28a81df 100644 --- a/libnemo-private/nemo-preview-image.c +++ b/libnemo-private/nemo-preview-image.c @@ -34,7 +34,7 @@ struct _NemoPreviewImage { }; typedef struct { - GtkWidget *image; + GtkWidget *drawing_area; GtkWidget *message_label; NemoFile *file; @@ -45,12 +45,16 @@ typedef struct { /* 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) @@ -71,6 +75,11 @@ nemo_preview_image_finalize (GObject *object) 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; @@ -91,12 +100,17 @@ nemo_preview_image_init (NemoPreviewImage *preview) priv->current_width = 0; priv->current_height = 0; priv->current_pixbuf = NULL; - - /* Create image widget */ - priv->image = gtk_image_new (); - gtk_widget_set_halign (priv->image, GTK_ALIGN_CENTER); - gtk_widget_set_valign (priv->image, GTK_ALIGN_CENTER); - gtk_box_pack_start (GTK_BOX (preview), priv->image, TRUE, TRUE, 0); + 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 (""); @@ -129,6 +143,42 @@ nemo_preview_image_new (void) 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) { @@ -196,9 +246,13 @@ load_image_at_size (NemoPreviewImage *widget, surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, ui_scale, NULL); if (surface != NULL) { - gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); - gtk_widget_show (priv->image); - cairo_surface_destroy (surface); + /* 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); @@ -212,7 +266,7 @@ load_image_at_size (NemoPreviewImage *widget, gtk_label_set_text (GTK_LABEL (priv->message_label), _("(Failed to load image)")); gtk_widget_show (priv->message_label); - gtk_widget_hide (priv->image); + gtk_widget_hide (priv->drawing_area); } g_free (path); @@ -265,8 +319,12 @@ scale_current_pixbuf_to_size (NemoPreviewImage *widget, surface = gdk_cairo_surface_create_from_pixbuf (scaled_pixbuf, ui_scale, NULL); if (surface != NULL) { - gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); - cairo_surface_destroy (surface); + /* 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); @@ -357,9 +415,12 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, priv->file = file; /* Clear current image */ - gtk_image_clear (GTK_IMAGE (priv->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->image); + gtk_widget_hide (priv->drawing_area); priv->current_width = 0; priv->current_height = 0; @@ -385,14 +446,17 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, if (icon_pixbuf != NULL) { surface = gdk_cairo_surface_create_from_pixbuf (icon_pixbuf, ui_scale, NULL); if (surface != NULL) { - gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); - cairo_surface_destroy (surface); + 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->image); + 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 */ @@ -404,15 +468,18 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, if (icon_pixbuf != NULL) { surface = gdk_cairo_surface_create_from_pixbuf (icon_pixbuf, ui_scale, NULL); if (surface != NULL) { - gtk_image_set_from_surface (GTK_IMAGE (priv->image), surface); - cairo_surface_destroy (surface); + 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->image); + gtk_widget_show (priv->drawing_area); + gtk_widget_queue_draw (priv->drawing_area); gtk_widget_show (priv->message_label); } } @@ -438,13 +505,17 @@ nemo_preview_image_clear (NemoPreviewImage *widget) 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_image_clear (GTK_IMAGE (priv->image)); - gtk_widget_hide (priv->image); + gtk_widget_hide (priv->drawing_area); gtk_widget_hide (priv->message_label); priv->current_width = 0; priv->current_height = 0; From b91a919c80c30b88e09838d75b82663ebcb2ad30 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Wed, 10 Dec 2025 18:19:30 -0500 Subject: [PATCH 06/19] remove max-width for detail info --- libnemo-private/nemo-preview-details.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libnemo-private/nemo-preview-details.c b/libnemo-private/nemo-preview-details.c index 04efb4dbb..6cf26f12d 100644 --- a/libnemo-private/nemo-preview-details.c +++ b/libnemo-private/nemo-preview-details.c @@ -79,7 +79,6 @@ create_label_pair (GtkGrid *grid, const gchar *label_text, gint row) 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_label_set_max_width_chars (GTK_LABEL (value), 30); gtk_grid_attach (grid, value, 1, row, 1, 1); gtk_widget_show (value); From 2ca021894495a8645630fff68f2055df53a431f4 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 11 Dec 2025 11:34:37 -0500 Subject: [PATCH 07/19] metadata --- libnemo-private/nemo-metadata.c | 1 + libnemo-private/nemo-metadata.h | 1 + libnemo-private/nemo-preview-image.c | 192 ++++++++++++++++++++------- src/nemo-window.c | 45 +++++++ 4 files changed, 193 insertions(+), 46 deletions(-) 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..c95af9336 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-image.c b/libnemo-private/nemo-preview-image.c index 5b28a81df..2941a3ffa 100644 --- a/libnemo-private/nemo-preview-image.c +++ b/libnemo-private/nemo-preview-image.c @@ -23,6 +23,7 @@ #include "nemo-preview-image.h" #include "nemo-file-attributes.h" +#include "nemo-icon-info.h" #include #define RESIZE_DEBOUNCE_MS 150 @@ -34,6 +35,7 @@ struct _NemoPreviewImage { }; typedef struct { + GtkWidget *frame; GtkWidget *drawing_area; GtkWidget *message_label; NemoFile *file; @@ -48,6 +50,9 @@ typedef struct { /* Current surface to draw */ cairo_surface_t *current_surface; + + /* Track if showing an icon vs image */ + gboolean showing_icon; } NemoPreviewImagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (NemoPreviewImage, nemo_preview_image, GTK_TYPE_BOX) @@ -101,6 +106,17 @@ nemo_preview_image_init (NemoPreviewImage *preview) priv->current_height = 0; priv->current_pixbuf = NULL; priv->current_surface = NULL; + priv->showing_icon = FALSE; + + /* Create frame to hold drawing area */ + priv->frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (priv->frame), GTK_SHADOW_IN); + 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); /* Create drawing area widget */ priv->drawing_area = gtk_drawing_area_new (); @@ -110,7 +126,7 @@ nemo_preview_image_init (NemoPreviewImage *preview) 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); + gtk_container_add (GTK_CONTAINER (priv->frame), priv->drawing_area); /* Create message label (hidden by default) */ priv->message_label = gtk_label_new (""); @@ -120,6 +136,8 @@ nemo_preview_image_init (NemoPreviewImage *preview) "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)); /* Connect size-allocate signal for resize handling */ g_signal_connect (preview, "size-allocate", G_CALLBACK (on_size_allocate), NULL); @@ -173,9 +191,30 @@ on_drawing_area_draw (GtkWidget *widget, x_offset = (widget_width - surface_width) / 2.0; y_offset = (widget_height - surface_height) / 2.0; + /* Draw the image first */ cairo_set_source_surface (cr, priv->current_surface, x_offset, y_offset); cairo_paint (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; /* Inset border slightly inside image bounds */ + + /* Get the border color from the frame's style context */ + style_context = gtk_widget_get_style_context (priv->frame); + gtk_style_context_get_border_color (style_context, GTK_STATE_FLAG_NORMAL, &border_color); + + /* Draw border rectangle inset from the image edge */ + 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, + surface_width - (inset * 2), surface_height - (inset * 2)); + cairo_stroke (cr); + } + return TRUE; } @@ -196,6 +235,100 @@ is_image_file (NemoFile *file) return is_image; } +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)); + + /* Calculate icon size - use the smaller dimension to fit in the space */ + icon_size = MIN (width, height) * ui_scale; + + /* Get the icon info from the file */ + 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 (); + + /* Load the icon at the exact size we need */ + 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) { + /* Save pixbuf for quick scaling during resize */ + if (priv->current_pixbuf != NULL) { + g_object_unref (priv->current_pixbuf); + } + priv->current_pixbuf = g_object_ref (icon_pixbuf); + + surface = gdk_cairo_surface_create_from_pixbuf (icon_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 (icon_pixbuf); + gtk_widget_hide (priv->message_label); + } else { + /* Failed to load icon */ + 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 { + /* No icon info available */ + 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, @@ -342,7 +475,12 @@ on_resize_timeout (gpointer user_data) priv->resize_timeout_id = 0; gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); - load_image_at_size (widget, allocation.width, allocation.height); + + if (priv->showing_icon) { + load_icon_at_size (widget, allocation.width, allocation.height); + } else { + load_image_at_size (widget, allocation.width, allocation.height); + } return G_SOURCE_REMOVE; } @@ -434,53 +572,14 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, if (is_image_file (file)) { /* Load the image at current widget size */ + priv->showing_icon = FALSE; 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); + /* Load folder or file icon at current widget size */ + priv->showing_icon = TRUE; + gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); + load_icon_at_size (widget, allocation.width, allocation.height); } } } @@ -519,4 +618,5 @@ nemo_preview_image_clear (NemoPreviewImage *widget) gtk_widget_hide (priv->message_label); priv->current_width = 0; priv->current_height = 0; + priv->showing_icon = FALSE; } diff --git a/src/nemo-window.c b/src/nemo-window.c index 656e28c3b..022c6a111 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -1573,6 +1573,8 @@ nemo_window_connect_content_view (NemoWindow *window, NemoView *view) { NemoWindowSlot *slot; + NemoFile *directory_file; + gboolean show_preview; g_assert (NEMO_IS_WINDOW (window)); g_assert (NEMO_IS_VIEW (view)); @@ -1587,6 +1589,21 @@ nemo_window_connect_content_view (NemoWindow *window, G_CALLBACK (zoom_level_changed_callback), window); + /* Check if this directory has a saved preview pane state */ + directory_file = nemo_view_get_directory_as_file (view); + if (directory_file != NULL) { + show_preview = nemo_file_get_boolean_metadata (directory_file, + NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE, + FALSE); + + /* Apply the saved state */ + if (show_preview && !window->details->show_preview_pane) { + nemo_window_preview_pane_on (window); + } else if (!show_preview && window->details->show_preview_pane) { + nemo_window_preview_pane_off (window); + } + } + /* Connect preview pane selection updates if preview is showing */ if (window->details->preview_pane) { g_signal_connect_object (view, "selection-changed", @@ -2376,12 +2393,26 @@ nemo_window_preview_pane_on (NemoWindow *window) window->details->show_preview_pane = TRUE; nemo_window_update_show_hide_ui_elements (window); + + /* Save preview pane state to directory metadata */ + if (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); + } + } } void nemo_window_preview_pane_off (NemoWindow *window) { GtkPaned *paned; + NemoWindowSlot *slot; if (window->details->preview_pane == NULL) { return; @@ -2407,6 +2438,20 @@ nemo_window_preview_pane_off (NemoWindow *window) window->details->show_preview_pane = FALSE; nemo_window_update_show_hide_ui_elements (window); + + /* Save preview pane state to directory metadata */ + slot = nemo_window_get_active_slot (window); + if (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); + } + } } gboolean From 5d1ec90e9a1a344bab2753da63b11007f71d1ebf Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 11 Dec 2025 12:46:57 -0500 Subject: [PATCH 08/19] fixes --- libnemo-private/nemo-preview-details.c | 38 +++++++++++---- libnemo-private/nemo-preview-image.c | 2 +- src/nemo-window-private.h | 1 + src/nemo-window.c | 66 +++++++++++++++++++++----- 4 files changed, 83 insertions(+), 24 deletions(-) diff --git a/libnemo-private/nemo-preview-details.c b/libnemo-private/nemo-preview-details.c index 6cf26f12d..6f1bd8e90 100644 --- a/libnemo-private/nemo-preview-details.c +++ b/libnemo-private/nemo-preview-details.c @@ -201,18 +201,36 @@ nemo_preview_details_set_file (NemoPreviewDetails *widget, 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); + /* 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 { - gtk_label_set_text (GTK_LABEL (priv->location_value_label), "—"); + /* 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); } - g_object_unref (location); } } diff --git a/libnemo-private/nemo-preview-image.c b/libnemo-private/nemo-preview-image.c index 2941a3ffa..c7f24be8b 100644 --- a/libnemo-private/nemo-preview-image.c +++ b/libnemo-private/nemo-preview-image.c @@ -110,7 +110,7 @@ nemo_preview_image_init (NemoPreviewImage *preview) /* Create frame to hold drawing area */ priv->frame = gtk_frame_new (NULL); - gtk_frame_set_shadow_type (GTK_FRAME (priv->frame), GTK_SHADOW_IN); + 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); diff --git a/src/nemo-window-private.h b/src/nemo-window-private.h index 8d3c1c58d..faadf3c34 100644 --- a/src/nemo-window-private.h +++ b/src/nemo-window-private.h @@ -102,6 +102,7 @@ struct NemoWindowDetails /* 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 gboolean disable_chrome; diff --git a/src/nemo-window.c b/src/nemo-window.c index 022c6a111..4680e7e8d 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -2241,11 +2241,49 @@ preview_pane_selection_changed_callback (NemoView *view, NemoWindow *window) 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->split_view_hpane); + + 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->split_view_hpane), position); + } else { + /* Fallback: 60/40 split */ + gtk_paned_set_position (GTK_PANED (window->details->split_view_hpane), 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)); @@ -2344,6 +2382,9 @@ nemo_window_preview_pane_on (NemoWindow *window) nemo_window_split_view_off (window); } + /* 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); @@ -2354,19 +2395,15 @@ nemo_window_preview_pane_on (NemoWindow *window) 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); + /* Set position from settings - check if paned is already realized */ + if (gtk_widget_get_realized (window->details->split_view_hpane)) { + /* Already realized, set position immediately */ + set_preview_pane_width_from_settings (window); } 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); + /* Not realized yet, wait for realize signal */ + g_signal_connect (window->details->split_view_hpane, "realize", + G_CALLBACK (preview_pane_realize_callback), + window); } /* Connect signal to save position on resize */ @@ -2418,7 +2455,10 @@ nemo_window_preview_pane_off (NemoWindow *window) return; } - /* Disconnect position signal */ + /* Disconnect signals */ + g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, + G_CALLBACK (preview_pane_realize_callback), + window); g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, G_CALLBACK (preview_pane_position_changed_callback), window); From c040bea22e0cf190fc6f5cff14b1007742d9921e Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 11 Dec 2025 12:55:03 -0500 Subject: [PATCH 09/19] fix location changes with the same view --- src/nemo-window.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/nemo-window.c b/src/nemo-window.c index 4680e7e8d..6d8a29f06 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -1054,6 +1054,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. */ @@ -1083,6 +1087,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"); @@ -1107,6 +1115,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); @@ -1546,9 +1559,6 @@ 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) @@ -2220,6 +2230,32 @@ 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; + + /* Update preview pane 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) { From 3ee18eb7eb4b038849182718994039a5dddec74a Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 11 Dec 2025 22:12:59 -0500 Subject: [PATCH 10/19] fix some stuff - broke more.. --- src/nemo-window-menus.c | 46 +++++++--- src/nemo-window.c | 184 +++++++++++++++++----------------------- 2 files changed, 114 insertions(+), 116 deletions(-) diff --git a/src/nemo-window-menus.c b/src/nemo-window-menus.c index b6210b664..569d90385 100644 --- a/src/nemo-window-menus.c +++ b/src/nemo-window-menus.c @@ -648,6 +648,9 @@ action_split_view_callback (GtkAction *action, NemoWindowSlot *slot; if (is_active) { + if (nemo_window_preview_pane_showing (window)) { + nemo_window_preview_pane_off (window); + } nemo_window_split_view_on (window); } else { nemo_window_split_view_off (window); @@ -658,6 +661,7 @@ action_split_view_callback (GtkAction *action, nemo_view_update_menus (slot->content_view); } } + nemo_window_update_show_hide_ui_elements (window); } static void @@ -678,6 +682,9 @@ action_preview_pane_callback (GtkAction *action, NemoWindowSlot *slot; if (is_active) { + if (nemo_window_split_view_showing (window)) { + nemo_window_split_view_off (window); + } nemo_window_preview_pane_on (window); } else { nemo_window_preview_pane_off (window); @@ -688,7 +695,6 @@ action_preview_pane_callback (GtkAction *action, nemo_view_update_menus (slot->content_view); } } - nemo_window_update_show_hide_ui_elements (window); } @@ -814,18 +820,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); @@ -834,10 +855,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); + } } } @@ -1966,7 +1990,7 @@ nemo_window_initialize_menus (NemoWindow *window) gtk_action_set_visible (action_to_hide, eel_vfs_supports_uri_scheme ("trash")); action_to_hide = gtk_action_group_get_action (action_group, "Go to Network"); gtk_action_set_visible (action_to_hide, eel_vfs_supports_uri_scheme ("network")); - +g_printerr ("FUCKKKKKK\n"); gtk_action_group_add_toggle_actions (action_group, main_toggle_entries, G_N_ELEMENTS (main_toggle_entries), window); diff --git a/src/nemo-window.c b/src/nemo-window.c index 6d8a29f06..e71ee7c04 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -2338,13 +2338,9 @@ 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); + gtk_widget_show_all (GTK_WIDGET (slot)); location = window->details->secondary_pane_last_location; @@ -2386,8 +2382,6 @@ 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), "position", 0, "position-set", FALSE, @@ -2396,8 +2390,6 @@ nemo_window_split_view_off (NemoWindow *window) nemo_window_set_active_pane (window, active_pane); nemo_navigation_state_set_master (window->details->nav_state, active_pane->action_group); - - nemo_window_update_show_hide_ui_elements (window); } gboolean @@ -2409,125 +2401,107 @@ nemo_window_split_view_showing (NemoWindow *window) void nemo_window_preview_pane_on (NemoWindow *window) { - NemoWindowSlot *slot; - GList *selection; - NemoFile *file = NULL; + 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); - } + window->details->preview_pane_width_set = FALSE; + window->details->preview_pane = nemo_preview_pane_new (window); - /* Reset flag so position can be set */ - window->details->preview_pane_width_set = FALSE; + gtk_paned_pack2 (GTK_PANED (window->details->split_view_hpane), + window->details->preview_pane, + TRUE, FALSE); - /* Create preview pane */ - window->details->preview_pane = nemo_preview_pane_new (window); + gtk_widget_show (window->details->preview_pane); - /* Pack into split view paned */ - gtk_paned_pack2 (GTK_PANED (window->details->split_view_hpane), - window->details->preview_pane, - TRUE, FALSE); + if (gtk_widget_get_realized (window->details->split_view_hpane)) { + set_preview_pane_width_from_settings (window); + } else { + g_signal_connect (window->details->split_view_hpane, "realize", + G_CALLBACK (preview_pane_realize_callback), + window); + } - gtk_widget_show (window->details->preview_pane); + g_signal_connect (window->details->split_view_hpane, "notify::position", + G_CALLBACK (preview_pane_position_changed_callback), + window); - /* Set position from settings - check if paned is already realized */ - if (gtk_widget_get_realized (window->details->split_view_hpane)) { - /* 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->split_view_hpane, "realize", - G_CALLBACK (preview_pane_realize_callback), - window); - } - - /* 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); + 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); - /* 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); + g_signal_connect_object (slot->content_view, "selection-changed", + G_CALLBACK (preview_pane_selection_changed_callback), + window, 0); + } - /* 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; - window->details->show_preview_pane = TRUE; - nemo_window_update_show_hide_ui_elements (window); + if (slot != NULL && slot->content_view != NULL) { + NemoFile *directory_file; - /* Save preview pane state to directory metadata */ - if (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); + } + } - 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); } void nemo_window_preview_pane_off (NemoWindow *window) { - GtkPaned *paned; - NemoWindowSlot *slot; - - if (window->details->preview_pane == NULL) { - return; - } + GtkPaned *paned; + NemoWindowSlot *slot; - /* Disconnect signals */ - g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, - G_CALLBACK (preview_pane_realize_callback), - window); - g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, - G_CALLBACK (preview_pane_position_changed_callback), - window); + if (window->details->preview_pane == NULL) { + return; + } - paned = GTK_PANED (window->details->split_view_hpane); + /* Disconnect signals */ + g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, + G_CALLBACK (preview_pane_realize_callback), + window); + g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, + G_CALLBACK (preview_pane_position_changed_callback), + window); - /* Remove from paned */ - gtk_container_remove (GTK_CONTAINER (paned), window->details->preview_pane); + paned = GTK_PANED (window->details->split_view_hpane); - /* Reset paned position */ - g_object_set (G_OBJECT (paned), - "position", 0, - "position-set", FALSE, - NULL); + /* Remove from paned */ + gtk_container_remove (GTK_CONTAINER (paned), window->details->preview_pane); - window->details->preview_pane = NULL; - window->details->show_preview_pane = FALSE; + /* Reset paned position */ + g_object_set (G_OBJECT (paned), + "position", 200, + "position-set", FALSE, + NULL); - nemo_window_update_show_hide_ui_elements (window); + window->details->preview_pane = NULL; + window->details->show_preview_pane = FALSE; - /* Save preview pane state to directory metadata */ - slot = nemo_window_get_active_slot (window); - if (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); - } - } + /* Save preview pane state to directory metadata */ + slot = nemo_window_get_active_slot (window); + if (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); + } + } } gboolean From 970591c44e1d7003543620202a7069480ec90e1d Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Fri, 12 Dec 2025 16:48:52 -0500 Subject: [PATCH 11/19] Fixed multipane again.. --- src/nemo-window-menus.c | 8 -- src/nemo-window-private.h | 6 +- src/nemo-window.c | 267 ++++++++++++++++++++++++-------------- 3 files changed, 171 insertions(+), 110 deletions(-) diff --git a/src/nemo-window-menus.c b/src/nemo-window-menus.c index 569d90385..8a99853c7 100644 --- a/src/nemo-window-menus.c +++ b/src/nemo-window-menus.c @@ -648,9 +648,6 @@ action_split_view_callback (GtkAction *action, NemoWindowSlot *slot; if (is_active) { - if (nemo_window_preview_pane_showing (window)) { - nemo_window_preview_pane_off (window); - } nemo_window_split_view_on (window); } else { nemo_window_split_view_off (window); @@ -661,7 +658,6 @@ action_split_view_callback (GtkAction *action, nemo_view_update_menus (slot->content_view); } } - nemo_window_update_show_hide_ui_elements (window); } static void @@ -682,9 +678,6 @@ action_preview_pane_callback (GtkAction *action, NemoWindowSlot *slot; if (is_active) { - if (nemo_window_split_view_showing (window)) { - nemo_window_split_view_off (window); - } nemo_window_preview_pane_on (window); } else { nemo_window_preview_pane_off (window); @@ -695,7 +688,6 @@ action_preview_pane_callback (GtkAction *action, nemo_view_update_menus (slot->content_view); } } - nemo_window_update_show_hide_ui_elements (window); } static void diff --git a/src/nemo-window-private.h b/src/nemo-window-private.h index faadf3c34..1f0b7e513 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,9 +95,6 @@ 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; diff --git a/src/nemo-window.c b/src/nemo-window.c index e71ee7c04..63488458b 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -415,7 +415,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); } @@ -430,7 +430,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); @@ -603,13 +603,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; @@ -686,28 +713,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; @@ -1890,7 +1922,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", @@ -2238,8 +2270,27 @@ slot_location_changed_callback (NemoWindowSlot *slot, { GList *selection; NemoFile *file = NULL; + NemoFile *directory_file; + gboolean show_preview; - /* Update preview pane when location changes */ + /* Check if this directory has a saved preview pane state */ + 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); + + /* Apply the saved state */ + if (show_preview && !window->details->show_preview_pane) { + nemo_window_preview_pane_on (window); + } else if (!show_preview && window->details->show_preview_pane) { + nemo_window_preview_pane_off (window); + } + } + } + + /* 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); @@ -2291,15 +2342,15 @@ set_preview_pane_width_from_settings (NemoWindow *window) /* 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->split_view_hpane); + 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->split_view_hpane), position); + gtk_paned_set_position (GTK_PANED (window->details->secondary_paned), position); } else { /* Fallback: 60/40 split */ - gtk_paned_set_position (GTK_PANED (window->details->split_view_hpane), total_width * 0.6); + gtk_paned_set_position (GTK_PANED (window->details->secondary_paned), total_width * 0.6); } } @@ -2361,6 +2412,7 @@ nemo_window_split_view_on (NemoWindow *window) g_object_unref (location); window_set_search_action_text (window, FALSE); + nemo_window_update_show_hide_ui_elements (window); } void @@ -2382,7 +2434,7 @@ nemo_window_split_view_off (NemoWindow *window) } } - 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); @@ -2390,6 +2442,8 @@ nemo_window_split_view_off (NemoWindow *window) nemo_window_set_active_pane (window, active_pane); nemo_navigation_state_set_master (window->details->nav_state, active_pane->action_group); + + nemo_window_update_show_hide_ui_elements (window); } gboolean @@ -2401,107 +2455,122 @@ nemo_window_split_view_showing (NemoWindow *window) void nemo_window_preview_pane_on (NemoWindow *window) { - NemoWindowSlot *slot; - GList *selection; - NemoFile *file = NULL; + NemoWindowSlot *slot; + GList *selection; + NemoFile *file = NULL; - window->details->preview_pane_width_set = FALSE; - window->details->preview_pane = nemo_preview_pane_new (window); + /* Reset flag so position can be set */ + window->details->preview_pane_width_set = FALSE; - gtk_paned_pack2 (GTK_PANED (window->details->split_view_hpane), - window->details->preview_pane, - TRUE, FALSE); + /* Create preview pane */ + window->details->preview_pane = nemo_preview_pane_new (window); - gtk_widget_show (window->details->preview_pane); + /* Pack into right side of secondary_paned */ + gtk_paned_pack2 (GTK_PANED (window->details->secondary_paned), + window->details->preview_pane, + TRUE, FALSE); - if (gtk_widget_get_realized (window->details->split_view_hpane)) { - set_preview_pane_width_from_settings (window); - } else { - g_signal_connect (window->details->split_view_hpane, "realize", - G_CALLBACK (preview_pane_realize_callback), - window); - } + gtk_widget_show (window->details->preview_pane); - g_signal_connect (window->details->split_view_hpane, "notify::position", - G_CALLBACK (preview_pane_position_changed_callback), - window); + /* 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); + } - 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 signal to save position on resize */ + g_signal_connect (window->details->secondary_paned, "notify::position", + G_CALLBACK (preview_pane_position_changed_callback), + window); - g_signal_connect_object (slot->content_view, "selection-changed", - G_CALLBACK (preview_pane_selection_changed_callback), - window, 0); - } + /* 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); - window->details->show_preview_pane = TRUE; + /* 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); + } - if (slot != NULL && slot->content_view != NULL) { - NemoFile *directory_file; + window->details->show_preview_pane = TRUE; + // nemo_window_update_show_hide_ui_elements (window); - 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); - } - } + /* Save preview pane state to directory metadata */ + if (slot != NULL && slot->content_view != NULL) { + NemoFile *directory_file; - // nemo_window_update_show_hide_ui_elements (window); + 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); } void nemo_window_preview_pane_off (NemoWindow *window) { - GtkPaned *paned; - NemoWindowSlot *slot; + GtkPaned *paned; + NemoWindowSlot *slot; - if (window->details->preview_pane == NULL) { - return; - } + if (window->details->preview_pane == NULL) { + return; + } - /* Disconnect signals */ - g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, - G_CALLBACK (preview_pane_realize_callback), - window); - g_signal_handlers_disconnect_by_func (window->details->split_view_hpane, - G_CALLBACK (preview_pane_position_changed_callback), - window); + /* 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->split_view_hpane); + paned = GTK_PANED (window->details->secondary_paned); - /* Remove from paned */ - gtk_container_remove (GTK_CONTAINER (paned), window->details->preview_pane); + /* Remove from paned */ + gtk_container_remove (GTK_CONTAINER (paned), window->details->preview_pane); - /* Reset paned position */ - g_object_set (G_OBJECT (paned), - "position", 200, - "position-set", FALSE, - NULL); + /* 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; + window->details->preview_pane = NULL; + window->details->show_preview_pane = FALSE; - /* Save preview pane state to directory metadata */ - slot = nemo_window_get_active_slot (window); - if (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); + + /* Save preview pane state to directory metadata */ + slot = nemo_window_get_active_slot (window); + if (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); } gboolean From 3ef7e026f1fb93de95794309c05292fb8fdfb39e Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Fri, 12 Dec 2025 19:13:29 -0500 Subject: [PATCH 12/19] Statusbar button --- src/nemo-statusbar.c | 81 +++++++++++++++++++++++++++++++++++++++ src/nemo-statusbar.h | 2 + src/nemo-window-private.h | 3 ++ src/nemo-window.c | 46 ++++++++++++++++++++++ 4 files changed, 132 insertions(+) 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-window-private.h b/src/nemo-window-private.h index 1f0b7e513..d203867f2 100644 --- a/src/nemo-window-private.h +++ b/src/nemo-window-private.h @@ -104,6 +104,9 @@ struct NemoWindowDetails 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 63488458b..e6b44e628 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -106,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, }; @@ -818,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; @@ -844,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; @@ -2156,6 +2178,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), @@ -2411,8 +2447,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 @@ -2443,7 +2482,10 @@ 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); + + g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_SPLIT_VIEW]); } gboolean @@ -2521,6 +2563,8 @@ nemo_window_preview_pane_on (NemoWindow *window) } } nemo_window_update_show_hide_ui_elements (window); + + g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_PREVIEW_PANE]); } void @@ -2571,6 +2615,8 @@ nemo_window_preview_pane_off (NemoWindow *window) } } nemo_window_update_show_hide_ui_elements (window); + + g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_PREVIEW_PANE]); } gboolean From d45a674cef163e0e78da054501d865d221f8ff9f Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Fri, 12 Dec 2025 19:58:25 -0500 Subject: [PATCH 13/19] fix jumpiness when switching tabs/panes. --- src/nemo-window.c | 76 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/src/nemo-window.c b/src/nemo-window.c index e6b44e628..6a09e7cb9 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -1626,6 +1626,49 @@ zoom_level_changed_callback (NemoView *view, nemo_window_sync_zoom_widgets (window); } +/* 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; +} + /* These are called * A) when switching the view within the active slot @@ -1664,7 +1707,10 @@ nemo_window_connect_content_view (NemoWindow *window, if (show_preview && !window->details->show_preview_pane) { nemo_window_preview_pane_on (window); } else if (!show_preview && window->details->show_preview_pane) { - nemo_window_preview_pane_off (window); + /* Only close preview pane if no other slot wants it */ + if (!any_other_slot_wants_preview_pane (window, slot)) { + nemo_window_preview_pane_off (window); + } } } @@ -2321,7 +2367,10 @@ slot_location_changed_callback (NemoWindowSlot *slot, if (show_preview && !window->details->show_preview_pane) { nemo_window_preview_pane_on (window); } else if (!show_preview && window->details->show_preview_pane) { - nemo_window_preview_pane_off (window); + /* Only close preview pane if no other slot wants it */ + if (!any_other_slot_wants_preview_pane (window, slot)) { + nemo_window_preview_pane_off (window); + } } } } @@ -2485,6 +2534,29 @@ nemo_window_split_view_off (NemoWindow *window) 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 */ + if (active_pane->active_slot != NULL && active_pane->active_slot->content_view != NULL) { + NemoFile *directory_file; + gboolean show_preview; + + directory_file = nemo_view_get_directory_as_file (active_pane->active_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 && !window->details->show_preview_pane) { + nemo_window_preview_pane_on (window); + } 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, active_pane->active_slot)) { + nemo_window_preview_pane_off (window); + } + } + } + } + g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_SPLIT_VIEW]); } From 579320a0ab235654f53a8b77b9a36d498a15a406 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Fri, 12 Dec 2025 20:05:34 -0500 Subject: [PATCH 14/19] Refactor --- src/nemo-window.c | 96 +++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/src/nemo-window.c b/src/nemo-window.c index 6a09e7cb9..88136ed3f 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -1669,6 +1669,41 @@ any_other_slot_wants_preview_pane (NemoWindow *window, 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. + */ +static void +sync_preview_pane_from_slot_metadata (NemoWindow *window, + NemoWindowSlot *slot) +{ + NemoFile *directory_file; + gboolean 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; + } + + show_preview = nemo_file_get_boolean_metadata (directory_file, + NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE, + FALSE); + + /* Apply the saved state */ + if (show_preview && !window->details->show_preview_pane) { + nemo_window_preview_pane_on (window); + } 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 (window); + } + } +} + /* These are called * A) when switching the view within the active slot @@ -1680,8 +1715,6 @@ nemo_window_connect_content_view (NemoWindow *window, NemoView *view) { NemoWindowSlot *slot; - NemoFile *directory_file; - gboolean show_preview; g_assert (NEMO_IS_WINDOW (window)); g_assert (NEMO_IS_VIEW (view)); @@ -1697,22 +1730,7 @@ nemo_window_connect_content_view (NemoWindow *window, window); /* Check if this directory has a saved preview pane state */ - directory_file = nemo_view_get_directory_as_file (view); - if (directory_file != NULL) { - show_preview = nemo_file_get_boolean_metadata (directory_file, - NEMO_METADATA_KEY_WINDOW_SHOW_PREVIEW_PANE, - FALSE); - - /* Apply the saved state */ - if (show_preview && !window->details->show_preview_pane) { - nemo_window_preview_pane_on (window); - } 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 (window); - } - } - } + sync_preview_pane_from_slot_metadata (window, slot); /* Connect preview pane selection updates if preview is showing */ if (window->details->preview_pane) { @@ -2352,28 +2370,9 @@ slot_location_changed_callback (NemoWindowSlot *slot, { GList *selection; NemoFile *file = NULL; - NemoFile *directory_file; - gboolean show_preview; /* Check if this directory has a saved preview pane state */ - 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); - - /* Apply the saved state */ - if (show_preview && !window->details->show_preview_pane) { - nemo_window_preview_pane_on (window); - } 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 (window); - } - } - } - } + sync_preview_pane_from_slot_metadata (window, slot); /* Update preview pane content when location changes */ if (window->details->preview_pane != NULL) { @@ -2536,26 +2535,7 @@ nemo_window_split_view_off (NemoWindow *window) /* After closing split view, check if the remaining active slot * wants the preview pane based on its directory metadata */ - if (active_pane->active_slot != NULL && active_pane->active_slot->content_view != NULL) { - NemoFile *directory_file; - gboolean show_preview; - - directory_file = nemo_view_get_directory_as_file (active_pane->active_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 && !window->details->show_preview_pane) { - nemo_window_preview_pane_on (window); - } 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, active_pane->active_slot)) { - nemo_window_preview_pane_off (window); - } - } - } - } + sync_preview_pane_from_slot_metadata (window, active_pane->active_slot); g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_SHOW_SPLIT_VIEW]); } From 17d86df7b20c03c133dd291203b79560f07c9c5d Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Fri, 12 Dec 2025 20:35:33 -0500 Subject: [PATCH 15/19] Add preference for default metadata --- .../nemo-file-management-properties.glade | 16 ++++++ libnemo-private/nemo-global-preferences.h | 1 + libnemo-private/nemo-metadata.h | 2 +- libnemo-private/org.nemo.gschema.xml | 5 ++ src/nemo-file-management-properties.c | 5 ++ src/nemo-view.c | 1 + src/nemo-window.c | 50 +++++++++++++++---- 7 files changed, 68 insertions(+), 12 deletions(-) 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 + + + Show preview pane + True + True + True + True + 0 + True + + + False + False + 6 + + diff --git a/libnemo-private/nemo-global-preferences.h b/libnemo-private/nemo-global-preferences.h index c10bf4b3d..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" diff --git a/libnemo-private/nemo-metadata.h b/libnemo-private/nemo-metadata.h index c95af9336..fcbc4ee54 100644 --- a/libnemo-private/nemo-metadata.h +++ b/libnemo-private/nemo-metadata.h @@ -60,7 +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_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/org.nemo.gschema.xml b/libnemo-private/org.nemo.gschema.xml index 77b116aa1..39265db27 100644 --- a/libnemo-private/org.nemo.gschema.xml +++ b/libnemo-private/org.nemo.gschema.xml @@ -274,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. + 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-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.c b/src/nemo-window.c index 88136ed3f..4f29ee590 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -1626,6 +1626,10 @@ 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. */ @@ -1672,6 +1676,7 @@ any_other_slot_wants_preview_pane (NemoWindow *window, /* 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, @@ -1679,6 +1684,7 @@ sync_preview_pane_from_slot_metadata (NemoWindow *window, { NemoFile *directory_file; gboolean show_preview; + gboolean default_show_preview; if (slot == NULL || slot->content_view == NULL) { return; @@ -1689,17 +1695,23 @@ sync_preview_pane_from_slot_metadata (NemoWindow *window, 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, - FALSE); + default_show_preview); - /* Apply the saved state */ + /* 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 (window); + 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 (window); + nemo_window_preview_pane_off_internal (window, FALSE); } } } @@ -2546,8 +2558,9 @@ 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) +static void +nemo_window_preview_pane_on_internal (NemoWindow *window, + gboolean write_metadata) { NemoWindowSlot *slot; GList *selection; @@ -2602,8 +2615,8 @@ nemo_window_preview_pane_on (NemoWindow *window) window->details->show_preview_pane = TRUE; // nemo_window_update_show_hide_ui_elements (window); - /* Save preview pane state to directory metadata */ - if (slot != NULL && slot->content_view != NULL) { + /* 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); @@ -2620,7 +2633,15 @@ nemo_window_preview_pane_on (NemoWindow *window) } void -nemo_window_preview_pane_off (NemoWindow *window) +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; @@ -2653,9 +2674,9 @@ nemo_window_preview_pane_off (NemoWindow *window) // nemo_window_update_show_hide_ui_elements (window); - /* Save preview pane state to directory metadata */ + /* Save preview pane state to directory metadata (only if explicitly requested) */ slot = nemo_window_get_active_slot (window); - if (slot != NULL && slot->content_view != NULL) { + if (write_metadata && slot != NULL && slot->content_view != NULL) { NemoFile *directory_file; directory_file = nemo_view_get_directory_as_file (slot->content_view); @@ -2671,6 +2692,13 @@ nemo_window_preview_pane_off (NemoWindow *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) { From 462018889408e27305e8adfd0f6cc769f3ce921d Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Fri, 12 Dec 2025 23:17:24 -0500 Subject: [PATCH 16/19] use thumbnails as backup. --- libnemo-private/nemo-file.c | 12 +++++ libnemo-private/nemo-file.h | 1 + libnemo-private/nemo-preview-image.c | 81 ++++++++++++++-------------- src/nemo-window-menus.c | 2 +- 4 files changed, 54 insertions(+), 42 deletions(-) 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-preview-image.c b/libnemo-private/nemo-preview-image.c index c7f24be8b..f1be2870d 100644 --- a/libnemo-private/nemo-preview-image.c +++ b/libnemo-private/nemo-preview-image.c @@ -24,6 +24,7 @@ #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 @@ -218,23 +219,6 @@ on_drawing_area_draw (GtkWidget *widget, 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_icon_at_size (NemoPreviewImage *widget, gint width, @@ -335,16 +319,15 @@ load_image_at_size (NemoPreviewImage *widget, 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); + ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); - if (priv->file == NULL || !is_image_file (priv->file)) { + if (priv->file == NULL) { return; } @@ -352,23 +335,43 @@ load_image_at_size (NemoPreviewImage *widget, return; } - location = nemo_file_get_location (priv->file); - path = g_file_get_path (location); - g_object_unref (location); - - if (path == NULL) { - return; + if (nemo_can_thumbnail_internally (priv->file)) { + gchar *path = nemo_file_get_path (priv->file); + + if (path != NULL) { + pixbuf = gdk_pixbuf_new_from_file_at_scale (path, + width * ui_scale, + height * ui_scale, + TRUE, + &error); + if (error != NULL) { + g_warning ("Failed to load direct image preview: %s", error->message); + g_clear_error (&error); + } + + g_free (path); + } + } + + if (pixbuf == NULL && nemo_file_has_loaded_thumbnail (priv->file)) { + gchar *thumbnail_path = nemo_file_get_thumbnail_path (priv->file); + + if (thumbnail_path != NULL) { + pixbuf = gdk_pixbuf_new_from_file_at_scale (thumbnail_path, + width * ui_scale, + height * ui_scale, + TRUE, + &error); + + if (error != NULL) { + g_warning ("Failed to load file thumbnail for preview: %s", error->message); + g_clear_error (&error); + } + + g_free (thumbnail_path); + } } - 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) { @@ -402,8 +405,6 @@ load_image_at_size (NemoPreviewImage *widget, gtk_widget_hide (priv->drawing_area); } - g_free (path); - priv->current_width = width; priv->current_height = height; } @@ -510,7 +511,7 @@ on_size_allocate (GtkWidget *widget, allocation->height < priv->current_height); /* If getting smaller, immediately scale down the current pixbuf for responsive UI */ - if (getting_smaller && priv->current_pixbuf != NULL) { + if (priv->current_pixbuf != NULL) { scale_current_pixbuf_to_size (preview, allocation->width, allocation->height); } @@ -569,9 +570,7 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, if (file != NULL) { nemo_file_ref (file); - - if (is_image_file (file)) { - /* Load the image at current widget size */ + if (nemo_can_thumbnail_internally (file) || nemo_file_has_loaded_thumbnail (file)) { priv->showing_icon = FALSE; gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); load_image_at_size (widget, allocation.width, allocation.height); diff --git a/src/nemo-window-menus.c b/src/nemo-window-menus.c index 8a99853c7..5d01b7e23 100644 --- a/src/nemo-window-menus.c +++ b/src/nemo-window-menus.c @@ -1982,7 +1982,7 @@ nemo_window_initialize_menus (NemoWindow *window) gtk_action_set_visible (action_to_hide, eel_vfs_supports_uri_scheme ("trash")); action_to_hide = gtk_action_group_get_action (action_group, "Go to Network"); gtk_action_set_visible (action_to_hide, eel_vfs_supports_uri_scheme ("network")); -g_printerr ("FUCKKKKKK\n"); + gtk_action_group_add_toggle_actions (action_group, main_toggle_entries, G_N_ELEMENTS (main_toggle_entries), window); From 5d46dc3064e39747954f1146d2f6f62bd21acf9d Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Sat, 13 Dec 2025 07:38:18 -0500 Subject: [PATCH 17/19] refactor --- libnemo-private/nemo-preview-image.c | 42 +++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/libnemo-private/nemo-preview-image.c b/libnemo-private/nemo-preview-image.c index f1be2870d..7701a993f 100644 --- a/libnemo-private/nemo-preview-image.c +++ b/libnemo-private/nemo-preview-image.c @@ -465,6 +465,29 @@ scale_current_pixbuf_to_size (NemoPreviewImage *widget, } } +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; + } + + /* Determine whether to show image/thumbnail or generic icon */ + 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) { @@ -476,12 +499,7 @@ on_resize_timeout (gpointer user_data) priv->resize_timeout_id = 0; gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); - - if (priv->showing_icon) { - load_icon_at_size (widget, allocation.width, allocation.height); - } else { - load_image_at_size (widget, allocation.width, allocation.height); - } + reload_at_size (widget, allocation.width, allocation.height); return G_SOURCE_REMOVE; } @@ -570,16 +588,8 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, if (file != NULL) { nemo_file_ref (file); - if (nemo_can_thumbnail_internally (file) || nemo_file_has_loaded_thumbnail (file)) { - priv->showing_icon = FALSE; - gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); - load_image_at_size (widget, allocation.width, allocation.height); - } else { - /* Load folder or file icon at current widget size */ - priv->showing_icon = TRUE; - gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); - load_icon_at_size (widget, allocation.width, allocation.height); - } + gtk_widget_get_allocation (GTK_WIDGET (widget), &allocation); + reload_at_size (widget, allocation.width, allocation.height); } } From e7f2e2864ccc71f78920e0c7019f685ecfbd9efd Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Sat, 13 Dec 2025 08:28:28 -0500 Subject: [PATCH 18/19] Prevent preview on the desktop. --- src/nemo-window.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nemo-window.c b/src/nemo-window.c index 4f29ee590..db168eb56 100644 --- a/src/nemo-window.c +++ b/src/nemo-window.c @@ -2566,6 +2566,10 @@ nemo_window_preview_pane_on_internal (NemoWindow *window, 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; From bd3f8e68ba6c16d232b5c2ec4a916759200fefdb Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 18 Dec 2025 21:27:00 -0500 Subject: [PATCH 19/19] Async loading --- libnemo-private/nemo-preview-image.c | 460 +++++++++++++++------------ 1 file changed, 262 insertions(+), 198 deletions(-) diff --git a/libnemo-private/nemo-preview-image.c b/libnemo-private/nemo-preview-image.c index 7701a993f..1212ef09f 100644 --- a/libnemo-private/nemo-preview-image.c +++ b/libnemo-private/nemo-preview-image.c @@ -30,38 +30,82 @@ #define RESIZE_DEBOUNCE_MS 150 #define MIN_SIZE_CHANGE 10 -/* Required for G_DECLARE_FINAL_TYPE */ struct _NemoPreviewImage { GtkBox parent; }; +typedef struct _LoadImageData LoadImageData; + typedef struct { GtkWidget *frame; 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; + gint surface_width; + gint surface_height; - /* Track if showing an icon vs image */ 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) -/* 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 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) { @@ -71,16 +115,16 @@ nemo_preview_image_finalize (GObject *object) 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_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; @@ -101,15 +145,15 @@ nemo_preview_image_init (NemoPreviewImage *preview) 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; + priv->surface_width = 0; + priv->surface_height = 0; priv->showing_icon = FALSE; + priv->current_load_data = NULL; - /* Create frame to hold drawing area */ 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); @@ -119,7 +163,6 @@ nemo_preview_image_init (NemoPreviewImage *preview) gtk_box_pack_start (GTK_BOX (preview), priv->frame, TRUE, TRUE, 0); - /* 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); @@ -129,7 +172,6 @@ nemo_preview_image_init (NemoPreviewImage *preview) G_CALLBACK (on_drawing_area_draw), preview); gtk_container_add (GTK_CONTAINER (priv->frame), priv->drawing_area); - /* 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); @@ -139,7 +181,6 @@ nemo_preview_image_init (NemoPreviewImage *preview) gtk_container_set_border_width (GTK_CONTAINER (preview), 4); gtk_widget_show_all (GTK_WIDGET (preview)); - /* Connect size-allocate signal for resize handling */ g_signal_connect (preview, "size-allocate", G_CALLBACK (on_size_allocate), NULL); } @@ -170,9 +211,9 @@ on_drawing_area_draw (GtkWidget *widget, NemoPreviewImage *preview = NEMO_PREVIEW_IMAGE (user_data); NemoPreviewImagePrivate *priv; gint widget_width, widget_height; - gint surface_width, surface_height; + gdouble scale_x, scale_y, scale; + gdouble scaled_width, scaled_height; gdouble x_offset, y_offset; - gint scale_factor; priv = nemo_preview_image_get_instance_private (preview); @@ -183,42 +224,183 @@ on_drawing_area_draw (GtkWidget *widget, 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; + 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; - /* Center the image in the drawing area */ - x_offset = (widget_width - surface_width) / 2.0; - y_offset = (widget_height - surface_height) / 2.0; + x_offset = (widget_width - scaled_width) / 2.0; + y_offset = (widget_height - scaled_height) / 2.0; - /* Draw the image first */ - cairo_set_source_surface (cr, priv->current_surface, x_offset, y_offset); + 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; /* Inset border slightly inside image bounds */ + gdouble inset = 0.5; - /* Get the border color from the frame's style context */ style_context = gtk_widget_get_style_context (priv->frame); gtk_style_context_get_border_color (style_context, GTK_STATE_FLAG_NORMAL, &border_color); - /* Draw border rectangle inset from the image edge */ 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, - surface_width - (inset * 2), surface_height - (inset * 2)); + 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, @@ -246,17 +428,13 @@ load_icon_at_size (NemoPreviewImage *widget, ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); - /* Calculate icon size - use the smaller dimension to fit in the space */ icon_size = MIN (width, height) * ui_scale; - - /* Get the icon info from the file */ 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 (); - /* Load the icon at the exact size we need */ icon_pixbuf = gtk_icon_theme_load_icon_for_scale (icon_theme, icon_name, icon_size / ui_scale, @@ -265,20 +443,17 @@ load_icon_at_size (NemoPreviewImage *widget, &error); if (icon_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 (icon_pixbuf); - - surface = gdk_cairo_surface_create_from_pixbuf (icon_pixbuf, ui_scale, NULL); + surface = create_surface_from_pixbuf (icon_pixbuf, ui_scale); if (surface != NULL) { - /* Replace old surface with new one */ 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); } @@ -286,7 +461,6 @@ load_icon_at_size (NemoPreviewImage *widget, g_object_unref (icon_pixbuf); gtk_widget_hide (priv->message_label); } else { - /* Failed to load icon */ if (error != NULL) { g_warning ("Failed to load icon '%s': %s", icon_name, error->message); g_error_free (error); @@ -299,7 +473,6 @@ load_icon_at_size (NemoPreviewImage *widget, nemo_icon_info_unref (icon_info); } else { - /* No icon info available */ if (icon_info != NULL) { nemo_icon_info_unref (icon_info); } @@ -319,13 +492,13 @@ load_image_at_size (NemoPreviewImage *widget, gint height) { NemoPreviewImagePrivate *priv; - GdkPixbuf *pixbuf = NULL; - cairo_surface_t *surface = NULL; + GTask *task; + LoadImageData *data; + gchar *file_path = NULL; + gchar *thumbnail_path = NULL; gint ui_scale; - GError *error = NULL; priv = nemo_preview_image_get_instance_private (widget); - ui_scale = gtk_widget_get_scale_factor (GTK_WIDGET (widget)); if (priv->file == NULL) { return; @@ -335,134 +508,42 @@ load_image_at_size (NemoPreviewImage *widget, return; } - if (nemo_can_thumbnail_internally (priv->file)) { - gchar *path = nemo_file_get_path (priv->file); - - if (path != NULL) { - pixbuf = gdk_pixbuf_new_from_file_at_scale (path, - width * ui_scale, - height * ui_scale, - TRUE, - &error); - if (error != NULL) { - g_warning ("Failed to load direct image preview: %s", error->message); - g_clear_error (&error); - } - - g_free (path); - } - } - - if (pixbuf == NULL && nemo_file_has_loaded_thumbnail (priv->file)) { - gchar *thumbnail_path = nemo_file_get_thumbnail_path (priv->file); - - if (thumbnail_path != NULL) { - pixbuf = gdk_pixbuf_new_from_file_at_scale (thumbnail_path, - width * ui_scale, - height * ui_scale, - TRUE, - &error); - - if (error != NULL) { - g_warning ("Failed to load file thumbnail for preview: %s", error->message); - g_clear_error (&error); - } - - g_free (thumbnail_path); - } + 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 (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 (nemo_can_thumbnail_internally (priv->file)) { + file_path = nemo_file_get_path (priv->file); + } - 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); - } + if (nemo_file_has_loaded_thumbnail (priv->file)) { + thumbnail_path = nemo_file_get_thumbnail_path (priv->file); + } - 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); - } + if (file_path == NULL && thumbnail_path == NULL) { gtk_label_set_text (GTK_LABEL (priv->message_label), - _("(Failed to load image)")); + _("(No preview available)")); gtk_widget_show (priv->message_label); gtk_widget_hide (priv->drawing_area); - } - - 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); + data = load_image_data_new (file_path, thumbnail_path, width, height, ui_scale); + g_free (file_path); + g_free (thumbnail_path); - if (scaled_pixbuf != NULL) { - surface = gdk_cairo_surface_create_from_pixbuf (scaled_pixbuf, ui_scale, NULL); + priv->current_load_data = data; - 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); - } + 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); - g_object_unref (scaled_pixbuf); - } + priv->current_width = width; + priv->current_height = height; } static void @@ -478,7 +559,6 @@ reload_at_size (NemoPreviewImage *widget, return; } - /* Determine whether to show image/thumbnail or generic icon */ 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); @@ -511,34 +591,17 @@ on_size_allocate (GtkWidget *widget, { 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 (priv->current_pixbuf != NULL) { - scale_current_pixbuf_to_size (preview, allocation->width, allocation->height); + if (priv->current_surface != NULL) { + gtk_widget_queue_draw (priv->drawing_area); } - /* 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); @@ -559,7 +622,11 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, return; } - /* Clear any pending resize timeout */ + 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; @@ -571,7 +638,6 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, priv->file = file; - /* Clear current image */ if (priv->current_surface != NULL) { cairo_surface_destroy (priv->current_surface); priv->current_surface = NULL; @@ -580,11 +646,8 @@ nemo_preview_image_set_file (NemoPreviewImage *widget, 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; - } + priv->surface_width = 0; + priv->surface_height = 0; if (file != NULL) { nemo_file_ref (file); @@ -602,17 +665,16 @@ nemo_preview_image_clear (NemoPreviewImage *widget) priv = nemo_preview_image_get_instance_private (widget); - /* Clear any pending resize timeout */ + 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_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; @@ -627,5 +689,7 @@ nemo_preview_image_clear (NemoPreviewImage *widget) 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; }