Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-day-view-layout.c
blob65405b047b5815328279b42e6197d15a4c152313
1 /*
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU Lesser General Public License as published by
5 * the Free Software Foundation.
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
10 * for more details.
12 * You should have received a copy of the GNU Lesser General Public License
13 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 * Authors:
17 * Damon Chaplin <damon@ximian.com>
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
24 * Lays out events for the Day & Work-Week views of the calendar. It is also
25 * used for printing.
28 #include "evolution-config.h"
30 #include "e-day-view-layout.h"
32 static void e_day_view_layout_long_event (EDayViewEvent *event,
33 guint8 *grid,
34 gint days_shown,
35 time_t *day_starts,
36 gint *rows_in_top_display);
38 static void e_day_view_layout_day_event (EDayViewEvent *event,
39 EBitArray **grid,
40 guint16 *group_starts,
41 guint8 *cols_per_row,
42 gint rows,
43 gint mins_per_row,
44 gint max_cols);
45 static void e_day_view_expand_day_event (EDayViewEvent *event,
46 EBitArray **grid,
47 guint8 *cols_per_row,
48 gint mins_per_row);
49 static void e_day_view_recalc_cols_per_row (gint rows,
50 guint8 *cols_per_row,
51 guint16 *group_starts);
53 void
54 e_day_view_layout_long_events (GArray *events,
55 gint days_shown,
56 time_t *day_starts,
57 gint *rows_in_top_display)
59 EDayViewEvent *event;
60 gint event_num;
61 guint8 *grid;
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, events->len * E_DAY_VIEW_MAX_DAYS);
69 /* Reset the number of rows in the top display to 0. It will be
70 * updated as events are layed out below. */
71 *rows_in_top_display = 0;
73 /* Iterate over the events, finding which days they cover, and putting
74 * them in the first free row available. */
75 for (event_num = 0; event_num < events->len; event_num++) {
76 event = &g_array_index (events, EDayViewEvent, event_num);
77 e_day_view_layout_long_event (
78 event, grid,
79 days_shown, day_starts,
80 rows_in_top_display);
83 /* Free the grid. */
84 g_free (grid);
87 static void
88 e_day_view_layout_long_event (EDayViewEvent *event,
89 guint8 *grid,
90 gint days_shown,
91 time_t *day_starts,
92 gint *rows_in_top_display)
94 gint start_day, end_day, free_row, day, row;
96 event->num_columns = 0;
98 if (!e_day_view_find_long_event_days (event,
99 days_shown, day_starts,
100 &start_day, &end_day))
101 return;
103 /* Try each row until we find a free one. */
104 row = 0;
105 do {
106 free_row = row;
107 for (day = start_day; day <= end_day; day++) {
108 if (grid[row * E_DAY_VIEW_MAX_DAYS + day]) {
109 free_row = -1;
110 break;
113 row++;
114 } while (free_row == -1);
116 event->start_row_or_col = free_row;
117 event->num_columns = 1;
119 /* Mark the cells as full. */
120 for (day = start_day; day <= end_day; day++) {
121 grid[free_row * E_DAY_VIEW_MAX_DAYS + day] = 1;
124 /* Update the number of rows in the top canvas if necessary. */
125 *rows_in_top_display = MAX (*rows_in_top_display, free_row + 1);
128 /* returns maximum number of columns among all rows */
129 gint
130 e_day_view_layout_day_events (GArray *events,
131 gint rows,
132 gint mins_per_row,
133 guint8 *cols_per_row,
134 gint max_cols)
136 EDayViewEvent *event;
137 gint row, event_num, res;
138 EBitArray **grid;
140 /* This is a temporary array which keeps track of rows which are
141 * connected. When an appointment spans multiple rows then the number
142 * of columns in each of these rows must be the same (i.e. the maximum
143 * of all of them). Each element in the array corresponds to one row
144 * and contains the index of the first row in the group of connected
145 * rows. */
146 guint16 group_starts[12 * 24];
148 /* This is a temporary 2-d grid which is used to place events.
149 * Each element is 0 if the position is empty, or 1 if occupied. */
150 grid = g_new0 (EBitArray *, rows);
152 /* Reset the cols_per_row array, and initialize the connected rows so
153 * that all rows are not connected - each row is the start of a new
154 * group. */
155 for (row = 0; row < rows; row++) {
156 cols_per_row[row] = 0;
157 group_starts[row] = row;
159 /* row doesn't contain any event at the moment */
160 grid[row] = e_bit_array_new (0);
163 /* Iterate over the events, finding which rows they cover, and putting
164 * them in the first free column available. Increment the number of
165 * events in each of the rows it covers, and make sure they are all
166 * in one group. */
167 for (event_num = 0; event_num < events->len; event_num++) {
168 event = &g_array_index (events, EDayViewEvent, event_num);
170 e_day_view_layout_day_event (
171 event, grid, group_starts,
172 cols_per_row, rows, mins_per_row, max_cols);
175 /* Recalculate the number of columns needed in each row. */
176 e_day_view_recalc_cols_per_row (rows, cols_per_row, group_starts);
178 /* Iterate over the events again, trying to expand events horizontally
179 * if there is enough space. */
180 for (event_num = 0; event_num < events->len; event_num++) {
181 event = &g_array_index (events, EDayViewEvent, event_num);
182 e_day_view_expand_day_event (
183 event, grid, cols_per_row,
184 mins_per_row);
187 /* Free the grid and compute maximum number of columns used. */
188 res = 0;
189 for (row = 0; row < rows; row++) {
190 res = MAX (res, e_bit_array_bit_count (grid[row]));
191 g_object_unref (grid[row]);
193 g_free (grid);
195 return res;
198 /* Finds the first free position to place the event in.
199 * Increments the number of events in each of the rows it covers, and makes
200 * sure they are all in one group. */
201 static void
202 e_day_view_layout_day_event (EDayViewEvent *event,
203 EBitArray **grid,
204 guint16 *group_starts,
205 guint8 *cols_per_row,
206 gint rows,
207 gint mins_per_row,
208 gint max_cols)
210 gint start_row, end_row, free_col, col, row, group_start;
212 start_row = event->start_minute / mins_per_row;
213 end_row = (event->end_minute - 1) / mins_per_row;
214 if (end_row < start_row)
215 end_row = start_row;
217 event->num_columns = 0;
219 /* If the event can't currently be seen, just return. */
220 if (start_row >= rows || end_row < 0)
221 return;
223 /* Make sure we don't go outside the visible times. */
224 start_row = CLAMP (start_row, 0, rows - 1);
225 end_row = CLAMP (end_row, 0, rows - 1);
227 /* Try each column until we find a free one. */
228 for (col = 0; max_cols <= 0 || col < max_cols; col++) {
229 free_col = col;
230 for (row = start_row; row <= end_row; row++) {
231 if (e_bit_array_bit_count (grid[row]) > col &&
232 e_bit_array_value_at (grid[row], col)) {
233 free_col = -1;
234 break;
238 if (free_col != -1)
239 break;
242 /* If we can't find space for the event, just return. */
243 if (free_col == -1)
244 return;
246 /* The event is assigned 1 col initially, but may be expanded later. */
247 event->start_row_or_col = free_col;
248 event->num_columns = 1;
250 /* Determine the start index of the group. */
251 group_start = group_starts[start_row];
253 /* Increment number of events in each of the rows the event covers.
254 * We use the cols_per_row array for this. It will be sorted out after
255 * all the events have been layed out. Also make sure all the rows that
256 * the event covers are in one group. */
257 for (row = start_row; row <= end_row; row++) {
258 /* resize the array if necessary */
259 if (e_bit_array_bit_count (grid[row]) <= free_col)
260 e_bit_array_insert (
261 grid[row], e_bit_array_bit_count (grid[row]),
262 free_col - e_bit_array_bit_count (grid[row]) + 1);
264 e_bit_array_change_one_row (grid[row], free_col, TRUE);
265 cols_per_row[row]++;
266 group_starts[row] = group_start;
269 /* If any following rows should be in the same group, add them. */
270 for (row = end_row + 1; row < rows; row++) {
271 if (group_starts[row] > end_row)
272 break;
273 group_starts[row] = group_start;
277 /* For each group of rows, find the max number of events in all the
278 * rows, and set the number of cols in each of the rows to that. */
279 static void
280 e_day_view_recalc_cols_per_row (gint rows,
281 guint8 *cols_per_row,
282 guint16 *group_starts)
284 gint start_row = 0, row, next_start_row, max_events;
286 while (start_row < rows) {
287 max_events = 0;
288 for (row = start_row; row < rows && group_starts[row] == start_row; row++)
289 max_events = MAX (max_events, cols_per_row[row]);
291 next_start_row = row;
293 for (row = start_row; row < next_start_row; row++)
294 cols_per_row[row] = max_events;
296 start_row = next_start_row;
300 /* Expands the event horizontally to fill any free space. */
301 static void
302 e_day_view_expand_day_event (EDayViewEvent *event,
303 EBitArray **grid,
304 guint8 *cols_per_row,
305 gint mins_per_row)
307 gint start_row, end_row, col, row;
308 gboolean clashed;
310 start_row = event->start_minute / mins_per_row;
311 end_row = (event->end_minute - 1) / mins_per_row;
312 if (end_row < start_row)
313 end_row = start_row;
315 /* Try each column until we find a free one. */
316 clashed = FALSE;
317 for (col = event->start_row_or_col + 1; col < cols_per_row[start_row]; col++) {
318 for (row = start_row; row <= end_row; row++) {
319 if (e_bit_array_bit_count (grid[row]) > col &&
320 e_bit_array_value_at (grid[row], col)) {
321 clashed = TRUE;
322 break;
326 if (clashed)
327 break;
329 event->num_columns++;
333 /* Find the start and end days for the event. */
334 gboolean
335 e_day_view_find_long_event_days (EDayViewEvent *event,
336 gint days_shown,
337 time_t *day_starts,
338 gint *start_day_return,
339 gint *end_day_return)
341 gint day, start_day, end_day;
343 start_day = -1;
344 end_day = -1;
346 for (day = 0; day < days_shown; day++) {
347 if (start_day == -1
348 && event->start < day_starts[day + 1])
349 start_day = day;
350 if (event->end > day_starts[day])
351 end_day = day;
354 if (event->start == event->end)
355 end_day = start_day;
357 /* Sanity check. */
358 if (start_day < 0 || start_day >= days_shown
359 || end_day < 0 || end_day >= days_shown
360 || end_day < start_day) {
361 g_warning ("Invalid date range for event, start/end days: %d / %d", start_day, end_day);
362 return FALSE;
365 *start_day_return = start_day;
366 *end_day_return = end_day;
368 return TRUE;