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