evolution-3.6.4/widgets/table/e-cell-date-edit.c

No issues found

   1 /*
   2  * This program is free software; you can redistribute it and/or
   3  * modify it under the terms of the GNU Lesser General Public
   4  * License as published by the Free Software Foundation; either
   5  * version 2 of the License, or (at your option) version 3.
   6  *
   7  * This program is distributed in the hope that it will be useful,
   8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  10  * Lesser General Public License for more details.
  11  *
  12  * You should have received a copy of the GNU Lesser General Public
  13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  14  *
  15  *
  16  * Authors:
  17  *		Damon Chaplin <damon@ximian.com>
  18  *
  19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  20  *
  21  */
  22 
  23 /*
  24  * ECellDateEdit - a subclass of ECellPopup used to show a date with a popup
  25  * window to edit it.
  26  */
  27 
  28 #ifdef HAVE_CONFIG_H
  29 #include <config.h>
  30 #endif
  31 
  32 #include "e-cell-date-edit.h"
  33 
  34 #include <string.h>
  35 #include <time.h>
  36 
  37 #include <gdk/gdkkeysyms.h>
  38 #include <gtk/gtk.h>
  39 
  40 #include "e-table-item.h"
  41 #include "e-cell-text.h"
  42 
  43 #include <glib/gi18n.h>
  44 
  45 #include <libedataserver/libedataserver.h>
  46 
  47 /* This depends on ECalendar which is why I didn't put it in gal. */
  48 #include <misc/e-calendar.h>
  49 
  50 static void e_cell_date_edit_get_property	(GObject	*object,
  51 						 guint		 property_id,
  52 						 GValue		*value,
  53 						 GParamSpec	*pspec);
  54 static void e_cell_date_edit_set_property	(GObject	*object,
  55 						 guint		 property_id,
  56 						 const GValue	*value,
  57 						 GParamSpec	*pspec);
  58 static void e_cell_date_edit_dispose		(GObject	*object);
  59 
  60 static gint e_cell_date_edit_do_popup		(ECellPopup	*ecp,
  61 						 GdkEvent	*event,
  62 						 gint             row,
  63 						 gint             view_col);
  64 static void e_cell_date_edit_set_popup_values	(ECellDateEdit	*ecde);
  65 static void e_cell_date_edit_select_matching_time (ECellDateEdit	*ecde,
  66 						  gchar		*time);
  67 static void e_cell_date_edit_show_popup		(ECellDateEdit	*ecde,
  68 						 gint             row,
  69 						 gint             view_col);
  70 static void e_cell_date_edit_get_popup_pos	(ECellDateEdit	*ecde,
  71 						 gint             row,
  72 						 gint             view_col,
  73 						 gint		*x,
  74 						 gint		*y,
  75 						 gint		*height,
  76 						 gint		*width);
  77 
  78 static void e_cell_date_edit_rebuild_time_list	(ECellDateEdit	*ecde);
  79 
  80 static gint e_cell_date_edit_key_press		(GtkWidget	*popup_window,
  81 						 GdkEventKey	*event,
  82 						 ECellDateEdit	*ecde);
  83 static gint  e_cell_date_edit_button_press	(GtkWidget	*popup_window,
  84 						 GdkEventButton	*event,
  85 						 ECellDateEdit	*ecde);
  86 static void e_cell_date_edit_on_ok_clicked	(GtkWidget	*button,
  87 						 ECellDateEdit	*ecde);
  88 static void e_cell_date_edit_show_time_invalid_warning	(ECellDateEdit	*ecde);
  89 static void e_cell_date_edit_on_now_clicked	(GtkWidget	*button,
  90 						 ECellDateEdit	*ecde);
  91 static void e_cell_date_edit_on_none_clicked	(GtkWidget	*button,
  92 						 ECellDateEdit	*ecde);
  93 static void e_cell_date_edit_on_today_clicked	(GtkWidget	*button,
  94 						 ECellDateEdit	*ecde);
  95 static void e_cell_date_edit_update_cell	(ECellDateEdit	*ecde,
  96 						 const gchar	*text);
  97 static void e_cell_date_edit_on_time_selected	(GtkTreeSelection *selection,
  98 						 ECellDateEdit *ecde);
  99 static void e_cell_date_edit_hide_popup		(ECellDateEdit	*ecde);
 100 
 101 /* Our arguments. */
 102 enum {
 103 	PROP_0,
 104 	PROP_SHOW_TIME,
 105 	PROP_SHOW_NOW_BUTTON,
 106 	PROP_SHOW_TODAY_BUTTON,
 107 	PROP_ALLOW_NO_DATE_SET,
 108 	PROP_USE_24_HOUR_FORMAT,
 109 	PROP_LOWER_HOUR,
 110 	PROP_UPPER_HOUR
 111 };
 112 
 113 G_DEFINE_TYPE (ECellDateEdit, e_cell_date_edit, E_TYPE_CELL_POPUP)
 114 
 115 static void
 116 e_cell_date_edit_class_init (ECellDateEditClass *class)
 117 {
 118 	GObjectClass *object_class;
 119 	ECellPopupClass	*ecpc;
 120 
 121 	object_class = G_OBJECT_CLASS (class);
 122 	object_class->get_property = e_cell_date_edit_get_property;
 123 	object_class->set_property = e_cell_date_edit_set_property;
 124 	object_class->dispose = e_cell_date_edit_dispose;
 125 
 126 	ecpc = E_CELL_POPUP_CLASS (class);
 127 	ecpc->popup = e_cell_date_edit_do_popup;
 128 
 129 	g_object_class_install_property (
 130 		object_class,
 131 		PROP_SHOW_TIME,
 132 		g_param_spec_boolean (
 133 			"show_time",
 134 			NULL,
 135 			NULL,
 136 			TRUE,
 137 			G_PARAM_READWRITE));
 138 
 139 	g_object_class_install_property (
 140 		object_class,
 141 		PROP_SHOW_NOW_BUTTON,
 142 		g_param_spec_boolean (
 143 			"show_now_button",
 144 			NULL,
 145 			NULL,
 146 			TRUE,
 147 			G_PARAM_READWRITE));
 148 
 149 	g_object_class_install_property (
 150 		object_class,
 151 		PROP_SHOW_TODAY_BUTTON,
 152 		g_param_spec_boolean (
 153 			"show_today_button",
 154 			NULL,
 155 			NULL,
 156 			TRUE,
 157 			G_PARAM_READWRITE));
 158 
 159 	g_object_class_install_property (
 160 		object_class,
 161 		PROP_ALLOW_NO_DATE_SET,
 162 		g_param_spec_boolean (
 163 			"allow_no_date_set",
 164 			NULL,
 165 			NULL,
 166 			TRUE,
 167 			G_PARAM_READWRITE));
 168 
 169 	g_object_class_install_property (
 170 		object_class,
 171 		PROP_USE_24_HOUR_FORMAT,
 172 		g_param_spec_boolean (
 173 			"use_24_hour_format",
 174 			NULL,
 175 			NULL,
 176 			TRUE,
 177 			G_PARAM_READWRITE));
 178 
 179 	g_object_class_install_property (
 180 		object_class,
 181 		PROP_LOWER_HOUR,
 182 		g_param_spec_int (
 183 			"lower_hour",
 184 			NULL,
 185 			NULL,
 186 			G_MININT,
 187 			G_MAXINT,
 188 			0,
 189 			G_PARAM_READWRITE));
 190 
 191 	g_object_class_install_property (
 192 		object_class,
 193 		PROP_UPPER_HOUR,
 194 		g_param_spec_int (
 195 			"upper_hour",
 196 			NULL,
 197 			NULL,
 198 			G_MININT,
 199 			G_MAXINT,
 200 			24,
 201 			G_PARAM_READWRITE));
 202 }
 203 
 204 static void
 205 e_cell_date_edit_init (ECellDateEdit *ecde)
 206 {
 207 	GtkWidget *frame, *vbox, *hbox, *vbox2;
 208 	GtkWidget *scrolled_window, *bbox, *tree_view;
 209 	GtkWidget *now_button, *today_button, *none_button, *ok_button;
 210 	GtkListStore *store;
 211 
 212 	ecde->lower_hour = 0;
 213 	ecde->upper_hour = 24;
 214 	ecde->use_24_hour_format = TRUE;
 215 	ecde->need_time_list_rebuild = TRUE;
 216 	ecde->freeze_count = 0;
 217 	ecde->time_callback = NULL;
 218 	ecde->time_callback_data = NULL;
 219 	ecde->time_callback_destroy = NULL;
 220 
 221 	/* We create one popup window for the ECell, since there will only
 222 	 * ever be one popup in use at a time. */
 223 	ecde->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
 224 
 225 	gtk_window_set_type_hint (
 226 		GTK_WINDOW (ecde->popup_window),
 227 		GDK_WINDOW_TYPE_HINT_COMBO);
 228 	gtk_window_set_resizable (GTK_WINDOW (ecde->popup_window), TRUE);
 229 
 230 	frame = gtk_frame_new (NULL);
 231 	gtk_container_add (GTK_CONTAINER (ecde->popup_window), frame);
 232 	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
 233 	gtk_widget_show (frame);
 234 
 235 	vbox = gtk_vbox_new (FALSE, 0);
 236 	gtk_container_add (GTK_CONTAINER (frame), vbox);
 237 	gtk_widget_show (vbox);
 238 
 239 	hbox = gtk_hbox_new (FALSE, 4);
 240 	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
 241 	gtk_widget_show (hbox);
 242 
 243 	ecde->calendar = e_calendar_new ();
 244 	gnome_canvas_item_set (
 245 		GNOME_CANVAS_ITEM (E_CALENDAR (ecde->calendar)->calitem),
 246 		"move_selection_when_moving", FALSE,
 247 		NULL);
 248 	gtk_box_pack_start (GTK_BOX (hbox), ecde->calendar, TRUE, TRUE, 0);
 249 	gtk_widget_show (ecde->calendar);
 250 
 251 	vbox2 = gtk_vbox_new (FALSE, 2);
 252 	gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0);
 253 	gtk_widget_show (vbox2);
 254 
 255 	ecde->time_entry = gtk_entry_new ();
 256 	gtk_widget_set_size_request (ecde->time_entry, 50, -1);
 257 	gtk_box_pack_start (
 258 		GTK_BOX (vbox2), ecde->time_entry,
 259 		FALSE, FALSE, 0);
 260 	gtk_widget_show (ecde->time_entry);
 261 
 262 	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
 263 	gtk_box_pack_start (GTK_BOX (vbox2), scrolled_window, TRUE, TRUE, 0);
 264 	gtk_scrolled_window_set_policy (
 265 		GTK_SCROLLED_WINDOW (scrolled_window),
 266 		GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
 267 	gtk_widget_show (scrolled_window);
 268 
 269 	store = gtk_list_store_new (1, G_TYPE_STRING);
 270 	tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
 271 	g_object_unref (store);
 272 
 273 	gtk_tree_view_append_column (
 274 		GTK_TREE_VIEW (tree_view),
 275 		gtk_tree_view_column_new_with_attributes (
 276 		"Text", gtk_cell_renderer_text_new (), "text", 0, NULL));
 277 
 278 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE);
 279 
 280 	gtk_scrolled_window_add_with_viewport (
 281 		GTK_SCROLLED_WINDOW (scrolled_window), tree_view);
 282 	gtk_container_set_focus_vadjustment (
 283 		GTK_CONTAINER (tree_view),
 284 		gtk_scrolled_window_get_vadjustment (
 285 		GTK_SCROLLED_WINDOW (scrolled_window)));
 286 	gtk_container_set_focus_hadjustment (
 287 		GTK_CONTAINER (tree_view),
 288 		gtk_scrolled_window_get_hadjustment (
 289 		GTK_SCROLLED_WINDOW (scrolled_window)));
 290 	gtk_widget_show (tree_view);
 291 	ecde->time_tree_view = tree_view;
 292 	g_signal_connect (
 293 		gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)), "changed",
 294 		G_CALLBACK (e_cell_date_edit_on_time_selected), ecde);
 295 
 296 	bbox = gtk_hbutton_box_new ();
 297 	gtk_container_set_border_width (GTK_CONTAINER (bbox), 4);
 298 	gtk_box_set_spacing (GTK_BOX (bbox), 2);
 299 	gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0);
 300 	gtk_widget_show (bbox);
 301 
 302 	now_button = gtk_button_new_with_label (_("Now"));
 303 	gtk_container_add (GTK_CONTAINER (bbox), now_button);
 304 	gtk_widget_show (now_button);
 305 	g_signal_connect (
 306 		now_button, "clicked",
 307 		G_CALLBACK (e_cell_date_edit_on_now_clicked), ecde);
 308 	ecde->now_button = now_button;
 309 
 310 	today_button = gtk_button_new_with_label (_("Today"));
 311 	gtk_container_add (GTK_CONTAINER (bbox), today_button);
 312 	gtk_widget_show (today_button);
 313 	g_signal_connect (
 314 		today_button, "clicked",
 315 		G_CALLBACK (e_cell_date_edit_on_today_clicked), ecde);
 316 	ecde->today_button = today_button;
 317 
 318 	/* Translators: "None" as a label of a button to unset date in a
 319 	 * date table cell. */
 320 	none_button = gtk_button_new_with_label (C_("table-date", "None"));
 321 	gtk_container_add (GTK_CONTAINER (bbox), none_button);
 322 	gtk_widget_show (none_button);
 323 	g_signal_connect (
 324 		none_button, "clicked",
 325 		G_CALLBACK (e_cell_date_edit_on_none_clicked), ecde);
 326 	ecde->none_button = none_button;
 327 
 328 	ok_button = gtk_button_new_with_label (_("OK"));
 329 	gtk_container_add (GTK_CONTAINER (bbox), ok_button);
 330 	gtk_widget_show (ok_button);
 331 	g_signal_connect (
 332 		ok_button, "clicked",
 333 		G_CALLBACK (e_cell_date_edit_on_ok_clicked), ecde);
 334 
 335 	g_signal_connect (
 336 		ecde->popup_window, "key_press_event",
 337 		G_CALLBACK (e_cell_date_edit_key_press), ecde);
 338 	g_signal_connect (
 339 		ecde->popup_window, "button_press_event",
 340 		G_CALLBACK (e_cell_date_edit_button_press), ecde);
 341 }
 342 
 343 /**
 344  * e_cell_date_edit_new:
 345  *
 346  * Creates a new ECellDateEdit renderer.
 347  *
 348  * Returns: an ECellDateEdit object.
 349  */
 350 ECell *
 351 e_cell_date_edit_new (void)
 352 {
 353 	return g_object_new (e_cell_date_edit_get_type (), NULL);
 354 }
 355 
 356 static void
 357 e_cell_date_edit_get_property (GObject *object,
 358                                guint property_id,
 359                                GValue *value,
 360                                GParamSpec *pspec)
 361 {
 362 	ECellDateEdit *ecde;
 363 
 364 	ecde = E_CELL_DATE_EDIT (object);
 365 
 366 	switch (property_id) {
 367 	case PROP_SHOW_TIME:
 368 		g_value_set_boolean (value, gtk_widget_get_visible (ecde->time_entry));
 369 		return;
 370 	case PROP_SHOW_NOW_BUTTON:
 371 		g_value_set_boolean (value, gtk_widget_get_visible (ecde->now_button));
 372 		return;
 373 	case PROP_SHOW_TODAY_BUTTON:
 374 		g_value_set_boolean (value, gtk_widget_get_visible (ecde->today_button));
 375 		return;
 376 	case PROP_ALLOW_NO_DATE_SET:
 377 		g_value_set_boolean (value, gtk_widget_get_visible (ecde->none_button));
 378 		return;
 379 	case PROP_USE_24_HOUR_FORMAT:
 380 		g_value_set_boolean (value, ecde->use_24_hour_format);
 381 		return;
 382 	case PROP_LOWER_HOUR:
 383 		g_value_set_int (value, ecde->lower_hour);
 384 		return;
 385 	case PROP_UPPER_HOUR:
 386 		g_value_set_int (value, ecde->upper_hour);
 387 		return;
 388 	}
 389 
 390 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 391 }
 392 
 393 static void
 394 e_cell_date_edit_set_property (GObject *object,
 395                                guint property_id,
 396                                const GValue *value,
 397                                GParamSpec *pspec)
 398 {
 399 	ECellDateEdit *ecde;
 400 	gint ivalue;
 401 	gboolean bvalue;
 402 
 403 	ecde = E_CELL_DATE_EDIT (object);
 404 
 405 	switch (property_id) {
 406 	case PROP_SHOW_TIME:
 407 		if (g_value_get_boolean (value)) {
 408 			gtk_widget_show (ecde->time_entry);
 409 			gtk_widget_show (ecde->time_tree_view);
 410 		} else {
 411 			gtk_widget_hide (ecde->time_entry);
 412 			gtk_widget_hide (ecde->time_tree_view);
 413 		}
 414 		return;
 415 	case PROP_SHOW_NOW_BUTTON:
 416 		if (g_value_get_boolean (value)) {
 417 			gtk_widget_show (ecde->now_button);
 418 		} else {
 419 			gtk_widget_hide (ecde->now_button);
 420 		}
 421 		return;
 422 	case PROP_SHOW_TODAY_BUTTON:
 423 		if (g_value_get_boolean (value)) {
 424 			gtk_widget_show (ecde->today_button);
 425 		} else {
 426 			gtk_widget_hide (ecde->today_button);
 427 		}
 428 		return;
 429 	case PROP_ALLOW_NO_DATE_SET:
 430 		if (g_value_get_boolean (value)) {
 431 			gtk_widget_show (ecde->none_button);
 432 		} else {
 433 			/* FIXME: What if we have no date set now. */
 434 			gtk_widget_hide (ecde->none_button);
 435 		}
 436 		return;
 437 	case PROP_USE_24_HOUR_FORMAT:
 438 		bvalue = g_value_get_boolean (value);
 439 		if (ecde->use_24_hour_format != bvalue) {
 440 			ecde->use_24_hour_format = bvalue;
 441 			ecde->need_time_list_rebuild = TRUE;
 442 		}
 443 		return;
 444 	case PROP_LOWER_HOUR:
 445 		ivalue = g_value_get_int (value);
 446 		ivalue = CLAMP (ivalue, 0, 24);
 447 		if (ecde->lower_hour != ivalue) {
 448 			ecde->lower_hour = ivalue;
 449 			ecde->need_time_list_rebuild = TRUE;
 450 		}
 451 		return;
 452 	case PROP_UPPER_HOUR:
 453 		ivalue = g_value_get_int (value);
 454 		ivalue = CLAMP (ivalue, 0, 24);
 455 		if (ecde->upper_hour != ivalue) {
 456 			ecde->upper_hour = ivalue;
 457 			ecde->need_time_list_rebuild = TRUE;
 458 		}
 459 		return;
 460 	}
 461 
 462 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 463 }
 464 
 465 static void
 466 e_cell_date_edit_dispose (GObject *object)
 467 {
 468 	ECellDateEdit *ecde = E_CELL_DATE_EDIT (object);
 469 
 470 	e_cell_date_edit_set_get_time_callback (ecde, NULL, NULL, NULL);
 471 
 472 	if (ecde->popup_window != NULL) {
 473 		gtk_widget_destroy (ecde->popup_window);
 474 		ecde->popup_window = NULL;
 475 	}
 476 
 477 	/* Chain up to parent's dispose() method. */
 478 	G_OBJECT_CLASS (e_cell_date_edit_parent_class)->dispose (object);
 479 }
 480 
 481 static gint
 482 e_cell_date_edit_do_popup (ECellPopup *ecp,
 483                            GdkEvent *event,
 484                            gint row,
 485                            gint view_col)
 486 {
 487 	ECellDateEdit *ecde = E_CELL_DATE_EDIT (ecp);
 488 	GdkWindow *window;
 489 
 490 	e_cell_date_edit_show_popup (ecde, row, view_col);
 491 	e_cell_date_edit_set_popup_values (ecde);
 492 
 493 	gtk_grab_add (ecde->popup_window);
 494 
 495 	/* Set the focus to the first widget. */
 496 	gtk_widget_grab_focus (ecde->time_entry);
 497 	window = gtk_widget_get_window (ecde->popup_window);
 498 	gdk_window_focus (window, GDK_CURRENT_TIME);
 499 
 500 	return TRUE;
 501 }
 502 
 503 static void
 504 e_cell_date_edit_set_popup_values (ECellDateEdit *ecde)
 505 {
 506 	ECellPopup *ecp = E_CELL_POPUP (ecde);
 507 	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
 508 	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
 509 	ETableItem *eti;
 510 	ETableCol *ecol;
 511 	gchar *cell_text;
 512 	ETimeParseStatus status;
 513 	struct tm date_tm;
 514 	GDate date;
 515 	ECalendarItem *calitem;
 516 	gchar buffer[64];
 517 	gboolean is_date = TRUE;
 518 
 519 	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
 520 	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
 521 
 522 	cell_text = e_cell_text_get_text (
 523 		ecell_text, ecv->e_table_model,
 524 		ecol->col_idx, ecp->popup_row);
 525 
 526 	/* Try to parse just a date first. If the value is only a date, we
 527 	 * use a DATE value. */
 528 	status = e_time_parse_date (cell_text, &date_tm);
 529 	if (status == E_TIME_PARSE_INVALID) {
 530 		is_date = FALSE;
 531 		status = e_time_parse_date_and_time (cell_text, &date_tm);
 532 	}
 533 
 534 	/* If there is no date and time set, or the date is invalid, we clear
 535 	 * the selections, else we select the appropriate date & time. */
 536 	calitem = E_CALENDAR_ITEM (E_CALENDAR (ecde->calendar)->calitem);
 537 	if (status == E_TIME_PARSE_NONE || status == E_TIME_PARSE_INVALID) {
 538 		gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), "");
 539 		e_calendar_item_set_selection (calitem, NULL, NULL);
 540 		gtk_tree_selection_unselect_all (
 541 			gtk_tree_view_get_selection (
 542 			GTK_TREE_VIEW (ecde->time_tree_view)));
 543 	} else {
 544 		if (is_date) {
 545 			buffer[0] = '\0';
 546 		} else {
 547 			e_time_format_time (
 548 				&date_tm, ecde->use_24_hour_format,
 549 				FALSE, buffer, sizeof (buffer));
 550 		}
 551 		gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), buffer);
 552 
 553 		g_date_clear (&date, 1);
 554 		g_date_set_dmy (
 555 			&date,
 556 			date_tm.tm_mday,
 557 			date_tm.tm_mon + 1,
 558 			date_tm.tm_year + 1900);
 559 		e_calendar_item_set_selection (calitem, &date, &date);
 560 
 561 		if (is_date) {
 562 			gtk_tree_selection_unselect_all (
 563 				gtk_tree_view_get_selection (
 564 				GTK_TREE_VIEW (ecde->time_tree_view)));
 565 		} else {
 566 			e_cell_date_edit_select_matching_time (ecde, buffer);
 567 		}
 568 	}
 569 
 570 	e_cell_text_free_text (ecell_text, cell_text);
 571 }
 572 
 573 static void
 574 e_cell_date_edit_select_matching_time (ECellDateEdit *ecde,
 575                                        gchar *time)
 576 {
 577 	gboolean found = FALSE;
 578 	gboolean valid;
 579 	GtkTreeSelection *selection;
 580 	GtkTreeIter iter;
 581 	GtkTreeModel *model;
 582 
 583 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ecde->time_tree_view));
 584 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ecde->time_tree_view));
 585 
 586 	for (valid = gtk_tree_model_get_iter_first (model, &iter);
 587 	     valid && !found;
 588 	     valid = gtk_tree_model_iter_next (model, &iter)) {
 589 		gchar *str = NULL;
 590 
 591 		gtk_tree_model_get (model, &iter, 0, &str, -1);
 592 
 593 		if (g_str_equal (str, time)) {
 594 			GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
 595 
 596 			gtk_tree_view_set_cursor (
 597 				GTK_TREE_VIEW (ecde->time_tree_view),
 598 				path, NULL, FALSE);
 599 			gtk_tree_view_scroll_to_cell (
 600 				GTK_TREE_VIEW (ecde->time_tree_view),
 601 				path, NULL, FALSE, 0.0, 0.0);
 602 			gtk_tree_path_free (path);
 603 
 604 			found = TRUE;
 605 		}
 606 
 607 		g_free (str);
 608 	}
 609 
 610 	if (!found) {
 611 		gtk_tree_selection_unselect_all (selection);
 612 		gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (ecde->time_tree_view), 0, 0);
 613 	}
 614 }
 615 
 616 static void
 617 e_cell_date_edit_show_popup (ECellDateEdit *ecde,
 618                              gint row,
 619                              gint view_col)
 620 {
 621 	GdkWindow *window;
 622 	gint x, y, width, height;
 623 
 624 	if (ecde->need_time_list_rebuild)
 625 		e_cell_date_edit_rebuild_time_list (ecde);
 626 
 627 	/* This code is practically copied from GtkCombo. */
 628 
 629 	e_cell_date_edit_get_popup_pos (ecde, row, view_col, &x, &y, &height, &width);
 630 
 631 	window = gtk_widget_get_window (ecde->popup_window);
 632 	gtk_window_move (GTK_WINDOW (ecde->popup_window), x, y);
 633 	gtk_widget_set_size_request (ecde->popup_window, width, height);
 634 	gtk_widget_realize (ecde->popup_window);
 635 	gdk_window_resize (window, width, height);
 636 	gtk_widget_show (ecde->popup_window);
 637 
 638 	e_cell_popup_set_shown (E_CELL_POPUP (ecde), TRUE);
 639 }
 640 
 641 /* Calculates the size and position of the popup window (like GtkCombo). */
 642 static void
 643 e_cell_date_edit_get_popup_pos (ECellDateEdit *ecde,
 644                                 gint row,
 645                                 gint view_col,
 646                                 gint *x,
 647                                 gint *y,
 648                                 gint *height,
 649                                 gint *width)
 650 {
 651 	ECellPopup *ecp = E_CELL_POPUP (ecde);
 652 	ETableItem *eti;
 653 	GtkWidget *canvas;
 654 	GtkRequisition popup_requisition;
 655 	GtkAdjustment *adjustment;
 656 	GtkScrollable *scrollable;
 657 	GdkWindow *window;
 658 	gint avail_height, screen_width, column_width, row_height;
 659 	gdouble x1, y1, wx, wy;
 660 	gint value;
 661 
 662 	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
 663 	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
 664 
 665 	window = gtk_widget_get_window (canvas);
 666 	gdk_window_get_origin (window, x, y);
 667 
 668 	x1 = e_table_header_col_diff (eti->header, 0, view_col + 1);
 669 	y1 = e_table_item_row_diff (eti, 0, row + 1);
 670 	column_width = e_table_header_col_diff (
 671 		eti->header, view_col, view_col + 1);
 672 	row_height = e_table_item_row_diff (eti, row, row + 1);
 673 	gnome_canvas_item_i2w (GNOME_CANVAS_ITEM (eti), &x1, &y1);
 674 
 675 	gnome_canvas_world_to_window (
 676 		GNOME_CANVAS (canvas), x1, y1, &wx, &wy);
 677 
 678 	x1 = wx;
 679 	y1 = wy;
 680 
 681 	*x += x1;
 682 	/* The ETable positions don't include the grid lines, I think, so we
 683 	 * add 1. */
 684 	scrollable = GTK_SCROLLABLE (&GNOME_CANVAS (canvas)->layout);
 685 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
 686 	value = (gint) gtk_adjustment_get_value (adjustment);
 687 	*y += y1 + 1 - value + ((GnomeCanvas *)canvas)->zoom_yofs;
 688 
 689 	avail_height = gdk_screen_height () - *y;
 690 
 691 	/* We'll use the entire screen width if needed, but we save space for
 692 	 * the vertical scrollbar in case we need to show that. */
 693 	screen_width = gdk_screen_width ();
 694 
 695 	gtk_widget_get_preferred_size (ecde->popup_window, &popup_requisition, NULL);
 696 
 697 	/* Calculate the desired width. */
 698 	*width = popup_requisition.width;
 699 
 700 	/* Use at least the same width as the column. */
 701 	if (*width < column_width)
 702 		*width = column_width;
 703 
 704 	/* Check if it fits in the available height. */
 705 	if (popup_requisition.height > avail_height) {
 706 		/* It doesn't fit, so we see if we have the minimum space
 707 		 * needed. */
 708 		if (*y - row_height > avail_height) {
 709 			/* We don't, so we show the popup above the cell
 710 			 * instead of below it. */
 711 			*y -= (popup_requisition.height + row_height);
 712 			if (*y < 0)
 713 				*y = 0;
 714 		}
 715 	}
 716 
 717 	/* We try to line it up with the right edge of the column, but we don't
 718 	 * want it to go off the edges of the screen. */
 719 	if (*x > screen_width)
 720 		*x = screen_width;
 721 	*x -= *width;
 722 	if (*x < 0)
 723 		*x = 0;
 724 
 725 	*height = popup_requisition.height;
 726 }
 727 
 728 /* This handles key press events in the popup window. If the Escape key is
 729  * pressed we hide the popup, and do not change the cell contents. */
 730 static gint
 731 e_cell_date_edit_key_press (GtkWidget *popup_window,
 732                             GdkEventKey *event,
 733                             ECellDateEdit *ecde)
 734 {
 735 	/* If the Escape key is pressed we hide the popup. */
 736 	if (event->keyval != GDK_KEY_Escape)
 737 		return FALSE;
 738 
 739 	e_cell_date_edit_hide_popup (ecde);
 740 
 741 	return TRUE;
 742 }
 743 
 744 /* This handles button press events in the popup window. If the button is
 745  * pressed outside the popup, we hide it and do not change the cell contents.
 746 */
 747 static gint
 748 e_cell_date_edit_button_press (GtkWidget *popup_window,
 749                                GdkEventButton *event,
 750                                ECellDateEdit *ecde)
 751 {
 752 	GtkWidget *event_widget;
 753 
 754 	event_widget = gtk_get_event_widget ((GdkEvent *) event);
 755 	if (gtk_widget_get_toplevel (event_widget) != popup_window) {
 756 		e_cell_date_edit_hide_popup (ecde);
 757 	}
 758 
 759 	return TRUE;
 760 }
 761 
 762 /* Clears the time list and rebuilds it using the lower_hour, upper_hour
 763  * and use_24_hour_format settings. */
 764 static void
 765 e_cell_date_edit_rebuild_time_list (ECellDateEdit *ecde)
 766 {
 767 	GtkListStore *store;
 768 	gchar buffer[40];
 769 	struct tm tmp_tm;
 770 	gint hour, min;
 771 
 772 	store = GTK_LIST_STORE (gtk_tree_view_get_model (
 773 		GTK_TREE_VIEW (ecde->time_tree_view)));
 774 	gtk_list_store_clear (store);
 775 
 776 	/* Fill the struct tm with some sane values. */
 777 	tmp_tm.tm_year = 2000;
 778 	tmp_tm.tm_mon = 0;
 779 	tmp_tm.tm_mday = 1;
 780 	tmp_tm.tm_sec  = 0;
 781 	tmp_tm.tm_isdst = 0;
 782 
 783 	for (hour = ecde->lower_hour; hour <= ecde->upper_hour; hour++) {
 784 		/* We don't want to display midnight at the end, since that is
 785 		 * really in the next day. */
 786 		if (hour == 24)
 787 			break;
 788 
 789 		/* We want to finish on upper_hour, with min == 0. */
 790 		for (min = 0;
 791 		     min == 0 || (min < 60 && hour != ecde->upper_hour);
 792 		     min += 30) {
 793 			GtkTreeIter iter;
 794 
 795 			tmp_tm.tm_hour = hour;
 796 			tmp_tm.tm_min  = min;
 797 			e_time_format_time (&tmp_tm, ecde->use_24_hour_format,
 798 					    FALSE, buffer, sizeof (buffer));
 799 
 800 			gtk_list_store_append (store, &iter);
 801 			gtk_list_store_set (store, &iter, 0, buffer, -1);
 802 		}
 803 	}
 804 
 805 	ecde->need_time_list_rebuild = FALSE;
 806 }
 807 
 808 static void
 809 e_cell_date_edit_on_ok_clicked (GtkWidget *button,
 810                                 ECellDateEdit *ecde)
 811 {
 812 	ECalendarItem *calitem;
 813 	GDate start_date, end_date;
 814 	gboolean day_selected;
 815 	struct tm date_tm;
 816 	gchar buffer[64];
 817 	const gchar *text;
 818 	ETimeParseStatus status;
 819 	gboolean is_date = FALSE;
 820 
 821 	calitem = E_CALENDAR_ITEM (E_CALENDAR (ecde->calendar)->calitem);
 822 	day_selected = e_calendar_item_get_selection (
 823 		calitem, &start_date, &end_date);
 824 
 825 	text = gtk_entry_get_text (GTK_ENTRY (ecde->time_entry));
 826 	status = e_time_parse_time (text, &date_tm);
 827 	if (status == E_TIME_PARSE_INVALID) {
 828 		e_cell_date_edit_show_time_invalid_warning (ecde);
 829 		return;
 830 	} else if (status == E_TIME_PARSE_NONE) {
 831 		is_date = TRUE;
 832 	}
 833 
 834 	if (day_selected) {
 835 		date_tm.tm_year = g_date_get_year (&start_date) - 1900;
 836 		date_tm.tm_mon = g_date_get_month (&start_date) - 1;
 837 		date_tm.tm_mday = g_date_get_day (&start_date);
 838 		/* We need to call this to set the weekday. */
 839 		mktime (&date_tm);
 840 		e_time_format_date_and_time (&date_tm,
 841 					     ecde->use_24_hour_format,
 842 					     !is_date, FALSE,
 843 					     buffer, sizeof (buffer));
 844 	} else {
 845 		buffer[0] = '\0';
 846 	}
 847 
 848 	e_cell_date_edit_update_cell (ecde, buffer);
 849 	e_cell_date_edit_hide_popup (ecde);
 850 }
 851 
 852 static void
 853 e_cell_date_edit_show_time_invalid_warning (ECellDateEdit *ecde)
 854 {
 855 	GtkWidget *dialog;
 856 	struct tm date_tm;
 857 	gchar buffer[64];
 858 
 859 	/* Create a useful error message showing the correct format. */
 860 	date_tm.tm_year = 100;
 861 	date_tm.tm_mon = 0;
 862 	date_tm.tm_mday = 1;
 863 	date_tm.tm_hour = 1;
 864 	date_tm.tm_min = 30;
 865 	date_tm.tm_sec = 0;
 866 	date_tm.tm_isdst = -1;
 867 	e_time_format_time (&date_tm, ecde->use_24_hour_format, FALSE,
 868 			    buffer, sizeof (buffer));
 869 
 870 	/* FIXME: Fix transient settings - I'm not sure it works with popup
 871 	 * windows. Maybe we need to use a normal window without decorations.*/
 872 	dialog = gtk_message_dialog_new (
 873 		GTK_WINDOW (ecde->popup_window),
 874 		GTK_DIALOG_DESTROY_WITH_PARENT,
 875 		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
 876 		_("The time must be in the format: %s"),
 877 		buffer);
 878 	gtk_dialog_run (GTK_DIALOG (dialog));
 879 	gtk_widget_destroy (dialog);
 880 }
 881 
 882 static void
 883 e_cell_date_edit_on_now_clicked (GtkWidget *button,
 884                                  ECellDateEdit *ecde)
 885 {
 886 	struct tm tmp_tm;
 887 	time_t t;
 888 	gchar buffer[64];
 889 
 890 	if (ecde->time_callback) {
 891 		tmp_tm = ecde->time_callback (
 892 			ecde, ecde->time_callback_data);
 893 	} else {
 894 		t = time (NULL);
 895 		tmp_tm = *localtime (&t);
 896 	}
 897 
 898 	e_time_format_date_and_time (
 899 		&tmp_tm, ecde->use_24_hour_format,
 900 		TRUE, FALSE, buffer, sizeof (buffer));
 901 
 902 	e_cell_date_edit_update_cell (ecde, buffer);
 903 	e_cell_date_edit_hide_popup (ecde);
 904 }
 905 
 906 static void
 907 e_cell_date_edit_on_none_clicked (GtkWidget *button,
 908                                   ECellDateEdit *ecde)
 909 {
 910 	e_cell_date_edit_update_cell (ecde, "");
 911 	e_cell_date_edit_hide_popup (ecde);
 912 }
 913 
 914 static void
 915 e_cell_date_edit_on_today_clicked (GtkWidget *button,
 916                                    ECellDateEdit *ecde)
 917 {
 918 	struct tm tmp_tm;
 919 	time_t t;
 920 	gchar buffer[64];
 921 
 922 	if (ecde->time_callback) {
 923 		tmp_tm = ecde->time_callback (
 924 			ecde, ecde->time_callback_data);
 925 	} else {
 926 		t = time (NULL);
 927 		tmp_tm = *localtime (&t);
 928 	}
 929 
 930 	tmp_tm.tm_hour = 0;
 931 	tmp_tm.tm_min = 0;
 932 	tmp_tm.tm_sec = 0;
 933 
 934 	e_time_format_date_and_time (
 935 		&tmp_tm, ecde->use_24_hour_format,
 936 		FALSE, FALSE, buffer, sizeof (buffer));
 937 
 938 	e_cell_date_edit_update_cell (ecde, buffer);
 939 	e_cell_date_edit_hide_popup (ecde);
 940 }
 941 
 942 static void
 943 e_cell_date_edit_update_cell (ECellDateEdit *ecde,
 944                               const gchar *text)
 945 {
 946 	ECellPopup *ecp = E_CELL_POPUP (ecde);
 947 	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
 948 	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
 949 	ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
 950 	ETableCol *ecol;
 951 	gchar *old_text;
 952 
 953 	/* Compare the new text with the existing cell contents. */
 954 	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
 955 
 956 	old_text = e_cell_text_get_text (
 957 		ecell_text, ecv->e_table_model,
 958 		ecol->col_idx, ecp->popup_row);
 959 
 960 	/* If they are different, update the cell contents. */
 961 	if (strcmp (old_text, text)) {
 962 		e_cell_text_set_value (
 963 			ecell_text, ecv->e_table_model,
 964 			ecol->col_idx, ecp->popup_row, text);
 965 		e_cell_leave_edit (
 966 			ecv, ecp->popup_view_col,
 967 			ecol->col_idx, ecp->popup_row, NULL);
 968 	}
 969 
 970 	e_cell_text_free_text (ecell_text, old_text);
 971 }
 972 
 973 static void
 974 e_cell_date_edit_on_time_selected (GtkTreeSelection *selection,
 975                                    ECellDateEdit *ecde)
 976 {
 977 	gchar *list_item_text = NULL;
 978 	GtkTreeIter iter;
 979 	GtkTreeModel *model;
 980 
 981 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
 982 		return;
 983 
 984 	gtk_tree_model_get (model, &iter, 0, &list_item_text, -1);
 985 
 986 	g_return_if_fail (list_item_text != NULL);
 987 
 988 	gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), list_item_text);
 989 
 990 	g_free (list_item_text);
 991 }
 992 
 993 static void
 994 e_cell_date_edit_hide_popup (ECellDateEdit *ecde)
 995 {
 996 	gtk_grab_remove (ecde->popup_window);
 997 	gtk_widget_hide (ecde->popup_window);
 998 	e_cell_popup_set_shown (E_CELL_POPUP (ecde), FALSE);
 999 }
1000 
1001 /* These freeze and thaw the rebuilding of the time list. They are useful when
1002  * setting several properties which result in rebuilds of the list, e.g. the
1003  * lower_hour, upper_hour and use_24_hour_format properties. */
1004 void
1005 e_cell_date_edit_freeze (ECellDateEdit *ecde)
1006 {
1007 	g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
1008 
1009 	ecde->freeze_count++;
1010 }
1011 
1012 void
1013 e_cell_date_edit_thaw (ECellDateEdit *ecde)
1014 {
1015 	g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
1016 
1017 	if (ecde->freeze_count > 0) {
1018 		ecde->freeze_count--;
1019 
1020 		if (ecde->freeze_count == 0)
1021 			e_cell_date_edit_rebuild_time_list (ecde);
1022 	}
1023 }
1024 
1025 /* Sets a callback to use to get the current time. This is useful if the
1026  * application needs to use its own timezone data rather than rely on the
1027  * Unix timezone. */
1028 void
1029 e_cell_date_edit_set_get_time_callback (ECellDateEdit *ecde,
1030                                         ECellDateEditGetTimeCallback cb,
1031                                         gpointer data,
1032                                         GDestroyNotify destroy)
1033 {
1034 	g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
1035 
1036 	if (ecde->time_callback_data && ecde->time_callback_destroy)
1037 		(*ecde->time_callback_destroy) (ecde->time_callback_data);
1038 
1039 	ecde->time_callback = cb;
1040 	ecde->time_callback_data = data;
1041 	ecde->time_callback_destroy = destroy;
1042 }