/* * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * published by the Free Software Foundation; either the * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * * Authors: * Damon Chaplin * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) */ /* * EDateEdit - a widget based on GnomeDateEdit to provide a date & optional * time field with popups for entering a date. */ #ifdef HAVE_CONFIG_H #include #endif #include "e-dateedit.h" #include #include #include #include #include #include #include #include #include #include "e-calendar.h" #include "e-util-enumtypes.h" #define E_DATE_EDIT_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_DATE_EDIT, EDateEditPrivate)) struct _EDateEditPrivate { GtkWidget *date_entry; GtkWidget *date_button; GtkWidget *space; GtkWidget *time_combo; GtkWidget *cal_popup; GtkWidget *calendar; GtkWidget *now_button; GtkWidget *today_button; GtkWidget *none_button; /* This will only be visible if a * 'None' date/time is permitted. */ GdkDevice *grabbed_keyboard; GdkDevice *grabbed_pointer; gboolean show_date; gboolean show_time; gboolean use_24_hour_format; /* This is TRUE if we want to make the time field insensitive rather * than hide it when set_show_time() is called. */ gboolean make_time_insensitive; /* This is the range of hours we show in the time popup. */ gint lower_hour; gint upper_hour; /* This indicates whether the last date committed was invalid. * (A date is committed by hitting Return, moving the keyboard focus, * or selecting a date in the popup). Note that this only indicates * that the date couldn't be parsed. A date set to 'None' is valid * here, though e_date_edit_date_is_valid() will return FALSE if an * empty date isn't actually permitted. */ gboolean date_is_valid; /* This is the last valid date which was set. If the date was set to * 'None' or empty, date_set_to_none will be TRUE and the other fields * are undefined, so don't use them. */ gboolean date_set_to_none; gint year; gint month; gint day; /* This indicates whether the last time committed was invalid. * (A time is committed by hitting Return, moving the keyboard focus, * or selecting a time in the popup). Note that this only indicates * that the time couldn't be parsed. An empty/None time is valid * here, though e_date_edit_time_is_valid() will return FALSE if an * empty time isn't actually permitted. */ gboolean time_is_valid; /* This is the last valid time which was set. If the time was set to * 'None' or empty, time_set_to_none will be TRUE and the other fields * are undefined, so don't use them. */ gboolean time_set_to_none; gint hour; gint minute; EDateEditGetTimeCallback time_callback; gpointer time_callback_data; GDestroyNotify time_callback_destroy; gboolean twodigit_year_can_future; /* set to TRUE when the date has been changed by typing to the entry */ gboolean has_been_changed; gboolean allow_no_date_set; }; enum { PROP_0, PROP_ALLOW_NO_DATE_SET, PROP_SHOW_DATE, PROP_SHOW_TIME, PROP_SHOW_WEEK_NUMBERS, PROP_USE_24_HOUR_FORMAT, PROP_WEEK_START_DAY, PROP_TWODIGIT_YEAR_CAN_FUTURE, PROP_SET_NONE }; enum { CHANGED, LAST_SIGNAL }; static void create_children (EDateEdit *dedit); static gboolean e_date_edit_mnemonic_activate (GtkWidget *widget, gboolean group_cycling); static void e_date_edit_grab_focus (GtkWidget *widget); static gint on_date_entry_key_press (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit); static gint on_date_entry_key_release (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit); static void on_date_button_clicked (GtkWidget *widget, EDateEdit *dedit); static void e_date_edit_show_date_popup (EDateEdit *dedit, GdkEvent *event); static void position_date_popup (EDateEdit *dedit); static void on_date_popup_none_button_clicked (GtkWidget *button, EDateEdit *dedit); static void on_date_popup_today_button_clicked (GtkWidget *button, EDateEdit *dedit); static void on_date_popup_now_button_clicked (GtkWidget *button, EDateEdit *dedit); static gint on_date_popup_delete_event (GtkWidget *widget, EDateEdit *dedit); static gint on_date_popup_key_press (GtkWidget *widget, GdkEventKey *event, EDateEdit *dedit); static gint on_date_popup_button_press (GtkWidget *widget, GdkEvent *button_event, gpointer data); static void on_date_popup_date_selected (ECalendarItem *calitem, EDateEdit *dedit); static void hide_date_popup (EDateEdit *dedit); static void rebuild_time_popup (EDateEdit *dedit); static gboolean field_set_to_none (const gchar *text); static gboolean e_date_edit_parse_date (EDateEdit *dedit, const gchar *date_text, struct tm *date_tm); static gboolean e_date_edit_parse_time (EDateEdit *dedit, const gchar *time_text, struct tm *time_tm); static void on_date_edit_time_selected (GtkComboBox *combo, EDateEdit *dedit); static gint on_time_entry_key_press (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit); static gint on_time_entry_key_release (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit); static gint on_date_entry_focus_out (GtkEntry *entry, GdkEventFocus *event, EDateEdit *dedit); static gint on_time_entry_focus_out (GtkEntry *entry, GdkEventFocus *event, EDateEdit *dedit); static void e_date_edit_update_date_entry (EDateEdit *dedit); static void e_date_edit_update_time_entry (EDateEdit *dedit); static void e_date_edit_update_time_combo_state (EDateEdit *dedit); static void e_date_edit_check_date_changed (EDateEdit *dedit); static void e_date_edit_check_time_changed (EDateEdit *dedit); static gboolean e_date_edit_set_date_internal (EDateEdit *dedit, gboolean valid, gboolean none, gint year, gint month, gint day); static gboolean e_date_edit_set_time_internal (EDateEdit *dedit, gboolean valid, gboolean none, gint hour, gint minute); static gint signals[LAST_SIGNAL]; G_DEFINE_TYPE_WITH_CODE ( EDateEdit, e_date_edit, GTK_TYPE_HBOX, G_IMPLEMENT_INTERFACE ( E_TYPE_EXTENSIBLE, NULL)) static void date_edit_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_ALLOW_NO_DATE_SET: e_date_edit_set_allow_no_date_set ( E_DATE_EDIT (object), g_value_get_boolean (value)); return; case PROP_SHOW_DATE: e_date_edit_set_show_date ( E_DATE_EDIT (object), g_value_get_boolean (value)); return; case PROP_SHOW_TIME: e_date_edit_set_show_time ( E_DATE_EDIT (object), g_value_get_boolean (value)); return; case PROP_SHOW_WEEK_NUMBERS: e_date_edit_set_show_week_numbers ( E_DATE_EDIT (object), g_value_get_boolean (value)); return; case PROP_USE_24_HOUR_FORMAT: e_date_edit_set_use_24_hour_format ( E_DATE_EDIT (object), g_value_get_boolean (value)); return; case PROP_WEEK_START_DAY: e_date_edit_set_week_start_day ( E_DATE_EDIT (object), g_value_get_enum (value)); return; case PROP_TWODIGIT_YEAR_CAN_FUTURE: e_date_edit_set_twodigit_year_can_future ( E_DATE_EDIT (object), g_value_get_boolean (value)); return; case PROP_SET_NONE: if (g_value_get_boolean (value)) e_date_edit_set_time (E_DATE_EDIT (object), -1); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void date_edit_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_ALLOW_NO_DATE_SET: g_value_set_boolean ( value, e_date_edit_get_allow_no_date_set ( E_DATE_EDIT (object))); return; case PROP_SHOW_DATE: g_value_set_boolean ( value, e_date_edit_get_show_date ( E_DATE_EDIT (object))); return; case PROP_SHOW_TIME: g_value_set_boolean ( value, e_date_edit_get_show_time ( E_DATE_EDIT (object))); return; case PROP_SHOW_WEEK_NUMBERS: g_value_set_boolean ( value, e_date_edit_get_show_week_numbers ( E_DATE_EDIT (object))); return; case PROP_USE_24_HOUR_FORMAT: g_value_set_boolean ( value, e_date_edit_get_use_24_hour_format ( E_DATE_EDIT (object))); return; case PROP_WEEK_START_DAY: g_value_set_enum ( value, e_date_edit_get_week_start_day ( E_DATE_EDIT (object))); return; case PROP_TWODIGIT_YEAR_CAN_FUTURE: g_value_set_boolean ( value, e_date_edit_get_twodigit_year_can_future ( E_DATE_EDIT (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void date_edit_dispose (GObject *object) { EDateEdit *dedit; dedit = E_DATE_EDIT (object); e_date_edit_set_get_time_callback (dedit, NULL, NULL, NULL); if (dedit->priv->cal_popup != NULL) { gtk_widget_destroy (dedit->priv->cal_popup); dedit->priv->cal_popup = NULL; } if (dedit->priv->grabbed_keyboard != NULL) { gdk_device_ungrab ( dedit->priv->grabbed_keyboard, GDK_CURRENT_TIME); g_object_unref (dedit->priv->grabbed_keyboard); dedit->priv->grabbed_keyboard = NULL; } if (dedit->priv->grabbed_pointer != NULL) { gdk_device_ungrab ( dedit->priv->grabbed_pointer, GDK_CURRENT_TIME); g_object_unref (dedit->priv->grabbed_pointer); dedit->priv->grabbed_pointer = NULL; } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_date_edit_parent_class)->dispose (object); } static void e_date_edit_class_init (EDateEditClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; g_type_class_add_private (class, sizeof (EDateEditPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = date_edit_set_property; object_class->get_property = date_edit_get_property; object_class->dispose = date_edit_dispose; widget_class = GTK_WIDGET_CLASS (class); widget_class->mnemonic_activate = e_date_edit_mnemonic_activate; widget_class->grab_focus = e_date_edit_grab_focus; g_object_class_install_property ( object_class, PROP_ALLOW_NO_DATE_SET, g_param_spec_boolean ( "allow-no-date-set", "Allow No Date Set", NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SHOW_DATE, g_param_spec_boolean ( "show-date", "Show Date", NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SHOW_TIME, g_param_spec_boolean ( "show-time", "Show Time", NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SHOW_WEEK_NUMBERS, g_param_spec_boolean ( "show-week-numbers", "Show Week Numbers", NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_USE_24_HOUR_FORMAT, g_param_spec_boolean ( "use-24-hour-format", "Use 24-Hour Format", NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_WEEK_START_DAY, g_param_spec_enum ( "week-start-day", "Week Start Day", NULL, E_TYPE_DATE_WEEKDAY, G_DATE_MONDAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_TWODIGIT_YEAR_CAN_FUTURE, g_param_spec_boolean ( "twodigit-year-can-future", "Two-digit year can be treated as future", NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SET_NONE, g_param_spec_boolean ( "set-none", "Sets None as selected date", NULL, FALSE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); signals[CHANGED] = g_signal_new ( "changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EDateEditClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void e_date_edit_init (EDateEdit *dedit) { dedit->priv = E_DATE_EDIT_GET_PRIVATE (dedit); dedit->priv->show_date = TRUE; dedit->priv->show_time = TRUE; dedit->priv->use_24_hour_format = TRUE; dedit->priv->make_time_insensitive = FALSE; dedit->priv->lower_hour = 0; dedit->priv->upper_hour = 24; dedit->priv->date_is_valid = TRUE; dedit->priv->date_set_to_none = TRUE; dedit->priv->time_is_valid = TRUE; dedit->priv->time_set_to_none = TRUE; dedit->priv->time_callback = NULL; dedit->priv->time_callback_data = NULL; dedit->priv->time_callback_destroy = NULL; dedit->priv->twodigit_year_can_future = TRUE; dedit->priv->has_been_changed = FALSE; create_children (dedit); /* Set it to the current time. */ e_date_edit_set_time (dedit, 0); e_extensible_load_extensions (E_EXTENSIBLE (dedit)); } /** * e_date_edit_new: * * Description: Creates a new #EDateEdit widget which can be used * to provide an easy to use way for entering dates and times. * * Returns: a new #EDateEdit widget. */ GtkWidget * e_date_edit_new (void) { EDateEdit *dedit; AtkObject *a11y; dedit = g_object_new (E_TYPE_DATE_EDIT, NULL); a11y = gtk_widget_get_accessible (GTK_WIDGET (dedit)); atk_object_set_name (a11y, _("Date and Time")); return GTK_WIDGET (dedit); } static void create_children (EDateEdit *dedit) { EDateEditPrivate *priv; ECalendar *calendar; GtkWidget *frame, *arrow; GtkWidget *vbox, *bbox; GtkWidget *child; AtkObject *a11y; GtkListStore *time_store; GList *cells; GtkCssProvider *css_provider; GtkStyleContext *style_context; const gchar *css; GError *error = NULL; priv = dedit->priv; priv->date_entry = gtk_entry_new (); a11y = gtk_widget_get_accessible (priv->date_entry); atk_object_set_description (a11y, _("Text entry to input date")); atk_object_set_name (a11y, _("Date")); gtk_box_pack_start (GTK_BOX (dedit), priv->date_entry, FALSE, TRUE, 0); gtk_widget_set_size_request (priv->date_entry, 100, -1); g_signal_connect ( priv->date_entry, "key_press_event", G_CALLBACK (on_date_entry_key_press), dedit); g_signal_connect ( priv->date_entry, "key_release_event", G_CALLBACK (on_date_entry_key_release), dedit); g_signal_connect_after ( priv->date_entry, "focus_out_event", G_CALLBACK (on_date_entry_focus_out), dedit); priv->date_button = gtk_button_new (); g_signal_connect ( priv->date_button, "clicked", G_CALLBACK (on_date_button_clicked), dedit); gtk_box_pack_start ( GTK_BOX (dedit), priv->date_button, FALSE, FALSE, 0); a11y = gtk_widget_get_accessible (priv->date_button); atk_object_set_description (a11y, _("Click this button to show a calendar")); atk_object_set_name (a11y, _("Date")); arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); gtk_container_add (GTK_CONTAINER (priv->date_button), arrow); gtk_widget_show (arrow); if (priv->show_date) { gtk_widget_show (priv->date_entry); gtk_widget_show (priv->date_button); } /* This is just to create a space between the date & time parts. */ priv->space = gtk_drawing_area_new (); gtk_box_pack_start (GTK_BOX (dedit), priv->space, FALSE, FALSE, 2); time_store = gtk_list_store_new (1, G_TYPE_STRING); priv->time_combo = gtk_combo_box_new_with_model_and_entry ( GTK_TREE_MODEL (time_store)); gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (priv->time_combo), 0); g_object_unref (time_store); css_provider = gtk_css_provider_new (); css = "GtkComboBox { -GtkComboBox-appears-as-list: 1; }"; gtk_css_provider_load_from_data (css_provider, css, -1, &error); style_context = gtk_widget_get_style_context (priv->time_combo); if (error == NULL) { gtk_style_context_add_provider ( style_context, GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } else { g_warning ("%s: %s", G_STRFUNC, error->message); g_clear_error (&error); } g_object_unref (css_provider); child = gtk_bin_get_child (GTK_BIN (priv->time_combo)); /* We need to make sure labels are right-aligned, since we want * digits to line up, and with a nonproportional font, the width * of a space != width of a digit. Technically, only 12-hour * format needs this, but we do it always, for consistency. */ g_object_set (child, "xalign", 1.0, NULL); cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->time_combo)); if (cells) { g_object_set (GTK_CELL_RENDERER (cells->data), "xalign", 1.0, NULL); g_list_free (cells); } gtk_box_pack_start (GTK_BOX (dedit), priv->time_combo, FALSE, TRUE, 0); gtk_widget_set_size_request (priv->time_combo, 110, -1); rebuild_time_popup (dedit); a11y = gtk_widget_get_accessible (priv->time_combo); atk_object_set_description (a11y, _("Drop-down combination box to select time")); atk_object_set_name (a11y, _("Time")); g_signal_connect ( child, "key_press_event", G_CALLBACK (on_time_entry_key_press), dedit); g_signal_connect ( child, "key_release_event", G_CALLBACK (on_time_entry_key_release), dedit); g_signal_connect_after ( child, "focus_out_event", G_CALLBACK (on_time_entry_focus_out), dedit); g_signal_connect_after ( priv->time_combo, "changed", G_CALLBACK (on_date_edit_time_selected), dedit); if (priv->show_time || priv->make_time_insensitive) gtk_widget_show (priv->time_combo); if (!priv->show_time && priv->make_time_insensitive) gtk_widget_set_sensitive (priv->time_combo, FALSE); if (priv->show_date && (priv->show_time || priv->make_time_insensitive)) gtk_widget_show (priv->space); priv->cal_popup = gtk_window_new (GTK_WINDOW_POPUP); gtk_window_set_type_hint ( GTK_WINDOW (priv->cal_popup), GDK_WINDOW_TYPE_HINT_COMBO); gtk_widget_set_events ( priv->cal_popup, gtk_widget_get_events (priv->cal_popup) | GDK_KEY_PRESS_MASK); g_signal_connect ( priv->cal_popup, "delete_event", G_CALLBACK (on_date_popup_delete_event), dedit); g_signal_connect ( priv->cal_popup, "key_press_event", G_CALLBACK (on_date_popup_key_press), dedit); g_signal_connect ( priv->cal_popup, "button_press_event", G_CALLBACK (on_date_popup_button_press), dedit); gtk_window_set_resizable (GTK_WINDOW (priv->cal_popup), TRUE); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (priv->cal_popup), frame); gtk_widget_show (frame); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_container_add (GTK_CONTAINER (frame), vbox); gtk_widget_show (vbox); priv->calendar = e_calendar_new (); calendar = E_CALENDAR (priv->calendar); gnome_canvas_item_set ( GNOME_CANVAS_ITEM (calendar->calitem), "maximum_days_selected", 1, "move_selection_when_moving", FALSE, NULL); g_signal_connect ( calendar->calitem, "selection_changed", G_CALLBACK (on_date_popup_date_selected), dedit); gtk_box_pack_start (GTK_BOX (vbox), priv->calendar, FALSE, FALSE, 0); gtk_widget_show (priv->calendar); bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); gtk_container_set_border_width (GTK_CONTAINER (bbox), 4); gtk_box_set_spacing (GTK_BOX (bbox), 2); gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); gtk_widget_show (bbox); priv->now_button = gtk_button_new_with_mnemonic (_("No_w")); gtk_container_add (GTK_CONTAINER (bbox), priv->now_button); gtk_widget_show (priv->now_button); g_signal_connect ( priv->now_button, "clicked", G_CALLBACK (on_date_popup_now_button_clicked), dedit); priv->today_button = gtk_button_new_with_mnemonic (_("_Today")); gtk_container_add (GTK_CONTAINER (bbox), priv->today_button); gtk_widget_show (priv->today_button); g_signal_connect ( priv->today_button, "clicked", G_CALLBACK (on_date_popup_today_button_clicked), dedit); /* Note that we don't show this here, since by default a 'None' date * is not permitted. */ priv->none_button = gtk_button_new_with_mnemonic (_("_None")); gtk_container_add (GTK_CONTAINER (bbox), priv->none_button); g_signal_connect ( priv->none_button, "clicked", G_CALLBACK (on_date_popup_none_button_clicked), dedit); g_object_bind_property ( dedit, "allow-no-date-set", priv->none_button, "visible", G_BINDING_SYNC_CREATE); } /* GtkWidget::mnemonic_activate() handler for the EDateEdit */ static gboolean e_date_edit_mnemonic_activate (GtkWidget *widget, gboolean group_cycling) { e_date_edit_grab_focus (widget); return TRUE; } /* Grab_focus handler for the EDateEdit. If the date field is being shown, we * grab the focus to that, otherwise we grab it to the time field. */ static void e_date_edit_grab_focus (GtkWidget *widget) { EDateEdit *dedit; GtkWidget *child; g_return_if_fail (E_IS_DATE_EDIT (widget)); dedit = E_DATE_EDIT (widget); child = gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo)); if (dedit->priv->show_date) gtk_widget_grab_focus (dedit->priv->date_entry); else gtk_widget_grab_focus (child); } /** * e_date_edit_set_editable: * @dedit: an #EDateEdit * @editable: whether or not the widget should accept edits. * * Allows the programmer to disallow editing (and the popping up of * the calendar widget), while still allowing the user to select the * date from the GtkEntry. */ void e_date_edit_set_editable (EDateEdit *dedit, gboolean editable) { EDateEditPrivate *priv; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; gtk_editable_set_editable (GTK_EDITABLE (priv->date_entry), editable); gtk_widget_set_sensitive (priv->date_button, editable); } /** * e_date_edit_get_time: * @dedit: an #EDateEdit * * Returns the last valid time entered. If empty times are valid, by calling * e_date_edit_set_allow_no_date_set(), then it may return -1. * * Note that the last time entered may actually have been invalid. You can * check this with e_date_edit_time_is_valid(). * * Returns: the last valid time entered, or -1 if the time is not set. */ time_t e_date_edit_get_time (EDateEdit *dedit) { EDateEditPrivate *priv; struct tm tmp_tm = { 0 }; g_return_val_if_fail (E_IS_DATE_EDIT (dedit), -1); priv = dedit->priv; /* Try to parse any new value now. */ e_date_edit_check_date_changed (dedit); e_date_edit_check_time_changed (dedit); if (priv->date_set_to_none) return -1; tmp_tm.tm_year = priv->year; tmp_tm.tm_mon = priv->month; tmp_tm.tm_mday = priv->day; if (!priv->show_time || priv->time_set_to_none) { tmp_tm.tm_hour = 0; tmp_tm.tm_min = 0; } else { tmp_tm.tm_hour = priv->hour; tmp_tm.tm_min = priv->minute; } tmp_tm.tm_sec = 0; tmp_tm.tm_isdst = -1; return mktime (&tmp_tm); } /** * e_date_edit_set_time: * @dedit: an #EDateEdit * @the_time: The time and date that should be set on the widget * * Description: Changes the displayed date and time in the EDateEdit * widget to be the one represented by @the_time. If @the_time is 0 * then current time is used. If it is -1, then the date is set to None. * * Note that the time is converted to local time using the Unix timezone, * so if you are using your own timezones then you should use * e_date_edit_set_date() and e_date_edit_set_time_of_day() instead. */ void e_date_edit_set_time (EDateEdit *dedit, time_t the_time) { EDateEditPrivate *priv; struct tm tmp_tm; gboolean date_changed = FALSE, time_changed = FALSE; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; if (the_time == -1) { date_changed = e_date_edit_set_date_internal ( dedit, TRUE, TRUE, 0, 0, 0); time_changed = e_date_edit_set_time_internal ( dedit, TRUE, TRUE, 0, 0); } else { if (the_time == 0) { if (priv->time_callback) { tmp_tm = (*priv->time_callback) (dedit, priv->time_callback_data); } else { the_time = time (NULL); tmp_tm = *localtime (&the_time); } } else { tmp_tm = *localtime (&the_time); } date_changed = e_date_edit_set_date_internal ( dedit, TRUE, FALSE, tmp_tm.tm_year, tmp_tm.tm_mon, tmp_tm.tm_mday); time_changed = e_date_edit_set_time_internal ( dedit, TRUE, FALSE, tmp_tm.tm_hour, tmp_tm.tm_min); } e_date_edit_update_date_entry (dedit); e_date_edit_update_time_entry (dedit); e_date_edit_update_time_combo_state (dedit); /* Emit the signals if the date and/or time has actually changed. */ if (date_changed || time_changed) g_signal_emit (dedit, signals[CHANGED], 0); } /** * e_date_edit_get_date: * @dedit: an #EDateEdit * @year: returns the year set. * @month: returns the month set (1 - 12). * @day: returns the day set (1 - 31). * @Returns: TRUE if a time was set, or FALSE if the field is empty or 'None'. * * Returns the last valid date entered into the date field. */ gboolean e_date_edit_get_date (EDateEdit *dedit, gint *year, gint *month, gint *day) { EDateEditPrivate *priv; g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); priv = dedit->priv; /* Try to parse any new value now. */ e_date_edit_check_date_changed (dedit); *year = priv->year + 1900; *month = priv->month + 1; *day = priv->day; if (priv->date_set_to_none && e_date_edit_get_allow_no_date_set (dedit)) return FALSE; return TRUE; } /** * e_date_edit_set_date: * @dedit: an #EDateEdit * @year: the year to set. * @month: the month to set (1 - 12). * @day: the day to set (1 - 31). * * Sets the date in the date field. */ void e_date_edit_set_date (EDateEdit *dedit, gint year, gint month, gint day) { gboolean date_changed = FALSE; g_return_if_fail (E_IS_DATE_EDIT (dedit)); date_changed = e_date_edit_set_date_internal ( dedit, TRUE, FALSE, year - 1900, month - 1, day); e_date_edit_update_date_entry (dedit); e_date_edit_update_time_combo_state (dedit); /* Emit the signals if the date has actually changed. */ if (date_changed) g_signal_emit (dedit, signals[CHANGED], 0); } /** * e_date_edit_get_time_of_day: * @dedit: an #EDateEdit * @hour: returns the hour set, or 0 if the time isn't set. * @minute: returns the minute set, or 0 if the time isn't set. * @Returns: TRUE if a time was set, or FALSE if the field is empty or 'None'. * * Returns the last valid time entered into the time field. */ gboolean e_date_edit_get_time_of_day (EDateEdit *dedit, gint *hour, gint *minute) { EDateEditPrivate *priv; g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); priv = dedit->priv; /* Try to parse any new value now. */ e_date_edit_check_time_changed (dedit); if (priv->time_set_to_none) { *hour = 0; *minute = 0; return FALSE; } else { *hour = priv->hour; *minute = priv->minute; return TRUE; } } /** * e_date_edit_set_time_of_day: * @dedit: an #EDateEdit * @hour: the hour to set, or -1 to set the time to None (i.e. empty). * @minute: the minute to set. * * Description: Sets the time in the time field. */ void e_date_edit_set_time_of_day (EDateEdit *dedit, gint hour, gint minute) { EDateEditPrivate *priv; gboolean time_changed = FALSE; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; if (hour == -1) { gboolean allow_no_date_set = e_date_edit_get_allow_no_date_set (dedit); g_return_if_fail (allow_no_date_set); if (!priv->time_set_to_none) { priv->time_set_to_none = TRUE; time_changed = TRUE; } } else if (priv->time_set_to_none || priv->hour != hour || priv->minute != minute) { priv->time_set_to_none = FALSE; priv->hour = hour; priv->minute = minute; time_changed = TRUE; } e_date_edit_update_time_entry (dedit); if (time_changed) g_signal_emit (dedit, signals[CHANGED], 0); } void e_date_edit_set_date_and_time_of_day (EDateEdit *dedit, gint year, gint month, gint day, gint hour, gint minute) { gboolean date_changed, time_changed; g_return_if_fail (E_IS_DATE_EDIT (dedit)); date_changed = e_date_edit_set_date_internal ( dedit, TRUE, FALSE, year - 1900, month - 1, day); time_changed = e_date_edit_set_time_internal ( dedit, TRUE, FALSE, hour, minute); e_date_edit_update_date_entry (dedit); e_date_edit_update_time_entry (dedit); e_date_edit_update_time_combo_state (dedit); if (date_changed || time_changed) g_signal_emit (dedit, signals[CHANGED], 0); } /** * e_date_edit_get_show_date: * @dedit: an #EDateEdit * @Returns: Whether the date field is shown. * * Description: Returns TRUE if the date field is currently shown. */ gboolean e_date_edit_get_show_date (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE); return dedit->priv->show_date; } /** * e_date_edit_set_show_date: * @dedit: an #EDateEdit * @show_date: TRUE if the date field should be shown. * * Description: Specifies whether the date field should be shown. The date * field would be hidden if only a time needed to be entered. */ void e_date_edit_set_show_date (EDateEdit *dedit, gboolean show_date) { EDateEditPrivate *priv; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; if (priv->show_date == show_date) return; priv->show_date = show_date; if (show_date) { gtk_widget_show (priv->date_entry); gtk_widget_show (priv->date_button); } else { gtk_widget_hide (priv->date_entry); gtk_widget_hide (priv->date_button); } e_date_edit_update_time_combo_state (dedit); if (priv->show_date && (priv->show_time || priv->make_time_insensitive)) gtk_widget_show (priv->space); else gtk_widget_hide (priv->space); g_object_notify (G_OBJECT (dedit), "show-date"); } /** * e_date_edit_get_show_time: * @dedit: an #EDateEdit * @Returns: Whether the time field is shown. * * Description: Returns TRUE if the time field is currently shown. */ gboolean e_date_edit_get_show_time (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE); return dedit->priv->show_time; } /** * e_date_edit_set_show_time: * @dedit: an #EDateEdit * @show_time: TRUE if the time field should be shown. * * Description: Specifies whether the time field should be shown. The time * field would be hidden if only a date needed to be entered. */ void e_date_edit_set_show_time (EDateEdit *dedit, gboolean show_time) { EDateEditPrivate *priv; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; if (priv->show_time == show_time) return; priv->show_time = show_time; e_date_edit_update_time_combo_state (dedit); g_object_notify (G_OBJECT (dedit), "show-time"); } /** * e_date_edit_get_make_time_insensitive: * @dedit: an #EDateEdit * @Returns: Whether the time field is be made insensitive instead of hiding * it. * * Description: Returns TRUE if the time field is made insensitive instead of * hiding it. */ gboolean e_date_edit_get_make_time_insensitive (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE); return dedit->priv->make_time_insensitive; } /** * e_date_edit_set_make_time_insensitive: * @dedit: an #EDateEdit * @make_insensitive: TRUE if the time field should be made insensitive instead * of hiding it. * * Description: Specifies whether the time field should be made insensitive * rather than hiding it. Note that this doesn't make it insensitive - you * need to call e_date_edit_set_show_time() with FALSE as show_time to do that. * * This is useful if you want to disable the time field, but don't want it to * disappear as that may affect the layout of the widgets. */ void e_date_edit_set_make_time_insensitive (EDateEdit *dedit, gboolean make_insensitive) { EDateEditPrivate *priv; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; if (priv->make_time_insensitive == make_insensitive) return; priv->make_time_insensitive = make_insensitive; e_date_edit_update_time_combo_state (dedit); } /** * e_date_edit_get_week_start_day: * @dedit: an #EDateEdit * * Returns the week start day currently used in the calendar popup. * * Returns: a #GDateWeekday */ GDateWeekday e_date_edit_get_week_start_day (EDateEdit *dedit) { GDateWeekday week_start_day; g_return_val_if_fail (E_IS_DATE_EDIT (dedit), 1); g_object_get ( E_CALENDAR (dedit->priv->calendar)->calitem, "week-start-day", &week_start_day, NULL); return week_start_day; } /** * e_date_edit_set_week_start_day: * @dedit: an #EDateEdit * @week_start_day: a #GDateWeekday * * Sets the week start day to use in the calendar popup. */ void e_date_edit_set_week_start_day (EDateEdit *dedit, GDateWeekday week_start_day) { g_return_if_fail (E_IS_DATE_EDIT (dedit)); g_return_if_fail (g_date_valid_weekday (week_start_day)); gnome_canvas_item_set ( GNOME_CANVAS_ITEM (E_CALENDAR (dedit->priv->calendar)->calitem), "week-start-day", week_start_day, NULL); g_object_notify (G_OBJECT (dedit), "week-start-day"); } /* Whether we show week numbers in the date popup. */ gboolean e_date_edit_get_show_week_numbers (EDateEdit *dedit) { gboolean show_week_numbers; g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); g_object_get ( E_CALENDAR (dedit->priv->calendar)->calitem, "show_week_numbers", &show_week_numbers, NULL); return show_week_numbers; } void e_date_edit_set_show_week_numbers (EDateEdit *dedit, gboolean show_week_numbers) { g_return_if_fail (E_IS_DATE_EDIT (dedit)); gnome_canvas_item_set ( GNOME_CANVAS_ITEM (E_CALENDAR (dedit->priv->calendar)->calitem), "show_week_numbers", show_week_numbers, NULL); g_object_notify (G_OBJECT (dedit), "show-week-numbers"); } /* Whether we use 24 hour format in the time field & popup. */ gboolean e_date_edit_get_use_24_hour_format (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE); return dedit->priv->use_24_hour_format; } void e_date_edit_set_use_24_hour_format (EDateEdit *dedit, gboolean use_24_hour_format) { g_return_if_fail (E_IS_DATE_EDIT (dedit)); if (dedit->priv->use_24_hour_format == use_24_hour_format) return; dedit->priv->use_24_hour_format = use_24_hour_format; rebuild_time_popup (dedit); e_date_edit_update_time_entry (dedit); g_object_notify (G_OBJECT (dedit), "use-24-hour-format"); } /* Whether we allow the date to be set to 'None'. e_date_edit_get_time() will * return (time_t) -1 in this case. */ gboolean e_date_edit_get_allow_no_date_set (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); return dedit->priv->allow_no_date_set; } void e_date_edit_set_allow_no_date_set (EDateEdit *dedit, gboolean allow_no_date_set) { g_return_if_fail (E_IS_DATE_EDIT (dedit)); if (dedit->priv->allow_no_date_set == allow_no_date_set) return; dedit->priv->allow_no_date_set = allow_no_date_set; if (!allow_no_date_set) { /* If the date is showing, we make sure it isn't 'None' (we * don't really mind if the time is empty), else if just the * time is showing we make sure it isn't 'None'. */ if (dedit->priv->show_date) { if (dedit->priv->date_set_to_none) e_date_edit_set_time (dedit, 0); } else { if (dedit->priv->time_set_to_none) e_date_edit_set_time (dedit, 0); } } g_object_notify (G_OBJECT (dedit), "allow-no-date-set"); } /* The range of time to show in the time combo popup. */ void e_date_edit_get_time_popup_range (EDateEdit *dedit, gint *lower_hour, gint *upper_hour) { g_return_if_fail (E_IS_DATE_EDIT (dedit)); *lower_hour = dedit->priv->lower_hour; *upper_hour = dedit->priv->upper_hour; } void e_date_edit_set_time_popup_range (EDateEdit *dedit, gint lower_hour, gint upper_hour) { EDateEditPrivate *priv; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; if (priv->lower_hour == lower_hour && priv->upper_hour == upper_hour) return; priv->lower_hour = lower_hour; priv->upper_hour = upper_hour; rebuild_time_popup (dedit); /* Setting the combo list items seems to mess up the time entry, so * we set it again. We have to reset it to its last valid time. */ priv->time_is_valid = TRUE; e_date_edit_update_time_entry (dedit); } /* The arrow button beside the date field has been clicked, so we show the * popup with the ECalendar in. */ static void on_date_button_clicked (GtkWidget *widget, EDateEdit *dedit) { GdkEvent *event; /* Obtain the GdkEvent that triggered * the date button's "clicked" signal. */ event = gtk_get_current_event (); e_date_edit_show_date_popup (dedit, event); } static void e_date_edit_show_date_popup (EDateEdit *dedit, GdkEvent *event) { EDateEditPrivate *priv; ECalendar *calendar; GdkDevice *event_device; GdkDevice *assoc_device; GdkDevice *keyboard_device; GdkDevice *pointer_device; GdkWindow *window; GdkGrabStatus grab_status; struct tm mtm; const gchar *date_text; GDate selected_day; gboolean clear_selection = FALSE; guint event_time; priv = dedit->priv; calendar = E_CALENDAR (priv->calendar); date_text = gtk_entry_get_text (GTK_ENTRY (priv->date_entry)); if (field_set_to_none (date_text) || !e_date_edit_parse_date (dedit, date_text, &mtm)) clear_selection = TRUE; if (clear_selection) { e_calendar_item_set_selection (calendar->calitem, NULL, NULL); } else { g_date_clear (&selected_day, 1); g_date_set_dmy ( &selected_day, mtm.tm_mday, mtm.tm_mon + 1, mtm.tm_year + 1900); e_calendar_item_set_selection ( calendar->calitem, &selected_day, NULL); } /* FIXME: Hack. Change ECalendarItem so it doesn't queue signal * emissions. */ calendar->calitem->selection_changed = FALSE; position_date_popup (dedit); gtk_widget_show (priv->cal_popup); gtk_widget_grab_focus (priv->cal_popup); gtk_grab_add (priv->cal_popup); window = gtk_widget_get_window (priv->cal_popup); g_return_if_fail (priv->grabbed_keyboard == NULL); g_return_if_fail (priv->grabbed_pointer == NULL); event_device = gdk_event_get_device (event); assoc_device = gdk_device_get_associated_device (event_device); event_time = gdk_event_get_time (event); if (gdk_device_get_source (event_device) == GDK_SOURCE_KEYBOARD) { keyboard_device = event_device; pointer_device = assoc_device; } else { keyboard_device = assoc_device; pointer_device = event_device; } if (keyboard_device != NULL) { grab_status = gdk_device_grab ( keyboard_device, window, GDK_OWNERSHIP_WINDOW, TRUE, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, NULL, event_time); if (grab_status == GDK_GRAB_SUCCESS) { priv->grabbed_keyboard = g_object_ref (keyboard_device); } } if (pointer_device != NULL) { grab_status = gdk_device_grab ( pointer_device, window, GDK_OWNERSHIP_WINDOW, TRUE, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, NULL, event_time); if (grab_status == GDK_GRAB_SUCCESS) { priv->grabbed_pointer = g_object_ref (pointer_device); } else if (priv->grabbed_keyboard != NULL) { gdk_device_ungrab ( priv->grabbed_keyboard, event_time); g_object_unref (priv->grabbed_keyboard); priv->grabbed_keyboard = NULL; } } gdk_window_focus (window, event_time); } /* This positions the date popup below and to the left of the arrow button, * just before it is shown. */ static void position_date_popup (EDateEdit *dedit) { gint x, y; gint win_x, win_y; gint bwidth, bheight; GtkWidget *toplevel; GdkWindow *window; GtkRequisition cal_req, button_req; gint screen_width, screen_height; gtk_widget_get_preferred_size (dedit->priv->cal_popup, &cal_req, NULL); gtk_widget_get_preferred_size (dedit->priv->date_button, &button_req, NULL); bwidth = button_req.width; gtk_widget_get_preferred_size ( gtk_widget_get_parent (dedit->priv->date_button), &button_req, NULL); bheight = button_req.height; gtk_widget_translate_coordinates ( dedit->priv->date_button, gtk_widget_get_toplevel (dedit->priv->date_button), bwidth - cal_req.width, bheight, &x, &y); toplevel = gtk_widget_get_toplevel (dedit->priv->date_button); window = gtk_widget_get_window (toplevel); gdk_window_get_origin (window, &win_x, &win_y); x += win_x; y += win_y; screen_width = gdk_screen_width (); screen_height = gdk_screen_height (); x = CLAMP (x, 0, MAX (0, screen_width - cal_req.width)); y = CLAMP (y, 0, MAX (0, screen_height - cal_req.height)); gtk_window_move (GTK_WINDOW (dedit->priv->cal_popup), x, y); } /* A date has been selected in the date popup, so we set the date field * and hide the popup. */ static void on_date_popup_date_selected (ECalendarItem *calitem, EDateEdit *dedit) { GDate start_date, end_date; hide_date_popup (dedit); if (!e_calendar_item_get_selection (calitem, &start_date, &end_date)) return; e_date_edit_set_date ( dedit, g_date_get_year (&start_date), g_date_get_month (&start_date), g_date_get_day (&start_date)); } static void on_date_popup_now_button_clicked (GtkWidget *button, EDateEdit *dedit) { hide_date_popup (dedit); e_date_edit_set_time (dedit, 0); } static void on_date_popup_today_button_clicked (GtkWidget *button, EDateEdit *dedit) { EDateEditPrivate *priv; struct tm tmp_tm; time_t t; priv = dedit->priv; hide_date_popup (dedit); if (priv->time_callback) { tmp_tm = (*priv->time_callback) (dedit, priv->time_callback_data); } else { t = time (NULL); tmp_tm = *localtime (&t); } e_date_edit_set_date ( dedit, tmp_tm.tm_year + 1900, tmp_tm.tm_mon + 1, tmp_tm.tm_mday); } static void on_date_popup_none_button_clicked (GtkWidget *button, EDateEdit *dedit) { hide_date_popup (dedit); e_date_edit_set_time (dedit, -1); } /* A key has been pressed while the date popup is showing. If it is the Escape * key we hide the popup. */ static gint on_date_popup_key_press (GtkWidget *widget, GdkEventKey *event, EDateEdit *dedit) { if (event->keyval == GDK_KEY_Escape) { g_signal_stop_emission_by_name (widget, "key_press_event"); hide_date_popup (dedit); return TRUE; } return FALSE; } /* A mouse button has been pressed while the date popup is showing. * Any button press events used to select days etc. in the popup will have * have been handled elsewhere, so here we just hide the popup. * (This function is yanked from gtkcombo.c) */ static gint on_date_popup_button_press (GtkWidget *widget, GdkEvent *button_event, gpointer data) { EDateEdit *dedit; GtkWidget *child; dedit = data; child = gtk_get_event_widget (button_event); /* We don't ask for button press events on the grab widget, so * if an event is reported directly to the grab widget, it must * be on a window outside the application (and thus we remove * the popup window). Otherwise, we check if the widget is a child * of the grab widget, and only remove the popup window if it * is not. */ if (child != widget) { while (child) { if (child == widget) return FALSE; child = gtk_widget_get_parent (child); } } hide_date_popup (dedit); return TRUE; } /* A delete event has been received for the date popup, so we hide it and * return TRUE so it doesn't get destroyed. */ static gint on_date_popup_delete_event (GtkWidget *widget, EDateEdit *dedit) { hide_date_popup (dedit); return TRUE; } /* Hides the date popup, removing any grabs. */ static void hide_date_popup (EDateEdit *dedit) { gtk_widget_hide (dedit->priv->cal_popup); gtk_grab_remove (dedit->priv->cal_popup); if (dedit->priv->grabbed_keyboard != NULL) { gdk_device_ungrab ( dedit->priv->grabbed_keyboard, GDK_CURRENT_TIME); g_object_unref (dedit->priv->grabbed_keyboard); dedit->priv->grabbed_keyboard = NULL; } if (dedit->priv->grabbed_pointer != NULL) { gdk_device_ungrab ( dedit->priv->grabbed_pointer, GDK_CURRENT_TIME); g_object_unref (dedit->priv->grabbed_pointer); dedit->priv->grabbed_pointer = NULL; } } /* some locales may not define am/pm equivalents for '%p', thus force 24 hour format for these, otherwise the am/pm time clashes */ static gboolean date_edit_use_24_hour_format (gboolean use_24_hour_format) { struct tm tmp_tm = { 0 }; gchar buffer[40]; if (use_24_hour_format) return TRUE; /* Fill the struct tm with some sane values. */ tmp_tm.tm_year = 2000; tmp_tm.tm_mon = 0; tmp_tm.tm_mday = 1; tmp_tm.tm_sec = 0; tmp_tm.tm_isdst = 0; tmp_tm.tm_hour = 1; tmp_tm.tm_min = 0; if (e_utf8_strftime (buffer, sizeof (buffer), "%p", &tmp_tm) == 0) return TRUE; tmp_tm.tm_hour = 13; tmp_tm.tm_min = 0; if (e_utf8_strftime (buffer, sizeof (buffer), "%p", &tmp_tm) == 0) return TRUE; return use_24_hour_format; } /* Clears the time popup and rebuilds it using the lower_hour, upper_hour * and use_24_hour_format settings. */ static void rebuild_time_popup (EDateEdit *dedit) { EDateEditPrivate *priv; GtkTreeModel *model; GtkListStore *list_store; GtkTreeIter iter; gchar buffer[40]; gboolean use_24_hour_format; struct tm tmp_tm = { 0 }; gint hour, min; priv = dedit->priv; model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->time_combo)); list_store = GTK_LIST_STORE (model); gtk_list_store_clear (list_store); /* Fill the struct tm with some sane values. */ tmp_tm.tm_year = 2000; tmp_tm.tm_mon = 0; tmp_tm.tm_mday = 1; tmp_tm.tm_sec = 0; tmp_tm.tm_isdst = 0; use_24_hour_format = date_edit_use_24_hour_format (priv->use_24_hour_format); for (hour = priv->lower_hour; hour <= priv->upper_hour; hour++) { /* We don't want to display midnight at the end, * since that is really in the next day. */ if (hour == 24) break; /* We want to finish on upper_hour, with min == 0. */ for (min = 0; min == 0 || (min < 60 && hour != priv->upper_hour); min += 30) { tmp_tm.tm_hour = hour; tmp_tm.tm_min = min; e_time_format_time ( &tmp_tm, use_24_hour_format, 0, buffer, sizeof (buffer)); /* For 12-hour am/pm format, we want space padding, * not zero padding. This can be done with strftime's * %l, but it's a potentially unportable extension. */ if (use_24_hour_format && buffer[0] == '0') buffer[0] = ' '; gtk_list_store_append (list_store, &iter); gtk_list_store_set (list_store, &iter, 0, buffer, -1); } } } static gboolean e_date_edit_parse_date (EDateEdit *dedit, const gchar *date_text, struct tm *date_tm) { gboolean twodigit_year = FALSE; if (e_time_parse_date_ex (date_text, date_tm, &twodigit_year) != E_TIME_PARSE_OK) return FALSE; if (twodigit_year && !dedit->priv->twodigit_year_can_future) { time_t t = time (NULL); struct tm *today_tm = localtime (&t); /* It was only 2 digit year in dedit and it was interpreted as * in the future, but we don't want it as this, so decrease by * 100 years to last century. */ if (date_tm->tm_year > today_tm->tm_year) date_tm->tm_year -= 100; } return TRUE; } static gboolean e_date_edit_parse_time (EDateEdit *dedit, const gchar *time_text, struct tm *time_tm) { if (field_set_to_none (time_text)) { time_tm->tm_hour = 0; time_tm->tm_min = 0; return TRUE; } if (e_time_parse_time (time_text, time_tm) != E_TIME_PARSE_OK) return FALSE; return TRUE; } /* Returns TRUE if the string is empty or is "None" in the current locale. * It ignores whitespace. */ static gboolean field_set_to_none (const gchar *text) { const gchar *pos; const gchar *none_string; gint n; pos = text; while (n = (gint)((guchar) * pos), isspace (n)) pos++; /* Translators: "None" for date field of a date edit, shown when * there is no date set. */ none_string = C_("date", "None"); if (*pos == '\0' || !strncmp (pos, none_string, strlen (none_string))) return TRUE; return FALSE; } static void on_date_edit_time_selected (GtkComboBox *combo, EDateEdit *dedit) { GtkWidget *child; child = gtk_bin_get_child (GTK_BIN (combo)); /* We only want to emit signals when an item is selected explicitly, * not when it is selected by the silly combo update thing. */ if (gtk_combo_box_get_active (combo) == -1) return; if (!gtk_widget_get_mapped (child)) return; e_date_edit_check_time_changed (dedit); } static gint on_date_entry_key_press (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit) { GdkModifierType event_state = 0; guint event_keyval = 0; gdk_event_get_keyval (key_event, &event_keyval); gdk_event_get_state (key_event, &event_state); if (event_state & GDK_MOD1_MASK && (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down || event_keyval == GDK_KEY_Return)) { g_signal_stop_emission_by_name (widget, "key_press_event"); e_date_edit_show_date_popup (dedit, key_event); return TRUE; } /* If the user hits the return key emit a "date_changed" signal if * needed. But let the signal carry on. */ if (event_keyval == GDK_KEY_Return) { e_date_edit_check_date_changed (dedit); return FALSE; } return FALSE; } static gint on_time_entry_key_press (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit) { GtkWidget *child; GdkModifierType event_state = 0; guint event_keyval = 0; gdk_event_get_keyval (key_event, &event_keyval); gdk_event_get_state (key_event, &event_state); child = gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo)); /* I'd like to use Alt+Up/Down for popping up the list, like Win32, * but the combo steals any Up/Down keys, so we use Alt + Return. */ #if 0 if (event_state & GDK_MOD1_MASK && (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down)) { #else if (event_state & GDK_MOD1_MASK && event_keyval == GDK_KEY_Return) { #endif g_signal_stop_emission_by_name (widget, "key_press_event"); g_signal_emit_by_name (child, "activate", 0); return TRUE; } /* Stop the return key from emitting the activate signal, and check * if we need to emit a "time_changed" signal. */ if (event_keyval == GDK_KEY_Return) { g_signal_stop_emission_by_name (widget, "key_press_event"); e_date_edit_check_time_changed (dedit); return TRUE; } return FALSE; } static gint on_date_entry_key_release (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit) { e_date_edit_check_date_changed (dedit); return TRUE; } static gint on_time_entry_key_release (GtkWidget *widget, GdkEvent *key_event, EDateEdit *dedit) { guint event_keyval = 0; gdk_event_get_keyval (key_event, &event_keyval); if (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down) { g_signal_stop_emission_by_name (widget, "key_release_event"); e_date_edit_check_time_changed (dedit); return TRUE; } return FALSE; } static gint on_date_entry_focus_out (GtkEntry *entry, GdkEventFocus *event, EDateEdit *dedit) { struct tm tmp_tm; GtkWidget *msg_dialog; tmp_tm.tm_year = 0; tmp_tm.tm_mon = 0; tmp_tm.tm_mday = 0; e_date_edit_check_date_changed (dedit); if (!e_date_edit_date_is_valid (dedit)) { msg_dialog = gtk_message_dialog_new ( NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", _("Invalid Date Value")); gtk_dialog_run (GTK_DIALOG (msg_dialog)); gtk_widget_destroy (msg_dialog); e_date_edit_get_date ( dedit, &tmp_tm.tm_year, &tmp_tm.tm_mon, &tmp_tm.tm_mday); e_date_edit_set_date ( dedit, tmp_tm.tm_year, tmp_tm.tm_mon, tmp_tm.tm_mday); gtk_widget_grab_focus (GTK_WIDGET (entry)); return FALSE; } else if (e_date_edit_get_date ( dedit, &tmp_tm.tm_year, &tmp_tm.tm_mon, &tmp_tm.tm_mday)) { e_date_edit_set_date ( dedit,tmp_tm.tm_year,tmp_tm.tm_mon,tmp_tm.tm_mday); if (dedit->priv->has_been_changed) { /* The previous one didn't emit changed signal, * but we want it even here, thus doing itself. */ g_signal_emit (dedit, signals[CHANGED], 0); dedit->priv->has_been_changed = FALSE; } } else { dedit->priv->date_set_to_none = TRUE; e_date_edit_update_date_entry (dedit); } return FALSE; } static gint on_time_entry_focus_out (GtkEntry *entry, GdkEventFocus *event, EDateEdit *dedit) { GtkWidget *msg_dialog; e_date_edit_check_time_changed (dedit); if (!e_date_edit_time_is_valid (dedit)) { msg_dialog = gtk_message_dialog_new ( NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", _("Invalid Time Value")); gtk_dialog_run (GTK_DIALOG (msg_dialog)); gtk_widget_destroy (msg_dialog); e_date_edit_set_time (dedit,e_date_edit_get_time (dedit)); gtk_widget_grab_focus (GTK_WIDGET (entry)); return FALSE; } return FALSE; } static void add_relation (EDateEdit *dedit, GtkWidget *widget) { AtkObject *a11yEdit, *a11yWidget; AtkRelationSet *set; AtkRelation *relation; GPtrArray *target; gpointer target_object; /* add a labelled_by relation for widget for accessibility */ a11yEdit = gtk_widget_get_accessible (GTK_WIDGET (dedit)); a11yWidget = gtk_widget_get_accessible (widget); set = atk_object_ref_relation_set (a11yWidget); if (set != NULL) { relation = atk_relation_set_get_relation_by_type ( set, ATK_RELATION_LABELLED_BY); /* check whether has a labelled_by relation already */ if (relation != NULL) return; } set = atk_object_ref_relation_set (a11yEdit); if (!set) return; relation = atk_relation_set_get_relation_by_type ( set, ATK_RELATION_LABELLED_BY); if (relation != NULL) { target = atk_relation_get_target (relation); target_object = g_ptr_array_index (target, 0); if (ATK_IS_OBJECT (target_object)) { atk_object_add_relationship ( a11yWidget, ATK_RELATION_LABELLED_BY, ATK_OBJECT (target_object)); } } } /* This sets the text in the date entry according to the current settings. */ static void e_date_edit_update_date_entry (EDateEdit *dedit) { EDateEditPrivate *priv; gchar buffer[100]; struct tm tmp_tm = { 0 }; priv = dedit->priv; if (priv->date_set_to_none || !priv->date_is_valid) { gtk_entry_set_text (GTK_ENTRY (priv->date_entry), C_("date", "None")); } else { /* This is a strftime() format for a short date. * %x the preferred date representation for the current locale * without the time, but is forced to use 4 digit year. */ gchar *format = e_time_get_d_fmt_with_4digit_year (); time_t tt; tmp_tm.tm_year = priv->year; tmp_tm.tm_mon = priv->month; tmp_tm.tm_mday = priv->day; tmp_tm.tm_isdst = -1; /* initialize all 'struct tm' members properly */ tt = mktime (&tmp_tm); if (tt && localtime (&tt)) tmp_tm = *localtime (&tt); e_utf8_strftime (buffer, sizeof (buffer), format, &tmp_tm); g_free (format); gtk_entry_set_text (GTK_ENTRY (priv->date_entry), buffer); } add_relation (dedit, priv->date_entry); add_relation (dedit, priv->date_button); } /* This sets the text in the time entry according to the current settings. */ static void e_date_edit_update_time_entry (EDateEdit *dedit) { EDateEditPrivate *priv; GtkComboBox *combo_box; GtkWidget *child; gchar buffer[40]; struct tm tmp_tm = { 0 }; priv = dedit->priv; combo_box = GTK_COMBO_BOX (priv->time_combo); child = gtk_bin_get_child (GTK_BIN (priv->time_combo)); if (priv->time_set_to_none || !priv->time_is_valid) { gtk_combo_box_set_active (combo_box, -1); gtk_entry_set_text (GTK_ENTRY (child), ""); } else { GtkTreeModel *model; GtkTreeIter iter; gboolean valid, use_24_hour_format; gchar *b; /* Set these to reasonable values just in case. */ tmp_tm.tm_year = 2000; tmp_tm.tm_mon = 0; tmp_tm.tm_mday = 1; tmp_tm.tm_hour = priv->hour; tmp_tm.tm_min = priv->minute; tmp_tm.tm_sec = 0; tmp_tm.tm_isdst = -1; use_24_hour_format = date_edit_use_24_hour_format (priv->use_24_hour_format); e_time_format_time ( &tmp_tm, use_24_hour_format, 0, buffer, sizeof (buffer)); /* For 12-hour am/pm format, we want space padding, not * zero padding. This can be done with strftime's %l, * but it's a potentially unportable extension. */ if (!use_24_hour_format && buffer[0] == '0') buffer[0] = ' '; gtk_entry_set_text (GTK_ENTRY (child), buffer); /* truncate left spaces */ b = buffer; while (*b == ' ') b++; model = gtk_combo_box_get_model (combo_box); valid = gtk_tree_model_get_iter_first (model, &iter); while (valid) { gchar *text = NULL; gtk_tree_model_get (model, &iter, 0, &text, -1); if (text) { gchar *t = text; /* truncate left spaces */ while (*t == ' ') t++; if (strcmp (b, t) == 0) { gtk_combo_box_set_active_iter ( combo_box, &iter); g_free (text); break; } } g_free (text); valid = gtk_tree_model_iter_next (model, &iter); } } add_relation (dedit, priv->time_combo); } static void e_date_edit_update_time_combo_state (EDateEdit *dedit) { EDateEditPrivate *priv; gboolean show = TRUE, show_now_button = TRUE; gboolean clear_entry = FALSE, sensitive = TRUE; const gchar *text; priv = dedit->priv; /* If the date entry is currently shown, and it is set to None, * clear the time entry and disable the time combo. */ if (priv->show_date && priv->date_set_to_none) { clear_entry = TRUE; sensitive = FALSE; } if (!priv->show_time) { if (priv->make_time_insensitive) { clear_entry = TRUE; sensitive = FALSE; } else { show = FALSE; } show_now_button = FALSE; } if (clear_entry) { GtkWidget *child; /* Only clear it if it isn't empty already. */ child = gtk_bin_get_child (GTK_BIN (priv->time_combo)); text = gtk_entry_get_text (GTK_ENTRY (child)); if (text[0]) gtk_entry_set_text (GTK_ENTRY (child), ""); } gtk_widget_set_sensitive (priv->time_combo, sensitive); if (show) gtk_widget_show (priv->time_combo); else gtk_widget_hide (priv->time_combo); if (show_now_button) gtk_widget_show (priv->now_button); else gtk_widget_hide (priv->now_button); if (priv->show_date && (priv->show_time || priv->make_time_insensitive)) gtk_widget_show (priv->space); else gtk_widget_hide (priv->space); } /* Parses the date, and if it is different from the current settings it * updates the settings and emits a "date_changed" signal. */ static void e_date_edit_check_date_changed (EDateEdit *dedit) { EDateEditPrivate *priv; const gchar *date_text; struct tm tmp_tm; gboolean none = FALSE, valid = TRUE, date_changed = FALSE; priv = dedit->priv; tmp_tm.tm_year = 0; tmp_tm.tm_mon = 0; tmp_tm.tm_mday = 0; date_text = gtk_entry_get_text (GTK_ENTRY (priv->date_entry)); if (field_set_to_none (date_text)) { none = TRUE; } else if (!e_date_edit_parse_date (dedit, date_text, &tmp_tm)) { valid = FALSE; tmp_tm.tm_year = 0; tmp_tm.tm_mon = 0; tmp_tm.tm_mday = 0; } date_changed = e_date_edit_set_date_internal ( dedit, valid, none, tmp_tm.tm_year, tmp_tm.tm_mon, tmp_tm.tm_mday); if (date_changed) { priv->has_been_changed = TRUE; g_signal_emit (dedit, signals[CHANGED], 0); } } /* Parses the time, and if it is different from the current settings it * updates the settings and emits a "time_changed" signal. */ static void e_date_edit_check_time_changed (EDateEdit *dedit) { EDateEditPrivate *priv; GtkWidget *child; const gchar *time_text; struct tm tmp_tm; gboolean none = FALSE, valid = TRUE, time_changed; priv = dedit->priv; tmp_tm.tm_hour = 0; tmp_tm.tm_min = 0; child = gtk_bin_get_child (GTK_BIN (priv->time_combo)); time_text = gtk_entry_get_text (GTK_ENTRY (child)); if (field_set_to_none (time_text)) none = TRUE; else if (!e_date_edit_parse_time (dedit, time_text, &tmp_tm)) valid = FALSE; time_changed = e_date_edit_set_time_internal ( dedit, valid, none, tmp_tm.tm_hour, tmp_tm.tm_min); if (time_changed) { e_date_edit_update_time_entry (dedit); g_signal_emit (dedit, signals[CHANGED], 0); } } /** * e_date_edit_date_is_valid: * @dedit: an #EDateEdit * @Returns: TRUE if the last date entered was valid. * * Returns TRUE if the last date entered was valid. * * Note that if this returns FALSE, you can still use e_date_edit_get_time() * or e_date_edit_get_date() to get the last time or date entered which was * valid. */ gboolean e_date_edit_date_is_valid (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); if (!dedit->priv->date_is_valid) return FALSE; /* If the date is empty/None and that isn't permitted, return FALSE. */ if (dedit->priv->date_set_to_none && !e_date_edit_get_allow_no_date_set (dedit)) return FALSE; return TRUE; } /** * e_date_edit_time_is_valid: * @dedit: an #EDateEdit * @Returns: TRUE if the last time entered was valid. * * Returns TRUE if the last time entered was valid. * * Note that if this returns FALSE, you can still use e_date_edit_get_time() * or e_date_edit_get_time_of_day() to get the last time or time of the day * entered which was valid. */ gboolean e_date_edit_time_is_valid (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); if (!dedit->priv->time_is_valid) return FALSE; /* If the time is empty and that isn't permitted, return FALSE. * Note that we don't mind an empty time if the date field is shown * - in that case we just assume 0:00. */ if (dedit->priv->time_set_to_none && !dedit->priv->show_date && !e_date_edit_get_allow_no_date_set (dedit)) return FALSE; return TRUE; } static gboolean e_date_edit_set_date_internal (EDateEdit *dedit, gboolean valid, gboolean none, gint year, gint month, gint day) { EDateEditPrivate *priv; gboolean date_changed = FALSE; priv = dedit->priv; if (!valid) { /* Date is invalid. */ if (priv->date_is_valid) { priv->date_is_valid = FALSE; date_changed = TRUE; } } else if (none) { /* Date has been set to 'None'. */ if (!priv->date_is_valid || !priv->date_set_to_none) { priv->date_is_valid = TRUE; priv->date_set_to_none = TRUE; date_changed = TRUE; } } else { /* Date has been set to a specific date. */ if (!priv->date_is_valid || priv->date_set_to_none || priv->year != year || priv->month != month || priv->day != day) { priv->date_is_valid = TRUE; priv->date_set_to_none = FALSE; priv->year = year; priv->month = month; priv->day = day; date_changed = TRUE; } } return date_changed; } static gboolean e_date_edit_set_time_internal (EDateEdit *dedit, gboolean valid, gboolean none, gint hour, gint minute) { EDateEditPrivate *priv; gboolean time_changed = FALSE; priv = dedit->priv; if (!valid) { /* Time is invalid. */ if (priv->time_is_valid) { priv->time_is_valid = FALSE; time_changed = TRUE; } } else if (none) { /* Time has been set to empty/'None'. */ if (!priv->time_is_valid || !priv->time_set_to_none) { priv->time_is_valid = TRUE; priv->time_set_to_none = TRUE; time_changed = TRUE; } } else { /* Time has been set to a specific time. */ if (!priv->time_is_valid || priv->time_set_to_none || priv->hour != hour || priv->minute != minute) { priv->time_is_valid = TRUE; priv->time_set_to_none = FALSE; priv->hour = hour; priv->minute = minute; time_changed = TRUE; } } return time_changed; } gboolean e_date_edit_get_twodigit_year_can_future (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); return dedit->priv->twodigit_year_can_future; } void e_date_edit_set_twodigit_year_can_future (EDateEdit *dedit, gboolean value) { g_return_if_fail (E_IS_DATE_EDIT (dedit)); dedit->priv->twodigit_year_can_future = value; } /* Sets a callback to use to get the current time. This is useful if the * application needs to use its own timezone data rather than rely on the * Unix timezone. */ void e_date_edit_set_get_time_callback (EDateEdit *dedit, EDateEditGetTimeCallback cb, gpointer data, GDestroyNotify destroy) { EDateEditPrivate *priv; g_return_if_fail (E_IS_DATE_EDIT (dedit)); priv = dedit->priv; if (priv->time_callback_data && priv->time_callback_destroy) (*priv->time_callback_destroy) (priv->time_callback_data); priv->time_callback = cb; priv->time_callback_data = data; priv->time_callback_destroy = destroy; } GtkWidget * e_date_edit_get_entry (EDateEdit *dedit) { g_return_val_if_fail (E_IS_DATE_EDIT (dedit), NULL); return GTK_WIDGET (dedit->priv->date_entry); }