/* Full day widget for gncal * * Copyright (C) 1998 The Free Software Foundation * * Author: Federico Mena */ #include #include #include #include #include "gncal-full-day.h" #include "view-utils.h" #define TEXT_BORDER 2 #define HANDLE_SIZE 8 #define MIN_WIDTH 200 #define XOR_RECT_WIDTH 2 #define UNSELECT_TIMEOUT 150 /* ms */ typedef struct { iCalObject *ico; GtkWidget *widget; GdkWindow *window; int lower_row; /* zero is first displayed row */ int rows_used; int x; /* coords of child's window */ int y; int width; int height; } Child; struct layout_row { int intersections; int *slots; }; struct drag_info { enum { DRAG_NONE, DRAG_SELECT, /* selecting a range in the main window */ DRAG_MOVE, /* moving a child */ DRAG_SIZE /* resizing a child */ } drag_mode; Child *child; int start_row; int rows_used; int sel_click_row; int sel_start_row; int sel_rows_used; guint32 click_time; }; enum { RANGE_ACTIVATED, LAST_SIGNAL }; static void gncal_full_day_class_init (GncalFullDayClass *class); static void gncal_full_day_init (GncalFullDay *fullday); static void gncal_full_day_destroy (GtkObject *object); static void gncal_full_day_map (GtkWidget *widget); static void gncal_full_day_unmap (GtkWidget *widget); static void gncal_full_day_realize (GtkWidget *widget); static void gncal_full_day_unrealize (GtkWidget *widget); static void gncal_full_day_draw (GtkWidget *widget, GdkRectangle *area); static void gncal_full_day_draw_focus (GtkWidget *widget); static void gncal_full_day_size_request (GtkWidget *widget, GtkRequisition *requisition); static void gncal_full_day_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static gint gncal_full_day_button_press (GtkWidget *widget, GdkEventButton *event); static gint gncal_full_day_button_release (GtkWidget *widget, GdkEventButton *event); static gint gncal_full_day_motion (GtkWidget *widget, GdkEventMotion *event); static gint gncal_full_day_expose (GtkWidget *widget, GdkEventExpose *event); static gint gncal_full_day_key_press (GtkWidget *widget, GdkEventKey *event); static gint gncal_full_day_focus_in (GtkWidget *widget, GdkEventFocus *event); static gint gncal_full_day_focus_out (GtkWidget *widget, GdkEventFocus *event); static void gncal_full_day_foreach (GtkContainer *container, GtkCallback callback, gpointer callback_data); static void range_activated (GncalFullDay *fullday); static GtkContainerClass *parent_class; static fullday_signals[LAST_SIGNAL] = { 0 }; static void get_tm_range (GncalFullDay *fullday, time_t time_lower, time_t time_upper, struct tm *lower, struct tm *upper, int *lower_row, int *rows_used) { struct tm tm_lower, tm_upper; int lmin, umin; int lrow; /* Lower */ tm_lower = *localtime (&time_lower); if ((tm_lower.tm_min % fullday->interval) != 0) { tm_lower.tm_min -= tm_lower.tm_min % fullday->interval; /* round down */ mktime (&tm_lower); } /* Upper */ tm_upper = *localtime (&time_upper); if ((tm_upper.tm_min % fullday->interval) != 0) { tm_upper.tm_min += fullday->interval - (tm_upper.tm_min % fullday->interval); /* round up */ mktime (&tm_upper); } if (lower) *lower = tm_lower; if (upper) *upper = tm_upper; lmin = 60 * tm_lower.tm_hour + tm_lower.tm_min; umin = 60 * tm_upper.tm_hour + tm_upper.tm_min; if (umin == 0) /* midnight of next day? */ umin = 60 * 24; lrow = lmin / fullday->interval; if (lower_row) *lower_row = lrow; if (rows_used) *rows_used = (umin - lmin) / fullday->interval; } static void child_map (GncalFullDay *fullday, Child *child) { gdk_window_show (child->window); gtk_widget_show (child->widget); /* OK, not just a map... */ } static void child_unmap (GncalFullDay *fullday, Child *child) { gdk_window_hide (child->window); if (GTK_WIDGET_MAPPED (child->widget)) gtk_widget_unmap (child->widget); } static void child_set_text_pos (Child *child) { GtkAllocation allocation; allocation.x = 0; allocation.y = (GTK_WIDGET_HAS_FOCUS (child->widget) ? HANDLE_SIZE : 0); allocation.width = child->width; allocation.height = child->height - (GTK_WIDGET_HAS_FOCUS (child->widget) ? (2 * HANDLE_SIZE) : 0); gtk_widget_size_request (child->widget, &child->widget->requisition); /* FIXME: is this needed? */ gtk_widget_size_allocate (child->widget, &allocation); } static void child_realize (GncalFullDay *fullday, Child *child) { GdkWindowAttr attributes; gint attributes_mask; GtkWidget *widget; widget = GTK_WIDGET (fullday); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = child->x; attributes.y = child->y; attributes.width = child->width; attributes.height = child->height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.cursor = fullday->up_down_cursor; attributes.event_mask = (GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_KEY_PRESS_MASK); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR; child->window = gdk_window_new (widget->window, &attributes, attributes_mask); gdk_window_set_user_data (child->window, widget); gtk_style_set_background (widget->style, child->window, GTK_STATE_NORMAL); gtk_widget_set_parent_window (child->widget, child->window); child_set_text_pos (child); } static void child_unrealize (GncalFullDay *fullday, Child *child) { gdk_window_set_user_data (child->window, NULL); gdk_window_destroy (child->window); child->window = NULL; } static void child_draw (GncalFullDay *fullday, Child *child, GdkRectangle *area, int draw_child) { GdkRectangle arect, rect, dest; if (!area) { arect.x = 0; arect.y = 0; arect.width = child->width; arect.height = child->height; area = &arect; } /* Top handle */ rect.x = 0; rect.y = 0; rect.width = child->width; rect.height = HANDLE_SIZE; if (gdk_rectangle_intersect (&rect, area, &dest)) view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT); /* Bottom handle */ rect.y = child->height - HANDLE_SIZE; if (gdk_rectangle_intersect (&rect, area, &dest)) view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT); if (draw_child) { area->y -= HANDLE_SIZE; gtk_widget_draw (child->widget, area); } } static void child_range_changed (GncalFullDay *fullday, Child *child) { struct tm start, end; int lower_row, rows_used; int f_lower_row; /* Calc display range for event */ get_tm_range (fullday, child->ico->dtstart, child->ico->dtend, &start, &end, &lower_row, &rows_used); get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, &f_lower_row, NULL); child->lower_row = lower_row - f_lower_row; child->rows_used = rows_used; } static void child_realized_setup (GtkWidget *widget, gpointer data) { Child *child; GncalFullDay *fullday; child = data; fullday = GNCAL_FULL_DAY (widget->parent); gdk_window_set_cursor (widget->window, fullday->beam_cursor); gtk_text_insert (GTK_TEXT (widget), NULL, NULL, NULL, child->ico->summary, strlen (child->ico->summary)); } static gint child_focus_in (GtkWidget *widget, GdkEventFocus *event, gpointer data) { Child *child; child = data; /* Paint handles on child */ child_set_text_pos (child); return FALSE; } static gint child_focus_out (GtkWidget *widget, GdkEventFocus *event, gpointer data) { Child *child; GncalFullDay *fullday; child = data; /* Update summary in calendar object */ if (child->ico->summary) g_free (child->ico->summary); child->ico->summary = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1); /* Erase handles from child */ child_set_text_pos (child); /* Notify calendar of change */ fullday = gtk_object_get_user_data (GTK_OBJECT (widget)); gnome_calendar_object_changed (fullday->calendar, child->ico, CHANGE_SUMMARY); return FALSE; } static Child * child_new (GncalFullDay *fullday, iCalObject *ico) { Child *child; child = g_new (Child, 1); child->ico = ico; child->widget = gtk_text_new (NULL, NULL); child->window = NULL; child->x = 0; child->y = 0; child->width = 0; child->height = 0; gtk_object_set_user_data (GTK_OBJECT (child->widget), fullday); child_range_changed (fullday, child); /* We set the i-beam cursor and the initial summary text upon realization */ gtk_signal_connect (GTK_OBJECT (child->widget), "realize", (GtkSignalFunc) child_realized_setup, child); gtk_signal_connect_after (GTK_OBJECT (child->widget), "focus_in_event", (GtkSignalFunc) child_focus_in, child); gtk_signal_connect_after (GTK_OBJECT (child->widget), "focus_out_event", (GtkSignalFunc) child_focus_out, child); /* Finish setup */ gtk_text_set_editable (GTK_TEXT (child->widget), TRUE); gtk_text_set_word_wrap (GTK_TEXT (child->widget), TRUE); gtk_widget_set_parent (child->widget, GTK_WIDGET (fullday)); return child; } static void child_destroy (GncalFullDay *fullday, Child *child) { /* Unparent the child widget manually as we don't have a remove method */ gtk_widget_ref (child->widget); gtk_widget_unparent (child->widget); if (GTK_WIDGET_MAPPED (fullday)) child_unmap (fullday, child); if (GTK_WIDGET_REALIZED (fullday)) child_unrealize (fullday, child); gtk_widget_unref (child->widget); g_free (child); } static void child_set_pos (GncalFullDay *fullday, Child *child, int x, int y, int width, int height) { child->x = x; child->y = y; child->width = width; child->height = height; if (!child->window) /* realized? */ return; child_set_text_pos (child); gdk_window_move_resize (child->window, x, y, width, height); } static struct layout_row * layout_get_rows (GncalFullDay *fullday) { struct layout_row *rows; int max_i; int f_rows; GList *children; Child *child; int i, n; get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows); rows = g_new0 (struct layout_row, f_rows); max_i = 0; for (children = fullday->children; children; children = children->next) { child = children->data; for (i = 0; i < child->rows_used; i++) { n = child->lower_row + i; rows[n].intersections++; if (rows[n].intersections > max_i) max_i = rows[n].intersections; } } for (i = 0; i < f_rows; i++) rows[i].slots = g_new0 (int, max_i); return rows; } static void layout_get_child_intersections (Child *child, struct layout_row *rows, int *min, int *max) { int i, n; int imin, imax; imax = 0; for (i = 0; i < child->rows_used; i++) { n = child->lower_row + i; if (rows[n].intersections > imax) imax = rows[n].intersections; } imin = imax; for (i = 0; i < child->rows_used; i++) { n = child->lower_row + i; if (rows[n].intersections < imin) imin = rows[n].intersections; } if (min) *min = imin; if (max) *max = imax; } static int calc_labels_width (GncalFullDay *fullday) { struct tm cur, upper; time_t tim, time_upper; int width, max_w; char buf[256]; get_tm_range (fullday, fullday->lower, fullday->upper, &cur, &upper, NULL, NULL); max_w = 0; tim = mktime (&cur); time_upper = mktime (&upper); while (tim < time_upper) { strftime (buf, 256, "%X", &cur); width = gdk_string_width (GTK_WIDGET (fullday)->style->font, buf); if (width > max_w) max_w = width; cur.tm_min += fullday->interval; tim = mktime (&cur); } return max_w; } static int calc_row_height (GncalFullDay *fullday) { int f_rows; GtkWidget *widget; get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows); widget = GTK_WIDGET (fullday); return (widget->allocation.height - 2 * widget->style->klass->ythickness) / f_rows; } static void layout_child (GncalFullDay *fullday, Child *child, struct layout_row *rows, int left_x) { int c_y, c_width, c_height; GtkWidget *widget; int row_height; /* Calculate child position */ widget = GTK_WIDGET (fullday); row_height = calc_row_height (fullday); c_y = widget->style->klass->ythickness; /* FIXME: for now, the children overlap. Make it layout them nicely. */ c_width = widget->allocation.width - (widget->style->klass->xthickness + left_x); c_y += child->lower_row * row_height; c_height = child->rows_used * row_height; /* Position child */ child_set_pos (fullday, child, left_x, c_y, c_width, c_height); } static void layout_children (GncalFullDay *fullday) { struct layout_row *rows; GList *children; GtkWidget *widget; int left_x; rows = layout_get_rows (fullday); widget = GTK_WIDGET (fullday); left_x = 2 * (widget->style->klass->xthickness + TEXT_BORDER) + calc_labels_width (fullday); for (children = fullday->children; children; children = children->next) layout_child (fullday, children->data, rows, left_x); g_free (rows); } guint gncal_full_day_get_type (void) { static guint full_day_type = 0; if (!full_day_type) { GtkTypeInfo full_day_info = { "GncalFullDay", sizeof (GncalFullDay), sizeof (GncalFullDayClass), (GtkClassInitFunc) gncal_full_day_class_init, (GtkObjectInitFunc) gncal_full_day_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL }; full_day_type = gtk_type_unique (gtk_container_get_type (), &full_day_info); } return full_day_type; } static void gncal_full_day_class_init (GncalFullDayClass *class) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; GtkContainerClass *container_class; object_class = (GtkObjectClass *) class; widget_class = (GtkWidgetClass *) class; container_class = (GtkContainerClass *) class; parent_class = gtk_type_class (gtk_container_get_type ()); fullday_signals[RANGE_ACTIVATED] = gtk_signal_new ("range_activated", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (GncalFullDayClass, range_activated), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, fullday_signals, LAST_SIGNAL); object_class->destroy = gncal_full_day_destroy; widget_class->map = gncal_full_day_map; widget_class->unmap = gncal_full_day_unmap; widget_class->realize = gncal_full_day_realize; widget_class->unrealize = gncal_full_day_unrealize; widget_class->draw = gncal_full_day_draw; widget_class->draw_focus = gncal_full_day_draw_focus; widget_class->size_request = gncal_full_day_size_request; widget_class->size_allocate = gncal_full_day_size_allocate; widget_class->button_press_event = gncal_full_day_button_press; widget_class->button_release_event = gncal_full_day_button_release; widget_class->motion_notify_event = gncal_full_day_motion; widget_class->expose_event = gncal_full_day_expose; widget_class->key_press_event = gncal_full_day_key_press; widget_class->focus_in_event = gncal_full_day_focus_in; widget_class->focus_out_event = gncal_full_day_focus_out; container_class->foreach = gncal_full_day_foreach; class->range_activated = range_activated; } static void gncal_full_day_init (GncalFullDay *fullday) { GTK_WIDGET_UNSET_FLAGS (fullday, GTK_NO_WINDOW); GTK_WIDGET_SET_FLAGS (fullday, GTK_CAN_FOCUS); fullday->calendar = NULL; fullday->lower = 0; fullday->upper = 0; fullday->interval = 30; /* 30 minutes by default */ fullday->children = NULL; fullday->drag_info = g_new0 (struct drag_info, 1); fullday->up_down_cursor = NULL; fullday->beam_cursor = NULL; } static void gncal_full_day_destroy (GtkObject *object) { GncalFullDay *fullday; GList *children; Child *child; g_return_if_fail (object != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (object)); fullday = GNCAL_FULL_DAY (object); /* Unparent the children manually as we don't have a remove method */ for (children = fullday->children; children; children = children->next) { child = children->data; gtk_widget_unparent (child->widget); } g_list_free (fullday->children); g_free (fullday->drag_info); if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } GtkWidget * gncal_full_day_new (GnomeCalendar *calendar, time_t lower, time_t upper) { GncalFullDay *fullday; g_return_val_if_fail (calendar != NULL, NULL); fullday = gtk_type_new (gncal_full_day_get_type ()); fullday->calendar = calendar; gncal_full_day_set_bounds (fullday, lower, upper); return GTK_WIDGET (fullday); } static void gncal_full_day_map (GtkWidget *widget) { GncalFullDay *fullday; GList *children; g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED); fullday = GNCAL_FULL_DAY (widget); gdk_window_show (widget->window); for (children = fullday->children; children; children = children->next) child_map (fullday, children->data); } static void gncal_full_day_unmap (GtkWidget *widget) { GncalFullDay *fullday; GList *children; g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED); fullday = GNCAL_FULL_DAY (widget); gdk_window_hide (widget->window); for (children = fullday->children; children; children = children->next) child_unmap (fullday, children->data); } static void gncal_full_day_realize (GtkWidget *widget) { GncalFullDay *fullday; GdkWindowAttr attributes; gint attributes_mask; GList *children; g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); fullday = GNCAL_FULL_DAY (widget); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, widget); widget->style = gtk_style_attach (widget->style, widget->window); gdk_window_set_background (widget->window, &widget->style->bg[GTK_STATE_PRELIGHT]); fullday->up_down_cursor = gdk_cursor_new (GDK_DOUBLE_ARROW); fullday->beam_cursor = gdk_cursor_new (GDK_XTERM); for (children = fullday->children; children; children = children->next) child_realize (fullday, children->data); } static void gncal_full_day_unrealize (GtkWidget *widget) { GncalFullDay *fullday; GList *children; g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); fullday = GNCAL_FULL_DAY (widget); for (children = fullday->children; children; children = children->next) child_unrealize (fullday, children->data); gdk_cursor_destroy (fullday->up_down_cursor); fullday->up_down_cursor = NULL; gdk_cursor_destroy (fullday->beam_cursor); fullday->beam_cursor = NULL; if (GTK_WIDGET_CLASS (parent_class)->unrealize) (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); } static void paint_back (GncalFullDay *fullday, GdkRectangle *area) { GtkWidget *widget; GdkRectangle rect, dest, aarea; struct drag_info *di; int x1, y1, width, height; int labels_width; int f_rows, row_height; int i, y; GdkGC *gc; struct tm tm; char buf[256]; widget = GTK_WIDGET (fullday); if (!area) { area = &aarea; area->x = 0; area->y = 0; area->width = widget->allocation.width; area->height = widget->allocation.height; } x1 = widget->style->klass->xthickness; y1 = widget->style->klass->ythickness; width = widget->allocation.width - 2 * x1; height = widget->allocation.height - 2 * y1; di = fullday->drag_info; labels_width = calc_labels_width (fullday); row_height = calc_row_height (fullday); get_tm_range (fullday, fullday->lower, fullday->upper, &tm, NULL, NULL, &f_rows); /* Frame shadow */ gtk_widget_draw_focus (widget); /* Area for labels before selection */ rect.x = x1; rect.y = y1; rect.width = 2 * TEXT_BORDER + labels_width; if (di->sel_rows_used == 0) rect.height = height; else rect.height = row_height * di->sel_start_row; if (gdk_rectangle_intersect (&rect, area, &dest)) gdk_draw_rectangle (widget->window, widget->style->bg_gc[GTK_STATE_NORMAL], TRUE, dest.x, dest.y, dest.width, dest.height); /* Blank area before selection */ rect.x += rect.width + widget->style->klass->xthickness; rect.width = width - (rect.x - x1); if (gdk_rectangle_intersect (&rect, area, &dest)) gdk_draw_rectangle (widget->window, widget->style->bg_gc[GTK_STATE_PRELIGHT], TRUE, dest.x, dest.y, dest.width, dest.height); /* Selection area */ if (di->sel_rows_used != 0) { rect.x = x1; rect.y = y1 + row_height * di->sel_start_row; rect.width = width; rect.height = row_height * di->sel_rows_used; if (gdk_rectangle_intersect (&rect, area, &dest)) gdk_draw_rectangle (widget->window, widget->style->bg_gc[GTK_STATE_SELECTED], TRUE, dest.x, dest.y, dest.width, dest.height); } /* Areas under selection */ if (di->sel_rows_used != 0) { /* Area for labels */ rect.x = x1; rect.y = y1 + row_height * (di->sel_start_row + di->sel_rows_used); rect.width = 2 * TEXT_BORDER + labels_width; rect.height = height - rect.y; if (gdk_rectangle_intersect (&rect, area, &dest)) gdk_draw_rectangle (widget->window, widget->style->bg_gc[GTK_STATE_NORMAL], TRUE, dest.x, dest.y, dest.width, dest.height); /* Blank area */ rect.x += rect.width + widget->style->klass->xthickness; rect.width = width - (rect.x - x1); if (gdk_rectangle_intersect (&rect, area, &dest)) gdk_draw_rectangle (widget->window, widget->style->bg_gc[GTK_STATE_PRELIGHT], TRUE, dest.x, dest.y, dest.width, dest.height); } /* Vertical division */ gtk_draw_vline (widget->style, widget->window, GTK_STATE_NORMAL, y1, y1 + height - 1, x1 + 2 * TEXT_BORDER + labels_width); /* Horizontal divisions */ y = y1 + row_height - 1; for (i = 1; i < f_rows; i++) { gdk_draw_line (widget->window, widget->style->black_gc, x1, y, x1 + width - 1, y); y += row_height; } /* Labels */ y = y1 + ((row_height - 1) - (widget->style->font->ascent + widget->style->font->descent)) / 2; rect.x = x1; rect.y = y1; rect.width = 2 * TEXT_BORDER + labels_width; rect.height = row_height - 1; for (i = 0; i < f_rows; i++) { mktime (&tm); if (gdk_rectangle_intersect (&rect, area, &dest)) { strftime (buf, 256, "%X", &tm); if ((di->sel_rows_used != 0) && (i >= di->sel_start_row) && (i < (di->sel_start_row + di->sel_rows_used))) gc = widget->style->fg_gc[GTK_STATE_SELECTED]; else gc = widget->style->fg_gc[GTK_STATE_NORMAL]; gdk_draw_string (widget->window, widget->style->font, gc, x1 + TEXT_BORDER, y + widget->style->font->ascent, buf); } rect.y += row_height; y += row_height; tm.tm_min += fullday->interval; } } static void paint_back_rows (GncalFullDay *fullday, int start_row, int rows_used) { int row_height; int xthickness, ythickness; GtkWidget *widget; GdkRectangle area; widget = GTK_WIDGET (fullday); row_height = calc_row_height (fullday); xthickness = widget->style->klass->xthickness; ythickness = widget->style->klass->ythickness; area.x = xthickness; area.y = ythickness + start_row * row_height; area.width = widget->allocation.width - 2 * xthickness; area.height = rows_used * row_height; paint_back (fullday, &area); } static void gncal_full_day_draw (GtkWidget *widget, GdkRectangle *area) { GncalFullDay *fullday; GList *children; Child *child; GdkRectangle rect, dest; g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); g_return_if_fail (area != NULL); if (!GTK_WIDGET_DRAWABLE (widget)) return; fullday = GNCAL_FULL_DAY (widget); paint_back (fullday, area); for (children = fullday->children; children; children = children->next) { child = children->data; rect.x = child->x; rect.y = child->y; rect.width = child->width; rect.height = child->height; if (gdk_rectangle_intersect (&rect, area, &dest)) { dest.x -= child->x; dest.y -= child->y; child_draw (fullday, child, &dest, TRUE); } } } static void gncal_full_day_draw_focus (GtkWidget *widget) { g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); if (!GTK_WIDGET_DRAWABLE (widget)) return; gtk_draw_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_IN, 0, 0, widget->allocation.width, widget->allocation.height); if (GTK_WIDGET_HAS_FOCUS (widget)) gdk_draw_rectangle (widget->window, widget->style->black_gc, FALSE, 0, 0, widget->allocation.width - 1, widget->allocation.height - 1); } static void gncal_full_day_size_request (GtkWidget *widget, GtkRequisition *requisition) { GncalFullDay *fullday; int labels_width; int rows; g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); g_return_if_fail (requisition != NULL); fullday = GNCAL_FULL_DAY (widget); /* Border and min width */ labels_width = calc_labels_width (fullday); requisition->width = 2 * widget->style->klass->xthickness + 4 * TEXT_BORDER + labels_width + MIN_WIDTH; requisition->height = 2 * widget->style->klass->ythickness; /* Rows */ get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &rows); requisition->height += (rows * (2 * TEXT_BORDER + widget->style->font->ascent + widget->style->font->descent) + (rows - 1)); /* division lines */ } static void gncal_full_day_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GncalFullDay *fullday; g_return_if_fail (widget != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (widget)); g_return_if_fail (allocation != NULL); widget->allocation = *allocation; fullday = GNCAL_FULL_DAY (widget); if (GTK_WIDGET_REALIZED (widget)) gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); layout_children (fullday); } static Child * find_child_by_window (GncalFullDay *fullday, GdkWindow *window) { GList *children; Child *child; for (children = fullday->children; children; children = children->next) { child = children->data; if (child->window == window) return child; } return NULL; } static void draw_xor_rect (GncalFullDay *fullday) { GtkWidget *widget; struct drag_info *di; int i; int row_height; int ythickness; widget = GTK_WIDGET (fullday); gdk_gc_set_function (widget->style->white_gc, GDK_INVERT); gdk_gc_set_subwindow (widget->style->white_gc, GDK_INCLUDE_INFERIORS); ythickness = widget->style->klass->ythickness; di = fullday->drag_info; row_height = calc_row_height (fullday); for (i = 0; i < XOR_RECT_WIDTH; i++) gdk_draw_rectangle (widget->window, widget->style->white_gc, FALSE, di->child->x + i, di->start_row * row_height + ythickness + i, di->child->width - 2 * i - 1, di->rows_used * row_height - 2 * i - 2); gdk_gc_set_function (widget->style->white_gc, GDK_COPY); gdk_gc_set_subwindow (widget->style->white_gc, GDK_CLIP_BY_CHILDREN); } static int get_row_from_y (GncalFullDay *fullday, int y, int round) { GtkWidget *widget; int row_height; int f_rows; int ythickness; get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows); row_height = calc_row_height (fullday); widget = GTK_WIDGET (fullday); ythickness = widget->style->klass->ythickness; y -= ythickness; if (y < 0) y = 0; else if (y >= (f_rows * row_height)) y = f_rows * row_height - 1; if (round) y += row_height / 2; y /= row_height; if (y > f_rows) y = f_rows; /* note that this is 1 more than the last row's index */ return y; } static gint gncal_full_day_button_press (GtkWidget *widget, GdkEventButton *event) { GncalFullDay *fullday; Child *child; struct drag_info *di; gint y; int row_height; int old_start_row, old_rows_used; int old_max; int paint_start_row, paint_rows_used; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); fullday = GNCAL_FULL_DAY (widget); if (event->window == widget->window) { /* Clicked on main window */ if (!GTK_WIDGET_HAS_FOCUS (widget)) gtk_widget_grab_focus (widget); /* Prepare for drag */ di = fullday->drag_info; di->drag_mode = DRAG_SELECT; old_start_row = di->sel_start_row; old_rows_used = di->sel_rows_used; di->sel_click_row = get_row_from_y (fullday, event->y, FALSE); di->sel_start_row = di->sel_click_row; di->sel_rows_used = 1; di->click_time = event->time; gdk_pointer_grab (widget->window, FALSE, (GDK_BUTTON_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_RELEASE_MASK), NULL, NULL, event->time); if (old_rows_used == 0) { paint_start_row = di->sel_start_row; paint_rows_used = di->sel_rows_used; } else { paint_start_row = MIN (old_start_row, di->sel_start_row); old_max = old_start_row + old_rows_used - 1; paint_rows_used = MAX (old_max, di->sel_start_row) - paint_start_row + 1; } paint_back_rows (fullday, paint_start_row, paint_rows_used); } else { /* Clicked on a child? */ child = find_child_by_window (fullday, event->window); if (!child) return FALSE; /* Prepare for drag */ di = fullday->drag_info; gtk_widget_get_pointer (widget, NULL, &y); if (event->y < HANDLE_SIZE) di->drag_mode = DRAG_MOVE; else di->drag_mode = DRAG_SIZE; row_height = calc_row_height (fullday); di->child = child; di->start_row = get_row_from_y (fullday, child->y, FALSE); di->rows_used = child->height / row_height; gdk_pointer_grab (child->window, FALSE, (GDK_BUTTON_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_RELEASE_MASK), NULL, NULL, event->time); draw_xor_rect (fullday); } return FALSE; } static void recompute_motion (GncalFullDay *fullday, int y) { struct drag_info *di; int f_rows; int row; di = fullday->drag_info; get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows); switch (di->drag_mode) { case DRAG_SELECT: row = get_row_from_y (fullday, y, FALSE); if (row >= f_rows) row = f_rows - 1; if (row < di->sel_click_row) { di->sel_start_row = row; di->sel_rows_used = di->sel_click_row - row + 1; } else { di->sel_start_row = di->sel_click_row; di->sel_rows_used = row - di->sel_start_row + 1; } break; case DRAG_MOVE: row = get_row_from_y (fullday, y, FALSE); if (row > (f_rows - di->rows_used)) row = f_rows - di->rows_used; di->start_row = row; break; case DRAG_SIZE: row = get_row_from_y (fullday, y, TRUE); if (row <= di->start_row) row = di->start_row + 1; else if (row > f_rows) row = f_rows; di->rows_used = row - di->start_row; break; default: g_assert_not_reached (); } } static void get_time_from_rows (GncalFullDay *fullday, int start_row, int rows_used, time_t *t_lower, time_t *t_upper) { struct tm tm; int row_height; get_tm_range (fullday, fullday->lower, fullday->upper, &tm, NULL, NULL, NULL); row_height = calc_row_height (fullday); tm.tm_min += fullday->interval * start_row; *t_lower = mktime (&tm); tm.tm_min += fullday->interval * rows_used; *t_upper = mktime (&tm); } static void update_from_drag_info (GncalFullDay *fullday) { struct drag_info *di; GtkWidget *widget; di = fullday->drag_info; widget = GTK_WIDGET (fullday); get_time_from_rows (fullday, di->start_row, di->rows_used, &di->child->ico->dtstart, &di->child->ico->dtend); child_range_changed (fullday, di->child); /* Notify calendar of change */ gnome_calendar_object_changed (fullday->calendar, di->child->ico, CHANGE_DATES); } static gint gncal_full_day_button_release (GtkWidget *widget, GdkEventButton *event) { GncalFullDay *fullday; struct drag_info *di; gint y; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); fullday = GNCAL_FULL_DAY (widget); di = fullday->drag_info; gtk_widget_get_pointer (widget, NULL, &y); switch (di->drag_mode) { case DRAG_NONE: return FALSE; case DRAG_SELECT: if ((event->time - di->click_time) < UNSELECT_TIMEOUT) di->sel_rows_used = 0; else recompute_motion (fullday, y); gdk_pointer_ungrab (event->time); paint_back_rows (fullday, di->sel_start_row, MAX (di->sel_rows_used, 1)); break; case DRAG_MOVE: case DRAG_SIZE: draw_xor_rect (fullday); recompute_motion (fullday, y); gdk_pointer_ungrab (event->time); update_from_drag_info (fullday); di->rows_used = 0; break; default: g_assert_not_reached (); } di->drag_mode = DRAG_NONE; di->child = NULL; return FALSE; } static gint gncal_full_day_motion (GtkWidget *widget, GdkEventMotion *event) { GncalFullDay *fullday; struct drag_info *di; gint y; int old_min, old_max; int new_min, new_max; int new_start_row, new_rows_used; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); fullday = GNCAL_FULL_DAY (widget); di = fullday->drag_info; gtk_widget_get_pointer (widget, NULL, &y); switch (di->drag_mode) { case DRAG_NONE: break; case DRAG_SELECT: old_min = di->sel_start_row; old_max = di->sel_start_row + di->sel_rows_used - 1; recompute_motion (fullday, y); new_min = di->sel_start_row; new_max = di->sel_start_row + di->sel_rows_used - 1; new_start_row = MIN (old_min, new_min); new_rows_used = MAX (old_max, new_max) - new_start_row + 1; paint_back_rows (fullday, new_start_row, new_rows_used); break; case DRAG_MOVE: case DRAG_SIZE: draw_xor_rect (fullday); recompute_motion (fullday, y); draw_xor_rect (fullday); break; default: g_assert_not_reached (); } return FALSE; } static gint gncal_full_day_expose (GtkWidget *widget, GdkEventExpose *event) { GncalFullDay *fullday; GList *children; Child *child; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (!GTK_WIDGET_DRAWABLE (widget)) return FALSE; fullday = GNCAL_FULL_DAY (widget); if (event->window == widget->window) paint_back (fullday, &event->area); else for (children = fullday->children; children; children = children->next) { child = children->data; if (event->window == child->window) { child_draw (fullday, child, &event->area, FALSE); break; } } return FALSE; } static gint gncal_full_day_key_press (GtkWidget *widget, GdkEventKey *event) { GncalFullDay *fullday; struct drag_info *di; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); fullday = GNCAL_FULL_DAY (widget); di = fullday->drag_info; if (di->sel_rows_used == 0) return FALSE; if (event->keyval == GDK_Return) { gtk_signal_emit (GTK_OBJECT (fullday), fullday_signals [RANGE_ACTIVATED]); return TRUE; } return FALSE; } static gint gncal_full_day_focus_in (GtkWidget *widget, GdkEventFocus *event) { g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); gtk_widget_draw_focus (widget); return FALSE; } static gint gncal_full_day_focus_out (GtkWidget *widget, GdkEventFocus *event) { g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); gtk_widget_draw_focus (widget); return FALSE; } static void gncal_full_day_foreach (GtkContainer *container, GtkCallback callback, gpointer callback_data) { GncalFullDay *fullday; GList *children; Child *child; g_return_if_fail (container != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (container)); g_return_if_fail (callback != NULL); fullday = GNCAL_FULL_DAY (container); for (children = fullday->children; children; children = children->next) { child = children->data; (*callback) (child->widget, callback_data); } } void gncal_full_day_update (GncalFullDay *fullday, iCalObject *ico, int flags) { GList *children; GList *l_events, *events; Child *child; g_return_if_fail (fullday != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (fullday)); if (!fullday->calendar->cal) return; /* Try to find child that changed */ for (children = fullday->children; children; children = children->next) { child = children->data; if (child->ico == ico) break; } /* If child was found and nothing but the summary changed, we can just paint the child and return */ if (children && !(flags & ~CHANGE_SUMMARY)) { child_draw (fullday, child, NULL, TRUE); return; } /* We have to regenerate and layout our list of children */ for (children = fullday->children; children; children = children->next) child_destroy (fullday, children->data); g_list_free (fullday->children); children = NULL; l_events = calendar_get_events_in_range (fullday->calendar->cal, fullday->lower, fullday->upper, calendar_compare_by_dtstart); for (events = l_events; events; events = events->next) { child = child_new (fullday, events->data); children = g_list_append (children, child); } g_list_free (l_events); fullday->children = g_list_first (children); layout_children (fullday); /* Realize and map children */ for (children = fullday->children; children; children = children->next) { if (GTK_WIDGET_REALIZED (fullday)) child_realize (fullday, children->data); if (GTK_WIDGET_MAPPED (fullday)) child_map (fullday, children->data); } gtk_widget_draw (GTK_WIDGET (fullday), NULL); } void gncal_full_day_set_bounds (GncalFullDay *fullday, time_t lower, time_t upper) { struct drag_info *di; g_return_if_fail (fullday != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (fullday)); if ((lower != fullday->lower) || (upper != fullday->upper)) { fullday->lower = lower; fullday->upper = upper; di = fullday->drag_info; di->sel_rows_used = 0; /* clear selection */ gncal_full_day_update (fullday, NULL, 0); } } int gncal_full_day_selection_range (GncalFullDay *fullday, time_t *lower, time_t *upper) { struct drag_info *di; time_t alower, aupper; g_return_val_if_fail (fullday != NULL, FALSE); g_return_val_if_fail (GNCAL_IS_FULL_DAY (fullday), FALSE); di = fullday->drag_info; if (di->sel_rows_used == 0) return FALSE; get_time_from_rows (fullday, di->sel_start_row, di->sel_rows_used, &alower, &aupper); if (lower) *lower = alower; if (upper) *upper= aupper; return TRUE; } void gncal_full_day_focus_child (GncalFullDay *fullday, iCalObject *ico) { GList *children; Child *child; GdkEvent event; g_return_if_fail (fullday != NULL); g_return_if_fail (ico != NULL); for (children = fullday->children; children; children = children->next) { child = children->data; if (child->ico == ico) { gtk_widget_grab_focus (child->widget); /* We synthesize a click because GtkText will not set the cursor and * the user will not be able to type-- this has to be fixed in * GtkText. */ memset (&event, 0, sizeof (event)); event.type = GDK_BUTTON_PRESS; event.button.window = child->widget->window; event.button.time = GDK_CURRENT_TIME; event.button.x = 0; event.button.y = 0; event.button.button = 1; gtk_widget_event (child->widget, &event); break; } } } static void range_activated (GncalFullDay *fullday) { struct drag_info *di; g_return_if_fail (fullday != NULL); g_return_if_fail (GNCAL_IS_FULL_DAY (fullday)); di = fullday->drag_info; /* Remove selection; at this point someone should already have added an appointment */ di->sel_rows_used = 0; paint_back (fullday, NULL); }