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-libs-enum-types.h"
58 #define _(string) dgettext(PACKAGE, string)
60 #define ENTRY_BORDERS 11
61 #define DATE_EDITOR_HEIGHT 30
63 #define DAY_ENTRY_WIDTH 2
64 #define MONTH_ENTRY_WIDTH 2
65 #define YEAR_ENTRY_WIDTH 4
67 #define DEFAULT_MIN_YEAR 1970
68 #define DEFAULT_MAX_YEAR 2037
70 #define HILDON_DATE_EDITOR_GET_PRIVATE(obj) \
71 (G_TYPE_INSTANCE_GET_PRIVATE((obj),\
72 HILDON_TYPE_DATE_EDITOR, HildonDateEditorPrivate));
74 static GtkContainerClass *parent_class;
76 typedef struct _HildonDateEditorPrivate HildonDateEditorPrivate;
79 hildon_date_editor_class_init(HildonDateEditorClass * editor_class);
81 static void hildon_date_editor_init(HildonDateEditor * editor);
84 hildon_date_editor_icon_press(GtkWidget * widget,
88 hildon_date_editor_released(GtkWidget * widget,
92 hildon_date_editor_keypress(GtkWidget * widget, GdkEventKey * event,
96 hildon_date_editor_keyrelease(GtkWidget * widget, GdkEventKey * event,
99 hildon_date_editor_clicked(GtkWidget * widget, gpointer data);
101 hildon_date_editor_entry_validate(GtkWidget *widget, gpointer data);
104 hildon_date_editor_entry_changed(GtkEditable *widget, gpointer data);
107 hildon_date_editor_entry_focus_out(GtkWidget * widget, GdkEventFocus * event,
110 static gboolean hildon_date_editor_date_error(HildonDateEditor *editor,
111 HildonDateEditorErrorType type);
113 static gboolean hildon_date_editor_entry_focusin(GtkWidget * widget,
114 GdkEventFocus * event,
116 static void hildon_date_editor_get_property( GObject *object, guint param_id,
117 GValue *value, GParamSpec *pspec );
118 static void hildon_date_editor_set_property (GObject *object, guint param_id,
119 const GValue *value, GParamSpec *pspec);
121 hildon_child_forall(GtkContainer * container,
122 gboolean include_internals,
123 GtkCallback callback, gpointer callback_data);
125 static void hildon_date_editor_destroy(GtkObject * self);
128 hildon_date_editor_size_allocate(GtkWidget * widget,
129 GtkAllocation * allocation);
132 hildon_date_editor_size_request(GtkWidget * widget,
133 GtkRequisition * requisition);
136 _hildon_date_editor_entry_select_all(GtkWidget *widget);
138 /* Property indices */
148 struct _HildonDateEditorPrivate {
149 /* Cache of values in the entries, used in setting only parts of the date */
150 guint year; /* current year in the entry */
151 guint month; /* current month in the entry */
152 guint day; /* current day in the entry */
154 gboolean calendar_icon_pressed;
156 GtkWidget *frame; /* borders around the date */
157 GtkWidget *d_button_image; /* icon */
158 GtkWidget *d_box_date; /* hbox for date */
160 GtkWidget *d_entry; /* GtkEntry for day */
161 GtkWidget *m_entry; /* GtkEntry for month */
162 GtkWidget *y_entry; /* GtkEntry for year */
164 GList *delims; /* List of delimeters between the fields (and possible at the ends) */
165 GtkWidget *calendar_icon;
167 gboolean skip_validation; /* don't validate date at all */
169 gint min_year; /* minimum year allowed */
170 gint max_year; /* maximum year allowed */
178 static guint date_editor_signals[LAST_SIGNAL] = { 0 };
180 GType hildon_date_editor_get_type(void)
182 static GType editor_type = 0;
185 static const GTypeInfo editor_info = {
186 sizeof(HildonDateEditorClass),
187 NULL, /* base_init */
188 NULL, /* base_finalize */
189 (GClassInitFunc) hildon_date_editor_class_init,
190 NULL, /* class_finalize */
191 NULL, /* class_data */
192 sizeof(HildonDateEditor),
194 (GInstanceInitFunc) hildon_date_editor_init,
196 editor_type = g_type_register_static(GTK_TYPE_CONTAINER,
204 hildon_date_editor_class_init(HildonDateEditorClass * editor_class)
206 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(editor_class);
207 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(editor_class);
208 GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
210 parent_class = g_type_class_peek_parent(editor_class);
212 g_type_class_add_private(editor_class,
213 sizeof(HildonDateEditorPrivate));
215 gobject_class->set_property = hildon_date_editor_set_property;
216 gobject_class->get_property = hildon_date_editor_get_property;
217 widget_class->size_request = hildon_date_editor_size_request;
218 widget_class->size_allocate = hildon_date_editor_size_allocate;
219 widget_class->focus = hildon_composite_widget_focus;
221 container_class->forall = hildon_child_forall;
222 GTK_OBJECT_CLASS(editor_class)->destroy = hildon_date_editor_destroy;
224 editor_class->date_error = hildon_date_editor_date_error;
226 date_editor_signals[DATE_ERROR] =
227 g_signal_new("date-error",
228 G_OBJECT_CLASS_TYPE(gobject_class),
230 G_STRUCT_OFFSET(HildonDateEditorClass, date_error),
231 g_signal_accumulator_true_handled, NULL,
232 _hildon_marshal_BOOLEAN__ENUM,
233 G_TYPE_BOOLEAN, 1, HILDON_TYPE_DATE_EDITOR_ERROR_TYPE);
236 * HildonDateEditor:year:
240 g_object_class_install_property( gobject_class, PROP_YEAR,
241 g_param_spec_uint("year",
246 G_PARAM_READABLE | G_PARAM_WRITABLE) );
249 * HildonDateEditor:month:
253 g_object_class_install_property( gobject_class, PROP_MONTH,
254 g_param_spec_uint("month",
259 G_PARAM_READABLE | G_PARAM_WRITABLE) );
262 * HildonDateEditor:day:
266 g_object_class_install_property( gobject_class, PROP_DAY,
267 g_param_spec_uint("day",
272 G_PARAM_READABLE | G_PARAM_WRITABLE) );
275 * HildonDateEditor:min-year:
277 * Minimum valid year.
279 g_object_class_install_property( gobject_class, PROP_MIN_YEAR,
280 g_param_spec_uint("min-year",
281 "Minimum valid year",
282 "Minimum valid year",
285 G_PARAM_READWRITE) );
288 * HildonDateEditor:max-year:
290 * Maximum valid year.
292 g_object_class_install_property( gobject_class, PROP_MAX_YEAR,
293 g_param_spec_uint("max-year",
294 "Maximum valid year",
295 "Maximum valid year",
298 G_PARAM_READWRITE) );
301 /* Forces setting of the icon to certain state. Used initially
302 and from the actual setter function */
304 real_set_calendar_icon_state(HildonDateEditorPrivate *priv,
307 gtk_image_set_from_icon_name(GTK_IMAGE(priv->calendar_icon),
308 pressed ? "qgn_widg_datedit_pr" : "qgn_widg_datedit",
309 HILDON_ICON_SIZE_WIDG);
311 priv->calendar_icon_pressed = pressed;
314 /* Sets the icon to given state (normal/pressed). Returns
315 info if the state actually changed. */
317 hildon_date_editor_set_calendar_icon_state(HildonDateEditor *editor,
320 HildonDateEditorPrivate *priv;
322 g_assert(HILDON_IS_DATE_EDITOR(editor));
324 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
325 if (pressed != priv->calendar_icon_pressed) {
326 real_set_calendar_icon_state(priv, pressed);
333 /* Packing day, month and year entries depend on locale settings
334 We find out the order and all separators by converting a known
335 date to default format and inspecting the result string */
336 static void apply_locale_field_order(HildonDateEditorPrivate *priv)
338 GDate locale_test_date;
341 gchar *iter, *delim_text;
343 g_date_set_dmy(&locale_test_date, 1, 2, 1970);
344 (void) g_date_strftime(buffer, sizeof(buffer), "%x", &locale_test_date);
352 /* Try to convert the current location into number. */
353 value = strtoul(iter, &endp, 10);
355 /* If the conversion didn't progress or the detected value was
356 unknown (we used a fixed date, you remember), we treat
357 current position as a literal */
361 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
362 priv->d_entry, FALSE, FALSE, 0);
365 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
366 priv->m_entry, FALSE, FALSE, 0);
368 case 70: /* %x format uses only 2 numbers for some locales */
370 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
371 priv->y_entry, FALSE, FALSE, 0);
374 /* All non-number characters starting from current position
375 form the delimeter */
376 for (endp = iter; *endp; endp++)
377 if (g_ascii_isdigit(*endp))
380 /* Now endp points one place past the delimeter text */
381 delim_text = g_strndup(iter, endp - iter);
382 delim = gtk_label_new(delim_text);
383 gtk_box_pack_start(GTK_BOX(priv->d_box_date),
384 delim, FALSE, FALSE, 0);
385 priv->delims = g_list_append(priv->delims, delim);
395 static void hildon_date_editor_init(HildonDateEditor * editor)
397 HildonDateEditorPrivate *priv;
400 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
402 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(editor), GTK_NO_WINDOW);
404 gtk_widget_push_composite_child();
406 /* initialize values */
407 g_date_clear(&cur_date, 1);
408 g_date_set_time(&cur_date, time(NULL));
410 priv->day = g_date_get_day(&cur_date);
411 priv->month = g_date_get_month(&cur_date);
412 priv->year = g_date_get_year(&cur_date);
413 priv->min_year = DEFAULT_MIN_YEAR;
414 priv->max_year = DEFAULT_MAX_YEAR;
417 priv->frame = gtk_frame_new(NULL);
418 gtk_container_set_border_width(GTK_CONTAINER(priv->frame), 0);
420 priv->d_entry = gtk_entry_new();
421 priv->m_entry = gtk_entry_new();
422 priv->y_entry = gtk_entry_new();
424 g_object_set (G_OBJECT(priv->d_entry), "input-mode",
425 HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
426 g_object_set (G_OBJECT(priv->m_entry), "input-mode",
427 HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
428 g_object_set (G_OBJECT(priv->y_entry), "input-mode",
429 HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
432 gtk_entry_set_width_chars(GTK_ENTRY(priv->d_entry), DAY_ENTRY_WIDTH);
433 gtk_entry_set_width_chars(GTK_ENTRY(priv->m_entry), MONTH_ENTRY_WIDTH);
434 gtk_entry_set_width_chars(GTK_ENTRY(priv->y_entry), YEAR_ENTRY_WIDTH);
436 gtk_entry_set_max_length(GTK_ENTRY(priv->d_entry), DAY_ENTRY_WIDTH);
437 gtk_entry_set_max_length(GTK_ENTRY(priv->m_entry), MONTH_ENTRY_WIDTH);
438 gtk_entry_set_max_length(GTK_ENTRY(priv->y_entry), YEAR_ENTRY_WIDTH);
440 gtk_entry_set_has_frame(GTK_ENTRY(priv->d_entry), FALSE);
441 gtk_entry_set_has_frame(GTK_ENTRY(priv->m_entry), FALSE);
442 gtk_entry_set_has_frame(GTK_ENTRY(priv->y_entry), FALSE);
444 gtk_widget_set_composite_name(priv->d_entry, "day_entry");
445 gtk_widget_set_composite_name(priv->m_entry, "month_entry");
446 gtk_widget_set_composite_name(priv->y_entry, "year_entry");
448 priv->d_box_date = gtk_hbox_new(FALSE, 0);
450 priv->d_button_image = gtk_button_new();
451 priv->calendar_icon = gtk_image_new();
452 real_set_calendar_icon_state(priv, FALSE);
453 GTK_WIDGET_UNSET_FLAGS(priv->d_button_image, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
455 apply_locale_field_order(priv);
457 gtk_container_add(GTK_CONTAINER(priv->frame), priv->d_box_date);
458 gtk_container_add(GTK_CONTAINER(priv->d_button_image), priv->calendar_icon);
459 gtk_button_set_relief(GTK_BUTTON(priv->d_button_image), GTK_RELIEF_NONE);
460 gtk_button_set_focus_on_click(GTK_BUTTON(priv->d_button_image), FALSE);
462 gtk_widget_set_parent(priv->frame, GTK_WIDGET(editor));
463 gtk_widget_set_parent(priv->d_button_image, GTK_WIDGET(editor));
464 gtk_widget_show_all(priv->frame);
465 gtk_widget_show_all(priv->d_button_image);
467 /* image button signal connects */
468 g_signal_connect(GTK_OBJECT(priv->d_button_image), "pressed",
469 G_CALLBACK(hildon_date_editor_icon_press), editor);
470 g_signal_connect(GTK_OBJECT(priv->d_button_image), "released",
471 G_CALLBACK(hildon_date_editor_released), editor);
472 g_signal_connect(GTK_OBJECT(priv->d_button_image), "clicked",
473 G_CALLBACK(hildon_date_editor_clicked), editor);
474 g_signal_connect(GTK_OBJECT(priv->d_button_image), "key_press_event",
475 G_CALLBACK(hildon_date_editor_keypress), editor);
477 /* entry signal connects */
478 g_signal_connect(GTK_OBJECT(priv->d_entry), "focus-in-event",
479 G_CALLBACK(hildon_date_editor_entry_focusin), editor);
481 g_signal_connect(GTK_OBJECT(priv->m_entry), "focus-in-event",
482 G_CALLBACK(hildon_date_editor_entry_focusin), editor);
484 g_signal_connect(GTK_OBJECT(priv->y_entry), "focus-in-event",
485 G_CALLBACK(hildon_date_editor_entry_focusin), editor);
487 g_signal_connect(GTK_OBJECT(priv->d_entry), "focus-out-event",
488 G_CALLBACK(hildon_date_editor_entry_focus_out), editor);
490 g_signal_connect(GTK_OBJECT(priv->m_entry), "focus-out-event",
491 G_CALLBACK(hildon_date_editor_entry_focus_out), editor);
493 g_signal_connect(GTK_OBJECT(priv->y_entry), "focus-out-event",
494 G_CALLBACK(hildon_date_editor_entry_focus_out), editor);
496 g_signal_connect(GTK_OBJECT(priv->d_entry), "key-press-event",
497 G_CALLBACK(hildon_date_editor_keypress), editor);
499 g_signal_connect(GTK_OBJECT(priv->m_entry), "key-press-event",
500 G_CALLBACK(hildon_date_editor_keypress), editor);
502 g_signal_connect(GTK_OBJECT(priv->y_entry), "key-press-event",
503 G_CALLBACK(hildon_date_editor_keypress), editor);
505 g_signal_connect(GTK_OBJECT(priv->d_entry), "key-release-event",
506 G_CALLBACK(hildon_date_editor_keyrelease), editor);
508 g_signal_connect(GTK_OBJECT(priv->m_entry), "key-release-event",
509 G_CALLBACK(hildon_date_editor_keyrelease), editor);
511 g_signal_connect(GTK_OBJECT(priv->y_entry), "key-release-event",
512 G_CALLBACK(hildon_date_editor_keyrelease), editor);
514 hildon_date_editor_set_date(editor, priv->year, priv->month, priv->day);
516 g_signal_connect(GTK_OBJECT(priv->d_entry), "changed",
517 G_CALLBACK(hildon_date_editor_entry_changed), editor);
519 g_signal_connect(GTK_OBJECT(priv->m_entry), "changed",
520 G_CALLBACK(hildon_date_editor_entry_changed), editor);
522 g_signal_connect(GTK_OBJECT(priv->y_entry), "changed",
523 G_CALLBACK(hildon_date_editor_entry_changed), editor);
525 gtk_widget_pop_composite_child();
528 static void hildon_date_editor_set_property (GObject *object, guint param_id,
529 const GValue *value, GParamSpec *pspec)
531 HildonDateEditor *editor = HILDON_DATE_EDITOR(object);
532 HildonDateEditorPrivate *priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
538 hildon_date_editor_set_year (editor, g_value_get_uint(value));
542 hildon_date_editor_set_month (editor, g_value_get_uint(value));
546 hildon_date_editor_set_day (editor, g_value_get_uint(value));
550 val = g_value_get_uint(value);
551 if (val <= priv->max_year)
553 priv->min_year = val;
554 /* Clamp current year */
555 if (hildon_date_editor_get_year (editor) < priv->min_year)
556 hildon_date_editor_set_year (editor, priv->min_year);
559 g_warning("min-year cannot be greater than max-year");
563 val = g_value_get_uint(value);
564 if (val >= priv->min_year)
566 priv->max_year = val;
567 /* Clamp current year */
568 if (hildon_date_editor_get_year (editor) > priv->max_year)
569 hildon_date_editor_set_year (editor, priv->max_year);
572 g_warning("max-year cannot be less than min-year");
576 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
581 static void hildon_date_editor_get_property( GObject *object, guint param_id,
582 GValue *value, GParamSpec *pspec )
584 HildonDateEditor *editor = HILDON_DATE_EDITOR(object);
585 HildonDateEditorPrivate *priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
590 g_value_set_uint (value, hildon_date_editor_get_year (editor));
594 g_value_set_uint (value, hildon_date_editor_get_month (editor));
598 g_value_set_uint (value, hildon_date_editor_get_day (editor));
602 g_value_set_uint (value, priv->min_year);
606 g_value_set_uint (value, priv->max_year);
610 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
615 static void hildon_child_forall(GtkContainer * container,
616 gboolean include_internals,
617 GtkCallback callback,
618 gpointer callback_data)
620 HildonDateEditor *editor;
621 HildonDateEditorPrivate *priv;
623 g_assert(HILDON_IS_DATE_EDITOR(container));
626 editor = HILDON_DATE_EDITOR(container);
627 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
629 if (include_internals) {
630 (*callback) (priv->frame, callback_data);
631 (*callback) (priv->d_button_image, callback_data);
635 static void hildon_date_editor_destroy(GtkObject * self)
637 HildonDateEditorPrivate *priv;
639 priv = HILDON_DATE_EDITOR_GET_PRIVATE(self);
642 gtk_widget_unparent(priv->frame);
645 if (priv->d_button_image) {
646 gtk_widget_unparent(priv->d_button_image);
647 priv->d_button_image = NULL;
650 g_list_free(priv->delims);
654 if (GTK_OBJECT_CLASS(parent_class)->destroy)
655 GTK_OBJECT_CLASS(parent_class)->destroy(self);
659 * hildon_date_editor_new:
661 * Creates a new date editor. The current system date
662 * is shown in the editor.
664 * Returns: pointer to a new @HildonDateEditor widget.
666 GtkWidget *hildon_date_editor_new(void)
668 return GTK_WIDGET(g_object_new(HILDON_TYPE_DATE_EDITOR, NULL));
672 * hildon_date_editor_set_date:
673 * @date: the @HildonDateEditor widget
678 * Sets the date shown in the editor.
680 void hildon_date_editor_set_date(HildonDateEditor * editor,
681 guint year, guint month, guint day)
683 HildonDateEditorPrivate *priv;
685 g_return_if_fail(HILDON_IS_DATE_EDITOR(editor));
687 /* This function cannot be implemented by calling
688 component setters, since applying the individual
689 values one by one can make the date temporarily
690 invalid (depending on what the previous values were),
691 which in turn causes that the desired date
692 is not set (even though it's valid). We must set all the
693 components at one go and not try to do any validation etc
696 g_return_if_fail(HILDON_IS_DATE_EDITOR(editor));
697 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
699 if (g_date_valid_dmy(day, month, year))
708 g_date_set_dmy(&date, day, month, year);
710 /* We apply the new values, but do not want automatic focus move
712 g_snprintf(buffer, sizeof(buffer), "%04d", year);
713 g_signal_handlers_block_by_func(priv->y_entry,
714 (gpointer) hildon_date_editor_entry_changed, editor);
715 gtk_entry_set_text(GTK_ENTRY(priv->y_entry), buffer);
716 g_signal_handlers_unblock_by_func(priv->y_entry,
717 (gpointer) hildon_date_editor_entry_changed, editor);
719 g_date_strftime(buffer, sizeof(buffer), "%m", &date);
720 g_signal_handlers_block_by_func(priv->m_entry,
721 (gpointer) hildon_date_editor_entry_changed, editor);
722 gtk_entry_set_text(GTK_ENTRY(priv->m_entry), buffer);
723 g_signal_handlers_unblock_by_func(priv->m_entry,
724 (gpointer) hildon_date_editor_entry_changed, editor);
726 g_date_strftime(buffer, sizeof(buffer), "%d", &date);
727 g_signal_handlers_block_by_func(priv->d_entry,
728 (gpointer) hildon_date_editor_entry_changed, editor);
729 gtk_entry_set_text(GTK_ENTRY(priv->d_entry), buffer);
730 g_signal_handlers_unblock_by_func(priv->d_entry,
731 (gpointer) hildon_date_editor_entry_changed, editor);
733 g_object_notify(G_OBJECT(editor), "year");
734 g_object_notify(G_OBJECT(editor), "month");
735 g_object_notify(G_OBJECT(editor), "day");
740 * hildon_date_editor_get_date:
741 * @date: the @HildonDateEditor widget
746 * Returns: the year, month, and day currently on the
749 void hildon_date_editor_get_date(HildonDateEditor * date,
750 guint * year, guint * month, guint * day)
752 HildonDateEditorPrivate *priv;
754 g_return_if_fail(HILDON_IS_DATE_EDITOR(date));
755 g_return_if_fail(year);
756 g_return_if_fail(month);
757 g_return_if_fail(day);
759 priv = HILDON_DATE_EDITOR_GET_PRIVATE(date);
761 /* FIXME: The role of priv->{day,month,year} members vs. entry contents
762 is unclear. They do not neccesarily match and still the texts are
763 used as return values and members for some internal validation!!
764 At least a partly reason is to allow empty text to become
765 0 return value, while members are restricted to valid ranges?!
766 However, if we change the current way, we are likely to break
767 some applications if they rely on some specific way how this
768 widget currently handles empty values and temporarily invalid values.
770 The key issue is this: What should the _get methods return while
771 user is editing a field and the result is incomplete. The
772 partial result? The last good result? If we return partial result
773 we also need a way to inform if the date is not valid. Current
774 implementation is some kind of hybrid of these two...
777 hildon_date_editor_set_day(editor, hildon_date_editor_get_day(editor));
779 easily fails, since set_day tries to force validity while get_day
782 Proposal: Always return the same values that are shown in the
783 fields. We add a separate flag (Or use GDate) to
784 indicate if the current date is valid. This would allow
785 setters to make the date invalid as well.
787 *year = /*priv->year;*/
788 (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->y_entry)));
789 *month = /*priv->month;*/
790 (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->m_entry)));
791 *day = /*priv->day;*/
792 (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->d_entry)));
795 /* icon button press event */
796 static gboolean hildon_date_editor_icon_press(GtkWidget * widget,
799 g_assert(GTK_IS_WIDGET(widget));
800 g_assert(HILDON_IS_DATE_EDITOR(data));
802 hildon_date_editor_set_calendar_icon_state(HILDON_DATE_EDITOR(data), TRUE);
807 static gboolean hildon_date_editor_entry_focusin(GtkWidget * widget,
808 GdkEventFocus * event,
811 g_idle_add((GSourceFunc)
812 _hildon_date_editor_entry_select_all, GTK_ENTRY(widget));
818 static void popup_calendar_dialog(HildonDateEditor *ed)
820 guint y = 0, m = 0, d = 0;
826 hildon_date_editor_get_date(ed, &y, &m, &d);
828 parent = gtk_widget_get_ancestor(GTK_WIDGET(ed), GTK_TYPE_WINDOW);
829 popup = hildon_calendar_popup_new(GTK_WINDOW(parent), y, m, d);
831 g_value_init(&val, G_TYPE_INT);
832 /* Set max/min year in calendar popup to date editor values */
833 g_object_get_property(G_OBJECT(ed), "min-year", &val);
834 g_object_set_property(G_OBJECT(popup), "min-year", &val);
835 g_object_get_property(G_OBJECT(ed), "max-year", &val);
836 g_object_set_property(G_OBJECT(popup), "max-year", &val);
838 /* Pop up calendar */
839 result = gtk_dialog_run(GTK_DIALOG(popup));
841 case GTK_RESPONSE_OK:
842 case GTK_RESPONSE_ACCEPT:
843 hildon_calendar_popup_get_date(HILDON_CALENDAR_POPUP(popup), &y,
845 hildon_date_editor_set_date(ed, y, m, d);
848 gtk_widget_destroy(popup);
851 /* button released */
852 static gboolean hildon_date_editor_released(GtkWidget * widget,
855 HildonDateEditor *ed;
857 g_assert(GTK_IS_WIDGET(widget));
858 g_assert(HILDON_IS_DATE_EDITOR(data));
860 ed = HILDON_DATE_EDITOR(data);
862 /* restores the icon state. The clicked cycle raises the dialog */
863 hildon_date_editor_set_calendar_icon_state(ed, FALSE);
868 /* button released */
869 static gboolean hildon_date_editor_clicked(GtkWidget * widget,
872 HildonDateEditor *ed;
874 g_assert(GTK_IS_WIDGET(widget));
875 g_assert(HILDON_IS_DATE_EDITOR(data));
877 ed = HILDON_DATE_EDITOR(data);
879 /* restores the non-clicked button state and raises the dialog */
880 hildon_date_editor_set_calendar_icon_state(ed, FALSE);
881 popup_calendar_dialog(ed);
886 /* This is called whenever some editor filed loses the focus and
887 when the all of the fields are filled.
888 Earlier this was called whenever an entry changed */
889 /* FIXME: Validation on focus_out is broken by concept */
891 hildon_date_editor_entry_validate(GtkWidget *widget, gpointer data)
893 HildonDateEditor *ed;
894 HildonDateEditorPrivate *priv;
895 gint d, m, y, max_days;
896 gboolean r; /* temp return values for signals */
898 gint error_code = NO_ERROR;
900 g_assert(HILDON_IS_DATE_EDITOR(data));
901 g_assert(GTK_IS_ENTRY(widget));
903 ed = HILDON_DATE_EDITOR(data);
904 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
906 if (priv->skip_validation)
909 /*check if the calling entry is empty*/
910 text = gtk_entry_get_text(GTK_ENTRY(widget));
911 if(text == NULL || text[0] == 0)
913 if (widget == priv->d_entry)
914 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, EMPTY_DAY, &r);
915 else if(widget == priv->m_entry)
916 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, EMPTY_MONTH, &r);
918 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, EMPTY_YEAR, &r);
920 /* restore empty entry to safe value */
921 hildon_date_editor_set_date (ed, priv->year, priv->month, priv->day);
925 /* Ok, we now check validity. Some fields can be empty */
926 text = gtk_entry_get_text(GTK_ENTRY(priv->d_entry));
927 if (text == NULL || text[0] == 0) return error_code;
929 text = gtk_entry_get_text(GTK_ENTRY(priv->m_entry));
930 if (text == NULL || text[0] == 0) return error_code;
932 text = gtk_entry_get_text(GTK_ENTRY(priv->y_entry));
933 if (text == NULL || text[0] == 0) return error_code;
936 /* Did it actually change? */
937 if (d != priv->day || m != priv->month || y != priv->year)
939 /* We could/should use hildon_date_editor_set_year and such functions
940 * to set the date, instead of use gtk_entry_set_text, and then change
941 * the priv member but hildon_date_editor_set_year and such functions
942 * check if the date is valid, we do want to do date validation check
943 * here according to spec */
946 if(widget == priv->m_entry) {
948 error_code = MIN_MONTH;
952 error_code = MAX_MONTH;
958 if(widget == priv->y_entry) {
959 if (y < priv->min_year) {
960 error_code = MIN_YEAR;
963 else if (y > priv->max_year) {
964 error_code = MAX_YEAR;
969 /* Validate day. We have to do this in every case, since
970 changing month or year can make the day number to be invalid */
971 max_days = g_date_get_days_in_month(m,y);
973 error_code = MIN_DAY;
976 else if (d > max_days) {
978 error_code = MAX_DAY;
981 else { /* the date does not exist (is invalid) */
982 error_code = INVALID_DATE;
983 /* check what was changed and restore previous value */
984 if ( widget == priv->y_entry )
986 else if ( widget == priv->m_entry )
993 if (error_code != NO_ERROR)
995 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, error_code, &r);
997 g_idle_add ((GSourceFunc)
998 _hildon_date_editor_entry_select_all,
1003 /* Fix and reformat the date after error signal is processed.
1004 reformatting can be needed even in a such case that numerical
1005 values of the date components are the same as earlier. */
1006 hildon_date_editor_set_date(ed, y, m, d);
1010 /* When entry becomes full, we move the focus to the next field.
1011 If we are on the last field, the whole contents are validated. */
1013 hildon_date_editor_entry_changed(GtkEditable *ed, gpointer data)
1017 HildonDateEditorPrivate *priv;
1019 g_assert(GTK_IS_ENTRY(ed));
1020 g_assert(HILDON_IS_DATE_EDITOR(data));
1022 entry = GTK_ENTRY(ed);
1024 /* If day entry is full, move to next entry or validate */
1025 if (g_utf8_strlen(gtk_entry_get_text(entry), -1) == gtk_entry_get_max_length(entry))
1027 error_code = hildon_date_editor_entry_validate(GTK_WIDGET(entry), data);
1028 if (error_code == NO_ERROR)
1030 priv = HILDON_DATE_EDITOR_GET_PRIVATE(HILDON_DATE_EDITOR(data));
1031 priv->skip_validation = TRUE;
1032 gtk_widget_child_focus(GTK_WIDGET(data), GTK_DIR_RIGHT);
1037 static gboolean hildon_date_editor_keyrelease(GtkWidget * widget,
1038 GdkEventKey * event,
1041 HildonDateEditor *ed;
1042 HildonDateEditorPrivate *priv;
1044 g_return_val_if_fail(data, FALSE);
1045 g_return_val_if_fail(widget, FALSE);
1047 ed = HILDON_DATE_EDITOR(data);
1048 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1050 if (event->keyval == GDK_KP_Enter || event->keyval == GDK_Return ||
1051 event->keyval == GDK_ISO_Enter) {
1052 if (hildon_date_editor_set_calendar_icon_state(ed, FALSE))
1054 popup_calendar_dialog(ed);
1057 } else if (event->keyval == GDK_Escape)
1058 priv->skip_validation = FALSE;
1063 /* keyboard handling */
1064 static gboolean hildon_date_editor_keypress(GtkWidget * widget,
1065 GdkEventKey * event,
1068 HildonDateEditor *ed;
1069 HildonDateEditorPrivate *priv;
1073 g_assert(HILDON_IS_DATE_EDITOR(data));
1074 g_assert(GTK_IS_ENTRY(widget));
1076 ed = HILDON_DATE_EDITOR(data);
1077 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1078 pos = gtk_editable_get_position(GTK_EDITABLE(widget));
1080 /* Show error message in case the key pressed is not allowed
1081 (only digits and control characters are allowed )*/
1082 if (!g_unichar_isdigit(event->keyval) && !(event->keyval & 0xF000)) {
1083 g_signal_emit(ed, date_editor_signals[DATE_ERROR], 0, INVALID_CHAR, &r);
1087 switch (event->keyval) {
1090 (void) gtk_widget_child_focus(GTK_WIDGET(data), GTK_DIR_LEFT);
1095 if (pos >= g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(widget)), -1)) {
1096 (void) gtk_widget_child_focus(GTK_WIDGET(data), GTK_DIR_RIGHT);
1102 /* Ignore return value, since we want to handle event at all times.
1103 otherwise vkb would popup when the keyrepeat starts. */
1104 (void) hildon_date_editor_set_calendar_icon_state(ed, TRUE);
1108 priv->skip_validation = TRUE;
1117 static gboolean hildon_date_editor_entry_focus_out(GtkWidget * widget,
1118 GdkEventFocus * event,
1121 HildonDateEditor *ed;
1122 HildonDateEditorPrivate *priv;
1124 g_assert(HILDON_IS_DATE_EDITOR(data));
1126 ed = HILDON_DATE_EDITOR(data);
1127 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1129 hildon_date_editor_entry_validate(widget, data);
1130 priv->skip_validation = FALSE;
1136 hildon_date_editor_date_error(HildonDateEditor *editor,
1137 HildonDateEditorErrorType type)
1139 HildonDateEditorPrivate *priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1144 gtk_infoprintf(NULL, _("ckct_ib_maximum_value"), 31);
1147 gtk_infoprintf(NULL, _("ckct_ib_maximum_value"), 12);
1150 gtk_infoprintf(NULL, _("ckct_ib_maximum_value"), priv->max_year);
1154 gtk_infoprintf(NULL, _("ckct_ib_minimum_value"), 1);
1157 gtk_infoprintf(NULL, _("ckct_ib_minimum_value"), priv->min_year);
1160 gtk_infoprintf(NULL, _("ckct_ib_set_a_value_within_range"), 1, 31);
1163 gtk_infoprintf(NULL, _("ckct_ib_set_a_value_within_range"), 1, 12);
1166 gtk_infoprintf(NULL, _("ckct_ib_set_a_value_within_range"),
1167 priv->min_year, priv->max_year);
1170 gtk_infoprint(NULL, _("ckct_ib_illegal_character"));
1173 gtk_infoprint(NULL, _("ckct_ib_date_does_not_exist"));
1176 /*default error message ?*/
1182 static void hildon_date_editor_size_request(GtkWidget * widget,
1183 GtkRequisition * requisition)
1185 HildonDateEditor *ed;
1186 HildonDateEditorPrivate *priv;
1187 GtkRequisition f_req, img_req;
1189 g_assert(GTK_IS_WIDGET(widget));
1190 g_assert(requisition != NULL);
1192 ed = HILDON_DATE_EDITOR(widget);
1193 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1195 /* Our own children affect our size */
1196 gtk_widget_size_request(priv->frame, &f_req);
1197 gtk_widget_size_request(priv->d_button_image, &img_req);
1199 /* calculate our size */
1200 requisition->width = f_req.width + img_req.width + HILDON_MARGIN_DEFAULT;
1202 /* FIXME: Fixed size is bad! We should use the maximum of our children, but
1203 doing so would break current pixel specifications, since
1204 the text entry by itself is already 30px tall + then frame takes
1206 requisition->height = DATE_EDITOR_HEIGHT;
1209 static void hildon_date_editor_size_allocate(GtkWidget * widget,
1210 GtkAllocation * allocation)
1212 HildonDateEditor *ed;
1213 HildonDateEditorPrivate *priv;
1214 GtkAllocation f_alloc, img_alloc;
1216 GtkRequisition max_req;
1219 g_assert(GTK_IS_WIDGET(widget));
1220 g_assert(allocation != NULL);
1222 ed = HILDON_DATE_EDITOR(widget);
1223 priv = HILDON_DATE_EDITOR_GET_PRIVATE(ed);
1225 widget->allocation = *allocation;
1227 gtk_widget_get_child_requisition(widget, &max_req);
1229 /* Center vertically */
1230 f_alloc.y = img_alloc.y = allocation->y +
1231 MAX(allocation->height - max_req.height, 0) / 2;
1233 /* Center horizontally */
1234 f_alloc.x = img_alloc.x = allocation->x +
1235 MAX(allocation->width - max_req.width, 0) / 2;
1237 /* allocate frame */
1238 if (GTK_WIDGET_VISIBLE(priv->frame)) {
1239 gtk_widget_get_child_requisition(priv->frame, &req);
1241 f_alloc.width = req.width;
1242 f_alloc.height = max_req.height;
1243 gtk_widget_size_allocate(priv->frame, &f_alloc);
1247 if (GTK_WIDGET_VISIBLE(priv->d_button_image)) {
1248 gtk_widget_get_child_requisition(priv->d_button_image,
1251 img_alloc.x += f_alloc.width + HILDON_MARGIN_DEFAULT;
1252 img_alloc.width = req.width;
1253 img_alloc.height = max_req.height;
1254 gtk_widget_size_allocate(priv->d_button_image, &img_alloc);
1257 /* FIXME: We really should not alloc delimeters by hand (since they
1258 are not our own children, but we need to force to appear
1259 higher. This ugly hack is needed to compensate the forced
1260 height in size_request. */
1261 for (iter = priv->delims; iter; iter = iter->next)
1264 GtkAllocation alloc;
1266 delim = GTK_WIDGET(iter->data);
1267 alloc = delim->allocation;
1268 alloc.height = max_req.height;
1269 alloc.y = priv->d_entry->allocation.y - 2;
1271 gtk_widget_size_allocate(delim, &alloc);
1276 * hildon_date_editor_set_year:
1277 * @editor: the @HildonDateEditor widget
1280 * Sets the year shown in the editor.
1282 * Returns: TRUE if the year is valid
1284 gboolean hildon_date_editor_set_year(HildonDateEditor *editor, guint year)
1286 HildonDateEditorPrivate *priv;
1287 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), FALSE );
1288 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1290 if (g_date_valid_dmy(priv->day, priv->month, year))
1295 g_snprintf(buffer, sizeof(buffer), "%04d", year);
1297 /* We apply the new day, but do not want automatic focus move
1298 etc to take place */
1299 g_signal_handlers_block_by_func(priv->y_entry,
1300 (gpointer) hildon_date_editor_entry_changed, editor);
1301 gtk_entry_set_text(GTK_ENTRY(priv->y_entry), buffer);
1302 g_signal_handlers_unblock_by_func(priv->y_entry,
1303 (gpointer) hildon_date_editor_entry_changed, editor);
1305 g_object_notify(G_OBJECT(editor), "year");
1313 * hildon_date_editor_set_month:
1314 * @editor: the @HildonDateEditor widget
1317 * Sets the month shown in the editor.
1319 * Returns: TRUE if the month is valid
1321 gboolean hildon_date_editor_set_month(HildonDateEditor *editor, guint month)
1323 HildonDateEditorPrivate *priv;
1324 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), FALSE );
1325 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1327 if (g_date_valid_dmy(priv->day, month, priv->year))
1332 priv->month = month;
1333 g_date_set_dmy(&date, priv->day, month, priv->year);
1334 g_date_strftime(buffer, sizeof(buffer), "%m", &date);
1336 /* We apply the new day, but do not want automatic focus move
1337 etc to take place */
1338 g_signal_handlers_block_by_func(priv->m_entry,
1339 (gpointer) hildon_date_editor_entry_changed, editor);
1340 gtk_entry_set_text(GTK_ENTRY(priv->m_entry), buffer);
1341 g_signal_handlers_unblock_by_func(priv->m_entry,
1342 (gpointer) hildon_date_editor_entry_changed, editor);
1344 g_object_notify(G_OBJECT(editor), "month");
1351 * hildon_date_editor_set_day:
1352 * @editor: the @HildonDateEditor widget
1355 * Sets the day shown in the editor.
1357 * Returns: TRUE if the day is valid
1359 gboolean hildon_date_editor_set_day(HildonDateEditor *editor, guint day)
1361 HildonDateEditorPrivate *priv;
1363 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), FALSE );
1364 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1366 if (g_date_valid_dmy(day, priv->month, priv->year))
1372 g_date_set_dmy(&date, day, priv->month, priv->year);
1373 g_date_strftime(buffer, sizeof(buffer), "%d", &date);
1375 /* We apply the new day, but do not want automatic focus move
1376 etc to take place */
1377 g_signal_handlers_block_by_func(priv->d_entry,
1378 (gpointer) hildon_date_editor_entry_changed, editor);
1379 gtk_entry_set_text(GTK_ENTRY(priv->d_entry), buffer);
1380 g_signal_handlers_unblock_by_func(priv->d_entry,
1381 (gpointer) hildon_date_editor_entry_changed, editor);
1383 g_object_notify(G_OBJECT(editor), "day");
1390 * hildon_date_editor_get_year:
1391 * @editor: the @HildonDateEditor widget
1393 * Returns: the current year shown in the editor.
1395 guint hildon_date_editor_get_year(HildonDateEditor *editor)
1397 HildonDateEditorPrivate *priv;
1398 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), 0 );
1399 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1400 return (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->y_entry)));
1404 * hildon_date_editor_get_month:
1405 * @editor: the @HildonDateEditor widget
1407 * Gets the month shown in the editor.
1409 * Returns: the current month shown in the editor.
1412 guint hildon_date_editor_get_month(HildonDateEditor *editor)
1414 HildonDateEditorPrivate *priv;
1415 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), 0 );
1416 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1417 return (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->m_entry)));
1421 * hildon_date_editor_get_day:
1422 * @editor: the @HildonDateEditor widget
1424 * Gets the day shown in the editor.
1426 * Returns: the current day shown in the editor
1429 guint hildon_date_editor_get_day(HildonDateEditor *editor)
1431 HildonDateEditorPrivate *priv;
1432 g_return_val_if_fail( HILDON_IS_DATE_EDITOR(editor), 0 );
1433 priv = HILDON_DATE_EDITOR_GET_PRIVATE(editor);
1434 return (guint) atoi(gtk_entry_get_text(GTK_ENTRY(priv->d_entry)));
1439 _hildon_date_editor_entry_select_all (GtkWidget *widget)
1441 GDK_THREADS_ENTER ();
1442 gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1);
1443 GDK_THREADS_LEAVE ();