Updated Traditional Chinese translation(Hong Kong and Taiwan)
[evolution.git] / calendar / gui / e-day-view-layout.c
blobbdb51977cc0928de939f3e51324099c3356140af
1 /*
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with the 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)
25 * Lays out events for the Day & Work-Week views of the calendar. It is also
26 * used for printing.
29 #include <config.h>
31 #include "e-day-view-layout.h"
32 #include "e-util/e-bit-array.h"
34 static void e_day_view_layout_long_event (EDayViewEvent *event,
35 guint8 *grid,
36 gint days_shown,
37 time_t *day_starts,
38 gint *rows_in_top_display);
40 static void e_day_view_layout_day_event (EDayViewEvent *event,
41 EBitArray **grid,
42 guint16 *group_starts,
43 guint8 *cols_per_row,
44 gint rows,
45 gint mins_per_row,
46 gint max_cols);
47 static void e_day_view_expand_day_event (EDayViewEvent *event,
48 EBitArray **grid,
49 guint8 *cols_per_row,
50 gint mins_per_row);
51 static void e_day_view_recalc_cols_per_row (gint rows,
52 guint8 *cols_per_row,
53 guint16 *group_starts);
55 void
56 e_day_view_layout_long_events (GArray *events,
57 gint days_shown,
58 time_t *day_starts,
59 gint *rows_in_top_display)
61 EDayViewEvent *event;
62 gint event_num;
63 guint8 *grid;
65 /* This is a temporary 2-d grid which is used to place events.
66 Each element is 0 if the position is empty, or 1 if occupied.
67 We allocate the maximum size possible here, assuming that each
68 event will need its own row. */
69 grid = g_new0 (guint8, events->len * E_DAY_VIEW_MAX_DAYS);
71 /* Reset the number of rows in the top display to 0. It will be
72 updated as events are layed out below. */
73 *rows_in_top_display = 0;
75 /* Iterate over the events, finding which days they cover, and putting
76 them in the first free row available. */
77 for (event_num = 0; event_num < events->len; event_num++) {
78 event = &g_array_index (events, EDayViewEvent, event_num);
79 e_day_view_layout_long_event (event, grid,
80 days_shown, day_starts,
81 rows_in_top_display);
84 /* Free the grid. */
85 g_free (grid);
88 static void
89 e_day_view_layout_long_event (EDayViewEvent *event,
90 guint8 *grid,
91 gint days_shown,
92 time_t *day_starts,
93 gint *rows_in_top_display)
95 gint start_day, end_day, free_row, day, row;
97 event->num_columns = 0;
99 if (!e_day_view_find_long_event_days (event,
100 days_shown, day_starts,
101 &start_day, &end_day))
102 return;
104 /* Try each row until we find a free one. */
105 row = 0;
106 do {
107 free_row = row;
108 for (day = start_day; day <= end_day; day++) {
109 if (grid[row * E_DAY_VIEW_MAX_DAYS + day]) {
110 free_row = -1;
111 break;
114 row++;
115 } while (free_row == -1);
117 event->start_row_or_col = free_row;
118 event->num_columns = 1;
120 /* Mark the cells as full. */
121 for (day = start_day; day <= end_day; day++) {
122 grid[free_row * E_DAY_VIEW_MAX_DAYS + day] = 1;
125 /* Update the number of rows in the top canvas if necessary. */
126 *rows_in_top_display = MAX (*rows_in_top_display, free_row + 1);
129 /* returns maximum number of columns among all rows */
130 gint
131 e_day_view_layout_day_events (GArray *events,
132 gint rows,
133 gint mins_per_row,
134 guint8 *cols_per_row,
135 gint max_cols)
137 EDayViewEvent *event;
138 gint row, event_num, res;
139 EBitArray **grid;
141 /* This is a temporary array which keeps track of rows which are
142 connected. When an appointment spans multiple rows then the number
143 of columns in each of these rows must be the same (i.e. the maximum
144 of all of them). Each element in the array corresponds to one row
145 and contains the index of the first row in the group of connected
146 rows. */
147 guint16 group_starts[12 * 24];
149 /* This is a temporary 2-d grid which is used to place events.
150 Each element is 0 if the position is empty, or 1 if occupied. */
151 grid = g_new0 (EBitArray *, rows);
153 /* Reset the cols_per_row array, and initialize the connected rows so
154 that all rows are not connected - each row is the start of a new
155 group. */
156 for (row = 0; row < rows; row++) {
157 cols_per_row[row] = 0;
158 group_starts[row] = row;
160 /* row doesn't contain any event at the moment */
161 grid[row] = e_bit_array_new (0);
164 /* Iterate over the events, finding which rows they cover, and putting
165 them in the first free column available. Increment the number of
166 events in each of the rows it covers, and make sure they are all
167 in one group. */
168 for (event_num = 0; event_num < events->len; event_num++) {
169 event = &g_array_index (events, EDayViewEvent, event_num);
171 e_day_view_layout_day_event (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 (event, grid, cols_per_row,
183 mins_per_row);
186 /* Free the grid and compute maximum number of columns used. */
187 res = 0;
188 for (row = 0; row < rows; row++) {
189 res = MAX (res, e_bit_array_bit_count (grid[row]));
190 g_object_unref (grid[row]);
192 g_free (grid);
194 return res;
197 /* Finds the first free position to place the event in.
198 Increments the number of events in each of the rows it covers, and makes
199 sure they are all in one group. */
200 static void
201 e_day_view_layout_day_event (EDayViewEvent *event,
202 EBitArray **grid,
203 guint16 *group_starts,
204 guint8 *cols_per_row,
205 gint rows,
206 gint mins_per_row,
207 gint max_cols)
209 gint start_row, end_row, free_col, col, row, group_start;
211 start_row = event->start_minute / mins_per_row;
212 end_row = (event->end_minute - 1) / mins_per_row;
213 if (end_row < start_row)
214 end_row = start_row;
216 event->num_columns = 0;
218 /* If the event can't currently be seen, just return. */
219 if (start_row >= rows || end_row < 0)
220 return;
222 /* Make sure we don't go outside the visible times. */
223 start_row = CLAMP (start_row, 0, rows - 1);
224 end_row = CLAMP (end_row, 0, rows - 1);
226 /* Try each column until we find a free one. */
227 for (col = 0; max_cols <= 0 || col < max_cols; col++) {
228 free_col = col;
229 for (row = start_row; row <= end_row; row++) {
230 if (e_bit_array_bit_count (grid[row]) > col &&
231 e_bit_array_value_at (grid[row], col)) {
232 free_col = -1;
233 break;
237 if (free_col != -1)
238 break;
241 /* If we can't find space for the event, just return. */
242 if (free_col == -1)
243 return;
245 /* The event is assigned 1 col initially, but may be expanded later. */
246 event->start_row_or_col = free_col;
247 event->num_columns = 1;
249 /* Determine the start index of the group. */
250 group_start = group_starts[start_row];
252 /* Increment number of events in each of the rows the event covers.
253 We use the cols_per_row array for this. It will be sorted out after
254 all the events have been layed out. Also make sure all the rows that
255 the event covers are in one group. */
256 for (row = start_row; row <= end_row; row++) {
257 /* resize the array if necessary */
258 if (e_bit_array_bit_count (grid[row]) <= free_col)
259 e_bit_array_insert (
260 grid[row], e_bit_array_bit_count (grid[row]),
261 free_col - e_bit_array_bit_count (grid[row]) + 1);
263 e_bit_array_change_one_row (grid[row], free_col, TRUE);
264 cols_per_row[row]++;
265 group_starts[row] = group_start;
268 /* If any following rows should be in the same group, add them. */
269 for (row = end_row + 1; row < rows; row++) {
270 if (group_starts[row] > end_row)
271 break;
272 group_starts[row] = group_start;
276 /* For each group of rows, find the max number of events in all the
277 rows, and set the number of cols in each of the rows to that. */
278 static void
279 e_day_view_recalc_cols_per_row (gint rows,
280 guint8 *cols_per_row,
281 guint16 *group_starts)
283 gint start_row = 0, row, next_start_row, max_events;
285 while (start_row < rows) {
286 max_events = 0;
287 for (row = start_row; row < rows && group_starts[row] == start_row; row++)
288 max_events = MAX (max_events, cols_per_row[row]);
290 next_start_row = row;
292 for (row = start_row; row < next_start_row; row++)
293 cols_per_row[row] = max_events;
295 start_row = next_start_row;
299 /* Expands the event horizontally to fill any free space. */
300 static void
301 e_day_view_expand_day_event (EDayViewEvent *event,
302 EBitArray **grid,
303 guint8 *cols_per_row,
304 gint mins_per_row)
306 gint start_row, end_row, col, row;
307 gboolean clashed;
309 start_row = event->start_minute / mins_per_row;
310 end_row = (event->end_minute - 1) / mins_per_row;
311 if (end_row < start_row)
312 end_row = start_row;
314 /* Try each column until we find a free one. */
315 clashed = FALSE;
316 for (col = event->start_row_or_col + 1; col < cols_per_row[start_row]; col++) {
317 for (row = start_row; row <= end_row; row++) {
318 if (e_bit_array_bit_count (grid[row]) > col &&
319 e_bit_array_value_at (grid[row], col)) {
320 clashed = TRUE;
321 break;
325 if (clashed)
326 break;
328 event->num_columns++;
332 /* Find the start and end days for the event. */
333 gboolean
334 e_day_view_find_long_event_days (EDayViewEvent *event,
335 gint days_shown,
336 time_t *day_starts,
337 gint *start_day_return,
338 gint *end_day_return)
340 gint day, start_day, end_day;
342 start_day = -1;
343 end_day = -1;
345 for (day = 0; day < days_shown; day++) {
346 if (start_day == -1
347 && event->start < day_starts[day + 1])
348 start_day = day;
349 if (event->end > day_starts[day])
350 end_day = day;
353 /* Sanity check. */
354 if (start_day < 0 || start_day >= days_shown
355 || end_day < 0 || end_day >= days_shown
356 || end_day < start_day) {
357 g_warning ("Invalid date range for event");
358 return FALSE;
361 *start_day_return = start_day;
362 *end_day_return = end_day;
364 return TRUE;