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
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/>.
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
28 #include "evolution-config.h"
30 #include "e-day-view-layout.h"
32 static void e_day_view_layout_long_event (EDayViewEvent
*event
,
36 gint
*rows_in_top_display
);
38 static void e_day_view_layout_day_event (EDayViewEvent
*event
,
40 guint16
*group_starts
,
45 static void e_day_view_expand_day_event (EDayViewEvent
*event
,
49 static void e_day_view_recalc_cols_per_row (gint rows
,
51 guint16
*group_starts
);
54 e_day_view_layout_long_events (GArray
*events
,
57 gint
*rows_in_top_display
)
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 (
79 days_shown
, day_starts
,
88 e_day_view_layout_long_event (EDayViewEvent
*event
,
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
))
103 /* Try each row until we find a free one. */
107 for (day
= start_day
; day
<= end_day
; day
++) {
108 if (grid
[row
* E_DAY_VIEW_MAX_DAYS
+ day
]) {
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 */
130 e_day_view_layout_day_events (GArray
*events
,
133 guint8
*cols_per_row
,
136 EDayViewEvent
*event
;
137 gint row
, event_num
, res
;
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
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
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
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
,
187 /* Free the grid and compute maximum number of columns used. */
189 for (row
= 0; row
< rows
; row
++) {
190 res
= MAX (res
, e_bit_array_bit_count (grid
[row
]));
191 g_object_unref (grid
[row
]);
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. */
202 e_day_view_layout_day_event (EDayViewEvent
*event
,
204 guint16
*group_starts
,
205 guint8
*cols_per_row
,
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
)
217 event
->num_columns
= 0;
219 /* If the event can't currently be seen, just return. */
220 if (start_row
>= rows
|| end_row
< 0)
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
++) {
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
)) {
242 /* If we can't find space for the event, just 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
)
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
);
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
)
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. */
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
) {
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. */
302 e_day_view_expand_day_event (EDayViewEvent
*event
,
304 guint8
*cols_per_row
,
307 gint start_row
, end_row
, col
, row
;
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
)
315 /* Try each column until we find a free one. */
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
)) {
329 event
->num_columns
++;
333 /* Find the start and end days for the event. */
335 e_day_view_find_long_event_days (EDayViewEvent
*event
,
338 gint
*start_day_return
,
339 gint
*end_day_return
)
341 gint day
, start_day
, end_day
;
346 for (day
= 0; day
< days_shown
; day
++) {
348 && event
->start
< day_starts
[day
+ 1])
350 if (event
->end
> day_starts
[day
])
354 if (event
->start
== event
->end
)
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
);
365 *start_day_return
= start_day
;
366 *end_day_return
= end_day
;