2 * This file is a part of modest
4 * Copyright (C) 2005, 2006, 2008 Nokia Corporation, all rights reserved.
9 * SECTION:modest-number-editor
10 * @short_description: A widget used to enter a number within a pre-defined range.
12 * ModestNumberEditor is used to enter a number from a specific range.
13 * There are two buttons to scroll the value in number field.
14 * Manual input is also possible.
17 * <title>ModestNumberEditor example</title>
19 * number_editor = modest_number_editor_new (-250, 500);
20 * modest_number_editor_set_range (number_editor, 0, 100);
25 #undef MODEST_DISABLE_DEPRECATED
35 #include <gdk/gdkkeysyms.h>
37 #include "modest-number-editor.h"
38 #include "modest-marshal.h"
39 #include <hildon/hildon-banner.h>
40 #include "modest-text-utils.h"
42 #define _(String) dgettext("modest-libs", String)
44 typedef struct _ModestNumberEditorPrivate ModestNumberEditorPrivate;
46 #define MODEST_NUMBER_EDITOR_GET_PRIVATE(obj) \
47 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MODEST_TYPE_NUMBER_EDITOR, \
48 ModestNumberEditorPrivate));
50 struct _ModestNumberEditorPrivate
52 gint start; /* Minimum */
53 gint end; /* Maximum */
58 guint select_all_idle_id; /* Selection repaint hack
59 see modest_number_editor_select_all */
64 modest_number_editor_class_init (ModestNumberEditorClass *editor_class);
67 modest_number_editor_init (ModestNumberEditor *editor);
70 modest_number_editor_entry_focusout (GtkWidget *widget,
75 modest_number_editor_entry_changed (GtkWidget *widget,
79 modest_number_editor_finalize (GObject *self);
82 modest_number_editor_range_error (ModestNumberEditor *editor,
83 ModestNumberEditorErrorType type);
86 modest_number_editor_select_all (ModestNumberEditor *editor);
89 modest_number_editor_validate_value (ModestNumberEditor *editor,
90 gboolean allow_intermediate);
93 modest_number_editor_set_property (GObject * object,
99 modest_number_editor_get_property (GObject *object,
116 static GtkContainerClass* parent_class;
118 static guint ModestNumberEditor_signal[LAST_SIGNAL] = {0};
121 * modest_number_editor_get_type:
123 * Returns GType for ModestNumberEditor.
125 * Returns: ModestNumberEditor type
128 modest_number_editor_get_type (void)
130 static GType editor_type = 0;
134 static const GTypeInfo editor_info =
136 sizeof (ModestNumberEditorClass),
137 NULL, /* base_init */
138 NULL, /* base_finalize */
139 (GClassInitFunc) modest_number_editor_class_init,
140 NULL, /* class_finalize */
141 NULL, /* class_data */
142 sizeof (ModestNumberEditor),
144 (GInstanceInitFunc) modest_number_editor_init,
146 editor_type = g_type_register_static (HILDON_TYPE_ENTRY,
147 "ModestNumberEditor",
154 modest_number_editor_class_init (ModestNumberEditorClass *editor_class)
156 GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
158 g_type_class_add_private (editor_class,
159 sizeof (ModestNumberEditorPrivate));
161 parent_class = g_type_class_peek_parent (editor_class);
163 editor_class->range_error = modest_number_editor_range_error;
165 gobject_class->finalize = modest_number_editor_finalize;
166 gobject_class->set_property = modest_number_editor_set_property;
167 gobject_class->get_property = modest_number_editor_get_property;
170 * ModestNumberEditor:value:
172 * The current value of the number editor.
174 g_object_class_install_property (gobject_class, PROP_VALUE,
175 g_param_spec_int ("value",
177 "The current value of number editor",
180 0, G_PARAM_READWRITE));
182 ModestNumberEditor_signal[RANGE_ERROR] =
183 g_signal_new ("range_error", MODEST_TYPE_NUMBER_EDITOR,
184 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
185 (ModestNumberEditorClass, range_error),
186 g_signal_accumulator_true_handled, NULL,
187 modest_marshal_BOOLEAN__ENUM,
188 G_TYPE_BOOLEAN, 1, MODEST_TYPE_NUMBER_EDITOR_ERROR_TYPE);
190 ModestNumberEditor_signal[VALID_CHANGED] =
191 g_signal_new ("valid_changed", MODEST_TYPE_NUMBER_EDITOR,
192 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
193 (ModestNumberEditorClass, valid_changed),
195 g_cclosure_marshal_VOID__BOOLEAN,
196 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
200 modest_number_editor_finalize (GObject *self)
202 ModestNumberEditorPrivate *priv;
204 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (self);
207 if (priv->select_all_idle_id)
208 g_source_remove (priv->select_all_idle_id);
210 /* Call parent class finalize, if have one */
211 if (G_OBJECT_CLASS (parent_class)->finalize)
212 G_OBJECT_CLASS (parent_class)->finalize(self);
216 modest_number_editor_init (ModestNumberEditor *editor)
218 ModestNumberEditorPrivate *priv;
220 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
223 priv->select_all_idle_id = 0;
225 /* Connect child widget signals */
226 g_signal_connect (GTK_OBJECT (editor), "changed",
227 G_CALLBACK (modest_number_editor_entry_changed),
230 g_signal_connect (GTK_OBJECT (editor), "focus-out-event",
231 G_CALLBACK (modest_number_editor_entry_focusout),
234 hildon_gtk_entry_set_input_mode (GTK_ENTRY (editor),
235 HILDON_GTK_INPUT_MODE_NUMERIC);
237 modest_number_editor_set_range (editor, G_MININT, G_MAXINT);
239 priv->is_valid = TRUE;
242 /* Format given number to editor field, no checks performed, all signals
243 are sent normally. */
245 modest_number_editor_real_set_value (ModestNumberEditor *editor,
248 /* FIXME: That looks REALLY bad */
251 /* Update text in entry to new value */
252 g_snprintf (buffer, sizeof (buffer), "%d", value);
253 gtk_entry_set_text (GTK_ENTRY (editor), buffer);
257 add_select_all_idle (ModestNumberEditor *editor)
259 ModestNumberEditorPrivate *priv;
261 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
263 if (! priv->select_all_idle_id)
265 priv->select_all_idle_id =
266 g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
271 modest_number_editor_validate_value (ModestNumberEditor *editor,
272 gboolean allow_intermediate)
274 ModestNumberEditorPrivate *priv;
275 gint error_code, fixup_value;
280 gboolean is_valid = TRUE;
282 g_assert (MODEST_IS_NUMBER_EDITOR(editor));
284 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
287 text = gtk_entry_get_text (GTK_ENTRY (editor));
289 fixup_value = priv->default_val;
291 if (text && text[0]) {
292 /* Try to convert entry text to number */
293 value = strtol (text, &tail, 10);
295 /* Check if conversion succeeded */
297 /* Check if value is in allowed range. This is tricky in those
298 cases when user is editing a value.
299 For example: Range = [100, 500] and user have just inputted "4".
300 This should not lead into error message. Otherwise value is
301 resetted back to "100" and next "4" press will reset it back
304 if (allow_intermediate) {
305 /* We now have the following error cases:
306 * If inputted value as above maximum and
307 maximum is either positive or then maximum
308 negative and value is positive.
309 * If inputted value is below minimum and minimum
310 is negative or minumum positive and value
312 In all other cases situation can be fixed just by
313 adding new numbers to the string.
315 if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0))) {
316 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
317 fixup_value = priv->end;
319 } else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0))) {
320 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
321 fixup_value = priv->start;
325 if (value > priv->end) {
326 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
327 fixup_value = priv->end;
329 } else if (value < priv->start) {
330 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
331 fixup_value = priv->start;
335 /* The only valid case when conversion can fail is when we
336 have plain '-', intermediate forms are allowed AND
337 minimum bound is negative */
340 if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
341 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
345 if (! allow_intermediate)
346 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
349 if (error_code != -1) {
350 /* If entry is empty and intermediate forms are nor allowed,
352 /* Change to default value */
353 modest_number_editor_set_value (editor, fixup_value);
354 g_signal_emit (editor, ModestNumberEditor_signal[RANGE_ERROR], 0, error_code, &r);
355 add_select_all_idle (editor);
358 if (priv->is_valid != is_valid) {
359 priv->is_valid = is_valid;
360 g_signal_emit (editor, ModestNumberEditor_signal[VALID_CHANGED], 0, is_valid);
365 modest_number_editor_entry_changed (GtkWidget *widget,
368 g_assert (MODEST_IS_NUMBER_EDITOR (data));
369 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR (data), TRUE);
370 g_object_notify (G_OBJECT (data), "value");
374 modest_number_editor_entry_focusout (GtkWidget *widget,
375 GdkEventFocus *event,
380 g_assert (MODEST_IS_NUMBER_EDITOR(data));
382 window = gtk_widget_get_toplevel (widget);
383 if (window && gtk_window_has_toplevel_focus (GTK_WINDOW (window)))
384 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR(data), FALSE);
390 modest_number_editor_range_error (ModestNumberEditor *editor,
391 ModestNumberEditorErrorType type)
395 gchar *err_msg = NULL;
396 ModestNumberEditorPrivate *priv;
398 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
404 /* Construct error message */
407 case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
408 err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
411 case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
412 err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
415 case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
417 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
421 /* Infoprint error */
424 hildon_banner_show_information (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
425 GTK_TYPE_WINDOW))), NULL, err_msg);
433 * modest_number_editor_new:
434 * @min: minimum accepted value
435 * @max: maximum accepted value
437 * Creates new number editor
439 * Returns: a new #ModestNumberEditor widget
442 modest_number_editor_new (gint min,
445 ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
447 /* Set user inputted range to editor */
448 modest_number_editor_set_range (editor, min, max);
450 return GTK_WIDGET (editor);
454 * modest_number_editor_set_range:
455 * @editor: a #ModestNumberEditor widget
456 * @min: minimum accepted value
457 * @max: maximum accepted value
459 * Sets accepted number range for editor
462 modest_number_editor_set_range (ModestNumberEditor *editor,
466 ModestNumberEditorPrivate *priv;
467 gchar buffer_min[32], buffer_max[32];
470 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
472 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
475 /* Set preferences */
476 priv->start = MIN (min, max);
477 priv->end = MAX (min, max);
479 /* Find maximum allowed length of value */
480 g_snprintf (buffer_min, sizeof (buffer_min), "%d", min);
481 g_snprintf (buffer_max, sizeof (buffer_max), "%d", max);
482 a = strlen (buffer_min);
483 b = strlen (buffer_max);
485 /* Set maximum size of entry */
486 gtk_entry_set_width_chars (GTK_ENTRY (editor), MAX (a, b));
487 modest_number_editor_set_value (editor, priv->start);
491 * modest_number_editor_is_valid:
492 * @editor: pointer to #ModestNumberEditor
494 * Returns: if @editor contents are valid
497 modest_number_editor_is_valid (ModestNumberEditor *editor)
499 ModestNumberEditorPrivate *priv;
501 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
503 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
506 return priv->is_valid;
511 * modest_number_editor_get_value:
512 * @editor: pointer to #ModestNumberEditor
514 * Returns: current NumberEditor value
517 modest_number_editor_get_value (ModestNumberEditor *editor)
519 ModestNumberEditorPrivate *priv;
521 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), 0);
523 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
526 return atoi (gtk_entry_get_text (GTK_ENTRY (editor)));
530 * modest_number_editor_set_value:
531 * @editor: pointer to #ModestNumberEditor
532 * @value: numeric value for number editor
534 * Sets numeric value for number editor
537 modest_number_editor_set_value (ModestNumberEditor *editor,
540 ModestNumberEditorPrivate *priv;
542 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
544 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
547 g_return_if_fail (value <= priv->end);
548 g_return_if_fail (value >= priv->start);
550 priv->default_val = value;
551 modest_number_editor_real_set_value (editor, value);
552 g_object_notify (G_OBJECT(editor), "value");
555 /* When calling gtk_entry_set_text, the entry widget does things that can
556 * cause the whole widget to redraw. This redrawing is delayed and if any
557 * selections are made right after calling the gtk_entry_set_text the
558 * setting of the selection might seem to have no effect.
560 * If the selection is delayed with a lower priority than the redrawing,
561 * the selection should stick. Calling this function with g_idle_add should
565 modest_number_editor_select_all (ModestNumberEditor *editor)
567 ModestNumberEditorPrivate *priv;
569 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
571 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
574 GDK_THREADS_ENTER ();
575 gtk_editable_select_region (GTK_EDITABLE (editor), 0, -1);
576 priv->select_all_idle_id = 0;
577 GDK_THREADS_LEAVE ();
582 modest_number_editor_set_property (GObject *object,
587 ModestNumberEditor *editor;
589 editor = MODEST_NUMBER_EDITOR (object);
594 modest_number_editor_set_value (editor, g_value_get_int (value));
598 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
604 modest_number_editor_get_property (GObject *object,
609 ModestNumberEditor *editor;
611 editor = MODEST_NUMBER_EDITOR (object);
616 g_value_set_int(value, modest_number_editor_get_value (editor));
620 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
626 /* enumerations from "modest-number-editor.h" */
628 modest_number_editor_error_type_get_type (void)
630 static GType etype = 0;
632 static const GEnumValue values[] = {
633 { MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED", "maximum-value-exceed" },
634 { MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED", "minimum-value-exceed" },
635 { MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE, "MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE", "erroneous-value" },
638 etype = g_enum_register_static ("ModestNumberEditorErrorType", values);