No issues found
1 /*
2 * Lays out events for the Week & Month views of the calendar. It is also
3 * used for printing.
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with the program; if not, see <http://www.gnu.org/licenses/>
17 *
18 * Authors:
19 * Damon Chaplin <damon@ximian.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include "e-week-view-layout.h"
29 #include "calendar-config.h"
30
31 static void e_week_view_layout_event (EWeekViewEvent *event,
32 guint8 *grid,
33 GArray *spans,
34 GArray *old_spans,
35 gboolean multi_week_view,
36 gint weeks_shown,
37 gboolean compress_weekend,
38 gint start_weekday,
39 time_t *day_starts,
40 gint *rows_per_day);
41 static gint e_week_view_find_day (time_t time_to_find,
42 gboolean include_midnight_in_prev_day,
43 gint days_shown,
44 time_t *day_starts);
45 static gint e_week_view_find_span_end (gboolean multi_week_view,
46 gboolean compress_weekend,
47 gint display_start_day,
48 gint day);
49
50 GArray *
51 e_week_view_layout_events (GArray *events,
52 GArray *old_spans,
53 gboolean multi_week_view,
54 gint weeks_shown,
55 gboolean compress_weekend,
56 gint start_weekday,
57 time_t *day_starts,
58 gint *rows_per_day)
59 {
60 EWeekViewEvent *event;
61 EWeekViewEventSpan *span;
62 gint num_days, day, event_num, span_num;
63 guint8 *grid;
64 GArray *spans;
65
66 /* This is a temporary 2-d grid which is used to place events.
67 * Each element is 0 if the position is empty, or 1 if occupied.
68 * We allocate the maximum size possible here, assuming that each
69 * event will need its own row. */
70 grid = g_new0 (guint8, E_WEEK_VIEW_MAX_ROWS_PER_CELL * 7
71 * E_WEEK_VIEW_MAX_WEEKS);
72
73 /* We create a new array of spans, which will replace the old one. */
74 spans = g_array_new (FALSE, FALSE, sizeof (EWeekViewEventSpan));
75
76 /* Clear the number of rows used per day. */
77 num_days = multi_week_view ? weeks_shown * 7 : 7;
78 for (day = 0; day < num_days; day++) {
79 rows_per_day[day] = 0;
80 }
81
82 /* Iterate over the events, finding which weeks they cover, and putting
83 * them in the first free row available. */
84 for (event_num = 0; event_num < events->len; event_num++) {
85 event = &g_array_index (events, EWeekViewEvent, event_num);
86 e_week_view_layout_event (
87 event, grid, spans, old_spans,
88 multi_week_view,
89 weeks_shown, compress_weekend,
90 start_weekday, day_starts,
91 rows_per_day);
92 }
93
94 /* Free the grid. */
95 g_free (grid);
96
97 /* Destroy the old spans array, destroying any unused canvas items. */
98 if (old_spans) {
99 for (span_num = 0; span_num < old_spans->len; span_num++) {
100 span = &g_array_index (old_spans, EWeekViewEventSpan,
101 span_num);
102 if (span->background_item)
103 g_object_run_dispose (G_OBJECT (span->background_item));
104 if (span->text_item)
105 g_object_run_dispose (G_OBJECT (span->text_item));
106 }
107 g_array_free (old_spans, TRUE);
108 }
109
110 return spans;
111 }
112
113 static void
114 e_week_view_layout_event (EWeekViewEvent *event,
115 guint8 *grid,
116 GArray *spans,
117 GArray *old_spans,
118 gboolean multi_week_view,
119 gint weeks_shown,
120 gboolean compress_weekend,
121 gint start_weekday,
122 time_t *day_starts,
123 gint *rows_per_day)
124 {
125 gint start_day, end_day, span_start_day, span_end_day, rows_per_cell;
126 gint free_row, row, day, span_num, spans_index, num_spans, days_shown;
127 EWeekViewEventSpan span, *old_span;
128
129 days_shown = multi_week_view ? weeks_shown * 7 : 7;
130 start_day = e_week_view_find_day (
131 event->start, FALSE, days_shown,
132 day_starts);
133 end_day = e_week_view_find_day (
134 event->end, TRUE, days_shown,
135 day_starts);
136 start_day = CLAMP (start_day, 0, days_shown - 1);
137 end_day = CLAMP (end_day, 0, days_shown - 1);
138
139 #if 0
140 g_print (
141 "In e_week_view_layout_event Start:%i End: %i\n",
142 start_day, end_day);
143 #endif
144
145 /* Iterate through each of the spans of the event, where each span
146 * is a sequence of 1 or more days displayed next to each other. */
147 span_start_day = start_day;
148 rows_per_cell = E_WEEK_VIEW_MAX_ROWS_PER_CELL;
149 span_num = 0;
150 spans_index = spans->len;
151 num_spans = 0;
152 while (span_start_day <= end_day) {
153 span_end_day = e_week_view_find_span_end (
154 multi_week_view,
155 compress_weekend,
156 start_weekday,
157 span_start_day);
158 span_end_day = MIN (span_end_day, end_day);
159 #if 0
160 g_print (
161 " Span start:%i end:%i\n", span_start_day,
162 span_end_day);
163 #endif
164 /* Try each row until we find a free one or we fall off the
165 * bottom of the available rows. */
166 row = 0;
167 free_row = -1;
168 while (free_row == -1 && row < rows_per_cell) {
169 free_row = row;
170 for (day = span_start_day; day <= span_end_day;
171 day++) {
172 if (grid[day * rows_per_cell + row]) {
173 free_row = -1;
174 break;
175 }
176 }
177 row++;
178 };
179
180 if (free_row != -1) {
181 /* Mark the cells as full. */
182 for (day = span_start_day; day <= span_end_day;
183 day++) {
184 grid[day * rows_per_cell + free_row] = 1;
185 rows_per_day[day] = MAX (
186 rows_per_day[day],
187 free_row + 1);
188 }
189 #if 0
190 g_print (
191 " Span start:%i end:%i row:%i\n",
192 span_start_day, span_end_day, free_row);
193 #endif
194 /* Add the span to the array, and try to reuse any
195 * canvas items from the old spans. */
196 span.start_day = span_start_day;
197 span.num_days = span_end_day - span_start_day + 1;
198 span.row = free_row;
199 span.background_item = NULL;
200 span.text_item = NULL;
201 if (event->num_spans > span_num) {
202 old_span = &g_array_index (
203 old_spans, EWeekViewEventSpan,
204 event->spans_index + span_num);
205 span.background_item = old_span->background_item;
206 span.text_item = old_span->text_item;
207 old_span->background_item = NULL;
208 old_span->text_item = NULL;
209 }
210
211 g_array_append_val (spans, span);
212 num_spans++;
213 }
214
215 span_start_day = span_end_day + 1;
216 span_num++;
217 }
218
219 /* Set the event's spans. */
220 event->spans_index = spans_index;
221 event->num_spans = num_spans;
222 }
223
224 /* Finds the day containing the given time.
225 * If include_midnight_in_prev_day is TRUE then if the time exactly
226 * matches the start of a day the previous day is returned. This is useful
227 * when calculating the end day of an event. */
228 static gint
229 e_week_view_find_day (time_t time_to_find,
230 gboolean include_midnight_in_prev_day,
231 gint days_shown,
232 time_t *day_starts)
233 {
234 gint day;
235
236 if (time_to_find < day_starts[0])
237 return -1;
238 if (time_to_find > day_starts[days_shown])
239 return days_shown;
240
241 for (day = 1; day <= days_shown; day++) {
242 if (time_to_find <= day_starts[day]) {
243 if (time_to_find == day_starts[day]
244 && !include_midnight_in_prev_day)
245 return day;
246 return day - 1;
247 }
248 }
249
250 g_return_val_if_reached (days_shown);
251 }
252
253 /* This returns the last possible day in the same span as the given day.
254 * A span is all the days which are displayed next to each other from left to
255 * right. In the week view all spans are only 1 day, since Tuesday is below
256 * Monday rather than beside it etc. In the month view, if the weekends are not
257 * compressed then each week is a span, otherwise we have to break a span up
258 * on Saturday, use a separate span for Sunday, and start again on Monday. */
259 static gint
260 e_week_view_find_span_end (gboolean multi_week_view,
261 gboolean compress_weekend,
262 gint display_start_day,
263 gint day)
264 {
265 gint week, col, sat_col, end_col;
266
267 if (multi_week_view) {
268 week = day / 7;
269 col = day % 7;
270
271 /* We default to the last column in the row. */
272 end_col = 6;
273
274 /* If the weekend is compressed we must end any spans on
275 * Saturday and Sunday. */
276 if (compress_weekend) {
277 sat_col = (5 + 7 - display_start_day) % 7;
278 if (col <= sat_col)
279 end_col = sat_col;
280 else if (col == sat_col + 1)
281 end_col = sat_col + 1;
282 }
283
284 return week * 7 + end_col;
285 } else {
286 return day;
287 }
288 }
289
290 void
291 e_week_view_layout_get_day_position (gint day,
292 gboolean multi_week_view,
293 gint weeks_shown,
294 gint display_start_day,
295 gboolean compress_weekend,
296 gint *day_x,
297 gint *day_y,
298 gint *rows)
299 {
300 gint week, day_of_week, col, weekend_col;
301
302 *day_x = *day_y = *rows = 0;
303 g_return_if_fail (day >= 0);
304
305 if (multi_week_view) {
306 g_return_if_fail (day < weeks_shown * 7);
307
308 week = day / 7;
309 col = day % 7;
310 day_of_week = (display_start_day + day) % 7;
311 if (compress_weekend && day_of_week >= 5) {
312 /* In the compressed view Saturday is above Sunday and
313 * both have just one row as opposed to 2 for all the
314 * other days. */
315 if (day_of_week == 5) {
316 *day_y = week * 2;
317 *rows = 1;
318 } else {
319 *day_y = week * 2 + 1;
320 *rows = 1;
321 col--;
322 }
323 /* Both Saturday and Sunday are in the same column. */
324 *day_x = col;
325 } else {
326 /* If the weekend is compressed and the day is after
327 * the weekend we have to move back a column. */
328 if (compress_weekend) {
329 /* Calculate where the weekend column is.
330 * Note that 5 is Saturday. */
331 weekend_col = (5 + 7 - display_start_day) % 7;
332 if (col > weekend_col)
333 col--;
334 }
335
336 *day_y = week * 2;
337 *rows = 2;
338 *day_x = col;
339 }
340 } else {
341 #define wk(x) \
342 ((working_days & \
343 (days[((x) + display_start_day) % 7])) ? 1 : 0)
344 CalWeekdays days[] = {
345 CAL_MONDAY,
346 CAL_TUESDAY,
347 CAL_WEDNESDAY,
348 CAL_THURSDAY,
349 CAL_FRIDAY,
350 CAL_SATURDAY,
351 CAL_SUNDAY };
352 CalWeekdays working_days;
353 gint arr[4] = {1, 1, 1, 1};
354 gint edge, i, wd, m, M;
355 gboolean any = TRUE;
356
357 g_return_if_fail (day < 7);
358
359 working_days = calendar_config_get_working_days ();
360 edge = 3;
361
362 if (wk (0) + wk (1) + wk (2) < wk (3) + wk (4) + wk (5) + wk (6))
363 edge++;
364
365 if (day < edge) {
366 *day_x = 0;
367 m = 0;
368 M = edge;
369 } else {
370 *day_x = 1;
371 m = edge;
372 M = 7;
373 }
374
375 wd = 0; /* number of used rows in column */
376 for (i = m; i < M; i++) {
377 arr[i - m] += wk (i);
378 wd += arr[i - m];
379 }
380
381 while (wd != 6 && any) {
382 any = FALSE;
383
384 for (i = M - 1; i >= m; i--) {
385 if (arr[i - m] > 1) {
386 any = TRUE;
387
388 /* too many rows, make last shorter */
389 if (wd > 6) {
390 arr[i - m] --;
391 wd--;
392
393 /* free rows left, enlarge those bigger */
394 } else if (wd < 6) {
395 arr[i - m] ++;
396 wd++;
397 }
398
399 if (wd == 6)
400 break;
401 }
402 }
403
404 if (!any && wd != 6) {
405 any = TRUE;
406
407 for (i = m; i < M; i++) {
408 arr[i - m] += 3;
409 wd += 3;
410 }
411 }
412 }
413
414 *rows = arr [day - m];
415
416 *day_y = 0;
417 for (i = m; i < day; i++) {
418 *day_y += arr [i - m];
419 }
420
421 #undef wk
422 }
423 }
424
425 /* Returns TRUE if the event span is visible or FALSE if it isn't.
426 * It also returns the number of days of the span that are visible.
427 * Usually this can easily be determined by the start & end days and row of
428 * the span, which are set in e_week_view_layout_event (). Though we need a
429 * special case for the weekends when they are compressed, since the span may
430 * not fit. */
431 gboolean
432 e_week_view_layout_get_span_position (EWeekViewEvent *event,
433 EWeekViewEventSpan *span,
434 gint rows_per_cell,
435 gint rows_per_compressed_cell,
436 gint display_start_day,
437 gboolean multi_week_view,
438 gboolean compress_weekend,
439 gint *span_num_days)
440 {
441 gint end_day_of_week;
442
443 if (multi_week_view && span->row >= rows_per_cell)
444 return FALSE;
445
446 end_day_of_week = (display_start_day + span->start_day
447 + span->num_days - 1) % 7;
448 *span_num_days = span->num_days;
449 /* Check if the row will not be visible in compressed cells. */
450 if (span->row >= rows_per_compressed_cell) {
451 if (multi_week_view) {
452 if (compress_weekend) {
453 /* If it ends on a Saturday and is 1 day glong
454 * we skip it, else we shorten it. If it ends
455 * on a Sunday it must be 1 day long and we
456 * skip it. */
457 if (end_day_of_week == 5) { /* Sat */
458 if (*span_num_days == 1) {
459 return FALSE;
460 } else {
461 (*span_num_days)--;
462 }
463 } else if (end_day_of_week == 6) { /* Sun */
464 return FALSE;
465 }
466 }
467 } else {
468 gint day_x, day_y, rows = 0;
469 e_week_view_layout_get_day_position (
470 end_day_of_week, multi_week_view, 1,
471 display_start_day, compress_weekend,
472 &day_x, &day_y, &rows);
473
474 if (((rows / 2) * rows_per_cell) + ((rows % 2) *
475 rows_per_compressed_cell) <= span->row)
476 return FALSE;
477 }
478 }
479
480 return TRUE;
481 }