evolution-3.6.4/calendar/gui/e-week-view-layout.c

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 }