/* Month view display for gncal * * Copyright (C) 1998 Red Hat Software, Inc. * * Author: Federico Mena */ #include #include #include #include "eventedit.h" #include "layout.h" #include "month-view.h" #include "main.h" #include "mark.h" #include "quick-view.h" #include "timeutil.h" /* Spacing between title and calendar */ #define SPACING 4 /* Padding between day borders and event text */ #define EVENT_PADDING 3 static void month_view_class_init (MonthViewClass *class); static void month_view_init (MonthView *mv); static void month_view_size_request (GtkWidget *widget, GtkRequisition *requisition); static void month_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static GnomeCanvasClass *parent_class; GtkType month_view_get_type (void) { static GtkType month_view_type = 0; if (!month_view_type) { GtkTypeInfo month_view_info = { "MonthView", sizeof (MonthView), sizeof (MonthViewClass), (GtkClassInitFunc) month_view_class_init, (GtkObjectInitFunc) month_view_init, NULL, /* reserved_1 */ NULL, /* reserved_2 */ (GtkClassInitFunc) NULL }; month_view_type = gtk_type_unique (gnome_canvas_get_type (), &month_view_info); } return month_view_type; } static void month_view_class_init (MonthViewClass *class) { GtkWidgetClass *widget_class; widget_class = (GtkWidgetClass *) class; parent_class = gtk_type_class (gnome_canvas_get_type ()); widget_class->size_request = month_view_size_request; widget_class->size_allocate = month_view_size_allocate; } /* Creates the quick view when a day is clicked in the month view */ static void do_quick_view_popup (MonthView *mv, GdkEventButton *event, int day) { time_t day_begin_time, day_end_time; GList *list; GtkWidget *qv; char date_str[256]; day_begin_time = time_from_day (mv->year, mv->month, day); day_end_time = time_day_end (day_begin_time); list = calendar_get_events_in_range (mv->calendar->cal, day_begin_time, day_end_time); strftime (date_str, sizeof (date_str), _("%a %b %d %Y"), localtime (&day_begin_time)); qv = quick_view_new (mv->calendar, date_str, list); quick_view_do_popup (QUICK_VIEW (qv), event); gtk_widget_destroy (qv); calendar_destroy_event_list (list); } /* Callback used to destroy the popup menu when the month view is destroyed */ static void destroy_menu (GtkWidget *widget, gpointer data) { gtk_widget_destroy (GTK_WIDGET (data)); } /* Creates a new appointment in the current day */ static void new_appointment (GtkWidget *widget, gpointer data) { MonthView *mv; time_t *t; mv = MONTH_VIEW (data); t = gtk_object_get_data (GTK_OBJECT (widget), "time_data"); event_editor_new_whole_day (mv->calendar, *t); } /* Convenience functions to jump to a view and set the time */ static void do_jump (GtkWidget *widget, gpointer data, char *view_name) { MonthView *mv; time_t *t; mv = MONTH_VIEW (data); /* Get the time data from the menu item */ t = gtk_object_get_data (GTK_OBJECT (widget), "time_data"); /* Set the view and time */ gnome_calendar_set_view (mv->calendar, view_name); gnome_calendar_goto (mv->calendar, *t); } /* The following three callbacks set the view in the calendar and change the time */ static void jump_to_day (GtkWidget *widget, gpointer data) { do_jump (widget, data, "dayview"); } static void jump_to_week (GtkWidget *widget, gpointer data) { do_jump (widget, data, "weekview"); } static void jump_to_year (GtkWidget *widget, gpointer data) { do_jump (widget, data, "yearview"); } static GnomeUIInfo mv_popup_menu[] = { GNOMEUIINFO_ITEM_STOCK (N_("_New appointment in this day..."), NULL, new_appointment, GNOME_STOCK_MENU_NEW), GNOMEUIINFO_SEPARATOR, GNOMEUIINFO_ITEM_STOCK (N_("Jump to this _day"), NULL, jump_to_day, GNOME_STOCK_MENU_JUMP_TO), GNOMEUIINFO_ITEM_STOCK (N_("Jump to this _week"), NULL, jump_to_week, GNOME_STOCK_MENU_JUMP_TO), GNOMEUIINFO_ITEM_STOCK (N_("Jump to this _year"), NULL, jump_to_year, GNOME_STOCK_MENU_JUMP_TO), GNOMEUIINFO_END }; /* Creates the popup menu for the month view if it does not yet exist, and attaches it to the month * view object so that it can be destroyed when appropriate. */ static GtkWidget * get_popup_menu (MonthView *mv) { GtkWidget *menu; menu = gtk_object_get_data (GTK_OBJECT (mv), "popup_menu"); if (!menu) { menu = gnome_popup_menu_new (mv_popup_menu); gtk_object_set_data (GTK_OBJECT (mv), "popup_menu", menu); gtk_signal_connect (GTK_OBJECT (mv), "destroy", (GtkSignalFunc) destroy_menu, menu); } return menu; } /* Pops up the menu for the month view. */ static void do_popup_menu (MonthView *mv, GdkEventButton *event, int day) { GtkWidget *menu; static time_t t; menu = get_popup_menu (mv); /* Enable or disable items as appropriate */ gtk_widget_set_sensitive (mv_popup_menu[0].widget, day != 0); gtk_widget_set_sensitive (mv_popup_menu[2].widget, day != 0); gtk_widget_set_sensitive (mv_popup_menu[3].widget, day != 0); if (day == 0) day = 1; /* Store the time for the menu item callbacks to use */ t = time_from_day (mv->year, mv->month, day); gtk_object_set_data (GTK_OBJECT (mv_popup_menu[0].widget), "time_data", &t); gtk_object_set_data (GTK_OBJECT (mv_popup_menu[2].widget), "time_data", &t); gtk_object_set_data (GTK_OBJECT (mv_popup_menu[3].widget), "time_data", &t); gtk_object_set_data (GTK_OBJECT (mv_popup_menu[4].widget), "time_data", &t); gnome_popup_menu_do_popup (menu, NULL, NULL, event, mv); } /* Event handler for day groups. When mouse button 1 is pressed, it will pop up a quick view with * the events in that day. When mouse button 3 is pressed, it will pop up a menu. */ static gint day_event (GnomeCanvasItem *item, GdkEvent *event, gpointer data) { MonthView *mv; int child_num; int day; mv = MONTH_VIEW (data); child_num = gnome_month_item_child2num (GNOME_MONTH_ITEM (mv->mitem), item); day = gnome_month_item_num2day (GNOME_MONTH_ITEM (mv->mitem), child_num); switch (event->type) { case GDK_BUTTON_PRESS: if ((event->button.button == 1) && (day != 0)) { do_quick_view_popup (mv, (GdkEventButton *) event, day); return TRUE; } else if (event->button.button == 3) { do_popup_menu (mv, (GdkEventButton *) event, day); return TRUE; } break; default: break; } return FALSE; } /* Returns the index of the specified arrow in the array of arrows */ static int get_arrow_index (MonthView *mv, GnomeCanvasItem *arrow) { int i; for (i = 0; i < 42; i++) if (mv->up[i] == arrow) return i; else if (mv->down[i] == arrow) return i + 42; g_warning ("Eeeek, arrow %p not found in month view %p", arrow, mv); return -1; } /* Checks whether arrows need to be displayed at the specified day index or not */ static void check_arrow_visibility (MonthView *mv, int day_index) { GtkArg args[3]; double text_height; double clip_height; double y_offset; args[0].name = "text_height"; args[1].name = "clip_height"; args[2].name = "y_offset"; gtk_object_getv (GTK_OBJECT (mv->text[day_index]), 3, args); text_height = GTK_VALUE_DOUBLE (args[0]); clip_height = GTK_VALUE_DOUBLE (args[1]); y_offset = GTK_VALUE_DOUBLE (args[2]); /* Check up arrow */ if (y_offset < 0.0) gnome_canvas_item_show (mv->up[day_index]); else gnome_canvas_item_hide (mv->up[day_index]); if (y_offset > (clip_height - text_height)) gnome_canvas_item_show (mv->down[day_index]); else gnome_canvas_item_hide (mv->down[day_index]); } /* Finds which arrow was clicked and scrolls the corresponding text item in the month view */ static void do_arrow_click (MonthView *mv, GnomeCanvasItem *arrow) { int arrow_index; int day_index; int up; GtkArg args[4]; double text_height, clip_height; double y_offset; GdkFont *font; arrow_index = get_arrow_index (mv, arrow); up = (arrow_index < 42); day_index = up ? arrow_index : (arrow_index - 42); /* See how much we can scroll */ args[0].name = "text_height"; args[1].name = "clip_height"; args[2].name = "y_offset"; args[3].name = "font_gdk"; gtk_object_getv (GTK_OBJECT (mv->text[day_index]), 4, args); text_height = GTK_VALUE_DOUBLE (args[0]); clip_height = GTK_VALUE_DOUBLE (args[1]); y_offset = GTK_VALUE_DOUBLE (args[2]); font = GTK_VALUE_BOXED (args[3]); if (up) y_offset += font->ascent + font->descent; else y_offset -= font->ascent + font->descent; if (y_offset > 0.0) y_offset = 0.0; else if (y_offset < (clip_height - text_height)) y_offset = clip_height - text_height; /* Scroll */ gnome_canvas_item_set (mv->text[day_index], "y_offset", y_offset, NULL); check_arrow_visibility (mv, day_index); } /* Event handler for the scroll arrows in the month view */ static gint arrow_event (GnomeCanvasItem *item, GdkEvent *event, gpointer data) { MonthView *mv; mv = MONTH_VIEW (data); switch (event->type) { case GDK_ENTER_NOTIFY: gnome_canvas_item_set (item, "fill_color", color_spec_from_prop (COLOR_PROP_PRELIGHT_DAY_BG), NULL); return TRUE; case GDK_LEAVE_NOTIFY: gnome_canvas_item_set (item, "fill_color", color_spec_from_prop (COLOR_PROP_DAY_FG), NULL); return TRUE; case GDK_BUTTON_PRESS: if (event->button.button != 1) break; do_arrow_click (mv, item); return TRUE; default: break; } return FALSE; } /* Creates a new arrow out of the specified points and connects the proper signals to it */ static GnomeCanvasItem * new_arrow (MonthView *mv, GnomeCanvasGroup *group, GnomeCanvasPoints *points) { GnomeCanvasItem *item; char *color_spec; color_spec = color_spec_from_prop (COLOR_PROP_DAY_FG); item = gnome_canvas_item_new (GNOME_CANVAS_GROUP (group), gnome_canvas_polygon_get_type (), "points", points, "fill_color", color_spec, "outline_color", color_spec, NULL); gtk_signal_connect (GTK_OBJECT (item), "event", (GtkSignalFunc) arrow_event, mv); return item; } static void month_view_init (MonthView *mv) { int i; GnomeCanvasItem *day_group; GnomeCanvasPoints *points; /* Title */ mv->title = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (mv)), gnome_canvas_text_get_type (), "anchor", GTK_ANCHOR_N, "fontset", HEADING_FONTSET, "fill_color", "black", NULL); /* Month item */ mv->mitem = gnome_month_item_new (gnome_canvas_root (GNOME_CANVAS (mv))); gnome_canvas_item_set (mv->mitem, "x", 0.0, "anchor", GTK_ANCHOR_NW, "day_anchor", GTK_ANCHOR_NE, "start_on_monday", week_starts_on_monday, "heading_padding", 2.0, "heading_fontset", BIG_DAY_HEADING_FONTSET, "day_fontset", BIG_NORMAL_DAY_FONTSET, NULL); /* Arrows and text items. The arrows start hidden by default; they will be shown as * appropriate by the item adjustment code. Also, connect to the event signal of the * day groups so that we can pop up the quick view when appropriate. */ points = gnome_canvas_points_new (3); for (i = 0; i < 42; i++) { day_group = gnome_month_item_num2child (GNOME_MONTH_ITEM (mv->mitem), i + GNOME_MONTH_ITEM_DAY_GROUP); gtk_signal_connect (GTK_OBJECT (day_group), "event", (GtkSignalFunc) day_event, mv); /* Up arrow */ points->coords[0] = 3; points->coords[1] = 10; points->coords[2] = 11; points->coords[3] = 10; points->coords[4] = 7; points->coords[5] = 3; mv->up[i] = new_arrow (mv, GNOME_CANVAS_GROUP (day_group), points); /* Down arrow */ points->coords[0] = 13; points->coords[1] = 3; points->coords[2] = 17; points->coords[3] = 10; points->coords[4] = 21; points->coords[5] = 3; mv->down[i] = new_arrow (mv, GNOME_CANVAS_GROUP (day_group), points); /* Text item */ mv->text[i] = gnome_canvas_item_new (GNOME_CANVAS_GROUP (day_group), gnome_canvas_text_get_type (), "fontset", EVENT_FONTSET, "anchor", GTK_ANCHOR_NW, "fill_color", color_spec_from_prop (COLOR_PROP_DAY_FG), "clip", TRUE, NULL); } mv->old_current_index = -1; } GtkWidget * month_view_new (GnomeCalendar *calendar, time_t month) { MonthView *mv; g_return_val_if_fail (calendar != NULL, NULL); g_return_val_if_fail (GNOME_IS_CALENDAR (calendar), NULL); mv = gtk_type_new (month_view_get_type ()); mv->calendar = calendar; month_view_colors_changed (mv); month_view_set (mv, month); return GTK_WIDGET (mv); } static void month_view_size_request (GtkWidget *widget, GtkRequisition *requisition) { g_return_if_fail (widget != NULL); g_return_if_fail (IS_MONTH_VIEW (widget)); g_return_if_fail (requisition != NULL); if (GTK_WIDGET_CLASS (parent_class)->size_request) (* GTK_WIDGET_CLASS (parent_class)->size_request) (widget, requisition); requisition->width = 200; requisition->height = 150; } /* Adjusts the text items for events in the month view to the appropriate size. It also makes the * corresponding arrows visible or invisible, as appropriate. */ static void adjust_children (MonthView *mv) { int i; GnomeCanvasItem *item; double x1, y1, x2, y2; GtkArg arg; for (i = 0; i < 42; i++) { /* Get dimensions of the day group */ item = gnome_month_item_num2child (GNOME_MONTH_ITEM (mv->mitem), i + GNOME_MONTH_ITEM_DAY_GROUP); gnome_canvas_item_get_bounds (item, &x1, &y1, &x2, &y2); /* Normalize and add paddings */ x2 -= x1 + EVENT_PADDING; x1 = EVENT_PADDING; y2 -= y1 + EVENT_PADDING; y1 = EVENT_PADDING; /* Add height of day label to y1 */ item = gnome_month_item_num2child (GNOME_MONTH_ITEM (mv->mitem), i + GNOME_MONTH_ITEM_DAY_LABEL); arg.name = "text_height"; gtk_object_getv (GTK_OBJECT (item), 1, &arg); y1 += GTK_VALUE_DOUBLE (arg); /* Set the position and clip size */ gnome_canvas_item_set (mv->text[i], "x", x1, "y", y1, "clip_width", x2 - x1, "clip_height", y2 - y1, "x_offset", 0.0, "y_offset", 0.0, NULL); /* See what visibility state the arrows should be set to */ check_arrow_visibility (mv, i); } } static void month_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { MonthView *mv; GdkFont *font; GtkArg arg; int y; g_return_if_fail (widget != NULL); g_return_if_fail (IS_MONTH_VIEW (widget)); g_return_if_fail (allocation != NULL); mv = MONTH_VIEW (widget); if (GTK_WIDGET_CLASS (parent_class)->size_allocate) (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation); gnome_canvas_set_scroll_region (GNOME_CANVAS (mv), 0, 0, allocation->width, allocation->height); /* Adjust items to new size */ arg.name = "font_gdk"; gtk_object_getv (GTK_OBJECT (mv->title), 1, &arg); font = GTK_VALUE_BOXED (arg); gnome_canvas_item_set (mv->title, "x", (double) allocation->width / 2.0, "y", (double) SPACING, NULL); y = font->ascent + font->descent + 2 * SPACING; gnome_canvas_item_set (mv->mitem, "y", (double) y, "width", (double) (allocation->width - 1), "height", (double) (allocation->height - y - 1), NULL); /* Adjust children */ adjust_children (mv); } /* This defines the environment for the calendar iterator function that is used to populate the * month view with events. */ struct iter_info { MonthView *mv; /* The month view we are creating children for */ int first_day_index; /* Index of the first day of the month within the month item */ time_t month_begin, month_end; /* Beginning and end of month */ GString **strings; /* Array of strings to populate */ }; /* This is the calendar iterator function used to populate the string array with event information. * For each event, it iterates through all the days that the event touches and appends the proper * information to the string array in the iter_info structure. */ static int add_event (iCalObject *ico, time_t start, time_t end, void *data) { struct iter_info *ii; struct tm *tm; time_t t; time_t day_begin_time, day_end_time; ii = data; /* Get the first day of the event */ t = MAX (start, ii->month_begin); day_begin_time = time_day_begin (t); day_end_time = time_day_end (day_begin_time); /* Loop until the event ends or the month ends. For each day touched, append the proper * information to the corresponding string. */ do { tm = localtime (&day_begin_time); g_string_sprintfa (ii->strings[ii->first_day_index + tm->tm_mday - 1], "%s\n", ico->summary); /* Next day */ day_begin_time = time_add_day (day_begin_time, 1); day_end_time = time_day_end (day_begin_time); } while ((end > day_begin_time) && (day_begin_time < ii->month_end)); return TRUE; /* this means we are not finished yet with event generation */ } void month_view_update (MonthView *mv, iCalObject *object, int flags) { struct iter_info ii; GString *strings[42]; int i; time_t t; g_return_if_fail (mv != NULL); g_return_if_fail (IS_MONTH_VIEW (mv)); ii.mv = mv; /* Create an array of empty GStrings */ ii.strings = strings; for (i = 0; i < 42; i++) strings[i] = g_string_new (NULL); ii.first_day_index = gnome_month_item_day2index (GNOME_MONTH_ITEM (mv->mitem), 1); g_assert (ii.first_day_index != -1); /* Populate the array of strings with events */ t = time_from_day (mv->year, mv->month, 1); ii.month_begin = time_month_begin (t); ii.month_end = time_month_end (t); calendar_iterate (mv->calendar->cal, ii.month_begin, ii.month_end, add_event, &ii); for (i = 0; i < 42; i++) { /* Delete the last character if it is a newline */ if (strings[i]->str && strings[i]->len && (strings[i]->str[strings[i]->len - 1] == '\n')) g_string_truncate (strings[i], strings[i]->len - 1); gnome_canvas_item_set (mv->text[i], "text", strings[i]->str, NULL); g_string_free (strings[i], TRUE); } /* Adjust children for scrolling */ adjust_children (mv); } /* Unmarks the old day that was marked as current and marks the current day if appropriate */ static void mark_current_day (MonthView *mv) { time_t t; struct tm *tm; GnomeCanvasItem *item; /* Unmark the old day */ if (mv->old_current_index != -1) { item = gnome_month_item_num2child (GNOME_MONTH_ITEM (mv->mitem), GNOME_MONTH_ITEM_DAY_LABEL + mv->old_current_index); gnome_canvas_item_set (item, "fill_color", color_spec_from_prop (COLOR_PROP_DAY_FG), "fontset", BIG_NORMAL_DAY_FONTSET, NULL); mv->old_current_index = -1; } /* Mark the new day */ t = time (NULL); tm = localtime (&t); if (((tm->tm_year + 1900) == mv->year) && (tm->tm_mon == mv->month)) { mv->old_current_index = gnome_month_item_day2index (GNOME_MONTH_ITEM (mv->mitem), tm->tm_mday); g_assert (mv->old_current_index != -1); item = gnome_month_item_num2child (GNOME_MONTH_ITEM (mv->mitem), GNOME_MONTH_ITEM_DAY_LABEL + mv->old_current_index); gnome_canvas_item_set (item, "fill_color", color_spec_from_prop (COLOR_PROP_CURRENT_DAY_FG), "fontset", BIG_CURRENT_DAY_FONTSET, NULL); } } void month_view_set (MonthView *mv, time_t month) { struct tm *tm; char buf[100]; g_return_if_fail (mv != NULL); g_return_if_fail (IS_MONTH_VIEW (mv)); /* Title */ tm = localtime (&month); mv->year = tm->tm_year + 1900; mv->month = tm->tm_mon; strftime (buf, 100, _("%B %Y"), tm); gnome_canvas_item_set (mv->title, "text", buf, NULL); /* Month item */ gnome_canvas_item_set (mv->mitem, "year", mv->year, "month", mv->month, NULL); /* Update events */ month_view_update (mv, NULL, 0); mark_current_day (mv); } void month_view_time_format_changed (MonthView *mv) { g_return_if_fail (mv != NULL); g_return_if_fail (IS_MONTH_VIEW (mv)); gnome_canvas_item_set (mv->mitem, "start_on_monday", week_starts_on_monday, NULL); month_view_set (mv, time_month_begin (time_from_day (mv->year, mv->month, 1))); } void month_view_colors_changed (MonthView *mv) { g_return_if_fail (mv != NULL); g_return_if_fail (IS_MONTH_VIEW (mv)); colorify_month_item (GNOME_MONTH_ITEM (mv->mitem), default_color_func, NULL); mark_current_day (mv); /* FIXME: set children to the marked color */ }