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 */
57 guint select_all_idle_id; /* Selection repaint hack
58 see modest_number_editor_select_all */
63 modest_number_editor_class_init (ModestNumberEditorClass *editor_class);
66 modest_number_editor_init (ModestNumberEditor *editor);
69 modest_number_editor_entry_focusout (GtkWidget *widget,
74 modest_number_editor_entry_changed (GtkWidget *widget,
78 modest_number_editor_finalize (GObject *self);
81 modest_number_editor_range_error (ModestNumberEditor *editor,
82 ModestNumberEditorErrorType type);
85 modest_number_editor_select_all (ModestNumberEditor *editor);
88 modest_number_editor_validate_value (ModestNumberEditor *editor,
89 gboolean allow_intermediate);
92 modest_number_editor_set_property (GObject * object,
98 modest_number_editor_get_property (GObject *object,
114 static GtkContainerClass* parent_class;
116 static guint ModestNumberEditor_signal[LAST_SIGNAL] = {0};
119 * modest_number_editor_get_type:
121 * Returns GType for ModestNumberEditor.
123 * Returns: ModestNumberEditor type
126 modest_number_editor_get_type (void)
128 static GType editor_type = 0;
132 static const GTypeInfo editor_info =
134 sizeof (ModestNumberEditorClass),
135 NULL, /* base_init */
136 NULL, /* base_finalize */
137 (GClassInitFunc) modest_number_editor_class_init,
138 NULL, /* class_finalize */
139 NULL, /* class_data */
140 sizeof (ModestNumberEditor),
142 (GInstanceInitFunc) modest_number_editor_init,
144 editor_type = g_type_register_static (HILDON_TYPE_ENTRY,
145 "ModestNumberEditor",
152 modest_number_editor_class_init (ModestNumberEditorClass *editor_class)
154 GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
156 g_type_class_add_private (editor_class,
157 sizeof (ModestNumberEditorPrivate));
159 parent_class = g_type_class_peek_parent (editor_class);
161 editor_class->range_error = modest_number_editor_range_error;
163 gobject_class->finalize = modest_number_editor_finalize;
164 gobject_class->set_property = modest_number_editor_set_property;
165 gobject_class->get_property = modest_number_editor_get_property;
168 * ModestNumberEditor:value:
170 * The current value of the number editor.
172 g_object_class_install_property (gobject_class, PROP_VALUE,
173 g_param_spec_int ("value",
175 "The current value of number editor",
178 0, G_PARAM_READWRITE));
180 ModestNumberEditor_signal[RANGE_ERROR] =
181 g_signal_new ("range_error", MODEST_TYPE_NUMBER_EDITOR,
182 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
183 (ModestNumberEditorClass, range_error),
184 g_signal_accumulator_true_handled, NULL,
185 modest_marshal_BOOLEAN__ENUM,
186 G_TYPE_BOOLEAN, 1, MODEST_TYPE_NUMBER_EDITOR_ERROR_TYPE);
190 modest_number_editor_finalize (GObject *self)
192 ModestNumberEditorPrivate *priv;
194 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (self);
197 if (priv->select_all_idle_id)
198 g_source_remove (priv->select_all_idle_id);
200 /* Call parent class finalize, if have one */
201 if (G_OBJECT_CLASS (parent_class)->finalize)
202 G_OBJECT_CLASS (parent_class)->finalize(self);
206 modest_number_editor_init (ModestNumberEditor *editor)
208 ModestNumberEditorPrivate *priv;
210 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
213 priv->select_all_idle_id = 0;
215 /* Connect child widget signals */
216 g_signal_connect (GTK_OBJECT (editor), "changed",
217 G_CALLBACK (modest_number_editor_entry_changed),
220 g_signal_connect (GTK_OBJECT (editor), "focus-out-event",
221 G_CALLBACK (modest_number_editor_entry_focusout),
225 g_object_set (G_OBJECT (editor),
226 "hildon-input-mode", HILDON_GTK_INPUT_MODE_NUMERIC, NULL);
228 modest_number_editor_set_range (editor, G_MININT, G_MAXINT);
231 /* Format given number to editor field, no checks performed, all signals
232 are sent normally. */
234 modest_number_editor_real_set_value (ModestNumberEditor *editor,
237 /* FIXME: That looks REALLY bad */
240 /* Update text in entry to new value */
241 g_snprintf (buffer, sizeof (buffer), "%d", value);
242 gtk_entry_set_text (GTK_ENTRY (editor), buffer);
246 add_select_all_idle (ModestNumberEditor *editor)
248 ModestNumberEditorPrivate *priv;
250 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
252 if (! priv->select_all_idle_id)
254 priv->select_all_idle_id =
255 g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
260 modest_number_editor_validate_value (ModestNumberEditor *editor,
261 gboolean allow_intermediate)
263 ModestNumberEditorPrivate *priv;
264 gint error_code, fixup_value;
270 g_assert (MODEST_IS_NUMBER_EDITOR(editor));
272 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
275 text = gtk_entry_get_text (GTK_ENTRY (editor));
277 fixup_value = priv->default_val;
281 /* Try to convert entry text to number */
282 value = strtol (text, &tail, 10);
284 /* Check if conversion succeeded */
287 /* Check if value is in allowed range. This is tricky in those
288 cases when user is editing a value.
289 For example: Range = [100, 500] and user have just inputted "4".
290 This should not lead into error message. Otherwise value is
291 resetted back to "100" and next "4" press will reset it back
293 if (allow_intermediate)
295 /* We now have the following error cases:
296 * If inputted value as above maximum and
297 maximum is either positive or then maximum
298 negative and value is positive.
299 * If inputted value is below minimum and minimum
300 is negative or minumum positive and value
302 In all other cases situation can be fixed just by
303 adding new numbers to the string.
305 if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0)))
307 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
308 fixup_value = priv->end;
310 else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0)))
312 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
313 fixup_value = priv->start;
318 if (value > priv->end) {
319 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
320 fixup_value = priv->end;
322 else if (value < priv->start) {
323 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
324 fixup_value = priv->start;
328 /* The only valid case when conversion can fail is when we
329 have plain '-', intermediate forms are allowed AND
330 minimum bound is negative */
331 else if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
332 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
334 else if (! allow_intermediate)
335 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
337 if (error_code != -1)
339 /* If entry is empty and intermediate forms are nor allowed,
341 /* Change to default value */
342 modest_number_editor_set_value (editor, fixup_value);
343 g_signal_emit (editor, ModestNumberEditor_signal[RANGE_ERROR], 0, error_code, &r);
344 add_select_all_idle (editor);
349 modest_number_editor_entry_changed (GtkWidget *widget,
352 g_assert (MODEST_IS_NUMBER_EDITOR (data));
353 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR (data), TRUE);
354 g_object_notify (G_OBJECT (data), "value");
358 modest_number_editor_entry_focusout (GtkWidget *widget,
359 GdkEventFocus *event,
362 g_assert (MODEST_IS_NUMBER_EDITOR(data));
364 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR(data), FALSE);
369 modest_number_editor_range_error (ModestNumberEditor *editor,
370 ModestNumberEditorErrorType type)
374 gchar *err_msg = NULL;
375 ModestNumberEditorPrivate *priv;
377 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
383 /* Construct error message */
386 case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
387 err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
390 case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
391 err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
394 case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
396 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
400 /* Infoprint error */
403 hildon_banner_show_information (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
404 GTK_TYPE_WINDOW))), NULL, err_msg);
412 * modest_number_editor_new:
413 * @min: minimum accepted value
414 * @max: maximum accepted value
416 * Creates new number editor
418 * Returns: a new #ModestNumberEditor widget
421 modest_number_editor_new (gint min,
424 ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
426 /* Set user inputted range to editor */
427 modest_number_editor_set_range (editor, min, max);
429 return GTK_WIDGET (editor);
433 * modest_number_editor_set_range:
434 * @editor: a #ModestNumberEditor widget
435 * @min: minimum accepted value
436 * @max: maximum accepted value
438 * Sets accepted number range for editor
441 modest_number_editor_set_range (ModestNumberEditor *editor,
445 ModestNumberEditorPrivate *priv;
446 gchar buffer_min[32], buffer_max[32];
449 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
451 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
454 /* Set preferences */
455 priv->start = MIN (min, max);
456 priv->end = MAX (min, max);
458 /* Find maximum allowed length of value */
459 g_snprintf (buffer_min, sizeof (buffer_min), "%d", min);
460 g_snprintf (buffer_max, sizeof (buffer_max), "%d", max);
461 a = strlen (buffer_min);
462 b = strlen (buffer_max);
464 /* Set maximum size of entry */
465 gtk_entry_set_width_chars (GTK_ENTRY (editor), MAX (a, b));
466 modest_number_editor_set_value (editor, priv->start);
470 * modest_number_editor_get_value:
471 * @editor: pointer to #ModestNumberEditor
473 * Returns: current NumberEditor value
476 modest_number_editor_get_value (ModestNumberEditor *editor)
478 ModestNumberEditorPrivate *priv;
480 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), 0);
482 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
485 return atoi (gtk_entry_get_text (GTK_ENTRY (editor)));
489 * modest_number_editor_set_value:
490 * @editor: pointer to #ModestNumberEditor
491 * @value: numeric value for number editor
493 * Sets numeric value for number editor
496 modest_number_editor_set_value (ModestNumberEditor *editor,
499 ModestNumberEditorPrivate *priv;
501 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
503 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
506 g_return_if_fail (value <= priv->end);
507 g_return_if_fail (value >= priv->start);
509 priv->default_val = value;
510 modest_number_editor_real_set_value (editor, value);
511 g_object_notify (G_OBJECT(editor), "value");
514 /* When calling gtk_entry_set_text, the entry widget does things that can
515 * cause the whole widget to redraw. This redrawing is delayed and if any
516 * selections are made right after calling the gtk_entry_set_text the
517 * setting of the selection might seem to have no effect.
519 * If the selection is delayed with a lower priority than the redrawing,
520 * the selection should stick. Calling this function with g_idle_add should
524 modest_number_editor_select_all (ModestNumberEditor *editor)
526 ModestNumberEditorPrivate *priv;
528 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
530 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
533 GDK_THREADS_ENTER ();
534 gtk_editable_select_region (GTK_EDITABLE (editor), 0, -1);
535 priv->select_all_idle_id = 0;
536 GDK_THREADS_LEAVE ();
541 modest_number_editor_set_property (GObject *object,
546 ModestNumberEditor *editor;
548 editor = MODEST_NUMBER_EDITOR (object);
553 modest_number_editor_set_value (editor, g_value_get_int (value));
557 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
563 modest_number_editor_get_property (GObject *object,
568 ModestNumberEditor *editor;
570 editor = MODEST_NUMBER_EDITOR (object);
575 g_value_set_int(value, modest_number_editor_get_value (editor));
579 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
585 /* enumerations from "modest-number-editor.h" */
587 modest_number_editor_error_type_get_type (void)
589 static GType etype = 0;
591 static const GEnumValue values[] = {
592 { MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED", "maximum-value-exceed" },
593 { MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED", "minimum-value-exceed" },
594 { MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE, "MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE", "erroneous-value" },
597 etype = g_enum_register_static ("ModestNumberEditorErrorType", values);