diff options
Diffstat (limited to 'calendar/gui/layout.c')
-rw-r--r-- | calendar/gui/layout.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/calendar/gui/layout.c b/calendar/gui/layout.c new file mode 100644 index 0000000000..c8dfde6bdb --- /dev/null +++ b/calendar/gui/layout.c @@ -0,0 +1,309 @@ +/* Evolution calendar - Event layout engine + * + * Copyright (C) 2000 Helix Code, Inc. + * + * Authors: Miguel de Icaza <miguel@helixcode.com> + * Federico Mena-Quintero <federico@helixcode.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <config.h> +#include <stdlib.h> +#include "layout.h" + + + +/* This structure is used to pass around layout information among the internal + * layout functions. + */ +struct layout_info { + GList *events; /* List of events from client */ + int num_events; /* The number of events (length of the list) */ + LayoutQueryTimeFunc func; /* Function to convert a list item to a start/end time pair */ + int num_rows; /* Size of the time partition */ + time_t *partition; /* The time partition containing start and end time values */ + int *array; /* Working array of free and allocated time slots */ + int *allocations; /* Returned array of slot allocations */ + int *slots; /* Returned array of slots used */ + int num_slots; /* Number of slots used */ +}; + + + +/* This defines the maximum number of events to overlap per row. More than that + * number of events will not be displayed. This is not ideal, so sue me. + */ +#define MAX_EVENTS_PER_ROW 32 + + +/* Compares two time_t values, used for qsort() */ +static int +compare_time_t (const void *a, const void *b) +{ + time_t ta, tb; + + ta = *((time_t *) a); + tb = *((time_t *) b); + + if (ta < tb) + return -1; + else if (ta > tb) + return 1; + else + return 0; +} + +/* Builds a partition of the time range occupied by the events in the list. It returns an array + * with the times that define the partition and the number of items in the partition. + */ +static void +build_partition (struct layout_info *li) +{ + time_t *rows, *p, *q; + GList *list; + int i, unique_vals; + + /* This is the maximum number of rows we would need */ + + li->num_rows = li->num_events * 2; + + /* Fill the rows with the times */ + + rows = g_new (time_t, li->num_rows); + + for (list = li->events, p = rows; list; list = list->next) { + (* li->func) (list, &p[0], &p[1]); + p += 2; + } + + /* Do a sort | uniq on the array */ + + qsort (rows, li->num_rows, sizeof (time_t), compare_time_t); + + p = rows; + q = rows + 1; + unique_vals = 1; + + for (i = 1; i < li->num_rows; i++, q++) + if (*q != *p) { + unique_vals++; + p++; + *p = *q; + } + + /* Return the number of unique values in the partition and the partition array itself */ + + li->num_rows = unique_vals; + li->partition = rows; +} + +/* Returns the index of the element in the partition that corresponds to the specified time */ +static int +find_index (struct layout_info *li, time_t t) +{ + int i; + + for (i = 0; ; i++) + if (li->partition[i] == t) + return i; + + g_assert_not_reached (); +} + +#define xy(li, x, y) li->array[(y * MAX_EVENTS_PER_ROW) + (x)] + +/* Checks that all the cells in the slot array at the specified slot column are free to use by an + * event that has the specified range. + */ +static int +range_is_empty (struct layout_info *li, int slot, time_t start, time_t end) +{ + int i; + + for (i = find_index (li, start); li->partition[i] < end; i++) + if (xy (li, slot, i) != -1) + return FALSE; + + return TRUE; +} + +/* Allocates a time in the slot array for the specified event's index */ +static void +range_allocate (struct layout_info *li, int slot, time_t start, time_t end, int ev_num) +{ + int i; + + for (i = find_index (li, start); li->partition[i] < end; i++) + xy (li, slot, i) = ev_num; +} + +/* Performs the initial allocation of slots for events. Each event gets one column; they will be + * expanded in a later stage. Returns the number of columns used. + */ +static void +initial_allocate (struct layout_info *li) +{ + GList *events; + int i; + int slot; + int num_slots; + time_t start, end; + + num_slots = 0; + + for (i = 0, events = li->events; events; events = events->next, i++) { + (* li->func) (events, &start, &end); + + /* Start with no allocation, no columns */ + + li->allocations[i] = -1; + li->slots[i] = 0; + + /* Find a free column for the event */ + + for (slot = 0; slot < MAX_EVENTS_PER_ROW; slot++) + if (range_is_empty (li, slot, start, end)) { + range_allocate (li, slot, start, end, i); + + li->allocations[i] = slot; + li->slots[i] = 1; + + if ((slot + 1) > num_slots) + num_slots = slot + 1; + + break; + } + } + + li->num_slots = num_slots; +} + +/* Returns the maximum number of columns that an event can expanded by in the slot array */ +static int +columns_to_expand (struct layout_info *li, int ev_num, time_t start, time_t end) +{ + int cols; + int slot; + int i_start; + int i; + + cols = 0; + + i_start = find_index (li, start); + + for (slot = li->allocations[ev_num] + 1; slot < li->num_slots; slot++) { + for (i = i_start; li->partition[i] < end; i++) + if (xy (li, slot, i) != -1) + return cols; + + cols++; + } + + return cols; +} + +/* Expands an event by the specified number of columns */ +static void +do_expansion (struct layout_info *li, int ev_num, time_t start, time_t end, int num_cols) +{ + int i, j; + int slot; + + for (i = find_index (li, start); li->partition[i] < end; i++) { + slot = li->allocations[ev_num] + 1; + + for (j = 0; j < num_cols; j++) + xy (li, slot + j, i) = ev_num; + } +} + +/* Expands the events in the slot array to occupy as many columns as possible. This is the second + * pass of the layout algorithm. + */ +static void +expand_events (struct layout_info *li) +{ + GList *events; + time_t start, end; + int i; + int cols; + + for (i = 0, events = li->events; events; events = events->next, i++) { + (* li->func) (events, &start, &end); + + cols = columns_to_expand (li, i, start, end); + + if (cols == 0) + continue; /* We can't expand this event */ + + do_expansion (li, i, start, end, cols); + + li->slots[i] += cols; + } +} + +void +layout_events (GList *events, LayoutQueryTimeFunc func, + int *num_slots, int **allocations, int **slots) +{ + struct layout_info li; + int i; + + g_return_if_fail (num_slots != NULL); + g_return_if_fail (allocations != NULL); + g_return_if_fail (slots != NULL); + + if (!events) { + *num_slots = 0; + *allocations = NULL; + *slots = NULL; + + return; + } + + li.events = events; + li.num_events = g_list_length (events); + li.func = func; + + /* Build the partition of the time range, and then build the array of slots */ + + build_partition (&li); + + li.array = g_new (int, li.num_rows * MAX_EVENTS_PER_ROW); + for (i = 0; i < (li.num_rows * MAX_EVENTS_PER_ROW); i++) + li.array[i] = -1; /* This is our 'empty' value */ + + /* Build the arrays for allocations and columns used */ + + li.allocations = g_new (int, li.num_events); + li.slots = g_new (int, li.num_events); + + /* Perform initial allocation and then expand the events to as many + * slots as they can occupy. + */ + + initial_allocate (&li); + expand_events (&li); + + /* Clean up and return values */ + + g_free (li.partition); + g_free (li.array); + + *num_slots = li.num_slots; + *allocations = li.allocations; + *slots = li.slots; +} |