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