Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
e-week-view.c:3467:45 | clang-analyzer | Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event') | ||
e-week-view.c:3467:45 | clang-analyzer | Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event') | ||
e-week-view.c:3476:8 | clang-analyzer | Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event') | ||
e-week-view.c:3476:8 | clang-analyzer | Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event') |
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with the program; if not, see <http://www.gnu.org/licenses/>
14 *
15 *
16 * Authors:
17 * Damon Chaplin <damon@ximian.com>
18 * Rodrigo Moya <rodrigo@ximian.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 /*
25 * EWeekView - displays the Week & Month views of the calendar.
26 */
27
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31
32 #include "e-week-view.h"
33 #include "ea-calendar.h"
34
35 #include <math.h>
36 #include <gdk/gdkkeysyms.h>
37 #include <glib/gi18n.h>
38 #include <libgnomecanvas/libgnomecanvas.h>
39 #include <text/e-text.h>
40 #include <misc/e-canvas-utils.h>
41 #include <e-util/e-unicode.h>
42 #include <e-util/e-categories-config.h>
43 #include <e-util/e-dialog-utils.h>
44 #include <e-util/e-util.h>
45 #include "dialogs/delete-comp.h"
46 #include "dialogs/delete-error.h"
47 #include "dialogs/send-comp.h"
48 #include "dialogs/cancel-comp.h"
49 #include "dialogs/recur-comp.h"
50 #include "dialogs/goto-dialog.h"
51 #include "calendar-config.h"
52 #include "comp-util.h"
53 #include "itip-utils.h"
54 #include "calendar-config.h"
55 #include "print.h"
56 #include "e-cal-model-calendar.h"
57 #include "e-week-view-event-item.h"
58 #include "e-week-view-layout.h"
59 #include "e-week-view-main-item.h"
60 #include "e-week-view-titles-item.h"
61 #include "misc.h"
62 #include <e-util/e-icon-factory.h>
63
64 /* Images */
65 #include "art/jump.xpm"
66
67 #define E_WEEK_VIEW_SMALL_FONT_PTSIZE 7
68
69 #define E_WEEK_VIEW_JUMP_BUTTON_WIDTH 16
70 #define E_WEEK_VIEW_JUMP_BUTTON_HEIGHT 8
71
72 #define E_WEEK_VIEW_JUMP_BUTTON_X_PAD 3
73 #define E_WEEK_VIEW_JUMP_BUTTON_Y_PAD 3
74
75 #define E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS -1
76
77 /* The timeout before we do a layout, so we don't do a layout for each event
78 * we get from the server. */
79 #define E_WEEK_VIEW_LAYOUT_TIMEOUT 100
80
81 typedef struct {
82 EWeekView *week_view;
83 ECalModelComponent *comp_data;
84 } AddEventData;
85
86 static void e_week_view_dispose (GObject *object);
87 static void e_week_view_realize (GtkWidget *widget);
88 static void e_week_view_set_colors (EWeekView *week_view, GtkWidget *widget);
89 static void e_week_view_unrealize (GtkWidget *widget);
90 static void e_week_view_style_set (GtkWidget *widget,
91 GtkStyle *previous_style);
92 static void e_week_view_size_allocate (GtkWidget *widget,
93 GtkAllocation *allocation);
94 static void e_week_view_recalc_cell_sizes (EWeekView *week_view);
95 static gint e_week_view_focus_in (GtkWidget *widget,
96 GdkEventFocus *event);
97 static gint e_week_view_focus_out (GtkWidget *widget,
98 GdkEventFocus *event);
99 static gboolean e_week_view_get_next_tab_event (EWeekView *week_view,
100 GtkDirectionType direction,
101 gint current_event_num,
102 gint current_span_num,
103 gint *next_event_num,
104 gint *next_span_num);
105 static gboolean e_week_view_focus (GtkWidget *widget,
106 GtkDirectionType direction);
107 static GList *e_week_view_get_selected_events (ECalendarView *cal_view);
108 static gboolean e_week_view_get_selected_time_range (ECalendarView *cal_view, time_t *start_time, time_t *end_time);
109 static void e_week_view_set_selected_time_range (ECalendarView *cal_view, time_t start_time, time_t end_time);
110 static gboolean e_week_view_get_visible_time_range (ECalendarView *cal_view, time_t *start_time, time_t *end_time);
111 static void e_week_view_paste_text (ECalendarView *week_view);
112 static void e_week_view_update_query (EWeekView *week_view);
113
114 static gboolean e_week_view_on_button_press (GtkWidget *widget,
115 GdkEventButton *event,
116 EWeekView *week_view);
117 static gboolean e_week_view_on_button_release (GtkWidget *widget,
118 GdkEventButton *event,
119 EWeekView *week_view);
120 static gboolean e_week_view_on_scroll (GtkWidget *widget,
121 GdkEventScroll *scroll,
122 EWeekView *week_view);
123 static gboolean e_week_view_on_motion (GtkWidget *widget,
124 GdkEventMotion *event,
125 EWeekView *week_view);
126 static gint e_week_view_convert_position_to_day (EWeekView *week_view,
127 gint x,
128 gint y);
129 static void e_week_view_update_selection (EWeekView *week_view,
130 gint day);
131
132 static void e_week_view_free_events (EWeekView *week_view);
133 static gboolean e_week_view_add_event (ECalComponent *comp,
134 time_t start,
135 time_t end,
136 gboolean prepend,
137 gpointer data);
138 static void e_week_view_check_layout (EWeekView *week_view);
139 static void e_week_view_ensure_events_sorted (EWeekView *week_view);
140 static void e_week_view_reshape_events (EWeekView *week_view);
141 static void e_week_view_reshape_event_span (EWeekView *week_view,
142 gint event_num,
143 gint span_num);
144 static void e_week_view_recalc_day_starts (EWeekView *week_view,
145 time_t lower);
146 static void e_week_view_on_editing_started (EWeekView *week_view,
147 GnomeCanvasItem *item);
148 static void e_week_view_on_editing_stopped (EWeekView *week_view,
149 GnomeCanvasItem *item);
150 static gboolean e_week_view_find_event_from_uid (EWeekView *week_view,
151 ECalClient *client,
152 const gchar *uid,
153 const gchar *rid,
154 gint *event_num_return);
155 typedef gboolean (* EWeekViewForeachEventCallback) (EWeekView *week_view,
156 gint event_num,
157 gpointer data);
158 static void e_week_view_foreach_event_with_uid (EWeekView *week_view,
159 const gchar *uid,
160 EWeekViewForeachEventCallback callback,
161 gpointer data);
162 static gboolean e_week_view_on_text_item_event (GnomeCanvasItem *item,
163 GdkEvent *event,
164 EWeekView *week_view);
165 static gboolean e_week_view_event_move (ECalendarView *cal_view, ECalViewMoveDirection direction);
166 static gint e_week_view_get_day_offset_of_event (EWeekView *week_view, time_t event_time);
167 static void e_week_view_change_event_time (EWeekView *week_view, time_t start_dt, time_t end_dt, gboolean is_all_day);
168 static gboolean e_week_view_on_jump_button_event (GnomeCanvasItem *item,
169 GdkEvent *event,
170 EWeekView *week_view);
171 static gboolean e_week_view_key_press (GtkWidget *widget, GdkEventKey *event);
172 static gboolean e_week_view_do_key_press (GtkWidget *widget,
173 GdkEventKey *event);
174 static gint e_week_view_get_adjust_days_for_move_up (EWeekView *week_view, gint
175 current_day);
176 static gint e_week_view_get_adjust_days_for_move_down (EWeekView *week_view,gint current_day);
177 static gint e_week_view_get_adjust_days_for_move_left (EWeekView *week_view,gint current_day);
178 static gint e_week_view_get_adjust_days_for_move_right (EWeekView *week_view,gint current_day);
179 static gboolean e_week_view_popup_menu (GtkWidget *widget);
180
181 static gboolean e_week_view_remove_event_cb (EWeekView *week_view,
182 gint event_num,
183 gpointer data);
184 static gboolean e_week_view_recalc_display_start_day (EWeekView *week_view);
185
186 static void e_week_view_queue_layout (EWeekView *week_view);
187 static void e_week_view_cancel_layout (EWeekView *week_view);
188 static gboolean e_week_view_layout_timeout_cb (gpointer data);
189
190 G_DEFINE_TYPE (EWeekView, e_week_view, E_TYPE_CALENDAR_VIEW)
191
192 enum {
193 PROP_0,
194 PROP_COMPRESS_WEEKEND,
195 PROP_SHOW_EVENT_END_TIMES
196 };
197
198 static gint map_left[] = {0, 1, 2, 0, 1, 2, 2};
199 static gint map_right[] = {3, 4, 5, 3, 4, 5, 6};
200
201 static void
202 week_view_process_component (EWeekView *week_view,
203 ECalModelComponent *comp_data)
204 {
205 ECalComponent *comp = NULL;
206 AddEventData add_event_data;
207 /* rid is never used in this function? */
208 const gchar *uid;
209 gchar *rid = NULL;
210
211 /* If we don't have a valid date set yet, just return. */
212 if (!g_date_valid (&week_view->first_day_shown))
213 return;
214
215 comp = e_cal_component_new ();
216 if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (comp_data->icalcomp))) {
217 g_object_unref (comp);
218
219 g_message (G_STRLOC ": Could not set icalcomponent on ECalComponent");
220 return;
221 }
222
223 e_cal_component_get_uid (comp, &uid);
224 if (e_cal_component_is_instance (comp))
225 rid = e_cal_component_get_recurid_as_string (comp);
226 else
227 rid = NULL;
228
229 /* Add the object */
230 add_event_data.week_view = week_view;
231 add_event_data.comp_data = comp_data;
232 e_week_view_add_event (comp, comp_data->instance_start, comp_data->instance_end, FALSE, &add_event_data);
233
234 g_object_unref (comp);
235 g_free (rid);
236 }
237
238 static void
239 week_view_update_row (EWeekView *week_view,
240 gint row)
241 {
242 ECalModelComponent *comp_data;
243 ECalModel *model;
244 gint event_num;
245 const gchar *uid;
246 gchar *rid = NULL;
247
248 model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
249 comp_data = e_cal_model_get_component_at (model, row);
250 g_return_if_fail (comp_data != NULL);
251
252 uid = icalcomponent_get_uid (comp_data->icalcomp);
253 if (e_cal_util_component_is_instance (comp_data->icalcomp)) {
254 icalproperty *prop;
255
256 prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY);
257 if (prop)
258 rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp));
259 }
260
261 if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num))
262 e_week_view_remove_event_cb (week_view, event_num, NULL);
263
264 g_free (rid);
265
266 week_view_process_component (week_view, comp_data);
267
268 gtk_widget_queue_draw (week_view->main_canvas);
269 e_week_view_queue_layout (week_view);
270 }
271
272 static void
273 week_view_model_cell_changed_cb (EWeekView *week_view,
274 gint col,
275 gint row)
276 {
277 if (!E_CALENDAR_VIEW (week_view)->in_focus) {
278 e_week_view_free_events (week_view);
279 week_view->requires_update = TRUE;
280 return;
281 }
282
283 week_view_update_row (week_view, row);
284 }
285
286 static void
287 week_view_model_comps_deleted_cb (EWeekView *week_view,
288 gpointer data)
289 {
290 GSList *l, *list = data;
291
292 /* FIXME Stop editing? */
293 if (!E_CALENDAR_VIEW (week_view)->in_focus) {
294 e_week_view_free_events (week_view);
295 week_view->requires_update = TRUE;
296 return;
297 }
298
299 for (l = list; l != NULL; l = g_slist_next (l)) {
300 gint event_num;
301 const gchar *uid;
302 gchar *rid = NULL;
303 ECalModelComponent *comp_data = l->data;
304
305 uid = icalcomponent_get_uid (comp_data->icalcomp);
306 if (e_cal_util_component_is_instance (comp_data->icalcomp)) {
307 icalproperty *prop;
308
309 prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY);
310 if (prop)
311 rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp));
312 }
313
314 if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num))
315 e_week_view_remove_event_cb (week_view, event_num, NULL);
316 g_free (rid);
317 }
318
319 gtk_widget_queue_draw (week_view->main_canvas);
320 e_week_view_queue_layout (week_view);
321 }
322
323 static void
324 week_view_model_row_changed_cb (EWeekView *week_view,
325 gint row)
326 {
327 if (!E_CALENDAR_VIEW (week_view)->in_focus) {
328 e_week_view_free_events (week_view);
329 week_view->requires_update = TRUE;
330 return;
331 }
332
333 week_view_update_row (week_view, row);
334 }
335
336 static void
337 week_view_model_rows_inserted_cb (EWeekView *week_view,
338 gint row,
339 gint count)
340 {
341 ECalModel *model;
342 gint i;
343
344 if (!E_CALENDAR_VIEW (week_view)->in_focus) {
345 e_week_view_free_events (week_view);
346 week_view->requires_update = TRUE;
347 return;
348 }
349
350 model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
351
352 for (i = 0; i < count; i++) {
353 ECalModelComponent *comp_data;
354
355 comp_data = e_cal_model_get_component_at (model, row + i);
356 if (comp_data == NULL) {
357 g_warning ("comp_data is NULL\n");
358 continue;
359 }
360 week_view_process_component (week_view, comp_data);
361 }
362
363 gtk_widget_queue_draw (week_view->main_canvas);
364 e_week_view_queue_layout (week_view);
365 }
366
367 static void
368 week_view_time_range_changed_cb (EWeekView *week_view,
369 time_t start_time,
370 time_t end_time,
371 ECalModel *model)
372 {
373 GDate date, base_date;
374 gint day_offset, weekday, week_start_offset;
375 gboolean update_adjustment_value = FALSE;
376
377 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
378
379 time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
380
381 /* Calculate the weekday of the given date, 0 = Mon. */
382 weekday = g_date_get_weekday (&date) - 1;
383
384 /* Convert it to an offset from the start of the display. */
385 week_start_offset = (weekday + 7 - week_view->display_start_day) % 7;
386
387 /* Set the day_offset to the result, so we move back to the
388 * start of the week. */
389 day_offset = week_start_offset;
390
391 /* Calculate the base date, i.e. the first day shown when the
392 * scrollbar adjustment value is 0. */
393 base_date = date;
394 g_date_subtract_days (&base_date, day_offset);
395
396 /* See if we need to update the base date. */
397 if (!g_date_valid (&week_view->base_date)
398 || week_view->update_base_date) {
399 week_view->base_date = base_date;
400 update_adjustment_value = TRUE;
401 }
402
403 /* See if we need to update the first day shown. */
404 if (!g_date_valid (&week_view->first_day_shown)
405 || g_date_compare (&week_view->first_day_shown, &base_date)) {
406 week_view->first_day_shown = base_date;
407 start_time = time_add_day_with_zone (
408 start_time, -day_offset,
409 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
410 start_time = time_day_begin_with_zone (
411 start_time,
412 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
413 e_week_view_recalc_day_starts (week_view, start_time);
414 }
415
416 /* Reset the adjustment value to 0 if the base address has changed.
417 * Note that we do this after updating first_day_shown so that our
418 * signal handler will not try to reload the events. */
419 if (update_adjustment_value) {
420 GtkRange *range;
421 GtkAdjustment *adjustment;
422
423 range = GTK_RANGE (week_view->vscrollbar);
424 adjustment = gtk_range_get_adjustment (range);
425 gtk_adjustment_set_value (adjustment, 0);
426 }
427
428 if (!E_CALENDAR_VIEW (week_view)->in_focus) {
429 e_week_view_free_events (week_view);
430 week_view->requires_update = TRUE;
431 return;
432 }
433
434 gtk_widget_queue_draw (week_view->main_canvas);
435
436 /* FIXME Preserve selection if possible */
437 if (week_view->selection_start_day == -1 ||
438 (week_view->multi_week_view ? week_view->weeks_shown * 7 : 7) <= week_view->selection_start_day)
439 e_week_view_set_selected_time_range (E_CALENDAR_VIEW (week_view), start_time, start_time);
440 }
441
442 static void
443 timezone_changed_cb (ECalModel *cal_model,
444 icaltimezone *old_zone,
445 icaltimezone *new_zone,
446 gpointer user_data)
447 {
448 ECalendarView *cal_view = (ECalendarView *) user_data;
449 struct icaltimetype tt = icaltime_null_time ();
450 time_t lower;
451 EWeekView *week_view = (EWeekView *) cal_view;
452
453 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
454
455 if (!cal_view->in_focus) {
456 e_week_view_free_events (week_view);
457 week_view->requires_update = TRUE;
458 return;
459 }
460
461 /* If we don't have a valid date set yet, just return. */
462 if (!g_date_valid (&week_view->first_day_shown))
463 return;
464
465 /* Recalculate the new start of the first week. We just use exactly
466 * the same time, but with the new timezone. */
467 tt.year = g_date_get_year (&week_view->first_day_shown);
468 tt.month = g_date_get_month (&week_view->first_day_shown);
469 tt.day = g_date_get_day (&week_view->first_day_shown);
470
471 lower = icaltime_as_timet_with_zone (tt, new_zone);
472
473 e_week_view_recalc_day_starts (week_view, lower);
474 e_week_view_update_query (week_view);
475 }
476
477 static void
478 week_view_notify_week_start_day_cb (EWeekView *week_view)
479 {
480 GDate *first_day_shown;
481
482 first_day_shown = &week_view->first_day_shown;
483
484 e_week_view_recalc_display_start_day (week_view);
485
486 /* Recalculate the days shown and reload if necessary. */
487 if (g_date_valid (first_day_shown))
488 e_week_view_set_first_day_shown (week_view, first_day_shown);
489
490 gtk_widget_queue_draw (week_view->titles_canvas);
491 gtk_widget_queue_draw (week_view->main_canvas);
492 }
493
494 static void
495 week_view_set_property (GObject *object,
496 guint property_id,
497 const GValue *value,
498 GParamSpec *pspec)
499 {
500 switch (property_id) {
501 case PROP_COMPRESS_WEEKEND:
502 e_week_view_set_compress_weekend (
503 E_WEEK_VIEW (object),
504 g_value_get_boolean (value));
505 return;
506
507 case PROP_SHOW_EVENT_END_TIMES:
508 e_week_view_set_show_event_end_times (
509 E_WEEK_VIEW (object),
510 g_value_get_boolean (value));
511 return;
512 }
513
514 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
515 }
516
517 static void
518 week_view_get_property (GObject *object,
519 guint property_id,
520 GValue *value,
521 GParamSpec *pspec)
522 {
523 switch (property_id) {
524 case PROP_COMPRESS_WEEKEND:
525 g_value_set_boolean (
526 value,
527 e_week_view_get_compress_weekend (
528 E_WEEK_VIEW (object)));
529 return;
530
531 case PROP_SHOW_EVENT_END_TIMES:
532 g_value_set_boolean (
533 value,
534 e_week_view_get_show_event_end_times (
535 E_WEEK_VIEW (object)));
536 return;
537 }
538
539 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
540 }
541
542 static void
543 week_view_constructed (GObject *object)
544 {
545 ECalModel *model;
546 ECalendarView *calendar_view;
547
548 /* Chain up to parent's constructed() method. */
549 G_OBJECT_CLASS (e_week_view_parent_class)->constructed (object);
550
551 calendar_view = E_CALENDAR_VIEW (object);
552 model = e_calendar_view_get_model (calendar_view);
553
554 e_week_view_recalc_display_start_day (E_WEEK_VIEW (object));
555
556 g_signal_connect_swapped (
557 model, "notify::week-start-day",
558 G_CALLBACK (week_view_notify_week_start_day_cb), object);
559
560 g_signal_connect_swapped (
561 model, "comps-deleted",
562 G_CALLBACK (week_view_model_comps_deleted_cb), object);
563
564 g_signal_connect_swapped (
565 model, "model-cell-changed",
566 G_CALLBACK (week_view_model_cell_changed_cb), object);
567
568 g_signal_connect_swapped (
569 model, "model-row-changed",
570 G_CALLBACK (week_view_model_row_changed_cb), object);
571
572 g_signal_connect_swapped (
573 model, "model-rows-inserted",
574 G_CALLBACK (week_view_model_rows_inserted_cb), object);
575
576 g_signal_connect_swapped (
577 model, "time-range-changed",
578 G_CALLBACK (week_view_time_range_changed_cb), object);
579
580 }
581
582 static void
583 week_view_cursor_key_up (EWeekView *week_view)
584 {
585 if (week_view->selection_start_day == -1)
586 return;
587
588 week_view->selection_start_day--;
589
590 if (week_view->selection_start_day < 0) {
591 e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_UP);
592 week_view->selection_start_day = 6;
593 }
594
595 week_view->selection_end_day = week_view->selection_start_day;
596 g_signal_emit_by_name (week_view, "selected_time_changed");
597 gtk_widget_queue_draw (week_view->main_canvas);
598 }
599
600 static void
601 week_view_cursor_key_down (EWeekView *week_view)
602 {
603 if (week_view->selection_start_day == -1)
604 return;
605
606 week_view->selection_start_day++;
607
608 if (week_view->selection_start_day > 6) {
609 e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_DOWN);
610 week_view->selection_start_day = 0;
611 }
612
613 week_view->selection_end_day = week_view->selection_start_day;
614 g_signal_emit_by_name (week_view, "selected_time_changed");
615 gtk_widget_queue_draw (week_view->main_canvas);
616 }
617
618 static void
619 week_view_cursor_key_left (EWeekView *week_view)
620 {
621 if (week_view->selection_start_day == -1)
622 return;
623
624 week_view->selection_start_day = map_left[week_view->selection_start_day];
625 week_view->selection_end_day = week_view->selection_start_day;
626 g_signal_emit_by_name (week_view, "selected_time_changed");
627 gtk_widget_queue_draw (week_view->main_canvas);
628 }
629
630 static void
631 week_view_cursor_key_right (EWeekView *week_view)
632 {
633 if (week_view->selection_start_day == -1)
634 return;
635
636 week_view->selection_start_day = map_right[week_view->selection_start_day];
637 week_view->selection_end_day = week_view->selection_start_day;
638 g_signal_emit_by_name (week_view, "selected_time_changed");
639 gtk_widget_queue_draw (week_view->main_canvas);
640 }
641
642 static void
643 month_scrol_by_week_changed_cb (GSettings *settings,
644 const gchar *key,
645 gpointer user_data)
646 {
647 EWeekView *week_view = user_data;
648
649 g_return_if_fail (week_view != NULL);
650 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
651
652 if (week_view->multi_week_view && week_view->month_scroll_by_week != calendar_config_get_month_scroll_by_week ()) {
653 week_view->multi_week_view = FALSE;
654 e_week_view_set_multi_week_view (week_view, TRUE);
655 }
656 }
657
658 static void
659 e_week_view_class_init (EWeekViewClass *class)
660 {
661 GObjectClass *object_class;
662 GtkWidgetClass *widget_class;
663 ECalendarViewClass *view_class;
664
665 object_class = G_OBJECT_CLASS (class);
666 object_class->set_property = week_view_set_property;
667 object_class->get_property = week_view_get_property;
668 object_class->constructed = week_view_constructed;
669 object_class->dispose = e_week_view_dispose;
670
671 widget_class = GTK_WIDGET_CLASS (class);
672 widget_class->realize = e_week_view_realize;
673 widget_class->unrealize = e_week_view_unrealize;
674 widget_class->style_set = e_week_view_style_set;
675 widget_class->size_allocate = e_week_view_size_allocate;
676 widget_class->focus_in_event = e_week_view_focus_in;
677 widget_class->focus_out_event = e_week_view_focus_out;
678 widget_class->key_press_event = e_week_view_key_press;
679 widget_class->popup_menu = e_week_view_popup_menu;
680 widget_class->focus = e_week_view_focus;
681
682 view_class = E_CALENDAR_VIEW_CLASS (class);
683 view_class->get_selected_events = e_week_view_get_selected_events;
684 view_class->get_selected_time_range = e_week_view_get_selected_time_range;
685 view_class->set_selected_time_range = e_week_view_set_selected_time_range;
686 view_class->get_visible_time_range = e_week_view_get_visible_time_range;
687 view_class->paste_text = e_week_view_paste_text;
688
689 class->cursor_key_up = week_view_cursor_key_up;
690 class->cursor_key_down = week_view_cursor_key_down;
691 class->cursor_key_left = week_view_cursor_key_left;
692 class->cursor_key_right = week_view_cursor_key_right;
693
694 /* XXX This property really belongs in EMonthView,
695 * but too much drawing code is tied to it. */
696 g_object_class_install_property (
697 object_class,
698 PROP_COMPRESS_WEEKEND,
699 g_param_spec_boolean (
700 "compress-weekend",
701 "Compress Weekend",
702 NULL,
703 TRUE,
704 G_PARAM_READWRITE));
705
706 g_object_class_install_property (
707 object_class,
708 PROP_SHOW_EVENT_END_TIMES,
709 g_param_spec_boolean (
710 "show-event-end-times",
711 "Show Event End Times",
712 NULL,
713 TRUE,
714 G_PARAM_READWRITE));
715
716 /* init the accessibility support for e_week_view */
717 e_week_view_a11y_init ();
718 }
719
720 static void
721 e_week_view_init (EWeekView *week_view)
722 {
723 GnomeCanvasGroup *canvas_group;
724 GtkAdjustment *adjustment;
725 GdkPixbuf *pixbuf;
726 gint i;
727
728 gtk_widget_set_can_focus (GTK_WIDGET (week_view), TRUE);
729
730 week_view->event_destroyed = FALSE;
731 week_view->events = g_array_new (
732 FALSE, FALSE,
733 sizeof (EWeekViewEvent));
734 week_view->events_sorted = TRUE;
735 week_view->events_need_layout = FALSE;
736 week_view->events_need_reshape = FALSE;
737
738 week_view->layout_timeout_id = 0;
739
740 week_view->spans = NULL;
741
742 week_view->multi_week_view = FALSE;
743 week_view->month_scroll_by_week = FALSE;
744 week_view->scroll_by_week_notif_id = 0;
745 week_view->update_base_date = TRUE;
746 week_view->weeks_shown = 6;
747 week_view->rows = 6;
748 week_view->columns = 2;
749 week_view->compress_weekend = TRUE;
750 week_view->show_event_end_times = TRUE;
751 week_view->display_start_day = 0; /* Monday. */
752
753 g_date_clear (&week_view->base_date, 1);
754 g_date_clear (&week_view->first_day_shown, 1);
755
756 week_view->row_height = 10;
757 week_view->rows_per_cell = 1;
758
759 week_view->selection_start_day = -1;
760 week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;
761
762 week_view->pressed_event_num = -1;
763 week_view->editing_event_num = -1;
764
765 week_view->last_edited_comp_string = NULL;
766
767 /* Create the small font. */
768 week_view->use_small_font = TRUE;
769
770 week_view->small_font_desc =
771 pango_font_description_copy (gtk_widget_get_style (GTK_WIDGET (week_view))->font_desc);
772 pango_font_description_set_size (
773 week_view->small_font_desc,
774 E_WEEK_VIEW_SMALL_FONT_PTSIZE * PANGO_SCALE);
775
776 /* String to use in 12-hour time format for times in the morning. */
777 week_view->am_string = _("am");
778
779 /* String to use in 12-hour time format for times in the afternoon. */
780 week_view->pm_string = _("pm");
781
782 week_view->bc_event_time = 0;
783 week_view->before_click_dtstart = 0;
784 week_view->before_click_dtend = 0;
785
786 /*
787 * Titles Canvas. Note that we don't show it is only shown in the
788 * Month view.
789 */
790 week_view->titles_canvas = e_canvas_new ();
791 gtk_table_attach (
792 GTK_TABLE (week_view), week_view->titles_canvas,
793 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
794
795 canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->titles_canvas)->root);
796
797 week_view->titles_canvas_item =
798 gnome_canvas_item_new (
799 canvas_group,
800 e_week_view_titles_item_get_type (),
801 "EWeekViewTitlesItem::week_view", week_view,
802 NULL);
803
804 /*
805 * Main Canvas
806 */
807 week_view->main_canvas = e_canvas_new ();
808 gtk_table_attach (
809 GTK_TABLE (week_view), week_view->main_canvas,
810 1, 2, 1, 2,
811 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
812 gtk_widget_show (week_view->main_canvas);
813
814 canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root);
815
816 week_view->main_canvas_item =
817 gnome_canvas_item_new (
818 canvas_group,
819 e_week_view_main_item_get_type (),
820 "EWeekViewMainItem::week_view", week_view,
821 NULL);
822
823 g_signal_connect_after (
824 week_view->main_canvas, "button_press_event",
825 G_CALLBACK (e_week_view_on_button_press), week_view);
826 g_signal_connect (
827 week_view->main_canvas, "button_release_event",
828 G_CALLBACK (e_week_view_on_button_release), week_view);
829 g_signal_connect (
830 week_view->main_canvas, "scroll_event",
831 G_CALLBACK (e_week_view_on_scroll), week_view);
832 g_signal_connect (
833 week_view->main_canvas, "motion_notify_event",
834 G_CALLBACK (e_week_view_on_motion), week_view);
835
836 /* Create the buttons to jump to each days. */
837 pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm);
838
839 for (i = 0; i < E_WEEK_VIEW_MAX_WEEKS * 7; i++) {
840 week_view->jump_buttons[i] = gnome_canvas_item_new
841 (canvas_group,
842 gnome_canvas_pixbuf_get_type (),
843 "GnomeCanvasPixbuf::pixbuf", pixbuf,
844 NULL);
845
846 g_signal_connect (
847 week_view->jump_buttons[i], "event",
848 G_CALLBACK (e_week_view_on_jump_button_event), week_view);
849 }
850 week_view->focused_jump_button = E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS;
851
852 g_object_unref (pixbuf);
853
854 /*
855 * Scrollbar.
856 */
857 adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, -52, 52, 1, 1, 1));
858
859 week_view->vscrollbar = gtk_vscrollbar_new (adjustment);
860 gtk_table_attach (
861 GTK_TABLE (week_view), week_view->vscrollbar,
862 2, 3, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0);
863 gtk_widget_show (week_view->vscrollbar);
864
865 /* Create the cursors. */
866 week_view->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR);
867 week_view->move_cursor = gdk_cursor_new (GDK_FLEUR);
868 week_view->resize_width_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
869 week_view->last_cursor_set = NULL;
870
871 week_view->requires_update = FALSE;
872 }
873
874 /**
875 * e_week_view_new:
876 * @Returns: a new #EWeekView.
877 *
878 * Creates a new #EWeekView.
879 **/
880 ECalendarView *
881 e_week_view_new (ECalModel *model)
882 {
883 ECalendarView *view;
884 g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
885
886 view = g_object_new (E_TYPE_WEEK_VIEW, "model", model, NULL);
887
888 g_signal_connect (
889 model, "timezone_changed",
890 G_CALLBACK (timezone_changed_cb), view);
891
892 return view;
893 }
894
895 static void
896 e_week_view_dispose (GObject *object)
897 {
898 EWeekView *week_view;
899
900 week_view = E_WEEK_VIEW (object);
901
902 e_week_view_cancel_layout (week_view);
903
904 if (week_view->events) {
905 e_week_view_free_events (week_view);
906 g_array_free (week_view->events, TRUE);
907 week_view->events = NULL;
908 }
909
910 if (week_view->small_font_desc) {
911 pango_font_description_free (week_view->small_font_desc);
912 week_view->small_font_desc = NULL;
913 }
914
915 if (week_view->normal_cursor) {
916 g_object_unref (week_view->normal_cursor);
917 week_view->normal_cursor = NULL;
918 }
919 if (week_view->move_cursor) {
920 g_object_unref (week_view->move_cursor);
921 week_view->move_cursor = NULL;
922 }
923 if (week_view->resize_width_cursor) {
924 g_object_unref (week_view->resize_width_cursor);
925 week_view->resize_width_cursor = NULL;
926 }
927
928 calendar_config_remove_notification (month_scrol_by_week_changed_cb, week_view);
929
930 /* Chain up to parent's dispose() method. */
931 G_OBJECT_CLASS (e_week_view_parent_class)->dispose (object);
932 }
933
934 static void
935 e_week_view_realize (GtkWidget *widget)
936 {
937 EWeekView *week_view;
938
939 if (GTK_WIDGET_CLASS (e_week_view_parent_class)->realize)
940 (*GTK_WIDGET_CLASS (e_week_view_parent_class)->realize)(widget);
941
942 week_view = E_WEEK_VIEW (widget);
943
944 /* Allocate the colors. */
945 e_week_view_set_colors (week_view, widget);
946
947 /* Create the pixmaps. */
948 week_view->reminder_icon = e_icon_factory_get_icon ("stock_bell", GTK_ICON_SIZE_MENU);
949 week_view->recurrence_icon = e_icon_factory_get_icon ("view-refresh", GTK_ICON_SIZE_MENU);
950 week_view->timezone_icon = e_icon_factory_get_icon ("stock_timezone", GTK_ICON_SIZE_MENU);
951 week_view->attach_icon = e_icon_factory_get_icon ("mail-attachment", GTK_ICON_SIZE_MENU);
952 week_view->meeting_icon = e_icon_factory_get_icon ("stock_people", GTK_ICON_SIZE_MENU);
953 }
954
955 static GdkColor
956 color_inc (GdkColor c,
957 gint amount)
958 {
959 #define dec(x) \
960 if (x + amount >= 0 \
961 && x + amount <= 0xFFFF) \
962 x += amount; \
963 else if (amount <= 0) \
964 x = 0; \
965 else \
966 x = 0xFFFF;
967
968 dec (c.red);
969 dec (c.green);
970 dec (c.blue);
971
972 #undef dec
973
974 return c;
975 }
976
977 static void
978 e_week_view_set_colors (EWeekView *week_view,
979 GtkWidget *widget)
980 {
981 GtkStyle *style;
982
983 style = gtk_widget_get_style (widget);
984
985 week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS] = style->base[GTK_STATE_INSENSITIVE];
986 week_view->colors[E_WEEK_VIEW_COLOR_ODD_MONTHS] = style->base[GTK_STATE_NORMAL];
987 week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND] = style->base[GTK_STATE_NORMAL];
988 week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BORDER] = style->dark[GTK_STATE_NORMAL];
989 week_view->colors[E_WEEK_VIEW_COLOR_EVENT_TEXT] = style->text[GTK_STATE_NORMAL];
990 week_view->colors[E_WEEK_VIEW_COLOR_GRID] = style->dark[GTK_STATE_NORMAL];
991 week_view->colors[E_WEEK_VIEW_COLOR_SELECTED] = style->base[GTK_STATE_SELECTED];
992 week_view->colors[E_WEEK_VIEW_COLOR_SELECTED_UNFOCUSSED] = style->bg[GTK_STATE_SELECTED];
993 week_view->colors[E_WEEK_VIEW_COLOR_DATES] = style->text[GTK_STATE_NORMAL];
994 week_view->colors[E_WEEK_VIEW_COLOR_DATES_SELECTED] = style->text[GTK_STATE_SELECTED];
995 week_view->colors[E_WEEK_VIEW_COLOR_TODAY] = style->base[GTK_STATE_SELECTED];
996 week_view->colors[E_WEEK_VIEW_COLOR_TODAY_BACKGROUND] = get_today_background (week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND]);
997 week_view->colors[E_WEEK_VIEW_COLOR_MONTH_NONWORKING_DAY] = color_inc (week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS], -0x0A0A);
998 }
999
1000 static void
1001 e_week_view_unrealize (GtkWidget *widget)
1002 {
1003 EWeekView *week_view;
1004
1005 week_view = E_WEEK_VIEW (widget);
1006
1007 g_object_unref (week_view->reminder_icon);
1008 week_view->reminder_icon = NULL;
1009 g_object_unref (week_view->recurrence_icon);
1010 week_view->recurrence_icon = NULL;
1011 g_object_unref (week_view->timezone_icon);
1012 week_view->timezone_icon = NULL;
1013 g_object_unref (week_view->attach_icon);
1014 week_view->attach_icon = NULL;
1015 g_object_unref (week_view->meeting_icon);
1016 week_view->meeting_icon = NULL;
1017
1018 if (GTK_WIDGET_CLASS (e_week_view_parent_class)->unrealize)
1019 (*GTK_WIDGET_CLASS (e_week_view_parent_class)->unrealize)(widget);
1020 }
1021
1022 static gint
1023 get_string_width (PangoLayout *layout,
1024 const gchar *string)
1025 {
1026 gint width;
1027
1028 pango_layout_set_text (layout, string, -1);
1029 pango_layout_get_pixel_size (layout, &width, NULL);
1030 return width;
1031 }
1032
1033 /* FIXME: This is also needed in e-day-view-time-item.c. We should probably use
1034 * pango's approximation function, but it needs a language tag. Find out how to
1035 * get one of those properly. */
1036 static gint
1037 get_digit_width (PangoLayout *layout)
1038 {
1039 gint digit;
1040 gint max_digit_width = 1;
1041
1042 for (digit = '0'; digit <= '9'; digit++) {
1043 gchar digit_char;
1044 gint digit_width;
1045
1046 digit_char = digit;
1047
1048 pango_layout_set_text (layout, &digit_char, 1);
1049 pango_layout_get_pixel_size (layout, &digit_width, NULL);
1050
1051 max_digit_width = MAX (max_digit_width, digit_width);
1052 }
1053
1054 return max_digit_width;
1055 }
1056
1057 static GdkColor
1058 e_week_view_get_text_color (EWeekView *week_view,
1059 EWeekViewEvent *event,
1060 GtkWidget *widget)
1061 {
1062 GtkStyle *style;
1063 GdkColor bg_color;
1064 guint16 red, green, blue;
1065 gdouble cc = 65535.0;
1066
1067 red = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].red;
1068 green = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].green;
1069 blue = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].blue;
1070
1071 if (is_comp_data_valid (event) && gdk_color_parse (e_cal_model_get_color_for_component (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), event->comp_data),
1072 &bg_color)) {
1073 red = bg_color.red;
1074 green = bg_color.green;
1075 blue = bg_color.blue;
1076 }
1077
1078 style = gtk_widget_get_style (widget);
1079
1080 if ((red / cc > 0.7) || (green / cc > 0.7) || (blue / cc > 0.7))
1081 return style->black;
1082 else
1083 return style->white;
1084 }
1085
1086 static void
1087 e_week_view_style_set (GtkWidget *widget,
1088 GtkStyle *previous_style)
1089 {
1090 EWeekView *week_view;
1091 GtkStyle *style;
1092 gint day, day_width, max_day_width, max_abbr_day_width;
1093 gint month, month_width, max_month_width, max_abbr_month_width;
1094 gint span_num;
1095 const gchar *name;
1096 PangoFontDescription *font_desc;
1097 PangoContext *pango_context;
1098 PangoFontMetrics *font_metrics;
1099 PangoLayout *layout;
1100 EWeekViewEventSpan *span;
1101
1102 if (GTK_WIDGET_CLASS (e_week_view_parent_class)->style_set)
1103 (*GTK_WIDGET_CLASS (e_week_view_parent_class)->style_set)(widget, previous_style);
1104
1105 week_view = E_WEEK_VIEW (widget);
1106 style = gtk_widget_get_style (widget);
1107
1108 e_week_view_set_colors (week_view, widget);
1109 if (week_view->spans) {
1110 for (span_num = 0; span_num < week_view->spans->len;
1111 span_num++) {
1112 span = &g_array_index (week_view->spans,
1113 EWeekViewEventSpan, span_num);
1114 if (span->text_item) {
1115 gnome_canvas_item_set (
1116 span->text_item,
1117 "fill_color_gdk", &style->text[GTK_STATE_NORMAL],
1118 NULL);
1119 }
1120 }
1121 }
1122
1123 /* Set up Pango prerequisites */
1124 font_desc = style->font_desc;
1125 pango_context = gtk_widget_get_pango_context (widget);
1126 font_metrics = pango_context_get_metrics (
1127 pango_context, font_desc,
1128 pango_context_get_language (pango_context));
1129 layout = pango_layout_new (pango_context);
1130
1131 /* Recalculate the height of each row based on the font size. */
1132 week_view->row_height = PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1133 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) +
1134 E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2 + E_WEEK_VIEW_EVENT_TEXT_Y_PAD * 2;
1135 week_view->row_height = MAX (week_view->row_height, E_WEEK_VIEW_ICON_HEIGHT + E_WEEK_VIEW_ICON_Y_PAD + E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2);
1136
1137 /* Check that the small font is smaller than the default font.
1138 * If it isn't, we won't use it. */
1139 if (week_view->small_font_desc) {
1140 if (PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1141 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1142 <= E_WEEK_VIEW_SMALL_FONT_PTSIZE)
1143 week_view->use_small_font = FALSE;
1144 }
1145
1146 /* Set the height of the top canvas. */
1147 gtk_widget_set_size_request (
1148 week_view->titles_canvas, -1,
1149 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1150 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + 5);
1151
1152 /* Save the sizes of various strings in the font, so we can quickly
1153 * decide which date formats to use. */
1154
1155 max_day_width = 0;
1156 max_abbr_day_width = 0;
1157 for (day = 0; day < 7; day++) {
1158 name = e_get_weekday_name (day + 1, FALSE);
1159 day_width = get_string_width (layout, name);
1160 week_view->day_widths[day] = day_width;
1161 max_day_width = MAX (max_day_width, day_width);
1162
1163 name = e_get_weekday_name (day + 1, TRUE);
1164 day_width = get_string_width (layout, name);
1165 week_view->abbr_day_widths[day] = day_width;
1166 max_abbr_day_width = MAX (max_abbr_day_width, day_width);
1167 }
1168
1169 max_month_width = 0;
1170 max_abbr_month_width = 0;
1171 for (month = 0; month < 12; month++) {
1172 name = e_get_month_name (month + 1, FALSE);
1173 month_width = get_string_width (layout, name);
1174 week_view->month_widths[month] = month_width;
1175 max_month_width = MAX (max_month_width, month_width);
1176
1177 name = e_get_month_name (month + 1, TRUE);
1178 month_width = get_string_width (layout, name);
1179 week_view->abbr_month_widths[month] = month_width;
1180 max_abbr_month_width = MAX (max_abbr_month_width, month_width);
1181 }
1182
1183 week_view->space_width = get_string_width (layout, " ");
1184 week_view->colon_width = get_string_width (layout, ":");
1185 week_view->slash_width = get_string_width (layout, "/");
1186 week_view->digit_width = get_digit_width (layout);
1187 if (week_view->small_font_desc) {
1188 pango_layout_set_font_description (layout, week_view->small_font_desc);
1189 week_view->small_digit_width = get_digit_width (layout);
1190 pango_layout_set_font_description (layout, style->font_desc);
1191 }
1192 week_view->max_day_width = max_day_width;
1193 week_view->max_abbr_day_width = max_abbr_day_width;
1194 week_view->max_month_width = max_month_width;
1195 week_view->max_abbr_month_width = max_abbr_month_width;
1196
1197 week_view->am_string_width = get_string_width (
1198 layout,
1199 week_view->am_string);
1200 week_view->pm_string_width = get_string_width (
1201 layout,
1202 week_view->pm_string);
1203
1204 g_object_unref (layout);
1205 pango_font_metrics_unref (font_metrics);
1206 }
1207
1208 /* This recalculates the sizes of each column. */
1209 static void
1210 e_week_view_size_allocate (GtkWidget *widget,
1211 GtkAllocation *allocation)
1212 {
1213 EWeekView *week_view;
1214 GtkAllocation canvas_allocation;
1215 gdouble old_x2, old_y2, new_x2, new_y2;
1216
1217 week_view = E_WEEK_VIEW (widget);
1218
1219 (*GTK_WIDGET_CLASS (e_week_view_parent_class)->size_allocate) (widget, allocation);
1220
1221 e_week_view_recalc_cell_sizes (week_view);
1222
1223 /* Set the scroll region of the top canvas to its allocated size. */
1224 gnome_canvas_get_scroll_region (
1225 GNOME_CANVAS (week_view->titles_canvas),
1226 NULL, NULL, &old_x2, &old_y2);
1227 gtk_widget_get_allocation (
1228 week_view->titles_canvas, &canvas_allocation);
1229 new_x2 = canvas_allocation.width - 1;
1230 new_y2 = canvas_allocation.height - 1;
1231 if (old_x2 != new_x2 || old_y2 != new_y2)
1232 gnome_canvas_set_scroll_region (
1233 GNOME_CANVAS (week_view->titles_canvas),
1234 0, 0, new_x2, new_y2);
1235
1236 /* Set the scroll region of the main canvas to its allocated width,
1237 * but with the height depending on the number of rows needed. */
1238 gnome_canvas_get_scroll_region (
1239 GNOME_CANVAS (week_view->main_canvas),
1240 NULL, NULL, &old_x2, &old_y2);
1241 gtk_widget_get_allocation (
1242 week_view->main_canvas, &canvas_allocation);
1243 new_x2 = canvas_allocation.width - 1;
1244 new_y2 = canvas_allocation.height - 1;
1245 if (old_x2 != new_x2 || old_y2 != new_y2)
1246 gnome_canvas_set_scroll_region (
1247 GNOME_CANVAS (week_view->main_canvas),
1248 0, 0, new_x2, new_y2);
1249
1250 /* Flag that we need to reshape the events. */
1251 if (old_x2 != new_x2 || old_y2 != new_y2) {
1252 week_view->events_need_reshape = TRUE;
1253 e_week_view_check_layout (week_view);
1254 }
1255 }
1256
1257 static void
1258 e_week_view_recalc_cell_sizes (EWeekView *week_view)
1259 {
1260 gfloat canvas_width, canvas_height, offset;
1261 gint row, col;
1262 GtkAllocation allocation;
1263 GtkWidget *widget;
1264 GtkStyle *style;
1265 gint width, height, time_width;
1266 PangoFontDescription *font_desc;
1267 PangoContext *pango_context;
1268 PangoFontMetrics *font_metrics;
1269
1270 if (week_view->multi_week_view) {
1271 week_view->rows = week_view->weeks_shown * 2;
1272 week_view->columns = week_view->compress_weekend ? 6 : 7;
1273 } else {
1274 week_view->rows = 6;
1275 week_view->columns = 2;
1276 }
1277
1278 gtk_widget_get_allocation (week_view->main_canvas, &allocation);
1279
1280 /* Calculate the column sizes, using floating point so that pixels
1281 * get divided evenly. Note that we use one more element than the
1282 * number of columns, to make it easy to get the column widths.
1283 * We also add one to the width so that the right border of the last
1284 * column is off the edge of the displayed area. */
1285 canvas_width = allocation.width + 1;
1286 canvas_width /= week_view->columns;
1287 offset = 0;
1288 for (col = 0; col <= week_view->columns; col++) {
1289 week_view->col_offsets[col] = floor (offset + 0.5);
1290 offset += canvas_width;
1291 }
1292
1293 /* Calculate the cell widths based on the offsets. */
1294 for (col = 0; col < week_view->columns; col++) {
1295 week_view->col_widths[col] = week_view->col_offsets[col + 1]
1296 - week_view->col_offsets[col];
1297 }
1298
1299 /* Now do the same for the row heights. */
1300 canvas_height = allocation.height + 1;
1301 canvas_height /= week_view->rows;
1302 offset = 0;
1303 for (row = 0; row <= week_view->rows; row++) {
1304 week_view->row_offsets[row] = floor (offset + 0.5);
1305 offset += canvas_height;
1306 }
1307
1308 /* Calculate the cell heights based on the offsets. */
1309 for (row = 0; row < week_view->rows; row++) {
1310 week_view->row_heights[row] = week_view->row_offsets[row + 1]
1311 - week_view->row_offsets[row];
1312 }
1313
1314 /* If the font hasn't been set yet just return. */
1315 widget = GTK_WIDGET (week_view);
1316 style = gtk_widget_get_style (widget);
1317 if (!style)
1318 return;
1319 font_desc = style->font_desc;
1320 if (!font_desc)
1321 return;
1322
1323 pango_context = gtk_widget_get_pango_context (widget);
1324 font_metrics = pango_context_get_metrics (
1325 pango_context, font_desc,
1326 pango_context_get_language (pango_context));
1327
1328 /* Calculate the number of rows of events in each cell, for the large
1329 * cells and the compressed weekend cells. */
1330 if (week_view->multi_week_view) {
1331 week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD +
1332 + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
1333 + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1334 + E_WEEK_VIEW_DATE_B_PAD;
1335 } else {
1336 week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD
1337 + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
1338 + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1339 + E_WEEK_VIEW_DATE_LINE_T_PAD + 1
1340 + E_WEEK_VIEW_DATE_LINE_B_PAD;
1341 }
1342
1343 height = week_view->row_heights[0];
1344 week_view->rows_per_cell =
1345 (height * 2 - week_view->events_y_offset) /
1346 (week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING);
1347 week_view->rows_per_cell = MIN (
1348 week_view->rows_per_cell,
1349 E_WEEK_VIEW_MAX_ROWS_PER_CELL);
1350
1351 week_view->rows_per_compressed_cell =
1352 (height - week_view->events_y_offset) /
1353 (week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING);
1354 week_view->rows_per_compressed_cell = MIN (
1355 week_view->rows_per_compressed_cell,
1356 E_WEEK_VIEW_MAX_ROWS_PER_CELL);
1357
1358 /* Determine which time format to use, based on the width of the cells.
1359 * We only allow the time to take up about half of the width. */
1360 width = week_view->col_widths[0];
1361
1362 time_width = e_week_view_get_time_string_width (week_view);
1363
1364 week_view->time_format = E_WEEK_VIEW_TIME_NONE;
1365 if (week_view->use_small_font && week_view->small_font_desc) {
1366 if (week_view->show_event_end_times
1367 && width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING)
1368 week_view->time_format = E_WEEK_VIEW_TIME_BOTH_SMALL_MIN;
1369 else if (width / 2 > time_width)
1370 week_view->time_format = E_WEEK_VIEW_TIME_START_SMALL_MIN;
1371 } else {
1372 if (week_view->show_event_end_times
1373 && width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING)
1374 week_view->time_format = E_WEEK_VIEW_TIME_BOTH;
1375 else if (width / 2 > time_width)
1376 week_view->time_format = E_WEEK_VIEW_TIME_START;
1377 }
1378
1379 pango_font_metrics_unref (font_metrics);
1380 }
1381
1382 static gint
1383 e_week_view_focus_in (GtkWidget *widget,
1384 GdkEventFocus *event)
1385 {
1386 EWeekView *week_view;
1387
1388 g_return_val_if_fail (widget != NULL, FALSE);
1389 g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
1390 g_return_val_if_fail (event != NULL, FALSE);
1391
1392 week_view = E_WEEK_VIEW (widget);
1393
1394 /* XXX Can't access flags directly anymore, but is it really needed?
1395 * If so, could we call gtk_widget_send_focus_change() instead? */
1396 #if 0
1397 GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
1398 #endif
1399
1400 if (E_CALENDAR_VIEW (week_view)->in_focus && week_view->requires_update) {
1401 time_t my_start = 0, my_end = 0, model_start = 0, model_end = 0;
1402
1403 week_view->requires_update = FALSE;
1404
1405 e_cal_model_get_time_range (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), &model_start, &model_end);
1406
1407 if (e_calendar_view_get_visible_time_range (E_CALENDAR_VIEW (week_view), &my_start, &my_end) &&
1408 model_start == my_start && model_end == my_end) {
1409 /* update only when the same time range is set in a view and in a model;
1410 * otherwise time range change invokes also query update */
1411 e_week_view_update_query (week_view);
1412 }
1413 }
1414
1415 gtk_widget_queue_draw (week_view->main_canvas);
1416
1417 return FALSE;
1418 }
1419
1420 static gint
1421 e_week_view_focus_out (GtkWidget *widget,
1422 GdkEventFocus *event)
1423 {
1424 EWeekView *week_view;
1425
1426 g_return_val_if_fail (widget != NULL, FALSE);
1427 g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
1428 g_return_val_if_fail (event != NULL, FALSE);
1429
1430 week_view = E_WEEK_VIEW (widget);
1431
1432 /* XXX Can't access flags directly anymore, but is it really needed?
1433 * If so, could we call gtk_widget_send_focus_change() instead? */
1434 #if 0
1435 GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
1436 #endif
1437
1438 gtk_widget_queue_draw (week_view->main_canvas);
1439
1440 return FALSE;
1441 }
1442
1443 /**
1444 * e_week_view_get_next_tab_event
1445 * @week_view: the week_view widget operate on
1446 * @direction: GTK_DIR_TAB_BACKWARD or GTK_DIR_TAB_FORWARD.
1447 * @current_event_num and @current_span_num: current status.
1448 * @next_event_num: the event number focus should go next.
1449 * -1 indicates focus should go to week_view widget.
1450 * @next_span_num: always return 0.
1451 **/
1452 static gboolean
1453 e_week_view_get_next_tab_event (EWeekView *week_view,
1454 GtkDirectionType direction,
1455 gint current_event_num,
1456 gint current_span_num,
1457 gint *next_event_num,
1458 gint *next_span_num)
1459 {
1460 gint event_num;
1461
1462 g_return_val_if_fail (week_view != NULL, FALSE);
1463 g_return_val_if_fail (next_event_num != NULL, FALSE);
1464 g_return_val_if_fail (next_span_num != NULL, FALSE);
1465
1466 if (week_view->events->len <= 0)
1467 return FALSE;
1468
1469 /* we only tab through events not spans */
1470 *next_span_num = 0;
1471
1472 switch (direction) {
1473 case GTK_DIR_TAB_BACKWARD:
1474 event_num = current_event_num - 1;
1475 break;
1476 case GTK_DIR_TAB_FORWARD:
1477 event_num = current_event_num + 1;
1478 break;
1479 default:
1480 return FALSE;
1481 }
1482
1483 if (event_num == -1)
1484 /* backward, out of event range, go to week view widget
1485 */
1486 *next_event_num = -1;
1487 else if (event_num < -1)
1488 /* backward from week_view, go to the last event
1489 */
1490 *next_event_num = week_view->events->len - 1;
1491 else if (event_num >= week_view->events->len)
1492 /* forward, out of event range, go to week view widget
1493 */
1494 *next_event_num = -1;
1495 else
1496 *next_event_num = event_num;
1497 return TRUE;
1498 }
1499
1500 static gboolean
1501 e_week_view_focus (GtkWidget *widget,
1502 GtkDirectionType direction)
1503 {
1504 EWeekView *week_view;
1505 gint new_event_num;
1506 gint new_span_num;
1507 gint event_loop;
1508 gboolean editable = FALSE;
1509 static gint last_focus_event_num = -1, last_focus_span_num = -1;
1510
1511 g_return_val_if_fail (widget != NULL, FALSE);
1512 g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
1513
1514 week_view = E_WEEK_VIEW (widget);
1515
1516 e_week_view_check_layout (week_view);
1517
1518 if (week_view->focused_jump_button == E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS) {
1519 last_focus_event_num = week_view->editing_event_num;
1520 last_focus_span_num = week_view->editing_span_num;
1521 }
1522
1523 /* if there is not event, just grab week_view */
1524 if (week_view->events->len == 0) {
1525 gtk_widget_grab_focus (widget);
1526 return TRUE;
1527 }
1528
1529 for (event_loop = 0; event_loop < week_view->events->len;
1530 ++event_loop) {
1531 if (!e_week_view_get_next_tab_event (week_view, direction,
1532 last_focus_event_num,
1533 last_focus_span_num,
1534 &new_event_num,
1535 &new_span_num))
1536 return FALSE;
1537
1538 if (new_event_num == -1) {
1539 /* focus should go to week_view widget
1540 */
1541 gtk_widget_grab_focus (widget);
1542 return TRUE;
1543 }
1544
1545 editable = e_week_view_start_editing_event (
1546 week_view,
1547 new_event_num,
1548 new_span_num,
1549 NULL);
1550 last_focus_event_num = new_event_num;
1551 last_focus_span_num = new_span_num;
1552
1553 if (editable)
1554 break;
1555 else {
1556 /* check if we should go to the jump button */
1557
1558 EWeekViewEvent *event;
1559 EWeekViewEventSpan *span;
1560 gint current_day;
1561
1562 if (!is_array_index_in_bounds (week_view->events, new_event_num))
1563 break;
1564
1565 event = &g_array_index (week_view->events,
1566 EWeekViewEvent,
1567 new_event_num);
1568
1569 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + new_span_num))
1570 break;
1571
1572 span = &g_array_index (week_view->spans,
1573 EWeekViewEventSpan,
1574 event->spans_index + new_span_num);
1575 current_day = span->start_day;
1576
1577 if ((week_view->focused_jump_button != current_day) &&
1578 e_week_view_is_jump_button_visible (week_view, current_day)) {
1579
1580 /* focus go to the jump button */
1581 e_week_view_stop_editing_event (week_view);
1582 gnome_canvas_item_grab_focus (week_view->jump_buttons[current_day]);
1583 return TRUE;
1584 }
1585 }
1586 }
1587 return editable;
1588 }
1589
1590 /* Returns the currently-selected event, or NULL if none */
1591 static GList *
1592 e_week_view_get_selected_events (ECalendarView *cal_view)
1593 {
1594 EWeekViewEvent *event = NULL;
1595 GList *list = NULL;
1596 EWeekView *week_view = (EWeekView *) cal_view;
1597
1598 g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), NULL);
1599
1600 if (week_view->editing_event_num != -1) {
1601 if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
1602 return NULL;
1603
1604 event = &g_array_index (week_view->events, EWeekViewEvent,
1605 week_view->editing_event_num);
1606 } else if (week_view->popup_event_num != -1) {
1607 if (!is_array_index_in_bounds (week_view->events, week_view->popup_event_num))
1608 return NULL;
1609
1610 event = &g_array_index (week_view->events, EWeekViewEvent,
1611 week_view->popup_event_num);
1612 }
1613
1614 if (event)
1615 list = g_list_prepend (list, event);
1616
1617 return list;
1618 }
1619
1620 /* Restarts a query for the week view */
1621 static void
1622 e_week_view_update_query (EWeekView *week_view)
1623 {
1624 gint rows, r;
1625
1626 if (!E_CALENDAR_VIEW (week_view)->in_focus) {
1627 e_week_view_free_events (week_view);
1628 week_view->requires_update = TRUE;
1629 return;
1630 }
1631
1632 gtk_widget_queue_draw (week_view->main_canvas);
1633 e_week_view_free_events (week_view);
1634 e_week_view_queue_layout (week_view);
1635
1636 rows = e_table_model_row_count (E_TABLE_MODEL (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view))));
1637 for (r = 0; r < rows; r++) {
1638 ECalModelComponent *comp_data;
1639
1640 comp_data = e_cal_model_get_component_at (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), r);
1641 if (comp_data == NULL) {
1642 g_warning ("comp_data is NULL\n");
1643 continue;
1644 }
1645 week_view_process_component (week_view, comp_data);
1646 }
1647 }
1648
1649 /* This sets the selected time range. The EWeekView will show the corresponding
1650 * month and the days between start_time and end_time will be selected.
1651 * To select a single day, use the same value for start_time & end_time. */
1652 static void
1653 e_week_view_set_selected_time_range (ECalendarView *cal_view,
1654 time_t start_time,
1655 time_t end_time)
1656 {
1657 GDate date, end_date;
1658 gint num_days;
1659 gboolean update_adjustment_value = FALSE;
1660 EWeekView *week_view = E_WEEK_VIEW (cal_view);
1661
1662 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1663
1664 if (!g_date_valid (&week_view->base_date)) {
1665 /* This view has not been initialized/shown yet, thus skip this. */
1666 return;
1667 }
1668
1669 time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1670
1671 /* Set the selection to the given days. */
1672 week_view->selection_start_day = g_date_get_julian (&date)
1673 - g_date_get_julian (&week_view->base_date);
1674 if (end_time == start_time
1675 || end_time <= time_add_day_with_zone (start_time, 1,
1676 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))))
1677 week_view->selection_end_day = week_view->selection_start_day;
1678 else {
1679 time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1680 week_view->selection_end_day = g_date_get_julian (&end_date)
1681 - g_date_get_julian (&week_view->base_date);
1682 }
1683
1684 /* Make sure the selection is valid. */
1685 num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1686 num_days--;
1687 week_view->selection_start_day = CLAMP (
1688 week_view->selection_start_day, 0, num_days);
1689 week_view->selection_end_day = CLAMP (
1690 week_view->selection_end_day,
1691 week_view->selection_start_day,
1692 num_days);
1693
1694 /* Reset the adjustment value to 0 if the base address has changed.
1695 * Note that we do this after updating first_day_shown so that our
1696 * signal handler will not try to reload the events. */
1697 if (update_adjustment_value) {
1698 GtkRange *range;
1699 GtkAdjustment *adjustment;
1700
1701 range = GTK_RANGE (week_view->vscrollbar);
1702 adjustment = gtk_range_get_adjustment (range);
1703 gtk_adjustment_set_value (adjustment, 0);
1704 }
1705
1706 gtk_widget_queue_draw (week_view->main_canvas);
1707 }
1708
1709 void
1710 e_week_view_set_selected_time_range_visible (EWeekView *week_view,
1711 time_t start_time,
1712 time_t end_time)
1713 {
1714 GDate date, end_date;
1715 gint num_days;
1716
1717 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1718
1719 time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1720
1721 /* Set the selection to the given days. */
1722 week_view->selection_start_day = g_date_get_julian (&date)
1723 - g_date_get_julian (&week_view->first_day_shown);
1724 if (end_time == start_time
1725 || end_time <= time_add_day_with_zone (start_time, 1,
1726 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))))
1727 week_view->selection_end_day = week_view->selection_start_day;
1728 else {
1729 time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1730 week_view->selection_end_day = g_date_get_julian (&end_date)
1731 - g_date_get_julian (&week_view->first_day_shown);
1732 }
1733
1734 /* Make sure the selection is valid. */
1735 num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1736 num_days--;
1737 week_view->selection_start_day = CLAMP (
1738 week_view->selection_start_day,
1739 0, num_days);
1740 week_view->selection_end_day = CLAMP (
1741 week_view->selection_end_day,
1742 week_view->selection_start_day,
1743 num_days);
1744
1745 gtk_widget_queue_draw (week_view->main_canvas);
1746 }
1747
1748 /* Returns the selected time range. */
1749 static gboolean
1750 e_week_view_get_selected_time_range (ECalendarView *cal_view,
1751 time_t *start_time,
1752 time_t *end_time)
1753 {
1754 gint start_day, end_day;
1755 EWeekView *week_view = E_WEEK_VIEW (cal_view);
1756
1757 start_day = week_view->selection_start_day;
1758 end_day = week_view->selection_end_day;
1759
1760 if (start_day == -1) {
1761 start_day = 0;
1762 end_day = 0;
1763 }
1764
1765 if (start_time)
1766 *start_time = week_view->day_starts[start_day];
1767
1768 if (end_time)
1769 *end_time = week_view->day_starts[end_day + 1];
1770
1771 return TRUE;
1772 }
1773
1774 /* Gets the visible time range. Returns FALSE if no time range has been set. */
1775 static gboolean
1776 e_week_view_get_visible_time_range (ECalendarView *cal_view,
1777 time_t *start_time,
1778 time_t *end_time)
1779 {
1780 gint num_days;
1781 EWeekView *week_view = E_WEEK_VIEW (cal_view);
1782
1783 /* If we don't have a valid date set yet, return FALSE. */
1784 if (!g_date_valid (&week_view->first_day_shown))
1785 return FALSE;
1786
1787 num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1788 *start_time = week_view->day_starts[0];
1789 *end_time = week_view->day_starts[num_days];
1790
1791 return TRUE;
1792 }
1793
1794 /* Note that the returned date may be invalid if no date has been set yet. */
1795 void
1796 e_week_view_get_first_day_shown (EWeekView *week_view,
1797 GDate *date)
1798 {
1799 *date = week_view->first_day_shown;
1800 }
1801
1802 /* This sets the first day shown in the view. It will be rounded down to the
1803 * nearest week. */
1804 void
1805 e_week_view_set_first_day_shown (EWeekView *week_view,
1806 GDate *date)
1807 {
1808 GDate base_date;
1809 gint weekday, day_offset, num_days;
1810 gboolean update_adjustment_value = FALSE;
1811 guint32 old_selection_start_julian = 0, old_selection_end_julian = 0;
1812 struct icaltimetype start_tt = icaltime_null_time ();
1813 time_t start_time;
1814
1815 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1816
1817 /* Calculate the old selection range. */
1818 if (week_view->selection_start_day != -1) {
1819 old_selection_start_julian =
1820 g_date_get_julian (&week_view->base_date)
1821 + week_view->selection_start_day;
1822 old_selection_end_julian =
1823 g_date_get_julian (&week_view->base_date)
1824 + week_view->selection_end_day;
1825 }
1826
1827 /* Calculate the weekday of the given date, 0 = Mon. */
1828 weekday = g_date_get_weekday (date) - 1;
1829
1830 /* Convert it to an offset from the start of the display. */
1831 day_offset = (weekday + 7 - week_view->display_start_day) % 7;
1832
1833 /* Calculate the base date, i.e. the first day shown when the
1834 * scrollbar adjustment value is 0. */
1835 base_date = *date;
1836 g_date_subtract_days (&base_date, day_offset);
1837
1838 /* See if we need to update the base date. */
1839 if (!g_date_valid (&week_view->base_date)
1840 || g_date_compare (&week_view->base_date, &base_date)) {
1841 week_view->base_date = base_date;
1842 update_adjustment_value = TRUE;
1843 }
1844
1845 /* See if we need to update the first day shown. */
1846 if (!g_date_valid (&week_view->first_day_shown)
1847 || g_date_compare (&week_view->first_day_shown, &base_date)) {
1848 week_view->first_day_shown = base_date;
1849
1850 start_tt.year = g_date_get_year (&base_date);
1851 start_tt.month = g_date_get_month (&base_date);
1852 start_tt.day = g_date_get_day (&base_date);
1853
1854 start_time = icaltime_as_timet_with_zone (
1855 start_tt,
1856 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1857
1858 e_week_view_recalc_day_starts (week_view, start_time);
1859 e_week_view_update_query (week_view);
1860 }
1861
1862 /* Try to keep the previous selection, but if it is no longer shown
1863 * just select the first day. */
1864 if (week_view->selection_start_day != -1) {
1865 week_view->selection_start_day = old_selection_start_julian
1866 - g_date_get_julian (&base_date);
1867 week_view->selection_end_day = old_selection_end_julian
1868 - g_date_get_julian (&base_date);
1869
1870 /* Make sure the selection is valid. */
1871 num_days = week_view->multi_week_view
1872 ? week_view->weeks_shown * 7 : 7;
1873 num_days--;
1874 week_view->selection_start_day = CLAMP (
1875 week_view->selection_start_day, 0, num_days);
1876 week_view->selection_end_day = CLAMP (
1877 week_view->selection_end_day,
1878 week_view->selection_start_day,
1879 num_days);
1880 }
1881
1882 /* Reset the adjustment value to 0 if the base address has changed.
1883 * Note that we do this after updating first_day_shown so that our
1884 * signal handler will not try to reload the events. */
1885 if (update_adjustment_value) {
1886 GtkRange *range;
1887 GtkAdjustment *adjustment;
1888
1889 range = GTK_RANGE (week_view->vscrollbar);
1890 adjustment = gtk_range_get_adjustment (range);
1891 gtk_adjustment_set_value (adjustment, 0);
1892 }
1893
1894 e_week_view_update_query (week_view);
1895 gtk_widget_queue_draw (week_view->main_canvas);
1896 }
1897
1898 /* Recalculates the time_t corresponding to the start of each day. */
1899 static void
1900 e_week_view_recalc_day_starts (EWeekView *week_view,
1901 time_t lower)
1902 {
1903 gint num_days, day;
1904 time_t tmp_time;
1905
1906 num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1907
1908 tmp_time = lower;
1909 week_view->day_starts[0] = tmp_time;
1910 for (day = 1; day <= num_days; day++) {
1911 tmp_time = time_add_day_with_zone (
1912 tmp_time, 1,
1913 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1914 week_view->day_starts[day] = tmp_time;
1915 }
1916 }
1917
1918 gboolean
1919 e_week_view_get_multi_week_view (EWeekView *week_view)
1920 {
1921 g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
1922
1923 return week_view->multi_week_view;
1924 }
1925
1926 void
1927 e_week_view_set_multi_week_view (EWeekView *week_view,
1928 gboolean multi_week_view)
1929 {
1930 GtkRange *range;
1931 GtkAdjustment *adjustment;
1932 gint page_increment, page_size;
1933
1934 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1935
1936 if (week_view->multi_week_view == multi_week_view)
1937 return;
1938
1939 week_view->multi_week_view = multi_week_view;
1940
1941 if (multi_week_view) {
1942 gtk_widget_show (week_view->titles_canvas);
1943 week_view->month_scroll_by_week = calendar_config_get_month_scroll_by_week ();
1944
1945 calendar_config_add_notification_month_scroll_by_week (month_scrol_by_week_changed_cb, week_view);
1946
1947 if (week_view->month_scroll_by_week) {
1948 page_increment = 1;
1949 page_size = 1;
1950 } else {
1951 page_increment = 4;
1952 page_size = 5;
1953 }
1954 } else {
1955 gtk_widget_hide (week_view->titles_canvas);
1956 page_increment = page_size = 1;
1957
1958 if (week_view->scroll_by_week_notif_id) {
1959 calendar_config_remove_notification (month_scrol_by_week_changed_cb, week_view);
1960 week_view->scroll_by_week_notif_id = 0;
1961 }
1962 }
1963
1964 range = GTK_RANGE (week_view->vscrollbar);
1965 adjustment = gtk_range_get_adjustment (range);
1966 gtk_adjustment_set_page_increment (adjustment, page_increment);
1967 gtk_adjustment_set_page_size (adjustment, page_size);
1968
1969 e_week_view_recalc_display_start_day (week_view);
1970 e_week_view_recalc_cell_sizes (week_view);
1971
1972 if (g_date_valid (&week_view->first_day_shown))
1973 e_week_view_set_first_day_shown (
1974 week_view,
1975 &week_view->first_day_shown);
1976 }
1977
1978 gboolean
1979 e_week_view_get_update_base_date (EWeekView *week_view)
1980 {
1981 g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
1982
1983 return week_view->update_base_date;
1984 }
1985
1986 void
1987 e_week_view_set_update_base_date (EWeekView *week_view,
1988 gboolean update_base_date)
1989 {
1990 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1991
1992 week_view->update_base_date = update_base_date;
1993 }
1994
1995 gint
1996 e_week_view_get_weeks_shown (EWeekView *week_view)
1997 {
1998 g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), 1);
1999
2000 return week_view->weeks_shown;
2001 }
2002
2003 void
2004 e_week_view_set_weeks_shown (EWeekView *week_view,
2005 gint weeks_shown)
2006 {
2007 GtkRange *range;
2008 GtkAdjustment *adjustment;
2009 gint page_increment, page_size;
2010
2011 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
2012
2013 weeks_shown = MIN (weeks_shown, E_WEEK_VIEW_MAX_WEEKS);
2014
2015 if (week_view->weeks_shown == weeks_shown)
2016 return;
2017
2018 week_view->weeks_shown = weeks_shown;
2019
2020 if (week_view->multi_week_view) {
2021 if (week_view->month_scroll_by_week) {
2022 page_increment = 1;
2023 page_size = 1;
2024 } else {
2025 page_increment = 4;
2026 page_size = 5;
2027 }
2028
2029 range = GTK_RANGE (week_view->vscrollbar);
2030 adjustment = gtk_range_get_adjustment (range);
2031 gtk_adjustment_set_page_increment (adjustment, page_increment);
2032 gtk_adjustment_set_page_size (adjustment, page_size);
2033
2034 e_week_view_recalc_cell_sizes (week_view);
2035
2036 if (g_date_valid (&week_view->first_day_shown))
2037 e_week_view_set_first_day_shown (week_view, &week_view->first_day_shown);
2038
2039 e_week_view_update_query (week_view);
2040 }
2041 }
2042
2043 gboolean
2044 e_week_view_get_compress_weekend (EWeekView *week_view)
2045 {
2046 g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
2047
2048 return week_view->compress_weekend;
2049 }
2050
2051 void
2052 e_week_view_set_compress_weekend (EWeekView *week_view,
2053 gboolean compress_weekend)
2054 {
2055 gboolean need_reload = FALSE;
2056
2057 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
2058
2059 if (week_view->compress_weekend == compress_weekend)
2060 return;
2061
2062 week_view->compress_weekend = compress_weekend;
2063
2064 /* The option only affects the month view. */
2065 if (!week_view->multi_week_view)
2066 return;
2067
2068 e_week_view_recalc_cell_sizes (week_view);
2069
2070 need_reload = e_week_view_recalc_display_start_day (week_view);
2071
2072 /* If the display_start_day has changed we need to recalculate the
2073 * date range shown and reload all events, otherwise we only need to
2074 * do a reshape. */
2075 if (need_reload) {
2076 /* Recalculate the days shown and reload if necessary. */
2077 if (g_date_valid (&week_view->first_day_shown))
2078 e_week_view_set_first_day_shown (week_view, &week_view->first_day_shown);
2079 } else {
2080 week_view->events_need_reshape = TRUE;
2081 e_week_view_check_layout (week_view);
2082 }
2083
2084 gtk_widget_queue_draw (week_view->titles_canvas);
2085 gtk_widget_queue_draw (week_view->main_canvas);
2086
2087 g_object_notify (G_OBJECT (week_view), "compress-weekend");
2088 }
2089
2090 /* Whether we display event end times. */
2091 gboolean
2092 e_week_view_get_show_event_end_times (EWeekView *week_view)
2093 {
2094 g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), TRUE);
2095
2096 return week_view->show_event_end_times;
2097 }
2098
2099 void
2100 e_week_view_set_show_event_end_times (EWeekView *week_view,
2101 gboolean show_event_end_times)
2102 {
2103 g_return_if_fail (E_IS_WEEK_VIEW (week_view));
2104
2105 if (week_view->show_event_end_times == show_event_end_times)
2106 return;
2107
2108 week_view->show_event_end_times = show_event_end_times;
2109 e_week_view_recalc_cell_sizes (week_view);
2110 week_view->events_need_reshape = TRUE;
2111 e_week_view_check_layout (week_view);
2112
2113 gtk_widget_queue_draw (week_view->titles_canvas);
2114 gtk_widget_queue_draw (week_view->main_canvas);
2115
2116 g_object_notify (G_OBJECT (week_view), "show-event-end-times");
2117 }
2118
2119 static gboolean
2120 e_week_view_recalc_display_start_day (EWeekView *week_view)
2121 {
2122 ECalModel *model;
2123 gint week_start_day;
2124 gint display_start_day;
2125
2126 model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
2127 week_start_day = e_cal_model_get_week_start_day (model);
2128
2129 /* The display start day defaults to week_start_day, but we have
2130 * to use Saturday if the weekend is compressed and week_start_day
2131 * is Sunday. */
2132 display_start_day = week_start_day;
2133
2134 if (display_start_day == 6
2135 && (!week_view->multi_week_view || week_view->compress_weekend))
2136 display_start_day = 5;
2137
2138 if (week_view->display_start_day != display_start_day) {
2139 week_view->display_start_day = display_start_day;
2140 return TRUE;
2141 }
2142
2143 return FALSE;
2144 }
2145
2146 /* Checks if the users participation status is NEEDS-ACTION and shows the summary as bold text */
2147 static void
2148 set_text_as_bold (EWeekViewEvent *event,
2149 EWeekViewEventSpan *span,
2150 ESourceRegistry *registry)
2151 {
2152 ECalComponent *comp;
2153 GSList *attendees = NULL, *l;
2154 gchar *address;
2155 ECalComponentAttendee *at = NULL;
2156
2157 if (!is_comp_data_valid (event))
2158 return;
2159
2160 comp = e_cal_component_new ();
2161 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
2162 address = itip_get_comp_attendee (
2163 registry, comp, event->comp_data->client);
2164 e_cal_component_get_attendee_list (comp, &attendees);
2165 for (l = attendees; l; l = l->next) {
2166 ECalComponentAttendee *attendee = l->data;
2167
2168 if ((g_str_equal (itip_strip_mailto (attendee->value), address))
2169 || (attendee->sentby && g_str_equal (itip_strip_mailto (attendee->sentby), address))) {
2170 at = attendee;
2171 break;
2172 }
2173 }
2174
2175 /* The attendee has not yet accepted the meeting, display the summary as bolded.
2176 * If the attendee is not present, it might have come through a mailing list.
2177 * In that case, we never show the meeting as bold even if it is unaccepted. */
2178 if (at && (at->status == ICAL_PARTSTAT_NEEDSACTION))
2179 gnome_canvas_item_set (span->text_item, "bold", TRUE, NULL);
2180
2181 e_cal_component_free_attendee_list (attendees);
2182 g_free (address);
2183 g_object_unref (comp);
2184 }
2185
2186 /* This calls a given function for each event instance that matches the given
2187 * uid. Note that it is safe for the callback to remove the event (since we
2188 * step backwards through the arrays). */
2189 static void
2190 e_week_view_foreach_event_with_uid (EWeekView *week_view,
2191 const gchar *uid,
2192 EWeekViewForeachEventCallback callback,
2193 gpointer data)
2194 {
2195 EWeekViewEvent *event;
2196 gint event_num;
2197
2198 for (event_num = week_view->events->len - 1;
2199 event_num >= 0;
2200 event_num--) {
2201 const gchar *u;
2202
2203 event = &g_array_index (week_view->events, EWeekViewEvent,
2204 event_num);
2205
2206 if (!is_comp_data_valid (event))
2207 continue;
2208
2209 u = icalcomponent_get_uid (event->comp_data->icalcomp);
2210 if (u && !strcmp (uid, u)) {
2211 if (!(*callback) (week_view, event_num, data))
2212 return;
2213 }
2214 }
2215 }
2216
2217 static gboolean
2218 e_week_view_remove_event_cb (EWeekView *week_view,
2219 gint event_num,
2220 gpointer data)
2221 {
2222 EWeekViewEvent *event;
2223 EWeekViewEventSpan *span;
2224 gint span_num;
2225
2226 if (!is_array_index_in_bounds (week_view->events, event_num))
2227 return TRUE;
2228
2229 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
2230 if (!event)
2231 return TRUE;
2232
2233 /* If we were editing this event, set editing_event_num to -1 so
2234 * on_editing_stopped doesn't try to update the event. */
2235 if (week_view->editing_event_num == event_num)
2236 week_view->editing_event_num = -1;
2237
2238 if (week_view->popup_event_num == event_num)
2239 week_view->popup_event_num = -1;
2240
2241 if (is_comp_data_valid (event))
2242 g_object_unref (event->comp_data);
2243 event->comp_data = NULL;
2244
2245 if (week_view->spans) {
2246 /* We leave the span elements in the array, but set the canvas item
2247 * pointers to NULL. */
2248 for (span_num = 0; span_num < event->num_spans; span_num++) {
2249 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
2250 break;
2251
2252 span = &g_array_index (week_view->spans, EWeekViewEventSpan,
2253 event->spans_index + span_num);
2254
2255 if (span->text_item) {
2256 g_object_run_dispose (G_OBJECT (span->text_item));
2257 span->text_item = NULL;
2258 }
2259 if (span->background_item) {
2260 g_object_run_dispose (G_OBJECT (span->background_item));
2261 span->background_item = NULL;
2262 }
2263 }
2264
2265 /* Update event_num numbers for already created spans with event_num higher than our event_num */
2266 for (span_num = 0; span_num < week_view->spans->len; span_num++) {
2267 span = &g_array_index (week_view->spans, EWeekViewEventSpan, span_num);
2268
2269 if (span && span->background_item && E_IS_WEEK_VIEW_EVENT_ITEM (span->background_item)) {
2270 EWeekViewEventItem *wveitem = E_WEEK_VIEW_EVENT_ITEM (span->background_item);
2271 gint wveitem_event_num;
2272
2273 wveitem_event_num =
2274 e_week_view_event_item_get_event_num (wveitem);
2275
2276 if (wveitem_event_num > event_num)
2277 e_week_view_event_item_set_event_num (
2278 wveitem, wveitem_event_num - 1);
2279 }
2280 }
2281 }
2282
2283 g_array_remove_index (week_view->events, event_num);
2284
2285 week_view->events_need_layout = TRUE;
2286
2287 return TRUE;
2288 }
2289
2290 void
2291 e_week_view_get_day_position (EWeekView *week_view,
2292 gint day,
2293 gint *day_x,
2294 gint *day_y,
2295 gint *day_w,
2296 gint *day_h)
2297 {
2298 gint cell_x, cell_y, cell_h;
2299
2300 e_week_view_layout_get_day_position (
2301 day,
2302 week_view->multi_week_view,
2303 week_view->weeks_shown,
2304 week_view->display_start_day,
2305 week_view->compress_weekend,
2306 &cell_x, &cell_y, &cell_h);
2307
2308 *day_x = week_view->col_offsets[cell_x];
2309 *day_y = week_view->row_offsets[cell_y];
2310
2311 *day_w = week_view->col_widths[cell_x];
2312 *day_h = week_view->row_heights[cell_y];
2313
2314 while (cell_h > 1) {
2315 *day_h += week_view->row_heights[cell_y + 1];
2316 cell_h--;
2317 cell_y++;
2318 }
2319 }
2320
2321 /* Returns the bounding box for a span of an event. Usually this can easily
2322 * be determined by the start & end days and row of the span, which are set in
2323 * e_week_view_layout_event (). Though we need a special case for the weekends
2324 * when they are compressed, since the span may not fit.
2325 * The bounding box includes the entire width of the days in the view (but
2326 * not the vertical line down the right of the last day), though the displayed
2327 * event doesn't normally extend to the edges of the day.
2328 * It returns FALSE if the span isn't visible. */
2329 gboolean
2330 e_week_view_get_span_position (EWeekView *week_view,
2331 gint event_num,
2332 gint span_num,
2333 gint *span_x,
2334 gint *span_y,
2335 gint *span_w)
2336 {
2337 EWeekViewEvent *event;
2338 EWeekViewEventSpan *span;
2339 gint num_days;
2340 gint start_x, start_y, start_w, start_h;
2341 gint end_x, end_y, end_w, end_h;
2342
2343 g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
2344 g_return_val_if_fail (event_num < week_view->events->len, FALSE);
2345
2346 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
2347
2348 g_return_val_if_fail (span_num < event->num_spans, FALSE);
2349
2350 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
2351 return FALSE;
2352
2353 span = &g_array_index (week_view->spans, EWeekViewEventSpan,
2354 event->spans_index + span_num);
2355
2356 if (!e_week_view_layout_get_span_position (event, span,
2357 week_view->rows_per_cell,
2358 week_view->rows_per_compressed_cell,
2359 week_view->display_start_day,
2360 week_view->multi_week_view,
2361 week_view->compress_weekend,
2362 &num_days)) {
2363 return FALSE;
2364 }
2365
2366 e_week_view_get_day_position (
2367 week_view, span->start_day,
2368 &start_x, &start_y, &start_w, &start_h);
2369 *span_y = start_y + week_view->events_y_offset
2370 + span->row * (week_view->row_height
2371 + E_WEEK_VIEW_EVENT_Y_SPACING);
2372 if (num_days == 1) {
2373 *span_x = start_x;
2374 *span_w = start_w - 1;
2375 } else {
2376 e_week_view_get_day_position (
2377 week_view,
2378 span->start_day + num_days - 1,
2379 &end_x, &end_y, &end_w, &end_h);
2380 *span_x = start_x;
2381 *span_w = end_x + end_w - start_x - 1;
2382 }
2383
2384 return TRUE;
2385 }
2386
2387 static gboolean
2388 ewv_pass_gdkevent_to_etext (EWeekView *week_view,
2389 GdkEvent *gevent)
2390 {
2391 g_return_val_if_fail (week_view != NULL, FALSE);
2392 g_return_val_if_fail (gevent != NULL, FALSE);
2393
2394 if (week_view->editing_event_num != -1 && week_view->editing_span_num != -1) {
2395 EWeekViewEvent *event;
2396 EWeekViewEventSpan *span;
2397
2398 if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
2399 return FALSE;
2400
2401 event = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num);
2402
2403 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + week_view->editing_span_num))
2404 return FALSE;
2405
2406 span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + week_view->editing_span_num);
2407
2408 if (span->text_item && E_IS_TEXT (span->text_item)) {
2409 GNOME_CANVAS_ITEM_GET_CLASS (span->text_item)->event (span->text_item, gevent);
2410 return TRUE;
2411 }
2412 }
2413
2414 return FALSE;
2415 }
2416
2417 static gboolean
2418 e_week_view_on_button_press (GtkWidget *widget,
2419 GdkEventButton *event,
2420 EWeekView *week_view)
2421 {
2422 gint x, y, day;
2423
2424 #if 0
2425 g_print ("In e_week_view_on_button_press\n");
2426 if (event->type == GDK_2BUTTON_PRESS)
2427 g_print (" is a double-click\n");
2428 if (week_view->pressed_event_num != -1)
2429 g_print (" item is pressed\n");
2430 #endif
2431
2432 /* Convert the mouse position to a week & day. */
2433 x = event->x;
2434 y = event->y;
2435 day = e_week_view_convert_position_to_day (week_view, x, y);
2436 if (day == -1)
2437 return FALSE;
2438
2439 if (ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) event))
2440 return TRUE;
2441
2442 /* If an event is pressed just return. */
2443 if (week_view->pressed_event_num != -1)
2444 return FALSE;
2445
2446 if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
2447 time_t dtstart, dtend;
2448
2449 e_calendar_view_get_selected_time_range ((ECalendarView *) week_view, &dtstart, &dtend);
2450 if (dtstart < week_view->before_click_dtend && dtend > week_view->before_click_dtstart) {
2451 e_week_view_set_selected_time_range ((ECalendarView *) week_view, week_view->before_click_dtstart, week_view->before_click_dtend);
2452 }
2453 e_calendar_view_new_appointment_full (E_CALENDAR_VIEW (week_view), FALSE, FALSE, calendar_config_get_prefer_meeting ());
2454 return TRUE;
2455 }
2456
2457 if (event->button == 1) {
2458 GdkWindow *window;
2459
2460 /* Start the selection drag. */
2461 if (!gtk_widget_has_focus (GTK_WIDGET (week_view)) && !gtk_widget_has_focus (GTK_WIDGET (week_view->main_canvas)))
2462 gtk_widget_grab_focus (GTK_WIDGET (week_view));
2463
2464 window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
2465
2466 if (gdk_pointer_grab (window, FALSE,
2467 GDK_POINTER_MOTION_MASK
2468 | GDK_BUTTON_RELEASE_MASK,
2469 NULL, NULL, event->time) == 0) {
2470 if (event->time - week_view->bc_event_time > 250)
2471 e_calendar_view_get_selected_time_range ((ECalendarView *) week_view, &week_view->before_click_dtstart, &week_view->before_click_dtend);
2472 week_view->bc_event_time = event->time;
2473 week_view->selection_start_day = day;
2474 week_view->selection_end_day = day;
2475 week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END;
2476 g_signal_emit_by_name (week_view, "selected_time_changed");
2477
2478 /* FIXME: Optimise? */
2479 gtk_widget_queue_draw (week_view->main_canvas);
2480 }
2481 } else if (event->button == 3) {
2482 if (!gtk_widget_has_focus (GTK_WIDGET (week_view)))
2483 gtk_widget_grab_focus (GTK_WIDGET (week_view));
2484
2485 if (day < week_view->selection_start_day || day > week_view->selection_end_day) {
2486 week_view->selection_start_day = day;
2487 week_view->selection_end_day = day;
2488 week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;
2489
2490 /* FIXME: Optimise? */
2491 gtk_widget_queue_draw (week_view->main_canvas);
2492 }
2493
2494 e_week_view_show_popup_menu (week_view, event, -1);
2495 }
2496
2497 return TRUE;
2498 }
2499
2500 static gboolean
2501 e_week_view_on_button_release (GtkWidget *widget,
2502 GdkEventButton *event,
2503 EWeekView *week_view)
2504 {
2505 #if 0
2506 g_print ("In e_week_view_on_button_release\n");
2507 #endif
2508
2509 if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) {
2510 week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;
2511 gdk_pointer_ungrab (event->time);
2512 } else {
2513 ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) event);
2514 }
2515
2516 return FALSE;
2517 }
2518
2519 static gboolean
2520 e_week_view_on_scroll (GtkWidget *widget,
2521 GdkEventScroll *scroll,
2522 EWeekView *week_view)
2523 {
2524 GtkRange *range;
2525 GtkAdjustment *adjustment;
2526 gdouble page_increment;
2527 gdouble new_value;
2528 gdouble page_size;
2529 gdouble lower;
2530 gdouble upper;
2531 gdouble value;
2532 GtkWidget *tool_window = g_object_get_data (G_OBJECT (week_view), "tooltip-window");
2533 guint timeout;
2534
2535 timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout"));
2536 if (timeout) {
2537 g_source_remove (timeout);
2538 g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL);
2539 }
2540
2541 if (tool_window) {
2542 gtk_widget_destroy (tool_window);
2543 g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL);
2544 }
2545
2546 range = GTK_RANGE (week_view->vscrollbar);
2547 adjustment = gtk_range_get_adjustment (range);
2548
2549 page_increment = gtk_adjustment_get_page_increment (adjustment);
2550 page_size = gtk_adjustment_get_page_size (adjustment);
2551 lower = gtk_adjustment_get_lower (adjustment);
2552 upper = gtk_adjustment_get_upper (adjustment);
2553 value = gtk_adjustment_get_value (adjustment);
2554
2555 switch (scroll->direction) {
2556 case GDK_SCROLL_UP:
2557 new_value = value - page_increment;
2558 break;
2559 case GDK_SCROLL_DOWN:
2560 new_value = value + page_increment;
2561 break;
2562 #if GTK_CHECK_VERSION(3,3,18)
2563 case GDK_SCROLL_SMOOTH:
2564 if (scroll->delta_y < -0.001 || scroll->delta_y > 0.001) {
2565 new_value = value + scroll->delta_y * page_increment;
2566 break;
2567 }
2568 return FALSE;
2569 #endif
2570 default:
2571 return FALSE;
2572 }
2573
2574 new_value = CLAMP (new_value, lower, upper - page_size);
2575 gtk_adjustment_set_value (adjustment, new_value);
2576
2577 return TRUE;
2578 }
2579
2580 static gboolean
2581 e_week_view_on_motion (GtkWidget *widget,
2582 GdkEventMotion *mevent,
2583 EWeekView *week_view)
2584 {
2585 gint x, y, day;
2586
2587 #if 0
2588 g_print ("In e_week_view_on_motion\n");
2589 #endif
2590
2591 /* Convert the mouse position to a week & day. */
2592 x = mevent->x;
2593 y = mevent->y;
2594 day = e_week_view_convert_position_to_day (week_view, x, y);
2595 if (day == -1)
2596 return FALSE;
2597
2598 if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) {
2599 e_week_view_update_selection (week_view, day);
2600 return TRUE;
2601 }
2602
2603 ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) mevent);
2604
2605 return FALSE;
2606 }
2607
2608 /* Converts a position in the canvas window to a day offset from the first
2609 * day displayed. Returns -1 if the position is outside the grid. */
2610 static gint
2611 e_week_view_convert_position_to_day (EWeekView *week_view,
2612 gint x,
2613 gint y)
2614 {
2615 gint col, row, grid_x = -1, grid_y = -1, week, day;
2616 gint weekend_col;
2617
2618 /* First we convert it to a grid position. */
2619 for (col = 0; col <= week_view->columns; col++) {
2620 if (x < week_view->col_offsets[col]) {
2621 grid_x = col - 1;
2622 break;
2623 }
2624 }
2625
2626 for (row = 0; row <= week_view->rows; row++) {
2627 if (y < week_view->row_offsets[row]) {
2628 grid_y = row - 1;
2629 break;
2630 }
2631 }
2632
2633 /* If the mouse is outside the grid return FALSE. */
2634 if (grid_x == -1 || grid_y == -1)
2635 return -1;
2636
2637 /* Now convert the grid position to a week and day. */
2638 if (week_view->multi_week_view) {
2639 week = grid_y / 2;
2640 day = grid_x;
2641
2642 if (week_view->compress_weekend) {
2643 weekend_col = (5 + 7 - week_view->display_start_day) % 7;
2644 if (grid_x > weekend_col
2645 || (grid_x == weekend_col && grid_y % 2 == 1))
2646 day++;
2647 }
2648 } else {
2649 week = 0;
2650
2651 for (day = 0; day < 7; day++) {
2652 gint day_x = 0, day_y = 0, rows = 0;
2653 e_week_view_layout_get_day_position (
2654 day, FALSE, 1,
2655 week_view->display_start_day,
2656 week_view->compress_weekend,
2657 &day_x, &day_y, &rows);
2658
2659 if (grid_x == day_x && grid_y >= day_y && grid_y < day_y + rows)
2660 break;
2661 }
2662
2663 if (day == 7)
2664 return -1;
2665 }
2666
2667 return week * 7 + day;
2668 }
2669
2670 static void
2671 e_week_view_update_selection (EWeekView *week_view,
2672 gint day)
2673 {
2674 gint tmp_day;
2675 gboolean need_redraw = FALSE;
2676
2677 #if 0
2678 g_print ("Updating selection %i,%i\n", week, day);
2679 #endif
2680
2681 if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START) {
2682 if (day != week_view->selection_start_day) {
2683 need_redraw = TRUE;
2684 week_view->selection_start_day = day;
2685 }
2686 } else {
2687 if (day != week_view->selection_end_day) {
2688 need_redraw = TRUE;
2689 week_view->selection_end_day = day;
2690 }
2691 }
2692
2693 /* Switch the drag position if necessary. */
2694 if (week_view->selection_start_day > week_view->selection_end_day) {
2695 tmp_day = week_view->selection_start_day;
2696 week_view->selection_start_day = week_view->selection_end_day;
2697 week_view->selection_end_day = tmp_day;
2698 if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START)
2699 week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END;
2700 else
2701 week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_START;
2702 }
2703
2704 /* FIXME: Optimise? */
2705 if (need_redraw) {
2706 gtk_widget_queue_draw (week_view->main_canvas);
2707 }
2708 }
2709
2710 static void
2711 e_week_view_free_events (EWeekView *week_view)
2712 {
2713 EWeekViewEvent *event;
2714 EWeekViewEventSpan *span;
2715 gint event_num, span_num, num_days, day;
2716
2717 /* Reset all our indices. */
2718 week_view->pressed_event_num = -1;
2719 week_view->pressed_span_num = -1;
2720 week_view->editing_event_num = -1;
2721 week_view->editing_span_num = -1;
2722 week_view->popup_event_num = -1;
2723
2724 for (event_num = 0; event_num < week_view->events->len; event_num++) {
2725 event = &g_array_index (week_view->events, EWeekViewEvent,
2726 event_num);
2727
2728 if (is_comp_data_valid (event))
2729 g_object_unref (event->comp_data);
2730 }
2731
2732 g_array_set_size (week_view->events, 0);
2733
2734 /* Destroy all the old canvas items. */
2735 if (week_view->spans) {
2736 for (span_num = 0; span_num < week_view->spans->len;
2737 span_num++) {
2738 span = &g_array_index (week_view->spans,
2739 EWeekViewEventSpan, span_num);
2740 if (span->background_item)
2741 g_object_run_dispose (G_OBJECT (span->background_item));
2742 if (span->text_item)
2743 g_object_run_dispose (G_OBJECT (span->text_item));
2744 }
2745 g_array_free (week_view->spans, TRUE);
2746 week_view->spans = NULL;
2747 }
2748
2749 /* Clear the number of rows used per day. */
2750 num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
2751 for (day = 0; day <= num_days; day++) {
2752 week_view->rows_per_day[day] = 0;
2753 }
2754
2755 /* Hide all the jump buttons. */
2756 for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) {
2757 gnome_canvas_item_hide (week_view->jump_buttons[day]);
2758 }
2759 }
2760
2761 /* This adds one event to the view, adding it to the appropriate array. */
2762 static gboolean
2763 e_week_view_add_event (ECalComponent *comp,
2764 time_t start,
2765 time_t end,
2766 gboolean prepend,
2767 gpointer data)
2768
2769 {
2770 AddEventData *add_event_data;
2771 EWeekViewEvent event;
2772 gint num_days;
2773 struct icaltimetype start_tt, end_tt;
2774
2775 add_event_data = data;
2776
2777 /* Check that the event times are valid. */
2778 num_days = add_event_data->week_view->multi_week_view ? add_event_data->week_view->weeks_shown * 7 : 7;
2779
2780 /*if (start > end || start >= add_event_data->week_view->day_starts[num_days] || end <= add_event_data->week_view->day_starts[0]) {
2781 g_print ("%s: week_view:%p\n", G_STRFUNC, add_event_data->week_view);
2782 g_print ("\tstart: %s", ctime (&start));
2783 g_print ("\tend: %s", ctime (&end));
2784 g_print ("\tday_starts[0]: %s", ctime (&add_event_data->week_view->day_starts[0]));
2785 g_print ("\tday_starts[%d]: %s\n", num_days, ctime (&add_event_data->week_view->day_starts[num_days]));
2786 }*/
2787
2788 g_return_val_if_fail (start <= end, TRUE);
2789 g_return_val_if_fail (start < add_event_data->week_view->day_starts[num_days], TRUE);
2790 g_return_val_if_fail (end > add_event_data->week_view->day_starts[0], TRUE);
2791
2792 start_tt = icaltime_from_timet_with_zone (
2793 start, FALSE,
2794 e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view)));
2795 end_tt = icaltime_from_timet_with_zone (
2796 end, FALSE,
2797 e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view)));
2798
2799 if (add_event_data->comp_data) {
2800 event.comp_data = g_object_ref (add_event_data->comp_data);
2801 } else {
2802 event.comp_data = g_object_new (E_TYPE_CAL_MODEL_COMPONENT, NULL);
2803
2804 event.comp_data->client = g_object_ref (e_cal_model_get_default_client (e_calendar_view_get_model (E_CALENDAR_VIEW (add_event_data->week_view))));
2805 e_cal_component_abort_sequence (comp);
2806 event.comp_data->icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2807 }
2808 event.start = start;
2809 event.end = end;
2810 event.tooltip = NULL;
2811 event.timeout = -1;
2812 event.color = NULL;
2813 event.spans_index = 0;
2814 event.num_spans = 0;
2815 event.comp_data->instance_start = start;
2816 event.comp_data->instance_end = end;
2817
2818 event.start_minute = start_tt.hour * 60 + start_tt.minute;
2819 event.end_minute = end_tt.hour * 60 + end_tt.minute;
2820 if (event.end_minute == 0 && start != end)
2821 event.end_minute = 24 * 60;
2822
2823 event.different_timezone = FALSE;
2824 if (!cal_comp_util_compare_event_timezones (
2825 comp,
2826 event.comp_data->client,
2827 e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view))))
2828 event.different_timezone = TRUE;
2829
2830 if (prepend)
2831 g_array_prepend_val (add_event_data->week_view->events, event);
2832 else
2833 g_array_append_val (add_event_data->week_view->events, event);
2834 add_event_data->week_view->events_sorted = FALSE;
2835 add_event_data->week_view->events_need_layout = TRUE;
2836
2837 return TRUE;
2838 }
2839
2840 /* This lays out the events, or reshapes them, as necessary. */
2841 static void
2842 e_week_view_check_layout (EWeekView *week_view)
2843 {
2844 /* Don't bother if we aren't visible. */
2845 if (!E_CALENDAR_VIEW (week_view)->in_focus) {
2846 e_week_view_free_events (week_view);
2847 week_view->requires_update = TRUE;
2848 return;
2849 }
2850
2851 /* Make sure the events are sorted (by start and size). */
2852 e_week_view_ensure_events_sorted (week_view);
2853
2854 if (week_view->events_need_layout)
2855 week_view->spans = e_week_view_layout_events
2856 (week_view->events, week_view->spans,
2857 week_view->multi_week_view,
2858 week_view->weeks_shown,
2859 week_view->compress_weekend,
2860 week_view->display_start_day,
2861 week_view->day_starts,
2862 week_view->rows_per_day);
2863
2864 if (week_view->events_need_layout || week_view->events_need_reshape)
2865 e_week_view_reshape_events (week_view);
2866
2867 week_view->events_need_layout = FALSE;
2868 week_view->events_need_reshape = FALSE;
2869 }
2870
2871 static void
2872 e_week_view_ensure_events_sorted (EWeekView *week_view)
2873 {
2874 if (!week_view->events_sorted) {
2875 qsort (
2876 week_view->events->data,
2877 week_view->events->len,
2878 sizeof (EWeekViewEvent),
2879 e_week_view_event_sort_func);
2880 week_view->events_sorted = TRUE;
2881 }
2882 }
2883
2884 gint
2885 e_week_view_event_sort_func (gconstpointer arg1,
2886 gconstpointer arg2)
2887 {
2888 EWeekViewEvent *event1, *event2;
2889
2890 event1 = (EWeekViewEvent *) arg1;
2891 event2 = (EWeekViewEvent *) arg2;
2892
2893 if (event1->start < event2->start)
2894 return -1;
2895 if (event1->start > event2->start)
2896 return 1;
2897
2898 if (event1->end > event2->end)
2899 return -1;
2900 if (event1->end < event2->end)
2901 return 1;
2902
2903 return 0;
2904 }
2905
2906 static void
2907 e_week_view_reshape_events (EWeekView *week_view)
2908 {
2909 EWeekViewEvent *event;
2910 gint event_num, span_num;
2911 gint num_days, day, day_x, day_y, day_w, day_h, max_rows;
2912 gboolean is_weekend;
2913
2914 for (event_num = 0; event_num < week_view->events->len; event_num++) {
2915 event = &g_array_index (week_view->events, EWeekViewEvent,
2916 event_num);
2917 if (!is_comp_data_valid (event))
2918 continue;
2919
2920 for (span_num = 0; span_num < event->num_spans; span_num++) {
2921 gchar *current_comp_string;
2922
2923 e_week_view_reshape_event_span (
2924 week_view, event_num, span_num);
2925 if (week_view->last_edited_comp_string == NULL)
2926 continue;
2927 current_comp_string = icalcomponent_as_ical_string_r (event->comp_data->icalcomp);
2928 if (strncmp (current_comp_string, week_view->last_edited_comp_string,50) == 0) {
2929 EWeekViewEventSpan *span;
2930
2931 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) {
2932 g_free (current_comp_string);
2933 continue;
2934 }
2935
2936 span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num);
2937 e_canvas_item_grab_focus (span->text_item, TRUE);
2938 g_free (week_view->last_edited_comp_string);
2939 week_view->last_edited_comp_string = NULL;
2940 }
2941 g_free (current_comp_string);
2942 }
2943 }
2944
2945 /* Reshape the jump buttons and show/hide them as appropriate. */
2946 num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
2947 for (day = 0; day < num_days; day++) {
2948
2949 is_weekend = ((week_view->display_start_day + day) % 7 >= 5) ? TRUE : FALSE;
2950 if (!is_weekend || (week_view->multi_week_view
2951 && !week_view->compress_weekend))
2952 max_rows = week_view->rows_per_cell;
2953 else
2954 max_rows = week_view->rows_per_compressed_cell;
2955
2956 /* Determine whether the jump button should be shown. */
2957 if (week_view->rows_per_day[day] <= max_rows) {
2958 gnome_canvas_item_hide (week_view->jump_buttons[day]);
2959 } else {
2960 cairo_matrix_t matrix;
2961
2962 e_week_view_get_day_position (
2963 week_view, day,
2964 &day_x, &day_y,
2965 &day_w, &day_h);
2966
2967 cairo_matrix_init_translate (
2968 &matrix,
2969 day_x + day_w - E_WEEK_VIEW_JUMP_BUTTON_X_PAD - E_WEEK_VIEW_JUMP_BUTTON_WIDTH,
2970 day_y + day_h - E_WEEK_VIEW_JUMP_BUTTON_Y_PAD - E_WEEK_VIEW_JUMP_BUTTON_HEIGHT);
2971 gnome_canvas_item_set_matrix (week_view->jump_buttons[day], &matrix);
2972
2973 gnome_canvas_item_show (week_view->jump_buttons[day]);
2974 gnome_canvas_item_raise_to_top (week_view->jump_buttons[day]);
2975 }
2976 }
2977
2978 for (day = num_days; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) {
2979 gnome_canvas_item_hide (week_view->jump_buttons[day]);
2980 }
2981 }
2982
2983 static EWeekViewEvent *
2984 tooltip_get_view_event (EWeekView *week_view,
2985 gint day,
2986 gint event_num)
2987 {
2988 EWeekViewEvent *pevent;
2989
2990 if (!is_array_index_in_bounds (week_view->events, event_num))
2991 return NULL;
2992
2993 pevent = &g_array_index (week_view->events, EWeekViewEvent, event_num);
2994
2995 return pevent;
2996 }
2997
2998 static void
2999 tooltip_destroy (EWeekView *week_view,
3000 GnomeCanvasItem *item)
3001 {
3002 gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
3003 EWeekViewEvent *pevent;
3004 guint timeout;
3005
3006 timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout"));
3007 if (timeout) {
3008 g_source_remove (timeout);
3009 g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL);
3010 }
3011
3012 pevent = tooltip_get_view_event (week_view, -1, event_num);
3013 if (pevent) {
3014 if (pevent->tooltip && g_object_get_data (G_OBJECT (week_view), "tooltip-window")) {
3015 gtk_widget_destroy (pevent->tooltip);
3016 pevent->tooltip = NULL;
3017 }
3018
3019 g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL);
3020 }
3021 }
3022
3023 static gboolean
3024 tooltip_event_cb (GnomeCanvasItem *item,
3025 GdkEvent *event,
3026 EWeekView *view)
3027 {
3028 gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
3029 EWeekViewEvent *pevent;
3030
3031 pevent = tooltip_get_view_event (view, -1, event_num);
3032
3033 switch (event->type) {
3034 case GDK_ENTER_NOTIFY:
3035 if (view->editing_event_num == -1) {
3036 ECalendarViewEventData *data;
3037
3038 data = g_malloc (sizeof (ECalendarViewEventData));
3039
3040 pevent->x = ((GdkEventCrossing *) event)->x_root;
3041 pevent->y = ((GdkEventCrossing *) event)->y_root;
3042 pevent->tooltip = NULL;
3043
3044 data->cal_view = (ECalendarView *) view;
3045 data->day = -1;
3046 data->event_num = event_num;
3047 data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event;
3048 pevent->timeout = g_timeout_add_full (
3049 G_PRIORITY_DEFAULT, 500,
3050 (GSourceFunc) e_calendar_view_get_tooltips,
3051 data, (GDestroyNotify) g_free);
3052 g_object_set_data ((GObject *) view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout));
3053
3054 return TRUE;
3055 } else {
3056 return FALSE;
3057 }
3058 case GDK_MOTION_NOTIFY:
3059 pevent->x = ((GdkEventMotion *) event)->x_root;
3060 pevent->y = ((GdkEventMotion *) event)->y_root;
3061 pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (view), "tooltip-window");
3062
3063 if (pevent->tooltip) {
3064 e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16);
3065 }
3066
3067 return TRUE;
3068 case GDK_LEAVE_NOTIFY:
3069 case GDK_KEY_PRESS:
3070 case GDK_BUTTON_PRESS:
3071 tooltip_destroy (view, item);
3072 default:
3073 return FALSE;
3074 }
3075 }
3076
3077 static const gchar *
3078 get_comp_summary (ECalClient *client,
3079 icalcomponent *icalcomp,
3080 gboolean *free_text)
3081 {
3082 const gchar *my_summary, *location;
3083 const gchar *summary;
3084 gboolean my_free_text = FALSE;
3085
3086 g_return_val_if_fail (icalcomp != NULL && free_text != NULL, NULL);
3087
3088 my_summary = e_calendar_view_get_icalcomponent_summary (client, icalcomp, &my_free_text);
3089
3090 location = icalcomponent_get_location (icalcomp);
3091 if (location && *location) {
3092 *free_text = TRUE;
3093 summary = g_strdup_printf ("%s (%s)", my_summary, location);
3094
3095 if (my_free_text)
3096 g_free ((gchar *) my_summary);
3097 } else {
3098 *free_text = my_free_text;
3099 summary = my_summary;
3100 }
3101
3102 return summary;
3103 }
3104
3105 static void
3106 e_week_view_reshape_event_span (EWeekView *week_view,
3107 gint event_num,
3108 gint span_num)
3109 {
3110 ECalendarView *cal_view;
3111 ECalModel *model;
3112 ESourceRegistry *registry;
3113 EWeekViewEvent *event;
3114 EWeekViewEventSpan *span;
3115 gint span_x, span_y, span_w, num_icons, icons_width, time_width;
3116 gint min_text_x, max_text_w, width;
3117 gboolean show_icons = TRUE, use_max_width = FALSE;
3118 gboolean one_day_event;
3119 ECalComponent *comp;
3120 gdouble text_x, text_y, text_w, text_h;
3121 gchar *text, *end_of_line;
3122 gint line_len, text_width;
3123 PangoFontDescription *font_desc;
3124 PangoContext *pango_context;
3125 PangoFontMetrics *font_metrics;
3126 PangoLayout *layout;
3127
3128 cal_view = E_CALENDAR_VIEW (week_view);
3129 model = e_calendar_view_get_model (cal_view);
3130
3131 registry = e_cal_model_get_registry (model);
3132
3133 if (!is_array_index_in_bounds (week_view->events, event_num))
3134 return;
3135
3136 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3137
3138 if (!is_comp_data_valid (event))
3139 return;
3140
3141 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
3142 return;
3143
3144 span = &g_array_index (week_view->spans, EWeekViewEventSpan,
3145 event->spans_index + span_num);
3146 comp = e_cal_component_new ();
3147 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
3148
3149 one_day_event = e_week_view_is_one_day_event (week_view, event_num);
3150
3151 /* If the span will not be visible destroy the canvas items and
3152 * return. */
3153 if (!e_week_view_get_span_position (week_view, event_num, span_num,
3154 &span_x, &span_y, &span_w)) {
3155 if (span->background_item)
3156 g_object_run_dispose (G_OBJECT (span->background_item));
3157 if (span->text_item)
3158 g_object_run_dispose (G_OBJECT (span->text_item));
3159 span->background_item = NULL;
3160 span->text_item = NULL;
3161
3162 g_object_unref (comp);
3163 return;
3164 }
3165
3166 /* Set up Pango prerequisites */
3167 font_desc = gtk_widget_get_style (GTK_WIDGET (week_view))->font_desc;
3168 pango_context = gtk_widget_get_pango_context (GTK_WIDGET (week_view));
3169 font_metrics = pango_context_get_metrics (
3170 pango_context, font_desc,
3171 pango_context_get_language (pango_context));
3172 layout = pango_layout_new (pango_context);
3173
3174 /* If we are editing a long event we don't show the icons and the EText
3175 * item uses the maximum width available. */
3176 if (!one_day_event && week_view->editing_event_num == event_num
3177 && week_view->editing_span_num == span_num) {
3178 show_icons = FALSE;
3179 use_max_width = TRUE;
3180 }
3181
3182 /* Calculate how many icons we need to show. */
3183 num_icons = 0;
3184 if (show_icons) {
3185 if (e_cal_component_has_alarms (comp))
3186 num_icons++;
3187 if (e_cal_component_has_recurrences (comp) || e_cal_component_is_instance (comp))
3188 num_icons++;
3189 if (e_cal_component_has_attachments (comp))
3190 num_icons++;
3191 if (e_cal_component_has_attendees (comp))
3192 num_icons++;
3193 if (event->different_timezone)
3194 num_icons++;
3195 num_icons += cal_comp_util_get_n_icons (comp, NULL);
3196 }
3197
3198 /* Create the background canvas item if necessary. */
3199 if (!span->background_item) {
3200 span->background_item =
3201 gnome_canvas_item_new (
3202 GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root),
3203 e_week_view_event_item_get_type (),
3204 NULL);
3205 }
3206
3207 g_object_set_data ((GObject *) span->background_item, "event-num", GINT_TO_POINTER (event_num));
3208 g_signal_connect (
3209 span->background_item, "event",
3210 G_CALLBACK (tooltip_event_cb), week_view);
3211
3212 gnome_canvas_item_set (
3213 span->background_item,
3214 "event_num", event_num,
3215 "span_num", span_num,
3216 NULL);
3217
3218 /* Create the text item if necessary. */
3219 if (!span->text_item) {
3220 const gchar *summary;
3221 GtkWidget *widget;
3222 GdkColor color;
3223 gboolean free_text = FALSE;
3224
3225 widget = (GtkWidget *) week_view;
3226
3227 color = e_week_view_get_text_color (week_view, event, widget);
3228 summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text);
3229
3230 span->text_item =
3231 gnome_canvas_item_new (
3232 GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root),
3233 e_text_get_type (),
3234 "clip", TRUE,
3235 "max_lines", 1,
3236 "editable", TRUE,
3237 "text", summary ? summary : "",
3238 "use_ellipsis", TRUE,
3239 "fill_color_gdk", &color,
3240 "im_context", E_CANVAS (week_view->main_canvas)->im_context,
3241 NULL);
3242
3243 if (free_text)
3244 g_free ((gchar *) summary);
3245
3246 if (e_client_check_capability (E_CLIENT (event->comp_data->client), CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING)
3247 && e_cal_util_component_has_attendee (event->comp_data->icalcomp)) {
3248 set_text_as_bold (event, span, registry);
3249 }
3250 g_object_set_data (G_OBJECT (span->text_item), "event-num", GINT_TO_POINTER (event_num));
3251 g_signal_connect (
3252 span->text_item, "event",
3253 G_CALLBACK (e_week_view_on_text_item_event), week_view);
3254 g_signal_emit_by_name (
3255 G_OBJECT (week_view),
3256 "event_added", event);
3257
3258 }
3259
3260 /* Calculate the position of the text item.
3261 * For events < 1 day it starts after the times & icons and ends at the
3262 * right edge of the span.
3263 * For events >= 1 day we need to determine whether times are shown at
3264 * the start and end of the span, then try to center the text item with
3265 * the icons in the middle, but making sure we don't go over the times.
3266 */
3267
3268 /* Calculate the space necessary to display a time, e.g. "13:00". */
3269 time_width = e_week_view_get_time_string_width (week_view);
3270
3271 /* Calculate the space needed for the icons. */
3272 icons_width = (E_WEEK_VIEW_ICON_WIDTH + E_WEEK_VIEW_ICON_X_PAD)
3273 * num_icons - E_WEEK_VIEW_ICON_X_PAD + E_WEEK_VIEW_ICON_R_PAD;
3274
3275 /* The y position and height are the same for both event types. */
3276 text_y = span_y + E_WEEK_VIEW_EVENT_BORDER_HEIGHT
3277 + E_WEEK_VIEW_EVENT_TEXT_Y_PAD;
3278
3279 text_h =
3280 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
3281 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
3282
3283 if (one_day_event) {
3284 /* Note that 1-day events don't have a border. Although we
3285 * still use the border height to position the events
3286 * vertically so they still line up neatly (see above),
3287 * we don't use the border width or edge padding at all. */
3288 text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD;
3289
3290 switch (week_view->time_format) {
3291 case E_WEEK_VIEW_TIME_BOTH_SMALL_MIN:
3292 case E_WEEK_VIEW_TIME_BOTH:
3293 /* These have 2 time strings with a small space between
3294 * them and some space before the EText item. */
3295 text_x += time_width * 2
3296 + E_WEEK_VIEW_EVENT_TIME_SPACING
3297 + E_WEEK_VIEW_EVENT_TIME_X_PAD;
3298 break;
3299 case E_WEEK_VIEW_TIME_START_SMALL_MIN:
3300 case E_WEEK_VIEW_TIME_START:
3301 /* These have just 1 time string with some space
3302 * before the EText item. */
3303 text_x += time_width + E_WEEK_VIEW_EVENT_TIME_X_PAD;
3304 break;
3305 case E_WEEK_VIEW_TIME_NONE:
3306 break;
3307 }
3308
3309 /* The icons_width includes space on the right of the icons. */
3310 text_x += icons_width;
3311
3312 /* The width of the EText item extends right to the edge of the
3313 * event, just inside the border. */
3314 text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD - text_x;
3315
3316 } else {
3317 if (use_max_width) {
3318 /* When we are editing the event we use all the
3319 * available width. */
3320 text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD
3321 + E_WEEK_VIEW_EVENT_BORDER_WIDTH
3322 + E_WEEK_VIEW_EVENT_EDGE_X_PAD;
3323 text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD
3324 - E_WEEK_VIEW_EVENT_BORDER_WIDTH
3325 - E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x;
3326 } else {
3327 text = NULL;
3328 /* Get the width of the text of the event. This is a
3329 * bit of a hack. It would be better if EText could
3330 * tell us this. */
3331 g_object_get (span->text_item, "text", &text, NULL);
3332 text_width = 0;
3333 if (text) {
3334 /* It should only have one line of text in it.
3335 * I'm not sure we need this any more. */
3336 end_of_line = strchr (text, '\n');
3337 if (end_of_line)
3338 line_len = end_of_line - text;
3339 else
3340 line_len = strlen (text);
3341
3342 pango_layout_set_text (layout, text, line_len);
3343 pango_layout_get_pixel_size (layout, &text_width, NULL);
3344 g_free (text);
3345 }
3346
3347 /* Add on the width of the icons and find the default
3348 * position, which centers the icons + text. */
3349 width = text_width + icons_width;
3350 text_x = span_x + (span_w - width) / 2;
3351
3352 /* Now calculate the left-most valid position, and make
3353 * sure we don't go to the left of that. */
3354 min_text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD
3355 + E_WEEK_VIEW_EVENT_BORDER_WIDTH
3356 + E_WEEK_VIEW_EVENT_EDGE_X_PAD;
3357 /* See if we will want to display the start time, and
3358 * if so take that into account. */
3359 if (event->start > week_view->day_starts[span->start_day])
3360 min_text_x += time_width
3361 + E_WEEK_VIEW_EVENT_TIME_X_PAD;
3362
3363 /* Now make sure we don't go to the left of the minimum
3364 * position. */
3365 text_x = MAX (text_x, min_text_x);
3366
3367 /* Now calculate the largest valid width, using the
3368 * calculated x position, and make sure we don't
3369 * exceed that. */
3370 max_text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD
3371 - E_WEEK_VIEW_EVENT_BORDER_WIDTH
3372 - E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x;
3373 if (event->end < week_view->day_starts[span->start_day
3374 + span->num_days])
3375 max_text_w -= time_width
3376 + E_WEEK_VIEW_EVENT_TIME_X_PAD;
3377
3378 text_w = MIN (width, max_text_w);
3379
3380 /* Now take out the space for the icons. */
3381 text_x += icons_width;
3382 text_w -= icons_width;
3383 }
3384 }
3385
3386 /* Make sure we don't try to use a negative width. */
3387 text_w = MAX (text_w, 0);
3388
3389 gnome_canvas_item_set (
3390 span->text_item,
3391 "clip_width", (gdouble) text_w,
3392 "clip_height", (gdouble) text_h,
3393 NULL);
3394 e_canvas_item_move_absolute (span->text_item, text_x, text_y);
3395
3396 g_object_unref (comp);
3397 g_object_unref (layout);
3398 pango_font_metrics_unref (font_metrics);
3399 }
3400
3401 gboolean
3402 e_week_view_start_editing_event (EWeekView *week_view,
3403 gint event_num,
3404 gint span_num,
3405 gchar *initial_text)
3406 {
3407 EWeekViewEvent *event;
3408 EWeekViewEventSpan *span;
3409 ETextEventProcessor *event_processor = NULL;
3410 ETextEventProcessorCommand command;
3411 ECalModelComponent *comp_data;
3412
3413 /* If we are already editing the event, just return. */
3414 if (event_num == week_view->editing_event_num
3415 && span_num == week_view->editing_span_num)
3416 return TRUE;
3417
3418 if (!is_array_index_in_bounds (week_view->events, event_num))
3419 return FALSE;
3420
3421 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3422
3423 if (!is_comp_data_valid (event))
3424 return FALSE;
3425
3426 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
3427 return FALSE;
3428
3429 span = &g_array_index (week_view->spans, EWeekViewEventSpan,
3430 event->spans_index + span_num);
3431
3432 if (e_client_is_readonly (E_CLIENT (event->comp_data->client)))
3433 return FALSE;
3434
3435 /* If the event is not shown, don't try to edit it. */
3436 if (!span->text_item)
3437 return FALSE;
3438
3439 if (week_view->editing_event_num >= 0) {
3440 EWeekViewEvent *editing;
3441
3442 if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
3443 return FALSE;
3444
3445 editing = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num);
3446
3447 /* do not change to other part of same component - the event is spread into more days */
3448 if (editing && event && editing->comp_data == event->comp_data)
3449 return FALSE;
3450 }
3451
3452 gnome_canvas_item_set (
3453 span->text_item,
3454 "text", initial_text ? initial_text : icalcomponent_get_summary (event->comp_data->icalcomp),
3455 NULL);
3456
3457 /* Save the comp_data value because we use that as our invariant */
3458 comp_data = event->comp_data;
3459
3460 e_canvas_item_grab_focus (span->text_item, TRUE);
3461
3462 /* If the above focus caused things to redraw, then find the
3463 * the event and the span again */
3464 if (event_num < week_view->events->len)
3465 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3466
3467 if (event_num >= week_view->events->len || event->comp_data != comp_data) {
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
3468 /* When got in because of other comp_data, then be sure we go through all events */
3469 event_num = week_view->events->len;
3470
3471 /* Unfocussing can cause a removal but not a new
3472 * addition so just run backwards through the
3473 * events */
3474 for (event_num--; event_num >= 0; event_num--) {
3475 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3476 if (event->comp_data == comp_data)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
3477 break;
3478 }
3479 g_return_val_if_fail (event_num >= 0, FALSE);
3480 }
3481
3482 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
3483 return FALSE;
3484
3485 span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num);
3486
3487 /* Try to move the cursor to the end of the text. */
3488 g_object_get (span->text_item, "event_processor", &event_processor, NULL);
3489 if (event_processor) {
3490 command.action = E_TEP_MOVE;
3491 command.position = E_TEP_END_OF_BUFFER;
3492 g_signal_emit_by_name (
3493 event_processor,
3494 "command", &command);
3495 }
3496 return TRUE;
3497 }
3498
3499 /* This stops any current edit. */
3500 void
3501 e_week_view_stop_editing_event (EWeekView *week_view)
3502 {
3503 GtkWidget *toplevel;
3504
3505 /* Check we are editing an event. */
3506 if (week_view->editing_event_num == -1)
3507 return;
3508
3509 /* Set focus to the toplevel so the item loses focus. */
3510 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (week_view));
3511 if (toplevel && GTK_IS_WINDOW (toplevel))
3512 gtk_window_set_focus (GTK_WINDOW (toplevel), NULL);
3513 }
3514
3515 /* Cancels the current edition by resetting the appointment's text to its original value */
3516 static void
3517 cancel_editing (EWeekView *week_view)
3518 {
3519 gint event_num, span_num;
3520 EWeekViewEvent *event;
3521 EWeekViewEventSpan *span;
3522 const gchar *summary;
3523
3524 event_num = week_view->editing_event_num;
3525 span_num = week_view->editing_span_num;
3526
3527 g_return_if_fail (event_num != -1);
3528
3529 if (!is_array_index_in_bounds (week_view->events, event_num))
3530 return;
3531
3532 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3533
3534 if (!is_comp_data_valid (event))
3535 return;
3536
3537 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
3538 return;
3539
3540 span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num);
3541
3542 /* Reset the text to what was in the component */
3543
3544 summary = icalcomponent_get_summary (event->comp_data->icalcomp);
3545 g_object_set (span->text_item, "text", summary ? summary : "", NULL);
3546
3547 /* Stop editing */
3548 e_week_view_stop_editing_event (week_view);
3549 }
3550
3551 static gboolean
3552 e_week_view_on_text_item_event (GnomeCanvasItem *item,
3553 GdkEvent *gdkevent,
3554 EWeekView *week_view)
3555 {
3556 EWeekViewEvent *event;
3557 gint event_num, span_num;
3558 gint nevent = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
3559 EWeekViewEvent *pevent;
3560
3561 pevent = tooltip_get_view_event (week_view, -1, nevent);
3562
3563 #if 0
3564 g_print ("In e_week_view_on_text_item_event\n");
3565 #endif
3566
3567 switch (gdkevent->type) {
3568 case GDK_KEY_PRESS:
3569 tooltip_destroy (week_view, item);
3570 if (!E_TEXT (item)->preedit_len && gdkevent && gdkevent->key.keyval == GDK_KEY_Return) {
3571 /* We set the keyboard focus to the EDayView, so the
3572 * EText item loses it and stops the edit. */
3573 gtk_widget_grab_focus (GTK_WIDGET (week_view));
3574
3575 /* Stop the signal last or we will also stop any
3576 * other events getting to the EText item. */
3577 g_signal_stop_emission_by_name (item, "event");
3578 return TRUE;
3579 } else if (gdkevent->key.keyval == GDK_KEY_Escape) {
3580 cancel_editing (week_view);
3581 g_signal_stop_emission_by_name (item, "event");
3582 /* focus should go to week view when stop editing */
3583 gtk_widget_grab_focus (GTK_WIDGET (week_view));
3584 return TRUE;
3585 }
3586 break;
3587 case GDK_2BUTTON_PRESS:
3588 if (!e_week_view_find_event_from_item (week_view, item,
3589 &event_num, &span_num))
3590 return FALSE;
3591
3592 if (!is_array_index_in_bounds (week_view->events, event_num))
3593 return FALSE;
3594
3595 event = &g_array_index (week_view->events, EWeekViewEvent,
3596 event_num);
3597
3598 if (!is_comp_data_valid (event))
3599 return FALSE;
3600
3601 /* if we started to editing new item on the canvas, then do not open editing dialog until it's saved,
3602 * because the save of the event recalculates event numbers and you can edit different one */
3603 if (!is_icalcomp_on_the_server (event->comp_data->icalcomp, event->comp_data->client))
3604 return TRUE;
3605
3606 e_calendar_view_edit_appointment (
3607 E_CALENDAR_VIEW (week_view),
3608 event->comp_data->client,
3609 event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT);
3610
3611 g_signal_stop_emission_by_name (item, "event");
3612 return TRUE;
3613 case GDK_BUTTON_PRESS:
3614 tooltip_destroy (week_view, item);
3615 if (!e_week_view_find_event_from_item (week_view, item,
3616 &event_num, &span_num))
3617 return FALSE;
3618
3619 if (gdkevent->button.button == 3) {
3620 EWeekViewEvent *e;
3621
3622 if (E_TEXT (item)->editing) {
3623 e_week_view_stop_editing_event (week_view);
3624 gtk_widget_grab_focus (GTK_WIDGET (week_view));
3625 return FALSE;
3626 }
3627
3628 if (!is_array_index_in_bounds (week_view->events, event_num))
3629 return FALSE;
3630
3631 e = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3632
3633 if (!gtk_widget_has_focus (GTK_WIDGET (week_view)))
3634 gtk_widget_grab_focus (GTK_WIDGET (week_view));
3635
3636 e_week_view_set_selected_time_range_visible (week_view, e->start, e->end);
3637
3638 e_week_view_show_popup_menu (
3639 week_view,
3640 (GdkEventButton *) gdkevent,
3641 event_num);
3642
3643 g_signal_stop_emission_by_name (
3644 item->canvas, "button_press_event");
3645 return TRUE;
3646 }
3647
3648 if (gdkevent->button.button != 3) {
3649 week_view->pressed_event_num = event_num;
3650 week_view->pressed_span_num = span_num;
3651 }
3652
3653 /* Only let the EText handle the event while editing. */
3654 if (!E_TEXT (item)->editing) {
3655 g_signal_stop_emission_by_name (item, "event");
3656
3657 if (gdkevent) {
3658 week_view->drag_event_x = gdkevent->button.x;
3659 week_view->drag_event_y = gdkevent->button.y;
3660 } else
3661 g_warning ("No GdkEvent");
3662
3663 /* FIXME: Remember the day offset from the start of
3664 * the event, for DnD. */
3665
3666 return TRUE;
3667 }
3668 break;
3669 case GDK_BUTTON_RELEASE:
3670 if (!E_TEXT (item)->editing) {
3671 /* This shouldn't ever happen. */
3672 if (!e_week_view_find_event_from_item (week_view,
3673 item,
3674 &event_num,
3675 &span_num))
3676 return FALSE;
3677
3678 if (week_view->pressed_event_num != -1
3679 && week_view->pressed_event_num == event_num
3680 && week_view->pressed_span_num == span_num) {
3681 e_week_view_start_editing_event (
3682 week_view,
3683 event_num,
3684 span_num,
3685 NULL);
3686 week_view->pressed_event_num = -1;
3687 }
3688
3689 /* Stop the signal last or we will also stop any
3690 * other events getting to the EText item. */
3691 g_signal_stop_emission_by_name (item, "event");
3692 return TRUE;
3693 }
3694 week_view->pressed_event_num = -1;
3695 break;
3696 case GDK_ENTER_NOTIFY:
3697 {
3698 ECalendarViewEventData *data;
3699 gint nspan;
3700
3701 if (week_view->editing_event_num != -1
3702 || !e_week_view_find_event_from_item (week_view, item, &nevent, &nspan))
3703 return FALSE;
3704
3705 g_object_set_data ((GObject *) item, "event-num", GINT_TO_POINTER (nevent));
3706
3707 pevent = tooltip_get_view_event (week_view, -1, nevent);
3708
3709 data = g_malloc (sizeof (ECalendarViewEventData));
3710
3711 pevent->x = ((GdkEventCrossing *) gdkevent)->x_root;
3712 pevent->y = ((GdkEventCrossing *) gdkevent)->y_root;
3713 pevent->tooltip = NULL;
3714
3715 data->cal_view = (ECalendarView *) week_view;
3716 data->day = -1;
3717 data->event_num = nevent;
3718 data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event;
3719 pevent->timeout = g_timeout_add_full (
3720 G_PRIORITY_DEFAULT, 500,
3721 (GSourceFunc) e_calendar_view_get_tooltips,
3722 data, (GDestroyNotify) g_free);
3723 g_object_set_data ((GObject *) week_view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout));
3724
3725 return TRUE;
3726 }
3727 case GDK_LEAVE_NOTIFY:
3728 tooltip_destroy (week_view, item);
3729
3730 return FALSE;
3731 case GDK_MOTION_NOTIFY:
3732 pevent->x = ((GdkEventMotion *) gdkevent)->x_root;
3733 pevent->y = ((GdkEventMotion *) gdkevent)->y_root;
3734 pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (week_view), "tooltip-window");
3735
3736 if (pevent->tooltip) {
3737 e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16);
3738 }
3739 return TRUE;
3740 case GDK_FOCUS_CHANGE:
3741 if (gdkevent->focus_change.in) {
3742 e_week_view_on_editing_started (week_view, item);
3743 } else {
3744 e_week_view_on_editing_stopped (week_view, item);
3745 }
3746
3747 return FALSE;
3748 default:
3749 break;
3750 }
3751
3752 return FALSE;
3753 }
3754
3755 static gboolean e_week_view_event_move (ECalendarView *cal_view, ECalViewMoveDirection direction)
3756 {
3757 EWeekViewEvent *event;
3758 gint event_num, adjust_days, current_start_day, current_end_day;
3759 time_t start_dt, end_dt;
3760 struct icaltimetype start_time,end_time;
3761 EWeekView *week_view = E_WEEK_VIEW (cal_view);
3762 gboolean is_all_day = FALSE;
3763
3764 event_num = week_view->editing_event_num;
3765 adjust_days = 0;
3766
3767 /* If no item is being edited, just return. */
3768 if (event_num == -1)
3769 return FALSE;
3770
3771 if (!is_array_index_in_bounds (week_view->events, event_num))
3772 return FALSE;
3773
3774 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3775
3776 if (!is_comp_data_valid (event))
3777 return FALSE;
3778
3779 end_dt = event->end;
3780 start_time = icalcomponent_get_dtstart (event->comp_data->icalcomp);
3781 end_time = icalcomponent_get_dtend (event->comp_data->icalcomp);
3782
3783 if (start_time.is_date && end_time.is_date)
3784 is_all_day = TRUE;
3785
3786 current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt);
3787
3788 switch (direction) {
3789 case E_CAL_VIEW_MOVE_UP:
3790 adjust_days = e_week_view_get_adjust_days_for_move_up (week_view,current_end_day);
3791 break;
3792 case E_CAL_VIEW_MOVE_DOWN:
3793 adjust_days = e_week_view_get_adjust_days_for_move_down (week_view,current_end_day);
3794 break;
3795 case E_CAL_VIEW_MOVE_LEFT:
3796 adjust_days = e_week_view_get_adjust_days_for_move_left (week_view,current_end_day);
3797 break;
3798 case E_CAL_VIEW_MOVE_RIGHT:
3799 adjust_days = e_week_view_get_adjust_days_for_move_right (week_view,current_end_day);
3800 break;
3801 default:
3802 break;
3803 }
3804
3805 icaltime_adjust (&start_time ,adjust_days,0,0,0);
3806 icaltime_adjust (&end_time ,adjust_days,0,0,0);
3807 start_dt = icaltime_as_timet_with_zone (
3808 start_time,
3809 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
3810 end_dt = icaltime_as_timet_with_zone (
3811 end_time,
3812 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
3813
3814 current_start_day = e_week_view_get_day_offset_of_event (week_view,start_dt);
3815 current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt);
3816 if (is_all_day)
3817 current_end_day--;
3818
3819 if (current_start_day < 0) {
3820 return TRUE;
3821 }
3822 if (week_view->multi_week_view) {
3823 if (current_end_day >= week_view->weeks_shown * 7) {
3824 return TRUE;
3825 }
3826 }else {
3827 if (current_end_day >= 7) {
3828 return TRUE;
3829 }
3830 }
3831
3832 e_week_view_change_event_time (week_view, start_dt, end_dt, is_all_day);
3833 return TRUE;
3834 }
3835
3836 static gint
3837 e_week_view_get_day_offset_of_event (EWeekView *week_view,
3838 time_t event_time)
3839 {
3840 time_t first_day = week_view->day_starts[0];
3841
3842 if (event_time - first_day < 0)
3843 return -1;
3844 else
3845 return (event_time - first_day) / (24 * 60 * 60);
3846 }
3847
3848 void
3849 e_week_view_scroll_a_step (EWeekView *week_view,
3850 ECalViewMoveDirection direction)
3851 {
3852 GtkAdjustment *adjustment;
3853 GtkRange *range;
3854 gdouble step_increment;
3855 gdouble page_size;
3856 gdouble new_value;
3857 gdouble lower;
3858 gdouble upper;
3859 gdouble value;
3860
3861 range = GTK_RANGE (week_view->vscrollbar);
3862 adjustment = gtk_range_get_adjustment (range);
3863
3864 step_increment = gtk_adjustment_get_step_increment (adjustment);
3865 page_size = gtk_adjustment_get_page_size (adjustment);
3866 lower = gtk_adjustment_get_lower (adjustment);
3867 upper = gtk_adjustment_get_upper (adjustment);
3868 value = gtk_adjustment_get_value (adjustment);
3869
3870 switch (direction) {
3871 case E_CAL_VIEW_MOVE_UP:
3872 new_value = value - step_increment;
3873 break;
3874 case E_CAL_VIEW_MOVE_DOWN:
3875 new_value = value + step_increment;
3876 break;
3877 case E_CAL_VIEW_MOVE_PAGE_UP:
3878 new_value = value - page_size;
3879 break;
3880 case E_CAL_VIEW_MOVE_PAGE_DOWN:
3881 new_value = value + page_size;
3882 break;
3883 default:
3884 return;
3885 }
3886
3887 new_value = CLAMP (new_value, lower, upper - page_size);
3888 gtk_adjustment_set_value (adjustment, new_value);
3889 }
3890
3891 static void
3892 e_week_view_change_event_time (EWeekView *week_view,
3893 time_t start_dt,
3894 time_t end_dt,
3895 gboolean is_all_day)
3896 {
3897 EWeekViewEvent *event;
3898 gint event_num;
3899 ECalComponent *comp;
3900 ECalComponentDateTime date;
3901 struct icaltimetype itt;
3902 ECalClient *client;
3903 CalObjModType mod = CALOBJ_MOD_ALL;
3904 GtkWindow *toplevel;
3905
3906 event_num = week_view->editing_event_num;
3907
3908 /* If no item is being edited, just return. */
3909 if (event_num == -1)
3910 return;
3911
3912 if (!is_array_index_in_bounds (week_view->events, event_num))
3913 return;
3914
3915 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3916
3917 if (!is_comp_data_valid (event))
3918 return;
3919
3920 client = event->comp_data->client;
3921
3922 /* We use a temporary shallow copy of the ico since we don't want to
3923 * change the original ico here. Otherwise we would not detect that
3924 * the event's time had changed in the "update_event" callback. */
3925 comp = e_cal_component_new ();
3926 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
3927 date.value = &itt;
3928 /* FIXME: Should probably keep the timezone of the original start
3929 * and end times. */
3930 date.tzid = icaltimezone_get_tzid (e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
3931
3932 *date.value = icaltime_from_timet_with_zone (start_dt, is_all_day,
3933 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
3934 cal_comp_set_dtstart_with_oldzone (client, comp, &date);
3935 *date.value = icaltime_from_timet_with_zone (end_dt, is_all_day,
3936 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
3937 cal_comp_set_dtend_with_oldzone (client, comp, &date);
3938
3939 e_cal_component_commit_sequence (comp);
3940
3941 if (week_view->last_edited_comp_string != NULL) {
3942 g_free (week_view->last_edited_comp_string);
3943 week_view->last_edited_comp_string = NULL;
3944 }
3945
3946 week_view->last_edited_comp_string = e_cal_component_get_as_string (comp);
3947
3948 if (e_cal_component_has_recurrences (comp)) {
3949 if (!recur_component_dialog (client, comp, &mod, NULL, FALSE)) {
3950 gtk_widget_queue_draw (week_view->main_canvas);
3951 goto out;
3952 }
3953
3954 if (mod == CALOBJ_MOD_ALL)
3955 comp_util_sanitize_recurrence_master (comp, client);
3956
3957 if (mod == CALOBJ_MOD_THIS) {
3958 e_cal_component_set_rdate_list (comp, NULL);
3959 e_cal_component_set_rrule_list (comp, NULL);
3960 e_cal_component_set_exdate_list (comp, NULL);
3961 e_cal_component_set_exrule_list (comp, NULL);
3962 }
3963 } else if (e_cal_component_is_instance (comp))
3964 mod = CALOBJ_MOD_THIS;
3965
3966 toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (week_view)));
3967
3968 e_cal_component_commit_sequence (comp);
3969
3970 e_calendar_view_modify_and_send (
3971 E_CALENDAR_VIEW (week_view),
3972 comp, client, mod, toplevel, TRUE);
3973
3974 out:
3975 g_object_unref (comp);
3976 }
3977
3978 static void
3979 e_week_view_on_editing_started (EWeekView *week_view,
3980 GnomeCanvasItem *item)
3981 {
3982 gint event_num, span_num;
3983
3984 if (!e_week_view_find_event_from_item (week_view, item,
3985 &event_num, &span_num))
3986 return;
3987
3988 #if 0
3989 g_print ("In e_week_view_on_editing_started event_num:%i span_num:%i\n", event_num, span_num);
3990 #endif
3991
3992 week_view->editing_event_num = event_num;
3993 week_view->editing_span_num = span_num;
3994
3995 /* We need to reshape long events so the whole width is used while
3996 * editing. */
3997 if (!e_week_view_is_one_day_event (week_view, event_num)) {
3998 e_week_view_reshape_event_span (
3999 week_view, event_num, span_num);
4000 }
4001
4002 g_signal_emit_by_name (week_view, "selection_changed");
4003 }
4004
4005 static void
4006 e_week_view_on_editing_stopped (EWeekView *week_view,
4007 GnomeCanvasItem *item)
4008 {
4009 gint event_num, span_num;
4010 EWeekViewEvent *event;
4011 EWeekViewEventSpan *span;
4012 gchar *text = NULL;
4013 ECalComponent *comp;
4014 ECalComponentText summary;
4015 ECalClient *client;
4016 const gchar *uid;
4017 gboolean on_server;
4018
4019 /* Note: the item we are passed here isn't reliable, so we just stop
4020 * the edit of whatever item was being edited. We also receive this
4021 * event twice for some reason. */
4022 event_num = week_view->editing_event_num;
4023 span_num = week_view->editing_span_num;
4024
4025 /* If no item is being edited, just return. */
4026 if (event_num == -1)
4027 return;
4028
4029 if (!is_array_index_in_bounds (week_view->events, event_num))
4030 return;
4031
4032 event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
4033
4034 if (!is_comp_data_valid (event))
4035 return;
4036
4037 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
4038 return;
4039
4040 span = &g_array_index (week_view->spans, EWeekViewEventSpan,
4041 event->spans_index + span_num);
4042
4043 /* Reset the edit fields. */
4044 week_view->editing_event_num = -1;
4045
4046 /* Check that the event is still valid. */
4047 uid = icalcomponent_get_uid (event->comp_data->icalcomp);
4048 if (!uid)
4049 return;
4050
4051 text = NULL;
4052 g_object_set (span->text_item, "handle_popup", FALSE, NULL);
4053 g_object_get (span->text_item, "text", &text, NULL);
4054
4055 comp = e_cal_component_new ();
4056 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
4057
4058 client = event->comp_data->client;
4059 on_server = cal_comp_is_on_server (comp, client);
4060
4061 if (string_is_empty (text) && !on_server) {
4062 e_cal_component_get_uid (comp, &uid);
4063 g_signal_handlers_disconnect_by_func (item, e_week_view_on_text_item_event, week_view);
4064 e_week_view_foreach_event_with_uid (week_view, uid,
4065 e_week_view_remove_event_cb, NULL);
4066 week_view->event_destroyed = TRUE;
4067 gtk_widget_queue_draw (week_view->main_canvas);
4068 e_week_view_check_layout (week_view);
4069 goto out;
4070 }
4071
4072 /* Only update the summary if necessary. */
4073 e_cal_component_get_summary (comp, &summary);
4074 if (summary.value && !strcmp (text, summary.value)) {
4075 gboolean free_text = FALSE;
4076 const gchar *summary;
4077
4078 summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text);
4079 g_object_set (span->text_item, "text", summary ? summary : "", NULL);
4080
4081 if (free_text)
4082 g_free ((gchar *) summary);
4083
4084 if (!e_week_view_is_one_day_event (week_view, event_num))
4085 e_week_view_reshape_event_span (week_view, event_num, span_num);
4086 } else if (summary.value || !string_is_empty (text)) {
4087 icalcomponent *icalcomp = e_cal_component_get_icalcomponent (comp);
4088
4089 summary.value = text;
4090 summary.altrep = NULL;
4091 e_cal_component_set_summary (comp, &summary);
4092 e_cal_component_commit_sequence (comp);
4093
4094 if (!on_server) {
4095 gchar *uid = NULL;
4096 GError *error = NULL;
4097
4098 e_cal_client_create_object_sync (
4099 client, icalcomp, &uid, NULL, &error);
4100
4101 if (error != NULL) {
4102 g_warning (
4103 G_STRLOC ": Could not create the object! %s",
4104 error->message);
4105 uid = NULL;
4106 } else {
4107 if (uid)
4108 icalcomponent_set_uid (icalcomp, uid);
4109
4110 e_calendar_view_emit_user_created (
4111 E_CALENDAR_VIEW (week_view