/* Event layout engine for Gnomecal * * Copyright (C) 1998 The Free Software Foundation * * Authors: Miguel de Icaza * Federico Mena */ #include "layout.h" /* 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 time_t * build_partition (GList *events, int num_events, int *num_rows) { time_t *rows, *p, *q; GList *list; CalendarObject *co; int i, unique_vals; /* This is the maximum number of rows we would need */ *num_rows = num_events * 2; /* Fill the rows with the times */ rows = g_new (time_t, *num_rows); for (list = events, p = rows; list; list = list->next) { co = list->data; *p++ = co->ev_start; *p++ = co->ev_end; } /* Do a sort | uniq on the array */ qsort (rows, *num_rows, sizeof (time_t), compare_time_t); p = rows; q = rows + 1; unique_vals = 1; for (i = 1; i < *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 */ *num_rows = unique_vals; return rows; } /* Returns the index of the element in the partition that corresponds to the specified time */ int find_index (time_t *partition, time_t t) { int i; for (i = 0; ; i++) if (partition[i] == t) return i; } #define xy(slot_array, x, y) slot_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 (time_t *partition, int *slot_array, int slot, time_t start, time_t end) { int i; for (i = find_index (partition, start); partition[i] < end; i++) if (xy (slot_array, slot, i) != -1) return FALSE; return TRUE; } /* Allocates a time in the slot array for the specified event's index */ static void range_allocate (time_t *partition, int *slot_array, int slot, time_t start, time_t end, int ev_num) { int i; for (i = find_index (partition, start); partition[i] < end; i++) xy (slot_array, 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 int initial_allocate (GList *events, time_t *partition, int *slot_array, int *allocations, int *columns_used) { CalendarObject *co; int i; int slot; int num_slots; num_slots = 0; for (i = 0; events; events = events->next, i++) { co = events->data; /* Start with no allocation, no columns */ allocations[i] = -1; columns_used[i] = 0; /* Find a free column for the event */ for (slot = 0; slot < MAX_EVENTS_PER_ROW; slot++) if (range_is_empty (partition, slot_array, slot, co->ev_start, co->ev_end)) { range_allocate (partition, slot_array, slot, co->ev_start, co->ev_end, i); allocations[i] = slot; columns_used[i] = 1; if ((slot + 1) > num_slots) num_slots = slot + 1; break; } } return num_slots; } /* Returns the maximum number of columns that an event can expanded by in the slot array */ static int columns_to_expand (time_t *partition, int *slot_array, int *allocations, int num_slots, int ev_num, time_t start, time_t end) { int cols; int slot; int i_start; int i; cols = 0; i_start = find_index (partition, start); for (slot = allocations[ev_num] + 1; slot < num_slots; slot++) { for (i = i_start; partition[i] < end; i++) if (xy (slot_array, slot, i) != -1) return cols; cols++; } return cols; } /* Expands an event to occupy the specified number of columns */ static void do_expansion (time_t *partition, int *slot_array, int *allocations, int ev_num, time_t start, time_t end, int num_cols) { int i, j; int slot; for (i = find_index (partition, start); partition[i] < end; i++) { slot = allocations[ev_num] + 1; for (j = 0; j < num_cols; j++) xy (slot_array, 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 (GList *events, time_t *partition, int *slot_array, int num_slots, int *allocations, int *columns_used) { int i; CalendarObject *co; int cols; for (i = 0; events; events = events->next, i++) { co = events->data; cols = columns_to_expand (partition, slot_array, allocations, num_slots, i, co->ev_start, co->ev_end); if (cols == 0) continue; /* We can't expand this event */ do_expansion (partition, slot_array, allocations, i, co->ev_start, co->ev_end, cols); columns_used[i] += cols; } } void layout_events (GList *events, int *num_slots, int **allocations, int **slots) { time_t *time_partition; int *slot_array; int num_events; int num_rows; 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; } num_events = g_list_length (events); /* Build the partition of the time range, and then build the array of slots */ time_partition = build_partition (events, num_events, &num_rows); slot_array = g_new (int, num_rows * MAX_EVENTS_PER_ROW); for (i = 0; i < (num_rows * MAX_EVENTS_PER_ROW); i++) slot_array[i] = -1; /* This is our 'empty' value */ /* Build the arrays for allocations and columns used */ *allocations = g_new (int, num_events); *slots = g_new (int, num_events); /* Perform initial allocation -- each event gets one column */ *num_slots = initial_allocate (events, time_partition, slot_array, *allocations, *slots); /* Expand the events to as many columns as possible */ expand_events (events, time_partition, slot_array, *num_slots, *allocations, *slots); g_free (slot_array); }