aboutsummaryrefslogtreecommitdiffstats
path: root/calendar/gui/e-day-view-layout.c
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/gui/e-day-view-layout.c')
-rw-r--r--calendar/gui/e-day-view-layout.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/calendar/gui/e-day-view-layout.c b/calendar/gui/e-day-view-layout.c
new file mode 100644
index 0000000000..00a448d7ec
--- /dev/null
+++ b/calendar/gui/e-day-view-layout.c
@@ -0,0 +1,349 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@ximian.com>
+ *
+ * Copyright 2001, Ximian, Inc.
+ *
+ * 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
+ */
+
+/*
+ * Lays out events for the Day & Work-Week views of the calendar. It is also
+ * used for printing.
+ */
+
+#include <config.h>
+
+#include "e-day-view-layout.h"
+
+static void e_day_view_layout_long_event (EDayViewEvent *event,
+ guint8 *grid,
+ gint days_shown,
+ time_t *day_starts,
+ gint *rows_in_top_display);
+
+static void e_day_view_layout_day_event (EDayViewEvent *event,
+ guint8 *grid,
+ guint16 *group_starts,
+ gint8 *cols_per_row,
+ gint rows,
+ gint mins_per_row);
+static void e_day_view_expand_day_event (EDayViewEvent *event,
+ guint8 *grid,
+ gint8 *cols_per_row,
+ gint mins_per_row);
+static void e_day_view_recalc_cols_per_row (gint rows,
+ gint8 *cols_per_row,
+ guint16 *group_starts);
+
+
+void
+e_day_view_layout_long_events (GArray *events,
+ gint days_shown,
+ time_t *day_starts,
+ gint *rows_in_top_display)
+{
+ EDayViewEvent *event;
+ gint event_num;
+ guint8 *grid;
+
+ /* This is a temporary 2-d grid which is used to place events.
+ Each element is 0 if the position is empty, or 1 if occupied.
+ We allocate the maximum size possible here, assuming that each
+ event will need its own row. */
+ grid = g_new0 (guint8, events->len * E_DAY_VIEW_MAX_DAYS);
+
+ /* Reset the number of rows in the top display to 0. It will be
+ updated as events are layed out below. */
+ *rows_in_top_display = 0;
+
+ /* Iterate over the events, finding which days they cover, and putting
+ them in the first free row available. */
+ for (event_num = 0; event_num < events->len; event_num++) {
+ event = &g_array_index (events, EDayViewEvent, event_num);
+ e_day_view_layout_long_event (event, grid,
+ days_shown, day_starts,
+ rows_in_top_display);
+ }
+
+ /* Free the grid. */
+ g_free (grid);
+}
+
+
+static void
+e_day_view_layout_long_event (EDayViewEvent *event,
+ guint8 *grid,
+ gint days_shown,
+ time_t *day_starts,
+ gint *rows_in_top_display)
+{
+ gint start_day, end_day, free_row, day, row;
+
+ event->num_columns = 0;
+
+ if (!e_day_view_find_long_event_days (event,
+ days_shown, day_starts,
+ &start_day, &end_day))
+ return;
+
+ /* Try each row until we find a free one. */
+ row = 0;
+ do {
+ free_row = row;
+ for (day = start_day; day <= end_day; day++) {
+ if (grid[row * E_DAY_VIEW_MAX_DAYS + day]) {
+ free_row = -1;
+ break;
+ }
+ }
+ row++;
+ } while (free_row == -1);
+
+ event->start_row_or_col = free_row;
+ event->num_columns = 1;
+
+ /* Mark the cells as full. */
+ for (day = start_day; day <= end_day; day++) {
+ grid[free_row * E_DAY_VIEW_MAX_DAYS + day] = 1;
+ }
+
+ /* Update the number of rows in the top canvas if necessary. */
+ *rows_in_top_display = MAX (*rows_in_top_display, free_row + 1);
+}
+
+
+void
+e_day_view_layout_day_events (GArray *events,
+ gint rows,
+ gint mins_per_row,
+ gint8 *cols_per_row)
+{
+ EDayViewEvent *event;
+ gint row, event_num;
+ guint8 *grid;
+
+ /* This is a temporary array which keeps track of rows which are
+ connected. When an appointment spans multiple rows then the number
+ of columns in each of these rows must be the same (i.e. the maximum
+ of all of them). Each element in the array corresponds to one row
+ and contains the index of the first row in the group of connected
+ rows. */
+ guint16 group_starts[12 * 24];
+
+ /* Reset the cols_per_row array, and initialize the connected rows so
+ that all rows are not connected - each row is the start of a new
+ group. */
+ for (row = 0; row < rows; row++) {
+ cols_per_row[row] = 0;
+ group_starts[row] = row;
+ }
+
+ /* This is a temporary 2-d grid which is used to place events.
+ Each element is 0 if the position is empty, or 1 if occupied. */
+ grid = g_new0 (guint8, rows * E_DAY_VIEW_MAX_COLUMNS);
+
+
+ /* Iterate over the events, finding which rows they cover, and putting
+ them in the first free column available. Increment the number of
+ events in each of the rows it covers, and make sure they are all
+ in one group. */
+ for (event_num = 0; event_num < events->len; event_num++) {
+ event = &g_array_index (events, EDayViewEvent, event_num);
+
+ e_day_view_layout_day_event (event, grid, group_starts,
+ cols_per_row, rows, mins_per_row);
+ }
+
+ /* Recalculate the number of columns needed in each row. */
+ e_day_view_recalc_cols_per_row (rows, cols_per_row, group_starts);
+
+ /* Iterate over the events again, trying to expand events horizontally
+ if there is enough space. */
+ for (event_num = 0; event_num < events->len; event_num++) {
+ event = &g_array_index (events, EDayViewEvent, event_num);
+ e_day_view_expand_day_event (event, grid, cols_per_row,
+ mins_per_row);
+ }
+
+ /* Free the grid. */
+ g_free (grid);
+}
+
+
+/* Finds the first free position to place the event in.
+ Increments the number of events in each of the rows it covers, and makes
+ sure they are all in one group. */
+static void
+e_day_view_layout_day_event (EDayViewEvent *event,
+ guint8 *grid,
+ guint16 *group_starts,
+ gint8 *cols_per_row,
+ gint rows,
+ gint mins_per_row)
+{
+ gint start_row, end_row, free_col, col, row, group_start;
+
+ start_row = event->start_minute / mins_per_row;
+ end_row = (event->end_minute - 1) / mins_per_row;
+
+ event->num_columns = 0;
+
+ /* If the event can't currently be seen, just return. */
+ if (start_row >= rows || end_row < 0)
+ return;
+
+ /* Make sure we don't go outside the visible times. */
+ start_row = CLAMP (start_row, 0, rows - 1);
+ end_row = CLAMP (end_row, 0, rows - 1);
+
+ /* Try each column until we find a free one. */
+ for (col = 0; col < E_DAY_VIEW_MAX_COLUMNS; col++) {
+ free_col = col;
+ for (row = start_row; row <= end_row; row++) {
+ if (grid[row * E_DAY_VIEW_MAX_COLUMNS + col]) {
+ free_col = -1;
+ break;
+ }
+ }
+
+ if (free_col != -1)
+ break;
+ }
+
+ /* If we can't find space for the event, just return. */
+ if (free_col == -1)
+ return;
+
+ /* The event is assigned 1 col initially, but may be expanded later. */
+ event->start_row_or_col = free_col;
+ event->num_columns = 1;
+
+ /* Determine the start index of the group. */
+ group_start = group_starts[start_row];
+
+ /* Increment number of events in each of the rows the event covers.
+ We use the cols_per_row array for this. It will be sorted out after
+ all the events have been layed out. Also make sure all the rows that
+ the event covers are in one group. */
+ for (row = start_row; row <= end_row; row++) {
+ grid[row * E_DAY_VIEW_MAX_COLUMNS + free_col] = 1;
+ cols_per_row[row]++;
+ group_starts[row] = group_start;
+ }
+
+ /* If any following rows should be in the same group, add them. */
+ for (row = end_row + 1; row < rows; row++) {
+ if (group_starts[row] > end_row)
+ break;
+ group_starts[row] = group_start;
+ }
+}
+
+
+/* For each group of rows, find the max number of events in all the
+ rows, and set the number of cols in each of the rows to that. */
+static void
+e_day_view_recalc_cols_per_row (gint rows,
+ gint8 *cols_per_row,
+ guint16 *group_starts)
+{
+ gint start_row = 0, row, next_start_row, max_events;
+
+ while (start_row < rows) {
+ max_events = 0;
+ for (row = start_row; row < rows && group_starts[row] == start_row; row++)
+ max_events = MAX (max_events, cols_per_row[row]);
+
+ next_start_row = row;
+
+ for (row = start_row; row < next_start_row; row++)
+ cols_per_row[row] = max_events;
+
+ start_row = next_start_row;
+ }
+}
+
+
+/* Expands the event horizontally to fill any free space. */
+static void
+e_day_view_expand_day_event (EDayViewEvent *event,
+ guint8 *grid,
+ gint8 *cols_per_row,
+ gint mins_per_row)
+{
+ gint start_row, end_row, col, row;
+ gboolean clashed;
+
+ start_row = event->start_minute / mins_per_row;
+ end_row = (event->end_minute - 1) / mins_per_row;
+
+ /* Try each column until we find a free one. */
+ clashed = FALSE;
+ for (col = event->start_row_or_col + 1; col < cols_per_row[start_row]; col++) {
+ for (row = start_row; row <= end_row; row++) {
+ if (grid[row * E_DAY_VIEW_MAX_COLUMNS + col]) {
+ clashed = TRUE;
+ break;
+ }
+ }
+
+ if (clashed)
+ break;
+
+ event->num_columns++;
+ }
+}
+
+
+/* Find the start and end days for the event. */
+gboolean
+e_day_view_find_long_event_days (EDayViewEvent *event,
+ gint days_shown,
+ time_t *day_starts,
+ gint *start_day_return,
+ gint *end_day_return)
+{
+ gint day, start_day, end_day;
+
+ start_day = -1;
+ end_day = -1;
+
+ for (day = 0; day < days_shown; day++) {
+ if (start_day == -1
+ && event->start < day_starts[day + 1])
+ start_day = day;
+ if (event->end > day_starts[day])
+ end_day = day;
+ }
+
+ /* Sanity check. */
+ if (start_day < 0 || start_day >= days_shown
+ || end_day < 0 || end_day >= days_shown
+ || end_day < start_day) {
+ g_warning ("Invalid date range for event");
+ return FALSE;
+ }
+
+ *start_day_return = start_day;
+ *end_day_return = end_day;
+
+ return TRUE;
+}
+
+