2 * This file is part of hildon-libs
4 * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
6 * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; version 2.1 of
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * SECTION:hildon-date-editor
27 * @short_description: A widget which queries a date from user or opens
28 * a HildonCalendarPopup
29 * @see_also: #HildonCalendarPopup, #HildonTimeEditor
31 * HildonDateEditor is a widget with three entry fields (day, month,
32 * year) and an icon (button): clicking on the icon opens up a
33 * HildonCalendarPopup.
38 #include <gdk/gdkkeysyms.h>
44 #include <hildon-widgets/hildon-date-editor.h>
45 #include <hildon-widgets/hildon-calendar-popup.h>
46 #include <hildon-widgets/gtk-infoprint.h>
47 #include <hildon-widgets/hildon-defines.h>
48 #include <hildon-widgets/hildon-input-mode-hint.h>
49 #include "hildon-composite-widget.h"
50 #include "hildon-marshalers.h"
51 #include "hildon-enum-types.h"
52 #include "hildon-time-editor.h"
59 #define _(string) dgettext(PACKAGE, string)
61 #define ENTRY_BORDERS 11
62 #define DATE_EDITOR_HEIGHT 30
64 #define DAY_ENTRY_WIDTH 2
65 #define MONTH_ENTRY_WIDTH 2
66 #define YEAR_ENTRY_WIDTH 4
68 #define DEFAULT_MIN_YEAR 1970
69 #define DEFAULT_MAX_YEAR 2037
71 #define HILDON_DATE_EDITOR_GET_PRIVATE(obj) \
72 (G_TYPE_INSTANCE_GET_PRIVATE((obj),\
73 HILDON_TYPE_DATE_EDITOR, HildonDateEditorPrivate));
75 static GtkContainerClass *parent_class;
77 typedef struct _HildonDateEditorPrivate HildonDateEditorPrivate;
80 hildon_date_editor_class_init(HildonDateEditorClass * editor_class);
82 static void hildon_date_editor_init(HildonDateEditor * editor);
85 hildon_date_editor_icon_press(GtkWidget * widget,
89 hildon_date_editor_released(GtkWidget * widget,
93 hildon_date_editor_keypress(GtkWidget * widget, GdkEventKey * event,
97 hildon_date_editor_keyrelease(GtkWidget * widget, GdkEventKey * event,
100 hildon_date_editor_clicked(GtkWidget * widget, gpointer data);
102 hildon_date_editor_entry_validate(GtkWidget *widget, gpointer data);
105 hildon_date_editor_entry_changed(GtkEditable *widget, gpointer data);
108 hildon_date_editor_entry_focus_out(GtkWidget * widget, GdkEventFocus * event,
111 static gboolean hildon_date_editor_date_error(HildonDateEditor *editor,
112 HildonDateTimeEditorError type);
114 static gboolean hildon_date_editor_entry_focusin(GtkWidget * widget,
115 GdkEventFocus * event,
117 static void hildon_date_editor_get_property( GObject *object, guint param_id,
118 GValue *value, GParamSpec *pspec );
119 static void hildon_date_editor_set_property (GObject *object, guint param_id,
120 const GValue *value, GParamSpec *pspec);
122 hildon_child_forall(GtkContainer * container,
123 gboolean include_internals,
124 GtkCallback callback, gpointer callback_data);
126 static void hildon_date_editor_destroy(GtkObject * self);
129 hildon_date_editor_size_allocate(GtkWidget * widget,
130 GtkAllocation * allocation);
133 hildon_date_editor_size_request(GtkWidget * widget,
134 GtkRequisition * requisition);
137 _hildon_date_editor_entry_select_all(GtkWidget *widget);
139 /* Property indices */
149 struct _HildonDateEditorPrivate {
150 /* Cache of values in the entries, used in setting only parts of the date */
151 guint year; /* current year in the entry */
152 guint month; /* current month in the entry */
153 guint day; /* current day in the entry */
155 gboolean calendar_icon_pressed;
157 GtkWidget *frame; /* borders around the date */
158 GtkWidget *d_button_image; /* icon */
159 GtkWidget *d_box_date; /* hbox for date */
161 GtkWidget *d_entry; /* GtkEntry for day */
162 GtkWidget *m_entry; /* GtkEntry for month */
163 GtkWidget *y_entry; /* GtkEntry for year */
165 GList *delims; /* List of delimeters between the fields (and possible at the ends) */
166 GtkWidget *calendar_icon;
168 gboolean skip_validation; /* don't validate date at all */
170 gint min_year; /* minimum year allowed */
171 gint max_year; /* maximum year allowed */
179 static guint date_editor_signals[LAST_SIGNAL] = { 0 };
181 GType hildon_date_editor_get_type(void)
183 static GType editor_type = 0;
186 static const GTypeInfo editor_info = {
187 sizeof(HildonDateEditorClass),
188 NULL, /* base_init */
189 NULL, /* base_finalize */
190 (GClassInitFunc) hildon_date_editor_class_init,
191 NULL, /* class_finalize */
192 NULL, /* class_data */
193 sizeof(HildonDateEditor),
195 (GInstanceInitFunc) hildon_date_editor_init,
197 editor_type = g_type_register_static(GTK_TYPE_CONTAINER,
205 hildon_date_editor_class_init(HildonDateEditorClass * editor_class)
207 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(editor_class);
208 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(editor_class);
209 GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
211 parent_class = g_type_class_peek_parent(editor_class);
213 g_type_class_add_private(editor_class,
214 sizeof(HildonDateEditorPrivate));
216 gobject_class->set_property = hildon_date_editor_set_property;
217 gobject_class->get_property = hildon_date_editor_get_property;
218 widget_class->size_request = hildon_date_editor_size_request;
219 widget_class->size_allocate = hildon_date_editor_size_allocate;
220 widget_class->focus = hildon_composite_widget_focus;
222 container_class->forall = hildon_child_forall;
223 GTK_OBJECT_CLASS(editor_class)->destroy = hildon_date_editor_destroy;
225 editor_class->date_error = hildon_date_editor_date_error;
227 date_editor_signals[DATE_ERROR] =
228 g_signal_new("date-error",
229 G_OBJECT_CLASS_TYPE(gobject_class),
231 G_STRUCT_OFFSET(HildonDateEditorClass, date_error),
232 g_signal_accumulator_true_handled, NULL,
233 _hildon_marshal_BOOLEAN__ENUM,
234 G_TYPE_BOOLEAN, 1, HILDON_TYPE_DATE_TIME_EDITOR_ERROR);
237 * HildonDateEditor:year:
241 g_object_class_install_property( gobject_class, PROP_YEAR,
242 g_param_spec_uint("year",
247 G_PARAM_READABLE | G_PARAM_WRITABLE) );
250 * HildonDateEditor:month:
254 g_object_class_install_property( gobject_class, PROP_MONTH,
255 g_param_spec_uint("month",
260 G_PARAM_READABLE | G_PARAM_WRITABLE) );
263 * HildonDateEditor:day:
267 g_object_class_install_property( gobject_class, PROP_DAY,
268 g_param_spec_uint("day",
273 G_PARAM_READABLE | G_PARAM_WRITABLE) );
276 * HildonDateEditor:min-year:
278 * Minimum valid year.
280 g_object_class_install_property( gobject_class, PROP_MIN_YEAR,
281 g_param_spec_uint("min-year",
282 "Minimum valid year",
283 "Minimum valid year",
286 G_PARAM_READWRITE) );
289 * HildonDateEditor:max-year:
291 * Maximum valid year.
293 g_object_class_install_property( gobject_class, PROP_MAX_YEAR,
294 g_param_spec_uint("max-year",
295 "Maximum valid year",
296 "Maximum valid year",
299 G_PARAM_READWRITE) );
302 /* Forces setting of the icon to certain state. Used initially
303 and from the actual setter function */
305 real_set_calendar_icon_state(HildonDateEditorPrivate *priv,
308 gtk_image_set_from_icon_name(GTK_IMAGE(priv->calendar_icon),
309 pressed ? "qgn_widg_datedit_pr" : "qgn_widg_datedit",
310 HILDON_ICON_SIZE_WIDG);
312 priv->calendar_icon_pressed = pressed;
315 /* Sets the icon to given state (normal/pressed). Returns
316 info if the state actually changed. */
318 hildon_date_editor_set_calendar_icon_state(HildonDateEditor *editor,
321 HildonDateEditorPrivate *priv;
323 g_assert(HILDON_IS_DATE_EDITOR(editor));
325 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
326 if (pressed != priv->calendar_icon_pressed) {
327 real_set_calendar_icon_state(priv, pressed);
334 /* Packing day, month and year entries depend on locale settings
335 We find out the order and all separators by converting a known
336 date to default format and inspecting the result string */
337 static void apply_locale_field_order(HildonDateEditorPrivate *priv)
339 GDate locale_test_date;
342 gchar *iter, *delim_text;
344 g_date_set_dmy(&locale_test_date, 1, 2, 1970);
345 (void) g_date_strftime(buffer, sizeof(buffer), "%x", &locale_test_date);
353 /* Try to convert the current location into number. */
354 value = strtoul(iter, &endp, 10);
356 /* If the conversion didn't progress or the detected value was
357 unknown (we used a fixed date, you remember), we treat
358 current position as a literal */
362 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
363 priv->d_entry, FALSE, FALSE, 0);
366 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
367 priv->m_entry, FALSE, FALSE, 0);
369 case 70: /* %x format uses only 2 numbers for some locales */
371 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
372 priv->y_entry, FALSE, FALSE, 0);
375 /* All non-number characters starting from current position
376 form the delimeter */
377 for (endp = iter; *endp; endp++)
378 if (g_ascii_isdigit(*endp))
381 /* Now endp points one place past the delimeter text */
382 delim_text = g_strndup(iter, endp - iter);
383 delim = gtk_label_new(delim_text);
384 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
385 delim, FALSE, FALSE, 0);
386 priv->delims = g_list_append(priv->delims, delim);
396 static void hildon_date_editor_init(HildonDateEditor * editor)
398 HildonDateEditorPrivate *priv;
401 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
403 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(editor), GTK_NO_WINDOW);
405 gtk_widget_push_composite_child();
407 /* initialize values */
408 g_date_clear(&cur_date, 1);
409 g_date_set_time(&cur_date, time(NULL));
411 priv->day = g_date_get_day(&cur_date);
412 priv->month = g_date_get_month(&cur_date);
413 priv->year = g_date_get_year(&cur_date);
414 priv->min_year = DEFAULT_MIN_YEAR;
415 priv->max_year = DEFAULT_MAX_YEAR;
418 priv->frame = gtk_frame_new(NULL);
419 gtk_container_set_border_width(GTK_CONTAINER(priv->frame), 0);
421 priv->d_entry = gtk_entry_new();
422 priv->m_entry = gtk_entry_new();
423 priv->y_entry = gtk_entry_new();
425 g_object_set (G_OBJECT(priv->d_entry), "input-mode",
426 HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
427 g_object_set (G_OBJECT(priv->m_entry), "input-mode",
428 HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
429 g_object_set (G_OBJECT(priv->y_entry), "input-mode",
430 HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
433 gtk_entry_set_width_chars(GTK_ENTRY(priv->d_entry), DAY_ENTRY_WIDTH);
434 gtk_entry_set_width_chars(GTK_ENTRY(priv->m_entry), MONTH_ENTRY_WIDTH);
435 gtk_entry_set_width_chars(GTK_ENTRY(priv->y_entry), YEAR_ENTRY_WIDTH);
437 gtk_entry_set_max_length(GTK_ENTRY(priv->d_entry), DAY_ENTRY_WIDTH);
438 gtk_entry_set_max_length(GTK_ENTRY(priv->m_entry), MONTH_ENTRY_WIDTH);
439 gtk_entry_set_max_length(GTK_ENTRY(priv->y_entry), YEAR_ENTRY_WIDTH);
441 gtk_entry_set_has_frame(GTK_ENTRY(priv->d_entry), FALSE);
442 gtk_entry_set_has_frame(GTK_ENTRY(priv->m_entry), FALSE);
443 gtk_entry_set_has_frame(GTK_ENTRY(priv->y_entry), FALSE);
445 gtk_widget_set_composite_name(priv->d_entry, "day_entry");
446 gtk_widget_set_composite_name(priv->m_entry, "month_entry");
447 gtk_widget_set_composite_name(priv->y_entry, "year_entry");
449 priv->d_box_date = gtk_hbox_new(FALSE, 0);
451 priv->d_button_image = gtk_button_new();
452 priv->calendar_icon = gtk_image_new();
453 real_set_calendar_icon_state(priv, FALSE);
454 GTK_WIDGET_UNSET_FLAGS(priv->d_button_image, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
456 apply_locale_field_order(priv);
458 gtk_container_add(GTK_CONTAINER(priv->frame), priv->d_box_date);
459 gtk_container_add(GTK_CONTAINER(priv->d_button_image), priv->calendar_icon);
460 gtk_button_set_relief(GTK_BUTTON(priv->d_button_image), GTK_RELIEF_NONE);
461 gtk_button_set_focus_on_click(GTK_BUTTON(priv->d_button_image), FALSE);
463 gtk_widget_set_parent(priv->frame, GTK_WIDGET(editor));
464 gtk_widget_set_parent(priv->d_button_image, GTK_WIDGET(editor));
465 gtk_widget_show_all(priv->frame);
466 gtk_widget_show_all(priv->d_button_image);
468 /* image button signal connects */
469 g_signal_connect(GTK_OBJECT(priv->d_button_image), "pressed",
470 G_CALLBACK(hildon_date_editor_icon_press), editor);
471 g_signal_connect(GTK_OBJECT(priv->d_button_image), "released",
472 G_CALLBACK(hildon_date_editor_released), editor);
473 g_signal_connect(GTK_OBJECT(priv->d_button_image), "clicked",
474 G_CALLBACK(hildon_date_editor_clicked), editor);
475 g_signal_connect(GTK_OBJECT(priv->d_button_image), "key_press_event",
476 G_CALLBACK(hildon_date_editor_keypress), editor);
478 /* entry signal connects */
479 g_signal_connect(GTK_OBJECT(priv->d_entry), "focus-in-event",
480 G_CALLBACK(hildon_date_editor_entry_focusin), editor);
482 g_signal_connect(GTK_OBJECT(priv->m_entry), "focus-in-event",
483 G_CALLBACK(hildon_date_editor_entry_focusin), editor);
485 g_signal_connect(GTK_OBJECT(priv->y_entry), "focus-in-event",
486 G_CALLBACK(hildon_date_editor_entry_focusin), editor);
488 g_signal_connect(GTK_OBJECT(priv->d_entry), "focus-out-event",
489 G_CALLBACK(hildon_date_editor_entry_focus_out), editor);
491 g_signal_connect(GTK_OBJECT(priv->m_entry), "focus-out-event",
492 G_CALLBACK(hildon_date_editor_entry_focus_out), editor);
494 g_signal_connect(GTK_OBJECT(priv->y_entry), "focus-out-event",
495 G_CALLBACK(hildon_date_editor_entry_focus_out), editor);
497 g_signal_connect(GTK_OBJECT(priv->d_entry), "key-press-event",
498 G_CALLBACK(hildon_date_editor_keypress), editor);
500 g_signal_connect(GTK_OBJECT(priv->m_entry), "key-press-event",
501 G_CALLBACK(hildon_date_editor_keypress), editor);
503 g_signal_connect(GTK_OBJECT(priv->y_entry), "key-press-event",
504 G_CALLBACK(hildon_date_editor_keypress), editor);
506 g_signal_connect(GTK_OBJECT(priv->d_entry), "key-release-event",
507 G_CALLBACK(hildon_date_editor_keyrelease), editor);
509 g_signal_connect(GTK_OBJECT(priv->m_entry), "key-release-event",
510 G_CALLBACK(hildon_date_editor_keyrelease), editor);
512 g_signal_connect(GTK_OBJECT(priv->y_entry), "key-release-event",
513 G_CALLBACK(hildon_date_editor_keyrelease), editor);
515 hildon_date_editor_set_date(editor, priv->year, priv->month, priv->day);
517 g_signal_connect(GTK_OBJECT(priv->d_entry), "changed",
518 G_CALLBACK(hildon_date_editor_entry_changed), editor);
520 g_signal_connect(GTK_OBJECT(priv->m_entry), "changed",
521 G_CALLBACK(hildon_date_editor_entry_changed), editor);
523 g_signal_connect(GTK_OBJECT(priv->y_entry), "changed",
524 G_CALLBACK(hildon_date_editor_entry_changed), editor);
526 gtk_widget_pop_composite_child();
529 static void hildon_date_editor_set_property (GObject *object, guint param_id,
530 const GValue *value, GParamSpec *pspec)
532 HildonDateEditor *editor = HILDON_DATE_EDITOR(object);
533 HildonDateEditorPrivate *priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
539 hildon_date_editor_set_year (editor, g_value_get_uint(value));
543 hildon_date_editor_set_month (editor, g_value_get_uint(value));
547 hildon_date_editor_set_day (editor, g_value_get_uint(value));
551 val = g_value_get_uint(value);
552 if (val <= priv->max_year)
554 priv->min_year = val;
555 /* Clamp current year */
556 if (hildon_date_editor_get_year (editor) < priv->min_year)
557 hildon_date_editor_set_year (editor, priv->min_year);
560 g_warning("min-year cannot be greater than max-year");
564 val = g_value_get_uint(value);
565 if (val >= priv->min_year)
567 priv->max_year = val;
568 /* Clamp current year */
569 if (hildon_date_editor_get_year (editor) > priv->max_year)
570 hildon_date_editor_set_year (editor, priv->max_year);
573 g_warning("max-year cannot be less than min-year");
577 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
582 static void hildon_date_editor_get_property( GObject *object, guint param_id,
583 GValue *value, GParamSpec *pspec )
585 HildonDateEditor *editor = HILDON_DATE_EDITOR(object);
586 HildonDateEditorPrivate *priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
591 g_value_set_uint (value, hildon_date_editor_get_year (editor));
595 g_value_set_uint (value, hildon_date_editor_get_month (editor));
599 g_value_set_uint (value, hildon_date_editor_get_day (editor));
603 g_value_set_uint (value, priv->min_year);
607 g_value_set_uint (value, priv->max_year);
611 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
616 static void hildon_child_forall(GtkContainer * container,
617 gboolean include_internals,
618 GtkCallback callback,
619 gpointer callback_data)
621 HildonDateEditor *editor;
622 HildonDateEditorPrivate *priv;
624 g_assert(HILDON_IS_DATE_EDITOR(container));
627 editor = HILDON_DATE_EDITOR(container);
628 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
630 if (include_internals) {
631 (*callback) (priv->frame, callback_data);
632 (*callback) (priv->d_button_image, callback_data);
636 static void hildon_date_editor_destroy(GtkObject * self)
638 HildonDateEditorPrivate *priv;
640 priv = HILDON_DATE_EDITOR_GET_PRIVATE(self);
643 gtk_widget_unparent(priv->frame);
646 if (priv->d_button_image) {
647 gtk_widget_unparent(priv->d_button_image);
648 priv->d_button_image = NULL;
651 g_list_free(priv->delims);
655 if (GTK_OBJECT_CLASS(parent_class)->destroy)
656 GTK_OBJECT_CLASS(parent_class)->destroy(self);
660 * hildon_date_editor_new:
662 * Creates a new date editor. The current system date
663 * is shown in the editor.
665 * Returns: pointer to a new @HildonDateEditor widget.
667 GtkWidget *hildon_date_editor_new(void)
669 return GTK_WIDGET(g_object_new(HILDON_TYPE_DATE_EDITOR, NULL));
673 * hildon_date_editor_set_date:
674 * @date: the @HildonDateEditor widget
679 * Sets the date shown in the editor.
681 void hildon_date_editor_set_date(HildonDateEditor * editor,
682 guint year, guint month, guint day)
684 HildonDateEditorPrivate *priv;
686 g_return_if_fail(HILDON_IS_DATE_EDITOR(editor));
688 /* This function cannot be implemented by calling
689 component setters, since applying the individual
690 values one by one can make the date temporarily
691 invalid (depending on what the previous values were),
692 which in turn causes that the desired date
693 is not set (even though it's valid). We must set all the
694 components at one go and not try to do any validation etc
697 g_return_if_fail(HILDON_IS_DATE_EDITOR(editor));
698 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
700 if (g_date_valid_dmy(day, month, year))
709 g_date_set_dmy(&date, day, month, year);
711 /* We apply the new values, but do not want automatic focus move
713 g_snprintf(buffer, sizeof(buffer), "%04d", year);
714 g_signal_handlers_block_by_func(priv->y_entry,
715 (gpointer) hildon_date_editor_entry_changed, editor);
716 gtk_entry_set_text(GTK_ENTRY(priv->y_entry), buffer);
717 g_signal_handlers_unblock_by_func(priv->y_entry,
718 (gpointer) hildon_date_editor_entry_changed, editor);
720 g_date_strftime(buffer, sizeof(buffer), "%m", &date);
721 g_signal_handlers_block_by_func(priv->m_entry,
722 (gpointer) hildon_date_editor_entry_changed, editor);
723 gtk_entry_set_text(GTK_ENTRY(priv->m_entry), buffer);
724 g_signal_handlers_unblock_by_func(priv->m_entry,
725 (gpointer) hildon_date_editor_entry_changed, editor);
727 g_date_strftime(buffer, sizeof(buffer), "%d", &date);
728 g_signal_handlers_block_by_func(priv->d_entry,
729 (gpointer) hildon_date_editor_entry_changed, editor);
730 gtk_entry_set_text(GTK_ENTRY(priv->d_entry), buffer);
731 g_signal_handlers_unblock_by_func(priv->d_entry,
732 (gpointer) hildon_date_editor_entry_changed, editor);
734 g_object_notify(G_OBJECT(editor), "year");
735 g_object_notify(G_OBJECT(editor), "month");
736 g_object_notify(G_OBJECT(editor), "day");
741 * hildon_date_editor_get_date:
742 * @date: the @HildonDateEditor widget
747 * Returns: the year, month, and day currently on the
750 void hildon_date_editor_get_date(HildonDateEditor * date,
751 guint * year, guint * month, guint * day)
753 HildonDateEditorPrivate *priv;
755 g_return_if_fail(HILDON_IS_DATE_EDITOR(date));
756 g_return_if_fail(year);
757 g_return_if_fail(month);
758 g_return_if_fail(day);
760 priv = HILDON_DATE_EDITOR_GET_PRIVATE(date);
762 /* FIXME: The role of priv->{day,month,year} members vs. entry contents
763 is unclear. They do not neccesarily match and still the texts are
764 used as return values and members for some internal validation!!
765 At least a partly reason is to allow empty text to become
766 0 return value, while members are restricted to valid ranges?!
767 However, if we change the current way, we are likely to break
768 some applications if they rely on some specific way how this
769 widget currently handles empty values and temporarily invalid values.
771 The key issue is this: What should the _get methods return while
772 user is editing a field and the result is incomplete. The
773 partial result? The last good result? If we return partial result
774 we also need a way to inform if the date is not valid. Current
775 implementation is some kind of hybrid of these two...
778 hildon_date_editor_set_day(editor, hildon_date_editor_get_day(editor));
780 easily fails, since set_day tries to force validity while get_day
783 Proposal: Always return the same values that are shown in the
784 fields. We add a separate flag (Or use GDate) to
785 indicate if the current date is valid. This would allow
786 setters to make the date invalid as well.
788 *year = /*priv->year;*/
789 (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->y_entry)));
790 *month = /*priv->month;*/
791 (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->m_entry)));
792 *day = /*priv->day;*/
793 (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->d_entry)));
796 /* icon button press event */
797 static gboolean hildon_date_editor_icon_press(GtkWidget * widget,
800 g_assert(GTK_IS_WIDGET(widget));
801 g_assert(HILDON_IS_DATE_EDITOR(data));
803 hildon_date_editor_set_calendar_icon_state(HILDON_DATE_EDITOR(data), TRUE);
808 static gboolean hildon_date_editor_entry_focusin(GtkWidget * widget,
809 GdkEventFocus * event,
812 g_idle_add((GSourceFunc)
813 _hildon_date_editor_entry_select_all, GTK_ENTRY(widget));
819 static void popup_calendar_dialog(HildonDateEditor *ed)
821 guint y = 0, m = 0, d = 0;
827 hildon_date_editor_get_date(ed, &y, &m, &d);
829 parent = gtk_widget_get_ancestor(GTK_WIDGET(ed), GTK_TYPE_WINDOW);
830 popup = hildon_calendar_popup_new(GTK_WINDOW(parent), y, m, d);
832 g_value_init(&val, G_TYPE_INT);
833 /* Set max/min year in calendar popup to date editor values */
834 g_object_get_property(G_OBJECT(ed), "min-year", &val);
835 g_object_set_property(G_OBJECT(popup), "min-year", &val);
836 g_object_get_property(G_OBJECT(ed), "max-year", &val);
837 g_object_set_property(G_OBJECT(popup), "max-year", &val);
839 /* Pop up calendar */
840 result = gtk_dialog_run(GTK_DIALOG(popup));
842 case GTK_RESPONSE_OK:
843 case GTK_RESPONSE_ACCEPT:
844 hildon_calendar_popup_get_date(HILDON_CALENDAR_POPUP(popup), &y,
846 hildon_date_editor_set_date(ed, y, m, d);
849 gtk_widget_destroy(popup);
852 /* button released */
853 static gboolean hildon_date_editor_released(GtkWidget * widget,
856 HildonDateEditor *ed;
858 g_assert(GTK_IS_WIDGET(widget));
859 g_assert(HILDON_IS_DATE_EDITOR(data));
861 ed = HILDON_DATE_EDITOR(data);
863 /* restores the icon state. The clicked cycle raises the dialog */
864 hildon_date_editor_set_calendar_icon_state(ed, FALSE);
869 /* button released */
870 static gboolean hildon_date_editor_clicked(GtkWidget * widget,
873 HildonDateEditor *ed;
875 g_assert(GTK_IS_WIDGET(widget));
876 g_assert(HILDON_IS_DATE_EDITOR(data));
878 ed = HILDON_DATE_EDITOR(data);
880 /* restores the non-clicked button state and raises the dialog */
881 hildon_date_editor_set_calendar_icon_state(ed, FALSE);
882 popup_calendar_dialog(ed);
887 /* This is called whenever some editor filed loses the focus and
888 when the all of the fields are filled.
889 Earlier this was called whenever an entry changed */
890 /* FIXME: Validation on focus_out is broken by concept */
892 hildon_date_editor_entry_validate(GtkWidget *widget, gpointer data)
894 HildonDateEditor *ed;
895 HildonDateEditorPrivate *priv;
896 gint d, m, y, max_days;
897 gboolean r; /* temp return values for signals */
899 gint error_code = NO_ERROR;
901 g_assert(HILDON_IS_DATE_EDITOR(data));
902 g_assert(GTK_IS_ENTRY(widget));
904 ed = HILDON_DATE_EDITOR(data);
905 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
907 if (priv->skip_validation)
910 /*check if the calling entry is empty*/
911 text = gtk_entry_get_text(GTK_ENTRY(widget));
912 if(text == NULL || text[0] == 0)
914 if (widget == priv->d_entry)
915 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, HILDON_DATE_TIME_ERROR_EMPTY_DAY, &r);
916 else if(widget == priv->m_entry)
917 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, HILDON_DATE_TIME_ERROR_EMPTY_MONTH, &r);
919 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, HILDON_DATE_TIME_ERROR_EMPTY_YEAR, &r);
921 /* restore empty entry to safe value */
922 hildon_date_editor_set_date (ed, priv->year, priv->month, priv->day);
926 /* Ok, we now check validity. Some fields can be empty */
927 text = gtk_entry_get_text(GTK_ENTRY(priv->d_entry));
928 if (text == NULL || text[0] == 0) return error_code;
930 text = gtk_entry_get_text(GTK_ENTRY(priv->m_entry));
931 if (text == NULL || text[0] == 0) return error_code;
933 text = gtk_entry_get_text(GTK_ENTRY(priv->y_entry));
934 if (text == NULL || text[0] == 0) return error_code;
937 /* Did it actually change? */
938 if (d != priv->day || m != priv->month || y != priv->year)
940 /* We could/should use hildon_date_editor_set_year and such functions
941 * to set the date, instead of use gtk_entry_set_text, and then change
942 * the priv member but hildon_date_editor_set_year and such functions
943 * check if the date is valid, we do want to do date validation check
944 * here according to spec */
947 if(widget == priv->m_entry) {
949 error_code = HILDON_DATE_TIME_ERROR_MIN_MONTH;
953 error_code = HILDON_DATE_TIME_ERROR_MAX_MONTH;
959 if(widget == priv->y_entry) {
960 if (y < priv->min_year) {
961 error_code = HILDON_DATE_TIME_ERROR_MIN_YEAR;
964 else if (y > priv->max_year) {
965 error_code = HILDON_DATE_TIME_ERROR_MAX_YEAR;
970 /* Validate day. We have to do this in every case, since
971 changing month or year can make the day number to be invalid */
972 max_days = g_date_get_days_in_month(m,y);
974 error_code = HILDON_DATE_TIME_ERROR_MIN_DAY;
977 else if (d > max_days) {
979 error_code = HILDON_DATE_TIME_ERROR_MAX_DAY;
982 else { /* the date does not exist (is invalid) */
983 error_code = HILDON_DATE_TIME_ERROR_INVALID_DATE;
984 /* check what was changed and restore previous value */
985 if ( widget == priv->y_entry )
987 else if ( widget == priv->m_entry )
994 if (error_code != NO_ERROR)
996 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, error_code, &r);
998 g_idle_add ((GSourceFunc)
999 _hildon_date_editor_entry_select_all,
1004 /* Fix and reformat the date after error signal is processed.
1005 reformatting can be needed even in a such case that numerical
1006 values of the date components are the same as earlier. */
1007 hildon_date_editor_set_date(ed, y, m, d);
1011 /* When entry becomes full, we move the focus to the next field.
1012 If we are on the last field, the whole contents are validated. */
1014 hildon_date_editor_entry_changed(GtkEditable *ed, gpointer data)
1018 HildonDateEditorPrivate *priv;
1020 g_assert(GTK_IS_ENTRY(ed));
1021 g_assert(HILDON_IS_DATE_EDITOR(data));
1023 entry = GTK_ENTRY(ed);
1025 /* If day entry is full, move to next entry or validate */
1026 if (g_utf8_strlen(gtk_entry_get_text(entry), -1) == gtk_entry_get_max_length(entry))
1028 error_code = hildon_date_editor_entry_validate(GTK_WIDGET(entry), data);
1029 if (error_code == NO_ERROR)
1031 priv = HILDON_DATE_EDITOR_GET_PRIVATE(HILDON_DATE_EDITOR(data));
1032 priv->skip_validation = TRUE;
1033 gtk_widget_child_focus(GTK_WIDGET(data), GTK_DIR_RIGHT);
1038 static gboolean hildon_date_editor_keyrelease(GtkWidget * widget,
1039 GdkEventKey * event,
1042 HildonDateEditor *ed;
1043 HildonDateEditorPrivate *priv;
1045 g_return_val_if_fail(data, FALSE);
1046 g_return_val_if_fail(widget, FALSE);
1048 ed = HILDON_DATE_EDITOR(data);
1049 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1051 if (event->keyval == GDK_KP_Enter || event->keyval == GDK_Return ||
1052 event->keyval == GDK_ISO_Enter) {
1053 if (hildon_date_editor_set_calendar_icon_state(ed, FALSE))
1055 popup_calendar_dialog(ed);
1058 } else if (event->keyval == GDK_Escape)
1059 priv->skip_validation = FALSE;
1064 /* keyboard handling */
1065 static gboolean hildon_date_editor_keypress(GtkWidget * widget,
1066 GdkEventKey * event,
1069 HildonDateEditor *ed;
1070 HildonDateEditorPrivate *priv;
1074 g_assert(HILDON_IS_DATE_EDITOR(data));
1075 g_assert(GTK_IS_ENTRY(widget));
1077 ed = HILDON_DATE_EDITOR(data);
1078 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1079 pos = gtk_editable_get_position(GTK_EDITABLE(widget));
1081 /* Show error message in case the key pressed is not allowed
1082 (only digits and control characters are allowed )*/
1083 if (!g_unichar_isdigit(event->keyval) && !(event->keyval & 0xF000)) {
1084 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, HILDON_DATE_TIME_ERROR_INVALID_CHAR, &r);
1088 switch (event->keyval) {
1091 (void) gtk_widget_child_focus(GTK_WIDGET(data), GTK_DIR_LEFT);
1096 if (pos >= g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(widget)), -1)) {
1097 (void) gtk_widget_child_focus(GTK_WIDGET(data), GTK_DIR_RIGHT);
1103 /* Ignore return value, since we want to handle event at all times.
1104 otherwise vkb would popup when the keyrepeat starts. */
1105 (void) hildon_date_editor_set_calendar_icon_state(ed, TRUE);
1109 priv->skip_validation = TRUE;
1118 static gboolean hildon_date_editor_entry_focus_out(GtkWidget * widget,
1119 GdkEventFocus * event,
1122 HildonDateEditor *ed;
1123 HildonDateEditorPrivate *priv;
1125 g_assert(HILDON_IS_DATE_EDITOR(data));
1127 ed = HILDON_DATE_EDITOR(data);
1128 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1130 hildon_date_editor_entry_validate(widget, data);
1131 priv->skip_validation = FALSE;
1137 hildon_date_editor_date_error(HildonDateEditor *editor,
1138 HildonDateTimeEditorError type)
1140 HildonDateEditorPrivate *priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1144 case HILDON_DATE_TIME_ERROR_MAX_DAY:
1145 gtk_infoprintf(NULL, _("ckct_ib_maximum_value"), 31);
1147 case HILDON_DATE_TIME_ERROR_MAX_MONTH:
1148 gtk_infoprintf(NULL, _("ckct_ib_maximum_value"), 12);
1150 case HILDON_DATE_TIME_ERROR_MAX_YEAR:
1151 gtk_infoprintf(NULL, _("ckct_ib_maximum_value"), priv->max_year);
1153 case HILDON_DATE_TIME_ERROR_MIN_DAY:
1154 case HILDON_DATE_TIME_ERROR_MIN_MONTH:
1155 gtk_infoprintf(NULL, _("ckct_ib_minimum_value"), 1);
1157 case HILDON_DATE_TIME_ERROR_MIN_YEAR:
1158 gtk_infoprintf(NULL, _("ckct_ib_minimum_value"), priv->min_year);
1160 case HILDON_DATE_TIME_ERROR_EMPTY_DAY:
1161 gtk_infoprintf(NULL, _("ckct_ib_set_a_value_within_range"), 1, 31);
1163 case HILDON_DATE_TIME_ERROR_EMPTY_MONTH:
1164 gtk_infoprintf(NULL, _("ckct_ib_set_a_value_within_range"), 1, 12);
1166 case HILDON_DATE_TIME_ERROR_EMPTY_YEAR:
1167 gtk_infoprintf(NULL, _("ckct_ib_set_a_value_within_range"),
1168 priv->min_year, priv->max_year);
1170 case HILDON_DATE_TIME_ERROR_INVALID_CHAR:
1171 gtk_infoprint(NULL, _("ckct_ib_illegal_character"));
1173 case HILDON_DATE_TIME_ERROR_INVALID_DATE:
1174 gtk_infoprint(NULL, _("ckct_ib_date_does_not_exist"));
1177 /*default error message ?*/
1183 static void hildon_date_editor_size_request(GtkWidget * widget,
1184 GtkRequisition * requisition)
1186 HildonDateEditor *ed;
1187 HildonDateEditorPrivate *priv;
1188 GtkRequisition f_req, img_req;
1190 g_assert(GTK_IS_WIDGET(widget));
1191 g_assert(requisition != NULL);
1193 ed = HILDON_DATE_EDITOR(widget);
1194 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1196 /* Our own children affect our size */
1197 gtk_widget_size_request(priv->frame, &f_req);
1198 gtk_widget_size_request(priv->d_button_image, &img_req);
1200 /* calculate our size */
1201 requisition->width = f_req.width + img_req.width + HILDON_MARGIN_DEFAULT;
1203 /* FIXME: Fixed size is bad! We should use the maximum of our children, but
1204 doing so would break current pixel specifications, since
1205 the text entry by itself is already 30px tall + then frame takes
1207 requisition->height = DATE_EDITOR_HEIGHT;
1210 static void hildon_date_editor_size_allocate(GtkWidget * widget,
1211 GtkAllocation * allocation)
1213 HildonDateEditor *ed;
1214 HildonDateEditorPrivate *priv;
1215 GtkAllocation f_alloc, img_alloc;
1217 GtkRequisition max_req;
1220 g_assert(GTK_IS_WIDGET(widget));
1221 g_assert(allocation != NULL);
1223 ed = HILDON_DATE_EDITOR(widget);
1224 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1226 widget->allocation = *allocation;
1228 gtk_widget_get_child_requisition(widget, &max_req);
1230 /* Center vertically */
1231 f_alloc.y = img_alloc.y = allocation->y +
1232 MAX(allocation->height - max_req.height, 0) / 2;
1234 /* Center horizontally */
1235 f_alloc.x = img_alloc.x = allocation->x +
1236 MAX(allocation->width - max_req.width, 0) / 2;
1238 /* allocate frame */
1239 if (GTK_WIDGET_VISIBLE(priv->frame)) {
1240 gtk_widget_get_child_requisition(priv->frame, &req);
1242 f_alloc.width = req.width;
1243 f_alloc.height = max_req.height;
1244 gtk_widget_size_allocate(priv->frame, &f_alloc);
1248 if (GTK_WIDGET_VISIBLE(priv->d_button_image)) {
1249 gtk_widget_get_child_requisition(priv->d_button_image,
1252 img_alloc.x += f_alloc.width + HILDON_MARGIN_DEFAULT;
1253 img_alloc.width = req.width;
1254 img_alloc.height = max_req.height;
1255 gtk_widget_size_allocate(priv->d_button_image, &img_alloc);
1258 /* FIXME: We really should not alloc delimeters by hand (since they
1259 are not our own children, but we need to force to appear
1260 higher. This ugly hack is needed to compensate the forced
1261 height in size_request. */
1262 for (iter = priv->delims; iter; iter = iter->next)
1265 GtkAllocation alloc;
1267 delim = GTK_WIDGET(iter->data);
1268 alloc = delim->allocation;
1269 alloc.height = max_req.height;
1270 alloc.y = priv->d_entry->allocation.y - 2;
1272 gtk_widget_size_allocate(delim, &alloc);
1277 * hildon_date_editor_set_year:
1278 * @editor: the @HildonDateEditor widget
1281 * Sets the year shown in the editor.
1283 * Returns: TRUE if the year is valid
1285 gboolean hildon_date_editor_set_year(HildonDateEditor *editor, guint year)
1287 HildonDateEditorPrivate *priv;
1288 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), FALSE );
1289 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1291 if (g_date_valid_dmy(priv->day, priv->month, year))
1296 g_snprintf(buffer, sizeof(buffer), "%04d", year);
1298 /* We apply the new day, but do not want automatic focus move
1299 etc to take place */
1300 g_signal_handlers_block_by_func(priv->y_entry,
1301 (gpointer) hildon_date_editor_entry_changed, editor);
1302 gtk_entry_set_text(GTK_ENTRY(priv->y_entry), buffer);
1303 g_signal_handlers_unblock_by_func(priv->y_entry,
1304 (gpointer) hildon_date_editor_entry_changed, editor);
1306 g_object_notify(G_OBJECT(editor), "year");
1314 * hildon_date_editor_set_month:
1315 * @editor: the @HildonDateEditor widget
1318 * Sets the month shown in the editor.
1320 * Returns: TRUE if the month is valid
1322 gboolean hildon_date_editor_set_month(HildonDateEditor *editor, guint month)
1324 HildonDateEditorPrivate *priv;
1325 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), FALSE );
1326 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1328 if (g_date_valid_dmy(priv->day, month, priv->year))
1333 priv->month = month;
1334 g_date_set_dmy(&date, priv->day, month, priv->year);
1335 g_date_strftime(buffer, sizeof(buffer), "%m", &date);
1337 /* We apply the new day, but do not want automatic focus move
1338 etc to take place */
1339 g_signal_handlers_block_by_func(priv->m_entry,
1340 (gpointer) hildon_date_editor_entry_changed, editor);
1341 gtk_entry_set_text(GTK_ENTRY(priv->m_entry), buffer);
1342 g_signal_handlers_unblock_by_func(priv->m_entry,
1343 (gpointer) hildon_date_editor_entry_changed, editor);
1345 g_object_notify(G_OBJECT(editor), "month");
1352 * hildon_date_editor_set_day:
1353 * @editor: the @HildonDateEditor widget
1356 * Sets the day shown in the editor.
1358 * Returns: TRUE if the day is valid
1360 gboolean hildon_date_editor_set_day(HildonDateEditor *editor, guint day)
1362 HildonDateEditorPrivate *priv;
1364 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), FALSE );
1365 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1367 if (g_date_valid_dmy(day, priv->month, priv->year))
1373 g_date_set_dmy(&date, day, priv->month, priv->year);
1374 g_date_strftime(buffer, sizeof(buffer), "%d", &date);
1376 /* We apply the new day, but do not want automatic focus move
1377 etc to take place */
1378 g_signal_handlers_block_by_func(priv->d_entry,
1379 (gpointer) hildon_date_editor_entry_changed, editor);
1380 gtk_entry_set_text(GTK_ENTRY(priv->d_entry), buffer);
1381 g_signal_handlers_unblock_by_func(priv->d_entry,
1382 (gpointer) hildon_date_editor_entry_changed, editor);
1384 g_object_notify(G_OBJECT(editor), "day");
1391 * hildon_date_editor_get_year:
1392 * @editor: the @HildonDateEditor widget
1394 * Returns: the current year shown in the editor.
1396 guint hildon_date_editor_get_year(HildonDateEditor *editor)
1398 HildonDateEditorPrivate *priv;
1399 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), 0 );
1400 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1401 return (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->y_entry)));
1405 * hildon_date_editor_get_month:
1406 * @editor: the @HildonDateEditor widget
1408 * Gets the month shown in the editor.
1410 * Returns: the current month shown in the editor.
1413 guint hildon_date_editor_get_month(HildonDateEditor *editor)
1415 HildonDateEditorPrivate *priv;
1416 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), 0 );
1417 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1418 return (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->m_entry)));
1422 * hildon_date_editor_get_day:
1423 * @editor: the @HildonDateEditor widget
1425 * Gets the day shown in the editor.
1427 * Returns: the current day shown in the editor
1430 guint hildon_date_editor_get_day(HildonDateEditor *editor)
1432 HildonDateEditorPrivate *priv;
1433 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), 0 );
1434 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1435 return (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->d_entry)));
1440 _hildon_date_editor_entry_select_all (GtkWidget *widget)
1442 GDK_THREADS_ENTER ();
1443 gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1);
1444 GDK_THREADS_LEAVE ();