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/>
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
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
,
38 gint
*rows_in_top_display
);
40 static void e_day_view_layout_day_event (EDayViewEvent
*event
,
42 guint16
*group_starts
,
47 static void e_day_view_expand_day_event (EDayViewEvent
*event
,
51 static void e_day_view_recalc_cols_per_row (gint rows
,
53 guint16
*group_starts
);
56 e_day_view_layout_long_events (GArray
*events
,
59 gint
*rows_in_top_display
)
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
,
89 e_day_view_layout_long_event (EDayViewEvent
*event
,
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
))
104 /* Try each row until we find a free one. */
108 for (day
= start_day
; day
<= end_day
; day
++) {
109 if (grid
[row
* E_DAY_VIEW_MAX_DAYS
+ day
]) {
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 */
131 e_day_view_layout_day_events (GArray
*events
,
134 guint8
*cols_per_row
,
137 EDayViewEvent
*event
;
138 gint row
, event_num
, res
;
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
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
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
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
,
186 /* Free the grid and compute maximum number of columns used. */
188 for (row
= 0; row
< rows
; row
++) {
189 res
= MAX (res
, e_bit_array_bit_count (grid
[row
]));
190 g_object_unref (grid
[row
]);
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. */
201 e_day_view_layout_day_event (EDayViewEvent
*event
,
203 guint16
*group_starts
,
204 guint8
*cols_per_row
,
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
)
216 event
->num_columns
= 0;
218 /* If the event can't currently be seen, just return. */
219 if (start_row
>= rows
|| end_row
< 0)
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
++) {
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
)) {
241 /* If we can't find space for the event, just 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
)
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
);
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
)
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. */
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
) {
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. */
301 e_day_view_expand_day_event (EDayViewEvent
*event
,
303 guint8
*cols_per_row
,
306 gint start_row
, end_row
, col
, row
;
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
)
314 /* Try each column until we find a free one. */
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
)) {
328 event
->num_columns
++;
332 /* Find the start and end days for the event. */
334 e_day_view_find_long_event_days (EDayViewEvent
*event
,
337 gint
*start_day_return
,
338 gint
*end_day_return
)
340 gint day
, start_day
, end_day
;
345 for (day
= 0; day
< days_shown
; day
++) {
347 && event
->start
< day_starts
[day
+ 1])
349 if (event
->end
> day_starts
[day
])
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");
361 *start_day_return
= start_day
;
362 *end_day_return
= end_day
;