No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | e-meeting-time-sel.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None | |
clang-analyzer | no-output-found | e-meeting-time-sel.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
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@gtk.org>
18 * Rodrigo Moya <rodrigo@novell.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include "e-meeting-time-sel.h"
29
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <time.h>
34 #include <glib/gi18n.h>
35 #include <gdk/gdkkeysyms.h>
36 #include <libebackend/libebackend.h>
37 #include <libgnomecanvas/libgnomecanvas.h>
38
39 #include "misc/e-canvas.h"
40 #include "misc/e-canvas-utils.h"
41 #include "misc/e-dateedit.h"
42
43 #include "e-util/e-util.h"
44 #include "e-util/e-datetime-format.h"
45
46 #include "e-meeting-utils.h"
47 #include "e-meeting-list-view.h"
48 #include "e-meeting-time-sel-item.h"
49
50 #define E_MEETING_TIME_SELECTOR_GET_PRIVATE(obj) \
51 (G_TYPE_INSTANCE_GET_PRIVATE \
52 ((obj), E_TYPE_MEETING_TIME_SELECTOR, EMeetingTimeSelectorPrivate))
53
54 struct _EMeetingTimeSelectorPrivate {
55 gint week_start_day;
56 guint show_week_numbers : 1;
57 guint use_24_hour_format : 1;
58 };
59
60 /* An array of hour strings for 24 hour time, "0:00" .. "23:00". */
61 const gchar *EMeetingTimeSelectorHours[24] = {
62 "0:00", "1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00",
63 "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00",
64 "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"
65 };
66
67 /* An array of hour strings for 12 hour time, "12:00am" .. "11:00pm". */
68 const gchar *EMeetingTimeSelectorHours12[24] = {
69 "12:00am", "1:00am", "2:00am", "3:00am", "4:00am", "5:00am", "6:00am",
70 "7:00am", "8:00am", "9:00am", "10:00am", "11:00am", "12:00pm",
71 "1:00pm", "2:00pm", "3:00pm", "4:00pm", "5:00pm", "6:00pm", "7:00pm",
72 "8:00pm", "9:00pm", "10:00pm", "11:00pm"
73 };
74
75 /* The number of days shown in the entire canvas. */
76 #define E_MEETING_TIME_SELECTOR_DAYS_SHOWN 35
77 #define E_MEETING_TIME_SELECTOR_DAYS_START_BEFORE 7
78 #define E_MEETING_TIME_SELECTOR_FB_DAYS_BEFORE 7
79 #define E_MEETING_TIME_SELECTOR_FB_DAYS_AFTER 28
80
81 /* This is the number of pixels between the mouse has to move before the
82 * scroll speed is incremented. */
83 #define E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH 10
84
85 /* This is the maximum scrolling speed. */
86 #define E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED 4
87
88 enum {
89 PROP_0,
90 PROP_SHOW_WEEK_NUMBERS,
91 PROP_USE_24_HOUR_FORMAT,
92 PROP_WEEK_START_DAY
93 };
94
95 enum {
96 CHANGED,
97 LAST_SIGNAL
98 };
99
100 static gint signals[LAST_SIGNAL] = { 0 };
101
102 static void e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector * mts,
103 const gchar *name, GdkColor *c);
104 static void e_meeting_time_selector_add_key_color (EMeetingTimeSelector * mts,
105 GtkWidget *hbox,
106 gchar *label_text,
107 GdkColor *color);
108 static gint e_meeting_time_selector_draw_key_color (GtkWidget *darea,
109 cairo_t *cr,
110 GdkColor *color);
111 static void e_meeting_time_selector_options_menu_detacher (GtkWidget *widget,
112 GtkMenu *menu);
113 static void e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget,
114 GtkMenu *menu);
115 static void e_meeting_time_selector_realize (GtkWidget *widget);
116 static void e_meeting_time_selector_unrealize (GtkWidget *widget);
117 static void e_meeting_time_selector_style_set (GtkWidget *widget,
118 GtkStyle *previous_style);
119 static gint e_meeting_time_selector_draw (GtkWidget *widget, cairo_t *cr);
120 static void e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts, cairo_t *cr);
121 static void e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment,
122 EMeetingTimeSelector *mts);
123 static void e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment,
124 EMeetingTimeSelector *mts);
125 static void e_meeting_time_selector_on_canvas_realized (GtkWidget *widget,
126 EMeetingTimeSelector *mts);
127
128 static void e_meeting_time_selector_on_options_button_clicked (GtkWidget *button,
129 EMeetingTimeSelector *mts);
130 static void e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu,
131 gint *x,
132 gint *y,
133 gboolean *push_in,
134 gpointer user_data);
135 static void e_meeting_time_selector_on_zoomed_out_toggled (GtkCheckMenuItem *button,
136 EMeetingTimeSelector *mts);
137 static void e_meeting_time_selector_on_working_hours_toggled (GtkCheckMenuItem *menuitem,
138 EMeetingTimeSelector *mts);
139 static void e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button,
140 EMeetingTimeSelector *mts);
141 static void e_meeting_time_selector_on_update_free_busy (GtkWidget *button,
142 EMeetingTimeSelector *mts);
143 static void e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button,
144 EMeetingTimeSelector *mts);
145 static void e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu,
146 gint *x,
147 gint *y,
148 gboolean *push_in,
149 gpointer user_data);
150 static void e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button,
151 EMeetingTimeSelector *mts);
152 static void e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button,
153 EMeetingTimeSelector *mts);
154 static void e_meeting_time_selector_on_next_button_clicked (GtkWidget *button,
155 EMeetingTimeSelector *mts);
156 static void e_meeting_time_selector_autopick (EMeetingTimeSelector *mts,
157 gboolean forward);
158 static void e_meeting_time_selector_calculate_time_difference (EMeetingTime *start,
159 EMeetingTime *end,
160 gint *days,
161 gint *hours,
162 gint *minutes);
163 static void e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts,
164 EMeetingTime *start_time,
165 EMeetingTime *end_time,
166 gint days, gint hours, gint mins);
167 static void e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts,
168 EMeetingTime *start_time,
169 EMeetingTime *end_time,
170 gint days, gint hours, gint mins);
171 static void e_meeting_time_selector_adjust_time (EMeetingTime *mtstime,
172 gint days, gint hours, gint minutes);
173 static EMeetingFreeBusyPeriod * e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
174 EMeetingAttendee *attendee,
175 EMeetingTime *start_time,
176 EMeetingTime *end_time);
177
178 static void e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts);
179 static void e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts);
180 static void e_meeting_time_selector_save_position (EMeetingTimeSelector *mts,
181 EMeetingTime *mtstime);
182 static void e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts,
183 EMeetingTime *mtstime);
184 static void e_meeting_time_selector_on_start_time_changed (GtkWidget *widget,
185 EMeetingTimeSelector *mts);
186 static void e_meeting_time_selector_on_end_time_changed (GtkWidget *widget,
187 EMeetingTimeSelector *mts);
188 static void e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts);
189 static void e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget,
190 GtkAllocation *allocation,
191 EMeetingTimeSelector *mts);
192 static void e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts);
193 static gboolean e_meeting_time_selector_timeout_handler (gpointer data);
194 static void e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts);
195 static void e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts);
196 static void e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts);
197 static void e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts);
198 static gboolean e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget, GdkEventScroll *event, EMeetingTimeSelector *mts);
199 static gboolean e_meeting_time_selector_on_canvas_query_tooltip (GtkWidget *widget,
200 gint x,
201 gint y,
202 gboolean keyboard_mode,
203 GtkTooltip *tooltip,
204 gpointer user_data);
205
206 static void row_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);
207 static void row_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);
208 static void row_deleted_cb (GtkTreeModel *model, GtkTreePath *path, gpointer data);
209
210 static void free_busy_template_changed_cb (EMeetingTimeSelector *mts);
211
212 G_DEFINE_TYPE_WITH_CODE (
213 EMeetingTimeSelector, e_meeting_time_selector, GTK_TYPE_TABLE,
214 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
215
216 static void
217 meeting_time_selector_set_property (GObject *object,
218 guint property_id,
219 const GValue *value,
220 GParamSpec *pspec)
221 {
222 switch (property_id) {
223 case PROP_SHOW_WEEK_NUMBERS:
224 e_meeting_time_selector_set_show_week_numbers (
225 E_MEETING_TIME_SELECTOR (object),
226 g_value_get_boolean (value));
227 return;
228
229 case PROP_USE_24_HOUR_FORMAT:
230 e_meeting_time_selector_set_use_24_hour_format (
231 E_MEETING_TIME_SELECTOR (object),
232 g_value_get_boolean (value));
233 return;
234
235 case PROP_WEEK_START_DAY:
236 e_meeting_time_selector_set_week_start_day (
237 E_MEETING_TIME_SELECTOR (object),
238 g_value_get_int (value));
239 return;
240 }
241
242 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
243 }
244
245 static void
246 meeting_time_selector_get_property (GObject *object,
247 guint property_id,
248 GValue *value,
249 GParamSpec *pspec)
250 {
251 switch (property_id) {
252 case PROP_SHOW_WEEK_NUMBERS:
253 g_value_set_boolean (
254 value,
255 e_meeting_time_selector_get_show_week_numbers (
256 E_MEETING_TIME_SELECTOR (object)));
257 return;
258
259 case PROP_USE_24_HOUR_FORMAT:
260 g_value_set_boolean (
261 value,
262 e_meeting_time_selector_get_use_24_hour_format (
263 E_MEETING_TIME_SELECTOR (object)));
264 return;
265
266 case PROP_WEEK_START_DAY:
267 g_value_set_int (
268 value,
269 e_meeting_time_selector_get_week_start_day (
270 E_MEETING_TIME_SELECTOR (object)));
271 return;
272 }
273
274 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
275 }
276
277 static void
278 meeting_time_selector_dispose (GObject *object)
279 {
280 EMeetingTimeSelector *mts;
281
282 mts = E_MEETING_TIME_SELECTOR (object);
283
284 e_meeting_time_selector_remove_timeout (mts);
285
286 if (mts->model) {
287 g_signal_handlers_disconnect_matched (
288 mts->model, G_SIGNAL_MATCH_DATA,
289 0, 0, NULL, NULL, mts);
290 g_object_unref (mts->model);
291 mts->model = NULL;
292 }
293
294 mts->display_top = NULL;
295 mts->display_main = NULL;
296
297 if (mts->fb_refresh_not != 0) {
298 g_source_remove (mts->fb_refresh_not);
299 mts->fb_refresh_not = 0;
300 }
301
302 if (mts->style_change_idle_id != 0) {
303 g_source_remove (mts->style_change_idle_id);
304 mts->style_change_idle_id = 0;
305 }
306
307 /* Chain up to parent's dispose() method. */
308 G_OBJECT_CLASS (e_meeting_time_selector_parent_class)->dispose (object);
309 }
310
311 static void
312 e_meeting_time_selector_class_init (EMeetingTimeSelectorClass *class)
313 {
314 GObjectClass *object_class;
315 GtkWidgetClass *widget_class;
316
317 g_type_class_add_private (class, sizeof (EMeetingTimeSelectorPrivate));
318
319 object_class = G_OBJECT_CLASS (class);
320 object_class->set_property = meeting_time_selector_set_property;
321 object_class->get_property = meeting_time_selector_get_property;
322 object_class->dispose = meeting_time_selector_dispose;
323
324 widget_class = GTK_WIDGET_CLASS (class);
325 widget_class->realize = e_meeting_time_selector_realize;
326 widget_class->unrealize = e_meeting_time_selector_unrealize;
327 widget_class->style_set = e_meeting_time_selector_style_set;
328 widget_class->draw = e_meeting_time_selector_draw;
329
330 g_object_class_install_property (
331 object_class,
332 PROP_SHOW_WEEK_NUMBERS,
333 g_param_spec_boolean (
334 "show-week-numbers",
335 "Show Week Numbers",
336 NULL,
337 TRUE,
338 G_PARAM_READWRITE));
339
340 g_object_class_install_property (
341 object_class,
342 PROP_USE_24_HOUR_FORMAT,
343 g_param_spec_boolean (
344 "use-24-hour-format",
345 "Use 24-Hour Format",
346 NULL,
347 TRUE,
348 G_PARAM_READWRITE));
349
350 g_object_class_install_property (
351 object_class,
352 PROP_WEEK_START_DAY,
353 g_param_spec_int (
354 "week-start-day",
355 "Week Start Day",
356 NULL,
357 0, /* Monday */
358 6, /* Sunday */
359 0,
360 G_PARAM_READWRITE));
361
362 signals[CHANGED] = g_signal_new (
363 "changed",
364 G_TYPE_FROM_CLASS (object_class),
365 G_SIGNAL_RUN_FIRST,
366 G_STRUCT_OFFSET (EMeetingTimeSelectorClass, changed),
367 NULL, NULL,
368 g_cclosure_marshal_VOID__VOID,
369 G_TYPE_NONE, 0);
370 }
371
372 static void
373 e_meeting_time_selector_init (EMeetingTimeSelector *mts)
374 {
375 mts->priv = E_MEETING_TIME_SELECTOR_GET_PRIVATE (mts);
376
377 /* The shadow is drawn in the border so it must be >= 2 pixels. */
378 gtk_container_set_border_width (GTK_CONTAINER (mts), 2);
379
380 mts->accel_group = gtk_accel_group_new ();
381
382 mts->working_hours_only = TRUE;
383 mts->day_start_hour = 9;
384 mts->day_start_minute = 0;
385 mts->day_end_hour = 18;
386 mts->day_end_minute = 0;
387 mts->zoomed_out = TRUE;
388 mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE;
389
390 mts->list_view = NULL;
391
392 mts->fb_refresh_not = 0;
393 mts->style_change_idle_id = 0;
394
395 e_extensible_load_extensions (E_EXTENSIBLE (mts));
396 }
397
398 void
399 e_meeting_time_selector_construct (EMeetingTimeSelector *mts,
400 EMeetingStore *ems)
401 {
402 GtkWidget *hbox, *vbox, *separator, *label, *table, *sw;
403 GtkWidget *alignment, *child_hbox, *arrow, *menuitem;
404 GtkWidget *child;
405 GtkAdjustment *adjustment;
406 GtkScrollable *scrollable;
407 GSList *group;
408 guint accel_key;
409 time_t meeting_start_time;
410 struct tm *meeting_start_tm;
411 AtkObject *a11y_label, *a11y_date_edit;
412
413 /* The default meeting time is the nearest half-hour interval in the
414 * future, in working hours. */
415 meeting_start_time = time (NULL);
416 g_date_clear (&mts->meeting_start_time.date, 1);
417 g_date_set_time_t (&mts->meeting_start_time.date, meeting_start_time);
418 meeting_start_tm = localtime (&meeting_start_time);
419 mts->meeting_start_time.hour = meeting_start_tm->tm_hour;
420 mts->meeting_start_time.minute = meeting_start_tm->tm_min;
421
422 e_meeting_time_selector_find_nearest_interval (
423 mts, &mts->meeting_start_time,
424 &mts->meeting_end_time,
425 0, 0, 30);
426
427 e_meeting_time_selector_update_dates_shown (mts);
428
429 mts->meeting_positions_valid = FALSE;
430
431 mts->row_height = 17;
432 mts->col_width = 55;
433 mts->day_width = 55 * 24 + 1;
434
435 mts->auto_scroll_timeout_id = 0;
436
437 vbox = gtk_vbox_new (FALSE, 0);
438 gtk_table_attach (
439 GTK_TABLE (mts),
440 vbox, 0, 1, 0, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
441 gtk_widget_show (vbox);
442
443 mts->attendees_vbox_spacer = gtk_vbox_new (FALSE, 0);
444 gtk_box_pack_start (GTK_BOX (vbox), mts->attendees_vbox_spacer, FALSE, FALSE, 0);
445 gtk_widget_show (mts->attendees_vbox_spacer);
446
447 mts->attendees_vbox = gtk_vbox_new (FALSE, 0);
448 gtk_box_pack_start (GTK_BOX (vbox), mts->attendees_vbox, TRUE, TRUE, 0);
449 gtk_widget_show (mts->attendees_vbox);
450
451 /* build the etable */
452 mts->model = ems;
453
454 if (mts->model)
455 g_object_ref (mts->model);
456
457 g_signal_connect_swapped (
458 mts->model, "notify::free-busy-template",
459 G_CALLBACK (free_busy_template_changed_cb), mts);
460
461 g_signal_connect (
462 mts->model, "row_inserted",
463 G_CALLBACK (row_inserted_cb), mts);
464 g_signal_connect (
465 mts->model, "row_changed",
466 G_CALLBACK (row_changed_cb), mts);
467 g_signal_connect (
468 mts->model, "row_deleted",
469 G_CALLBACK (row_deleted_cb), mts);
470
471 mts->list_view = e_meeting_list_view_new (mts->model);
472 e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_ROLE_COL, FALSE);
473 e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_RSVP_COL, FALSE);
474 e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_STATUS_COL, FALSE);
475 e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_TYPE_COL, FALSE);
476
477 gtk_widget_show (GTK_WIDGET (mts->list_view));
478
479 sw = gtk_scrolled_window_new (NULL, NULL);
480 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
481 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
482 gtk_widget_set_child_visible (
483 gtk_scrolled_window_get_vscrollbar (
484 GTK_SCROLLED_WINDOW (sw)), FALSE);
485 gtk_widget_show (sw);
486 gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (mts->list_view));
487
488 #if 0
489 /* FIXME: do we need sorting here */
490 g_signal_connect (
491 real_table->sort_info, "sort_info_changed",
492 G_CALLBACK (sort_info_changed_cb), mts);
493 #endif
494
495 gtk_box_pack_start (GTK_BOX (mts->attendees_vbox), GTK_WIDGET (sw), TRUE, TRUE, 6);
496
497 /* The free/busy information */
498 mts->display_top = gnome_canvas_new ();
499 gtk_widget_set_size_request (mts->display_top, -1, mts->row_height * 3);
500 gnome_canvas_set_scroll_region (
501 GNOME_CANVAS (mts->display_top),
502 0, 0,
503 mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
504 mts->row_height * 3);
505 /* Add some horizontal padding for the shadow around the display. */
506 gtk_table_attach (
507 GTK_TABLE (mts), mts->display_top,
508 1, 4, 0, 1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
509 gtk_widget_show (mts->display_top);
510 g_signal_connect (
511 mts->display_top, "realize",
512 G_CALLBACK (e_meeting_time_selector_on_canvas_realized), mts);
513
514 mts->display_main = gnome_canvas_new ();
515 e_meeting_time_selector_update_main_canvas_scroll_region (mts);
516 /* Add some horizontal padding for the shadow around the display. */
517 gtk_table_attach (
518 GTK_TABLE (mts), mts->display_main,
519 1, 4, 1, 2,
520 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
521 gtk_widget_show (mts->display_main);
522 g_signal_connect (
523 mts->display_main, "realize",
524 G_CALLBACK (e_meeting_time_selector_on_canvas_realized), mts);
525 g_signal_connect (
526 mts->display_main, "size_allocate",
527 G_CALLBACK (e_meeting_time_selector_on_canvas_size_allocate), mts);
528 g_signal_connect (
529 mts->display_main, "scroll-event",
530 G_CALLBACK (e_meeting_time_selector_on_canvas_scroll_event), mts);
531 /* used for displaying extended free/busy (XFB) display when hovering
532 * over a busy period which carries XFB information */
533 g_signal_connect (mts->display_main,
534 "query-tooltip",
535 G_CALLBACK (e_meeting_time_selector_on_canvas_query_tooltip),
536 mts);
537 g_object_set (G_OBJECT (mts->display_main),
538 "has-tooltip", TRUE,
539 NULL);
540
541 scrollable = GTK_SCROLLABLE (mts->display_main);
542
543 adjustment = gtk_scrollable_get_vadjustment (scrollable);
544 gtk_scrolled_window_set_vadjustment (
545 GTK_SCROLLED_WINDOW (sw), adjustment);
546
547 adjustment = gtk_scrollable_get_hadjustment (scrollable);
548 mts->hscrollbar = gtk_hscrollbar_new (adjustment);
549 gtk_adjustment_set_step_increment (adjustment, mts->day_width);
550 gtk_table_attach (
551 GTK_TABLE (mts), mts->hscrollbar,
552 1, 4, 2, 3, GTK_EXPAND | GTK_FILL, 0, 0, 0);
553 gtk_widget_show (mts->hscrollbar);
554
555 adjustment = gtk_scrollable_get_vadjustment (scrollable);
556 mts->vscrollbar = gtk_vscrollbar_new (adjustment);
557 gtk_adjustment_set_step_increment (adjustment, mts->row_height);
558 gtk_table_attach (
559 GTK_TABLE (mts), mts->vscrollbar,
560 4, 5, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0);
561 gtk_widget_show (mts->vscrollbar);
562
563 /* Create the item in the top canvas. */
564 mts->item_top = gnome_canvas_item_new (
565 GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_top)->root),
566 e_meeting_time_selector_item_get_type (),
567 "EMeetingTimeSelectorItem::meeting_time_selector", mts,
568 NULL);
569
570 /* Create the item in the main canvas. */
571 mts->item_main = gnome_canvas_item_new (
572 GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_main)->root),
573 e_meeting_time_selector_item_get_type (),
574 "EMeetingTimeSelectorItem::meeting_time_selector", mts,
575 NULL);
576
577 /* Create the hbox containing the color key. */
578 hbox = gtk_hbox_new (FALSE, 2);
579 gtk_table_attach (
580 GTK_TABLE (mts), hbox,
581 1, 4, 3, 4, GTK_FILL, 0, 0, 8);
582 gtk_widget_show (hbox);
583
584 e_meeting_time_selector_add_key_color (mts, hbox, _("Tentative"), &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]);
585 e_meeting_time_selector_add_key_color (mts, hbox, _("Busy"), &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]);
586 e_meeting_time_selector_add_key_color (mts, hbox, _("Out of Office"), &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]);
587 e_meeting_time_selector_add_key_color (
588 mts, hbox, _("No Information"),
589 NULL);
590
591 separator = gtk_hseparator_new ();
592 gtk_table_attach (
593 GTK_TABLE (mts), separator,
594 0, 5, 4, 5, GTK_FILL, 0, 6, 6);
595 gtk_widget_show (separator);
596
597 /* Create the Invite Others & Options buttons on the left. */
598 hbox = gtk_hbox_new (FALSE, 4);
599 gtk_table_attach (
600 GTK_TABLE (mts), hbox,
601 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
602 gtk_widget_show (hbox);
603
604 mts->add_attendees_button =
605 gtk_button_new_with_mnemonic (_("Atte_ndees..."));
606 gtk_button_set_image (
607 GTK_BUTTON (mts->add_attendees_button),
608 gtk_image_new_from_stock (
609 GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON));
610 gtk_box_pack_start (GTK_BOX (hbox), mts->add_attendees_button, TRUE, TRUE, 6);
611 gtk_widget_show (mts->add_attendees_button);
612 g_signal_connect (
613 mts->add_attendees_button, "clicked",
614 G_CALLBACK (e_meeting_time_selector_on_invite_others_button_clicked), mts);
615
616 mts->options_button = gtk_button_new ();
617 gtk_box_pack_start (GTK_BOX (hbox), mts->options_button, TRUE, TRUE, 6);
618 gtk_widget_show (mts->options_button);
619
620 g_signal_connect (
621 mts->options_button, "clicked",
622 G_CALLBACK (e_meeting_time_selector_on_options_button_clicked), mts);
623
624 child_hbox = gtk_hbox_new (FALSE, 2);
625 gtk_container_add (GTK_CONTAINER (mts->options_button), child_hbox);
626 gtk_widget_show (child_hbox);
627
628 label = gtk_label_new_with_mnemonic (_("O_ptions"));
629 accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
630 gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
631 gtk_widget_show (label);
632 gtk_widget_add_accelerator (
633 mts->options_button, "clicked", mts->accel_group,
634 accel_key, GDK_MOD1_MASK, 0);
635
636 arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
637 gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 6);
638 gtk_widget_show (arrow);
639
640 /* Create the Options menu. */
641 mts->options_menu = gtk_menu_new ();
642 gtk_menu_attach_to_widget (
643 GTK_MENU (mts->options_menu), mts->options_button,
644 e_meeting_time_selector_options_menu_detacher);
645
646 menuitem = gtk_check_menu_item_new_with_label ("");
647 child = gtk_bin_get_child (GTK_BIN (menuitem));
648 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Show _only working hours"));
649 gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
650 gtk_check_menu_item_set_active (
651 GTK_CHECK_MENU_ITEM (menuitem),
652 mts->working_hours_only);
653
654 g_signal_connect (
655 menuitem, "toggled",
656 G_CALLBACK (e_meeting_time_selector_on_working_hours_toggled), mts);
657 gtk_widget_show (menuitem);
658
659 menuitem = gtk_check_menu_item_new_with_label ("");
660 child = gtk_bin_get_child (GTK_BIN (menuitem));
661 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Show _zoomed out"));
662 gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
663 gtk_check_menu_item_set_active (
664 GTK_CHECK_MENU_ITEM (menuitem),
665 mts->zoomed_out);
666
667 g_signal_connect (
668 menuitem, "toggled",
669 G_CALLBACK (e_meeting_time_selector_on_zoomed_out_toggled), mts);
670 gtk_widget_show (menuitem);
671
672 menuitem = gtk_menu_item_new ();
673 gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
674 gtk_widget_set_sensitive (menuitem, FALSE);
675 gtk_widget_show (menuitem);
676
677 menuitem = gtk_menu_item_new_with_label ("");
678 child = gtk_bin_get_child (GTK_BIN (menuitem));
679 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_Update free/busy"));
680 gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
681
682 g_signal_connect (
683 menuitem, "activate",
684 G_CALLBACK (e_meeting_time_selector_on_update_free_busy), mts);
685 gtk_widget_show (menuitem);
686
687 /* Create the 3 AutoPick buttons on the left. */
688 hbox = gtk_hbox_new (FALSE, 0);
689 gtk_table_attach (
690 GTK_TABLE (mts), hbox,
691 0, 1, 5, 6, GTK_FILL, 0, 0, 0);
692 gtk_widget_show (hbox);
693
694 mts->autopick_down_button = gtk_button_new_with_label ("");
695 child = gtk_bin_get_child (GTK_BIN (mts->autopick_down_button));
696 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_<<"));
697 accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
698 gtk_widget_add_accelerator (
699 mts->autopick_down_button, "clicked", mts->accel_group,
700 accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0);
701 gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_down_button, TRUE, TRUE, 6);
702 gtk_widget_show (mts->autopick_down_button);
703 g_signal_connect (
704 mts->autopick_down_button, "clicked",
705 G_CALLBACK (e_meeting_time_selector_on_prev_button_clicked), mts);
706
707 mts->autopick_button = gtk_button_new ();
708 gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_button, TRUE, TRUE, 6);
709 gtk_widget_show (mts->autopick_button);
710
711 child_hbox = gtk_hbox_new (FALSE, 2);
712 gtk_container_add (GTK_CONTAINER (mts->autopick_button), child_hbox);
713 gtk_widget_show (child_hbox);
714
715 label = gtk_label_new ("");
716 gtk_label_set_text_with_mnemonic (GTK_LABEL (label), _("_Autopick"));
717 accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
718 gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
719 gtk_widget_show (label);
720 gtk_widget_add_accelerator (
721 mts->autopick_button, "clicked", mts->accel_group,
722 accel_key, GDK_MOD1_MASK, 0);
723 g_signal_connect (
724 mts->autopick_button, "clicked",
725 G_CALLBACK (e_meeting_time_selector_on_autopick_button_clicked), mts);
726
727 arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
728 gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 6);
729 gtk_widget_show (arrow);
730
731 mts->autopick_up_button = gtk_button_new_with_label ("");
732 child = gtk_bin_get_child (GTK_BIN (mts->autopick_up_button));
733 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _(">_>"));
734 accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
735 gtk_widget_add_accelerator (
736 mts->autopick_up_button, "clicked", mts->accel_group,
737 accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0);
738 gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_up_button, TRUE, TRUE, 6);
739 gtk_widget_show (mts->autopick_up_button);
740 g_signal_connect (
741 mts->autopick_up_button, "clicked",
742 G_CALLBACK (e_meeting_time_selector_on_next_button_clicked), mts);
743
744 /* Create the Autopick menu. */
745 mts->autopick_menu = gtk_menu_new ();
746 gtk_menu_attach_to_widget (
747 GTK_MENU (mts->autopick_menu), mts->autopick_button,
748 e_meeting_time_selector_autopick_menu_detacher);
749
750 menuitem = gtk_radio_menu_item_new_with_label (NULL, "");
751 mts->autopick_all_item = menuitem;
752 child = gtk_bin_get_child (GTK_BIN (menuitem));
753 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
754 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_All people and resources"));
755 gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
756 g_signal_connect (
757 menuitem, "toggled",
758 G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
759 gtk_widget_show (menuitem);
760
761 menuitem = gtk_radio_menu_item_new_with_label (group, "");
762 mts->autopick_all_people_one_resource_item = menuitem;
763 child = gtk_bin_get_child (GTK_BIN (menuitem));
764 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
765 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("All _people and one resource"));
766 gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
767 g_signal_connect (
768 menuitem, "toggled",
769 G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
770 gtk_widget_show (menuitem);
771
772 menuitem = gtk_radio_menu_item_new_with_label (group, "");
773 mts->autopick_required_people_item = menuitem;
774 child = gtk_bin_get_child (GTK_BIN (menuitem));
775 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
776 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_Required people"));
777 gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
778 g_signal_connect (
779 menuitem, "activate",
780 G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
781 gtk_widget_show (menuitem);
782
783 menuitem = gtk_radio_menu_item_new_with_label (group, "");
784 mts->autopick_required_people_one_resource_item = menuitem;
785 child = gtk_bin_get_child (GTK_BIN (menuitem));
786 gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Required people and _one resource"));
787 gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
788 g_signal_connect (
789 menuitem, "activate",
790 G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
791 gtk_widget_show (menuitem);
792
793 /* Create the date entry fields on the right. */
794 alignment = gtk_alignment_new (0.0, 0.5, 0, 0);
795 gtk_table_attach (
796 GTK_TABLE (mts), alignment,
797 1, 4, 5, 6, GTK_FILL, 0, 0, 0);
798 gtk_widget_show (alignment);
799
800 table = gtk_table_new (2, 2, FALSE);
801 gtk_table_set_row_spacings (GTK_TABLE (table), 4);
802 gtk_container_add (GTK_CONTAINER (alignment), table);
803 gtk_widget_show (table);
804
805 mts->start_date_edit = e_date_edit_new ();
806 gtk_label_set_mnemonic_widget (GTK_LABEL (label), mts->start_date_edit);
807 a11y_label = gtk_widget_get_accessible (label);
808 a11y_date_edit = gtk_widget_get_accessible (mts->start_date_edit);
809 if (a11y_label != NULL && a11y_date_edit != NULL) {
810 atk_object_add_relationship (
811 a11y_date_edit,
812 ATK_RELATION_LABELLED_BY,
813 a11y_label);
814 }
815 e_date_edit_set_show_time (E_DATE_EDIT (mts->start_date_edit), TRUE);
816
817 g_object_bind_property (
818 mts, "show-week-numbers",
819 mts->start_date_edit, "show-week-numbers",
820 G_BINDING_SYNC_CREATE);
821
822 g_object_bind_property (
823 mts, "use-24-hour-format",
824 mts->start_date_edit, "use-24-hour-format",
825 G_BINDING_SYNC_CREATE);
826
827 g_object_bind_property (
828 mts, "week-start-day",
829 mts->start_date_edit, "week-start-day",
830 G_BINDING_SYNC_CREATE);
831
832 gtk_table_attach (
833 GTK_TABLE (table), mts->start_date_edit,
834 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
835 gtk_widget_show (mts->start_date_edit);
836 g_signal_connect (
837 mts->start_date_edit, "changed",
838 G_CALLBACK (e_meeting_time_selector_on_start_time_changed), mts);
839
840 label = gtk_label_new_with_mnemonic (_("_Start time:"));
841 gtk_label_set_mnemonic_widget (GTK_LABEL (label), (mts->start_date_edit));
842
843 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
844 gtk_table_attach (
845 GTK_TABLE (table), label,
846 0, 1, 0, 1, GTK_FILL, 0, 4, 0);
847 gtk_widget_show (label);
848
849 mts->end_date_edit = e_date_edit_new ();
850 gtk_label_set_mnemonic_widget (GTK_LABEL (label), mts->end_date_edit);
851 a11y_label = gtk_widget_get_accessible (label);
852 a11y_date_edit = gtk_widget_get_accessible (mts->end_date_edit);
853 if (a11y_label != NULL && a11y_date_edit != NULL) {
854 atk_object_add_relationship (
855 a11y_date_edit,
856 ATK_RELATION_LABELLED_BY,
857 a11y_label);
858 }
859 e_date_edit_set_show_time (E_DATE_EDIT (mts->end_date_edit), TRUE);
860
861 g_object_bind_property (
862 mts, "show-week-numbers",
863 mts->end_date_edit, "show-week-numbers",
864 G_BINDING_SYNC_CREATE);
865
866 g_object_bind_property (
867 mts, "use-24-hour-format",
868 mts->end_date_edit, "use-24-hour-format",
869 G_BINDING_SYNC_CREATE);
870
871 g_object_bind_property (
872 mts, "week-start-day",
873 mts->end_date_edit, "week-start-day",
874 G_BINDING_SYNC_CREATE);
875
876 gtk_table_attach (
877 GTK_TABLE (table), mts->end_date_edit,
878 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
879 gtk_widget_show (mts->end_date_edit);
880 g_signal_connect (
881 mts->end_date_edit, "changed",
882 G_CALLBACK (e_meeting_time_selector_on_end_time_changed), mts);
883
884 label = gtk_label_new_with_mnemonic (_("_End time:"));
885 gtk_label_set_mnemonic_widget (GTK_LABEL (label), (mts->end_date_edit));
886
887 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
888 gtk_table_attach (
889 GTK_TABLE (table), label,
890 0, 1, 1, 2, GTK_FILL, 0, 4, 0);
891 gtk_widget_show (label);
892
893 gtk_table_set_col_spacing (GTK_TABLE (mts), 0, 4);
894 gtk_table_set_row_spacing (GTK_TABLE (mts), 4, 12);
895
896 /* Allocate the colors. */
897 e_meeting_time_selector_alloc_named_color (mts, "snow", &mts->bg_color);
898 e_meeting_time_selector_alloc_named_color (mts, "snow3", &mts->all_attendees_bg_color);
899 e_meeting_time_selector_alloc_named_color (mts, "black", &mts->grid_color);
900 e_meeting_time_selector_alloc_named_color (mts, "white", &mts->grid_shadow_color);
901 e_meeting_time_selector_alloc_named_color (mts, "gray50", &mts->grid_unused_color);
902 e_meeting_time_selector_alloc_named_color (mts, "white", &mts->attendee_list_bg_color);
903
904 e_meeting_time_selector_alloc_named_color (mts, "snow4", &mts->meeting_time_bg_color);
905 e_meeting_time_selector_alloc_named_color (mts, "snow", &mts->busy_colors[E_MEETING_FREE_BUSY_FREE]);
906 e_meeting_time_selector_alloc_named_color (mts, "#a5d3ef", &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]);
907 e_meeting_time_selector_alloc_named_color (mts, "blue", &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]);
908 e_meeting_time_selector_alloc_named_color (mts, "#ce6194", &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]);
909
910 /* Connect handlers to the adjustments scroll the other items. */
911 scrollable = GTK_SCROLLABLE (mts->display_main);
912 adjustment = gtk_scrollable_get_hadjustment (scrollable);
913 g_signal_connect (
914 adjustment, "value_changed",
915 G_CALLBACK (e_meeting_time_selector_hadjustment_changed), mts);
916 adjustment = gtk_scrollable_get_vadjustment (scrollable);
917 g_signal_connect (
918 adjustment, "value_changed",
919 G_CALLBACK (e_meeting_time_selector_vadjustment_changed), mts);
920 g_signal_connect (
921 adjustment, "changed",
922 G_CALLBACK (e_meeting_time_selector_vadjustment_changed), mts);
923
924 e_meeting_time_selector_recalc_grid (mts);
925 e_meeting_time_selector_ensure_meeting_time_shown (mts);
926 e_meeting_time_selector_update_start_date_edit (mts);
927 e_meeting_time_selector_update_end_date_edit (mts);
928 e_meeting_time_selector_update_date_popup_menus (mts);
929
930 g_signal_emit (mts, signals[CHANGED], 0);
931 }
932
933 /* This adds a color to the color key beneath the main display. If color is
934 * NULL, it displays the No Info pattern instead. */
935 static void
936 e_meeting_time_selector_add_key_color (EMeetingTimeSelector *mts,
937 GtkWidget *hbox,
938 gchar *label_text,
939 GdkColor *color)
940 {
941 GtkWidget *child_hbox, *darea, *label;
942
943 child_hbox = gtk_hbox_new (FALSE, 4);
944 gtk_box_pack_start (GTK_BOX (hbox), child_hbox, TRUE, TRUE, 0);
945 gtk_widget_show (child_hbox);
946
947 darea = gtk_drawing_area_new ();
948 gtk_box_pack_start (GTK_BOX (child_hbox), darea, FALSE, FALSE, 0);
949 g_object_set_data (G_OBJECT (darea), "data", mts);
950 gtk_widget_set_size_request (darea, 14, 14);
951 gtk_widget_show (darea);
952
953 label = gtk_label_new (label_text);
954 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
955 gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
956 gtk_widget_show (label);
957
958 g_signal_connect (
959 darea, "draw",
960 G_CALLBACK (e_meeting_time_selector_draw_key_color), color);
961 }
962
963 static gint
964 e_meeting_time_selector_draw_key_color (GtkWidget *darea,
965 cairo_t *cr,
966 GdkColor *color)
967 {
968 EMeetingTimeSelector * mts;
969 GtkAllocation allocation;
970 GtkStyle *style;
971
972 style = gtk_widget_get_style (darea);
973 gtk_widget_get_allocation (darea, &allocation);
974
975 mts = g_object_get_data (G_OBJECT (darea), "data");
976
977 gtk_paint_shadow (
978 style, cr, GTK_STATE_NORMAL,
979 GTK_SHADOW_IN, NULL, NULL, 0, 0,
980 allocation.width, allocation.height);
981
982 if (color) {
983 gdk_cairo_set_source_color (cr, color);
984 } else {
985 cairo_set_source (cr, mts->no_info_pattern);
986 }
987 cairo_rectangle (
988 cr,
989 1, 1,
990 allocation.width - 2, allocation.height - 2);
991 cairo_fill (cr);
992
993 return TRUE;
994 }
995
996 static void
997 e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector *mts,
998 const gchar *name,
999 GdkColor *c)
1000 {
1001 g_return_if_fail (name != NULL);
1002 g_return_if_fail (c != NULL);
1003
1004 if (!gdk_color_parse (name, c))
1005 g_warning ("Failed to parse color: %s\n", name);
1006 }
1007
1008 static void
1009 e_meeting_time_selector_options_menu_detacher (GtkWidget *widget,
1010 GtkMenu *menu)
1011 {
1012 EMeetingTimeSelector *mts;
1013
1014 g_return_if_fail (widget != NULL);
1015 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (widget));
1016
1017 mts = E_MEETING_TIME_SELECTOR (widget);
1018 g_return_if_fail (mts->options_menu == (GtkWidget *) menu);
1019
1020 mts->options_menu = NULL;
1021 }
1022
1023 static void
1024 e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget,
1025 GtkMenu *menu)
1026 {
1027 EMeetingTimeSelector *mts;
1028
1029 g_return_if_fail (widget != NULL);
1030 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (widget));
1031
1032 mts = E_MEETING_TIME_SELECTOR (widget);
1033 g_return_if_fail (mts->autopick_menu == (GtkWidget *) menu);
1034
1035 mts->autopick_menu = NULL;
1036 }
1037
1038 GtkWidget *
1039 e_meeting_time_selector_new (EMeetingStore *ems)
1040 {
1041 GtkWidget *mts;
1042
1043 mts = g_object_new (E_TYPE_MEETING_TIME_SELECTOR, NULL);
1044
1045 e_meeting_time_selector_construct (E_MEETING_TIME_SELECTOR (mts), ems);
1046
1047 return mts;
1048 }
1049
1050 gboolean
1051 e_meeting_time_selector_get_show_week_numbers (EMeetingTimeSelector *mts)
1052 {
1053 g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);
1054
1055 return mts->priv->show_week_numbers;
1056 }
1057
1058 void
1059 e_meeting_time_selector_set_show_week_numbers (EMeetingTimeSelector *mts,
1060 gboolean show_week_numbers)
1061 {
1062 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1063
1064 if (mts->priv->show_week_numbers == show_week_numbers)
1065 return;
1066
1067 mts->priv->show_week_numbers = show_week_numbers;
1068
1069 g_object_notify (G_OBJECT (mts), "show-week-numbers");
1070 }
1071
1072 gboolean
1073 e_meeting_time_selector_get_use_24_hour_format (EMeetingTimeSelector *mts)
1074 {
1075 g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);
1076
1077 return mts->priv->use_24_hour_format;
1078 }
1079
1080 void
1081 e_meeting_time_selector_set_use_24_hour_format (EMeetingTimeSelector *mts,
1082 gboolean use_24_hour_format)
1083 {
1084 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1085
1086 if (mts->priv->use_24_hour_format == use_24_hour_format)
1087 return;
1088
1089 mts->priv->use_24_hour_format = use_24_hour_format;
1090
1091 g_object_notify (G_OBJECT (mts), "use-24-hour-format");
1092 }
1093
1094 gint
1095 e_meeting_time_selector_get_week_start_day (EMeetingTimeSelector *mts)
1096 {
1097 g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), 0);
1098
1099 return mts->priv->week_start_day;
1100 }
1101
1102 void
1103 e_meeting_time_selector_set_week_start_day (EMeetingTimeSelector *mts,
1104 gint week_start_day)
1105 {
1106 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1107
1108 if (mts->priv->week_start_day == week_start_day)
1109 return;
1110
1111 mts->priv->week_start_day = week_start_day;
1112
1113 g_object_notify (G_OBJECT (mts), "week-start-day");
1114 }
1115
1116 static cairo_pattern_t *
1117 e_meeting_time_selector_create_no_info_pattern (EMeetingTimeSelector *mts)
1118 {
1119 cairo_surface_t *surface;
1120 cairo_pattern_t *pattern;
1121 GdkColor color;
1122 cairo_t *cr;
1123
1124 surface = gdk_window_create_similar_surface (
1125 gtk_widget_get_window (GTK_WIDGET (mts)),
1126 CAIRO_CONTENT_COLOR, 8, 8);
1127 cr = cairo_create (surface);
1128
1129 gdk_color_parse ("white", &color);
1130 gdk_cairo_set_source_color (cr, &color);
1131 cairo_paint (cr);
1132
1133 gdk_cairo_set_source_color (cr, &mts->grid_color);
1134 cairo_set_line_width (cr, 1.0);
1135 cairo_move_to (cr, -1, 5);
1136 cairo_line_to (cr, 9, -5);
1137 cairo_move_to (cr, -1, 13);
1138 cairo_line_to (cr, 9, 3);
1139 cairo_stroke (cr);
1140
1141 cairo_destroy (cr);
1142
1143 pattern = cairo_pattern_create_for_surface (surface);
1144 cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
1145
1146 cairo_surface_destroy (surface);
1147
1148 return pattern;
1149 }
1150
1151 static void
1152 e_meeting_time_selector_realize (GtkWidget *widget)
1153 {
1154 EMeetingTimeSelector *mts;
1155
1156 if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->realize)
1157 (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->realize)(widget);
1158
1159 mts = E_MEETING_TIME_SELECTOR (widget);
1160
1161 mts->no_info_pattern = e_meeting_time_selector_create_no_info_pattern (mts);
1162 }
1163
1164 static void
1165 e_meeting_time_selector_unrealize (GtkWidget *widget)
1166 {
1167 EMeetingTimeSelector *mts;
1168
1169 mts = E_MEETING_TIME_SELECTOR (widget);
1170
1171 cairo_pattern_destroy (mts->no_info_pattern);
1172 mts->no_info_pattern = NULL;
1173
1174 if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->unrealize)
1175 (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->unrealize)(widget);
1176 }
1177
1178 static gint
1179 get_cell_height (GtkTreeView *tree)
1180 {
1181 GtkTreeViewColumn *column;
1182 gint height = -1;
1183
1184 column = gtk_tree_view_get_column (tree, 0);
1185 gtk_tree_view_column_cell_get_size (
1186 column, NULL,
1187 NULL, NULL,
1188 NULL, &height);
1189
1190 return height;
1191 }
1192
1193 static gboolean
1194 style_change_idle_func (EMeetingTimeSelector *mts)
1195 {
1196 EMeetingTime saved_time;
1197 GtkAdjustment *adjustment;
1198 GtkWidget *widget;
1199 gint hour, max_hour_width;
1200 /*int maxheight; */
1201 PangoFontDescription *font_desc;
1202 PangoContext *pango_context;
1203 PangoFontMetrics *font_metrics;
1204 PangoLayout *layout;
1205
1206 /* Set up Pango prerequisites */
1207 widget = GTK_WIDGET (mts);
1208 font_desc = gtk_widget_get_style (widget)->font_desc;
1209 pango_context = gtk_widget_get_pango_context (widget);
1210 font_metrics = pango_context_get_metrics (
1211 pango_context, font_desc,
1212 pango_context_get_language (pango_context));
1213 layout = pango_layout_new (pango_context);
1214
1215 /* Calculate the widths of the hour strings in the style's font. */
1216 max_hour_width = 0;
1217 for (hour = 0; hour < 24; hour++) {
1218 if (e_meeting_time_selector_get_use_24_hour_format (mts))
1219 pango_layout_set_text (layout, EMeetingTimeSelectorHours[hour], -1);
1220 else
1221 pango_layout_set_text (layout, EMeetingTimeSelectorHours12[hour], -1);
1222
1223 pango_layout_get_pixel_size (layout, &mts->hour_widths[hour], NULL);
1224 max_hour_width = MAX (max_hour_width, mts->hour_widths[hour]);
1225 }
1226
1227 /* add also some padding for lines so it fits better */
1228 mts->row_height = get_cell_height (GTK_TREE_VIEW (mts->list_view)) + 2;
1229 mts->col_width = max_hour_width + 6;
1230
1231 e_meeting_time_selector_save_position (mts, &saved_time);
1232 e_meeting_time_selector_recalc_grid (mts);
1233 e_meeting_time_selector_restore_position (mts, &saved_time);
1234
1235 gtk_widget_set_size_request (mts->display_top, -1, mts->row_height * 3 + 4);
1236
1237 /*
1238 * FIXME: I can't find a way to get the treeview header heights
1239 * other than the below but it isn't nice to realize that widget here
1240 *
1241 *
1242 gtk_widget_realize (mts->list_view);
1243 gdk_window_get_position (
1244 gtk_tree_view_get_bin_window (GTK_TREE_VIEW (mts->list_view)),
1245 NULL, &maxheight);
1246 gtk_widget_set_size_request (mts->attendees_vbox_spacer, 1, mts->row_height * 3 - maxheight);
1247 *
1248 */
1249
1250 gtk_widget_set_size_request (mts->attendees_vbox_spacer, 1, mts->row_height * 2 - 6);
1251
1252 widget = mts->display_main;
1253
1254 adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
1255 gtk_adjustment_set_step_increment (adjustment, mts->day_width);
1256
1257 adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
1258 gtk_adjustment_set_step_increment (adjustment, mts->row_height);
1259
1260 g_object_unref (layout);
1261 pango_font_metrics_unref (font_metrics);
1262
1263 mts->style_change_idle_id = 0;
1264
1265 return FALSE;
1266 }
1267
1268 static void
1269 e_meeting_time_selector_style_set (GtkWidget *widget,
1270 GtkStyle *previous_style)
1271 {
1272 EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (widget);
1273
1274 if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)
1275 (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)(widget, previous_style);
1276
1277 if (!mts->style_change_idle_id)
1278 mts->style_change_idle_id = g_idle_add (
1279 (GSourceFunc) style_change_idle_func, widget);
1280 }
1281
1282 /* This draws a shadow around the top display and main display. */
1283 static gint
1284 e_meeting_time_selector_draw (GtkWidget *widget,
1285 cairo_t *cr)
1286 {
1287 EMeetingTimeSelector *mts;
1288
1289 mts = E_MEETING_TIME_SELECTOR (widget);
1290
1291 e_meeting_time_selector_draw_shadow (mts, cr);
1292
1293 if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->draw)
1294 (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->draw)(widget, cr);
1295
1296 return FALSE;
1297 }
1298
1299 static void
1300 e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts,
1301 cairo_t *cr)
1302 {
1303 GtkAllocation allocation;
1304 GtkStyle *style;
1305 gint x, y, w, h;
1306
1307 cairo_save (cr);
1308
1309 /* Draw the shadow around the graphical displays. */
1310 gtk_widget_get_allocation (mts->display_top, &allocation);
1311 x = allocation.x - 2;
1312 y = allocation.y - 2;
1313 w = allocation.width + 4;
1314 h = allocation.height + allocation.height + 4;
1315
1316 style = gtk_widget_get_style (GTK_WIDGET (mts));
1317
1318 gtk_paint_shadow (
1319 style, cr, GTK_STATE_NORMAL,
1320 GTK_SHADOW_IN, NULL, NULL, x, y, w, h);
1321
1322 cairo_restore (cr);
1323 }
1324
1325 /* When the main canvas scrolls, we scroll the other canvases. */
1326 static void
1327 e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment,
1328 EMeetingTimeSelector *mts)
1329 {
1330 GtkAdjustment *hadjustment;
1331 GtkScrollable *scrollable;
1332 gdouble value;
1333
1334 scrollable = GTK_SCROLLABLE (mts->display_top);
1335 hadjustment = gtk_scrollable_get_hadjustment (scrollable);
1336
1337 value = gtk_adjustment_get_value (adjustment);
1338 gtk_adjustment_set_value (hadjustment, value);
1339 }
1340
1341 static void
1342 e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment,
1343 EMeetingTimeSelector *mts)
1344 {
1345 GtkAdjustment *vadjustment;
1346 GtkTreeView *tree_view;
1347 gdouble value;
1348
1349 tree_view = GTK_TREE_VIEW (mts->list_view);
1350 vadjustment = gtk_tree_view_get_vadjustment (tree_view);
1351
1352 value = gtk_adjustment_get_value (adjustment);
1353 gtk_adjustment_set_value (vadjustment, value);
1354 }
1355
1356 void
1357 e_meeting_time_selector_get_meeting_time (EMeetingTimeSelector *mts,
1358 gint *start_year,
1359 gint *start_month,
1360 gint *start_day,
1361 gint *start_hour,
1362 gint *start_minute,
1363 gint *end_year,
1364 gint *end_month,
1365 gint *end_day,
1366 gint *end_hour,
1367 gint *end_minute)
1368 {
1369 *start_year = g_date_get_year (&mts->meeting_start_time.date);
1370 *start_month = g_date_get_month (&mts->meeting_start_time.date);
1371 *start_day = g_date_get_day (&mts->meeting_start_time.date);
1372 *start_hour = mts->meeting_start_time.hour;
1373 *start_minute = mts->meeting_start_time.minute;
1374
1375 *end_year = g_date_get_year (&mts->meeting_end_time.date);
1376 *end_month = g_date_get_month (&mts->meeting_end_time.date);
1377 *end_day = g_date_get_day (&mts->meeting_end_time.date);
1378 *end_hour = mts->meeting_end_time.hour;
1379 *end_minute = mts->meeting_end_time.minute;
1380 }
1381
1382 gboolean
1383 e_meeting_time_selector_set_meeting_time (EMeetingTimeSelector *mts,
1384 gint start_year,
1385 gint start_month,
1386 gint start_day,
1387 gint start_hour,
1388 gint start_minute,
1389 gint end_year,
1390 gint end_month,
1391 gint end_day,
1392 gint end_hour,
1393 gint end_minute)
1394 {
1395 g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);
1396
1397 /* Check the dates are valid. */
1398 if (!g_date_valid_dmy (start_day, start_month, start_year)
1399 || !g_date_valid_dmy (end_day, end_month, end_year)
1400 || start_hour < 0 || start_hour > 23
1401 || end_hour < 0 || end_hour > 23
1402 || start_minute < 0 || start_minute > 59
1403 || end_minute < 0 || end_minute > 59)
1404 return FALSE;
1405
1406 g_date_set_dmy (
1407 &mts->meeting_start_time.date, start_day, start_month,
1408 start_year);
1409 mts->meeting_start_time.hour = start_hour;
1410 mts->meeting_start_time.minute = start_minute;
1411 g_date_set_dmy (
1412 &mts->meeting_end_time.date,
1413 end_day, end_month, end_year);
1414 mts->meeting_end_time.hour = end_hour;
1415 mts->meeting_end_time.minute = end_minute;
1416
1417 mts->meeting_positions_valid = FALSE;
1418
1419 gtk_widget_queue_draw (mts->display_top);
1420 gtk_widget_queue_draw (mts->display_main);
1421
1422 /* Set the times in the EDateEdit widgets. */
1423 e_meeting_time_selector_update_start_date_edit (mts);
1424 e_meeting_time_selector_update_end_date_edit (mts);
1425
1426 g_signal_emit (mts, signals[CHANGED], 0);
1427
1428 return TRUE;
1429 }
1430
1431 void
1432 e_meeting_time_selector_set_all_day (EMeetingTimeSelector *mts,
1433 gboolean all_day)
1434 {
1435 EMeetingTime saved_time;
1436
1437 mts->all_day = all_day;
1438
1439 e_date_edit_set_show_time (
1440 E_DATE_EDIT (mts->start_date_edit),
1441 !all_day);
1442 e_date_edit_set_show_time (
1443 E_DATE_EDIT (mts->end_date_edit),
1444 !all_day);
1445
1446 e_meeting_time_selector_save_position (mts, &saved_time);
1447 e_meeting_time_selector_recalc_grid (mts);
1448 e_meeting_time_selector_restore_position (mts, &saved_time);
1449
1450 gtk_widget_queue_draw (mts->display_top);
1451 gtk_widget_queue_draw (mts->display_main);
1452 e_meeting_time_selector_update_date_popup_menus (mts);
1453 }
1454
1455 void
1456 e_meeting_time_selector_set_working_hours_only (EMeetingTimeSelector *mts,
1457 gboolean working_hours_only)
1458 {
1459 EMeetingTime saved_time;
1460
1461 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1462
1463 if (mts->working_hours_only == working_hours_only)
1464 return;
1465
1466 mts->working_hours_only = working_hours_only;
1467
1468 e_meeting_time_selector_save_position (mts, &saved_time);
1469 e_meeting_time_selector_recalc_grid (mts);
1470 e_meeting_time_selector_restore_position (mts, &saved_time);
1471
1472 gtk_widget_queue_draw (mts->display_top);
1473 gtk_widget_queue_draw (mts->display_main);
1474 e_meeting_time_selector_update_date_popup_menus (mts);
1475 }
1476
1477 void
1478 e_meeting_time_selector_set_working_hours (EMeetingTimeSelector *mts,
1479 gint day_start_hour,
1480 gint day_start_minute,
1481 gint day_end_hour,
1482 gint day_end_minute)
1483 {
1484 EMeetingTime saved_time;
1485
1486 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1487
1488 if (mts->day_start_hour == day_start_hour
1489 && mts->day_start_minute == day_start_minute
1490 && mts->day_end_hour == day_end_hour
1491 && mts->day_end_minute == day_end_minute)
1492 return;
1493
1494 mts->day_start_hour = day_start_hour;
1495 mts->day_start_minute = day_start_minute;
1496
1497 /* Make sure we always show atleast an hour */
1498 if (day_start_hour * 60 + day_start_minute + 60 < day_end_hour * 60 + day_end_minute) {
1499 mts->day_end_hour = day_end_hour;
1500 mts->day_end_minute = day_end_minute;
1501 } else {
1502 mts->day_end_hour = day_start_hour + 1;
1503 mts->day_end_minute = day_start_minute;
1504 }
1505
1506 e_meeting_time_selector_save_position (mts, &saved_time);
1507 e_meeting_time_selector_recalc_grid (mts);
1508 e_meeting_time_selector_restore_position (mts, &saved_time);
1509
1510 gtk_widget_queue_draw (mts->display_top);
1511 gtk_widget_queue_draw (mts->display_main);
1512 e_meeting_time_selector_update_date_popup_menus (mts);
1513 }
1514
1515 void
1516 e_meeting_time_selector_set_zoomed_out (EMeetingTimeSelector *mts,
1517 gboolean zoomed_out)
1518 {
1519 EMeetingTime saved_time;
1520
1521 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1522
1523 if (mts->zoomed_out == zoomed_out)
1524 return;
1525
1526 mts->zoomed_out = zoomed_out;
1527
1528 e_meeting_time_selector_save_position (mts, &saved_time);
1529 e_meeting_time_selector_recalc_grid (mts);
1530 e_meeting_time_selector_restore_position (mts, &saved_time);
1531
1532 gtk_widget_queue_draw (mts->display_top);
1533 gtk_widget_queue_draw (mts->display_main);
1534 }
1535
1536 static gboolean
1537 e_meeting_time_selector_refresh_cb (gpointer data)
1538 {
1539 EMeetingTimeSelector *mts = data;
1540
1541 if (e_meeting_store_get_num_queries (mts->model) == 0) {
1542 GdkCursor *cursor;
1543 GdkWindow *window;
1544
1545 cursor = gdk_cursor_new (GDK_LEFT_PTR);
1546 window = gtk_widget_get_window (GTK_WIDGET (mts));
1547 if (window)
1548 gdk_window_set_cursor (window, cursor);
1549 g_object_unref (cursor);
1550
1551 mts->last_cursor_set = GDK_LEFT_PTR;
1552
1553 e_meeting_time_selector_item_set_normal_cursor (E_MEETING_TIME_SELECTOR_ITEM (mts->item_top));
1554 e_meeting_time_selector_item_set_normal_cursor (E_MEETING_TIME_SELECTOR_ITEM (mts->item_main));
1555 }
1556
1557 if (mts->display_top != NULL)
1558 gtk_widget_queue_draw (mts->display_top);
1559 if (mts->display_main != NULL)
1560 gtk_widget_queue_draw (mts->display_main);
1561
1562 g_object_unref (mts);
1563
1564 return FALSE;
1565 }
1566
1567 void
1568 e_meeting_time_selector_refresh_free_busy (EMeetingTimeSelector *mts,
1569 gint row,
1570 gboolean all)
1571 {
1572 EMeetingTime start, end;
1573
1574 /* nothing to refresh, lets not leak a busy cursor */
1575 if (e_meeting_store_count_actual_attendees (mts->model) <= 0)
1576 return;
1577
1578 start = mts->meeting_start_time;
1579 g_date_subtract_days (&start.date, E_MEETING_TIME_SELECTOR_FB_DAYS_BEFORE);
1580 start.hour = 0;
1581 start.minute = 0;
1582 end = mts->meeting_end_time;
1583 g_date_add_days (&end.date, E_MEETING_TIME_SELECTOR_FB_DAYS_AFTER);
1584 end.hour = 0;
1585 end.minute = 0;
1586
1587 /* XXX This function is called during schedule page initialization
1588 * before the meeting time selector is realized, meaning it has
1589 * no GdkWindow yet. This avoids a runtime warning. */
1590 if (gtk_widget_get_realized (GTK_WIDGET (mts))) {
1591 GdkCursor *cursor;
1592 GdkWindow *window;
1593
1594 /* Set the cursor to Busy. We need to reset it to
1595 * normal once the free busy queries are complete. */
1596 cursor = gdk_cursor_new (GDK_WATCH);
1597 window = gtk_widget_get_window (GTK_WIDGET (mts));
1598 gdk_window_set_cursor (window, cursor);
1599 g_object_unref (cursor);
1600
1601 mts->last_cursor_set = GDK_WATCH;
1602 }
1603
1604 /* Ref ourselves in case we are called back after destruction,
1605 * we can do this because we will get a call back even after
1606 * an error */
1607 /* FIXME We should really have a mechanism to unqueue the
1608 * notification */
1609 if (all) {
1610 gint i;
1611
1612 for (i = 0; i < e_meeting_store_count_actual_attendees (mts->model); i++)
1613 g_object_ref (mts);
1614 } else {
1615 g_object_ref (mts);
1616 }
1617
1618 if (all)
1619 e_meeting_store_refresh_all_busy_periods (
1620 mts->model, &start, &end,
1621 e_meeting_time_selector_refresh_cb, mts);
1622 else
1623 e_meeting_store_refresh_busy_periods (
1624 mts->model, row, &start, &end,
1625 e_meeting_time_selector_refresh_cb, mts);
1626 }
1627
1628 EMeetingTimeSelectorAutopickOption
1629 e_meeting_time_selector_get_autopick_option (EMeetingTimeSelector *mts)
1630 {
1631 GtkWidget *widget;
1632
1633 widget = mts->autopick_all_item;
1634 if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
1635 return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES;
1636
1637 widget = mts->autopick_all_people_one_resource_item;
1638 if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
1639 return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE;
1640
1641 widget = mts->autopick_required_people_item;
1642 if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
1643 return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE;
1644
1645 return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE;
1646 }
1647
1648 void
1649 e_meeting_time_selector_set_autopick_option (EMeetingTimeSelector *mts,
1650 EMeetingTimeSelectorAutopickOption autopick_option)
1651 {
1652 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1653
1654 switch (autopick_option) {
1655 case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES:
1656 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_item), TRUE);
1657 break;
1658 case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE:
1659 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_people_one_resource_item), TRUE);
1660 break;
1661 case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE:
1662 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_item), TRUE);
1663 break;
1664 case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE:
1665 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_one_resource_item), TRUE);
1666 break;
1667 }
1668 }
1669
1670 void
1671 e_meeting_time_selector_set_read_only (EMeetingTimeSelector *mts,
1672 gboolean read_only)
1673 {
1674 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1675
1676 gtk_widget_set_sensitive (GTK_WIDGET (mts->list_view), !read_only);
1677 gtk_widget_set_sensitive (mts->display_main, !read_only);
1678 gtk_widget_set_sensitive (mts->add_attendees_button, !read_only);
1679 gtk_widget_set_sensitive (mts->autopick_down_button, !read_only);
1680 gtk_widget_set_sensitive (mts->autopick_button, !read_only);
1681 gtk_widget_set_sensitive (mts->autopick_up_button, !read_only);
1682 gtk_widget_set_sensitive (mts->start_date_edit, !read_only);
1683 gtk_widget_set_sensitive (mts->end_date_edit, !read_only);
1684 }
1685
1686 /*
1687 * DEBUGGING ROUTINES - functions to output various bits of data.
1688 */
1689
1690 #ifdef E_MEETING_TIME_SELECTOR_DEBUG
1691
1692 /* Debugging function to dump information on all attendees. */
1693 void
1694 e_meeting_time_selector_dump (EMeetingTimeSelector *mts)
1695 {
1696 EMeetingTimeSelectorAttendee *attendee;
1697 EMeetingTimeSelectorPeriod *period;
1698 gint row, period_num;
1699 gchar buffer[128];
1700
1701 g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1702
1703 g_print ("\n\nAttendee Information:\n");
1704
1705 for (row = 0; row < mts->attendees->len; row++) {
1706 attendee = &g_array_index (mts->attendees,
1707 EMeetingTimeSelectorAttendee, row);
1708 g_print ("Attendee: %s\n", attendee->name);
1709 g_print (
1710 " Longest Busy Period: %i days\n",
1711 attendee->longest_period_in_days);
1712
1713 e_meeting_time_selector_attendee_ensure_periods_sorted (mts, attendee);
1714 #if 1
1715 for (period_num = 0;
1716 period_num < attendee->busy_periods->len;
1717 period_num++) {
1718 period = &g_array_index (attendee->busy_periods,
1719 EMeetingTimeSelectorPeriod,
1720 period_num);
1721
1722 /* These are just for debugging so don't need i18n. */
1723 g_date_strftime (
1724 buffer, sizeof (buffer),
1725 "%A, %B %d, %Y", &period->start.date);
1726 g_print (
1727 " Start: %s %i:%02i\n", buffer,
1728 period->start.hour, period->start.minute);
1729
1730 g_date_strftime (
1731 buffer, sizeof (buffer),
1732 "%A, %B %d, %Y", &period->end.date);
1733 g_print (
1734 " End : %s %i:%02i\n", buffer,
1735 period->end.hour, period->end.minute);
1736 }
1737 #endif
1738 }
1739
1740 }
1741
1742 /* This formats a EMeetingTimein a string and returns it.
1743 * Note that it uses a static buffer. */
1744 gchar *
1745 e_meeting_time_selector_dump_time (EMeetingTime *mtstime)
1746 {
1747 static gchar buffer[128];
1748
1749 gchar buffer2[128];
1750
1751 /* This is just for debugging so doesn't need i18n. */
1752 g_date_strftime (
1753 buffer, sizeof (buffer), "%A, %B %d, %Y",
1754 &mtstime->date);
1755 sprintf (
1756 buffer2, " at %i:%02i", (gint) mtstime->hour,
1757 (gint) mtstime->minute);
1758 strcat (buffer, buffer2);
1759
1760 return buffer;
1761 }
1762
1763 /* This formats a GDate in a string and returns it.
1764 * Note that it uses a static buffer. */
1765 gchar *
1766 e_meeting_time_selector_dump_date (GDate *date)
1767 {
1768 static gchar buffer[128];
1769
1770 /* This is just for debugging so doesn't need i18n. */
1771 g_date_strftime (buffer, sizeof (buffer), "%A, %B %d, %Y", date);
1772 return buffer;
1773 }
1774
1775 #endif /* E_MEETING_TIME_SELECTOR_DEBUG */
1776
1777 static void
1778 e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button,
1779 EMeetingTimeSelector *mts)
1780 {
1781 e_meeting_list_view_invite_others_dialog (mts->list_view);
1782 }
1783
1784 static void
1785 e_meeting_time_selector_on_options_button_clicked (GtkWidget *button,
1786 EMeetingTimeSelector *mts)
1787 {
1788 gtk_menu_popup (
1789 GTK_MENU (mts->options_menu), NULL, NULL,
1790 e_meeting_time_selector_options_menu_position_callback,
1791 mts, 1, GDK_CURRENT_TIME);
1792 }
1793
1794 static void
1795 e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu,
1796 gint *x,
1797 gint *y,
1798 gboolean *push_in,
1799 gpointer user_data)
1800 {
1801 EMeetingTimeSelector *mts;
1802 GtkRequisition menu_requisition;
1803 GtkAllocation allocation;
1804 GtkWidget *widget;
1805 GdkWindow *window;
1806 gint max_x, max_y;
1807
1808 mts = E_MEETING_TIME_SELECTOR (user_data);
1809
1810 /* Calculate our preferred position. */
1811 widget = mts->options_button;
1812 window = gtk_widget_get_window (widget);
1813 gdk_window_get_origin (window, x, y);
1814 gtk_widget_get_allocation (widget, &allocation);
1815 *x += allocation.x;
1816 *y += allocation.y + allocation.height - 2;
1817
1818 /* Now make sure we are on the screen. */
1819 gtk_widget_get_preferred_size (mts->options_menu, &menu_requisition, NULL);
1820 max_x = MAX (0, gdk_screen_width () - menu_requisition.width);
1821 max_y = MAX (0, gdk_screen_height () - menu_requisition.height);
1822 *x = CLAMP (*x, 0, max_x);
1823 *y = CLAMP (*y, 0, max_y);
1824 }
1825
1826 static void
1827 e_meeting_time_selector_on_update_free_busy (GtkWidget *button,
1828 EMeetingTimeSelector *mts)
1829 {
1830 /* Make sure the menu pops down, which doesn't happen by default if
1831 * keyboard accelerators are used. */
1832 if (gtk_widget_get_visible (mts->options_menu))
1833 gtk_menu_popdown (GTK_MENU (mts->options_menu));
1834
1835 e_meeting_time_selector_refresh_free_busy (mts, 0, TRUE);
1836 }
1837
1838 static void
1839 e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button,
1840 EMeetingTimeSelector *mts)
1841 {
1842 gtk_menu_popup (
1843 GTK_MENU (mts->autopick_menu), NULL, NULL,
1844 e_meeting_time_selector_autopick_menu_position_callback,
1845 mts, 1, GDK_CURRENT_TIME);
1846 }
1847
1848 static void
1849 e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu,
1850 gint *x,
1851 gint *y,
1852 gboolean *push_in,
1853 gpointer user_data)
1854 {
1855 EMeetingTimeSelector *mts;
1856 GtkRequisition menu_requisition;
1857 GtkAllocation allocation;
1858 GtkWidget *widget;
1859 GdkWindow *window;
1860 gint max_x, max_y;
1861
1862 mts = E_MEETING_TIME_SELECTOR (user_data);
1863
1864 /* Calculate our preferred position. */
1865 widget = mts->autopick_button;
1866 window = gtk_widget_get_window (widget);
1867 gdk_window_get_origin (window, x, y);
1868 gtk_widget_get_allocation (widget, &allocation);
1869 *x += allocation.x;
1870 *y += allocation.y + allocation.height - 2;
1871
1872 /* Now make sure we are on the screen. */
1873 gtk_widget_get_preferred_size (mts->autopick_menu, &menu_requisition, NULL);
1874 max_x = MAX (0, gdk_screen_width () - menu_requisition.width);
1875 max_y = MAX (0, gdk_screen_height () - menu_requisition.height);
1876 *x = CLAMP (*x, 0, max_x);
1877 *y = CLAMP (*y, 0, max_y);
1878 }
1879
1880 static void
1881 e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button,
1882 EMeetingTimeSelector *mts)
1883 {
1884 /* Make sure the menu pops down, which doesn't happen by default if
1885 * keyboard accelerators are used. */
1886 if (gtk_widget_get_visible (mts->autopick_menu))
1887 gtk_menu_popdown (GTK_MENU (mts->autopick_menu));
1888 }
1889
1890 static void
1891 e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button,
1892 EMeetingTimeSelector *mts)
1893 {
1894 e_meeting_time_selector_autopick (mts, FALSE);
1895 }
1896
1897 static void
1898 e_meeting_time_selector_on_next_button_clicked (GtkWidget *button,
1899 EMeetingTimeSelector *mts)
1900 {
1901 e_meeting_time_selector_autopick (mts, TRUE);
1902 }
1903
1904 /* This tries to find the previous or next meeting time for which all
1905 * attendees will be available. */
1906 static void
1907 e_meeting_time_selector_autopick (EMeetingTimeSelector *mts,
1908 gboolean forward)
1909 {
1910 EMeetingTime start_time, end_time, *resource_free;
1911 EMeetingAttendee *attendee;
1912 EMeetingFreeBusyPeriod *period;
1913 EMeetingTimeSelectorAutopickOption autopick_option;
1914 gint duration_days, duration_hours, duration_minutes, row;
1915 gboolean meeting_time_ok, skip_optional = FALSE;
1916 gboolean need_one_resource = FALSE, found_resource;
1917
1918 /* Get the current meeting duration in days + hours + minutes. */
1919 e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes);
1920
1921 /* Find the first appropriate start time. */
1922 start_time = mts->meeting_start_time;
1923 if (forward)
1924 e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
1925 else
1926 e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
1927
1928 /* Determine if we can skip optional people and if we only need one
1929 * resource based on the autopick option. */
1930 autopick_option = e_meeting_time_selector_get_autopick_option (mts);
1931 if (autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE
1932 || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE)
1933 skip_optional = TRUE;
1934 if (autopick_option == E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE
1935 || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE)
1936 need_one_resource = TRUE;
1937
1938 /* Keep moving forward or backward until we find a possible meeting
1939 * time. */
1940 for (;;) {
1941 meeting_time_ok = TRUE;
1942 found_resource = FALSE;
1943 resource_free = NULL;
1944
1945 /* Step through each attendee, checking if the meeting time
1946 * intersects one of the attendees busy periods. */
1947 for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++) {
1948 attendee = e_meeting_store_find_attendee_at_row (mts->model, row);
1949
1950 /* Skip optional people if they don't matter. */
1951 if (skip_optional && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_OPTIONAL_PERSON)
1952 continue;
1953
1954 period = e_meeting_time_selector_find_time_clash (mts, attendee, &start_time, &end_time);
1955
1956 if (need_one_resource && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_RESOURCE) {
1957 if (period) {
1958 /* We want to remember the closest
1959 * prev/next time that one resource is
1960 * available, in case we don't find any
1961 * free resources. */
1962 if (forward) {
1963 if (!resource_free || e_meeting_time_compare_times (resource_free, &period->end) > 0)
1964 resource_free = &period->end;
1965 } else {
1966 if (!resource_free || e_meeting_time_compare_times (resource_free, &period->start) < 0)
1967 resource_free = &period->start;
1968 }
1969
1970 } else {
1971 found_resource = TRUE;
1972 }
1973 } else if (period) {
1974 /* Skip the period which clashed. */
1975 if (forward) {
1976 start_time = period->end;
1977 } else {
1978 start_time = period->start;
1979 e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes);
1980 }
1981 meeting_time_ok = FALSE;
1982 break;
1983 }
1984 }
1985
1986 /* Check that we found one resource if necessary. If not, skip
1987 * to the closest time that a resource is free. Note that if
1988 * there are no resources, resource_free will never get set,
1989 * so we assume the meeting time is OK. */
1990 if (meeting_time_ok && need_one_resource && !found_resource
1991 && resource_free) {
1992 if (forward) {
1993 start_time = *resource_free;
1994 } else {
1995 start_time = *resource_free;
1996 e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes);
1997 }
1998 meeting_time_ok = FALSE;
1999 }
2000
2001 if (meeting_time_ok) {
2002 mts->meeting_start_time = start_time;
2003 mts->meeting_end_time = end_time;
2004 mts->meeting_positions_valid = FALSE;
2005 gtk_widget_queue_draw (mts->display_top);
2006 gtk_widget_queue_draw (mts->display_main);
2007
2008 /* Make sure the time is shown. */
2009 e_meeting_time_selector_ensure_meeting_time_shown (mts);
2010
2011 /* Set the times in the EDateEdit widgets. */
2012 e_meeting_time_selector_update_start_date_edit (mts);
2013 e_meeting_time_selector_update_end_date_edit (mts);
2014
2015 g_signal_emit (mts, signals[CHANGED], 0);
2016
2017 return;
2018 }
2019
2020 /* Move forward to the next possible interval. */
2021 if (forward)
2022 e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
2023 else
2024 e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
2025 }
2026 }
2027
2028 static void
2029 e_meeting_time_selector_calculate_time_difference (EMeetingTime *start,
2030 EMeetingTime *end,
2031 gint *days,
2032 gint *hours,
2033 gint *minutes)
2034 {
2035 *days = g_date_get_julian (&end->date) - g_date_get_julian (&start->date);
2036 *hours = end->hour - start->hour;
2037 *minutes = end->minute - start->minute;
2038 if (*minutes < 0) {
2039 *minutes += 60;
2040 *hours = *hours - 1;
2041 }
2042 if (*hours < 0) {
2043 *hours += 24;
2044 *days = *days - 1;
2045 }
2046 }
2047
2048 /* This moves the given time forward to the next suitable start of a meeting.
2049 * If zoomed_out is set, this means every hour. If not every half-hour. */
2050 static void
2051 e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts,
2052 EMeetingTime *start_time,
2053 EMeetingTime *end_time,
2054 gint days,
2055 gint hours,
2056 gint mins)
2057 {
2058 gint minutes_shown;
2059 gboolean set_to_start_of_working_day = FALSE;
2060
2061 if (!mts->all_day) {
2062 if (mts->zoomed_out) {
2063 start_time->hour++;
2064 start_time->minute = 0;
2065 } else {
2066 start_time->minute += 30;
2067 start_time->minute -= start_time->minute % 30;
2068 }
2069 } else {
2070 g_date_add_days (&start_time->date, 1);
2071 start_time->hour = 0;
2072 start_time->minute = 0;
2073 }
2074 e_meeting_time_selector_fix_time_overflows (start_time);
2075
2076 *end_time = *start_time;
2077 e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2078
2079 /* Check if the interval is less than a day as seen in the display.
2080 * If it isn't we don't worry about the working day. */
2081 if (!mts->working_hours_only || days > 0)
2082 return;
2083 minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60;
2084 minutes_shown += mts->day_end_minute - mts->day_start_minute;
2085 if (hours * 60 + mins > minutes_shown)
2086 return;
2087
2088 /* If the meeting time finishes past the end of the working day, move
2089 * onto the start of the next working day. If the meeting time starts
2090 * before the working day, move it on as well. */
2091 if (start_time->hour > mts->day_end_hour
2092 || (start_time->hour == mts->day_end_hour
2093 && start_time->minute > mts->day_end_minute)
2094 || end_time->hour > mts->day_end_hour
2095 || (end_time->hour == mts->day_end_hour
2096 && end_time->minute > mts->day_end_minute)) {
2097 g_date_add_days (&start_time->date, 1);
2098 set_to_start_of_working_day = TRUE;
2099 } else if (start_time->hour < mts->day_start_hour
2100 || (start_time->hour == mts->day_start_hour
2101 && start_time->minute < mts->day_start_minute)) {
2102 set_to_start_of_working_day = TRUE;
2103 }
2104
2105 if (set_to_start_of_working_day) {
2106 start_time->hour = mts->day_start_hour;
2107 start_time->minute = mts->day_start_minute;
2108
2109 if (mts->zoomed_out) {
2110 if (start_time->minute > 0) {
2111 start_time->hour++;
2112 start_time->minute = 0;
2113 }
2114 } else {
2115 start_time->minute += 29;
2116 start_time->minute -= start_time->minute % 30;
2117 }
2118
2119 e_meeting_time_selector_fix_time_overflows (start_time);
2120
2121 *end_time = *start_time;
2122 e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2123 }
2124 }
2125
2126 /* This moves the given time backward to the next suitable start of a meeting.
2127 * If zoomed_out is set, this means every hour. If not every half-hour. */
2128 static void
2129 e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts,
2130 EMeetingTime *start_time,
2131 EMeetingTime *end_time,
2132 gint days,
2133 gint hours,
2134 gint mins)
2135 {
2136 gint new_hour, minutes_shown;
2137 gboolean set_to_end_of_working_day = FALSE;
2138
2139 if (!mts->all_day) {
2140 new_hour = start_time->hour;
2141 if (mts->zoomed_out) {
2142 if (start_time->minute == 0)
2143 new_hour--;
2144 start_time->minute = 0;
2145 } else {
2146 if (start_time->minute == 0) {
2147 start_time->minute = 30;
2148 new_hour--;
2149 } else if (start_time->minute <= 30)
2150 start_time->minute = 0;
2151 else
2152 start_time->minute = 30;
2153 }
2154 if (new_hour < 0) {
2155 new_hour += 24;
2156 g_date_subtract_days (&start_time->date, 1);
2157 }
2158 start_time->hour = new_hour;
2159 } else {
2160 g_date_subtract_days (&start_time->date, 1);
2161 start_time->hour = 0;
2162 start_time->minute = 0;
2163 }
2164
2165 *end_time = *start_time;
2166 e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2167
2168 /* Check if the interval is less than a day as seen in the display.
2169 * If it isn't we don't worry about the working day. */
2170 if (!mts->working_hours_only || days > 0)
2171 return;
2172 minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60;
2173 minutes_shown += mts->day_end_minute - mts->day_start_minute;
2174 if (hours * 60 + mins > minutes_shown)
2175 return;
2176
2177 /* If the meeting time finishes past the end of the working day, move
2178 * back to the end of the working day. If the meeting time starts
2179 * before the working day, move it back to the end of the previous
2180 * working day. */
2181 if (start_time->hour > mts->day_end_hour
2182 || (start_time->hour == mts->day_end_hour
2183 && start_time->minute > mts->day_end_minute)
2184 || end_time->hour > mts->day_end_hour
2185 || (end_time->hour == mts->day_end_hour
2186 && end_time->minute > mts->day_end_minute)) {
2187 set_to_end_of_working_day = TRUE;
2188 } else if (start_time->hour < mts->day_start_hour
2189 || (start_time->hour == mts->day_start_hour
2190 && start_time->minute < mts->day_start_minute)) {
2191 g_date_subtract_days (&end_time->date, 1);
2192 set_to_end_of_working_day = TRUE;
2193 }
2194
2195 if (set_to_end_of_working_day) {
2196 end_time->hour = mts->day_end_hour;
2197 end_time->minute = mts->day_end_minute;
2198 *start_time = *end_time;
2199 e_meeting_time_selector_adjust_time (start_time, -days, -hours, -mins);
2200
2201 if (mts->zoomed_out) {
2202 start_time->minute = 0;
2203 } else {
2204 start_time->minute -= start_time->minute % 30;
2205 }
2206
2207 *end_time = *start_time;
2208 e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2209 }
2210 }
2211
2212 /* This adds on the given days, hours & minutes to a EMeetingTimeSelectorTime.
2213 * It is used to calculate the end of a period given a start & duration.
2214 * Days, hours & minutes can be negative, to move backwards, but they should
2215 * be within normal ranges, e.g. hours should be between -23 and 23. */
2216 static void
2217 e_meeting_time_selector_adjust_time (EMeetingTime *mtstime,
2218 gint days,
2219 gint hours,
2220 gint minutes)
2221 {
2222 gint new_hours, new_minutes;
2223
2224 /* We have to handle negative values for hous and minutes here, since
2225 * EMeetingTimeuses guint8s to store them. */
2226 new_minutes = mtstime->minute + minutes;
2227 if (new_minutes < 0) {
2228 new_minutes += 60;
2229 hours -= 1;
2230 }
2231
2232 new_hours = mtstime->hour + hours;
2233 if (new_hours < 0) {
2234 new_hours += 24;
2235 days -= 1;
2236 }
2237
2238 g_date_add_days (&mtstime->date, days);
2239 mtstime->hour = new_hours;
2240 mtstime->minute = new_minutes;
2241
2242 e_meeting_time_selector_fix_time_overflows (mtstime);
2243 }
2244
2245 /* This looks for any busy period of the given attendee which clashes with
2246 * the start and end time. It uses a binary search. */
2247 static EMeetingFreeBusyPeriod *
2248 e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
2249 EMeetingAttendee *attendee,
2250 EMeetingTime *start_time,
2251 EMeetingTime *end_time)
2252 {
2253 EMeetingFreeBusyPeriod *period;
2254 const GArray *busy_periods;
2255 gint period_num;
2256
2257 busy_periods = e_meeting_attendee_get_busy_periods (attendee);
2258 period_num = e_meeting_attendee_find_first_busy_period (attendee, &start_time->date);
2259
2260 if (period_num == -1)
2261 return NULL;
2262
2263 /* Step forward through the busy periods until we find a clash or we
2264 * go past the end_time. */
2265 while (period_num < busy_periods->len) {
2266 period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num);
2267
2268 /* If the period starts at or after the end time, there is no
2269 * clash and we are finished. The busy periods are sorted by
2270 * their start times, so all the rest will be later. */
2271 if (e_meeting_time_compare_times (&period->start, end_time) >= 0)
2272 return NULL;
2273
2274 /* If the period ends after the start time, we have found a
2275 * clash. From the above test we already know the busy period
2276 * isn't completely after the meeting time. */
2277 if (e_meeting_time_compare_times (&period->end, start_time) > 0)
2278 return period;
2279
2280 period_num++;
2281 }
2282
2283 return NULL;
2284 }
2285
2286 static void
2287 e_meeting_time_selector_on_zoomed_out_toggled (GtkCheckMenuItem *menuitem,
2288 EMeetingTimeSelector *mts)
2289 {
2290 gboolean active;
2291
2292 /* Make sure the menu pops down, which doesn't happen by default if
2293 * keyboard accelerators are used. */
2294 if (gtk_widget_get_visible (mts->options_menu))
2295 gtk_menu_popdown (GTK_MENU (mts->options_menu));
2296
2297 active = gtk_check_menu_item_get_active (menuitem);
2298 e_meeting_time_selector_set_zoomed_out (mts, active);
2299 e_meeting_time_selector_ensure_meeting_time_shown (mts);
2300 }
2301
2302 static void
2303 e_meeting_time_selector_on_working_hours_toggled (GtkCheckMenuItem *menuitem,
2304 EMeetingTimeSelector *mts)
2305 {
2306 gboolean active;
2307
2308 /* Make sure the menu pops down, which doesn't happen by default if
2309 * keyboard accelerators are used. */
2310 if (gtk_widget_get_visible (mts->options_menu))
2311 gtk_menu_popdown (GTK_MENU (mts->options_menu));
2312
2313 active = gtk_check_menu_item_get_active (menuitem);
2314 e_meeting_time_selector_set_working_hours_only (mts, active);
2315 e_meeting_time_selector_ensure_meeting_time_shown (mts);
2316 }
2317
2318 /* This recalculates day_width, first_hour_shown and last_hour_shown. */
2319 static void
2320 e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts)
2321 {
2322 if (mts->working_hours_only) {
2323 mts->first_hour_shown = mts->day_start_hour;
2324 mts->last_hour_shown = mts->day_end_hour;
2325 if (mts->day_end_minute != 0)
2326 mts->last_hour_shown += 1;
2327 } else {
2328 mts->first_hour_shown = 0;
2329 mts->last_hour_shown = 24;
2330 }
2331
2332 /* In the brief view we use the nearest hours divisible by 3. */
2333 if (mts->zoomed_out) {
2334 mts->first_hour_shown -= mts->first_hour_shown % 3;
2335 mts->last_hour_shown += 2;
2336 mts->last_hour_shown -= mts->last_hour_shown % 3;
2337 }
2338
2339 mts->day_width = mts->col_width * (mts->last_hour_shown - mts->first_hour_shown);
2340 if (mts->zoomed_out)
2341 mts->day_width /= 3;
2342
2343 /* Add one pixel for the extra vertical grid line. */
2344 mts->day_width++;
2345
2346 gnome_canvas_set_scroll_region (
2347 GNOME_CANVAS (mts->display_top),
2348 0, 0,
2349 mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
2350 mts->row_height * 3);
2351 e_meeting_time_selector_update_main_canvas_scroll_region (mts);
2352
2353 e_meeting_time_selector_recalc_date_format (mts);
2354 mts->meeting_positions_valid = FALSE;
2355 }
2356
2357 /* This saves the first visible time in the given EMeetingTimeSelectorTime. */
2358 static void
2359 e_meeting_time_selector_save_position (EMeetingTimeSelector *mts,
2360 EMeetingTime *mtstime)
2361 {
2362 gint scroll_x, scroll_y;
2363
2364 gnome_canvas_get_scroll_offsets (
2365 GNOME_CANVAS (mts->display_main),
2366 &scroll_x, &scroll_y);
2367 e_meeting_time_selector_calculate_time (mts, scroll_x, mtstime);
2368 }
2369
2370 /* This restores a saved position. */
2371 static void
2372 e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts,
2373 EMeetingTime *mtstime)
2374 {
2375 gint scroll_x, scroll_y, new_scroll_x;
2376
2377 new_scroll_x = e_meeting_time_selector_calculate_time_position (
2378 mts, mtstime);
2379 gnome_canvas_get_scroll_offsets (
2380 GNOME_CANVAS (mts->display_main),
2381 &scroll_x, &scroll_y);
2382 gnome_canvas_scroll_to (
2383 GNOME_CANVAS (mts->display_main),
2384 new_scroll_x, scroll_y);
2385 }
2386
2387 /* This returns the x pixel coords of the meeting time in the entire scroll
2388 * region. It recalculates them if they have been marked as invalid.
2389 * If it returns FALSE then no meeting time is set or the meeting time is
2390 * not visible in the current scroll area. */
2391 gboolean
2392 e_meeting_time_selector_get_meeting_time_positions (EMeetingTimeSelector *mts,
2393 gint *start_x,
2394 gint *end_x)
2395 {
2396 if (mts->meeting_positions_valid) {
2397 if (mts->meeting_positions_in_scroll_area) {
2398 *start_x = mts->meeting_start_x;
2399 *end_x = mts->meeting_end_x;
2400 return TRUE;
2401 } else {
2402 return FALSE;
2403 }
2404 }
2405
2406 mts->meeting_positions_valid = TRUE;
2407
2408 /* Check if the days aren't in our current range. */
2409 if (g_date_compare (&mts->meeting_start_time.date, &mts->last_date_shown) > 0
2410 || g_date_compare (&mts->meeting_end_time.date, &mts->first_date_shown) < 0) {
2411 mts->meeting_positions_in_scroll_area = FALSE;
2412 return FALSE;
2413 }
2414
2415 mts->meeting_positions_in_scroll_area = TRUE;
2416 *start_x = mts->meeting_start_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_start_time);
2417 *end_x = mts->meeting_end_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_end_time);
2418
2419 return TRUE;
2420 }
2421
2422 /* This recalculates the date format to used, by computing the width of the
2423 * longest date strings in the widget's font and seeing if they fit. */
2424 static void
2425 e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts)
2426 {
2427 /* An array of dates, one for each month in the year 2000. They must
2428 * all be Sundays. */
2429 static const gint days[12] = { 23, 20, 19, 23, 21, 18,
2430 23, 20, 17, 22, 19, 24 };
2431 GDate date;
2432 gint max_date_width, longest_weekday_width, longest_month_width, width;
2433 gint day, longest_weekday, month, longest_month;
2434 gchar buffer[128], *str;
2435 const gchar *name;
2436 PangoContext *pango_context;
2437 PangoLayout *layout;
2438 struct tm tm_time;
2439
2440 /* Set up Pango prerequisites */
2441 pango_context = gtk_widget_get_pango_context (GTK_WIDGET (mts));
2442 layout = pango_layout_new (pango_context);
2443
2444 /* Calculate the maximum date width we can fit into the display. */
2445 max_date_width = mts->day_width - 2;
2446
2447 /* Find the biggest full weekday name. We start on a particular
2448 * Monday and go through seven days. */
2449 g_date_clear (&date, 1);
2450 g_date_set_dmy (&date, 3, 1, 2000); /* Monday 3rd Jan 2000. */
2451 longest_weekday_width = 0;
2452 longest_weekday = G_DATE_MONDAY;
2453 for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) {
2454 name = e_get_weekday_name (day, FALSE);
2455 pango_layout_set_text (layout, name, -1);
2456 pango_layout_get_pixel_size (layout, &width, NULL);
2457 if (width > longest_weekday_width) {
2458 longest_weekday = day;
2459 longest_weekday_width = width;
2460 }
2461 }
2462
2463 /* Now find the biggest month name. */
2464 longest_month_width = 0;
2465 longest_month = G_DATE_JANUARY;
2466 for (month = G_DATE_JANUARY; month <= G_DATE_DECEMBER; month++) {
2467 name = e_get_month_name (month, FALSE);
2468 pango_layout_set_text (layout, name, -1);
2469 pango_layout_get_pixel_size (layout, &width, NULL);
2470 if (width > longest_month_width) {
2471 longest_month = month;
2472 longest_month_width = width;
2473 }
2474 }
2475
2476 /* Now try it with abbreviated weekday names. */
2477 longest_weekday_width = 0;
2478 longest_weekday = G_DATE_MONDAY;
2479 for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) {
2480 name = e_get_weekday_name (day, TRUE);
2481 pango_layout_set_text (layout, name, -1);
2482 pango_layout_get_pixel_size (layout, &width, NULL);
2483 if (width > longest_weekday_width) {
2484 longest_weekday = day;
2485 longest_weekday_width = width;
2486 }
2487 }
2488
2489 g_date_set_dmy (
2490 &date, days[longest_month - 1] + longest_weekday,
2491 longest_month, 2000);
2492
2493 g_date_to_struct_tm (&date, &tm_time);
2494 str = e_datetime_format_format_tm ("calendar", "table", DTFormatKindDate, &tm_time);
2495
2496 g_return_if_fail (str != NULL);
2497
2498 if (!e_datetime_format_includes_day_name ("calendar", "table", DTFormatKindDate)) {
2499 gchar *tmp;
2500
2501 g_date_strftime (buffer, sizeof (buffer), "%a", &date);
2502
2503 tmp = str;
2504 str = g_strconcat (buffer, " ", str, NULL);
2505 g_free (tmp);
2506 }
2507
2508 #if 0
2509 g_print (
2510 "longest_month: %i longest_weekday: %i date: %s\n",
2511 longest_month, longest_weekday, str);
2512 #endif
2513
2514 pango_layout_set_text (layout, str, -1);
2515 pango_layout_get_pixel_size (layout, &width, NULL);
2516 if (width < max_date_width)
2517 mts->date_format = E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY;
2518 else
2519 mts->date_format = E_MEETING_TIME_SELECTOR_DATE_SHORT;
2520
2521 g_object_unref (layout);
2522 g_free (str);
2523 }
2524
2525 /* Turn off the background of the canvas windows. This reduces flicker
2526 * considerably when scrolling. (Why isn't it in GnomeCanvas?). */
2527 static void
2528 e_meeting_time_selector_on_canvas_realized (GtkWidget *widget,
2529 EMeetingTimeSelector *mts)
2530 {
2531 GdkWindow *window;
2532
2533 window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
2534 gdk_window_set_background_pattern (window, NULL);
2535 }
2536
2537 /* This is called when the meeting start time GnomeDateEdit is changed,
2538 * either via the "date_changed". "time_changed" or "activate" signals on one
2539 * of the GtkEntry widgets. So don't use the widget parameter since it may be
2540 * one of the child GtkEntry widgets. */
2541 static void
2542 e_meeting_time_selector_on_start_time_changed (GtkWidget *widget,
2543 EMeetingTimeSelector *mts)
2544 {
2545 gint duration_days, duration_hours, duration_minutes;
2546 EMeetingTime mtstime;
2547 gint hour = 0, minute = 0;
2548 time_t newtime;
2549
2550 /* Date */
2551 newtime = e_date_edit_get_time (E_DATE_EDIT (mts->start_date_edit));
2552 g_date_clear (&mtstime.date, 1);
2553 g_date_set_time_t (&mtstime.date, newtime);
2554
2555 /* Time */
2556 e_date_edit_get_time_of_day (E_DATE_EDIT (mts->start_date_edit), &hour, &minute);
2557 mtstime.hour = hour;
2558 mtstime.minute = minute;
2559
2560 /* If the time hasn't changed, just return. */
2561 if (e_meeting_time_compare_times (&mtstime, &mts->meeting_start_time) == 0)
2562 return;
2563
2564 /* Calculate the current meeting duration. */
2565 e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes);
2566
2567 /* Set the new start time. */
2568 mts->meeting_start_time = mtstime;
2569
2570 /* Update the end time so the meeting duration stays the same. */
2571 mts->meeting_end_time = mts->meeting_start_time;
2572 e_meeting_time_selector_adjust_time (&mts->meeting_end_time, duration_days, duration_hours, duration_minutes);
2573 e_meeting_time_selector_update_end_date_edit (mts);
2574
2575 mts->meeting_positions_valid = FALSE;
2576 e_meeting_time_selector_ensure_meeting_time_shown (mts);
2577 gtk_widget_queue_draw (mts->display_top);
2578 gtk_widget_queue_draw (mts->display_main);
2579
2580 g_signal_emit (mts, signals[CHANGED], 0);
2581 }
2582
2583 /* This is called when the meeting end time GnomeDateEdit is changed,
2584 * either via the "date_changed", "time_changed" or "activate" signals on one
2585 * of the GtkEntry widgets. So don't use the widget parameter since it may be
2586 * one of the child GtkEntry widgets. */
2587 static void
2588 e_meeting_time_selector_on_end_time_changed (GtkWidget *widget,
2589 EMeetingTimeSelector *mts)
2590 {
2591 EMeetingTime mtstime;
2592 gint hour = 0, minute = 0;
2593 time_t newtime;
2594
2595 /* Date */
2596 newtime = e_date_edit_get_time (E_DATE_EDIT (mts->end_date_edit));
2597 g_date_clear (&mtstime.date, 1);
2598 g_date_set_time_t (&mtstime.date, newtime);
2599 if (mts->all_day)
2600 g_date_add_days (&mtstime.date, 1);
2601
2602 /* Time */
2603 e_date_edit_get_time_of_day (E_DATE_EDIT (mts->end_date_edit), &hour, &minute);
2604 mtstime.hour = hour;
2605 mtstime.minute = minute;
2606
2607 /* If the time hasn't changed, just return. */
2608 if (e_meeting_time_compare_times (&mtstime, &mts->meeting_end_time) == 0)
2609 return;
2610
2611 /* Set the new end time. */
2612 mts->meeting_end_time = mtstime;
2613
2614 /* If the start time is after the end time, set it to the same time. */
2615 if (e_meeting_time_compare_times (&mtstime, &mts->meeting_start_time) <= 0) {
2616 /* We set it first, before updating the widget, so the signal
2617 * handler will just return. */
2618 mts->meeting_start_time = mtstime;
2619 if (mts->all_day)
2620 g_date_subtract_days (&mts->meeting_start_time.date, 1);
2621 e_meeting_time_selector_update_start_date_edit (mts);
2622 }
2623
2624 mts->meeting_positions_valid = FALSE;
2625 e_meeting_time_selector_ensure_meeting_time_shown (mts);
2626 gtk_widget_queue_draw (mts->display_top);
2627 gtk_widget_queue_draw (mts->display_main);
2628
2629 g_signal_emit (mts, signals[CHANGED], 0);
2630 }
2631
2632 /* This updates the ranges shown in the GnomeDateEdit popup menus, according
2633 * to working_hours_only etc. */
2634 static void
2635 e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts)
2636 {
2637 EDateEdit *start_edit, *end_edit;
2638 gint low_hour, high_hour;
2639
2640 start_edit = E_DATE_EDIT (mts->start_date_edit);
2641 end_edit = E_DATE_EDIT (mts->end_date_edit);
2642
2643 if (mts->working_hours_only) {
2644 low_hour = mts->day_start_hour;
2645 high_hour = mts->day_end_hour;
2646 } else {
2647 low_hour = 0;
2648 high_hour = 23;
2649 }
2650
2651 e_date_edit_set_time_popup_range (start_edit, low_hour, high_hour);
2652 e_date_edit_set_time_popup_range (end_edit, low_hour, high_hour);
2653 }
2654
2655 static void
2656 e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget,
2657 GtkAllocation *allocation,
2658 EMeetingTimeSelector *mts)
2659 {
2660 e_meeting_time_selector_update_main_canvas_scroll_region (mts);
2661
2662 e_meeting_time_selector_ensure_meeting_time_shown (mts);
2663 }
2664
2665 static gboolean
2666 e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget,
2667 GdkEventScroll *event,
2668 EMeetingTimeSelector *mts)
2669 {
2670 gboolean return_val = FALSE;
2671
2672 /* escalate to the list view's parent, which is a scrolled window */
2673 g_signal_emit_by_name (gtk_widget_get_parent (GTK_WIDGET (mts->list_view)), "scroll-event", event, &return_val);
2674
2675 return return_val;
2676 }
2677
2678 /* Sets a tooltip for the busy periods canvas. If the mouse pointer
2679 * hovers over a busy period for which extended free/busy (XFB) data
2680 * could be extracted from the vfreebusy calendar object, the tooltip
2681 * will be shown (currently displays the summary and the location of
2682 * for the busy period, if available). See EMeetingXfbData for a reference.
2683 *
2684 */
2685 static gboolean
2686 e_meeting_time_selector_on_canvas_query_tooltip (GtkWidget *widget,
2687 gint x,
2688 gint y,
2689 gboolean keyboard_mode,
2690 GtkTooltip *tooltip,
2691 gpointer user_data)
2692 {
2693 EMeetingTimeSelector *mts = NULL;
2694 EMeetingAttendee *attendee = NULL;
2695 EMeetingFreeBusyPeriod *period = NULL;
2696 EMeetingXfbData *xfb = NULL;
2697 GtkScrollable *scrollable = NULL;
2698 GtkAdjustment *adjustment = NULL;
2699 const GArray *periods = NULL;
2700 gint scroll_x = 0;
2701 gint scroll_y = 0;
2702 gint mouse_x = 0;
2703 gint row = 0;
2704 gint first_idx = 0;
2705 gint ii = 0;
2706 gchar *tt_text = NULL;
2707
2708 g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE);
2709 g_return_val_if_fail (GTK_IS_TOOLTIP (tooltip), FALSE);
2710 g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (user_data), FALSE);
2711
2712 mts = E_MEETING_TIME_SELECTOR (user_data);
2713
2714 scrollable = GTK_SCROLLABLE (widget);
2715 adjustment = gtk_scrollable_get_hadjustment (scrollable);
2716 scroll_x = (gint) gtk_adjustment_get_value (adjustment);
2717 adjustment = gtk_scrollable_get_vadjustment (scrollable);
2718 scroll_y = (gint) gtk_adjustment_get_value (adjustment);
2719
2720 /* calculate the attendee index (row) we're at */
2721 row = (scroll_y + y) / mts->row_height;
2722
2723 /* no tooltip if we have no attendee in the row */
2724 if (row > e_meeting_store_count_actual_attendees (mts->model) - 1)
2725 return FALSE;
2726
2727 /* no tooltip if attendee has no calendar info */
2728 attendee = e_meeting_store_find_attendee_at_row (mts->model, row);
2729 g_return_val_if_fail (E_IS_MEETING_ATTENDEE (attendee), FALSE);
2730 if (!e_meeting_attendee_get_has_calendar_info (attendee))
2731 return FALSE;
2732
2733 /* get the attendee's busy times array */
2734 periods = e_meeting_attendee_get_busy_periods (attendee);
2735 g_return_val_if_fail (periods != NULL, FALSE);
2736 g_return_val_if_fail (periods->len > 0, FALSE);
2737
2738 /* no tooltip if no busy period reaches into the current canvas area */
2739 first_idx = e_meeting_attendee_find_first_busy_period (attendee,
2740 &(mts->first_date_shown));
2741 if (first_idx < 0)
2742 return FALSE;
2743
2744 /* calculate the mouse tip x position in the canvas area */
2745 mouse_x = x + scroll_x;
2746
2747 /* find out whether mouse_x lies inside a busy
2748 * period (we start with the index of the first
2749 * one reaching into the current canvas area)
2750 */
2751 for (ii = first_idx; ii < periods->len; ii++) {
2752 EMeetingFreeBusyPeriod *p = NULL;
2753 gint sx = 0;
2754 gint ex = 0;
2755
2756 p = &(g_array_index (periods,
2757 EMeetingFreeBusyPeriod,
2758 ii));
2759 /* meeting start time x position */
2760 sx = e_meeting_time_selector_calculate_time_position (mts,
2761 &(p->start));
2762 /* meeting end time x position */
2763 ex = e_meeting_time_selector_calculate_time_position (mts,
2764 &(p->end));
2765 if ((mouse_x >= sx) && (mouse_x <= ex)) {
2766 /* found busy period the mouse tip is over */
2767 period = p;
2768 break;
2769 }
2770 }
2771
2772 /* no tooltip if we did not find a busy period under
2773 * the mouse pointer
2774 */
2775 if (period == NULL)
2776 return FALSE;
2777
2778 /* get the extended free/busy data
2779 * (no tooltip if none available)
2780 */
2781 xfb = &(period->xfb);
2782 if ((xfb->summary == NULL) && (xfb->location == NULL))
2783 return FALSE;
2784
2785 /* Create the tooltip text. The data sent by the server will
2786 * have been validated for UTF-8 conformance (and possibly
2787 * forced into) as well as length-limited by a call to the
2788 * e_meeting_xfb_utf8_string_new_from_ical() function in
2789 * process_free_busy_comp_get_xfb() (e-meeting-store.c)
2790 */
2791 tt_text = g_strdup_printf ("%s\n\n%s",
2792 (xfb->summary == NULL) ? "" : xfb->summary,
2793 (xfb->location == NULL) ? "" : xfb->location);
2794
2795 /* set XFB information as tooltip text */
2796 gtk_tooltip_set_text (tooltip, tt_text);
2797 g_free (tt_text);
2798
2799 return TRUE;
2800 }
2801
2802 /* This updates the canvas scroll regions according to the number of attendees.
2803 * If the total height needed is less than the height of the canvas, we must
2804 * use the height of the canvas, or it causes problems. */
2805 static void
2806 e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts)
2807 {
2808 GtkAllocation allocation;
2809 gint height;
2810
2811 gtk_widget_get_allocation (mts->display_main, &allocation);
2812 height = mts->row_height * (e_meeting_store_count_actual_attendees (mts->model) + 2);
2813 height = MAX (height, allocation.height);
2814
2815 gnome_canvas_set_scroll_region (
2816 GNOME_CANVAS (mts->display_main),
2817 0, 0,
2818 mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
2819 height);
2820 }
2821
2822 /* This changes the meeting time based on the given x coordinate and whether
2823 * we are dragging the start or end bar. It returns the new position, which
2824 * will be swapped if the start bar is dragged past the end bar or vice versa.
2825 * It make sure the meeting time is never dragged outside the visible canvas
2826 * area. */
2827 void
2828 e_meeting_time_selector_drag_meeting_time (EMeetingTimeSelector *mts,
2829 gint x)
2830 {
2831 EMeetingTime first_time, last_time, drag_time, *time_to_set;
2832 gint scroll_x, scroll_y, canvas_width;
2833 gboolean set_both_times = FALSE;
2834 GtkAllocation allocation;
2835
2836 /* Get the x coords of visible part of the canvas. */
2837 gnome_canvas_get_scroll_offsets (
2838 GNOME_CANVAS (mts->display_main),
2839 &scroll_x, &scroll_y);
2840 gtk_widget_get_allocation (mts->display_main, &allocation);
2841 canvas_width = allocation.width;
2842
2843 /* Save the x coordinate for the timeout handler. */
2844 mts->last_drag_x = (x < scroll_x) ? x - scroll_x
2845 : x - scroll_x - canvas_width + 1;
2846
2847 /* Check if the mouse is off the edge of the canvas. */
2848 if (x < scroll_x || x > scroll_x + canvas_width) {
2849 /* If we haven't added a timeout function, add one. */
2850 if (mts->auto_scroll_timeout_id == 0) {
2851 mts->auto_scroll_timeout_id = g_timeout_add (60, e_meeting_time_selector_timeout_handler, mts);
2852 mts->scroll_count = 0;
2853
2854 /* Call the handler to start scrolling now. */
2855 e_meeting_time_selector_timeout_handler (mts);
2856 return;
2857 }
2858 } else {
2859 e_meeting_time_selector_remove_timeout (mts);
2860 }
2861
2862 /* Calculate the minimum & maximum times we can use, based on the
2863 * scroll offsets and whether zoomed_out is set. */
2864 e_meeting_time_selector_calculate_time (mts, scroll_x, &first_time);
2865 e_meeting_time_selector_calculate_time (
2866 mts, scroll_x + canvas_width - 1, &last_time);
2867 if (!mts->all_day) {
2868 if (mts->zoomed_out) {
2869 if (first_time.minute > 30)
2870 first_time.hour++;
2871 first_time.minute = 0;
2872 last_time.minute = 0;
2873 } else {
2874 first_time.minute += 15;
2875 first_time.minute -= first_time.minute % 30;
2876 last_time.minute -= last_time.minute % 30;
2877 }
2878 } else {
2879 if (first_time.hour > 0 || first_time.minute > 0)
2880 g_date_add_days (&first_time.date, 1);
2881 first_time.hour = 0;
2882 first_time.minute = 0;
2883 last_time.hour = 0;
2884 last_time.minute = 0;
2885 }
2886 e_meeting_time_selector_fix_time_overflows (&first_time);
2887 e_meeting_time_selector_fix_time_overflows (&last_time);
2888
2889 /* Calculate the time from x coordinate. */
2890 e_meeting_time_selector_calculate_time (mts, x, &drag_time);
2891
2892 /* Calculate the nearest half-hour or hour, depending on whether
2893 * zoomed_out is set. */
2894 if (!mts->all_day) {
2895 if (mts->zoomed_out) {
2896 if (drag_time.minute > 30)
2897 drag_time.hour++;
2898 drag_time.minute = 0;
2899 } else {
2900 drag_time.minute += 15;
2901 drag_time.minute -= drag_time.minute % 30;
2902 }
2903 } else {
2904 if (drag_time.hour > 12)
2905 g_date_add_days (&drag_time.date, 1);
2906 drag_time.hour = 0;
2907 drag_time.minute = 0;
2908 }
2909 e_meeting_time_selector_fix_time_overflows (&drag_time);
2910
2911 /* Now make sure we are between first_time & last_time. */
2912 if (e_meeting_time_compare_times (&drag_time, &first_time) < 0)
2913 drag_time = first_time;
2914 if (e_meeting_time_compare_times (&drag_time, &last_time) > 0)
2915 drag_time = last_time;
2916
2917 /* Set the meeting start or end time to drag_time. */
2918 if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2919 time_to_set = &mts->meeting_start_time;
2920 else
2921 time_to_set = &mts->meeting_end_time;
2922
2923 /* If the time is unchanged, just return. */
2924 if (e_meeting_time_compare_times (time_to_set, &drag_time) == 0)
2925 return;
2926
2927 /* Don't let an empty occur for all day events */
2928 if (mts->all_day
2929 && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START
2930 && e_meeting_time_compare_times (&mts->meeting_end_time, &drag_time) == 0)
2931 return;
2932 else if (mts->all_day
2933 && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
2934 && e_meeting_time_compare_times (&mts->meeting_start_time, &drag_time) == 0)
2935 return;
2936
2937 *time_to_set = drag_time;
2938
2939 /* Check if the start time and end time need to be switched. */
2940 if (e_meeting_time_compare_times (&mts->meeting_start_time,
2941 &mts->meeting_end_time) > 0) {
2942 drag_time = mts->meeting_start_time;
2943 mts->meeting_start_time = mts->meeting_end_time;
2944 mts->meeting_end_time = drag_time;
2945
2946 if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2947 mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END;
2948 else
2949 mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START;
2950
2951 set_both_times = TRUE;
2952 }
2953
2954 /* Mark the calculated positions as invalid. */
2955 mts->meeting_positions_valid = FALSE;
2956
2957 /* Redraw the canvases. */
2958 gtk_widget_queue_draw (mts->display_top);
2959 gtk_widget_queue_draw (mts->display_main);
2960
2961 /* Set the times in the GnomeDateEdit widgets. */
2962 if (set_both_times
2963 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2964 e_meeting_time_selector_update_start_date_edit (mts);
2965
2966 if (set_both_times
2967 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END)
2968 e_meeting_time_selector_update_end_date_edit (mts);
2969
2970 if (set_both_times
2971 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
2972 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2973 g_signal_emit (mts, signals[CHANGED], 0);
2974 }
2975
2976 /* This is the timeout function which handles auto-scrolling when the user is
2977 * dragging one of the meeting time vertical bars outside the left or right
2978 * edge of the canvas. */
2979 static gboolean
2980 e_meeting_time_selector_timeout_handler (gpointer data)
2981 {
2982 EMeetingTimeSelector *mts;
2983 EMeetingTime drag_time, *time_to_set;
2984 gint scroll_x, max_scroll_x, scroll_y, canvas_width;
2985 gint scroll_speed, scroll_offset;
2986 gboolean set_both_times = FALSE;
2987 GtkAllocation allocation;
2988
2989 mts = E_MEETING_TIME_SELECTOR (data);
2990
2991 /* Return if we don't need to scroll yet. */
2992 if (mts->scroll_count-- > 0)
2993 return TRUE;
2994
2995 /* Get the x coords of visible part of the canvas. */
2996 gnome_canvas_get_scroll_offsets (
2997 GNOME_CANVAS (mts->display_main),
2998 &scroll_x, &scroll_y);
2999 gtk_widget_get_allocation (mts->display_main, &allocation);
3000 canvas_width = allocation.width;
3001
3002 /* Calculate the scroll delay, between 0 and MAX_SCROLL_SPEED. */
3003 scroll_speed = abs (mts->last_drag_x / E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH);
3004 scroll_speed = MIN (
3005 scroll_speed,
3006 E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED);
3007
3008 /* Reset the scroll count. */
3009 mts->scroll_count = E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED - scroll_speed;
3010
3011 /* Calculate how much we need to scroll. */
3012 if (mts->last_drag_x >= 0)
3013 scroll_offset = mts->col_width;
3014 else
3015 scroll_offset = -mts->col_width;
3016
3017 scroll_x += scroll_offset;
3018 max_scroll_x = (mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN)
3019 - canvas_width;
3020 scroll_x = CLAMP (scroll_x, 0, max_scroll_x);
3021
3022 /* Calculate the minimum or maximum visible time in the canvas, which
3023 * we will now set the dragged time to. */
3024 if (scroll_offset > 0) {
3025 e_meeting_time_selector_calculate_time (
3026 mts, scroll_x + canvas_width - 1, &drag_time);
3027 if (!mts->all_day) {
3028 if (mts->zoomed_out) {
3029 drag_time.minute = 0;
3030 } else {
3031 drag_time.minute -= drag_time.minute % 30;
3032 }
3033 } else {
3034 drag_time.hour = 0;
3035 drag_time.minute = 0;
3036 }
3037 } else {
3038 e_meeting_time_selector_calculate_time (
3039 mts, scroll_x, &drag_time);
3040 if (!mts->all_day) {
3041 if (mts->zoomed_out) {
3042 if (drag_time.minute > 30)
3043 drag_time.hour++;
3044 drag_time.minute = 0;
3045 } else {
3046 drag_time.minute += 15;
3047 drag_time.minute -= drag_time.minute % 30;
3048 }
3049 } else {
3050 if (drag_time.hour > 0 || drag_time.minute > 0)
3051 g_date_add_days (&drag_time.date, 1);
3052 drag_time.hour = 0;
3053 drag_time.minute = 0;
3054 }
3055 }
3056 e_meeting_time_selector_fix_time_overflows (&drag_time);
3057
3058 /* Set the meeting start or end time to drag_time. */
3059 if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3060 time_to_set = &mts->meeting_start_time;
3061 else
3062 time_to_set = &mts->meeting_end_time;
3063
3064 /* If the time is unchanged, just return. */
3065 if (e_meeting_time_compare_times (time_to_set, &drag_time) == 0)
3066 goto scroll;
3067
3068 /* Don't let an empty occur for all day events */
3069 if (mts->all_day
3070 && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START
3071 && e_meeting_time_compare_times (&mts->meeting_end_time, &drag_time) == 0)
3072 goto scroll;
3073 else if (mts->all_day
3074 && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
3075 && e_meeting_time_compare_times (&mts->meeting_start_time, &drag_time) == 0)
3076 goto scroll;
3077
3078 *time_to_set = drag_time;
3079
3080 /* Check if the start time and end time need to be switched. */
3081 if (e_meeting_time_compare_times (&mts->meeting_start_time, &mts->meeting_end_time) > 0) {
3082 drag_time = mts->meeting_start_time;
3083 mts->meeting_start_time = mts->meeting_end_time;
3084 mts->meeting_end_time = drag_time;
3085
3086 if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3087 mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END;
3088 else
3089 mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START;
3090
3091 set_both_times = TRUE;
3092 }
3093
3094 /* Mark the calculated positions as invalid. */
3095 mts->meeting_positions_valid = FALSE;
3096
3097 /* Set the times in the GnomeDateEdit widgets. */
3098 if (set_both_times
3099 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3100 e_meeting_time_selector_update_start_date_edit (mts);
3101
3102 if (set_both_times
3103 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END)
3104 e_meeting_time_selector_update_end_date_edit (mts);
3105
3106 if (set_both_times
3107 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
3108 || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3109 g_signal_emit (mts, signals[CHANGED], 0);
3110
3111 scroll:
3112 /* Redraw the canvases. We freeze and thaw the layouts so that they
3113 * get redrawn completely. Otherwise the pixels get scrolled left or
3114 * right which is not good for us (since our vertical bars have been
3115 * moved) and causes flicker. */
3116 gnome_canvas_scroll_to (
3117 GNOME_CANVAS (mts->display_main),
3118 scroll_x, scroll_y);
3119 gnome_canvas_scroll_to (
3120 GNOME_CANVAS (mts->display_top),
3121 scroll_x, scroll_y);
3122
3123 return TRUE;
3124 }
3125
3126 /* This removes our auto-scroll timeout function, if we have one installed. */
3127 void
3128 e_meeting_time_selector_remove_timeout (EMeetingTimeSelector *mts)
3129 {
3130 if (mts->auto_scroll_timeout_id) {
3131 g_source_remove (mts->auto_scroll_timeout_id);
3132 mts->auto_scroll_timeout_id = 0;
3133 }
3134 }
3135
3136 /* This updates the GnomeDateEdit widget displaying the meeting start time. */
3137 static void
3138 e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts)
3139 {
3140 e_date_edit_set_date_and_time_of_day (
3141 E_DATE_EDIT (mts->start_date_edit),
3142 g_date_get_year (&mts->meeting_start_time.date),
3143 g_date_get_month (&mts->meeting_start_time.date),
3144 g_date_get_day (&mts->meeting_start_time.date),
3145 mts->meeting_start_time.hour,
3146 mts->meeting_start_time.minute);
3147 }
3148
3149 /* This updates the GnomeDateEdit widget displaying the meeting end time. */
3150 static void
3151 e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts)
3152 {
3153 GDate date;
3154
3155 date = mts->meeting_end_time.date;
3156 if (mts->all_day)
3157 g_date_subtract_days (&date, 1);
3158
3159 e_date_edit_set_date_and_time_of_day (
3160 E_DATE_EDIT (mts->end_date_edit),
3161 g_date_get_year (&date),
3162 g_date_get_month (&date),
3163 g_date_get_day (&date),
3164 mts->meeting_end_time.hour,
3165 mts->meeting_end_time.minute);
3166 }
3167
3168 /* This ensures that the meeting time is shown on screen, by scrolling the
3169 * canvas and possibly by changing the range of dates shown in the canvas. */
3170 static void
3171 e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts)
3172 {
3173 gint start_x, end_x, scroll_x, scroll_y;
3174 gint new_scroll_x;
3175 GtkAllocation allocation;
3176 EMeetingTime time;
3177
3178 /* Check if we need to change the range of dates shown. */
3179 if (g_date_compare (&mts->meeting_start_time.date,
3180 &mts->first_date_shown) < 0
3181 || g_date_compare (&mts->meeting_end_time.date,
3182 &mts->last_date_shown) > 0) {
3183 e_meeting_time_selector_update_dates_shown (mts);
3184 gtk_widget_queue_draw (mts->display_top);
3185 gtk_widget_queue_draw (mts->display_main);
3186 }
3187
3188 /* If all of the meeting time is visible, just return. */
3189 if (e_meeting_time_selector_get_meeting_time_positions (mts, &start_x,
3190 &end_x)) {
3191 time.date = mts->meeting_start_time.date;
3192 time.hour = 0;
3193 time.minute = 0;
3194 start_x = e_meeting_time_selector_calculate_time_position (mts, &time);
3195 }
3196
3197 gnome_canvas_get_scroll_offsets (
3198 GNOME_CANVAS (mts->display_main),
3199 &scroll_x, &scroll_y);
3200 gtk_widget_get_allocation (mts->display_main, &allocation);
3201 if (start_x > scroll_x && end_x <= scroll_x + allocation.width)
3202 return;
3203
3204 new_scroll_x = start_x;
3205 gnome_canvas_scroll_to (
3206 GNOME_CANVAS (mts->display_main),
3207 new_scroll_x, scroll_y);
3208 }
3209
3210 /* This updates the range of dates shown in the canvas, to make sure that the
3211 * currently selected meeting time is in the range. */
3212 static void
3213 e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts)
3214 {
3215 mts->first_date_shown = mts->meeting_start_time.date;
3216 g_date_subtract_days (
3217 &mts->first_date_shown,
3218 E_MEETING_TIME_SELECTOR_DAYS_START_BEFORE);
3219
3220 mts->last_date_shown = mts->first_date_shown;
3221 g_date_add_days (
3222 &mts->last_date_shown,
3223 E_MEETING_TIME_SELECTOR_DAYS_SHOWN - 1);
3224 }
3225
3226 /* This checks if the time's hour is over 24 or its minute is over 60 and if
3227 * so it updates the day/hour appropriately. Note that hours and minutes are
3228 * stored in guint8's so they can't overflow by much. */
3229 void
3230 e_meeting_time_selector_fix_time_overflows (EMeetingTime *mtstime)
3231 {
3232 gint hours_to_add, days_to_add;
3233
3234 hours_to_add = mtstime->minute / 60;
3235 if (hours_to_add > 0) {
3236 mtstime->minute -= hours_to_add * 60;
3237 mtstime->hour += hours_to_add;
3238 }
3239
3240 days_to_add = mtstime->hour / 24;
3241 if (days_to_add > 0) {
3242 mtstime->hour -= days_to_add * 24;
3243 g_date_add_days (&mtstime->date, days_to_add);
3244 }
3245 }
3246
3247 /*
3248 * CONVERSION ROUTINES - functions to convert between different coordinate
3249 * spaces and dates.
3250 */
3251
3252 /* This takes an x pixel coordinate within the entire canvas scroll region and
3253 * returns the date in which it falls. If day_position is not NULL it also
3254 * returns the x coordinate within the date, relative to the visible part of
3255 * the canvas. It is used when painting the days in the item_draw function.
3256 * Note that it must handle negative x coordinates in case we are dragging off
3257 * the edge of the canvas. */
3258 void
3259 e_meeting_time_selector_calculate_day_and_position (EMeetingTimeSelector *mts,
3260 gint x,
3261 GDate *date,
3262 gint *day_position)
3263 {
3264 gint days_from_first_shown;
3265
3266 *date = mts->first_date_shown;
3267
3268 if (x >= 0) {
3269 days_from_first_shown = x / mts->day_width;
3270 g_date_add_days (date, days_from_first_shown);
3271 if (day_position)
3272 *day_position = - x % mts->day_width;
3273 } else {
3274 days_from_first_shown = -x / mts->day_width + 1;
3275 g_date_subtract_days (date, days_from_first_shown);
3276 if (day_position)
3277 *day_position = -mts->day_width - x % mts->day_width;
3278 }
3279 }
3280
3281 /* This takes an x pixel coordinate within a day, and converts it to hours
3282 * and minutes, depending on working_hours_only and zoomed_out. */
3283 void
3284 e_meeting_time_selector_convert_day_position_to_hours_and_mins (EMeetingTimeSelector *mts,
3285 gint day_position,
3286 guint8 *hours,
3287 guint8 *minutes)
3288 {
3289 if (mts->zoomed_out)
3290 day_position *= 3;
3291
3292 /* Calculate the hours & minutes from the first displayed. */
3293 *hours = day_position / mts->col_width;
3294 *minutes = (day_position % mts->col_width) * 60 / mts->col_width;
3295
3296 /* Now add on the first hour shown. */
3297 *hours += mts->first_hour_shown;
3298 }
3299
3300 /* This takes an x pixel coordinate within the entire canvas scroll region and
3301 * returns the time in which it falls. Note that it won't be extremely
3302 * accurate since hours may only be a few pixels wide in the display.
3303 * With zoomed_out set each pixel may represent 5 minutes or more, depending
3304 * on how small the font is. */
3305 void
3306 e_meeting_time_selector_calculate_time (EMeetingTimeSelector *mts,
3307 gint x,
3308 EMeetingTime *time)
3309 {
3310 gint day_position;
3311
3312 /* First get the day and the x position within the day. */
3313 e_meeting_time_selector_calculate_day_and_position (
3314 mts, x, &time->date, NULL);
3315
3316 /* Now convert the day_position into an hour and minute. */
3317 if (x >= 0)
3318 day_position = x % mts->day_width;
3319 else
3320 day_position = mts->day_width + x % mts->day_width;
3321
3322 e_meeting_time_selector_convert_day_position_to_hours_and_mins (
3323 mts, day_position, &time->hour, &time->minute);
3324 }
3325
3326 /* This takes a EMeetingTime and calculates the x pixel coordinate
3327 * within the entire canvas scroll region. It is used to draw the selected
3328 * meeting time and all the busy periods. */
3329 gint
3330 e_meeting_time_selector_calculate_time_position (EMeetingTimeSelector *mts,
3331 EMeetingTime *mtstime)
3332 {
3333 gint x, date_offset, day_offset;
3334
3335 /* Calculate the number of days since the first date shown in the
3336 * entire canvas scroll region. */
3337 date_offset =
3338 g_date_get_julian (&mtstime->date) -
3339 g_date_get_julian (&mts->first_date_shown);
3340
3341 /* Calculate the x pixel coordinate of the start of the day. */
3342 x = date_offset * mts->day_width;
3343
3344 /* Add on the hours and minutes, depending on whether zoomed_out and
3345 * working_hours_only are set. */
3346 day_offset = (mtstime->hour - mts->first_hour_shown) * 60
3347 + mtstime->minute;
3348 /* The day width includes an extra vertical grid line so subtract 1. */
3349 day_offset *= (mts->day_width - 1);
3350 day_offset /= (mts->last_hour_shown - mts->first_hour_shown) * 60;
3351
3352 /* Clamp the day_offset in case the time isn't actually visible. */
3353 x += CLAMP (day_offset, 0, mts->day_width);
3354
3355 return x;
3356 }
3357
3358 static void
3359 row_inserted_cb (GtkTreeModel *model,
3360 GtkTreePath *path,
3361 GtkTreeIter *iter,
3362 gpointer data)
3363 {
3364 EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3365 gint row = gtk_tree_path_get_indices (path)[0];
3366 /* Update the scroll region. */
3367 e_meeting_time_selector_update_main_canvas_scroll_region (mts);
3368
3369 /* Redraw */
3370 gtk_widget_queue_draw (mts->display_top);
3371 gtk_widget_queue_draw (mts->display_main);
3372
3373 /* Get the latest free/busy info */
3374 e_meeting_time_selector_refresh_free_busy (mts, row, FALSE);
3375 }
3376
3377 static void
3378 row_changed_cb (GtkTreeModel *model,
3379 GtkTreePath *path,
3380 GtkTreeIter *iter,
3381 gpointer data)
3382 {
3383 EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3384 gint row = gtk_tree_path_get_indices (path)[0];
3385
3386 /* Get the latest free/busy info */
3387 e_meeting_time_selector_refresh_free_busy (mts, row, FALSE);
3388 }
3389
3390 static void
3391 row_deleted_cb (GtkTreeModel *model,
3392 GtkTreePath *path,
3393 gpointer data)
3394 {
3395 EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3396
3397 /* Update the scroll region. */
3398 e_meeting_time_selector_update_main_canvas_scroll_region (mts);
3399
3400 /* Redraw */
3401 gtk_widget_queue_draw (mts->display_top);
3402 gtk_widget_queue_draw (mts->display_main);
3403 }
3404
3405 #define REFRESH_PAUSE 5
3406
3407 static gboolean
3408 free_busy_timeout_refresh (gpointer data)
3409 {
3410 EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3411
3412 /* Update all free/busy info, so we use the new template uri */
3413 e_meeting_time_selector_refresh_free_busy (mts, 0, TRUE);
3414
3415 mts->fb_refresh_not = 0;
3416
3417 return FALSE;
3418 }
3419
3420 static void
3421 free_busy_template_changed_cb (EMeetingTimeSelector *mts)
3422 {
3423 /* Wait REFRESH_PAUSE before refreshing, using the latest uri value */
3424 if (mts->fb_refresh_not != 0)
3425 g_source_remove (mts->fb_refresh_not);
3426
3427 mts->fb_refresh_not = g_timeout_add_seconds (
3428 REFRESH_PAUSE, free_busy_timeout_refresh, mts);
3429 }