mirror of https://github.com/GNOME/gimp.git
645 lines
18 KiB
C
645 lines
18 KiB
C
/* LIBGIMP - The GIMP Library
|
|
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
|
|
*
|
|
* gimpratioentry.c
|
|
* Copyright (C) 2006 Simon Budig <simon@gimp.org>
|
|
* Copyright (C) 2007 Sven Neumann <sven@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 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 <string.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
#include "gimpwidgetstypes.h"
|
|
|
|
#include "gimpratioentry.h"
|
|
|
|
|
|
#define EPSILON 0.000001
|
|
|
|
|
|
enum
|
|
{
|
|
RATIO_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_RATIO,
|
|
PROP_NUMERATOR,
|
|
PROP_DENOMINATOR,
|
|
PROP_ASPECT
|
|
};
|
|
|
|
enum
|
|
{
|
|
COLUMN_NUMERATOR,
|
|
COLUMN_DENOMINATOR,
|
|
COLUMN_TEXT,
|
|
NUM_COLUMNS
|
|
};
|
|
|
|
|
|
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);
|
|
|
|
static gboolean gimp_ratio_entry_history_select (GtkEntryCompletion *completion,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
GimpRatioEntry *entry);
|
|
|
|
static void gimp_ratio_entry_history_add (GimpRatioEntry *entry,
|
|
gdouble numerator,
|
|
gdouble denominator,
|
|
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));
|
|
g_object_class_install_property (object_class, PROP_ASPECT,
|
|
g_param_spec_enum ("aspect",
|
|
"Aspect", NULL,
|
|
GIMP_TYPE_ASPECT_TYPE,
|
|
GIMP_ASPECT_SQUARE,
|
|
GIMP_PARAM_READWRITE));
|
|
|
|
|
|
klass->history = gtk_list_store_new (NUM_COLUMNS,
|
|
G_TYPE_DOUBLE, G_TYPE_DOUBLE,
|
|
G_TYPE_STRING);
|
|
}
|
|
|
|
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;
|
|
|
|
case PROP_ASPECT:
|
|
gimp_ratio_entry_set_aspect (entry, g_value_get_enum (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
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;
|
|
|
|
case PROP_ASPECT:
|
|
g_value_set_enum (value, gimp_ratio_entry_get_aspect (entry));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_ratio_entry_init (GimpRatioEntry *entry)
|
|
{
|
|
GtkEntryCompletion *completion;
|
|
|
|
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);
|
|
|
|
completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION,
|
|
"model",
|
|
GIMP_RATIO_ENTRY_GET_CLASS (entry)->history,
|
|
"inline-completion", TRUE,
|
|
NULL);
|
|
gtk_entry_completion_set_text_column (completion, COLUMN_TEXT);
|
|
gtk_entry_set_completion (GTK_ENTRY (entry), completion);
|
|
g_object_unref (completion);
|
|
|
|
g_signal_connect (completion, "match-selected",
|
|
G_CALLBACK (gimp_ratio_entry_history_select),
|
|
entry);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* 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.
|
|
*
|
|
* Returns: The ratio value.
|
|
*
|
|
* 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,
|
|
gdouble numerator,
|
|
gdouble denominator)
|
|
{
|
|
GimpAspectType old_aspect;
|
|
gdouble old_ratio;
|
|
|
|
g_return_if_fail (GIMP_IS_RATIO_ENTRY (entry));
|
|
|
|
old_aspect = gimp_ratio_entry_get_aspect (entry);
|
|
old_ratio = gimp_ratio_entry_get_ratio (entry);
|
|
|
|
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);
|
|
|
|
g_object_freeze_notify (G_OBJECT (entry));
|
|
|
|
g_object_notify (G_OBJECT (entry), "numerator");
|
|
g_object_notify (G_OBJECT (entry), "denominator");
|
|
|
|
if (fabs (old_ratio - entry->numerator / entry->denominator) > EPSILON)
|
|
{
|
|
g_object_notify (G_OBJECT (entry), "ratio");
|
|
|
|
if (old_aspect != gimp_ratio_entry_get_aspect (entry))
|
|
g_object_notify (G_OBJECT (entry), "aspect");
|
|
|
|
g_object_thaw_notify (G_OBJECT (entry));
|
|
|
|
g_signal_emit (entry, entry_signals[RATIO_CHANGED], 0);
|
|
}
|
|
else
|
|
{
|
|
g_object_thaw_notify (G_OBJECT (entry));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* gimp_ratio_entry_set_aspect:
|
|
* @entry: a #GimpRatioEntry widget
|
|
* @aspect: the new aspect
|
|
*
|
|
* Sets the aspect of the ratio by swapping the numerator and denominator
|
|
* (or setting them to 1.0 in case that @aspect is %GIMP_ASPECT_SQUARE).
|
|
*
|
|
* Since: GIMP 2.4
|
|
**/
|
|
void
|
|
gimp_ratio_entry_set_aspect (GimpRatioEntry *entry,
|
|
GimpAspectType aspect)
|
|
{
|
|
g_return_if_fail (GIMP_IS_RATIO_ENTRY (entry));
|
|
|
|
if (gimp_ratio_entry_get_aspect (entry) == aspect)
|
|
return;
|
|
|
|
switch (aspect)
|
|
{
|
|
case GIMP_ASPECT_SQUARE:
|
|
gimp_ratio_entry_set_fraction (entry, 1.0, 1.0);
|
|
break;
|
|
|
|
case GIMP_ASPECT_LANDSCAPE:
|
|
case GIMP_ASPECT_PORTRAIT:
|
|
gimp_ratio_entry_set_fraction (entry,
|
|
entry->denominator, entry->numerator);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gimp_ratio_entry_get_aspect:
|
|
* @entry: a #GimpRatioEntry widget
|
|
*
|
|
* Gets the aspect of the ratio displayed by a #GimpRatioEntry.
|
|
*
|
|
* Returns: The entry's current aspect.
|
|
*
|
|
* Since: GIMP 2.4
|
|
**/
|
|
GimpAspectType
|
|
gimp_ratio_entry_get_aspect (GimpRatioEntry *entry)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_RATIO_ENTRY (entry), GIMP_ASPECT_SQUARE);
|
|
|
|
if (entry->numerator > entry->denominator)
|
|
{
|
|
return GIMP_ASPECT_LANDSCAPE;
|
|
}
|
|
else if (entry->numerator < entry->denominator)
|
|
{
|
|
return GIMP_ASPECT_PORTRAIT;
|
|
}
|
|
else
|
|
{
|
|
return GIMP_ASPECT_SQUARE;
|
|
}
|
|
}
|
|
|
|
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);
|
|
gimp_ratio_entry_history_add (entry,
|
|
entry->numerator, entry->denominator, 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);
|
|
}
|
|
|
|
static gboolean
|
|
gimp_ratio_entry_history_select (GtkEntryCompletion *completion,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
GimpRatioEntry *entry)
|
|
{
|
|
gdouble numerator;
|
|
gdouble denominator;
|
|
|
|
gtk_tree_model_get (model, iter,
|
|
COLUMN_NUMERATOR, &numerator,
|
|
COLUMN_DENOMINATOR, &denominator,
|
|
-1);
|
|
|
|
gimp_ratio_entry_set_fraction (entry, numerator, denominator);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gimp_ratio_entry_history_add (GimpRatioEntry *entry,
|
|
gdouble numerator,
|
|
gdouble denominator,
|
|
const gchar *text)
|
|
{
|
|
GimpRatioEntryClass *klass = GIMP_RATIO_ENTRY_GET_CLASS (entry);
|
|
GtkTreeModel *model = GTK_TREE_MODEL (klass->history);
|
|
GValue value = { 0, };
|
|
GtkTreeIter iter;
|
|
gboolean iter_valid;
|
|
|
|
for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
|
|
iter_valid;
|
|
iter_valid = gtk_tree_model_iter_next (model, &iter))
|
|
{
|
|
gtk_tree_model_get_value (model, &iter, COLUMN_TEXT, &value);
|
|
|
|
if (strcmp (text, g_value_get_string (&value)) == 0)
|
|
{
|
|
g_value_unset (&value);
|
|
break;
|
|
}
|
|
|
|
g_value_unset (&value);
|
|
}
|
|
|
|
if (iter_valid)
|
|
{
|
|
gtk_list_store_move_after (GTK_LIST_STORE (model), &iter, NULL);
|
|
}
|
|
else
|
|
{
|
|
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
|
|
|
|
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
|
COLUMN_NUMERATOR, numerator,
|
|
COLUMN_DENOMINATOR, denominator,
|
|
COLUMN_TEXT, text,
|
|
-1);
|
|
|
|
/* FIXME: limit the size of the history */
|
|
}
|
|
}
|