diff --git a/libgimp/gimpresolutionentry-private.c b/libgimp/gimpresolutionentry-private.c
new file mode 100644
index 0000000000..296ebb28ae
--- /dev/null
+++ b/libgimp/gimpresolutionentry-private.c
@@ -0,0 +1,742 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpresolutionentry.c
+ * Copyright (C) 2024 Jehan
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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. If not, see .
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "gimp.h"
+#include "gimpui.h"
+#include "gimpresolutionentry-private.h"
+
+#include "libgimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_PIXEL_DENSITY,
+ PROP_UNIT,
+ PROP_KEEP_RATIO,
+ N_PROPS
+};
+
+struct _GimpResolutionEntry
+{
+ GtkGrid parent_instance;
+
+ gint width;
+ gint height;
+ gdouble ppi;
+ GimpUnit unit;
+ gboolean keep_ratio;
+
+ GtkWidget *phy_width_label;
+ GtkWidget *phy_height_label;
+
+ GtkWidget *chainbutton;
+};
+
+G_DEFINE_FINAL_TYPE (GimpResolutionEntry, gimp_resolution_entry, GTK_TYPE_GRID)
+
+#define parent_class gimp_resolution_entry_parent_class
+
+
+static void gimp_resolution_entry_class_init (GimpResolutionEntryClass *class);
+static void gimp_resolution_entry_init (GimpResolutionEntry *gre);
+static void gimp_resolution_entry_constructed (GObject *object);
+static void gimp_resolution_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_resolution_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GtkWidget * gimp_resolution_entry_attach_label (GimpResolutionEntry *entry,
+ const gchar *text,
+ gint row,
+ gint column,
+ gfloat alignment);
+static void gimp_resolution_entry_format_label (GimpResolutionEntry *entry,
+ GtkWidget *label,
+ gdouble size);
+static void gimp_resolution_entry_update_labels (GimpResolutionEntry *entry,
+ GParamSpec *param_spec,
+ GtkWidget *label);
+
+static gboolean gimp_resolution_entry_ppi_to_unit (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ GimpResolutionEntry *entry);
+static gboolean gimp_resolution_entry_unit_to_ppi (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ GimpResolutionEntry *entry);
+
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+
+static void
+gimp_resolution_entry_class_init (GimpResolutionEntryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_resolution_entry_constructed;
+ object_class->set_property = gimp_resolution_entry_set_property;
+ object_class->get_property = gimp_resolution_entry_get_property;
+
+ props[PROP_WIDTH] = g_param_spec_int ("width",
+ "Width in pixel",
+ NULL,
+ 0, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_CONSTRUCT);
+ props[PROP_HEIGHT] = g_param_spec_int ("height",
+ "Height in pixel",
+ NULL,
+ 0, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_CONSTRUCT);
+ props[PROP_PIXEL_DENSITY] = g_param_spec_double ("pixel-density",
+ "Pixel density in pixel per inch",
+ NULL,
+ GIMP_MIN_RESOLUTION, GIMP_MAX_RESOLUTION, 300.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_CONSTRUCT);
+ props[PROP_UNIT] = gimp_param_spec_unit ("unit",
+ "Physical unit for the pixel density",
+ "This unit is used to select the pixel density "
+ "and show dimensions in physical unit",
+ FALSE, FALSE,
+ GIMP_UNIT_INCH,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_CONSTRUCT);
+ props[PROP_KEEP_RATIO] = g_param_spec_boolean ("keep-ratio",
+ _("_Keep aspect ratio"),
+ _("Force dimensions with aspect ratio"),
+ TRUE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_CONSTRUCT);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+gimp_resolution_entry_init (GimpResolutionEntry *entry)
+{
+ entry->width = 0;
+ entry->height = 0;
+ entry->ppi = 300.0;
+ entry->unit = GIMP_UNIT_INCH;
+ entry->keep_ratio = TRUE;
+
+ gtk_grid_set_row_spacing (GTK_GRID (entry), 2);
+ gtk_grid_set_column_spacing (GTK_GRID (entry), 4);
+}
+
+static void
+gimp_resolution_entry_constructed (GObject *object)
+{
+ GimpResolutionEntry *entry = GIMP_RESOLUTION_ENTRY (object);
+ GtkTreeModel *model;
+ GtkWidget *label;
+ GtkWidget *widget;
+ GBinding *binding;
+ GtkAdjustment *adj;
+
+ widget = gimp_prop_spin_button_new (object, "pixel-density", 1.0, 10.0, 2);
+ adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget));
+ binding = g_object_get_data (G_OBJECT (adj), "gimp-prop-adjustment-binding");
+ g_binding_unbind (binding);
+ binding = g_object_bind_property_full (object, "pixel-density",
+ widget, "value",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+ (GBindingTransformFunc) gimp_resolution_entry_ppi_to_unit,
+ (GBindingTransformFunc) gimp_resolution_entry_unit_to_ppi,
+ entry, NULL);
+ g_object_set_data (G_OBJECT (adj), "gimp-prop-adjustment-binding", binding);
+ gtk_grid_attach (GTK_GRID (entry), widget, 1, 3, 1, 1);
+ gtk_widget_show (widget);
+
+ widget = gimp_prop_unit_combo_box_new (object, "unit");
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
+ g_object_set (model,
+ "short-format", _("pixels/%a"),
+ "long-format", _("pixels/%a"),
+ NULL);
+ gtk_grid_attach (GTK_GRID (entry), widget, 3, 3, 1, 1);
+ gtk_widget_show (widget);
+
+ widget = gimp_prop_spin_button_new (object, "width", 1.0, 10.0, 0);
+ gtk_grid_attach (GTK_GRID (entry), widget, 1, 1, 1, 1);
+ gtk_widget_show (widget);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "xalign", 0.0,
+ "yalign", 0.5,
+ NULL);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gimp_resolution_entry_format_label (entry, label,
+ entry->width / entry->ppi);
+ entry->phy_width_label = label;
+ gtk_grid_attach (GTK_GRID (entry), entry->phy_width_label, 3, 1, 1, 1);
+ gtk_widget_show (entry->phy_width_label);
+
+ widget = gimp_prop_spin_button_new (object, "height", 1.0, 10.0, 0);
+ gtk_grid_attach (GTK_GRID (entry), widget, 1, 2, 1, 1);
+ gtk_widget_show (widget);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "xalign", 0.0,
+ "yalign", 0.5,
+ NULL);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gimp_resolution_entry_format_label (entry, label,
+ entry->height / entry->ppi);
+ entry->phy_height_label = label;
+ gtk_grid_attach (GTK_GRID (entry), entry->phy_height_label, 3, 2, 1, 1);
+ gtk_widget_show (entry->phy_height_label);
+
+ g_signal_connect (object, "notify::width",
+ G_CALLBACK (gimp_resolution_entry_update_labels),
+ entry->phy_width_label);
+ g_signal_connect (object, "notify::height",
+ G_CALLBACK (gimp_resolution_entry_update_labels),
+ entry->phy_height_label);
+ g_signal_connect (object, "notify::pixel-density",
+ G_CALLBACK (gimp_resolution_entry_update_labels),
+ NULL);
+}
+
+static void
+gimp_resolution_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpResolutionEntry *entry = GIMP_RESOLUTION_ENTRY (object);
+
+ switch (property_id)
+ {
+ case PROP_WIDTH:
+ gimp_resolution_entry_set_width (entry, g_value_get_int (value));
+ break;
+ case PROP_HEIGHT:
+ gimp_resolution_entry_set_height (entry, g_value_get_int (value));
+ break;
+ case PROP_PIXEL_DENSITY:
+ gimp_resolution_entry_set_pixel_density (entry, g_value_get_double (value));
+ break;
+ case PROP_UNIT:
+ gimp_resolution_entry_set_unit (entry, g_value_get_int (value));
+ break;
+ case PROP_KEEP_RATIO:
+ gimp_resolution_entry_set_keep_ratio (entry, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_resolution_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpResolutionEntry *entry = GIMP_RESOLUTION_ENTRY (object);
+
+ switch (property_id)
+ {
+ case PROP_WIDTH:
+ g_value_set_int (value, entry->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, entry->height);
+ break;
+ case PROP_PIXEL_DENSITY:
+ g_value_set_double (value, entry->ppi);
+ break;
+ case PROP_UNIT:
+ g_value_set_int (value, entry->unit);
+ break;
+ case PROP_KEEP_RATIO:
+ g_value_set_boolean (value, entry->keep_ratio);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GtkWidget *
+gimp_prop_resolution_entry_new (GObject *config,
+ const gchar *width_prop,
+ const gchar *height_prop,
+ const gchar *ppi_prop,
+ const gchar *unit_prop)
+{
+ GtkWidget *widget;
+ GParamSpec *w_pspec;
+ GParamSpec *h_pspec;
+ GParamSpec *d_pspec;
+ GParamSpec *u_pspec;
+ gint width = 0;
+ gint height = 0;
+ gdouble ppi = 300.0;
+ GimpUnit unit = GIMP_UNIT_INCH;
+
+ g_return_val_if_fail (G_IS_OBJECT (config), NULL);
+ g_return_val_if_fail (width_prop != NULL, NULL);
+ g_return_val_if_fail (height_prop != NULL, NULL);
+ g_return_val_if_fail (ppi_prop != NULL, NULL);
+ g_return_val_if_fail (unit_prop != NULL, NULL);
+
+ w_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), width_prop);
+ h_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), height_prop);
+ d_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), ppi_prop);
+ u_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), unit_prop);
+
+ g_return_val_if_fail (w_pspec != NULL, NULL);
+ g_return_val_if_fail (h_pspec != NULL, NULL);
+ g_return_val_if_fail (d_pspec != NULL, NULL);
+ g_return_val_if_fail (u_pspec != NULL, NULL);
+
+ g_object_get (config,
+ width_prop, &width,
+ height_prop, &height,
+ ppi_prop, &ppi,
+ unit_prop, &unit,
+ NULL);
+
+ widget = gimp_resolution_entry_new (g_param_spec_get_nick (w_pspec), width,
+ g_param_spec_get_nick (h_pspec), height,
+ g_param_spec_get_nick (d_pspec), ppi,
+ unit);
+
+ g_object_bind_property (config, width_prop,
+ widget, "width",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ g_object_bind_property (config, height_prop,
+ widget, "height",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ g_object_bind_property (config, ppi_prop,
+ widget, "pixel-density",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ g_object_bind_property (config, unit_prop,
+ widget, "unit",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ return widget;
+}
+
+/**
+ * gimp_resolution_entry_new:
+ * @width_label: Optional label for the width control.
+ * @width: Width of the item, specified in pixels.
+ * @height_label: Optional label for the height control.
+ * @height: Height of the item, specified in pixels.
+ * @res_label: Optional label for the resolution entry.
+ * @pixel_density: The initial resolution in pixel per inch.
+ * @display_unit: The display unit.
+ *
+ * Creates a new #GimpResolutionEntry widget.
+ *
+ * The #GimpResolutionEntry is derived from #GtkGrid and will have
+ * an empty border of one cell width on each side plus an empty column left
+ * of the #GimpUnitMenu to allow the caller to add labels or other widgets.
+ *
+ * A #GimpChainButton is displayed if independent is set to %TRUE.
+ *
+ * Returns: A pointer to the new #GimpResolutionEntry widget.
+ **/
+GtkWidget *
+gimp_resolution_entry_new (const gchar *width_label,
+ gint width,
+ const gchar *height_label,
+ gint height,
+ const gchar *res_label,
+ gdouble pixel_density,
+ GimpUnit display_unit)
+{
+ GimpResolutionEntry *entry;
+
+ entry = g_object_new (GIMP_TYPE_RESOLUTION_ENTRY,
+ "width", width,
+ "height", height,
+ "pixel-density", pixel_density,
+ "unit", display_unit,
+ NULL);
+
+ if (width_label)
+ gimp_resolution_entry_attach_label (entry, width_label, 1, 0, 0.0);
+
+ if (height_label)
+ gimp_resolution_entry_attach_label (entry, height_label, 2, 0, 0.0);
+
+ if (res_label)
+ gimp_resolution_entry_attach_label (entry, res_label, 3, 0, 0.0);
+
+ return GTK_WIDGET (entry);
+}
+
+static gboolean
+gimp_resolution_entry_idle_notify (GimpResolutionEntry *entry)
+{
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_WIDTH]);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HEIGHT]);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+gimp_resolution_entry_set_width (GimpResolutionEntry *entry,
+ gint width)
+{
+ if (width == 0)
+ {
+ /* Do nothing, i.e. revert back to value it was. Yet notify after an idle
+ * source, otherwise the entry is not updated (the binding likely blocks
+ * notifications for this property to avoid infinite loops.
+ */
+ g_idle_add ((GSourceFunc) gimp_resolution_entry_idle_notify, entry);
+ }
+ else if (entry->width != width)
+ {
+ gdouble inch_width = 0.0;
+
+ if (entry->width != 0 && entry->height != 0 && entry->ppi != 0.0)
+ inch_width = (gdouble) entry->width / entry->ppi;
+
+ g_object_freeze_notify (G_OBJECT (entry));
+
+ if (entry->keep_ratio && entry->width != 0)
+ {
+ gdouble ratio;
+ gint height;
+
+ ratio = (gdouble) entry->height / (gdouble) entry->width;
+ height = (gint) (width * ratio);
+
+ if (height != entry->height)
+ {
+ entry->height = height;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HEIGHT]);
+ }
+ }
+
+ entry->width = width;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_WIDTH]);
+
+ if (inch_width != 0.0)
+ {
+ entry->ppi = (gdouble) entry->width / inch_width;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PIXEL_DENSITY]);
+ }
+
+ g_object_thaw_notify (G_OBJECT (entry));
+ }
+}
+
+void
+gimp_resolution_entry_set_height (GimpResolutionEntry *entry,
+ gint height)
+{
+ if (height == 0)
+ {
+ /* Do nothing, i.e. revert back to value it was. Yet notify after an idle
+ * source, otherwise the entry is not updated (the binding likely blocks
+ * notifications for this property to avoid infinite loops.
+ */
+ g_idle_add ((GSourceFunc) gimp_resolution_entry_idle_notify, entry);
+ }
+ else if (entry->height != height)
+ {
+ gdouble inch_height = 0.0;
+
+ if (entry->width != 0 && entry->height != 0 && entry->ppi != 0.0)
+ inch_height = (gdouble) entry->height / entry->ppi;
+
+ g_object_freeze_notify (G_OBJECT (entry));
+
+ if (entry->keep_ratio && entry->height != 0)
+ {
+ gdouble ratio;
+ gint width;
+
+ ratio = (gdouble) entry->width / (gdouble) entry->height;
+ width = (gint) (height * ratio);
+
+ if (width != entry->width)
+ {
+ entry->width = width;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_WIDTH]);
+ }
+ }
+
+ entry->height = height;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HEIGHT]);
+
+ if (inch_height != 0.0)
+ {
+ entry->ppi = (gdouble) entry->height / inch_height;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PIXEL_DENSITY]);
+ }
+
+ g_object_thaw_notify (G_OBJECT (entry));
+ }
+}
+
+void
+gimp_resolution_entry_set_pixel_density (GimpResolutionEntry *entry,
+ gdouble ppi)
+{
+ if (entry->ppi != ppi)
+ {
+ gdouble inch_width = entry->width / entry->ppi;
+ gdouble inch_height = entry->height / entry->ppi;
+
+ g_object_freeze_notify (G_OBJECT (entry));
+ entry->ppi = ppi;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PIXEL_DENSITY]);
+
+ /* This test is just to check if we are in construction. */
+ if (entry->phy_width_label)
+ {
+ entry->width = inch_width * ppi;
+ entry->height = inch_height * ppi;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_WIDTH]);
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_HEIGHT]);
+ }
+
+ g_object_thaw_notify (G_OBJECT (entry));
+ }
+}
+
+void
+gimp_resolution_entry_set_unit (GimpResolutionEntry *entry,
+ GimpUnit unit)
+{
+ g_return_if_fail (unit != GIMP_UNIT_PIXEL);
+ g_return_if_fail (unit != GIMP_UNIT_PERCENT);
+
+ if (entry->unit != unit)
+ {
+ entry->unit = unit;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_UNIT]);
+ /* Even though the pixel density (in pixel per inch) has not in fact
+ * changed, we send a notification to force a refresh of the displayed
+ * pixel per other-unit value in the entry field.
+ */
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PIXEL_DENSITY]);
+
+ if (entry->phy_width_label)
+ gimp_resolution_entry_format_label (entry, entry->phy_width_label,
+ gimp_unit_get_factor (entry->unit) *
+ entry->width / entry->ppi);
+ if (entry->phy_height_label)
+ gimp_resolution_entry_format_label (entry, entry->phy_height_label,
+ gimp_unit_get_factor (entry->unit) *
+ entry->height / entry->ppi);
+ }
+}
+
+void
+gimp_resolution_entry_set_keep_ratio (GimpResolutionEntry *entry,
+ gboolean keep_ratio)
+{
+ if (keep_ratio != entry->keep_ratio)
+ {
+ entry->keep_ratio = keep_ratio;
+ g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_KEEP_RATIO]);
+ }
+}
+
+gint
+gimp_resolution_entry_get_width (GimpResolutionEntry *entry)
+{
+ return entry->width;
+}
+
+gint
+gimp_resolution_entry_get_height (GimpResolutionEntry *entry)
+{
+ return entry->height;
+}
+
+gdouble
+gimp_resolution_entry_get_density (GimpResolutionEntry *entry)
+{
+ return entry->ppi;
+}
+
+GimpUnit
+gimp_resolution_entry_get_unit (GimpResolutionEntry *entry)
+{
+ return entry->unit;
+}
+
+gboolean
+gimp_resolution_entry_get_keep_ratio (GimpResolutionEntry *entry)
+{
+ return entry->keep_ratio;
+}
+
+
+/* Private functions. */
+
+/**
+ * gimp_resolution_entry_attach_label:
+ * @gre: The #GimpResolutionEntry you want to add a label to.
+ * @text: The text of the label.
+ * @row: The row where the label will be attached.
+ * @column: The column where the label will be attached.
+ * @alignment: The horizontal alignment of the label.
+ *
+ * Attaches a #GtkLabel to the #GimpResolutionEntry (which is a #GtkGrid).
+ *
+ * Returns: A pointer to the new #GtkLabel widget.
+ **/
+static GtkWidget *
+gimp_resolution_entry_attach_label (GimpResolutionEntry *gre,
+ const gchar *text,
+ gint row,
+ gint column,
+ gfloat alignment)
+{
+ GtkWidget *label;
+
+ g_return_val_if_fail (GIMP_IS_RESOLUTION_ENTRY (gre), NULL);
+ g_return_val_if_fail (text != NULL, NULL);
+
+ label = gtk_label_new_with_mnemonic (text);
+
+ if (column == 0)
+ {
+ GList *children;
+ GList *list;
+
+ children = gtk_container_get_children (GTK_CONTAINER (gre));
+
+ for (list = children; list; list = g_list_next (list))
+ {
+ GtkWidget *child = list->data;
+ gint left_attach;
+ gint top_attach;
+
+ gtk_container_child_get (GTK_CONTAINER (gre), child,
+ "left-attach", &left_attach,
+ "top-attach", &top_attach,
+ NULL);
+
+ if (left_attach == 1 && top_attach == row)
+ {
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), child);
+ break;
+ }
+ }
+
+ g_list_free (children);
+ }
+
+ gtk_label_set_xalign (GTK_LABEL (label), alignment);
+
+ gtk_grid_attach (GTK_GRID (gre), label, column, row, 1, 1);
+ gtk_widget_show (label);
+
+ return label;
+}
+
+static void
+gimp_resolution_entry_format_label (GimpResolutionEntry *entry,
+ GtkWidget *label,
+ gdouble size_inch)
+{
+ gchar *format = g_strdup_printf ("%%.%df %%s",
+ gimp_unit_get_digits (entry->unit));
+ gchar *text = g_strdup_printf (format,
+ size_inch * gimp_unit_get_factor (entry->unit),
+ gimp_unit_get_plural (entry->unit));
+ g_free (format);
+
+ gtk_label_set_text (GTK_LABEL (label), text);
+ g_free (text);
+}
+
+static void
+gimp_resolution_entry_update_labels (GimpResolutionEntry *entry,
+ GParamSpec *param_spec,
+ GtkWidget *label)
+{
+ if ((label == NULL && entry->phy_width_label != NULL) ||
+ (label != NULL && label == entry->phy_width_label))
+ gimp_resolution_entry_format_label (entry, entry->phy_width_label,
+ entry->width / entry->ppi);
+
+ if ((label == NULL && entry->phy_height_label != NULL) ||
+ (label != NULL && label == entry->phy_height_label))
+ gimp_resolution_entry_format_label (entry, entry->phy_height_label,
+ entry->height / entry->ppi);
+}
+
+static gboolean
+gimp_resolution_entry_ppi_to_unit (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ GimpResolutionEntry *entry)
+{
+ gdouble ppi = g_value_get_double (from_value);
+
+ g_value_set_double (to_value, ppi / gimp_unit_get_factor (entry->unit));
+
+ return TRUE;
+}
+
+static gboolean
+gimp_resolution_entry_unit_to_ppi (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ GimpResolutionEntry *entry)
+{
+ gdouble ppu = g_value_get_double (from_value);
+
+ g_value_set_double (to_value, ppu * gimp_unit_get_factor (entry->unit));
+
+ return TRUE;
+}
diff --git a/libgimp/gimpresolutionentry-private.h b/libgimp/gimpresolutionentry-private.h
new file mode 100644
index 0000000000..737f063cd4
--- /dev/null
+++ b/libgimp/gimpresolutionentry-private.h
@@ -0,0 +1,80 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpresolutionentry.h
+ * Copyright (C) 2024 Jehan
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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. If not, see .
+ */
+
+#if !defined (__GIMP_UI_H_INSIDE__) && !defined (GIMP_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __GIMP_RESOLUTION_ENTRY__
+#define __GIMP_RESOLUTION_ENTRY__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+/*
+ * This widget was initially part of libgimpwidgets. Then it got removed and
+ * moved as private code to file-pdf-load plug-in.
+ *
+ * I'm adding it back for GIMP 3 release as its own code but not making it
+ * public (only to be used by GimpVectorLoadProcedure so far) so that we don't
+ * have to make sure the API is really right.
+ *
+ * TODO: we want to eventually get this back to public API in libgimpwidgets,
+ * with a much nicer API and also adding a property widget function.
+ */
+#define GIMP_TYPE_RESOLUTION_ENTRY (gimp_resolution_entry_get_type ())
+G_DECLARE_FINAL_TYPE (GimpResolutionEntry, gimp_resolution_entry, GIMP, RESOLUTION_ENTRY, GtkGrid)
+
+typedef struct _GimpResolutionEntry GimpResolutionEntry;
+
+GtkWidget * gimp_prop_resolution_entry_new (GObject *config,
+ const gchar *width_prop,
+ const gchar *height_prop,
+ const gchar *ppi_prop,
+ const gchar *unit_prop);
+GtkWidget * gimp_resolution_entry_new (const gchar *width_label,
+ gint width,
+ const gchar *height_label,
+ gint height,
+ const gchar *res_label,
+ gdouble pixel_density,
+ GimpUnit display_unit);
+
+void gimp_resolution_entry_set_width (GimpResolutionEntry *entry,
+ gint width);
+void gimp_resolution_entry_set_height (GimpResolutionEntry *entry,
+ gint height);
+void gimp_resolution_entry_set_pixel_density (GimpResolutionEntry *entry,
+ gdouble ppi);
+void gimp_resolution_entry_set_unit (GimpResolutionEntry *entry,
+ GimpUnit unit);
+void gimp_resolution_entry_set_keep_ratio (GimpResolutionEntry *entry,
+ gboolean keep_ratio);
+
+gint gimp_resolution_entry_get_width (GimpResolutionEntry *entry);
+gint gimp_resolution_entry_get_height (GimpResolutionEntry *entry);
+gdouble gimp_resolution_entry_get_density (GimpResolutionEntry *entry);
+GimpUnit gimp_resolution_entry_get_unit (GimpResolutionEntry *entry);
+gboolean gimp_resolution_entry_get_keep_ratio (GimpResolutionEntry *entry);
+
+G_END_DECLS
+
+#endif /* __GIMP_RESOLUTION_ENTRY__ */
diff --git a/libgimp/gimpui.def b/libgimp/gimpui.def
index a049891d25..f5e9cce2c6 100644
--- a/libgimp/gimpui.def
+++ b/libgimp/gimpui.def
@@ -73,6 +73,19 @@ EXPORTS
gimp_prop_gradient_chooser_new
gimp_prop_palette_chooser_new
gimp_prop_pattern_chooser_new
+ gimp_prop_resolution_entry_new
+ gimp_resolution_entry_get_density
+ gimp_resolution_entry_get_height
+ gimp_resolution_entry_get_keep_ratio
+ gimp_resolution_entry_get_type
+ gimp_resolution_entry_get_unit
+ gimp_resolution_entry_get_width
+ gimp_resolution_entry_new
+ gimp_resolution_entry_set_height
+ gimp_resolution_entry_set_keep_ratio
+ gimp_resolution_entry_set_pixel_density
+ gimp_resolution_entry_set_unit
+ gimp_resolution_entry_set_width
gimp_resource_chooser_get_label
gimp_resource_chooser_get_resource
gimp_resource_chooser_get_type
diff --git a/libgimp/meson.build b/libgimp/meson.build
index 5bdb6a5e24..979aab5295 100644
--- a/libgimp/meson.build
+++ b/libgimp/meson.build
@@ -296,6 +296,9 @@ libgimpui_sources_introspectable = [
libgimpui_sources = [
libgimpui_sources_introspectable,
gimpuimarshal,
+
+ # TODO: eventually move to libgimpwidgets.
+ 'gimpresolutionentry-private.c'
]
libgimpui_headers_introspectable = [