/*
* EMeetingTimeSelectorItem - A GnomeCanvasItem which is used for both the main
* display canvas and the top display (with the dates, times & All Attendees).
* I didn't make these separate GnomeCanvasItems since they share a lot of
* code.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* 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 Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Damon Chaplin <damon@gtk.org>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
#include <glib/gi18n.h>
#include "calendar-config.h"
#include "e-meeting-time-sel-item.h"
#include "e-meeting-time-sel.h"
/* Initially the grid lines were drawn at the bottom of cells, but this didn't
* line up well with the GtkEntry widgets, which in the default theme draw a
* black shadow line across the top. So I've switched our code to draw the
* lines across the top of cells. */
#define E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM 0
static void e_meeting_time_selector_item_dispose (GObject *object);
static void e_meeting_time_selector_item_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void e_meeting_time_selector_item_update (GnomeCanvasItem *item,
const cairo_matrix_t *i2c,
gint flags);
static void e_meeting_time_selector_item_draw (GnomeCanvasItem *item,
cairo_t *cr,
gint x, gint y,
gint width, gint height);
static GnomeCanvasItem *e_meeting_time_selector_item_point (GnomeCanvasItem *item,
double x, double y,
gint cx, gint cy);
static gint e_meeting_time_selector_item_event (GnomeCanvasItem *item,
GdkEvent *event);
static gint e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item,
GdkEvent *event);
static gint e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item,
GdkEvent *event);
static gint e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item,
GdkEvent *event);
static void e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
GDate *date,
gint x, gint scroll_y,
gint width, gint height);
static void e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint y, gint width, gint height);
static void e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
GDate *date,
gint x, gint scroll_y,
gint width, gint height);
static void e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height);
static gint e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item, GDate *date, gint row);
static void e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, gint row, gint x, gint y, gint width, gint first_period, EMeetingFreeBusyType busy_type);
static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y);
static gboolean e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts,
gint row,
gint x,
gint width,
gint *start_x,
gint *end_x);
enum {
PROP_0,
PROP_MEETING_TIME_SELECTOR
};
G_DEFINE_TYPE (EMeetingTimeSelectorItem, e_meeting_time_selector_item, GNOME_TYPE_CANVAS_ITEM)
static void
e_meeting_time_selector_item_class_init (EMeetingTimeSelectorItemClass *class)
{
GObjectClass *object_class;
GnomeCanvasItemClass *item_class;
object_class = G_OBJECT_CLASS (class);
object_class->dispose = e_meeting_time_selector_item_dispose;
object_class->set_property = e_meeting_time_selector_item_set_property;
item_class = GNOME_CANVAS_ITEM_CLASS (class);
item_class->update = e_meeting_time_selector_item_update;
item_class->draw = e_meeting_time_selector_item_draw;
item_class->point = e_meeting_time_selector_item_point;
item_class->event = e_meeting_time_selector_item_event;
g_object_class_install_property (
object_class,
PROP_MEETING_TIME_SELECTOR,
g_param_spec_pointer (
"meeting_time_selector",
NULL,
NULL,
G_PARAM_WRITABLE));
}
static void
e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item)
{
GnomeCanvasItem *item = GNOME_CANVAS_ITEM (mts_item);
mts_item->mts = NULL;
/* Create the cursors. */
mts_item->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR);
mts_item->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
mts_item->busy_cursor = gdk_cursor_new (GDK_WATCH);
mts_item->last_cursor_set = NULL;
item->x1 = 0;
item->y1 = 0;
item->x2 = 0;
item->y2 = 0;
}
static void
e_meeting_time_selector_item_dispose (GObject *object)
{
EMeetingTimeSelectorItem *mts_item;
mts_item = E_MEETING_TIME_SELECTOR_ITEM (object);
if (mts_item->normal_cursor) {
g_object_unref (mts_item->normal_cursor);
mts_item->normal_cursor = NULL;
}
if (mts_item->resize_cursor) {
g_object_unref (mts_item->resize_cursor);
mts_item->resize_cursor = NULL;
}
if (mts_item->busy_cursor) {
g_object_unref (mts_item->busy_cursor);
mts_item->busy_cursor = NULL;
}
G_OBJECT_CLASS (e_meeting_time_selector_item_parent_class)->dispose (object);
}
static void
e_meeting_time_selector_item_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
EMeetingTimeSelectorItem *mts_item;
mts_item = E_MEETING_TIME_SELECTOR_ITEM (object);
switch (property_id) {
case PROP_MEETING_TIME_SELECTOR:
mts_item->mts = g_value_get_pointer (value);
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
e_meeting_time_selector_item_update (GnomeCanvasItem *item,
const cairo_matrix_t *i2c,
gint flags)
{
if (GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update)
(* GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update) (item, i2c, flags);
/* The grid covers the entire canvas area. */
item->x1 = 0;
item->y1 = 0;
item->x2 = INT_MAX;
item->y2 = INT_MAX;
}
/*
* DRAWING ROUTINES - functions to paint the canvas item.
*/
static void
draw_strikeout_box (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
gint x,
gint y,
gint width,
gint height)
{
GnomeCanvas *canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
EMeetingTimeSelector *mts = mts_item->mts;
cairo_save (cr);
cairo_rectangle (cr, x, y, width, height);
cairo_clip (cr);
cairo_translate (cr, -canvas->draw_xofs, -canvas->draw_yofs);
cairo_set_source (cr, mts->no_info_pattern);
cairo_paint (cr);
cairo_restore (cr);
}
static void
e_meeting_time_selector_item_draw (GnomeCanvasItem *item,
cairo_t *cr,
gint x,
gint y,
gint width,
gint height)
{
EMeetingTimeSelector *mts;
EMeetingTimeSelectorItem *mts_item;
EMeetingAttendee *ia;
gint day_x, meeting_start_x, meeting_end_x, bar_y, bar_height;
gint row, row_y, start_x, end_x;
GDate date, last_date, current_date;
gboolean is_display_top, show_meeting_time;
mts_item = E_MEETING_TIME_SELECTOR_ITEM (item);
mts = mts_item->mts;
g_return_if_fail (mts != NULL);
is_display_top = (GTK_WIDGET (item->canvas) == mts->display_top)
? TRUE : FALSE;
/* Calculate the first and last visible days and positions. */
e_meeting_time_selector_calculate_day_and_position (
mts, x,
&date, &day_x);
e_meeting_time_selector_calculate_day_and_position (
mts, x + width,
&last_date, NULL);
/* For the top display draw the 'All Attendees' row background. */
cairo_save (cr);
if (is_display_top) {
gdk_cairo_set_source_color (cr, &mts->all_attendees_bg_color);
cairo_rectangle (
cr, 0, mts->row_height * 2 - y,
width, mts->row_height);
cairo_fill (cr);
} else {
gdk_cairo_set_source_color (cr, &mts->bg_color);
cairo_rectangle (cr, 0, 0, width, height);
cairo_fill (cr);
}
cairo_restore (cr);
/* Calculate the x coordinates of the meeting time. */
show_meeting_time = e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x);
/* Draw the meeting time background. */
if (show_meeting_time
&& (meeting_end_x - 1 >= x) && (meeting_start_x + 1 < x + width)
&& (meeting_end_x - meeting_start_x > 2)) {
cairo_save (cr);
gdk_cairo_set_source_color (cr, &mts->meeting_time_bg_color);
if (is_display_top) {
cairo_rectangle (
cr, meeting_start_x + 1 - x, mts->row_height * 2 - y,
meeting_end_x - meeting_start_x - 2, mts->row_height);
cairo_fill (cr);
} else {
cairo_rectangle (
cr, meeting_start_x + 1 - x, 0,
meeting_end_x - meeting_start_x - 2, height);
cairo_fill (cr);
}
cairo_restore (cr);
}
/* For the main display draw the no-info pattern background for attendee's
* that have no calendar information. */
if (!is_display_top) {
gdk_cairo_set_source_color (cr, &mts->grid_color);
row = y / mts->row_height;
row_y = row * mts->row_height - y;
while (row < e_meeting_store_count_actual_attendees (mts->model) && row_y < height) {
ia = e_meeting_store_find_attendee_at_row (mts->model, row);
if (e_meeting_attendee_get_has_calendar_info (ia)) {
if (e_meeting_time_selector_item_calculate_busy_range (mts, row, x, width, &start_x, &end_x)) {
if (start_x >= width || end_x <= 0) {
draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height);
} else {
if (start_x >= 0) {
draw_strikeout_box (mts_item, cr, 0, row_y, start_x, mts->row_height);
cairo_move_to (cr, start_x, row_y);
cairo_line_to (cr, start_x, row_y + mts->row_height);
cairo_stroke (cr);
}
if (end_x <= width) {
draw_strikeout_box (mts_item, cr, end_x, row_y, width - end_x, mts->row_height);
cairo_move_to (cr, end_x, row_y);
cairo_line_to (cr, end_x, row_y + mts->row_height);
cairo_stroke (cr);
}
}
}
} else {
draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height);
}
row++;
row_y += mts->row_height;
}
}
/* Now paint the visible days one by one. */
current_date = date;
for (;;) {
/* Currently we use the same GnomeCanvasItem class for the
* top display and the main display. We may use separate
* classes in future if necessary. */
if (is_display_top)
e_meeting_time_selector_item_paint_day_top (mts_item, cr, ¤t_date, day_x, y, width, height);
else
e_meeting_time_selector_item_paint_day (mts_item, cr, ¤t_date, day_x, y, width, height);
day_x += mts_item->mts->day_width;
if (g_date_compare (¤t_date, &last_date) == 0)
break;
g_date_add_days (¤t_date, 1);
}
/* Draw the busy periods. */
if (is_display_top)
e_meeting_time_selector_item_paint_all_attendees_busy_periods (mts_item, cr, &date, x, y, width, height);
else
e_meeting_time_selector_item_paint_busy_periods (mts_item, cr, &date, x, y, width, height);
/* Draw the currently-selected meeting time vertical bars. */
if (show_meeting_time) {
if (is_display_top) {
bar_y = mts->row_height * 2 - y;
bar_height = mts->row_height;
} else {
bar_y = 0;
bar_height = height;
}
cairo_save (cr);
gdk_cairo_set_source_color (cr, &mts->grid_color);
if ((meeting_start_x + 2 >= x)
&& (meeting_start_x - 2 < x + width)) {
cairo_rectangle (
cr, meeting_start_x - 2 - x, bar_y,
5, bar_height);
cairo_fill (cr);
}
if ((meeting_end_x + 2 >= x)
&& (meeting_end_x - 2 < x + width)) {
cairo_rectangle (
cr, meeting_end_x - 2 - x, bar_y,
5, bar_height);
cairo_fill (cr);
}
cairo_restore (cr);
}
}
static void
e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
GDate *date,
gint x,
gint scroll_y,
gint width,
gint height)
{
EMeetingTimeSelector *mts;
gint y, grid_x;
gchar *str;
gint hour, hour_x, hour_y;
PangoLayout *layout;
struct tm tm_time;
cairo_save (cr);
cairo_set_line_width (cr, 1.0);
mts = mts_item->mts;
layout = gtk_widget_create_pango_layout (GTK_WIDGET (mts), NULL);
/* Draw the horizontal lines. */
y = mts->row_height - 1 - scroll_y;
gdk_cairo_set_source_color (cr, &mts->grid_color);
cairo_move_to (cr, x + 0.5, y + 0.5);
cairo_rel_line_to (cr, mts->day_width - 1, 0);
cairo_stroke (cr);
gdk_cairo_set_source_color (cr, &mts->grid_shadow_color);
cairo_move_to (cr, x + 0.5, y + 1.5);
cairo_rel_line_to (cr, mts->day_width - 1, 0);
cairo_stroke (cr);
gdk_cairo_set_source_color (cr, &mts->grid_color);
y += mts->row_height;
cairo_move_to (cr, x + 0.5, y + 0.5);
cairo_rel_line_to (cr, mts->day_width - 1, 0);
y += mts->row_height;
cairo_move_to (cr, x + 0.5, y + 0.5);
cairo_rel_line_to (cr, mts->day_width - 1, 0);
/* Draw the vertical grid lines. */
for (grid_x = mts->col_width - 1;
grid_x < mts->day_width - mts->col_width;
grid_x += mts->col_width) {
cairo_move_to (cr, x + grid_x + 0.5, mts->row_height * 2 - 1 - scroll_y + 0.5);
cairo_line_to (cr, x + grid_x + 0.5, height + 0.5);
}
grid_x = mts->day_width - 2;
cairo_move_to (cr, x + grid_x + 0.5, 0.5);
cairo_rel_line_to (cr, 0, height);
grid_x++;
cairo_move_to (cr, x + grid_x + 0.5, 0.5);
cairo_rel_line_to (cr, 0, height);
cairo_stroke (cr);
g_date_to_struct_tm (date, &tm_time);
str = e_datetime_format_format_tm ("calendar", "table", DTFormatKindDate, &tm_time);
g_return_if_fail (str != NULL);
/* Draw the date. Set a clipping rectangle so we don't draw over the
* next day. */
if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY
&& !e_datetime_format_includes_day_name ("calendar", "table", DTFormatKindDate)) {
gchar buffer[128];
gchar *tmp;
g_date_strftime (buffer, sizeof (buffer), "%a", date);
tmp = str;
str = g_strconcat (buffer, " ", str, NULL);
g_free (tmp);
}
cairo_save (cr);
cairo_rectangle (cr, x, -scroll_y, mts->day_width - 2, mts->row_height - 2);
cairo_clip (cr);
pango_layout_set_text (layout, str, -1);
cairo_move_to (cr, x + 2, 4 - scroll_y);
pango_cairo_show_layout (cr, layout);
cairo_restore (cr);
/* Draw the hours. */
hour = mts->first_hour_shown;
hour_x = x + 2;
hour_y = mts->row_height + 4 - scroll_y;
while (hour < mts->last_hour_shown) {
if (e_meeting_time_selector_get_use_24_hour_format (mts))
pango_layout_set_text (layout, EMeetingTimeSelectorHours[hour], -1);
else
pango_layout_set_text (layout, EMeetingTimeSelectorHours12[hour], -1);
cairo_move_to (cr, hour_x, hour_y);
pango_cairo_show_layout (cr, layout);
hour += mts->zoomed_out ? 3 : 1;
hour_x += mts->col_width;
}
g_object_unref (layout);
cairo_restore (cr);
g_free (str);
}
/* This paints the colored bars representing busy periods for the combined
* list of attendees. For now we just paint the bars for each attendee of
* each other. If we want to speed it up we could optimise it later. */
static void
e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
GDate *date,
gint x,
gint scroll_y,
gint width,
gint height)
{
EMeetingTimeSelector *mts;
EMeetingFreeBusyType busy_type;
gint row, y;
gint *first_periods;
mts = mts_item->mts;
/* Calculate the y coordinate to paint the row at in the drawable. */
y = 2 * mts->row_height - scroll_y - 1;
/* Get the first visible busy periods for all the attendees. */
first_periods = g_new (gint, e_meeting_store_count_actual_attendees (mts->model));
for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++)
first_periods[row] = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row);
for (busy_type = 0;
busy_type < E_MEETING_FREE_BUSY_LAST;
busy_type++) {
gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]);
for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++) {
if (first_periods[row] == -1)
continue;
e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_periods[row], busy_type);
}
}
g_free (first_periods);
}
static void
e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
GDate *date,
gint x,
gint scroll_y,
gint width,
gint height)
{
EMeetingTimeSelector *mts;
gint grid_x, grid_y, attendee_index, unused_y;
cairo_save (cr);
cairo_set_line_width (cr, 1.0);
mts = mts_item->mts;
/* Draw the grid lines. The grid lines around unused rows are drawn in
* a different color. */
/* Draw the horizontal grid lines. */
attendee_index = scroll_y / mts->row_height;
#if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM
for (grid_y = mts->row_height - 1 - (scroll_y % mts->row_height);
#else
for (grid_y = - (scroll_y % mts->row_height);
#endif
grid_y < height;
grid_y += mts->row_height)
{
if (attendee_index <= e_meeting_store_count_actual_attendees (mts->model)) {
gdk_cairo_set_source_color (cr, &mts->grid_color);
} else {
gdk_cairo_set_source_color (cr, &mts->grid_unused_color);
}
cairo_move_to (cr, 0, grid_y);
cairo_rel_line_to (cr, width, 0);
cairo_stroke (cr);
attendee_index++;
}
/* Draw the vertical grid lines. */
unused_y = (e_meeting_store_count_actual_attendees (mts->model) * mts->row_height) - scroll_y;
if (unused_y >= 0) {
gdk_cairo_set_source_color (cr, &mts->grid_color);
for (grid_x = mts->col_width - 1;
grid_x < mts->day_width - mts->col_width;
grid_x += mts->col_width)
{
cairo_move_to (cr, x + grid_x, 0);
cairo_line_to (cr, x + grid_x, unused_y - 1);
}
cairo_stroke (cr);
cairo_rectangle (
cr,
x + mts->day_width - 2, 0,
2, unused_y);
cairo_fill (cr);
}
if (unused_y < height) {
gdk_cairo_set_source_color (cr, &mts->grid_unused_color);
for (grid_x = mts->col_width - 1;
grid_x < mts->day_width - mts->col_width;
grid_x += mts->col_width)
{
cairo_move_to (cr, x + grid_x, unused_y);
cairo_line_to (cr, x + grid_x, height);
}
cairo_stroke (cr);
cairo_rectangle (
cr,
x + mts->day_width - 2, unused_y,
2, height - unused_y);
cairo_fill (cr);
}
cairo_restore (cr);
}
/* This paints the colored bars representing busy periods for the individual
* attendees. */
static void
e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
GDate *date,
gint x,
gint scroll_y,
gint width,
gint height)
{
EMeetingTimeSelector *mts;
EMeetingFreeBusyType busy_type;
gint row, y, first_period;
mts = mts_item->mts;
/* Calculate the first visible attendee row. */
row = scroll_y / mts->row_height;
/* Calculate the y coordinate to paint the row at in the drawable. */
y = row * mts->row_height - scroll_y;
/* Step through the attendees painting the busy periods. */
while (y < height && row < e_meeting_store_count_actual_attendees (mts->model)) {
/* Find the first visible busy period. */
first_period = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row);
if (first_period != -1) {
/* Paint the different types of busy periods, in
* reverse order of precedence, so the highest
* precedences are displayed. */
for (busy_type = 0;
busy_type < E_MEETING_FREE_BUSY_LAST;
busy_type++) {
gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]);
e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_period, busy_type);
}
}
y += mts->row_height;
row++;
}
}
/* This subtracts the attendees longest_period_in_days from the given date,
* and does a binary search of the attendee's busy periods array to find the
* first one which could possible end on the given day or later.
* If none are found it returns -1. */
static gint
e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item,
GDate *date,
gint row)
{
EMeetingTimeSelector *mts;
EMeetingAttendee *ia;
const GArray *busy_periods;
EMeetingFreeBusyPeriod *period;
gint period_num;
mts = mts_item->mts;
ia = e_meeting_store_find_attendee_at_row (mts->model, row);
period_num = e_meeting_attendee_find_first_busy_period (ia, date);
if (period_num == -1)
return -1;
/* Check if the period starts after the end of the current canvas
* scroll area. */
busy_periods = e_meeting_attendee_get_busy_periods (ia);
period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num);
if (g_date_compare (&mts->last_date_shown, &period->start.date) < 0)
return -1;
return period_num;
}
/* This paints the visible busy periods for one attendee which are of a certain
* busy type, e.g out of office. It is passed the index of the first visible
* busy period of the attendee and continues until it runs off the screen. */
static void
e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item,
cairo_t *cr,
gint x,
gint y,
gint width,
gint row,
gint first_period,
EMeetingFreeBusyType busy_type)
{
EMeetingTimeSelector *mts;
EMeetingAttendee *ia;
const GArray *busy_periods;
EMeetingFreeBusyPeriod *period;
gint period_num, x1, x2, x2_within_day, x2_within_col;
mts = mts_item->mts;
ia = e_meeting_store_find_attendee_at_row (mts->model, row);
busy_periods = e_meeting_attendee_get_busy_periods (ia);
for (period_num = first_period;
period_num < busy_periods->len;
period_num++) {
period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num);
if (period->busy_type != busy_type)
continue;
/* Convert the period start and end times to x coordinates. */
x1 = e_meeting_time_selector_calculate_time_position (mts, &period->start);
/* If the period is off the right of the area being drawn, we
* are finished. */
if (x1 >= x + width)
return;
x2 = e_meeting_time_selector_calculate_time_position (mts, &period->end);
/* If the period is off the left edge of the area skip it. */
if (x2 <= x)
continue;
/* We paint from x1 to x2 - 1, so that for example a time
* from 5:00-6:00 is distinct from 6:00-7:00.
* We never finish on a grid line separating days, and we only
* ever paint on a normal vertical grid line if the period is
* only 1 pixel wide. */
x2_within_day = x2 % mts->day_width;
if (x2_within_day == 0) {
x2 -= 2;
} else if (x2_within_day == mts->day_width - 1) {
x2 -= 1;
} else {
x2_within_col = x2_within_day % mts->col_width;
if (x2_within_col == 0 && x2 > x1 + 1)
x2 -= 1;
}
/* Paint the rectangle. We leave a gap of 2 pixels at the
* top and bottom, remembering that the grid is painted along
* the top/bottom line of each row. */
if (x2 - x1 > 0) {
#if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM
cairo_rectangle (
cr, x1 - x, y + 2,
x2 - x1, mts->row_height - 5);
#else
cairo_rectangle (
cr, x1 - x, y + 3,
x2 - x1, mts->row_height - 5);
#endif
cairo_fill (cr);
}
}
}
/*
* CANVAS ITEM ROUTINES - functions to be a GnomeCanvasItem.
*/
static GnomeCanvasItem *
e_meeting_time_selector_item_point (GnomeCanvasItem *item,
gdouble x,
gdouble y,
gint cx,
gint cy)
{
return item;
}
static gint
e_meeting_time_selector_item_event (GnomeCanvasItem *item,
GdkEvent *event)
{
EMeetingTimeSelectorItem *mts_item;
mts_item = E_MEETING_TIME_SELECTOR_ITEM (item);
switch (event->type) {
case GDK_BUTTON_PRESS:
return e_meeting_time_selector_item_button_press (
mts_item,
event);
case GDK_BUTTON_RELEASE:
return e_meeting_time_selector_item_button_release (
mts_item,
event);
case GDK_MOTION_NOTIFY:
return e_meeting_time_selector_item_motion_notify (
mts_item,
event);
default:
break;
}
return FALSE;
}
/* This handles all button press events for the item. If the cursor is over
* one of the meeting time vertical bars we start a drag. If not we set the
* meeting time to the nearest half-hour interval.
* Note that GnomeCanvas converts the event coords to world coords,
* i.e. relative to the entire canvas scroll area. */
static gint
e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item,
GdkEvent *event)
{
EMeetingTimeSelector *mts;
EMeetingTime start_time, end_time;
EMeetingTimeSelectorPosition position;
GDate *start_date, *end_date;
gint x, y;
mts = mts_item->mts;
x = (gint) event->button.x;
y = (gint) event->button.y;
/* Check if we are starting a drag of the vertical meeting time bars.*/
position = e_meeting_time_selector_item_get_drag_position (
mts_item,
x, y);
if (position != E_MEETING_TIME_SELECTOR_POS_NONE) {
GdkGrabStatus grab_status;
GdkDevice *event_device;
guint32 event_time;
event_device = gdk_event_get_device (event);
event_time = gdk_event_get_time (event);
grab_status = gnome_canvas_item_grab (
GNOME_CANVAS_ITEM (mts_item),
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_RELEASE_MASK,
mts_item->resize_cursor,
event_device,
event_time);
if (grab_status == GDK_GRAB_SUCCESS) {
mts->dragging_position = position;
return TRUE;
}
}
/* Convert the x coordinate into a EMeetingTimeSelectorTime. */
e_meeting_time_selector_calculate_time (mts, x, &start_time);
start_date = &start_time.date;
end_date = &end_time.date;
/* Find the nearest half-hour or hour interval, depending on whether
* zoomed_out is set. */
if (!mts->all_day) {
gint astart_year, astart_month, astart_day, astart_hour, astart_minute;
gint aend_year, aend_month, aend_day, aend_hour, aend_minute;
gint hdiff, mdiff;
GDate asdate, aedate;
e_meeting_time_selector_get_meeting_time (
mts_item->mts,
&astart_year,
&astart_month,
&astart_day,
&astart_hour,
&astart_minute,
&aend_year,
&aend_month,
&aend_day,
&aend_hour,
&aend_minute);
if (mts->zoomed_out)
start_time.minute = 0;
else
start_time.minute -= start_time.minute % 30;
g_date_set_dmy (&asdate, astart_day, astart_month, astart_year);
g_date_set_dmy (&aedate, aend_day, aend_month, aend_year);
end_time = start_time;
mdiff = end_time.minute + aend_minute - astart_minute;
hdiff = end_time.hour + aend_hour - astart_hour + (24 * g_date_days_between (&asdate, &aedate));
while (mdiff < 0) {
mdiff += 60;
hdiff -= 1;
}
while (mdiff > 60) {
mdiff -= 60;
hdiff += 1;
}
while (hdiff < 0) {
hdiff += 24;
g_date_subtract_days (end_date, 1);
}
while (hdiff >= 24) {
hdiff -= 24;
g_date_add_days (end_date, 1);
}
end_time.minute = mdiff;
end_time.hour = hdiff;
} else {
start_time.hour = 0;
start_time.minute = 0;
end_time = start_time;
g_date_add_days (&end_time.date, 1);
}
/* Fix any overflows. */
e_meeting_time_selector_fix_time_overflows (&end_time);
/* Set the new meeting time. */
e_meeting_time_selector_set_meeting_time (
mts_item->mts,
g_date_get_year (start_date),
g_date_get_month (start_date),
g_date_get_day (start_date),
start_time.hour,
start_time.minute,
g_date_get_year (end_date),
g_date_get_month (end_date),
g_date_get_day (end_date),
end_time.hour,
end_time.minute);
return FALSE;
}
/* This handles all button release events for the item. If we were dragging,
* we finish the drag. */
static gint
e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item,
GdkEvent *event)
{
EMeetingTimeSelector *mts;
mts = mts_item->mts;
/* Reset any drag. */
if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) {
mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE;
e_meeting_time_selector_remove_timeout (mts);
gnome_canvas_item_ungrab (
GNOME_CANVAS_ITEM (mts_item),
event->button.time);
}
return FALSE;
}
/* This handles all motion notify events for the item. If button1 is pressed
* we check if a drag is in progress. If not, we set the cursor if we are over
* the meeting time vertical bars. Note that GnomeCanvas doesn't use motion
* hints, which may affect performance. */
static gint
e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item,
GdkEvent *event)
{
EMeetingTimeSelector *mts;
EMeetingTimeSelectorPosition position;
GdkCursor *cursor;
gint x, y;
mts = mts_item->mts;
x = (gint) event->motion.x;
y = (gint) event->motion.y;
if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) {
e_meeting_time_selector_drag_meeting_time (mts, x);
return TRUE;
}
position = e_meeting_time_selector_item_get_drag_position (
mts_item,
x, y);
/* Determine which cursor should be used. */
if (position != E_MEETING_TIME_SELECTOR_POS_NONE)
cursor = mts_item->resize_cursor;
/* If the Main window shows busy cursor show the same */
else if (mts_item->mts->last_cursor_set == GDK_WATCH)
cursor = mts_item->busy_cursor;
else
cursor = mts_item->normal_cursor;
/* Only set the cursor if it is different to the last one we set. */
if (mts_item->last_cursor_set != cursor) {
GdkWindow *window;
GnomeCanvas *canvas;
mts_item->last_cursor_set = cursor;
canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
window = gtk_widget_get_window (GTK_WIDGET (canvas));
gdk_window_set_cursor (window, cursor);
}
return FALSE;
}
static EMeetingTimeSelectorPosition
e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item,
gint x,
gint y)
{
EMeetingTimeSelector *mts;
gboolean is_display_top;
gint meeting_start_x, meeting_end_x;
mts = mts_item->mts;
is_display_top = (GTK_WIDGET (GNOME_CANVAS_ITEM (mts_item)->canvas) == mts->display_top) ? TRUE : FALSE;
if (is_display_top && y < mts->row_height * 2)
return E_MEETING_TIME_SELECTOR_POS_NONE;
if (!e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x))
return E_MEETING_TIME_SELECTOR_POS_NONE;
if (x >= meeting_end_x - 2 && x <= meeting_end_x + 2)
return E_MEETING_TIME_SELECTOR_POS_END;
if (x >= meeting_start_x - 2 && x <= meeting_start_x + 2)
return E_MEETING_TIME_SELECTOR_POS_START;
return E_MEETING_TIME_SELECTOR_POS_NONE;
}
static gboolean
e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts,
gint row,
gint x,
gint width,
gint *start_x,
gint *end_x)
{
EMeetingAttendee *ia;
EMeetingTime busy_periods_start;
EMeetingTime busy_periods_end;
ia = e_meeting_store_find_attendee_at_row (mts->model, row);
busy_periods_start = e_meeting_attendee_get_start_busy_range (ia);
busy_periods_end = e_meeting_attendee_get_end_busy_range (ia);
*start_x = -1;
*end_x = -1;
if (!g_date_valid (&busy_periods_start.date)
|| !g_date_valid (&busy_periods_end.date))
return FALSE;
*start_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_start) - x - 1;
*end_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_end) - x;
return TRUE;
}
void
e_meeting_time_selector_item_set_normal_cursor (EMeetingTimeSelectorItem *mts_item)
{
GnomeCanvas *canvas;
GdkWindow *window;
g_return_if_fail (IS_E_MEETING_TIME_SELECTOR_ITEM (mts_item));
canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
window = gtk_widget_get_window (GTK_WIDGET (canvas));
if (window)
gdk_window_set_cursor (window, mts_item->normal_cursor);
}