diff --git a/ChangeLog b/ChangeLog index 657228fb94..49562b5f8e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2006-12-30 Simon Budig + + * libgimpwidgets/gimpratioentry.[ch]: New files implementing a widget + for entering ratios. Will be improved over time... + + * libgimpwidgets/gimpwidgetstypes.h + * libgimpwidgets/gimpwidgets.h + * libgimpwidgets/Makefile.am: changed accordingly. + + * app/widgets/gimppropwidgets.c: use it for the crop/rectangle + select tools. + 2006-12-30 Sven Neumann * plug-ins/print/print.c: use a GtkMessageDialog to display errors. diff --git a/app/widgets/gimppropwidgets.c b/app/widgets/gimppropwidgets.c index d2fa9e12f4..6751e14fb1 100644 --- a/app/widgets/gimppropwidgets.c +++ b/app/widgets/gimppropwidgets.c @@ -558,20 +558,16 @@ typedef struct const gchar *height_property; } AspectData; -static void gimp_prop_numeric_entry_notify (GObject *config, +static void gimp_prop_ratio_entry_notify (GObject *config, GParamSpec *param_spec, GtkEntry *entry); -static void gimp_prop_numeric_entry_callback (GtkWidget *widget, - GObject *config); - -static gboolean gimp_prop_numeric_entry_focus_out (GtkWidget *widget, - GdkEventFocus *event, - GObject *config); static void gimp_prop_aspect_ratio_flip (GtkWidget *widget, gpointer data); static void gimp_prop_aspect_ratio_square (GtkWidget *widget, gpointer data); +static void gimp_prop_aspect_ratio_ratio (GtkWidget *widget, + gpointer data); static void gimp_prop_aspect_ratio_set (GtkWidget *widget, gpointer data); @@ -614,14 +610,11 @@ gimp_prop_aspect_ratio_new (GObject *config, gint col0) { AspectData *aspect_data; - GParamSpec *param_spec; - GtkWidget *label; GtkWidget *entry; GtkWidget *hbox; GtkWidget *button; gdouble numerator; gdouble denominator; - gchar num_string[20]; g_object_get (config, numerator_property, &numerator, @@ -636,53 +629,23 @@ gimp_prop_aspect_ratio_new (GObject *config, aspect_data->width_property = width_property; aspect_data->height_property = height_property; - /* numerator entry */ - param_spec = find_param_spec (config, numerator_property, G_STRFUNC); - if (! param_spec) - return; - entry = gtk_entry_new (); - gtk_entry_set_width_chars (GTK_ENTRY (entry), 5); - sprintf (num_string, "%lg", numerator); - gtk_entry_set_text (GTK_ENTRY (entry), num_string); + entry = gimp_ratio_entry_new (); + g_object_set_data (G_OBJECT (entry), + "gimp-ratio-entry-aspect-data", + aspect_data); + gtk_entry_set_width_chars (GTK_ENTRY (entry), 9); + gimp_ratio_entry_set_fraction (GIMP_RATIO_ENTRY (entry), + numerator, denominator); gtk_table_attach_defaults (GTK_TABLE (table), entry, - col0, col0 + 1, row0, row0 + 1); - set_param_spec (G_OBJECT (entry), entry, param_spec); - g_signal_connect (entry, "activate", - G_CALLBACK (gimp_prop_numeric_entry_callback), - config); - g_signal_connect (entry, "focus-out-event", - G_CALLBACK (gimp_prop_numeric_entry_focus_out), - config); + col0, col0 + 3, row0, row0 + 1); + g_signal_connect (entry, "ratio-changed", + G_CALLBACK (gimp_prop_aspect_ratio_ratio), + aspect_data); connect_notify (config, numerator_property, - G_CALLBACK (gimp_prop_numeric_entry_notify), + G_CALLBACK (gimp_prop_ratio_entry_notify), entry); - gtk_widget_show (entry); - - /* ":" label */ - label = gtk_label_new (":"); - gtk_table_attach_defaults (GTK_TABLE (table), label, - col0 + 1, col0 + 2, row0, row0 + 1); - gtk_widget_show (label); - - /* denominator entry */ - param_spec = find_param_spec (config, denominator_property, G_STRFUNC); - if (! param_spec) - return; - entry = gtk_entry_new (); - gtk_entry_set_width_chars (GTK_ENTRY (entry), 5); - sprintf (num_string, "%lg", denominator); - gtk_entry_set_text (GTK_ENTRY (entry), num_string); - gtk_table_attach_defaults (GTK_TABLE (table), entry, - col0 + 2, col0 + 3, row0, row0 + 1); - set_param_spec (G_OBJECT (entry), entry, param_spec); - g_signal_connect (entry, "activate", - G_CALLBACK (gimp_prop_numeric_entry_callback), - config); - g_signal_connect (entry, "focus-out-event", - G_CALLBACK (gimp_prop_numeric_entry_focus_out), - config); connect_notify (config, denominator_property, - G_CALLBACK (gimp_prop_numeric_entry_notify), + G_CALLBACK (gimp_prop_ratio_entry_notify), entry); gtk_widget_show (entry); @@ -717,53 +680,23 @@ gimp_prop_aspect_ratio_new (GObject *config, } static void -gimp_prop_numeric_entry_notify (GObject *config, - GParamSpec *param_spec, - GtkEntry *entry) +gimp_prop_ratio_entry_notify (GObject *config, + GParamSpec *param_spec, + GtkEntry *entry) { - gdouble value; - gchar text[20]; + AspectData *aspect_data = g_object_get_data (G_OBJECT (entry), + "gimp-ratio-entry-aspect-data"); - g_object_get (config, param_spec->name, &value, NULL); + gdouble num, denom; - sprintf (text, "%3lg", value); + g_return_if_fail (aspect_data != NULL); - gtk_entry_set_text (entry, text); -} + g_object_get (config, + aspect_data->numerator_property, &num, + aspect_data->denominator_property, &denom, + NULL); -static gboolean -gimp_prop_numeric_entry_focus_out (GtkWidget *widget, - GdkEventFocus *event, - GObject *config) -{ - gimp_prop_numeric_entry_callback (widget, config); - - return FALSE; -} - -static void -gimp_prop_numeric_entry_callback (GtkWidget *widget, - GObject *config) -{ - GParamSpec *param_spec; - gdouble value; - - g_return_if_fail (GTK_IS_ENTRY (widget)); - - param_spec = get_param_spec (G_OBJECT (widget)); - if (! param_spec) - return; - - /* we use strtod instead of g_ascii_strtod because it uses the locale, - which is what we want here */ - value = strtod (gtk_entry_get_text (GTK_ENTRY (widget)), NULL); - - if (value != 0) - g_object_set (config, - param_spec->name, value, - NULL); - else - g_message ("Invalid value entered for aspect ratio."); + gimp_ratio_entry_set_fraction (GTK_RATIO_ENTRY (entry), num, denom); } static void @@ -830,6 +763,22 @@ gimp_prop_aspect_ratio_square (GtkWidget *widget, NULL); } +static void +gimp_prop_aspect_ratio_ratio (GtkWidget *widget, + gpointer data) +{ + AspectData *aspect_data = data; + gdouble num, denom; + + gimp_ratio_entry_get_fraction (GIMP_RATIO_ENTRY (widget), &num, &denom); + + g_object_set (aspect_data->config, + aspect_data->numerator_property, num, + aspect_data->denominator_property, denom, + aspect_data->fixed_aspect_property, TRUE, + NULL); +} + static void gimp_prop_aspect_ratio_set (GtkWidget *widget, gpointer data) diff --git a/libgimpwidgets/Makefile.am b/libgimpwidgets/Makefile.am index 31e11f3a7d..65276ddc9e 100644 --- a/libgimpwidgets/Makefile.am +++ b/libgimpwidgets/Makefile.am @@ -142,6 +142,8 @@ libgimpwidgets_2_0_la_sources = \ gimppropwidgets.h \ gimpquerybox.c \ gimpquerybox.h \ + gimpratioentry.c \ + gimpratioentry.h \ gimpresolutionentry.c \ gimpresolutionentry.h \ gimpscrolledpreview.c \ diff --git a/libgimpwidgets/gimpratioentry.c b/libgimpwidgets/gimpratioentry.c new file mode 100644 index 0000000000..82e03a4a93 --- /dev/null +++ b/libgimpwidgets/gimpratioentry.c @@ -0,0 +1,424 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpratioentry.c + * Copyright (C) 2006 Simon Budig + * + * 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 2 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 + * Library 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "gimpwidgetstypes.h" + +#include "gimpratioentry.h" + +#define EPSILON 0.000001 + +enum +{ + RATIO_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_RATIO, + PROP_NUMERATOR, + PROP_DENOMINATOR, +}; + + +static void gimp_ratio_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_ratio_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static gboolean gimp_ratio_entry_events (GtkWidget *widget, + GdkEvent *event); +static void gimp_ratio_entry_format_text (GimpRatioEntry *entry); +static void gimp_ratio_entry_parse_text (GimpRatioEntry *entry, + const gchar *text); + +G_DEFINE_TYPE (GimpRatioEntry, gimp_ratio_entry, GTK_TYPE_ENTRY) + +#define parent_class gimp_ratio_entry_parent_class + +static guint entry_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_ratio_entry_class_init (GimpRatioEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + entry_signals[RATIO_CHANGED] = + g_signal_new ("ratio-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpRatioEntryClass, ratio_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->ratio_changed = NULL; + + object_class->set_property = gimp_ratio_entry_set_property; + object_class->get_property = gimp_ratio_entry_get_property; + + g_object_class_install_property (object_class, PROP_RATIO, + g_param_spec_double ("ratio", + "Ratio", NULL, + G_MINDOUBLE, G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_NUMERATOR, + g_param_spec_double ("numerator", + "Numerator of the ratio", NULL, + G_MINDOUBLE, G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_DENOMINATOR, + g_param_spec_double ("denominator", + "Denominator of the ratio", NULL, + G_MINDOUBLE, G_MAXDOUBLE, + 1.0, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_ratio_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpRatioEntry *entry = GIMP_RATIO_ENTRY (object); + + switch (property_id) + { + case PROP_RATIO: + gimp_ratio_entry_set_ratio (entry, g_value_get_double (value)); + break; + case PROP_NUMERATOR: + gimp_ratio_entry_set_fraction (entry, + g_value_get_double (value), + entry->denominator); + break; + case PROP_DENOMINATOR: + gimp_ratio_entry_set_fraction (entry, + entry->numerator, + g_value_get_double (value)); + break; + } +} + +static void +gimp_ratio_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpRatioEntry *entry = GIMP_RATIO_ENTRY (object); + + switch (property_id) + { + case PROP_RATIO: + g_value_set_double (value, gimp_ratio_entry_get_ratio (entry)); + break; + case PROP_NUMERATOR: + g_value_set_double (value, entry->numerator); + break; + case PROP_DENOMINATOR: + g_value_set_double (value, entry->denominator); + break; + } +} + + +static void +gimp_ratio_entry_init (GimpRatioEntry *entry) +{ + entry->numerator = 1; + entry->denominator = 1; + + gtk_entry_set_text (GTK_ENTRY (entry), "1:1"); + + g_signal_connect (entry, "focus-out-event", + G_CALLBACK (gimp_ratio_entry_events), + NULL); + g_signal_connect (entry, "key-press-event", + G_CALLBACK (gimp_ratio_entry_events), + NULL); +} + +/** + * gimp_ratio_entry_new: + * + * Return value: a new #GimpRatioEntry widget + * + * Since: GIMP 2.4 + **/ +GtkWidget * +gimp_ratio_entry_new (void) +{ + return g_object_new (GIMP_TYPE_RATIO_ENTRY, NULL); +} + +/** + * gimp_ratio_entry_set_ratio: + * @entry: a #GimpRatioEntry widget + * @ratio: ratio to set in the widget + * @calc_fraction: convert into a fraction? + * + * Sets the ratio displayed by a #GimpRatioEntry. If the new ratio is + * different than the previous ratio, the "ratio-changed" signal is + * emitted. + * + * An attempt is made to convert the decimal number into a fraction with + * numerator and denominator < 1000. + * + * Since: GIMP 2.4 + **/ +void +gimp_ratio_entry_set_ratio (GimpRatioEntry *entry, + gdouble ratio) +{ + gdouble remainder, next_cf; + gint p0, p1, p2; + gint q0, q1, q2; + + /* calculate the continued fraction to approximate the desired ratio */ + + p0 = 1; + q0 = 0; + p1 = floor (ratio); + q1 = 1; + + remainder = ratio - p1; + + while (fabs (remainder) >= 0.0001 && + fabs (((gdouble) p1 / q1) - ratio) > 0.0001) + { + remainder = 1.0 / remainder; + + next_cf = floor (remainder); + + p2 = next_cf * p1 + p0; + q2 = next_cf * q1 + q0; + + /* remember the last two fractions */ + p0 = p1; + q0 = q1; + p1 = p2; + q1 = q2; + + remainder = remainder - next_cf; + } + + /* only use the calculated fraction if it is "reasonable" */ + if (p1 < 1000 && q1 < 1000) + gimp_ratio_entry_set_fraction (entry, p1, q1); + else + gimp_ratio_entry_set_fraction (entry, ratio, 1.0); +} + +/** + * gimp_ratio_entry_get_ratio: + * @entry: a #GimpRatioEntry widget + * + * Retrieves the ratio value displayed by a #GimpRatioEntry. + * + * Since: GIMP 2.4 + **/ +gdouble +gimp_ratio_entry_get_ratio (GimpRatioEntry *entry) +{ + return entry->denominator == 0.0 ? entry->numerator : + entry->numerator / entry->denominator; +} + +/** + * gimp_ratio_entry_set_fraction: + * @entry: a #GimpRatioEntry widget + * @numerator: numerator of the fraction to set in the widget + * @denominator: denominator of the fraction to set in the widget + * + * Sets the fraction displayed by a #GimpRatioEntry. If the resulting + * ratio is different to the previously set ratio, the "ratio-changed" + * signal is emitted. + * + * If the denominator is zero, the #GimpRatioEntry will silently + * convert it to 1.0. + * + * Since: GIMP 2.4 + **/ +void +gimp_ratio_entry_set_fraction (GimpRatioEntry *entry, + const gdouble numerator, + const gdouble denominator) +{ + gdouble old_ratio; + g_return_if_fail (GIMP_IS_RATIO_ENTRY (entry)); + + old_ratio = entry->numerator / entry->denominator; + + entry->numerator = numerator; + entry->denominator = denominator; + + if (entry->denominator < 0) + { + entry->numerator *= -1; + entry->denominator *= -1; + } + + if (entry->denominator < EPSILON) + entry->denominator = 1.0; + + gimp_ratio_entry_format_text (entry); + + if (fabs (old_ratio - entry->numerator / entry->denominator) > EPSILON) + g_signal_emit (entry, entry_signals[RATIO_CHANGED], 0); +} + +/** + * gimp_ratio_entry_get_fraction: + * @entry: a #GimpRatioEntry widget + * @numerator: pointer to store the numerator of the fraction + * @denominator: pointer to store the denominator of the fraction + * + * Gets the fraction displayed by a #GimpRatioEntry. + * + * The denominator may be zero if the #GimpRatioEntry shows just a single + * value. You can use #gimp_ratio_entry_get_ratio to retrieve the ratio + * as a single decimal value. + * + * Since: GIMP 2.4 + **/ +void +gimp_ratio_entry_get_fraction (GimpRatioEntry *entry, + gdouble *numerator, + gdouble *denominator) +{ + g_return_if_fail (GIMP_IS_RATIO_ENTRY (entry)); + g_return_if_fail (numerator != NULL); + g_return_if_fail (denominator != NULL); + + *numerator = entry->numerator; + *denominator = entry->denominator; +} + +static gboolean +gimp_ratio_entry_events (GtkWidget *widget, + GdkEvent *event) +{ + GimpRatioEntry *entry = GIMP_RATIO_ENTRY (widget); + const gchar *text; + + switch (event->type) + { + case GDK_KEY_PRESS: + if (((GdkEventKey *) event)->keyval != GDK_Return) + break; + /* else fall through */ + + case GDK_FOCUS_CHANGE: + text = gtk_entry_get_text (GTK_ENTRY (entry)); + gimp_ratio_entry_parse_text (entry, text); + break; + + default: + /* do nothing */ + break; + } + + return FALSE; +} + +static void +gimp_ratio_entry_format_text (GimpRatioEntry *entry) +{ + gchar *buffer; + + buffer = g_strdup_printf ("%g:%g", entry->numerator, entry->denominator); + + gtk_entry_set_text (GTK_ENTRY (entry), buffer); + g_free (buffer); +} + +static void +gimp_ratio_entry_parse_text (GimpRatioEntry *entry, + const gchar *text) +{ + gint count; + gchar op1, op2, dummy; + gdouble num = 1.0, denom = 0.0; + + count = sscanf (text, " %lf %c %lf %c %c ", &num, &op1, &denom, &op2, &dummy); + + switch (count) + { + case EOF: + case 0: + gimp_ratio_entry_set_fraction (entry, 1.0, 1.0); + break; + + case 1: + gimp_ratio_entry_set_fraction (entry, num, 1.0); + break; + + case 2: + if (op1 == '=') + gimp_ratio_entry_set_ratio (entry, num); + break; + + case 3: + if (op1 == ':' || op1 == '/') + gimp_ratio_entry_set_fraction (entry, num, denom); + break; + + case 4: + if ((op1 == ':' || op1 == '/') && denom != 0 && op2 == '=') + { + if (fabs (denom - 1.0) < EPSILON) + gimp_ratio_entry_set_ratio (entry, num / denom); + else + gimp_ratio_entry_set_fraction (entry, num / denom, 1.0); + } + break; + + case 5: + /* we have additional stuff at the end - malformed input. */ + break; + + default: + break; + } + + /* sanitize text */ + gimp_ratio_entry_format_text (entry); + + gtk_editable_set_position (GTK_EDITABLE (entry), -1); +} diff --git a/libgimpwidgets/gimpratioentry.h b/libgimpwidgets/gimpratioentry.h new file mode 100644 index 0000000000..f47803f72b --- /dev/null +++ b/libgimpwidgets/gimpratioentry.h @@ -0,0 +1,79 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpratioentry.h + * Copyright (C) 2006 Simon Budig + * + * 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 2 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 + * Library 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GIMP_RATIO_ENTRY_H__ +#define __GIMP_RATIO_ENTRY_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_RATIO_ENTRY (gimp_ratio_entry_get_type ()) +#define GIMP_RATIO_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_RATIO_ENTRY, GimpRatioEntry)) +#define GIMP_RATIO_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_RATIO_ENTRY, GimpRatioEntryClass)) +#define GIMP_IS_RATIO_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_RATIO_ENTRY)) +#define GIMP_IS_RATIO_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_RATIO_ENTRY)) +#define GIMP_RATIO_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_RATIO_AREA, GimpRatioEntryClass)) + + +typedef struct _GimpRatioEntryClass GimpRatioEntryClass; + +struct _GimpRatioEntry +{ + GtkEntry parent_instance; + + gdouble numerator; + gdouble denominator; +}; + +struct _GimpRatioEntryClass +{ + GtkEntryClass parent_class; + + void (* ratio_changed) (GimpRatioEntry *entry); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_ratio_entry_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_ratio_entry_new (void); + +void gimp_ratio_entry_set_fraction (GimpRatioEntry *entry, + gdouble numerator, + gdouble denominator); +void gimp_ratio_entry_get_fraction (GimpRatioEntry *entry, + gdouble *numerator, + gdouble *denominator); + +void gimp_ratio_entry_set_ratio (GimpRatioEntry *entry, + gdouble ratio); +gdouble gimp_ratio_entry_get_ratio (GimpRatioEntry *entry); + + +G_END_DECLS + +#endif /* __GIMP_RATIO_ENTRY_H__ */ diff --git a/libgimpwidgets/gimpwidgets.h b/libgimpwidgets/gimpwidgets.h index c35b991caf..ae955a5643 100644 --- a/libgimpwidgets/gimpwidgets.h +++ b/libgimpwidgets/gimpwidgets.h @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include diff --git a/libgimpwidgets/gimpwidgetstypes.h b/libgimpwidgets/gimpwidgetstypes.h index 91de1d8441..50bcabc1ba 100644 --- a/libgimpwidgets/gimpwidgetstypes.h +++ b/libgimpwidgets/gimpwidgetstypes.h @@ -64,6 +64,7 @@ typedef struct _GimpPickButton GimpPickButton; typedef struct _GimpPreview GimpPreview; typedef struct _GimpPreviewArea GimpPreviewArea; typedef struct _GimpPixmap GimpPixmap; +typedef struct _GimpRatioEntry GimpRatioEntry; typedef struct _GimpResolutionEntry GimpResolutionEntry; typedef struct _GimpScrolledPreview GimpScrolledPreview; typedef struct _GimpSizeEntry GimpSizeEntry;