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