diff options
Diffstat (limited to '')
-rw-r--r-- | libgimpwidgets/gimpzoommodel.c | 698 |
1 files changed, 698 insertions, 0 deletions
diff --git a/libgimpwidgets/gimpzoommodel.c b/libgimpwidgets/gimpzoommodel.c new file mode 100644 index 0000000..2860595 --- /dev/null +++ b/libgimpwidgets/gimpzoommodel.c @@ -0,0 +1,698 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpzoommodel.c + * Copyright (C) 2005 David Odin <dindinx@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gtk/gtk.h> + +#include "gimpwidgetstypes.h" + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "gimphelpui.h" +#include "gimpwidgetsmarshal.h" +#include "gimpzoommodel.h" + + +/** + * SECTION: gimpzoommodel + * @title: GimpZoomModel + * @short_description: A model for zoom values. + * + * A model for zoom values. + **/ + + +#define ZOOM_MIN (1.0 / 256.0) +#define ZOOM_MAX (256.0) + +enum +{ + ZOOMED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_VALUE, + PROP_MINIMUM, + PROP_MAXIMUM, + PROP_FRACTION, + PROP_PERCENTAGE +}; + + +typedef struct +{ + gdouble value; + gdouble minimum; + gdouble maximum; +} GimpZoomModelPrivate; + +#define GIMP_ZOOM_MODEL_GET_PRIVATE(obj) \ + ((GimpZoomModelPrivate *) ((GimpZoomModel *) (obj))->priv) + + +static void gimp_zoom_model_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_zoom_model_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + + +static guint zoom_model_signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE_WITH_PRIVATE (GimpZoomModel, gimp_zoom_model, G_TYPE_OBJECT) + +#define parent_class gimp_zoom_model_parent_class + + +static void +gimp_zoom_model_class_init (GimpZoomModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + /** + * GimpZoomModel::zoomed: + * @model: the object that received the signal + * @old_factor: the zoom factor before it changes + * @new_factor: the zoom factor after it has changed. + * + * Emitted when the zoom factor of the zoom model changes. + */ + zoom_model_signals[ZOOMED] = + g_signal_new ("zoomed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpZoomModelClass, + zoomed), + NULL, NULL, + _gimp_widgets_marshal_VOID__DOUBLE_DOUBLE, + G_TYPE_NONE, 2, + G_TYPE_DOUBLE, G_TYPE_DOUBLE); + + object_class->set_property = gimp_zoom_model_set_property; + object_class->get_property = gimp_zoom_model_get_property; + + /** + * GimpZoomModel:value: + * + * The zoom factor. + */ + g_object_class_install_property (object_class, PROP_VALUE, + g_param_spec_double ("value", + "Value", + "Zoom factor", + ZOOM_MIN, ZOOM_MAX, + 1.0, + GIMP_PARAM_READWRITE)); + /** + * GimpZoomModel:minimum: + * + * The minimum zoom factor. + */ + g_object_class_install_property (object_class, PROP_MINIMUM, + g_param_spec_double ("minimum", + "Minimum", + "Lower limit for the zoom factor", + ZOOM_MIN, ZOOM_MAX, + ZOOM_MIN, + GIMP_PARAM_READWRITE)); + /** + * GimpZoomModel:maximum: + * + * The maximum zoom factor. + */ + g_object_class_install_property (object_class, PROP_MAXIMUM, + g_param_spec_double ("maximum", + "Maximum", + "Upper limit for the zoom factor", + ZOOM_MIN, ZOOM_MAX, + ZOOM_MAX, + GIMP_PARAM_READWRITE)); + + /** + * GimpZoomModel:fraction: + * + * The zoom factor expressed as a fraction. + */ + g_object_class_install_property (object_class, PROP_FRACTION, + g_param_spec_string ("fraction", + "Fraction", + "The zoom factor expressed as a fraction", + "1:1", + GIMP_PARAM_READABLE)); + /** + * GimpZoomModel:percentage: + * + * The zoom factor expressed as percentage. + */ + g_object_class_install_property (object_class, PROP_PERCENTAGE, + g_param_spec_string ("percentage", + "Percentage", + "The zoom factor expressed as a percentage", + "100%", + GIMP_PARAM_READABLE)); +} + +static void +gimp_zoom_model_init (GimpZoomModel *model) +{ + GimpZoomModelPrivate *priv; + + model->priv = gimp_zoom_model_get_instance_private (model); + + priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + priv->value = 1.0; + priv->minimum = ZOOM_MIN; + priv->maximum = ZOOM_MAX; +} + +static void +gimp_zoom_model_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (object); + gdouble previous_value; + + previous_value = priv->value; + g_object_freeze_notify (object); + + switch (property_id) + { + case PROP_VALUE: + priv->value = g_value_get_double (value); + + g_object_notify (object, "value"); + g_object_notify (object, "fraction"); + g_object_notify (object, "percentage"); + break; + + case PROP_MINIMUM: + priv->minimum = MIN (g_value_get_double (value), priv->maximum); + break; + + case PROP_MAXIMUM: + priv->maximum = MAX (g_value_get_double (value), priv->minimum); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + if (priv->value > priv->maximum || priv->value < priv->minimum) + { + priv->value = CLAMP (priv->value, priv->minimum, priv->maximum); + + g_object_notify (object, "value"); + g_object_notify (object, "fraction"); + g_object_notify (object, "percentage"); + } + + g_object_thaw_notify (object); + + if (priv->value != previous_value) + { + g_signal_emit (object, zoom_model_signals[ZOOMED], + 0, previous_value, priv->value); + } +} + +static void +gimp_zoom_model_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (object); + gchar *tmp; + + switch (property_id) + { + case PROP_VALUE: + g_value_set_double (value, priv->value); + break; + + case PROP_MINIMUM: + g_value_set_double (value, priv->minimum); + break; + + case PROP_MAXIMUM: + g_value_set_double (value, priv->maximum); + break; + + case PROP_FRACTION: + { + gint numerator; + gint denominator; + + gimp_zoom_model_get_fraction (GIMP_ZOOM_MODEL (object), + &numerator, &denominator); + + tmp = g_strdup_printf ("%d:%d", numerator, denominator); + g_value_set_string (value, tmp); + g_free (tmp); + } + break; + + case PROP_PERCENTAGE: + tmp = g_strdup_printf (priv->value >= 0.15 ? "%.0f%%" : "%.2f%%", + priv->value * 100.0); + g_value_set_string (value, tmp); + g_free (tmp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_zoom_model_zoom_in (GimpZoomModel *model) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + if (priv->value < priv->maximum) + gimp_zoom_model_zoom (model, GIMP_ZOOM_IN, 0.0); +} + +static void +gimp_zoom_model_zoom_out (GimpZoomModel *model) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + if (priv->value > priv->minimum) + gimp_zoom_model_zoom (model, GIMP_ZOOM_OUT, 0.0); +} + +/** + * gimp_zoom_model_new: + * + * Creates a new #GimpZoomModel. + * + * Return value: a new #GimpZoomModel. + * + * Since GIMP 2.4 + **/ +GimpZoomModel * +gimp_zoom_model_new (void) +{ + return g_object_new (GIMP_TYPE_ZOOM_MODEL, NULL); +} + + +/** + * gimp_zoom_model_set_range: + * @model: a #GimpZoomModel + * @min: new lower limit for zoom factor + * @max: new upper limit for zoom factor + * + * Sets the allowed range of the @model. + * + * Since GIMP 2.4 + **/ +void +gimp_zoom_model_set_range (GimpZoomModel *model, + gdouble min, + gdouble max) +{ + g_return_if_fail (GIMP_IS_ZOOM_MODEL (model)); + g_return_if_fail (min < max); + g_return_if_fail (min >= ZOOM_MIN); + g_return_if_fail (max <= ZOOM_MAX); + + g_object_set (model, + "minimum", min, + "maximum", max, + NULL); +} + +/** + * gimp_zoom_model_zoom: + * @model: a #GimpZoomModel + * @zoom_type: the #GimpZoomType + * @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO + * + * Since GIMP 2.4 + **/ +void +gimp_zoom_model_zoom (GimpZoomModel *model, + GimpZoomType zoom_type, + gdouble scale) +{ + g_return_if_fail (GIMP_IS_ZOOM_MODEL (model)); + + if (zoom_type != GIMP_ZOOM_TO) + scale = gimp_zoom_model_get_factor (model); + + g_object_set (model, + "value", gimp_zoom_model_zoom_step (zoom_type, scale), + NULL); +} + +/** + * gimp_zoom_model_get_factor: + * @model: a #GimpZoomModel + * + * Retrieves the current zoom factor of @model. + * + * Return value: the current scale factor + * + * Since GIMP 2.4 + **/ +gdouble +gimp_zoom_model_get_factor (GimpZoomModel *model) +{ + g_return_val_if_fail (GIMP_IS_ZOOM_MODEL (model), 1.0); + + return GIMP_ZOOM_MODEL_GET_PRIVATE (model)->value; +} + + +/** + * gimp_zoom_model_get_fraction + * @model: a #GimpZoomModel + * @numerator: return location for numerator + * @denominator: return location for denominator + * + * Retrieves the current zoom factor of @model as a fraction. + * + * Since GIMP 2.4 + **/ +void +gimp_zoom_model_get_fraction (GimpZoomModel *model, + gint *numerator, + gint *denominator) +{ + gint p0, p1, p2; + gint q0, q1, q2; + gdouble zoom_factor; + gdouble remainder, next_cf; + gboolean swapped = FALSE; + + g_return_if_fail (GIMP_IS_ZOOM_MODEL (model)); + g_return_if_fail (numerator != NULL && denominator != NULL); + + zoom_factor = gimp_zoom_model_get_factor (model); + + /* make sure that zooming behaves symmetrically */ + if (zoom_factor < 1.0) + { + zoom_factor = 1.0 / zoom_factor; + swapped = TRUE; + } + + /* calculate the continued fraction for the desired zoom factor */ + + p0 = 1; + q0 = 0; + p1 = floor (zoom_factor); + q1 = 1; + + remainder = zoom_factor - p1; + + while (fabs (remainder) >= 0.0001 && + fabs (((gdouble) p1 / q1) - zoom_factor) > 0.0001) + { + remainder = 1.0 / remainder; + + next_cf = floor (remainder); + + p2 = next_cf * p1 + p0; + q2 = next_cf * q1 + q0; + + /* Numerator and Denominator are limited by 256 */ + /* also absurd ratios like 170:171 are excluded */ + if (p2 > 256 || q2 > 256 || (p2 > 1 && q2 > 1 && p2 * q2 > 200)) + break; + + /* remember the last two fractions */ + p0 = p1; + p1 = p2; + q0 = q1; + q1 = q2; + + remainder = remainder - next_cf; + } + + zoom_factor = (gdouble) p1 / q1; + + /* hard upper and lower bounds for zoom ratio */ + + if (zoom_factor > 256.0) + { + p1 = 256; + q1 = 1; + } + else if (zoom_factor < 1.0 / 256.0) + { + p1 = 1; + q1 = 256; + } + + if (swapped) + { + *numerator = q1; + *denominator = p1; + } + else + { + *numerator = p1; + *denominator = q1; + } +} + +static GtkWidget * +zoom_button_new (const gchar *icon_name, + GtkIconSize icon_size) +{ + GtkWidget *button; + GtkWidget *image; + + image = gtk_image_new_from_icon_name (icon_name, + icon_size > 0 ? + icon_size : GTK_ICON_SIZE_BUTTON); + + button = gtk_button_new (); + gtk_container_add (GTK_CONTAINER (button), image); + gtk_widget_show (image); + + return button; +} + +static void +zoom_in_button_callback (GimpZoomModel *model, + gdouble old, + gdouble new, + GtkWidget *button) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + gtk_widget_set_sensitive (button, priv->value != priv->maximum); +} + +static void +zoom_out_button_callback (GimpZoomModel *model, + gdouble old, + gdouble new, + GtkWidget *button) +{ + GimpZoomModelPrivate *priv = GIMP_ZOOM_MODEL_GET_PRIVATE (model); + + gtk_widget_set_sensitive (button, priv->value != priv->minimum); +} + +/** + * gimp_zoom_button_new: + * @model: a #GimpZoomModel + * @zoom_type: + * @icon_size: use 0 for a button with text labels + * + * Return value: a newly created GtkButton + * + * Since GIMP 2.4 + **/ +GtkWidget * +gimp_zoom_button_new (GimpZoomModel *model, + GimpZoomType zoom_type, + GtkIconSize icon_size) +{ + GtkWidget *button = NULL; + + g_return_val_if_fail (GIMP_IS_ZOOM_MODEL (model), NULL); + + switch (zoom_type) + { + case GIMP_ZOOM_IN: + button = zoom_button_new ("zoom-in", icon_size); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_zoom_model_zoom_in), + model); + g_signal_connect_object (model, "zoomed", + G_CALLBACK (zoom_in_button_callback), + button, 0); + break; + + case GIMP_ZOOM_OUT: + button = zoom_button_new ("zoom-out", icon_size); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_zoom_model_zoom_out), + model); + g_signal_connect_object (model, "zoomed", + G_CALLBACK (zoom_out_button_callback), + button, 0); + break; + + default: + g_warning ("sorry, no button for this zoom type (%d)", zoom_type); + break; + } + + if (button) + { + gdouble zoom = gimp_zoom_model_get_factor (model); + + /* set initial button sensitivity */ + g_signal_emit (model, zoom_model_signals[ZOOMED], 0, zoom, zoom); + + if (icon_size > 0) + { + const gchar *desc; + + if (gimp_enum_get_value (GIMP_TYPE_ZOOM_TYPE, zoom_type, + NULL, NULL, &desc, NULL)) + { + gimp_help_set_help_data (button, desc, NULL); + } + } + } + + return button; +} + +/** + * gimp_zoom_model_zoom_step: + * @zoom_type: the zoom type + * @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO + * + * Utility function to calculate a new scale factor. + * + * Return value: the new scale factor + * + * Since GIMP 2.4 + **/ +gdouble +gimp_zoom_model_zoom_step (GimpZoomType zoom_type, + gdouble scale) +{ + gint i, n_presets; + gdouble new_scale = 1.0; + + /* This table is constructed to have fractions, that approximate + * sqrt(2)^k. This gives a smooth feeling regardless of the starting + * zoom level. + * + * Zooming in/out always jumps to a zoom step from the list below. + * However, we try to guarantee a certain size of the step, to + * avoid silly jumps from 101% to 100%. + * + * The factor 1.1 is chosen a bit arbitrary, but feels better + * than the geometric median of the zoom steps (2^(1/4)). + */ + +#define ZOOM_MIN_STEP 1.1 + + const gdouble presets[] = { + 1.0 / 256, 1.0 / 180, 1.0 / 128, 1.0 / 90, + 1.0 / 64, 1.0 / 45, 1.0 / 32, 1.0 / 23, + 1.0 / 16, 1.0 / 11, 1.0 / 8, 2.0 / 11, + 1.0 / 4, 1.0 / 3, 1.0 / 2, 2.0 / 3, + 1.0, + 3.0 / 2, 2.0, 3.0, + 4.0, 11.0 / 2, 8.0, 11.0, + 16.0, 23.0, 32.0, 45.0, + 64.0, 90.0, 128.0, 180.0, + 256.0, + }; + + n_presets = G_N_ELEMENTS (presets); + + switch (zoom_type) + { + case GIMP_ZOOM_IN: + scale *= ZOOM_MIN_STEP; + + new_scale = presets[n_presets - 1]; + for (i = n_presets - 1; i >= 0 && presets[i] > scale; i--) + new_scale = presets[i]; + + break; + + case GIMP_ZOOM_OUT: + scale /= ZOOM_MIN_STEP; + + new_scale = presets[0]; + for (i = 0; i < n_presets && presets[i] < scale; i++) + new_scale = presets[i]; + + break; + + case GIMP_ZOOM_IN_MORE: + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_IN, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_IN, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_IN, scale); + new_scale = scale; + break; + + case GIMP_ZOOM_OUT_MORE: + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, scale); + scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, scale); + new_scale = scale; + break; + + case GIMP_ZOOM_IN_MAX: + new_scale = ZOOM_MAX; + break; + + case GIMP_ZOOM_OUT_MAX: + new_scale = ZOOM_MIN; + break; + + case GIMP_ZOOM_TO: + new_scale = scale; + break; + } + + return CLAMP (new_scale, ZOOM_MIN, ZOOM_MAX); + +#undef ZOOM_MIN_STEP +} |