aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-cell-date-edit.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-cell-date-edit.c')
-rw-r--r--e-util/e-cell-date-edit.c1039
1 files changed, 1039 insertions, 0 deletions
diff --git a/e-util/e-cell-date-edit.c b/e-util/e-cell-date-edit.c
new file mode 100644
index 0000000000..4f35fbb266
--- /dev/null
+++ b/e-util/e-cell-date-edit.c
@@ -0,0 +1,1039 @@
+/*
+ * 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 the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Damon Chaplin <damon@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellDateEdit - a subclass of ECellPopup used to show a date with a popup
+ * window to edit it.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-cell-date-edit.h"
+
+#include <string.h>
+#include <time.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-calendar.h"
+#include "e-cell-text.h"
+#include "e-table-item.h"
+
+static void e_cell_date_edit_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void e_cell_date_edit_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void e_cell_date_edit_dispose (GObject *object);
+
+static gint e_cell_date_edit_do_popup (ECellPopup *ecp,
+ GdkEvent *event,
+ gint row,
+ gint view_col);
+static void e_cell_date_edit_set_popup_values (ECellDateEdit *ecde);
+static void e_cell_date_edit_select_matching_time (ECellDateEdit *ecde,
+ gchar *time);
+static void e_cell_date_edit_show_popup (ECellDateEdit *ecde,
+ gint row,
+ gint view_col);
+static void e_cell_date_edit_get_popup_pos (ECellDateEdit *ecde,
+ gint row,
+ gint view_col,
+ gint *x,
+ gint *y,
+ gint *height,
+ gint *width);
+
+static void e_cell_date_edit_rebuild_time_list (ECellDateEdit *ecde);
+
+static gint e_cell_date_edit_key_press (GtkWidget *popup_window,
+ GdkEventKey *event,
+ ECellDateEdit *ecde);
+static gint e_cell_date_edit_button_press (GtkWidget *popup_window,
+ GdkEvent *button_event,
+ ECellDateEdit *ecde);
+static void e_cell_date_edit_on_ok_clicked (GtkWidget *button,
+ ECellDateEdit *ecde);
+static void e_cell_date_edit_show_time_invalid_warning (ECellDateEdit *ecde);
+static void e_cell_date_edit_on_now_clicked (GtkWidget *button,
+ ECellDateEdit *ecde);
+static void e_cell_date_edit_on_none_clicked (GtkWidget *button,
+ ECellDateEdit *ecde);
+static void e_cell_date_edit_on_today_clicked (GtkWidget *button,
+ ECellDateEdit *ecde);
+static void e_cell_date_edit_update_cell (ECellDateEdit *ecde,
+ const gchar *text);
+static void e_cell_date_edit_on_time_selected (GtkTreeSelection *selection,
+ ECellDateEdit *ecde);
+static void e_cell_date_edit_hide_popup (ECellDateEdit *ecde);
+
+/* Our arguments. */
+enum {
+ PROP_0,
+ PROP_SHOW_TIME,
+ PROP_SHOW_NOW_BUTTON,
+ PROP_SHOW_TODAY_BUTTON,
+ PROP_ALLOW_NO_DATE_SET,
+ PROP_USE_24_HOUR_FORMAT,
+ PROP_LOWER_HOUR,
+ PROP_UPPER_HOUR
+};
+
+G_DEFINE_TYPE (ECellDateEdit, e_cell_date_edit, E_TYPE_CELL_POPUP)
+
+static void
+e_cell_date_edit_class_init (ECellDateEditClass *class)
+{
+ GObjectClass *object_class;
+ ECellPopupClass *ecpc;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = e_cell_date_edit_get_property;
+ object_class->set_property = e_cell_date_edit_set_property;
+ object_class->dispose = e_cell_date_edit_dispose;
+
+ ecpc = E_CELL_POPUP_CLASS (class);
+ ecpc->popup = e_cell_date_edit_do_popup;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SHOW_TIME,
+ g_param_spec_boolean (
+ "show_time",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SHOW_NOW_BUTTON,
+ g_param_spec_boolean (
+ "show_now_button",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SHOW_TODAY_BUTTON,
+ g_param_spec_boolean (
+ "show_today_button",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ALLOW_NO_DATE_SET,
+ g_param_spec_boolean (
+ "allow_no_date_set",
+ NULL,
+ 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",
+ NULL,
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_LOWER_HOUR,
+ g_param_spec_int (
+ "lower_hour",
+ NULL,
+ NULL,
+ G_MININT,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_UPPER_HOUR,
+ g_param_spec_int (
+ "upper_hour",
+ NULL,
+ NULL,
+ G_MININT,
+ G_MAXINT,
+ 24,
+ G_PARAM_READWRITE));
+}
+
+static void
+e_cell_date_edit_init (ECellDateEdit *ecde)
+{
+ GtkWidget *frame, *vbox, *hbox, *vbox2;
+ GtkWidget *scrolled_window, *bbox, *tree_view;
+ GtkWidget *now_button, *today_button, *none_button, *ok_button;
+ GtkListStore *store;
+
+ ecde->lower_hour = 0;
+ ecde->upper_hour = 24;
+ ecde->use_24_hour_format = TRUE;
+ ecde->need_time_list_rebuild = TRUE;
+ ecde->freeze_count = 0;
+ ecde->time_callback = NULL;
+ ecde->time_callback_data = NULL;
+ ecde->time_callback_destroy = NULL;
+
+ /* We create one popup window for the ECell, since there will only
+ * ever be one popup in use at a time. */
+ ecde->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_window_set_type_hint (
+ GTK_WINDOW (ecde->popup_window),
+ GDK_WINDOW_TYPE_HINT_COMBO);
+ gtk_window_set_resizable (GTK_WINDOW (ecde->popup_window), TRUE);
+
+ frame = gtk_frame_new (NULL);
+ gtk_container_add (GTK_CONTAINER (ecde->popup_window), frame);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+ gtk_widget_show (frame);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+ gtk_widget_show (vbox);
+
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ ecde->calendar = e_calendar_new ();
+ gnome_canvas_item_set (
+ GNOME_CANVAS_ITEM (E_CALENDAR (ecde->calendar)->calitem),
+ "move_selection_when_moving", FALSE,
+ NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), ecde->calendar, TRUE, TRUE, 0);
+ gtk_widget_show (ecde->calendar);
+
+ vbox2 = gtk_vbox_new (FALSE, 2);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0);
+ gtk_widget_show (vbox2);
+
+ ecde->time_entry = gtk_entry_new ();
+ gtk_widget_set_size_request (ecde->time_entry, 50, -1);
+ gtk_box_pack_start (
+ GTK_BOX (vbox2), ecde->time_entry,
+ FALSE, FALSE, 0);
+ gtk_widget_show (ecde->time_entry);
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_box_pack_start (GTK_BOX (vbox2), scrolled_window, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+ gtk_widget_show (scrolled_window);
+
+ store = gtk_list_store_new (1, G_TYPE_STRING);
+ tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ gtk_tree_view_append_column (
+ GTK_TREE_VIEW (tree_view),
+ gtk_tree_view_column_new_with_attributes (
+ "Text", gtk_cell_renderer_text_new (), "text", 0, NULL));
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE);
+
+ gtk_scrolled_window_add_with_viewport (
+ GTK_SCROLLED_WINDOW (scrolled_window), tree_view);
+ gtk_container_set_focus_vadjustment (
+ GTK_CONTAINER (tree_view),
+ gtk_scrolled_window_get_vadjustment (
+ GTK_SCROLLED_WINDOW (scrolled_window)));
+ gtk_container_set_focus_hadjustment (
+ GTK_CONTAINER (tree_view),
+ gtk_scrolled_window_get_hadjustment (
+ GTK_SCROLLED_WINDOW (scrolled_window)));
+ gtk_widget_show (tree_view);
+ ecde->time_tree_view = tree_view;
+ g_signal_connect (
+ gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)), "changed",
+ G_CALLBACK (e_cell_date_edit_on_time_selected), ecde);
+
+ bbox = gtk_hbutton_box_new ();
+ 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);
+
+ now_button = gtk_button_new_with_label (_("Now"));
+ gtk_container_add (GTK_CONTAINER (bbox), now_button);
+ gtk_widget_show (now_button);
+ g_signal_connect (
+ now_button, "clicked",
+ G_CALLBACK (e_cell_date_edit_on_now_clicked), ecde);
+ ecde->now_button = now_button;
+
+ today_button = gtk_button_new_with_label (_("Today"));
+ gtk_container_add (GTK_CONTAINER (bbox), today_button);
+ gtk_widget_show (today_button);
+ g_signal_connect (
+ today_button, "clicked",
+ G_CALLBACK (e_cell_date_edit_on_today_clicked), ecde);
+ ecde->today_button = today_button;
+
+ /* Translators: "None" as a label of a button to unset date in a
+ * date table cell. */
+ none_button = gtk_button_new_with_label (C_("table-date", "None"));
+ gtk_container_add (GTK_CONTAINER (bbox), none_button);
+ gtk_widget_show (none_button);
+ g_signal_connect (
+ none_button, "clicked",
+ G_CALLBACK (e_cell_date_edit_on_none_clicked), ecde);
+ ecde->none_button = none_button;
+
+ ok_button = gtk_button_new_with_label (_("OK"));
+ gtk_container_add (GTK_CONTAINER (bbox), ok_button);
+ gtk_widget_show (ok_button);
+ g_signal_connect (
+ ok_button, "clicked",
+ G_CALLBACK (e_cell_date_edit_on_ok_clicked), ecde);
+
+ g_signal_connect (
+ ecde->popup_window, "key_press_event",
+ G_CALLBACK (e_cell_date_edit_key_press), ecde);
+ g_signal_connect (
+ ecde->popup_window, "button_press_event",
+ G_CALLBACK (e_cell_date_edit_button_press), ecde);
+}
+
+/**
+ * e_cell_date_edit_new:
+ *
+ * Creates a new ECellDateEdit renderer.
+ *
+ * Returns: an ECellDateEdit object.
+ */
+ECell *
+e_cell_date_edit_new (void)
+{
+ return g_object_new (e_cell_date_edit_get_type (), NULL);
+}
+
+static void
+e_cell_date_edit_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ECellDateEdit *ecde;
+
+ ecde = E_CELL_DATE_EDIT (object);
+
+ switch (property_id) {
+ case PROP_SHOW_TIME:
+ g_value_set_boolean (value, gtk_widget_get_visible (ecde->time_entry));
+ return;
+ case PROP_SHOW_NOW_BUTTON:
+ g_value_set_boolean (value, gtk_widget_get_visible (ecde->now_button));
+ return;
+ case PROP_SHOW_TODAY_BUTTON:
+ g_value_set_boolean (value, gtk_widget_get_visible (ecde->today_button));
+ return;
+ case PROP_ALLOW_NO_DATE_SET:
+ g_value_set_boolean (value, gtk_widget_get_visible (ecde->none_button));
+ return;
+ case PROP_USE_24_HOUR_FORMAT:
+ g_value_set_boolean (value, ecde->use_24_hour_format);
+ return;
+ case PROP_LOWER_HOUR:
+ g_value_set_int (value, ecde->lower_hour);
+ return;
+ case PROP_UPPER_HOUR:
+ g_value_set_int (value, ecde->upper_hour);
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_cell_date_edit_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ECellDateEdit *ecde;
+ gint ivalue;
+ gboolean bvalue;
+
+ ecde = E_CELL_DATE_EDIT (object);
+
+ switch (property_id) {
+ case PROP_SHOW_TIME:
+ if (g_value_get_boolean (value)) {
+ gtk_widget_show (ecde->time_entry);
+ gtk_widget_show (ecde->time_tree_view);
+ } else {
+ gtk_widget_hide (ecde->time_entry);
+ gtk_widget_hide (ecde->time_tree_view);
+ }
+ return;
+ case PROP_SHOW_NOW_BUTTON:
+ if (g_value_get_boolean (value)) {
+ gtk_widget_show (ecde->now_button);
+ } else {
+ gtk_widget_hide (ecde->now_button);
+ }
+ return;
+ case PROP_SHOW_TODAY_BUTTON:
+ if (g_value_get_boolean (value)) {
+ gtk_widget_show (ecde->today_button);
+ } else {
+ gtk_widget_hide (ecde->today_button);
+ }
+ return;
+ case PROP_ALLOW_NO_DATE_SET:
+ if (g_value_get_boolean (value)) {
+ gtk_widget_show (ecde->none_button);
+ } else {
+ /* FIXME: What if we have no date set now. */
+ gtk_widget_hide (ecde->none_button);
+ }
+ return;
+ case PROP_USE_24_HOUR_FORMAT:
+ bvalue = g_value_get_boolean (value);
+ if (ecde->use_24_hour_format != bvalue) {
+ ecde->use_24_hour_format = bvalue;
+ ecde->need_time_list_rebuild = TRUE;
+ }
+ return;
+ case PROP_LOWER_HOUR:
+ ivalue = g_value_get_int (value);
+ ivalue = CLAMP (ivalue, 0, 24);
+ if (ecde->lower_hour != ivalue) {
+ ecde->lower_hour = ivalue;
+ ecde->need_time_list_rebuild = TRUE;
+ }
+ return;
+ case PROP_UPPER_HOUR:
+ ivalue = g_value_get_int (value);
+ ivalue = CLAMP (ivalue, 0, 24);
+ if (ecde->upper_hour != ivalue) {
+ ecde->upper_hour = ivalue;
+ ecde->need_time_list_rebuild = TRUE;
+ }
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_cell_date_edit_dispose (GObject *object)
+{
+ ECellDateEdit *ecde = E_CELL_DATE_EDIT (object);
+
+ e_cell_date_edit_set_get_time_callback (ecde, NULL, NULL, NULL);
+
+ if (ecde->popup_window != NULL) {
+ gtk_widget_destroy (ecde->popup_window);
+ ecde->popup_window = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_cell_date_edit_parent_class)->dispose (object);
+}
+
+static gint
+e_cell_date_edit_do_popup (ECellPopup *ecp,
+ GdkEvent *event,
+ gint row,
+ gint view_col)
+{
+ ECellDateEdit *ecde = E_CELL_DATE_EDIT (ecp);
+ GdkWindow *window;
+
+ e_cell_date_edit_show_popup (ecde, row, view_col);
+ e_cell_date_edit_set_popup_values (ecde);
+
+ gtk_grab_add (ecde->popup_window);
+
+ /* Set the focus to the first widget. */
+ gtk_widget_grab_focus (ecde->time_entry);
+ window = gtk_widget_get_window (ecde->popup_window);
+ gdk_window_focus (window, GDK_CURRENT_TIME);
+
+ return TRUE;
+}
+
+static void
+e_cell_date_edit_set_popup_values (ECellDateEdit *ecde)
+{
+ ECellPopup *ecp = E_CELL_POPUP (ecde);
+ ECellText *ecell_text = E_CELL_TEXT (ecp->child);
+ ECellView *ecv = (ECellView *) ecp->popup_cell_view;
+ ETableItem *eti;
+ ETableCol *ecol;
+ gchar *cell_text;
+ ETimeParseStatus status;
+ struct tm date_tm;
+ GDate date;
+ ECalendarItem *calitem;
+ gchar buffer[64];
+ gboolean is_date = TRUE;
+
+ eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
+ ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
+
+ cell_text = e_cell_text_get_text (
+ ecell_text, ecv->e_table_model,
+ ecol->col_idx, ecp->popup_row);
+
+ /* Try to parse just a date first. If the value is only a date, we
+ * use a DATE value. */
+ status = e_time_parse_date (cell_text, &date_tm);
+ if (status == E_TIME_PARSE_INVALID) {
+ is_date = FALSE;
+ status = e_time_parse_date_and_time (cell_text, &date_tm);
+ }
+
+ /* If there is no date and time set, or the date is invalid, we clear
+ * the selections, else we select the appropriate date & time. */
+ calitem = E_CALENDAR_ITEM (E_CALENDAR (ecde->calendar)->calitem);
+ if (status == E_TIME_PARSE_NONE || status == E_TIME_PARSE_INVALID) {
+ gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), "");
+ e_calendar_item_set_selection (calitem, NULL, NULL);
+ gtk_tree_selection_unselect_all (
+ gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (ecde->time_tree_view)));
+ } else {
+ if (is_date) {
+ buffer[0] = '\0';
+ } else {
+ e_time_format_time (
+ &date_tm, ecde->use_24_hour_format,
+ FALSE, buffer, sizeof (buffer));
+ }
+ gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), buffer);
+
+ g_date_clear (&date, 1);
+ g_date_set_dmy (
+ &date,
+ date_tm.tm_mday,
+ date_tm.tm_mon + 1,
+ date_tm.tm_year + 1900);
+ e_calendar_item_set_selection (calitem, &date, &date);
+
+ if (is_date) {
+ gtk_tree_selection_unselect_all (
+ gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (ecde->time_tree_view)));
+ } else {
+ e_cell_date_edit_select_matching_time (ecde, buffer);
+ }
+ }
+
+ e_cell_text_free_text (ecell_text, cell_text);
+}
+
+static void
+e_cell_date_edit_select_matching_time (ECellDateEdit *ecde,
+ gchar *time)
+{
+ gboolean found = FALSE;
+ gboolean valid;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (ecde->time_tree_view));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ecde->time_tree_view));
+
+ for (valid = gtk_tree_model_get_iter_first (model, &iter);
+ valid && !found;
+ valid = gtk_tree_model_iter_next (model, &iter)) {
+ gchar *str = NULL;
+
+ gtk_tree_model_get (model, &iter, 0, &str, -1);
+
+ if (g_str_equal (str, time)) {
+ GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
+
+ gtk_tree_view_set_cursor (
+ GTK_TREE_VIEW (ecde->time_tree_view),
+ path, NULL, FALSE);
+ gtk_tree_view_scroll_to_cell (
+ GTK_TREE_VIEW (ecde->time_tree_view),
+ path, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_path_free (path);
+
+ found = TRUE;
+ }
+
+ g_free (str);
+ }
+
+ if (!found) {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (ecde->time_tree_view), 0, 0);
+ }
+}
+
+static void
+e_cell_date_edit_show_popup (ECellDateEdit *ecde,
+ gint row,
+ gint view_col)
+{
+ GdkWindow *window;
+ gint x, y, width, height;
+
+ if (ecde->need_time_list_rebuild)
+ e_cell_date_edit_rebuild_time_list (ecde);
+
+ /* This code is practically copied from GtkCombo. */
+
+ e_cell_date_edit_get_popup_pos (ecde, row, view_col, &x, &y, &height, &width);
+
+ window = gtk_widget_get_window (ecde->popup_window);
+ gtk_window_move (GTK_WINDOW (ecde->popup_window), x, y);
+ gtk_widget_set_size_request (ecde->popup_window, width, height);
+ gtk_widget_realize (ecde->popup_window);
+ gdk_window_resize (window, width, height);
+ gtk_widget_show (ecde->popup_window);
+
+ e_cell_popup_set_shown (E_CELL_POPUP (ecde), TRUE);
+}
+
+/* Calculates the size and position of the popup window (like GtkCombo). */
+static void
+e_cell_date_edit_get_popup_pos (ECellDateEdit *ecde,
+ gint row,
+ gint view_col,
+ gint *x,
+ gint *y,
+ gint *height,
+ gint *width)
+{
+ ECellPopup *ecp = E_CELL_POPUP (ecde);
+ ETableItem *eti;
+ GtkWidget *canvas;
+ GtkRequisition popup_requisition;
+ GtkAdjustment *adjustment;
+ GtkScrollable *scrollable;
+ GdkWindow *window;
+ gint avail_height, screen_width, column_width, row_height;
+ gdouble x1, y1, wx, wy;
+ gint value;
+
+ eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
+ canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
+
+ window = gtk_widget_get_window (canvas);
+ gdk_window_get_origin (window, x, y);
+
+ x1 = e_table_header_col_diff (eti->header, 0, view_col + 1);
+ y1 = e_table_item_row_diff (eti, 0, row + 1);
+ column_width = e_table_header_col_diff (
+ eti->header, view_col, view_col + 1);
+ row_height = e_table_item_row_diff (eti, row, row + 1);
+ gnome_canvas_item_i2w (GNOME_CANVAS_ITEM (eti), &x1, &y1);
+
+ gnome_canvas_world_to_window (
+ GNOME_CANVAS (canvas), x1, y1, &wx, &wy);
+
+ x1 = wx;
+ y1 = wy;
+
+ *x += x1;
+ /* The ETable positions don't include the grid lines, I think, so we
+ * add 1. */
+ scrollable = GTK_SCROLLABLE (&GNOME_CANVAS (canvas)->layout);
+ adjustment = gtk_scrollable_get_vadjustment (scrollable);
+ value = (gint) gtk_adjustment_get_value (adjustment);
+ *y += y1 + 1 - value + ((GnomeCanvas *)canvas)->zoom_yofs;
+
+ avail_height = gdk_screen_height () - *y;
+
+ /* We'll use the entire screen width if needed, but we save space for
+ * the vertical scrollbar in case we need to show that. */
+ screen_width = gdk_screen_width ();
+
+ gtk_widget_get_preferred_size (ecde->popup_window, &popup_requisition, NULL);
+
+ /* Calculate the desired width. */
+ *width = popup_requisition.width;
+
+ /* Use at least the same width as the column. */
+ if (*width < column_width)
+ *width = column_width;
+
+ /* Check if it fits in the available height. */
+ if (popup_requisition.height > avail_height) {
+ /* It doesn't fit, so we see if we have the minimum space
+ * needed. */
+ if (*y - row_height > avail_height) {
+ /* We don't, so we show the popup above the cell
+ * instead of below it. */
+ *y -= (popup_requisition.height + row_height);
+ if (*y < 0)
+ *y = 0;
+ }
+ }
+
+ /* We try to line it up with the right edge of the column, but we don't
+ * want it to go off the edges of the screen. */
+ if (*x > screen_width)
+ *x = screen_width;
+ *x -= *width;
+ if (*x < 0)
+ *x = 0;
+
+ *height = popup_requisition.height;
+}
+
+/* This handles key press events in the popup window. If the Escape key is
+ * pressed we hide the popup, and do not change the cell contents. */
+static gint
+e_cell_date_edit_key_press (GtkWidget *popup_window,
+ GdkEventKey *event,
+ ECellDateEdit *ecde)
+{
+ /* If the Escape key is pressed we hide the popup. */
+ if (event->keyval != GDK_KEY_Escape)
+ return FALSE;
+
+ e_cell_date_edit_hide_popup (ecde);
+
+ return TRUE;
+}
+
+/* This handles button press events in the popup window. If the button is
+ * pressed outside the popup, we hide it and do not change the cell contents.
+*/
+static gint
+e_cell_date_edit_button_press (GtkWidget *popup_window,
+ GdkEvent *button_event,
+ ECellDateEdit *ecde)
+{
+ GtkWidget *event_widget;
+
+ event_widget = gtk_get_event_widget (button_event);
+
+ if (gtk_widget_get_toplevel (event_widget) != popup_window)
+ e_cell_date_edit_hide_popup (ecde);
+
+ return TRUE;
+}
+
+/* Clears the time list and rebuilds it using the lower_hour, upper_hour
+ * and use_24_hour_format settings. */
+static void
+e_cell_date_edit_rebuild_time_list (ECellDateEdit *ecde)
+{
+ GtkListStore *store;
+ gchar buffer[40];
+ struct tm tmp_tm;
+ gint hour, min;
+
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (
+ GTK_TREE_VIEW (ecde->time_tree_view)));
+ gtk_list_store_clear (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;
+
+ for (hour = ecde->lower_hour; hour <= ecde->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 != ecde->upper_hour);
+ min += 30) {
+ GtkTreeIter iter;
+
+ tmp_tm.tm_hour = hour;
+ tmp_tm.tm_min = min;
+ e_time_format_time (&tmp_tm, ecde->use_24_hour_format,
+ FALSE, buffer, sizeof (buffer));
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, buffer, -1);
+ }
+ }
+
+ ecde->need_time_list_rebuild = FALSE;
+}
+
+static void
+e_cell_date_edit_on_ok_clicked (GtkWidget *button,
+ ECellDateEdit *ecde)
+{
+ ECalendarItem *calitem;
+ GDate start_date, end_date;
+ gboolean day_selected;
+ struct tm date_tm;
+ gchar buffer[64];
+ const gchar *text;
+ ETimeParseStatus status;
+ gboolean is_date = FALSE;
+
+ calitem = E_CALENDAR_ITEM (E_CALENDAR (ecde->calendar)->calitem);
+ day_selected = e_calendar_item_get_selection (
+ calitem, &start_date, &end_date);
+
+ text = gtk_entry_get_text (GTK_ENTRY (ecde->time_entry));
+ status = e_time_parse_time (text, &date_tm);
+ if (status == E_TIME_PARSE_INVALID) {
+ e_cell_date_edit_show_time_invalid_warning (ecde);
+ return;
+ } else if (status == E_TIME_PARSE_NONE) {
+ is_date = TRUE;
+ }
+
+ if (day_selected) {
+ date_tm.tm_year = g_date_get_year (&start_date) - 1900;
+ date_tm.tm_mon = g_date_get_month (&start_date) - 1;
+ date_tm.tm_mday = g_date_get_day (&start_date);
+ /* We need to call this to set the weekday. */
+ mktime (&date_tm);
+ e_time_format_date_and_time (&date_tm,
+ ecde->use_24_hour_format,
+ !is_date, FALSE,
+ buffer, sizeof (buffer));
+ } else {
+ buffer[0] = '\0';
+ }
+
+ e_cell_date_edit_update_cell (ecde, buffer);
+ e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_show_time_invalid_warning (ECellDateEdit *ecde)
+{
+ GtkWidget *dialog;
+ struct tm date_tm;
+ gchar buffer[64];
+
+ /* Create a useful error message showing the correct format. */
+ date_tm.tm_year = 100;
+ date_tm.tm_mon = 0;
+ date_tm.tm_mday = 1;
+ date_tm.tm_hour = 1;
+ date_tm.tm_min = 30;
+ date_tm.tm_sec = 0;
+ date_tm.tm_isdst = -1;
+ e_time_format_time (&date_tm, ecde->use_24_hour_format, FALSE,
+ buffer, sizeof (buffer));
+
+ /* FIXME: Fix transient settings - I'm not sure it works with popup
+ * windows. Maybe we need to use a normal window without decorations.*/
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (ecde->popup_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ _("The time must be in the format: %s"),
+ buffer);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+static void
+e_cell_date_edit_on_now_clicked (GtkWidget *button,
+ ECellDateEdit *ecde)
+{
+ struct tm tmp_tm;
+ time_t t;
+ gchar buffer[64];
+
+ if (ecde->time_callback) {
+ tmp_tm = ecde->time_callback (
+ ecde, ecde->time_callback_data);
+ } else {
+ t = time (NULL);
+ tmp_tm = *localtime (&t);
+ }
+
+ e_time_format_date_and_time (
+ &tmp_tm, ecde->use_24_hour_format,
+ TRUE, FALSE, buffer, sizeof (buffer));
+
+ e_cell_date_edit_update_cell (ecde, buffer);
+ e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_on_none_clicked (GtkWidget *button,
+ ECellDateEdit *ecde)
+{
+ e_cell_date_edit_update_cell (ecde, "");
+ e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_on_today_clicked (GtkWidget *button,
+ ECellDateEdit *ecde)
+{
+ struct tm tmp_tm;
+ time_t t;
+ gchar buffer[64];
+
+ if (ecde->time_callback) {
+ tmp_tm = ecde->time_callback (
+ ecde, ecde->time_callback_data);
+ } else {
+ t = time (NULL);
+ tmp_tm = *localtime (&t);
+ }
+
+ tmp_tm.tm_hour = 0;
+ tmp_tm.tm_min = 0;
+ tmp_tm.tm_sec = 0;
+
+ e_time_format_date_and_time (
+ &tmp_tm, ecde->use_24_hour_format,
+ FALSE, FALSE, buffer, sizeof (buffer));
+
+ e_cell_date_edit_update_cell (ecde, buffer);
+ e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_update_cell (ECellDateEdit *ecde,
+ const gchar *text)
+{
+ ECellPopup *ecp = E_CELL_POPUP (ecde);
+ ECellText *ecell_text = E_CELL_TEXT (ecp->child);
+ ECellView *ecv = (ECellView *) ecp->popup_cell_view;
+ ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
+ ETableCol *ecol;
+ gchar *old_text;
+
+ /* Compare the new text with the existing cell contents. */
+ ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
+
+ old_text = e_cell_text_get_text (
+ ecell_text, ecv->e_table_model,
+ ecol->col_idx, ecp->popup_row);
+
+ /* If they are different, update the cell contents. */
+ if (strcmp (old_text, text)) {
+ e_cell_text_set_value (
+ ecell_text, ecv->e_table_model,
+ ecol->col_idx, ecp->popup_row, text);
+ e_cell_leave_edit (
+ ecv, ecp->popup_view_col,
+ ecol->col_idx, ecp->popup_row, NULL);
+ }
+
+ e_cell_text_free_text (ecell_text, old_text);
+}
+
+static void
+e_cell_date_edit_on_time_selected (GtkTreeSelection *selection,
+ ECellDateEdit *ecde)
+{
+ gchar *list_item_text = NULL;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ gtk_tree_model_get (model, &iter, 0, &list_item_text, -1);
+
+ g_return_if_fail (list_item_text != NULL);
+
+ gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), list_item_text);
+
+ g_free (list_item_text);
+}
+
+static void
+e_cell_date_edit_hide_popup (ECellDateEdit *ecde)
+{
+ gtk_grab_remove (ecde->popup_window);
+ gtk_widget_hide (ecde->popup_window);
+ e_cell_popup_set_shown (E_CELL_POPUP (ecde), FALSE);
+}
+
+/* These freeze and thaw the rebuilding of the time list. They are useful when
+ * setting several properties which result in rebuilds of the list, e.g. the
+ * lower_hour, upper_hour and use_24_hour_format properties. */
+void
+e_cell_date_edit_freeze (ECellDateEdit *ecde)
+{
+ g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
+
+ ecde->freeze_count++;
+}
+
+void
+e_cell_date_edit_thaw (ECellDateEdit *ecde)
+{
+ g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
+
+ if (ecde->freeze_count > 0) {
+ ecde->freeze_count--;
+
+ if (ecde->freeze_count == 0)
+ e_cell_date_edit_rebuild_time_list (ecde);
+ }
+}
+
+/* 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_cell_date_edit_set_get_time_callback (ECellDateEdit *ecde,
+ ECellDateEditGetTimeCallback cb,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
+
+ if (ecde->time_callback_data && ecde->time_callback_destroy)
+ (*ecde->time_callback_destroy) (ecde->time_callback_data);
+
+ ecde->time_callback = cb;
+ ecde->time_callback_data = data;
+ ecde->time_callback_destroy = destroy;
+}