1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * published by the Free Software Foundation; either the
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  * Authors:
15  *		Damon Chaplin <damon@ximian.com>
16  *
17  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
18  */
19 
20 /*
21  * EDateEdit - a widget based on GnomeDateEdit to provide a date & optional
22  * time field with popups for entering a date.
23  */
24 
25 #include "evolution-config.h"
26 
27 #include "e-dateedit.h"
28 
29 #include <ctype.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <gdk/gdkkeysyms.h>
34 #include <atk/atkrelation.h>
35 #include <atk/atkrelationset.h>
36 #include <glib/gi18n.h>
37 
38 #include <libebackend/libebackend.h>
39 
40 #include "e-calendar.h"
41 #include "e-util-enumtypes.h"
42 
43 #define E_DATE_EDIT_GET_PRIVATE(obj) \
44 	(G_TYPE_INSTANCE_GET_PRIVATE \
45 	((obj), E_TYPE_DATE_EDIT, EDateEditPrivate))
46 
47 struct _EDateEditPrivate {
48 	GtkWidget *date_entry;
49 	GtkWidget *date_button;
50 
51 	GtkWidget *space;
52 
53 	GtkWidget *time_combo;
54 
55 	GtkWidget *cal_popup;
56 	GtkWidget *calendar;
57 	GtkWidget *now_button;
58 	GtkWidget *today_button;
59 	GtkWidget *none_button;		/* This will only be visible if a
60 					 * 'None' date/time is permitted. */
61 
62 	GdkDevice *grabbed_keyboard;
63 	GdkDevice *grabbed_pointer;
64 
65 	gboolean show_date;
66 	gboolean show_time;
67 	gboolean use_24_hour_format;
68 
69 	/* This is TRUE if we want to make the time field insensitive rather
70 	 * than hide it when set_show_time() is called. */
71 	gboolean make_time_insensitive;
72 
73 	/* This is the range of hours we show in the time popup. */
74 	gint lower_hour;
75 	gint upper_hour;
76 
77 	/* This indicates whether the last date committed was invalid.
78 	 * (A date is committed by hitting Return, moving the keyboard focus,
79 	 * or selecting a date in the popup). Note that this only indicates
80 	 * that the date couldn't be parsed. A date set to 'None' is valid
81 	 * here, though e_date_edit_date_is_valid() will return FALSE if an
82 	 * empty date isn't actually permitted. */
83 	gboolean date_is_valid;
84 
85 	/* This is the last valid date which was set. If the date was set to
86 	 * 'None' or empty, date_set_to_none will be TRUE and the other fields
87 	 * are undefined, so don't use them. */
88 	gboolean date_set_to_none;
89 	gint year;
90 	gint month;
91 	gint day;
92 
93 	/* This indicates whether the last time committed was invalid.
94 	 * (A time is committed by hitting Return, moving the keyboard focus,
95 	 * or selecting a time in the popup). Note that this only indicates
96 	 * that the time couldn't be parsed. An empty/None time is valid
97 	 * here, though e_date_edit_time_is_valid() will return FALSE if an
98 	 * empty time isn't actually permitted. */
99 	gboolean time_is_valid;
100 
101 	/* This is the last valid time which was set. If the time was set to
102 	 * 'None' or empty, time_set_to_none will be TRUE and the other fields
103 	 * are undefined, so don't use them. */
104 	gboolean time_set_to_none;
105 	gint hour;
106 	gint minute;
107 
108 	EDateEditGetTimeCallback time_callback;
109 	gpointer time_callback_data;
110 	GDestroyNotify time_callback_destroy;
111 
112 	gboolean twodigit_year_can_future;
113 
114 	/* set to TRUE when the date has been changed by typing to the entry */
115 	gboolean date_been_changed;
116 	gboolean time_been_changed;
117 
118 	gboolean allow_no_date_set;
119 };
120 
121 enum {
122 	PROP_0,
123 	PROP_ALLOW_NO_DATE_SET,
124 	PROP_SHOW_DATE,
125 	PROP_SHOW_TIME,
126 	PROP_SHOW_WEEK_NUMBERS,
127 	PROP_USE_24_HOUR_FORMAT,
128 	PROP_WEEK_START_DAY,
129 	PROP_TWODIGIT_YEAR_CAN_FUTURE,
130 	PROP_SET_NONE
131 };
132 
133 enum {
134 	CHANGED,
135 	LAST_SIGNAL
136 };
137 
138 static void create_children			(EDateEdit	*dedit);
139 static gboolean e_date_edit_mnemonic_activate	(GtkWidget	*widget,
140 						 gboolean	 group_cycling);
141 static void e_date_edit_grab_focus		(GtkWidget	*widget);
142 
143 static gint on_date_entry_key_press		(GtkWidget	*widget,
144 						 GdkEvent	*key_event,
145 						 EDateEdit	*dedit);
146 static gint on_date_entry_key_release		(GtkWidget	*widget,
147 						 GdkEvent	*key_event,
148 						 EDateEdit	*dedit);
149 static void on_date_button_clicked		(GtkWidget	*widget,
150 						 EDateEdit	*dedit);
151 static void e_date_edit_show_date_popup		(EDateEdit	*dedit,
152 						 GdkEvent	*event);
153 static void position_date_popup			(EDateEdit	*dedit);
154 static void on_date_popup_none_button_clicked	(GtkWidget	*button,
155 						 EDateEdit	*dedit);
156 static void on_date_popup_today_button_clicked	(GtkWidget	*button,
157 						 EDateEdit	*dedit);
158 static void on_date_popup_now_button_clicked	(GtkWidget	*button,
159 						 EDateEdit	*dedit);
160 static gint on_date_popup_delete_event		(GtkWidget	*widget,
161 						 EDateEdit	*dedit);
162 static gint on_date_popup_key_press		(GtkWidget	*widget,
163 						 GdkEventKey	*event,
164 						 EDateEdit	*dedit);
165 static gint on_date_popup_button_press		(GtkWidget	*widget,
166 						 GdkEvent	*button_event,
167 						 gpointer	 data);
168 static void on_date_popup_date_selected		(ECalendarItem	*calitem,
169 						 EDateEdit	*dedit);
170 static void hide_date_popup			(EDateEdit	*dedit);
171 static void rebuild_time_popup			(EDateEdit	*dedit);
172 static gboolean field_set_to_none		(const gchar	*text);
173 static gboolean e_date_edit_parse_date		(EDateEdit	*dedit,
174 						 const gchar	*date_text,
175 						 struct tm	*date_tm);
176 static gboolean e_date_edit_parse_time		(EDateEdit	*dedit,
177 						 const gchar	*time_text,
178 						 struct tm	*time_tm);
179 static void on_date_edit_time_selected		(GtkComboBox	*combo,
180 						 EDateEdit	*dedit);
181 static gint on_time_entry_key_press		(GtkWidget	*widget,
182 						 GdkEvent	*key_event,
183 						 EDateEdit	*dedit);
184 static gint on_time_entry_key_release		(GtkWidget	*widget,
185 						 GdkEvent	*key_event,
186 						 EDateEdit	*dedit);
187 static gint on_date_entry_focus_out		(GtkEntry	*entry,
188 						 GdkEventFocus  *event,
189 						 EDateEdit	*dedit);
190 static gint on_time_entry_focus_out		(GtkEntry	*entry,
191 						 GdkEventFocus  *event,
192 						 EDateEdit	*dedit);
193 static void e_date_edit_update_date_entry	(EDateEdit	*dedit);
194 static void e_date_edit_update_time_entry	(EDateEdit	*dedit);
195 static void e_date_edit_update_time_combo_state	(EDateEdit	*dedit);
196 static gboolean e_date_edit_check_date_changed	(EDateEdit	*dedit);
197 static gboolean e_date_edit_check_time_changed	(EDateEdit	*dedit);
198 static gboolean e_date_edit_set_date_internal	(EDateEdit	*dedit,
199 						 gboolean	 valid,
200 						 gboolean	 none,
201 						 gint		 year,
202 						 gint		 month,
203 						 gint		 day);
204 static gboolean e_date_edit_set_time_internal	(EDateEdit	*dedit,
205 						 gboolean	 valid,
206 						 gboolean	 none,
207 						 gint		 hour,
208 						 gint		 minute);
209 
210 static gint signals[LAST_SIGNAL];
211 
G_DEFINE_TYPE_WITH_CODE(EDateEdit,e_date_edit,GTK_TYPE_BOX,G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL))212 G_DEFINE_TYPE_WITH_CODE (
213 	EDateEdit,
214 	e_date_edit,
215 	GTK_TYPE_BOX,
216 	G_IMPLEMENT_INTERFACE (
217 		E_TYPE_EXTENSIBLE, NULL))
218 
219 static void
220 date_edit_set_property (GObject *object,
221                         guint property_id,
222                         const GValue *value,
223                         GParamSpec *pspec)
224 {
225 	switch (property_id) {
226 		case PROP_ALLOW_NO_DATE_SET:
227 			e_date_edit_set_allow_no_date_set (
228 				E_DATE_EDIT (object),
229 				g_value_get_boolean (value));
230 			return;
231 
232 		case PROP_SHOW_DATE:
233 			e_date_edit_set_show_date (
234 				E_DATE_EDIT (object),
235 				g_value_get_boolean (value));
236 			return;
237 
238 		case PROP_SHOW_TIME:
239 			e_date_edit_set_show_time (
240 				E_DATE_EDIT (object),
241 				g_value_get_boolean (value));
242 			return;
243 
244 		case PROP_SHOW_WEEK_NUMBERS:
245 			e_date_edit_set_show_week_numbers (
246 				E_DATE_EDIT (object),
247 				g_value_get_boolean (value));
248 			return;
249 
250 		case PROP_USE_24_HOUR_FORMAT:
251 			e_date_edit_set_use_24_hour_format (
252 				E_DATE_EDIT (object),
253 				g_value_get_boolean (value));
254 			return;
255 
256 		case PROP_WEEK_START_DAY:
257 			e_date_edit_set_week_start_day (
258 				E_DATE_EDIT (object),
259 				g_value_get_enum (value));
260 			return;
261 
262 		case PROP_TWODIGIT_YEAR_CAN_FUTURE:
263 			e_date_edit_set_twodigit_year_can_future (
264 				E_DATE_EDIT (object),
265 				g_value_get_boolean (value));
266 			return;
267 
268 		case PROP_SET_NONE:
269 			if (g_value_get_boolean (value))
270 				e_date_edit_set_time (E_DATE_EDIT (object), -1);
271 			return;
272 	}
273 
274 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
275 }
276 
277 static void
date_edit_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)278 date_edit_get_property (GObject *object,
279                         guint property_id,
280                         GValue *value,
281                         GParamSpec *pspec)
282 {
283 	switch (property_id) {
284 		case PROP_ALLOW_NO_DATE_SET:
285 			g_value_set_boolean (
286 				value, e_date_edit_get_allow_no_date_set (
287 				E_DATE_EDIT (object)));
288 			return;
289 
290 		case PROP_SHOW_DATE:
291 			g_value_set_boolean (
292 				value, e_date_edit_get_show_date (
293 				E_DATE_EDIT (object)));
294 			return;
295 
296 		case PROP_SHOW_TIME:
297 			g_value_set_boolean (
298 				value, e_date_edit_get_show_time (
299 				E_DATE_EDIT (object)));
300 			return;
301 
302 		case PROP_SHOW_WEEK_NUMBERS:
303 			g_value_set_boolean (
304 				value, e_date_edit_get_show_week_numbers (
305 				E_DATE_EDIT (object)));
306 			return;
307 
308 		case PROP_USE_24_HOUR_FORMAT:
309 			g_value_set_boolean (
310 				value, e_date_edit_get_use_24_hour_format (
311 				E_DATE_EDIT (object)));
312 			return;
313 
314 		case PROP_WEEK_START_DAY:
315 			g_value_set_enum (
316 				value, e_date_edit_get_week_start_day (
317 				E_DATE_EDIT (object)));
318 			return;
319 
320 		case PROP_TWODIGIT_YEAR_CAN_FUTURE:
321 			g_value_set_boolean (
322 				value, e_date_edit_get_twodigit_year_can_future (
323 				E_DATE_EDIT (object)));
324 			return;
325 	}
326 
327 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
328 }
329 
330 static void
date_edit_dispose(GObject * object)331 date_edit_dispose (GObject *object)
332 {
333 	EDateEdit *dedit;
334 
335 	dedit = E_DATE_EDIT (object);
336 
337 	e_date_edit_set_get_time_callback (dedit, NULL, NULL, NULL);
338 
339 	g_clear_pointer (&dedit->priv->cal_popup, gtk_widget_destroy);
340 
341 	if (dedit->priv->grabbed_keyboard != NULL) {
342 		gdk_device_ungrab (
343 			dedit->priv->grabbed_keyboard,
344 			GDK_CURRENT_TIME);
345 		g_object_unref (dedit->priv->grabbed_keyboard);
346 		dedit->priv->grabbed_keyboard = NULL;
347 	}
348 
349 	if (dedit->priv->grabbed_pointer != NULL) {
350 		gdk_device_ungrab (
351 			dedit->priv->grabbed_pointer,
352 			GDK_CURRENT_TIME);
353 		g_object_unref (dedit->priv->grabbed_pointer);
354 		dedit->priv->grabbed_pointer = NULL;
355 	}
356 
357 	/* Chain up to parent's dispose() method. */
358 	G_OBJECT_CLASS (e_date_edit_parent_class)->dispose (object);
359 }
360 
361 static void
e_date_edit_class_init(EDateEditClass * class)362 e_date_edit_class_init (EDateEditClass *class)
363 {
364 	GObjectClass *object_class;
365 	GtkWidgetClass *widget_class;
366 
367 	g_type_class_add_private (class, sizeof (EDateEditPrivate));
368 
369 	object_class = G_OBJECT_CLASS (class);
370 	object_class->set_property = date_edit_set_property;
371 	object_class->get_property = date_edit_get_property;
372 	object_class->dispose = date_edit_dispose;
373 
374 	widget_class = GTK_WIDGET_CLASS (class);
375 	widget_class->mnemonic_activate = e_date_edit_mnemonic_activate;
376 	widget_class->grab_focus = e_date_edit_grab_focus;
377 
378 	g_object_class_install_property (
379 		object_class,
380 		PROP_ALLOW_NO_DATE_SET,
381 		g_param_spec_boolean (
382 			"allow-no-date-set",
383 			"Allow No Date Set",
384 			NULL,
385 			FALSE,
386 			G_PARAM_READWRITE));
387 
388 	g_object_class_install_property (
389 		object_class,
390 		PROP_SHOW_DATE,
391 		g_param_spec_boolean (
392 			"show-date",
393 			"Show Date",
394 			NULL,
395 			TRUE,
396 			G_PARAM_READWRITE));
397 
398 	g_object_class_install_property (
399 		object_class,
400 		PROP_SHOW_TIME,
401 		g_param_spec_boolean (
402 			"show-time",
403 			"Show Time",
404 			NULL,
405 			TRUE,
406 			G_PARAM_READWRITE));
407 
408 	g_object_class_install_property (
409 		object_class,
410 		PROP_SHOW_WEEK_NUMBERS,
411 		g_param_spec_boolean (
412 			"show-week-numbers",
413 			"Show Week Numbers",
414 			NULL,
415 			TRUE,
416 			G_PARAM_READWRITE));
417 
418 	g_object_class_install_property (
419 		object_class,
420 		PROP_USE_24_HOUR_FORMAT,
421 		g_param_spec_boolean (
422 			"use-24-hour-format",
423 			"Use 24-Hour Format",
424 			NULL,
425 			TRUE,
426 			G_PARAM_READWRITE));
427 
428 	g_object_class_install_property (
429 		object_class,
430 		PROP_WEEK_START_DAY,
431 		g_param_spec_enum (
432 			"week-start-day",
433 			"Week Start Day",
434 			NULL,
435 			E_TYPE_DATE_WEEKDAY,
436 			G_DATE_MONDAY,
437 			G_PARAM_READWRITE |
438 			G_PARAM_STATIC_STRINGS));
439 
440 	g_object_class_install_property (
441 		object_class,
442 		PROP_TWODIGIT_YEAR_CAN_FUTURE,
443 		g_param_spec_boolean (
444 			"twodigit-year-can-future",
445 			"Two-digit year can be treated as future",
446 			NULL,
447 			TRUE,
448 			G_PARAM_READWRITE));
449 
450 	g_object_class_install_property (
451 		object_class,
452 		PROP_SET_NONE,
453 		g_param_spec_boolean (
454 			"set-none",
455 			"Sets None as selected date",
456 			NULL,
457 			FALSE,
458 			G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
459 
460 	signals[CHANGED] = g_signal_new (
461 		"changed",
462 		G_OBJECT_CLASS_TYPE (object_class),
463 		G_SIGNAL_RUN_FIRST,
464 		G_STRUCT_OFFSET (EDateEditClass, changed),
465 		NULL, NULL,
466 		g_cclosure_marshal_VOID__VOID,
467 		G_TYPE_NONE, 0);
468 }
469 
470 static void
e_date_edit_init(EDateEdit * dedit)471 e_date_edit_init (EDateEdit *dedit)
472 {
473 	dedit->priv = E_DATE_EDIT_GET_PRIVATE (dedit);
474 
475 	dedit->priv->show_date = TRUE;
476 	dedit->priv->show_time = TRUE;
477 	dedit->priv->use_24_hour_format = TRUE;
478 
479 	dedit->priv->make_time_insensitive = FALSE;
480 
481 	dedit->priv->lower_hour = 0;
482 	dedit->priv->upper_hour = 24;
483 
484 	dedit->priv->date_is_valid = TRUE;
485 	dedit->priv->date_set_to_none = TRUE;
486 	dedit->priv->time_is_valid = TRUE;
487 	dedit->priv->time_set_to_none = TRUE;
488 	dedit->priv->time_callback = NULL;
489 	dedit->priv->time_callback_data = NULL;
490 	dedit->priv->time_callback_destroy = NULL;
491 
492 	dedit->priv->twodigit_year_can_future = TRUE;
493 	dedit->priv->date_been_changed = FALSE;
494 	dedit->priv->time_been_changed = FALSE;
495 
496 	gtk_orientable_set_orientation (GTK_ORIENTABLE (dedit), GTK_ORIENTATION_HORIZONTAL);
497 	gtk_box_set_spacing (GTK_BOX (dedit), 3);
498 
499 	create_children (dedit);
500 
501 	/* Set it to the current time. */
502 	e_date_edit_set_time (dedit, 0);
503 
504 	e_extensible_load_extensions (E_EXTENSIBLE (dedit));
505 }
506 
507 /**
508  * e_date_edit_new:
509  *
510  * Description: Creates a new #EDateEdit widget which can be used
511  * to provide an easy to use way for entering dates and times.
512  *
513  * Returns: a new #EDateEdit widget.
514  */
515 GtkWidget *
e_date_edit_new(void)516 e_date_edit_new (void)
517 {
518 	EDateEdit *dedit;
519 	AtkObject *a11y;
520 
521 	dedit = g_object_new (E_TYPE_DATE_EDIT, NULL);
522 	a11y = gtk_widget_get_accessible (GTK_WIDGET (dedit));
523 	atk_object_set_name (a11y, _("Date and Time"));
524 
525 	return GTK_WIDGET (dedit);
526 }
527 
528 static void
on_time_entry_changed_cb(GtkEditable * editable,EDateEdit * dedit)529 on_time_entry_changed_cb (GtkEditable *editable,
530 			  EDateEdit *dedit)
531 {
532 	e_date_edit_check_time_changed (dedit);
533 }
534 
535 static void
create_children(EDateEdit * dedit)536 create_children (EDateEdit *dedit)
537 {
538 	EDateEditPrivate *priv;
539 	ECalendar *calendar;
540 	GtkWidget *frame, *arrow;
541 	GtkWidget *vbox, *bbox;
542 	GtkWidget *child;
543 	AtkObject *a11y;
544 	GtkListStore *time_store;
545 	GList *cells;
546 	GtkCssProvider *css_provider;
547 	GtkStyleContext *style_context;
548 	const gchar *css;
549 	GError *error = NULL;
550 	PangoAttrList *tnum;
551 	PangoAttribute *attr;
552 
553 	priv = dedit->priv;
554 
555 	priv->date_entry = gtk_entry_new ();
556 	a11y = gtk_widget_get_accessible (priv->date_entry);
557 	atk_object_set_description (a11y, _("Text entry to input date"));
558 	atk_object_set_name (a11y, _("Date"));
559 	gtk_box_pack_start (GTK_BOX (dedit), priv->date_entry, FALSE, TRUE, 0);
560 	gtk_widget_set_size_request (priv->date_entry, 100, -1);
561 
562 	g_signal_connect (
563 		priv->date_entry, "key_press_event",
564 		G_CALLBACK (on_date_entry_key_press), dedit);
565 	g_signal_connect (
566 		priv->date_entry, "key_release_event",
567 		G_CALLBACK (on_date_entry_key_release), dedit);
568 	g_signal_connect_after (
569 		priv->date_entry, "focus_out_event",
570 		G_CALLBACK (on_date_entry_focus_out), dedit);
571 
572 	priv->date_button = gtk_button_new ();
573 	g_signal_connect (
574 		priv->date_button, "clicked",
575 		G_CALLBACK (on_date_button_clicked), dedit);
576 	gtk_box_pack_start (
577 		GTK_BOX (dedit), priv->date_button,
578 		FALSE, FALSE, 0);
579 	a11y = gtk_widget_get_accessible (priv->date_button);
580 	atk_object_set_description (a11y, _("Click this button to show a calendar"));
581 	atk_object_set_name (a11y, _("Date"));
582 
583 	arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
584 	gtk_container_add (GTK_CONTAINER (priv->date_button), arrow);
585 	gtk_widget_show (arrow);
586 
587 	if (priv->show_date) {
588 		gtk_widget_show (priv->date_entry);
589 		gtk_widget_show (priv->date_button);
590 	}
591 
592 	/* This is just to create a space between the date & time parts. */
593 	priv->space = gtk_drawing_area_new ();
594 	gtk_box_pack_start (GTK_BOX (dedit), priv->space, FALSE, FALSE, 2);
595 
596 	time_store = gtk_list_store_new (1, G_TYPE_STRING);
597 	priv->time_combo = gtk_combo_box_new_with_model_and_entry (
598 		GTK_TREE_MODEL (time_store));
599 	gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (priv->time_combo), 0);
600 	gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (priv->time_combo), 6);
601 	g_object_unref (time_store);
602 
603 	css_provider = gtk_css_provider_new ();
604 	css = "GtkComboBox { -GtkComboBox-appears-as-list: 1; }";
605 	gtk_css_provider_load_from_data (css_provider, css, -1, &error);
606 	style_context = gtk_widget_get_style_context (priv->time_combo);
607 	if (error == NULL) {
608 		gtk_style_context_add_provider (
609 			style_context,
610 			GTK_STYLE_PROVIDER (css_provider),
611 			GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
612 	} else {
613 		g_warning ("%s: %s", G_STRFUNC, error->message);
614 		g_clear_error (&error);
615 	}
616 	g_object_unref (css_provider);
617 
618 	child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
619 
620 	/* We need to make sure labels are right-aligned, since we want
621 	 * digits to line up, and with a nonproportional font, the width
622 	 * of a space != width of a digit.  Technically, only 12-hour
623 	 * format needs this, but we do it always, for consistency. */
624 	g_object_set (child, "xalign", 1.0, NULL);
625 	cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->time_combo));
626 	if (cells) {
627 		g_object_set (GTK_CELL_RENDERER (cells->data), "xalign", 1.0, NULL);
628 		tnum = pango_attr_list_new ();
629 		attr = pango_attr_font_features_new ("tnum=1");
630 		pango_attr_list_insert_before (tnum, attr);
631 		g_object_set (GTK_CELL_RENDERER (cells->data), "attributes", tnum, NULL);
632 		pango_attr_list_unref (tnum);
633 		g_list_free (cells);
634 	}
635 
636 	gtk_box_pack_start (GTK_BOX (dedit), priv->time_combo, FALSE, TRUE, 0);
637 	gtk_widget_set_size_request (priv->time_combo, 110, -1);
638 	rebuild_time_popup (dedit);
639 	a11y = gtk_widget_get_accessible (priv->time_combo);
640 	atk_object_set_description (a11y, _("Drop-down combination box to select time"));
641 	atk_object_set_name (a11y, _("Time"));
642 
643 	g_signal_connect (
644 		child, "key_press_event",
645 		G_CALLBACK (on_time_entry_key_press), dedit);
646 	g_signal_connect (
647 		child, "key_release_event",
648 		G_CALLBACK (on_time_entry_key_release), dedit);
649 	g_signal_connect_after (
650 		child, "focus_out_event",
651 		G_CALLBACK (on_time_entry_focus_out), dedit);
652 	g_signal_connect (
653 		child, "changed",
654 		G_CALLBACK (on_time_entry_changed_cb), dedit);
655 	g_signal_connect_after (
656 		priv->time_combo, "changed",
657 		G_CALLBACK (on_date_edit_time_selected), dedit);
658 
659 	if (priv->show_time || priv->make_time_insensitive)
660 		gtk_widget_show (priv->time_combo);
661 
662 	if (!priv->show_time && priv->make_time_insensitive)
663 		gtk_widget_set_sensitive (priv->time_combo, FALSE);
664 
665 	if (priv->show_date
666 	    && (priv->show_time || priv->make_time_insensitive))
667 		gtk_widget_show (priv->space);
668 
669 	priv->cal_popup = gtk_window_new (GTK_WINDOW_POPUP);
670 	gtk_window_set_type_hint (
671 		GTK_WINDOW (priv->cal_popup),
672 		GDK_WINDOW_TYPE_HINT_COMBO);
673 	gtk_widget_set_events (
674 		priv->cal_popup,
675 		gtk_widget_get_events (priv->cal_popup)
676 		| GDK_KEY_PRESS_MASK);
677 	g_signal_connect (
678 		priv->cal_popup, "delete_event",
679 		G_CALLBACK (on_date_popup_delete_event), dedit);
680 	g_signal_connect (
681 		priv->cal_popup, "key_press_event",
682 		G_CALLBACK (on_date_popup_key_press), dedit);
683 	g_signal_connect (
684 		priv->cal_popup, "button_press_event",
685 		G_CALLBACK (on_date_popup_button_press), dedit);
686 	gtk_window_set_resizable (GTK_WINDOW (priv->cal_popup), TRUE);
687 
688 	frame = gtk_frame_new (NULL);
689 	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
690 	gtk_container_add (GTK_CONTAINER (priv->cal_popup), frame);
691 	gtk_widget_show (frame);
692 
693 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
694 	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
695 	gtk_container_add (GTK_CONTAINER (frame), vbox);
696 	gtk_widget_show (vbox);
697 
698 	priv->calendar = e_calendar_new ();
699 	calendar = E_CALENDAR (priv->calendar);
700 	gnome_canvas_item_set (
701 		GNOME_CANVAS_ITEM (e_calendar_get_item (calendar)),
702 		"maximum_days_selected", 1,
703 		"move_selection_when_moving", FALSE,
704 		NULL);
705 
706 	g_signal_connect (
707 		e_calendar_get_item (calendar), "selection_changed",
708 		G_CALLBACK (on_date_popup_date_selected), dedit);
709 
710 	gtk_box_pack_start (GTK_BOX (vbox), priv->calendar, FALSE, FALSE, 0);
711 	gtk_widget_show (priv->calendar);
712 
713 	bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
714 	gtk_box_set_spacing (GTK_BOX (bbox), 2);
715 	gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0);
716 	gtk_widget_show (bbox);
717 
718 	priv->now_button = gtk_button_new_with_mnemonic (_("No_w"));
719 	gtk_container_add (GTK_CONTAINER (bbox), priv->now_button);
720 	gtk_widget_show (priv->now_button);
721 	g_signal_connect (
722 		priv->now_button, "clicked",
723 		G_CALLBACK (on_date_popup_now_button_clicked), dedit);
724 
725 	priv->today_button = gtk_button_new_with_mnemonic (_("_Today"));
726 	gtk_container_add (GTK_CONTAINER (bbox), priv->today_button);
727 	gtk_widget_show (priv->today_button);
728 	g_signal_connect (
729 		priv->today_button, "clicked",
730 		G_CALLBACK (on_date_popup_today_button_clicked), dedit);
731 
732 	/* Note that we don't show this here, since by default a 'None' date
733 	 * is not permitted. */
734 	priv->none_button = gtk_button_new_with_mnemonic (_("_None"));
735 	gtk_container_add (GTK_CONTAINER (bbox), priv->none_button);
736 	g_signal_connect (
737 		priv->none_button, "clicked",
738 		G_CALLBACK (on_date_popup_none_button_clicked), dedit);
739 	e_binding_bind_property (
740 		dedit, "allow-no-date-set",
741 		priv->none_button, "visible",
742 		G_BINDING_SYNC_CREATE);
743 }
744 
745 /* GtkWidget::mnemonic_activate() handler for the EDateEdit */
746 static gboolean
e_date_edit_mnemonic_activate(GtkWidget * widget,gboolean group_cycling)747 e_date_edit_mnemonic_activate (GtkWidget *widget,
748                                gboolean group_cycling)
749 {
750 	e_date_edit_grab_focus (widget);
751 	return TRUE;
752 }
753 
754 /* Grab_focus handler for the EDateEdit. If the date field is being shown, we
755  * grab the focus to that, otherwise we grab it to the time field. */
756 static void
e_date_edit_grab_focus(GtkWidget * widget)757 e_date_edit_grab_focus (GtkWidget *widget)
758 {
759 	EDateEdit *dedit;
760 	GtkWidget *child;
761 
762 	g_return_if_fail (E_IS_DATE_EDIT (widget));
763 
764 	dedit = E_DATE_EDIT (widget);
765 	child = gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo));
766 
767 	if (dedit->priv->show_date)
768 		gtk_widget_grab_focus (dedit->priv->date_entry);
769 	else
770 		gtk_widget_grab_focus (child);
771 }
772 
773 /**
774  * e_date_edit_set_editable:
775  * @dedit: an #EDateEdit
776  * @editable: whether or not the widget should accept edits.
777  *
778  * Allows the programmer to disallow editing (and the popping up of
779  * the calendar widget), while still allowing the user to select the
780  * date from the GtkEntry.
781  */
782 void
e_date_edit_set_editable(EDateEdit * dedit,gboolean editable)783 e_date_edit_set_editable (EDateEdit *dedit,
784                           gboolean editable)
785 {
786 	EDateEditPrivate *priv;
787 
788 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
789 
790 	priv = dedit->priv;
791 
792 	gtk_editable_set_editable (GTK_EDITABLE (priv->date_entry), editable);
793 	gtk_widget_set_sensitive (priv->date_button, editable);
794 }
795 
796 /**
797  * e_date_edit_get_time:
798  * @dedit: an #EDateEdit
799  *
800  * Returns the last valid time entered. If empty times are valid, by calling
801  * e_date_edit_set_allow_no_date_set(), then it may return -1.
802  *
803  * Note that the last time entered may actually have been invalid. You can
804  * check this with e_date_edit_time_is_valid().
805  *
806  * Returns: the last valid time entered, or -1 if the time is not set.
807  */
808 time_t
e_date_edit_get_time(EDateEdit * dedit)809 e_date_edit_get_time (EDateEdit *dedit)
810 {
811 	EDateEditPrivate *priv;
812 	struct tm tmp_tm = { 0 };
813 
814 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), -1);
815 
816 	priv = dedit->priv;
817 
818 	/* Try to parse any new value now. */
819 	e_date_edit_check_date_changed (dedit);
820 	e_date_edit_check_time_changed (dedit);
821 
822 	if (priv->date_set_to_none)
823 		return -1;
824 
825 	tmp_tm.tm_year = priv->year;
826 	tmp_tm.tm_mon = priv->month;
827 	tmp_tm.tm_mday = priv->day;
828 
829 	if (!priv->show_time || priv->time_set_to_none) {
830 		tmp_tm.tm_hour = 0;
831 		tmp_tm.tm_min = 0;
832 	} else {
833 		tmp_tm.tm_hour = priv->hour;
834 		tmp_tm.tm_min = priv->minute;
835 	}
836 	tmp_tm.tm_sec = 0;
837 	tmp_tm.tm_isdst = -1;
838 
839 	return mktime (&tmp_tm);
840 }
841 
842 /**
843  * e_date_edit_set_time:
844  * @dedit: an #EDateEdit
845  * @the_time: The time and date that should be set on the widget
846  *
847  * Description:  Changes the displayed date and time in the EDateEdit
848  * widget to be the one represented by @the_time.  If @the_time is 0
849  * then current time is used. If it is -1, then the date is set to None.
850  *
851  * Note that the time is converted to local time using the Unix timezone,
852  * so if you are using your own timezones then you should use
853  * e_date_edit_set_date() and e_date_edit_set_time_of_day() instead.
854  */
855 void
e_date_edit_set_time(EDateEdit * dedit,time_t the_time)856 e_date_edit_set_time (EDateEdit *dedit,
857                       time_t the_time)
858 {
859 	EDateEditPrivate *priv;
860 	struct tm tmp_tm;
861 	gboolean date_changed = FALSE, time_changed = FALSE;
862 
863 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
864 
865 	priv = dedit->priv;
866 
867 	if (the_time == -1) {
868 		date_changed = e_date_edit_set_date_internal (
869 			dedit, TRUE,
870 			TRUE, 0, 0, 0);
871 		time_changed = e_date_edit_set_time_internal (
872 			dedit, TRUE,
873 			TRUE, 0, 0);
874 	} else {
875 		if (the_time == 0) {
876 			if (priv->time_callback) {
877 				tmp_tm = (*priv->time_callback) (dedit, priv->time_callback_data);
878 			} else {
879 				the_time = time (NULL);
880 				tmp_tm = *localtime (&the_time);
881 			}
882 		} else {
883 			tmp_tm = *localtime (&the_time);
884 		}
885 
886 		date_changed = e_date_edit_set_date_internal (
887 			dedit, TRUE,
888 			FALSE,
889 			tmp_tm.tm_year,
890 			tmp_tm.tm_mon,
891 			tmp_tm.tm_mday);
892 		time_changed = e_date_edit_set_time_internal (
893 			dedit, TRUE,
894 			FALSE,
895 			tmp_tm.tm_hour,
896 			tmp_tm.tm_min);
897 	}
898 
899 	e_date_edit_update_date_entry (dedit);
900 	e_date_edit_update_time_entry (dedit);
901 	e_date_edit_update_time_combo_state (dedit);
902 
903 	/* Emit the signals if the date and/or time has actually changed. */
904 	if (date_changed || time_changed)
905 		g_signal_emit (dedit, signals[CHANGED], 0);
906 }
907 
908 /**
909  * e_date_edit_get_date:
910  * @dedit: an #EDateEdit
911  * @year: returns the year set.
912  * @month: returns the month set (1 - 12).
913  * @day: returns the day set (1 - 31).
914  * @Returns: TRUE if a time was set, or FALSE if the field is empty or 'None'.
915  *
916  * Returns the last valid date entered into the date field.
917  */
918 gboolean
e_date_edit_get_date(EDateEdit * dedit,gint * year,gint * month,gint * day)919 e_date_edit_get_date (EDateEdit *dedit,
920                       gint *year,
921                       gint *month,
922                       gint *day)
923 {
924 	EDateEditPrivate *priv;
925 
926 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
927 
928 	priv = dedit->priv;
929 
930 	/* Try to parse any new value now. */
931 	e_date_edit_check_date_changed (dedit);
932 
933 	*year = priv->year + 1900;
934 	*month = priv->month + 1;
935 	*day = priv->day;
936 
937 	if (priv->date_set_to_none
938 	    && e_date_edit_get_allow_no_date_set (dedit))
939 		return FALSE;
940 
941 	return TRUE;
942 }
943 
944 /**
945  * e_date_edit_set_date:
946  * @dedit: an #EDateEdit
947  * @year: the year to set.
948  * @month: the month to set (1 - 12).
949  * @day: the day to set (1 - 31).
950  *
951  * Sets the date in the date field.
952  */
953 void
e_date_edit_set_date(EDateEdit * dedit,gint year,gint month,gint day)954 e_date_edit_set_date (EDateEdit *dedit,
955                       gint year,
956                       gint month,
957                       gint day)
958 {
959 	gboolean date_changed = FALSE;
960 
961 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
962 
963 	date_changed = e_date_edit_set_date_internal (
964 		dedit, TRUE, FALSE,
965 		year - 1900, month - 1,
966 		day);
967 
968 	e_date_edit_update_date_entry (dedit);
969 	e_date_edit_update_time_combo_state (dedit);
970 
971 	/* Emit the signals if the date has actually changed. */
972 	if (date_changed)
973 		g_signal_emit (dedit, signals[CHANGED], 0);
974 }
975 
976 /**
977  * e_date_edit_get_time_of_day:
978  * @dedit: an #EDateEdit
979  * @hour: returns the hour set, or 0 if the time isn't set.
980  * @minute: returns the minute set, or 0 if the time isn't set.
981  * @Returns: TRUE if a time was set, or FALSE if the field is empty or 'None'.
982  *
983  * Returns the last valid time entered into the time field.
984  */
985 gboolean
e_date_edit_get_time_of_day(EDateEdit * dedit,gint * hour,gint * minute)986 e_date_edit_get_time_of_day (EDateEdit *dedit,
987                              gint *hour,
988                              gint *minute)
989 {
990 	EDateEditPrivate *priv;
991 
992 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
993 
994 	priv = dedit->priv;
995 
996 	/* Try to parse any new value now. */
997 	e_date_edit_check_time_changed (dedit);
998 
999 	if (priv->time_set_to_none) {
1000 		*hour = 0;
1001 		*minute = 0;
1002 		return FALSE;
1003 	} else {
1004 		*hour = priv->hour;
1005 		*minute = priv->minute;
1006 		return TRUE;
1007 	}
1008 }
1009 
1010 /**
1011  * e_date_edit_set_time_of_day:
1012  * @dedit: an #EDateEdit
1013  * @hour: the hour to set, or -1 to set the time to None (i.e. empty).
1014  * @minute: the minute to set.
1015  *
1016  * Description: Sets the time in the time field.
1017  */
1018 void
e_date_edit_set_time_of_day(EDateEdit * dedit,gint hour,gint minute)1019 e_date_edit_set_time_of_day (EDateEdit *dedit,
1020                              gint hour,
1021                              gint minute)
1022 {
1023 	EDateEditPrivate *priv;
1024 	gboolean time_changed = FALSE;
1025 
1026 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1027 
1028 	priv = dedit->priv;
1029 
1030 	if (hour == -1) {
1031 		gboolean allow_no_date_set = e_date_edit_get_allow_no_date_set (dedit);
1032 		g_return_if_fail (allow_no_date_set);
1033 		if (!priv->time_set_to_none) {
1034 			priv->time_set_to_none = TRUE;
1035 			time_changed = TRUE;
1036 		}
1037 	} else if (priv->time_set_to_none
1038 		   || priv->hour != hour
1039 		   || priv->minute != minute) {
1040 		priv->time_set_to_none = FALSE;
1041 		priv->hour = hour;
1042 		priv->minute = minute;
1043 		time_changed = TRUE;
1044 	}
1045 
1046 	e_date_edit_update_time_entry (dedit);
1047 
1048 	if (time_changed)
1049 		g_signal_emit (dedit, signals[CHANGED], 0);
1050 }
1051 
1052 void
e_date_edit_set_date_and_time_of_day(EDateEdit * dedit,gint year,gint month,gint day,gint hour,gint minute)1053 e_date_edit_set_date_and_time_of_day (EDateEdit *dedit,
1054                                       gint year,
1055                                       gint month,
1056                                       gint day,
1057                                       gint hour,
1058                                       gint minute)
1059 {
1060 	gboolean date_changed, time_changed;
1061 
1062 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1063 
1064 	date_changed = e_date_edit_set_date_internal (
1065 		dedit, TRUE, FALSE,
1066 		year - 1900, month - 1, day);
1067 	time_changed = e_date_edit_set_time_internal (
1068 		dedit, TRUE, FALSE,
1069 		hour, minute);
1070 
1071 	e_date_edit_update_date_entry (dedit);
1072 	e_date_edit_update_time_entry (dedit);
1073 	e_date_edit_update_time_combo_state (dedit);
1074 
1075 	if (date_changed || time_changed)
1076 		g_signal_emit (dedit, signals[CHANGED], 0);
1077 }
1078 
1079 /**
1080  * e_date_edit_get_show_date:
1081  * @dedit: an #EDateEdit
1082  * @Returns: Whether the date field is shown.
1083  *
1084  * Description: Returns TRUE if the date field is currently shown.
1085  */
1086 gboolean
e_date_edit_get_show_date(EDateEdit * dedit)1087 e_date_edit_get_show_date (EDateEdit *dedit)
1088 {
1089 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
1090 
1091 	return dedit->priv->show_date;
1092 }
1093 
1094 /**
1095  * e_date_edit_set_show_date:
1096  * @dedit: an #EDateEdit
1097  * @show_date: TRUE if the date field should be shown.
1098  *
1099  * Description: Specifies whether the date field should be shown. The date
1100  * field would be hidden if only a time needed to be entered.
1101  */
1102 void
e_date_edit_set_show_date(EDateEdit * dedit,gboolean show_date)1103 e_date_edit_set_show_date (EDateEdit *dedit,
1104                            gboolean show_date)
1105 {
1106 	EDateEditPrivate *priv;
1107 
1108 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1109 
1110 	priv = dedit->priv;
1111 
1112 	if (priv->show_date == show_date)
1113 		return;
1114 
1115 	priv->show_date = show_date;
1116 
1117 	if (show_date) {
1118 		gtk_widget_show (priv->date_entry);
1119 		gtk_widget_show (priv->date_button);
1120 	} else {
1121 		gtk_widget_hide (priv->date_entry);
1122 		gtk_widget_hide (priv->date_button);
1123 	}
1124 
1125 	e_date_edit_update_time_combo_state (dedit);
1126 
1127 	if (priv->show_date
1128 	    && (priv->show_time || priv->make_time_insensitive))
1129 		gtk_widget_show (priv->space);
1130 	else
1131 		gtk_widget_hide (priv->space);
1132 
1133 	g_object_notify (G_OBJECT (dedit), "show-date");
1134 }
1135 
1136 /**
1137  * e_date_edit_get_show_time:
1138  * @dedit: an #EDateEdit
1139  * @Returns: Whether the time field is shown.
1140  *
1141  * Description: Returns TRUE if the time field is currently shown.
1142  */
1143 gboolean
e_date_edit_get_show_time(EDateEdit * dedit)1144 e_date_edit_get_show_time (EDateEdit *dedit)
1145 {
1146 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
1147 
1148 	return dedit->priv->show_time;
1149 }
1150 
1151 /**
1152  * e_date_edit_set_show_time:
1153  * @dedit: an #EDateEdit
1154  * @show_time: TRUE if the time field should be shown.
1155  *
1156  * Description: Specifies whether the time field should be shown. The time
1157  * field would be hidden if only a date needed to be entered.
1158  */
1159 void
e_date_edit_set_show_time(EDateEdit * dedit,gboolean show_time)1160 e_date_edit_set_show_time (EDateEdit *dedit,
1161                            gboolean show_time)
1162 {
1163 	EDateEditPrivate *priv;
1164 
1165 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1166 
1167 	priv = dedit->priv;
1168 
1169 	if (priv->show_time == show_time)
1170 		return;
1171 
1172 	priv->show_time = show_time;
1173 
1174 	e_date_edit_update_time_combo_state (dedit);
1175 
1176 	g_object_notify (G_OBJECT (dedit), "show-time");
1177 }
1178 
1179 /**
1180  * e_date_edit_get_make_time_insensitive:
1181  * @dedit: an #EDateEdit
1182  * @Returns: Whether the time field is be made insensitive instead of hiding
1183  * it.
1184  *
1185  * Description: Returns TRUE if the time field is made insensitive instead of
1186  * hiding it.
1187  */
1188 gboolean
e_date_edit_get_make_time_insensitive(EDateEdit * dedit)1189 e_date_edit_get_make_time_insensitive (EDateEdit *dedit)
1190 {
1191 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
1192 
1193 	return dedit->priv->make_time_insensitive;
1194 }
1195 
1196 /**
1197  * e_date_edit_set_make_time_insensitive:
1198  * @dedit: an #EDateEdit
1199  * @make_insensitive: TRUE if the time field should be made insensitive instead
1200  * of hiding it.
1201  *
1202  * Description: Specifies whether the time field should be made insensitive
1203  * rather than hiding it. Note that this doesn't make it insensitive - you
1204  * need to call e_date_edit_set_show_time() with FALSE as show_time to do that.
1205  *
1206  * This is useful if you want to disable the time field, but don't want it to
1207  * disappear as that may affect the layout of the widgets.
1208  */
1209 void
e_date_edit_set_make_time_insensitive(EDateEdit * dedit,gboolean make_insensitive)1210 e_date_edit_set_make_time_insensitive (EDateEdit *dedit,
1211                                        gboolean make_insensitive)
1212 {
1213 	EDateEditPrivate *priv;
1214 
1215 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1216 
1217 	priv = dedit->priv;
1218 
1219 	if (priv->make_time_insensitive == make_insensitive)
1220 		return;
1221 
1222 	priv->make_time_insensitive = make_insensitive;
1223 
1224 	e_date_edit_update_time_combo_state (dedit);
1225 }
1226 
1227 /**
1228  * e_date_edit_get_week_start_day:
1229  * @dedit: an #EDateEdit
1230  *
1231  * Returns the week start day currently used in the calendar popup.
1232  *
1233  * Returns: a #GDateWeekday
1234  */
1235 GDateWeekday
e_date_edit_get_week_start_day(EDateEdit * dedit)1236 e_date_edit_get_week_start_day (EDateEdit *dedit)
1237 {
1238 	GDateWeekday week_start_day;
1239 
1240 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), 1);
1241 
1242 	g_object_get (
1243 		e_calendar_get_item (E_CALENDAR (dedit->priv->calendar)),
1244 		"week-start-day", &week_start_day, NULL);
1245 
1246 	return week_start_day;
1247 }
1248 
1249 /**
1250  * e_date_edit_set_week_start_day:
1251  * @dedit: an #EDateEdit
1252  * @week_start_day: a #GDateWeekday
1253  *
1254  * Sets the week start day to use in the calendar popup.
1255  */
1256 void
e_date_edit_set_week_start_day(EDateEdit * dedit,GDateWeekday week_start_day)1257 e_date_edit_set_week_start_day (EDateEdit *dedit,
1258                                 GDateWeekday week_start_day)
1259 {
1260 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1261 	g_return_if_fail (g_date_valid_weekday (week_start_day));
1262 
1263 	gnome_canvas_item_set (
1264 		GNOME_CANVAS_ITEM (e_calendar_get_item (E_CALENDAR (dedit->priv->calendar))),
1265 		"week-start-day", week_start_day, NULL);
1266 
1267 	g_object_notify (G_OBJECT (dedit), "week-start-day");
1268 }
1269 
1270 /* Whether we show week numbers in the date popup. */
1271 gboolean
e_date_edit_get_show_week_numbers(EDateEdit * dedit)1272 e_date_edit_get_show_week_numbers (EDateEdit *dedit)
1273 {
1274 	gboolean show_week_numbers;
1275 
1276 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
1277 
1278 	g_object_get (
1279 		e_calendar_get_item (E_CALENDAR (dedit->priv->calendar)),
1280 		"show_week_numbers", &show_week_numbers, NULL);
1281 
1282 	return show_week_numbers;
1283 }
1284 
1285 void
e_date_edit_set_show_week_numbers(EDateEdit * dedit,gboolean show_week_numbers)1286 e_date_edit_set_show_week_numbers (EDateEdit *dedit,
1287                                    gboolean show_week_numbers)
1288 {
1289 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1290 
1291 	gnome_canvas_item_set (
1292 		GNOME_CANVAS_ITEM (e_calendar_get_item (E_CALENDAR (dedit->priv->calendar))),
1293 		"show_week_numbers", show_week_numbers, NULL);
1294 
1295 	g_object_notify (G_OBJECT (dedit), "show-week-numbers");
1296 }
1297 
1298 /* Whether we use 24 hour format in the time field & popup. */
1299 gboolean
e_date_edit_get_use_24_hour_format(EDateEdit * dedit)1300 e_date_edit_get_use_24_hour_format (EDateEdit *dedit)
1301 {
1302 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
1303 
1304 	return dedit->priv->use_24_hour_format;
1305 }
1306 
1307 void
e_date_edit_set_use_24_hour_format(EDateEdit * dedit,gboolean use_24_hour_format)1308 e_date_edit_set_use_24_hour_format (EDateEdit *dedit,
1309                                     gboolean use_24_hour_format)
1310 {
1311 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1312 
1313 	if (dedit->priv->use_24_hour_format == use_24_hour_format)
1314 		return;
1315 
1316 	dedit->priv->use_24_hour_format = use_24_hour_format;
1317 
1318 	rebuild_time_popup (dedit);
1319 
1320 	e_date_edit_update_time_entry (dedit);
1321 
1322 	g_object_notify (G_OBJECT (dedit), "use-24-hour-format");
1323 }
1324 
1325 /* Whether we allow the date to be set to 'None'. e_date_edit_get_time() will
1326  * return (time_t) -1 in this case. */
1327 gboolean
e_date_edit_get_allow_no_date_set(EDateEdit * dedit)1328 e_date_edit_get_allow_no_date_set (EDateEdit *dedit)
1329 {
1330 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
1331 
1332 	return dedit->priv->allow_no_date_set;
1333 }
1334 
1335 void
e_date_edit_set_allow_no_date_set(EDateEdit * dedit,gboolean allow_no_date_set)1336 e_date_edit_set_allow_no_date_set (EDateEdit *dedit,
1337                                    gboolean allow_no_date_set)
1338 {
1339 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1340 
1341 	if (dedit->priv->allow_no_date_set == allow_no_date_set)
1342 		return;
1343 
1344 	dedit->priv->allow_no_date_set = allow_no_date_set;
1345 
1346 	if (!allow_no_date_set) {
1347 		/* If the date is showing, we make sure it isn't 'None' (we
1348 		 * don't really mind if the time is empty), else if just the
1349 		 * time is showing we make sure it isn't 'None'. */
1350 		if (dedit->priv->show_date) {
1351 			if (dedit->priv->date_set_to_none)
1352 				e_date_edit_set_time (dedit, 0);
1353 		} else {
1354 			if (dedit->priv->time_set_to_none)
1355 				e_date_edit_set_time (dedit, 0);
1356 		}
1357 	}
1358 
1359 	g_object_notify (G_OBJECT (dedit), "allow-no-date-set");
1360 }
1361 
1362 /* The range of time to show in the time combo popup. */
1363 void
e_date_edit_get_time_popup_range(EDateEdit * dedit,gint * lower_hour,gint * upper_hour)1364 e_date_edit_get_time_popup_range (EDateEdit *dedit,
1365                                   gint *lower_hour,
1366                                   gint *upper_hour)
1367 {
1368 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1369 
1370 	*lower_hour = dedit->priv->lower_hour;
1371 	*upper_hour = dedit->priv->upper_hour;
1372 }
1373 
1374 void
e_date_edit_set_time_popup_range(EDateEdit * dedit,gint lower_hour,gint upper_hour)1375 e_date_edit_set_time_popup_range (EDateEdit *dedit,
1376                                   gint lower_hour,
1377                                   gint upper_hour)
1378 {
1379 	EDateEditPrivate *priv;
1380 
1381 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
1382 
1383 	priv = dedit->priv;
1384 
1385 	if (priv->lower_hour == lower_hour
1386 	    && priv->upper_hour == upper_hour)
1387 		return;
1388 
1389 	priv->lower_hour = lower_hour;
1390 	priv->upper_hour = upper_hour;
1391 
1392 	rebuild_time_popup (dedit);
1393 
1394 	/* Setting the combo list items seems to mess up the time entry, so
1395 	 * we set it again. We have to reset it to its last valid time. */
1396 	priv->time_is_valid = TRUE;
1397 	e_date_edit_update_time_entry (dedit);
1398 }
1399 
1400 /* The arrow button beside the date field has been clicked, so we show the
1401  * popup with the ECalendar in. */
1402 static void
on_date_button_clicked(GtkWidget * widget,EDateEdit * dedit)1403 on_date_button_clicked (GtkWidget *widget,
1404                         EDateEdit *dedit)
1405 {
1406 	GdkEvent *event;
1407 
1408 	/* Obtain the GdkEvent that triggered
1409 	 * the date button's "clicked" signal. */
1410 	event = gtk_get_current_event ();
1411 	e_date_edit_show_date_popup (dedit, event);
1412 }
1413 
1414 static void
e_date_edit_show_date_popup(EDateEdit * dedit,GdkEvent * event)1415 e_date_edit_show_date_popup (EDateEdit *dedit,
1416                              GdkEvent *event)
1417 {
1418 	EDateEditPrivate *priv;
1419 	ECalendar *calendar;
1420 	GdkDevice *event_device;
1421 	GdkDevice *assoc_device;
1422 	GdkDevice *keyboard_device;
1423 	GdkDevice *pointer_device;
1424 	GdkWindow *window;
1425 	GdkGrabStatus grab_status;
1426 	GtkWidget *toplevel;
1427 	struct tm mtm;
1428 	const gchar *date_text;
1429 	GDate selected_day;
1430 	gboolean clear_selection = FALSE;
1431 	guint event_time;
1432 
1433 	priv = dedit->priv;
1434 	calendar = E_CALENDAR (priv->calendar);
1435 
1436 	date_text = gtk_entry_get_text (GTK_ENTRY (priv->date_entry));
1437 	if (field_set_to_none (date_text)
1438 	    || !e_date_edit_parse_date (dedit, date_text, &mtm))
1439 		clear_selection = TRUE;
1440 
1441 	if (clear_selection) {
1442 		e_calendar_item_set_selection (e_calendar_get_item (calendar), NULL, NULL);
1443 	} else {
1444 		g_date_clear (&selected_day, 1);
1445 		g_date_set_dmy (
1446 			&selected_day, mtm.tm_mday, mtm.tm_mon + 1,
1447 			mtm.tm_year + 1900);
1448 		e_calendar_item_set_selection (
1449 			e_calendar_get_item (calendar),
1450 			&selected_day, NULL);
1451 	}
1452 
1453 	/* FIXME: Hack. Change ECalendarItem so it doesn't queue signal
1454 	 * emissions. */
1455 	e_calendar_get_item (calendar)->selection_changed = FALSE;
1456 
1457 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (dedit));
1458 	if (!GTK_IS_WINDOW (toplevel))
1459 		toplevel = NULL;
1460 
1461 	gtk_window_set_transient_for (GTK_WINDOW (priv->cal_popup), toplevel ? GTK_WINDOW (toplevel) : NULL);
1462 
1463 	position_date_popup (dedit);
1464 	gtk_widget_show (priv->cal_popup);
1465 	gtk_widget_grab_focus (priv->cal_popup);
1466 	gtk_grab_add (priv->cal_popup);
1467 
1468 	window = gtk_widget_get_window (priv->cal_popup);
1469 
1470 	g_return_if_fail (priv->grabbed_keyboard == NULL);
1471 	g_return_if_fail (priv->grabbed_pointer == NULL);
1472 
1473 	event_device = gdk_event_get_device (event);
1474 	assoc_device = gdk_device_get_associated_device (event_device);
1475 
1476 	event_time = gdk_event_get_time (event);
1477 
1478 	if (gdk_device_get_source (event_device) == GDK_SOURCE_KEYBOARD) {
1479 		keyboard_device = event_device;
1480 		pointer_device = assoc_device;
1481 	} else {
1482 		keyboard_device = assoc_device;
1483 		pointer_device = event_device;
1484 	}
1485 
1486 	if (keyboard_device != NULL) {
1487 		grab_status = gdk_device_grab (
1488 			keyboard_device,
1489 			window,
1490 			GDK_OWNERSHIP_WINDOW,
1491 			TRUE,
1492 			GDK_KEY_PRESS_MASK |
1493 			GDK_KEY_RELEASE_MASK,
1494 			NULL,
1495 			event_time);
1496 		if (grab_status == GDK_GRAB_SUCCESS) {
1497 			priv->grabbed_keyboard =
1498 				g_object_ref (keyboard_device);
1499 		}
1500 	}
1501 
1502 	if (pointer_device != NULL) {
1503 		grab_status = gdk_device_grab (
1504 			pointer_device,
1505 			window,
1506 			GDK_OWNERSHIP_WINDOW,
1507 			TRUE,
1508 			GDK_BUTTON_PRESS_MASK |
1509 			GDK_BUTTON_RELEASE_MASK |
1510 			GDK_POINTER_MOTION_MASK,
1511 			NULL,
1512 			event_time);
1513 		if (grab_status == GDK_GRAB_SUCCESS) {
1514 			priv->grabbed_pointer =
1515 				g_object_ref (pointer_device);
1516 		} else if (priv->grabbed_keyboard != NULL) {
1517 			gdk_device_ungrab (
1518 				priv->grabbed_keyboard,
1519 				event_time);
1520 			g_object_unref (priv->grabbed_keyboard);
1521 			priv->grabbed_keyboard = NULL;
1522 		}
1523 	}
1524 
1525 	gdk_window_focus (window, event_time);
1526 }
1527 
1528 /* This positions the date popup below and to the left of the arrow button,
1529  * just before it is shown. */
1530 static void
position_date_popup(EDateEdit * dedit)1531 position_date_popup (EDateEdit *dedit)
1532 {
1533 	gint x, y;
1534 	gint win_x, win_y;
1535 	gint bwidth, bheight;
1536 	GtkWidget *toplevel;
1537 	GdkWindow *window;
1538 	GtkRequisition cal_req, button_req;
1539 	gint screen_width, screen_height;
1540 
1541 	gtk_widget_get_preferred_size (dedit->priv->cal_popup, &cal_req, NULL);
1542 
1543 	gtk_widget_get_preferred_size (dedit->priv->date_button, &button_req, NULL);
1544 	bwidth = button_req.width;
1545 	gtk_widget_get_preferred_size (
1546 		gtk_widget_get_parent (dedit->priv->date_button), &button_req, NULL);
1547 	bheight = button_req.height;
1548 
1549 	gtk_widget_translate_coordinates (
1550 		dedit->priv->date_button,
1551 		gtk_widget_get_toplevel (dedit->priv->date_button),
1552 		bwidth - cal_req.width, bheight, &x, &y);
1553 
1554 	toplevel = gtk_widget_get_toplevel (dedit->priv->date_button);
1555 	window = gtk_widget_get_window (toplevel);
1556 	gdk_window_get_origin (window, &win_x, &win_y);
1557 
1558 	x += win_x;
1559 	y += win_y;
1560 
1561 	screen_width = gdk_screen_width ();
1562 	screen_height = gdk_screen_height ();
1563 
1564 	x = CLAMP (x, 0, MAX (0, screen_width - cal_req.width));
1565 	y = CLAMP (y, 0, MAX (0, screen_height - cal_req.height));
1566 
1567 	gtk_window_move (GTK_WINDOW (dedit->priv->cal_popup), x, y);
1568 }
1569 
1570 /* A date has been selected in the date popup, so we set the date field
1571  * and hide the popup. */
1572 static void
on_date_popup_date_selected(ECalendarItem * calitem,EDateEdit * dedit)1573 on_date_popup_date_selected (ECalendarItem *calitem,
1574                              EDateEdit *dedit)
1575 {
1576 	GDate start_date, end_date;
1577 
1578 	hide_date_popup (dedit);
1579 
1580 	if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
1581 		return;
1582 
1583 	e_date_edit_set_date (
1584 		dedit, g_date_get_year (&start_date),
1585 		g_date_get_month (&start_date),
1586 		g_date_get_day (&start_date));
1587 }
1588 
1589 static void
on_date_popup_now_button_clicked(GtkWidget * button,EDateEdit * dedit)1590 on_date_popup_now_button_clicked (GtkWidget *button,
1591                                   EDateEdit *dedit)
1592 {
1593 	hide_date_popup (dedit);
1594 	e_date_edit_set_time (dedit, 0);
1595 }
1596 
1597 static void
on_date_popup_today_button_clicked(GtkWidget * button,EDateEdit * dedit)1598 on_date_popup_today_button_clicked (GtkWidget *button,
1599                                     EDateEdit *dedit)
1600 {
1601 	EDateEditPrivate *priv;
1602 	struct tm tmp_tm;
1603 	time_t t;
1604 
1605 	priv = dedit->priv;
1606 
1607 	hide_date_popup (dedit);
1608 
1609 	if (priv->time_callback) {
1610 		tmp_tm = (*priv->time_callback) (dedit, priv->time_callback_data);
1611 	} else {
1612 		t = time (NULL);
1613 		tmp_tm = *localtime (&t);
1614 	}
1615 
1616 	e_date_edit_set_date (
1617 		dedit, tmp_tm.tm_year + 1900,
1618 		tmp_tm.tm_mon + 1, tmp_tm.tm_mday);
1619 }
1620 
1621 static void
on_date_popup_none_button_clicked(GtkWidget * button,EDateEdit * dedit)1622 on_date_popup_none_button_clicked (GtkWidget *button,
1623                                    EDateEdit *dedit)
1624 {
1625 	hide_date_popup (dedit);
1626 	e_date_edit_set_time (dedit, -1);
1627 }
1628 
1629 /* A key has been pressed while the date popup is showing. If it is the Escape
1630  * key we hide the popup. */
1631 static gint
on_date_popup_key_press(GtkWidget * widget,GdkEventKey * event,EDateEdit * dedit)1632 on_date_popup_key_press (GtkWidget *widget,
1633                          GdkEventKey *event,
1634                          EDateEdit *dedit)
1635 {
1636 	if (event->keyval == GDK_KEY_Escape) {
1637 		g_signal_stop_emission_by_name (widget, "key_press_event");
1638 		hide_date_popup (dedit);
1639 		return TRUE;
1640 	}
1641 
1642 	return FALSE;
1643 }
1644 
1645 /* A mouse button has been pressed while the date popup is showing.
1646  * Any button press events used to select days etc. in the popup will have
1647  * have been handled elsewhere, so here we just hide the popup.
1648  * (This function is yanked from gtkcombo.c) */
1649 static gint
on_date_popup_button_press(GtkWidget * widget,GdkEvent * button_event,gpointer data)1650 on_date_popup_button_press (GtkWidget *widget,
1651                             GdkEvent *button_event,
1652                             gpointer data)
1653 {
1654 	EDateEdit *dedit;
1655 	GtkWidget *child;
1656 
1657 	dedit = data;
1658 
1659 	child = gtk_get_event_widget (button_event);
1660 
1661 	/* We don't ask for button press events on the grab widget, so
1662 	 *  if an event is reported directly to the grab widget, it must
1663 	 *  be on a window outside the application (and thus we remove
1664 	 *  the popup window). Otherwise, we check if the widget is a child
1665 	 *  of the grab widget, and only remove the popup window if it
1666 	 *  is not.
1667 	 */
1668 	if (child != widget) {
1669 		while (child) {
1670 			if (child == widget)
1671 				return FALSE;
1672 			child = gtk_widget_get_parent (child);
1673 		}
1674 	}
1675 
1676 	hide_date_popup (dedit);
1677 
1678 	return TRUE;
1679 }
1680 
1681 /* A delete event has been received for the date popup, so we hide it and
1682  * return TRUE so it doesn't get destroyed. */
1683 static gint
on_date_popup_delete_event(GtkWidget * widget,EDateEdit * dedit)1684 on_date_popup_delete_event (GtkWidget *widget,
1685                             EDateEdit *dedit)
1686 {
1687 	hide_date_popup (dedit);
1688 	return TRUE;
1689 }
1690 
1691 /* Hides the date popup, removing any grabs. */
1692 static void
hide_date_popup(EDateEdit * dedit)1693 hide_date_popup (EDateEdit *dedit)
1694 {
1695 	gtk_widget_hide (dedit->priv->cal_popup);
1696 	gtk_grab_remove (dedit->priv->cal_popup);
1697 
1698 	if (dedit->priv->grabbed_keyboard != NULL) {
1699 		gdk_device_ungrab (
1700 			dedit->priv->grabbed_keyboard,
1701 			GDK_CURRENT_TIME);
1702 		g_object_unref (dedit->priv->grabbed_keyboard);
1703 		dedit->priv->grabbed_keyboard = NULL;
1704 	}
1705 
1706 	if (dedit->priv->grabbed_pointer != NULL) {
1707 		gdk_device_ungrab (
1708 			dedit->priv->grabbed_pointer,
1709 			GDK_CURRENT_TIME);
1710 		g_object_unref (dedit->priv->grabbed_pointer);
1711 		dedit->priv->grabbed_pointer = NULL;
1712 	}
1713 }
1714 
1715 /* some locales may not define am/pm equivalents for '%p',
1716  * thus force 24 hour format for these, otherwise the am/pm
1717  * time clashes */
1718 static gboolean
date_edit_use_24_hour_format(gboolean use_24_hour_format)1719 date_edit_use_24_hour_format (gboolean use_24_hour_format)
1720 {
1721 	struct tm tmp_tm = { 0 };
1722 	gchar buffer[40];
1723 
1724 	if (use_24_hour_format)
1725 		return TRUE;
1726 
1727 	/* Fill the struct tm with some sane values. */
1728 	tmp_tm.tm_year = 2000;
1729 	tmp_tm.tm_mon = 0;
1730 	tmp_tm.tm_mday = 1;
1731 	tmp_tm.tm_sec = 0;
1732 	tmp_tm.tm_isdst = 0;
1733 	tmp_tm.tm_hour = 1;
1734 	tmp_tm.tm_min = 0;
1735 
1736 	if (e_utf8_strftime (buffer, sizeof (buffer), "%p", &tmp_tm) == 0)
1737 		return TRUE;
1738 
1739 	tmp_tm.tm_hour = 13;
1740 	tmp_tm.tm_min = 0;
1741 
1742 	if (e_utf8_strftime (buffer, sizeof (buffer), "%p", &tmp_tm) == 0)
1743 		return TRUE;
1744 
1745 	return use_24_hour_format;
1746 }
1747 
1748 /* Clears the time popup and rebuilds it using the lower_hour, upper_hour
1749  * and use_24_hour_format settings. */
1750 static void
rebuild_time_popup(EDateEdit * dedit)1751 rebuild_time_popup (EDateEdit *dedit)
1752 {
1753 	EDateEditPrivate *priv;
1754 	GtkTreeModel *model;
1755 	GtkListStore *list_store;
1756 	GtkTreeIter iter;
1757 	gchar buffer[40];
1758 	gboolean use_24_hour_format;
1759 	struct tm tmp_tm = { 0 };
1760 	gint hour, min;
1761 	guint ii, index, step, offset, wrap_width;
1762 	GPtrArray *values;
1763 
1764 	priv = dedit->priv;
1765 
1766 	model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->time_combo));
1767 	list_store = GTK_LIST_STORE (model);
1768 	gtk_list_store_clear (list_store);
1769 
1770 	/* Fill the struct tm with some sane values. */
1771 	tmp_tm.tm_year = 2000;
1772 	tmp_tm.tm_mon = 0;
1773 	tmp_tm.tm_mday = 1;
1774 	tmp_tm.tm_sec = 0;
1775 	tmp_tm.tm_isdst = 0;
1776 
1777 	use_24_hour_format = date_edit_use_24_hour_format (priv->use_24_hour_format);
1778 
1779 	values = g_ptr_array_new_full ((priv->upper_hour - priv->lower_hour) * 2, g_free);
1780 
1781 	for (hour = priv->lower_hour; hour <= priv->upper_hour; hour++) {
1782 
1783 		/* We don't want to display midnight at the end,
1784 		 * since that is really in the next day. */
1785 		if (hour == 24)
1786 			break;
1787 
1788 		/* We want to finish on upper_hour, with min == 0. */
1789 		for (min = 0;
1790 		     min == 0 || (min < 60 && hour != priv->upper_hour);
1791 		     min += 30) {
1792 			tmp_tm.tm_hour = hour;
1793 			tmp_tm.tm_min = min;
1794 
1795 			e_time_format_time (
1796 				&tmp_tm, use_24_hour_format, 0,
1797 				buffer, sizeof (buffer));
1798 
1799 			/* For 12-hour am/pm format, we want space padding,
1800 			 * not zero padding. This can be done with strftime's
1801 			 * %l, but it's a potentially unportable extension. */
1802 			if (use_24_hour_format && buffer[0] == '0')
1803 				buffer[0] = ' ';
1804 
1805 			g_ptr_array_add (values, g_strdup (buffer));
1806 		}
1807 	}
1808 
1809 	for (step = 6; step > 1; step--) {
1810 		if ((values->len % step) == 0 && values->len / step >= step - 1) {
1811 			gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (priv->time_combo), step);
1812 			step = values->len / step;
1813 			break;
1814 		}
1815 	}
1816 
1817 	if (step == 1)
1818 		gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (priv->time_combo), step);
1819 
1820 	wrap_width = gtk_combo_box_get_wrap_width (GTK_COMBO_BOX (priv->time_combo));
1821 	index = 0;
1822 	offset = 0;
1823 
1824 	for (ii = 0; ii < values->len; ii++) {
1825 		gtk_list_store_append (list_store, &iter);
1826 		gtk_list_store_set (list_store, &iter, 0, g_ptr_array_index (values, (index + offset) % values->len), -1);
1827 
1828 		index = (index + step) % values->len;
1829 
1830 		if (wrap_width > 1 && ((ii + 1) % wrap_width) == 0)
1831 			offset++;
1832 	}
1833 
1834 	g_ptr_array_free (values, TRUE);
1835 }
1836 
1837 static gboolean
e_date_edit_parse_date(EDateEdit * dedit,const gchar * date_text,struct tm * date_tm)1838 e_date_edit_parse_date (EDateEdit *dedit,
1839                         const gchar *date_text,
1840                         struct tm *date_tm)
1841 {
1842 	gboolean twodigit_year = FALSE;
1843 
1844 	if (e_time_parse_date_ex (date_text, date_tm, &twodigit_year) != E_TIME_PARSE_OK)
1845 		return FALSE;
1846 
1847 	if (twodigit_year && !dedit->priv->twodigit_year_can_future) {
1848 		time_t t = time (NULL);
1849 		struct tm *today_tm = localtime (&t);
1850 
1851 		/* It was only 2 digit year in dedit and it was interpreted as
1852 		 * in the future, but we don't want it as this, so decrease by
1853 		 * 100 years to last century. */
1854 		if (date_tm->tm_year > today_tm->tm_year)
1855 			date_tm->tm_year -= 100;
1856 	}
1857 
1858 	return TRUE;
1859 }
1860 
1861 static gboolean
e_date_edit_parse_time(EDateEdit * dedit,const gchar * time_text,struct tm * time_tm)1862 e_date_edit_parse_time (EDateEdit *dedit,
1863                         const gchar *time_text,
1864                         struct tm *time_tm)
1865 {
1866 	if (field_set_to_none (time_text)) {
1867 		time_tm->tm_hour = 0;
1868 		time_tm->tm_min = 0;
1869 		return TRUE;
1870 	}
1871 
1872 	if (e_time_parse_time (time_text, time_tm) != E_TIME_PARSE_OK)
1873 		return FALSE;
1874 
1875 	return TRUE;
1876 }
1877 
1878 /* Returns TRUE if the string is empty or is "None" in the current locale.
1879  * It ignores whitespace. */
1880 static gboolean
field_set_to_none(const gchar * text)1881 field_set_to_none (const gchar *text)
1882 {
1883 	const gchar *pos;
1884 	const gchar *none_string;
1885 	gint n;
1886 
1887 	pos = text;
1888 	while (n = (gint)((guchar) * pos), isspace (n))
1889 		pos++;
1890 
1891 	/* Translators: "None" for date field of a date edit, shown when
1892 	 * there is no date set. */
1893 	none_string = C_("date", "None");
1894 
1895 	if (*pos == '\0' || !strncmp (pos, none_string, strlen (none_string)))
1896 		return TRUE;
1897 	return FALSE;
1898 }
1899 
1900 static void
on_date_edit_time_selected(GtkComboBox * combo,EDateEdit * dedit)1901 on_date_edit_time_selected (GtkComboBox *combo,
1902                             EDateEdit *dedit)
1903 {
1904 	GtkWidget *child;
1905 
1906 	child = gtk_bin_get_child (GTK_BIN (combo));
1907 
1908 	/* We only want to emit signals when an item is selected explicitly,
1909 	 * not when it is selected by the silly combo update thing. */
1910 	if (gtk_combo_box_get_active (combo) == -1)
1911 		return;
1912 
1913 	if (!gtk_widget_get_mapped (child))
1914 		return;
1915 
1916 	e_date_edit_check_time_changed (dedit);
1917 }
1918 
1919 static gint
on_date_entry_key_press(GtkWidget * widget,GdkEvent * key_event,EDateEdit * dedit)1920 on_date_entry_key_press (GtkWidget *widget,
1921                          GdkEvent *key_event,
1922                          EDateEdit *dedit)
1923 {
1924 	GdkModifierType event_state = 0;
1925 	guint event_keyval = 0;
1926 
1927 	gdk_event_get_keyval (key_event, &event_keyval);
1928 	gdk_event_get_state (key_event, &event_state);
1929 
1930 	if (event_state & GDK_MOD1_MASK
1931 	    && (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down
1932 		|| event_keyval == GDK_KEY_Return)) {
1933 		g_signal_stop_emission_by_name (widget, "key_press_event");
1934 		e_date_edit_show_date_popup (dedit, key_event);
1935 		return TRUE;
1936 	}
1937 
1938 	/* If the user hits the return key emit a "date_changed" signal if
1939 	 * needed. But let the signal carry on. */
1940 	if (event_keyval == GDK_KEY_Return) {
1941 		e_date_edit_check_date_changed (dedit);
1942 		return FALSE;
1943 	}
1944 
1945 	return FALSE;
1946 }
1947 
1948 static gint
on_time_entry_key_press(GtkWidget * widget,GdkEvent * key_event,EDateEdit * dedit)1949 on_time_entry_key_press (GtkWidget *widget,
1950                          GdkEvent *key_event,
1951                          EDateEdit *dedit)
1952 {
1953 	GtkWidget *child;
1954 	GdkModifierType event_state = 0;
1955 	guint event_keyval = 0;
1956 
1957 	gdk_event_get_keyval (key_event, &event_keyval);
1958 	gdk_event_get_state (key_event, &event_state);
1959 
1960 	child = gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo));
1961 
1962 	/* I'd like to use Alt+Up/Down for popping up the list, like Win32,
1963 	 * but the combo steals any Up/Down keys, so we use Alt + Return. */
1964 #if 0
1965 	if (event_state & GDK_MOD1_MASK
1966 	    && (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down)) {
1967 #else
1968 	if (event_state & GDK_MOD1_MASK && event_keyval == GDK_KEY_Return) {
1969 #endif
1970 		g_signal_stop_emission_by_name (widget, "key_press_event");
1971 		g_signal_emit_by_name (child, "activate", 0);
1972 		return TRUE;
1973 	}
1974 
1975 	/* Stop the return key from emitting the activate signal, and check
1976 	 * if we need to emit a "time_changed" signal. */
1977 	if (event_keyval == GDK_KEY_Return) {
1978 		g_signal_stop_emission_by_name (widget, "key_press_event");
1979 		e_date_edit_check_time_changed (dedit);
1980 		return TRUE;
1981 	}
1982 
1983 	return FALSE;
1984 }
1985 
1986 static gint
1987 on_date_entry_key_release (GtkWidget *widget,
1988                            GdkEvent *key_event,
1989                            EDateEdit *dedit)
1990 {
1991 	e_date_edit_check_date_changed (dedit);
1992 	return TRUE;
1993 }
1994 
1995 static gint
1996 on_time_entry_key_release (GtkWidget *widget,
1997                            GdkEvent *key_event,
1998                            EDateEdit *dedit)
1999 {
2000 	guint event_keyval = 0;
2001 
2002 	gdk_event_get_keyval (key_event, &event_keyval);
2003 
2004 	if (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down) {
2005 		g_signal_stop_emission_by_name (widget, "key_release_event");
2006 		e_date_edit_check_time_changed (dedit);
2007 		return TRUE;
2008 	}
2009 
2010 	return FALSE;
2011 }
2012 
2013 static gint
2014 on_date_entry_focus_out (GtkEntry *entry,
2015                          GdkEventFocus *event,
2016                          EDateEdit *dedit)
2017 {
2018 	gboolean did_change, success = TRUE;
2019 	struct tm tmp_tm;
2020 
2021 	tmp_tm.tm_year = 0;
2022 	tmp_tm.tm_mon = 0;
2023 	tmp_tm.tm_mday = 0;
2024 
2025 	did_change = e_date_edit_check_date_changed (dedit);
2026 
2027 	if (!e_date_edit_date_is_valid (dedit)) {
2028 		gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
2029 		gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, _("Invalid Date Value"));
2030 		gtk_entry_grab_focus_without_selecting (entry);
2031 		success = FALSE;
2032 	} else if (e_date_edit_get_date (
2033 		dedit, &tmp_tm.tm_year, &tmp_tm.tm_mon, &tmp_tm.tm_mday)) {
2034 
2035 		e_date_edit_set_date (
2036 			dedit,tmp_tm.tm_year,tmp_tm.tm_mon,tmp_tm.tm_mday);
2037 	} else {
2038 		dedit->priv->date_set_to_none = TRUE;
2039 		e_date_edit_update_date_entry (dedit);
2040 	}
2041 
2042 	if (success) {
2043 		gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, NULL);
2044 		gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, NULL);
2045 
2046 		if (!did_change && dedit->priv->date_been_changed) {
2047 			/* The previous one didn't emit changed signal,
2048 			 * but we want it even here, thus doing itself. */
2049 			g_signal_emit (dedit, signals[CHANGED], 0);
2050 		}
2051 
2052 		dedit->priv->date_been_changed = FALSE;
2053 	}
2054 
2055 	return FALSE;
2056 }
2057 
2058 static gint
2059 on_time_entry_focus_out (GtkEntry *entry,
2060                          GdkEventFocus *event,
2061                          EDateEdit *dedit)
2062 {
2063 	gboolean did_change;
2064 
2065 	did_change = e_date_edit_check_time_changed (dedit);
2066 
2067 	if (!e_date_edit_time_is_valid (dedit)) {
2068 		gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, "dialog-warning");
2069 		gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, _("Invalid Time Value"));
2070 		gtk_entry_grab_focus_without_selecting (entry);
2071 	} else {
2072 		gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, NULL);
2073 		gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, NULL);
2074 
2075 		if (!did_change && dedit->priv->time_been_changed) {
2076 			/* The previous one didn't emit changed signal,
2077 			 * but we want it even here, thus doing itself. */
2078 			g_signal_emit (dedit, signals[CHANGED], 0);
2079 		}
2080 
2081 		dedit->priv->time_been_changed = FALSE;
2082 	}
2083 
2084 	return FALSE;
2085 }
2086 
2087 static void
2088 add_relation (EDateEdit *dedit,
2089               GtkWidget *widget)
2090 {
2091 	AtkObject *a11yEdit, *a11yWidget;
2092 	AtkRelationSet *set;
2093 	AtkRelation *relation;
2094 	GPtrArray *target;
2095 	gpointer target_object;
2096 
2097 	/* add a labelled_by relation for widget for accessibility */
2098 
2099 	a11yEdit = gtk_widget_get_accessible (GTK_WIDGET (dedit));
2100 	a11yWidget = gtk_widget_get_accessible (widget);
2101 
2102 	set = atk_object_ref_relation_set (a11yWidget);
2103 	if (set != NULL) {
2104 		relation = atk_relation_set_get_relation_by_type (
2105 			set, ATK_RELATION_LABELLED_BY);
2106 		/* check whether has a labelled_by relation already */
2107 		if (relation != NULL) {
2108 			g_object_unref (set);
2109 			return;
2110 		}
2111 	}
2112 
2113 	g_clear_object (&set);
2114 
2115 	set = atk_object_ref_relation_set (a11yEdit);
2116 	if (!set)
2117 		return;
2118 
2119 	relation = atk_relation_set_get_relation_by_type (
2120 		set, ATK_RELATION_LABELLED_BY);
2121 	if (relation != NULL) {
2122 		target = atk_relation_get_target (relation);
2123 		target_object = g_ptr_array_index (target, 0);
2124 		if (ATK_IS_OBJECT (target_object)) {
2125 			atk_object_add_relationship (
2126 				a11yWidget,
2127 					ATK_RELATION_LABELLED_BY,
2128 					ATK_OBJECT (target_object));
2129 		}
2130 	}
2131 
2132 	g_clear_object (&set);
2133 }
2134 
2135 /* This sets the text in the date entry according to the current settings. */
2136 static void
2137 e_date_edit_update_date_entry (EDateEdit *dedit)
2138 {
2139 	EDateEditPrivate *priv;
2140 	gchar buffer[100];
2141 	struct tm tmp_tm = { 0 };
2142 
2143 	priv = dedit->priv;
2144 
2145 	if (priv->date_set_to_none || !priv->date_is_valid) {
2146 		gtk_entry_set_text (GTK_ENTRY (priv->date_entry), C_("date", "None"));
2147 	} else {
2148 		/* This is a strftime() format for a short date.
2149 		 * %x the preferred date representation for the current locale
2150 		 * without the time, but is forced to use 4 digit year. */
2151 		gchar *format = e_time_get_d_fmt_with_4digit_year ();
2152 		time_t tt;
2153 
2154 		tmp_tm.tm_year = priv->year;
2155 		tmp_tm.tm_mon = priv->month;
2156 		tmp_tm.tm_mday = priv->day;
2157 		tmp_tm.tm_isdst = -1;
2158 
2159 		/* initialize all 'struct tm' members properly */
2160 		tt = mktime (&tmp_tm);
2161 		if (tt && localtime (&tt))
2162 			tmp_tm = *localtime (&tt);
2163 
2164 		e_utf8_strftime (buffer, sizeof (buffer), format, &tmp_tm);
2165 		g_free (format);
2166 		gtk_entry_set_text (GTK_ENTRY (priv->date_entry), buffer);
2167 	}
2168 
2169 	add_relation (dedit, priv->date_entry);
2170 	add_relation (dedit, priv->date_button);
2171 }
2172 
2173 /* This sets the text in the time entry according to the current settings. */
2174 static void
2175 e_date_edit_update_time_entry (EDateEdit *dedit)
2176 {
2177 	EDateEditPrivate *priv;
2178 	GtkComboBox *combo_box;
2179 	GtkWidget *child;
2180 	gchar buffer[40];
2181 	struct tm tmp_tm = { 0 };
2182 
2183 	priv = dedit->priv;
2184 
2185 	combo_box = GTK_COMBO_BOX (priv->time_combo);
2186 	child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
2187 
2188 	if (priv->time_set_to_none || !priv->time_is_valid) {
2189 		gtk_combo_box_set_active (combo_box, -1);
2190 		gtk_entry_set_text (GTK_ENTRY (child), "");
2191 	} else {
2192 		GtkTreeModel *model;
2193 		GtkTreeIter iter;
2194 		gboolean valid, use_24_hour_format;
2195 		gchar *b;
2196 
2197 		/* Set these to reasonable values just in case. */
2198 		tmp_tm.tm_year = 2000;
2199 		tmp_tm.tm_mon = 0;
2200 		tmp_tm.tm_mday = 1;
2201 
2202 		tmp_tm.tm_hour = priv->hour;
2203 		tmp_tm.tm_min = priv->minute;
2204 
2205 		tmp_tm.tm_sec = 0;
2206 		tmp_tm.tm_isdst = -1;
2207 
2208 		use_24_hour_format = date_edit_use_24_hour_format (priv->use_24_hour_format);
2209 
2210 		e_time_format_time (
2211 			&tmp_tm, use_24_hour_format, 0, buffer, sizeof (buffer));
2212 
2213 		/* For 12-hour am/pm format, we want space padding, not
2214 		 * zero padding.  This can be done with strftime's %l,
2215 		 * but it's a potentially unportable extension. */
2216 		if (!use_24_hour_format && buffer[0] == '0')
2217 			buffer[0] = ' ';
2218 
2219 		gtk_entry_set_text (GTK_ENTRY (child), buffer);
2220 
2221 		/* truncate left spaces */
2222 		b = buffer;
2223 		while (*b == ' ')
2224 			b++;
2225 
2226 		model = gtk_combo_box_get_model (combo_box);
2227 		valid = gtk_tree_model_get_iter_first (model, &iter);
2228 
2229 		while (valid) {
2230 			gchar *text = NULL;
2231 
2232 			gtk_tree_model_get (model, &iter, 0, &text, -1);
2233 			if (text) {
2234 				gchar *t = text;
2235 
2236 				/* truncate left spaces */
2237 				while (*t == ' ')
2238 					t++;
2239 
2240 				if (strcmp (b, t) == 0) {
2241 					gtk_combo_box_set_active_iter (
2242 						combo_box, &iter);
2243 					g_free (text);
2244 					break;
2245 				}
2246 			}
2247 
2248 			g_free (text);
2249 
2250 			valid = gtk_tree_model_iter_next (model, &iter);
2251 		}
2252 	}
2253 
2254 	add_relation (dedit, priv->time_combo);
2255 }
2256 
2257 static void
2258 e_date_edit_update_time_combo_state (EDateEdit *dedit)
2259 {
2260 	EDateEditPrivate *priv;
2261 	gboolean show = TRUE, show_now_button = TRUE;
2262 	gboolean clear_entry = FALSE, sensitive = TRUE;
2263 	const gchar *text;
2264 
2265 	priv = dedit->priv;
2266 
2267 	/* If the date entry is currently shown, and it is set to None,
2268 	 * clear the time entry and disable the time combo. */
2269 	if (priv->show_date && priv->date_set_to_none) {
2270 		clear_entry = TRUE;
2271 		sensitive = FALSE;
2272 	}
2273 
2274 	if (!priv->show_time) {
2275 		if (priv->make_time_insensitive) {
2276 			clear_entry = TRUE;
2277 			sensitive = FALSE;
2278 		} else {
2279 			show = FALSE;
2280 		}
2281 
2282 		show_now_button = FALSE;
2283 	}
2284 
2285 	if (clear_entry) {
2286 		GtkWidget *child;
2287 
2288 		/* Only clear it if it isn't empty already. */
2289 		child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
2290 		text = gtk_entry_get_text (GTK_ENTRY (child));
2291 		if (text[0])
2292 			gtk_entry_set_text (GTK_ENTRY (child), "");
2293 	}
2294 
2295 	gtk_widget_set_sensitive (priv->time_combo, sensitive);
2296 
2297 	if (show)
2298 		gtk_widget_show (priv->time_combo);
2299 	else
2300 		gtk_widget_hide (priv->time_combo);
2301 
2302 	if (show_now_button)
2303 		gtk_widget_show (priv->now_button);
2304 	else
2305 		gtk_widget_hide (priv->now_button);
2306 
2307 	if (priv->show_date
2308 	    && (priv->show_time || priv->make_time_insensitive))
2309 		gtk_widget_show (priv->space);
2310 	else
2311 		gtk_widget_hide (priv->space);
2312 }
2313 
2314 /* Parses the date, and if it is different from the current settings it
2315  * updates the settings and emits a "date_changed" signal. */
2316 static gboolean
2317 e_date_edit_check_date_changed (EDateEdit *dedit)
2318 {
2319 	EDateEditPrivate *priv;
2320 	const gchar *date_text;
2321 	struct tm tmp_tm;
2322 	gboolean none = FALSE, valid = TRUE, date_changed = FALSE;
2323 
2324 	priv = dedit->priv;
2325 
2326 	tmp_tm.tm_year = 0;
2327 	tmp_tm.tm_mon = 0;
2328 	tmp_tm.tm_mday = 0;
2329 
2330 	date_text = gtk_entry_get_text (GTK_ENTRY (priv->date_entry));
2331 	if (field_set_to_none (date_text)) {
2332 		none = TRUE;
2333 	} else if (!e_date_edit_parse_date (dedit, date_text, &tmp_tm)) {
2334 		valid = FALSE;
2335 		tmp_tm.tm_year = 0;
2336 		tmp_tm.tm_mon = 0;
2337 		tmp_tm.tm_mday = 0;
2338 	}
2339 
2340 	date_changed = e_date_edit_set_date_internal (
2341 		dedit, valid, none,
2342 		tmp_tm.tm_year,
2343 		tmp_tm.tm_mon,
2344 		tmp_tm.tm_mday);
2345 
2346 	if (date_changed) {
2347 		priv->date_been_changed = TRUE;
2348 		g_signal_emit (dedit, signals[CHANGED], 0);
2349 	}
2350 
2351 	return date_changed;
2352 }
2353 
2354 /* Parses the time, and if it is different from the current settings it
2355  * updates the settings and emits a "time_changed" signal. */
2356 static gboolean
2357 e_date_edit_check_time_changed (EDateEdit *dedit)
2358 {
2359 	EDateEditPrivate *priv;
2360 	GtkWidget *child;
2361 	const gchar *time_text;
2362 	struct tm tmp_tm;
2363 	gboolean none = FALSE, valid = TRUE, time_changed;
2364 
2365 	priv = dedit->priv;
2366 
2367 	tmp_tm.tm_hour = 0;
2368 	tmp_tm.tm_min = 0;
2369 
2370 	child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
2371 	time_text = gtk_entry_get_text (GTK_ENTRY (child));
2372 	if (field_set_to_none (time_text))
2373 		none = TRUE;
2374 	else if (!e_date_edit_parse_time (dedit, time_text, &tmp_tm))
2375 		valid = FALSE;
2376 
2377 	time_changed = e_date_edit_set_time_internal (
2378 		dedit, valid, none,
2379 		tmp_tm.tm_hour,
2380 		tmp_tm.tm_min);
2381 
2382 	if (time_changed) {
2383 		dedit->priv->time_been_changed = TRUE;
2384 		/* Do not call e_date_edit_update_time_entry (dedit); let the user correct the value */
2385 		g_signal_emit (dedit, signals[CHANGED], 0);
2386 	}
2387 
2388 	return time_changed;
2389 }
2390 
2391 /**
2392  * e_date_edit_date_is_valid:
2393  * @dedit: an #EDateEdit
2394  * @Returns: TRUE if the last date entered was valid.
2395  *
2396  * Returns TRUE if the last date entered was valid.
2397  *
2398  * Note that if this returns FALSE, you can still use e_date_edit_get_time()
2399  * or e_date_edit_get_date() to get the last time or date entered which was
2400  * valid.
2401  */
2402 gboolean
2403 e_date_edit_date_is_valid (EDateEdit *dedit)
2404 {
2405 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
2406 
2407 	if (!dedit->priv->date_is_valid)
2408 		return FALSE;
2409 
2410 	/* If the date is empty/None and that isn't permitted, return FALSE. */
2411 	if (dedit->priv->date_set_to_none
2412 	    && !e_date_edit_get_allow_no_date_set (dedit))
2413 		return FALSE;
2414 
2415 	return TRUE;
2416 }
2417 
2418 /**
2419  * e_date_edit_time_is_valid:
2420  * @dedit: an #EDateEdit
2421  * @Returns: TRUE if the last time entered was valid.
2422  *
2423  * Returns TRUE if the last time entered was valid.
2424  *
2425  * Note that if this returns FALSE, you can still use e_date_edit_get_time()
2426  * or e_date_edit_get_time_of_day() to get the last time or time of the day
2427  * entered which was valid.
2428  */
2429 gboolean
2430 e_date_edit_time_is_valid (EDateEdit *dedit)
2431 {
2432 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
2433 
2434 	if (!dedit->priv->time_is_valid)
2435 		return FALSE;
2436 
2437 	/* If the time is empty and that isn't permitted, return FALSE.
2438 	 * Note that we don't mind an empty time if the date field is shown
2439 	 * - in that case we just assume 0:00. */
2440 	if (dedit->priv->time_set_to_none && !dedit->priv->show_date
2441 	    && !e_date_edit_get_allow_no_date_set (dedit))
2442 		return FALSE;
2443 
2444 	return TRUE;
2445 }
2446 
2447 static gboolean
2448 e_date_edit_set_date_internal (EDateEdit *dedit,
2449                                gboolean valid,
2450                                gboolean none,
2451                                gint year,
2452                                gint month,
2453                                gint day)
2454 {
2455 	EDateEditPrivate *priv;
2456 	gboolean date_changed = FALSE;
2457 
2458 	priv = dedit->priv;
2459 
2460 	if (!valid) {
2461 		/* Date is invalid. */
2462 		if (priv->date_is_valid) {
2463 			priv->date_is_valid = FALSE;
2464 			date_changed = TRUE;
2465 		}
2466 	} else if (none) {
2467 		/* Date has been set to 'None'. */
2468 		if (!priv->date_is_valid
2469 		    || !priv->date_set_to_none) {
2470 			priv->date_is_valid = TRUE;
2471 			priv->date_set_to_none = TRUE;
2472 			date_changed = TRUE;
2473 		}
2474 	} else {
2475 		/* Date has been set to a specific date. */
2476 		if (!priv->date_is_valid
2477 		    || priv->date_set_to_none
2478 		    || priv->year != year
2479 		    || priv->month != month
2480 		    || priv->day != day) {
2481 			priv->date_is_valid = TRUE;
2482 			priv->date_set_to_none = FALSE;
2483 			priv->year = year;
2484 			priv->month = month;
2485 			priv->day = day;
2486 			date_changed = TRUE;
2487 		}
2488 	}
2489 
2490 	if (date_changed) {
2491 		GtkEntry *entry;
2492 
2493 		entry = GTK_ENTRY (dedit->priv->date_entry);
2494 
2495 		if (priv->date_is_valid) {
2496 			gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, NULL);
2497 			gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, NULL);
2498 		} else {
2499 			gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
2500 			gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, _("Invalid Date Value"));
2501 		}
2502 	}
2503 
2504 	return date_changed;
2505 }
2506 
2507 static gboolean
2508 e_date_edit_set_time_internal (EDateEdit *dedit,
2509                                gboolean valid,
2510                                gboolean none,
2511                                gint hour,
2512                                gint minute)
2513 {
2514 	EDateEditPrivate *priv;
2515 	gboolean time_changed = FALSE;
2516 
2517 	priv = dedit->priv;
2518 
2519 	if (!valid) {
2520 		/* Time is invalid. */
2521 		if (priv->time_is_valid) {
2522 			priv->time_is_valid = FALSE;
2523 			time_changed = TRUE;
2524 		}
2525 	} else if (none) {
2526 		/* Time has been set to empty/'None'. */
2527 		if (!priv->time_is_valid
2528 		    || !priv->time_set_to_none) {
2529 			priv->time_is_valid = TRUE;
2530 			priv->time_set_to_none = TRUE;
2531 			time_changed = TRUE;
2532 		}
2533 	} else {
2534 		/* Time has been set to a specific time. */
2535 		if (!priv->time_is_valid
2536 		    || priv->time_set_to_none
2537 		    || priv->hour != hour
2538 		    || priv->minute != minute) {
2539 			priv->time_is_valid = TRUE;
2540 			priv->time_set_to_none = FALSE;
2541 			priv->hour = hour;
2542 			priv->minute = minute;
2543 			time_changed = TRUE;
2544 		}
2545 	}
2546 
2547 	if (time_changed) {
2548 		GtkEntry *entry;
2549 
2550 		entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo)));
2551 
2552 		if (priv->time_is_valid) {
2553 			gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, NULL);
2554 			gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, NULL);
2555 		} else {
2556 			gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, "dialog-warning");
2557 			gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, _("Invalid Time Value"));
2558 		}
2559 	}
2560 
2561 	return time_changed;
2562 }
2563 
2564 gboolean
2565 e_date_edit_get_twodigit_year_can_future (EDateEdit *dedit)
2566 {
2567 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
2568 
2569 	return dedit->priv->twodigit_year_can_future;
2570 }
2571 
2572 void
2573 e_date_edit_set_twodigit_year_can_future (EDateEdit *dedit,
2574                                           gboolean value)
2575 {
2576 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
2577 
2578 	dedit->priv->twodigit_year_can_future = value;
2579 }
2580 
2581 /* Sets a callback to use to get the current time. This is useful if the
2582  * application needs to use its own timezone data rather than rely on the
2583  * Unix timezone. */
2584 void
2585 e_date_edit_set_get_time_callback (EDateEdit *dedit,
2586                                    EDateEditGetTimeCallback cb,
2587                                    gpointer data,
2588                                    GDestroyNotify destroy)
2589 {
2590 	EDateEditPrivate *priv;
2591 
2592 	g_return_if_fail (E_IS_DATE_EDIT (dedit));
2593 
2594 	priv = dedit->priv;
2595 
2596 	if (priv->time_callback_data && priv->time_callback_destroy)
2597 		(*priv->time_callback_destroy) (priv->time_callback_data);
2598 
2599 	priv->time_callback = cb;
2600 	priv->time_callback_data = data;
2601 	priv->time_callback_destroy = destroy;
2602 
2603 }
2604 
2605 GtkWidget *
2606 e_date_edit_get_entry (EDateEdit *dedit)
2607 {
2608 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), NULL);
2609 
2610 	return GTK_WIDGET (dedit->priv->date_entry);
2611 }
2612 
2613 gboolean
2614 e_date_edit_has_focus (EDateEdit *dedit)
2615 {
2616 	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
2617 
2618 	if (gtk_widget_has_focus (GTK_WIDGET (dedit)) ||
2619 	    (dedit->priv->date_entry && gtk_widget_has_focus (dedit->priv->date_entry)))
2620 		return TRUE;
2621 
2622 	return e_date_edit_get_show_time (dedit) &&
2623 	       dedit->priv->time_combo && (
2624 		(gtk_widget_has_focus (dedit->priv->time_combo) ||
2625 		 gtk_widget_has_focus (gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo)))));
2626 }
2627