Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-week-view-layout.c
bloba8ae6738520050b0f48f73eed5c911421fe80e42
1 /*
2 * Lays out events for the Week & Month views of the calendar. It is also
3 * used for printing.
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 * Authors:
18 * Damon Chaplin <damon@ximian.com>
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
23 #include "evolution-config.h"
25 #include "e-week-view-layout.h"
26 #include "calendar-config.h"
28 static void e_week_view_layout_event (EWeekViewEvent *event,
29 guint8 *grid,
30 GArray *spans,
31 GArray *old_spans,
32 gboolean multi_week_view,
33 gint weeks_shown,
34 gboolean compress_weekend,
35 gint start_weekday,
36 time_t *day_starts,
37 gint *rows_per_day);
38 static gint e_week_view_find_day (time_t time_to_find,
39 gboolean include_midnight_in_prev_day,
40 gint days_shown,
41 time_t *day_starts);
42 static gint e_week_view_find_span_end (gboolean multi_week_view,
43 gboolean compress_weekend,
44 GDateWeekday display_start_day,
45 gint day);
47 GArray *
48 e_week_view_layout_events (GArray *events,
49 GArray *old_spans,
50 gboolean multi_week_view,
51 gint weeks_shown,
52 gboolean compress_weekend,
53 gint start_weekday,
54 time_t *day_starts,
55 gint *rows_per_day)
57 EWeekViewEvent *event;
58 EWeekViewEventSpan *span;
59 gint num_days, day, event_num, span_num;
60 guint8 *grid;
61 GArray *spans;
63 /* This is a temporary 2-d grid which is used to place events.
64 * Each element is 0 if the position is empty, or 1 if occupied.
65 * We allocate the maximum size possible here, assuming that each
66 * event will need its own row. */
67 grid = g_new0 (guint8, E_WEEK_VIEW_MAX_ROWS_PER_CELL * 7
68 * E_WEEK_VIEW_MAX_WEEKS);
70 /* We create a new array of spans, which will replace the old one. */
71 spans = g_array_new (FALSE, FALSE, sizeof (EWeekViewEventSpan));
73 /* Clear the number of rows used per day. */
74 num_days = multi_week_view ? weeks_shown * 7 : 7;
75 for (day = 0; day < num_days; day++) {
76 rows_per_day[day] = 0;
79 /* Iterate over the events, finding which weeks they cover, and putting
80 * them in the first free row available. */
81 for (event_num = 0; event_num < events->len; event_num++) {
82 event = &g_array_index (events, EWeekViewEvent, event_num);
83 e_week_view_layout_event (
84 event, grid, spans, old_spans,
85 multi_week_view,
86 weeks_shown, compress_weekend,
87 start_weekday, day_starts,
88 rows_per_day);
91 /* Free the grid. */
92 g_free (grid);
94 /* Destroy the old spans array, destroying any unused canvas items. */
95 if (old_spans) {
96 for (span_num = 0; span_num < old_spans->len; span_num++) {
97 span = &g_array_index (old_spans, EWeekViewEventSpan,
98 span_num);
99 if (span->background_item)
100 g_object_run_dispose (G_OBJECT (span->background_item));
101 if (span->text_item)
102 g_object_run_dispose (G_OBJECT (span->text_item));
104 g_array_free (old_spans, TRUE);
107 return spans;
110 static void
111 e_week_view_layout_event (EWeekViewEvent *event,
112 guint8 *grid,
113 GArray *spans,
114 GArray *old_spans,
115 gboolean multi_week_view,
116 gint weeks_shown,
117 gboolean compress_weekend,
118 gint start_weekday,
119 time_t *day_starts,
120 gint *rows_per_day)
122 gint start_day, end_day, span_start_day, span_end_day, rows_per_cell;
123 gint free_row, row, day, span_num, spans_index, num_spans, days_shown;
124 EWeekViewEventSpan span, *old_span;
126 days_shown = multi_week_view ? weeks_shown * 7 : 7;
127 start_day = e_week_view_find_day (
128 event->start, FALSE, days_shown,
129 day_starts);
130 end_day = e_week_view_find_day (
131 event->end, event->start != event->end, days_shown,
132 day_starts);
133 start_day = CLAMP (start_day, 0, days_shown - 1);
134 end_day = CLAMP (end_day, 0, days_shown - 1);
136 #if 0
137 g_print (
138 "In e_week_view_layout_event Start:%i End: %i\n",
139 start_day, end_day);
140 #endif
142 /* Iterate through each of the spans of the event, where each span
143 * is a sequence of 1 or more days displayed next to each other. */
144 span_start_day = start_day;
145 rows_per_cell = E_WEEK_VIEW_MAX_ROWS_PER_CELL;
146 span_num = 0;
147 spans_index = spans->len;
148 num_spans = 0;
149 while (span_start_day <= end_day) {
150 span_end_day = e_week_view_find_span_end (
151 multi_week_view,
152 compress_weekend,
153 start_weekday,
154 span_start_day);
155 span_end_day = MIN (span_end_day, end_day);
156 #if 0
157 g_print (
158 " Span start:%i end:%i\n", span_start_day,
159 span_end_day);
160 #endif
161 /* Try each row until we find a free one or we fall off the
162 * bottom of the available rows. */
163 row = 0;
164 free_row = -1;
165 while (free_row == -1 && row < rows_per_cell) {
166 free_row = row;
167 for (day = span_start_day; day <= span_end_day;
168 day++) {
169 if (grid[day * rows_per_cell + row]) {
170 free_row = -1;
171 break;
174 row++;
177 if (free_row != -1) {
178 /* Mark the cells as full. */
179 for (day = span_start_day; day <= span_end_day;
180 day++) {
181 grid[day * rows_per_cell + free_row] = 1;
182 rows_per_day[day] = MAX (
183 rows_per_day[day],
184 free_row + 1);
186 #if 0
187 g_print (
188 " Span start:%i end:%i row:%i\n",
189 span_start_day, span_end_day, free_row);
190 #endif
191 /* Add the span to the array, and try to reuse any
192 * canvas items from the old spans. */
193 span.start_day = span_start_day;
194 span.num_days = span_end_day - span_start_day + 1;
195 span.row = free_row;
196 span.background_item = NULL;
197 span.text_item = NULL;
198 if (event->num_spans > span_num) {
199 old_span = &g_array_index (
200 old_spans, EWeekViewEventSpan,
201 event->spans_index + span_num);
202 span.background_item = old_span->background_item;
203 span.text_item = old_span->text_item;
204 old_span->background_item = NULL;
205 old_span->text_item = NULL;
208 g_array_append_val (spans, span);
209 num_spans++;
212 span_start_day = span_end_day + 1;
213 span_num++;
216 /* Set the event's spans. */
217 event->spans_index = spans_index;
218 event->num_spans = num_spans;
221 /* Finds the day containing the given time.
222 * If include_midnight_in_prev_day is TRUE then if the time exactly
223 * matches the start of a day the previous day is returned. This is useful
224 * when calculating the end day of an event. */
225 static gint
226 e_week_view_find_day (time_t time_to_find,
227 gboolean include_midnight_in_prev_day,
228 gint days_shown,
229 time_t *day_starts)
231 gint day;
233 if (time_to_find < day_starts[0])
234 return -1;
235 if (time_to_find > day_starts[days_shown])
236 return days_shown;
238 for (day = 1; day <= days_shown; day++) {
239 if (time_to_find <= day_starts[day]) {
240 if (time_to_find == day_starts[day]
241 && !include_midnight_in_prev_day)
242 return day;
243 return day - 1;
247 g_return_val_if_reached (days_shown);
250 /* This returns the last possible day in the same span as the given day.
251 * A span is all the days which are displayed next to each other from left to
252 * right. In the week view all spans are only 1 day, since Tuesday is below
253 * Monday rather than beside it etc. In the month view, if the weekends are not
254 * compressed then each week is a span, otherwise we have to break a span up
255 * on Saturday, use a separate span for Sunday, and start again on Monday. */
256 static gint
257 e_week_view_find_span_end (gboolean multi_week_view,
258 gboolean compress_weekend,
259 GDateWeekday display_start_day,
260 gint day)
262 gint week, col, sat_col, end_col;
264 if (multi_week_view) {
265 week = day / 7;
266 col = day % 7;
268 /* We default to the last column in the row. */
269 end_col = 6;
271 /* If the weekend is compressed we must end any spans on
272 * Saturday and Sunday. */
273 if (compress_weekend) {
274 sat_col = e_weekday_get_days_between (
275 display_start_day, G_DATE_SATURDAY);
276 if (col <= sat_col)
277 end_col = sat_col;
278 else if (col == sat_col + 1)
279 end_col = sat_col + 1;
282 return week * 7 + end_col;
283 } else {
284 return day;
288 void
289 e_week_view_layout_get_day_position (gint day,
290 gboolean multi_week_view,
291 gint weeks_shown,
292 GDateWeekday display_start_day,
293 gboolean compress_weekend,
294 gint *day_x,
295 gint *day_y,
296 gint *rows)
298 GDateWeekday day_of_week;
299 gint week, col, weekend_col;
301 *day_x = *day_y = *rows = 0;
302 g_return_if_fail (day >= 0);
304 if (multi_week_view) {
305 g_return_if_fail (day < weeks_shown * 7);
307 week = day / 7;
308 col = day % 7;
309 day_of_week = e_weekday_add_days (display_start_day, day);
310 if (compress_weekend && day_of_week >= G_DATE_SATURDAY) {
311 /* In the compressed view Saturday is above Sunday and
312 * both have just one row as opposed to 2 for all the
313 * other days. */
314 if (day_of_week == G_DATE_SATURDAY) {
315 *day_y = week * 2;
316 *rows = 1;
317 } else {
318 *day_y = week * 2 + 1;
319 *rows = 1;
320 col--;
322 /* Both Saturday and Sunday are in the same column. */
323 *day_x = col;
324 } else {
325 /* If the weekend is compressed and the day is after
326 * the weekend we have to move back a column. */
327 if (compress_weekend) {
328 /* Calculate where the weekend column is. */
329 weekend_col = e_weekday_get_days_between (
330 display_start_day, G_DATE_SATURDAY);
331 if (col > weekend_col)
332 col--;
335 *day_y = week * 2;
336 *rows = 2;
337 *day_x = col;
339 } else {
340 GSettings *settings;
341 gint arr[4] = {1, 1, 1, 1};
342 gint edge, i, wd, m, M;
343 gboolean any = TRUE;
344 gboolean days_left_to_right;
345 gint n_work_days_mon_wed = 0;
346 gint n_work_days_thu_sun = 0;
348 /* 0 = Monday, 6 = Sunday */
349 gint work_days[7] = { 0, 0, 0, 0, 0, 0, 0 };
351 g_return_if_fail (day < 7);
353 settings = e_util_ref_settings ("org.gnome.evolution.calendar");
355 days_left_to_right = g_settings_get_boolean (settings, "week-view-days-left-to-right");
357 if (g_settings_get_boolean (settings, "work-day-monday"))
358 work_days[0] = 1, n_work_days_mon_wed++;
359 if (g_settings_get_boolean (settings, "work-day-tuesday"))
360 work_days[1] = 1, n_work_days_mon_wed++;
361 if (g_settings_get_boolean (settings, "work-day-wednesday"))
362 work_days[2] = 1, n_work_days_mon_wed++;
363 if (g_settings_get_boolean (settings, "work-day-thursday"))
364 work_days[3] = 1, n_work_days_thu_sun++;
365 if (g_settings_get_boolean (settings, "work-day-friday"))
366 work_days[4] = 1, n_work_days_thu_sun++;
367 if (g_settings_get_boolean (settings, "work-day-saturday"))
368 work_days[5] = 1, n_work_days_thu_sun++;
369 if (g_settings_get_boolean (settings, "work-day-sunday"))
370 work_days[6] = 1, n_work_days_thu_sun++;
372 g_object_unref (settings);
374 if (n_work_days_mon_wed < n_work_days_thu_sun)
375 edge = 4; /* Friday */
376 else
377 edge = 3; /* Thursday */
379 if (days_left_to_right) {
380 /* The default view is always with two columns:
381 Mon Thu
382 Tue Fri
383 Wed Sat/Sun
384 eventually:
385 Mon Fri
386 Tue Sat
387 Wed Sun
390 if (edge == 3) {
391 const gint transform[] = { 0, 3, 1, 4, 2, 5, 6 };
392 day = transform[day];
393 } else {
394 const gint transform[] = { 0, 4, 1, 5, 2, 3, 6 };
395 day = transform[day];
399 if (day < edge) {
400 *day_x = 0;
401 m = 0;
402 M = edge;
403 } else {
404 *day_x = 1;
405 m = edge;
406 M = 7;
409 wd = 0; /* number of used rows in column */
410 for (i = m; i < M; i++) {
411 arr[i - m] += work_days[i];
412 wd += arr[i - m];
415 while (wd != 6 && any) {
416 any = FALSE;
418 for (i = M - 1; i >= m; i--) {
419 if (arr[i - m] > 1) {
420 any = TRUE;
422 /* too many rows, make last shorter */
423 if (wd > 6) {
424 arr[i - m] --;
425 wd--;
427 /* free rows left, enlarge those bigger */
428 } else if (wd < 6) {
429 arr[i - m] ++;
430 wd++;
433 if (wd == 6)
434 break;
438 if (!any && wd != 6) {
439 any = TRUE;
441 for (i = m; i < M; i++) {
442 arr[i - m] += 3;
443 wd += 3;
448 *rows = arr [day - m];
450 *day_y = 0;
451 for (i = m; i < day; i++) {
452 *day_y += arr [i - m];
455 #undef wk
459 /* Returns TRUE if the event span is visible or FALSE if it isn't.
460 * It also returns the number of days of the span that are visible.
461 * Usually this can easily be determined by the start & end days and row of
462 * the span, which are set in e_week_view_layout_event (). Though we need a
463 * special case for the weekends when they are compressed, since the span may
464 * not fit. */
465 gboolean
466 e_week_view_layout_get_span_position (EWeekViewEvent *event,
467 EWeekViewEventSpan *span,
468 gint rows_per_cell,
469 gint rows_per_compressed_cell,
470 GDateWeekday display_start_day,
471 gboolean multi_week_view,
472 gboolean compress_weekend,
473 gint *span_num_days)
475 GDateWeekday end_day_of_week;
476 guint n_days;
478 if (multi_week_view && span->row >= rows_per_cell)
479 return FALSE;
481 n_days = span->start_day + span->num_days - 1;
482 end_day_of_week = e_weekday_add_days (display_start_day, n_days);
484 *span_num_days = span->num_days;
485 /* Check if the row will not be visible in compressed cells. */
486 if (span->row >= rows_per_compressed_cell) {
487 if (multi_week_view) {
488 if (compress_weekend) {
489 /* If it ends on a Saturday and is 1 day glong
490 * we skip it, else we shorten it. If it ends
491 * on a Sunday it must be 1 day long and we
492 * skip it. */
493 if (end_day_of_week == G_DATE_SATURDAY) {
494 if (*span_num_days == 1) {
495 return FALSE;
496 } else {
497 (*span_num_days)--;
499 } else if (end_day_of_week == G_DATE_SUNDAY) {
500 return FALSE;
503 } else {
504 gint day_x, day_y, rows = 0;
505 e_week_view_layout_get_day_position (
506 end_day_of_week - 1, multi_week_view, 1,
507 display_start_day, compress_weekend,
508 &day_x, &day_y, &rows);
510 if (((rows / 2) * rows_per_cell) + ((rows % 2) *
511 rows_per_compressed_cell) <= span->row)
512 return FALSE;
516 return TRUE;