gimp/libgimp/gimpresolutionentry-private.c

712 lines
26 KiB
C

/* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#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;
gdouble ratio;
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,
GIMP_MIN_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, GIMP_MIN_IMAGE_SIZE,
GIMP_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_CONSTRUCT);
props[PROP_HEIGHT] = g_param_spec_int ("height",
"Height in pixel",
NULL,
GIMP_MIN_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, GIMP_MIN_IMAGE_SIZE,
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;
g_return_if_fail (entry->height != 0);
/* Initial ratio is from the initial values. */
entry->ratio = (gdouble) entry->width / (gdouble) entry->height;
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_object (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_object (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)
{
g_object_freeze_notify (G_OBJECT (entry));
if (entry->keep_ratio && entry->width != 0)
{
gint height;
height = (gint) ((gdouble) width / entry->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]);
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)
{
g_object_freeze_notify (G_OBJECT (entry));
if (entry->keep_ratio && entry->height != 0)
{
gint width;
width = (gint) ((gdouble) height * entry->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]);
g_object_thaw_notify (G_OBJECT (entry));
}
}
void
gimp_resolution_entry_set_pixel_density (GimpResolutionEntry *entry,
gdouble ppi)
{
if (entry->ppi != ppi)
{
entry->ppi = ppi;
g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PIXEL_DENSITY]);
}
}
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;
if (entry->keep_ratio)
entry->ratio = (gdouble) entry->width / (gdouble) entry->height;
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_name (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;
}