/* * Evolution calendar - Print support * * 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; either * version 2 of the License, or (at your option) version 3. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Michael Zucchi * Federico Mena-Quintero * Damon Chaplin * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include "print.h" #include #include #include #include #include #include #include #include "e-cal-model.h" #include "e-day-view.h" #include "e-day-view-layout.h" #include "e-week-view.h" #include "e-week-view-layout.h" #include "e-task-table.h" #include "gnome-cal.h" #include "art/jump.xpm" typedef struct PrintCompItem PrintCompItem; typedef struct PrintCalItem PrintCalItem; struct PrintCompItem { ECalClient *client; ECalComponent *comp; icaltimezone *zone; gboolean use_24_hour_format; }; struct PrintCalItem { GnomeCalendar *gcal; time_t start; }; static gdouble evo_calendar_print_renderer_get_width (GtkPrintContext *context, PangoFontDescription *font, const gchar *text) { PangoLayout *layout; gint layout_width, layout_height; layout = gtk_print_context_create_pango_layout (context); pango_layout_set_font_description (layout, font); pango_layout_set_text (layout, text, -1); pango_layout_set_indent (layout, 0); pango_layout_get_size (layout, &layout_width, &layout_height); g_object_unref (layout); return pango_units_to_double (layout_width); } static gdouble evo_calendar_print_renderer_get_height (GtkPrintContext *context, PangoFontDescription *font, const gchar *text) { PangoLayout *layout; gint layout_width, layout_height; layout = gtk_print_context_create_pango_layout (context); pango_layout_set_font_description (layout, font); pango_layout_set_text (layout, text, -1); pango_layout_set_indent (layout, 0); pango_layout_get_size (layout, &layout_width, &layout_height); g_object_unref (layout); return pango_units_to_double (layout_height); } static gdouble get_font_size (PangoFontDescription *font) { g_return_val_if_fail (font, 0.0); return pango_units_to_double (pango_font_description_get_size (font)); } static gint get_day_view_time_divisions (void) { GSettings *settings; gint time_divisions; settings = g_settings_new ("org.gnome.evolution.calendar"); time_divisions = g_settings_get_int (settings, "time-divisions"); if (time_divisions < 5 || time_divisions > 30) time_divisions = 30; g_object_unref (settings); return time_divisions; } /* * Note that most dimensions are in points (1/72 of an inch) since that is * what gnome-print uses. */ /* GtkHTML prints using a fixed margin. It has code to get the margins from * gnome-print keys, but it's commented out. The corresponding code here * doesn't seem to work either (getting zero margins), so we adopt * gtkhtml's cheat. */ #define TEMP_MARGIN .05 /* The fonts to use */ #define FONT_FAMILY "Sans" /* The font size to use for normal text. */ #define DAY_NORMAL_FONT_SIZE 12 #define WEEK_NORMAL_FONT_SIZE 12 #define MONTH_NORMAL_FONT_SIZE 8 #define WEEK_EVENT_FONT_SIZE 9 #define WEEK_SMALL_FONT_SIZE 8 /* The height of the header bar across the top of the Day, Week & Month views, * which contains the dates shown and the 2 small calendar months. */ #define HEADER_HEIGHT 80 /* The width of the small calendar months, the space from the right edge of * the header rectangle, and the space between the months. */ #define MIN_SMALL_MONTH_WIDTH 120 #define SMALL_MONTH_PAD 5 #define SMALL_MONTH_SPACING 20 /* The minimum number of rows we leave space for for the long events in the * day view. */ #define DAY_VIEW_MIN_ROWS_IN_TOP_DISPLAY 2 /* The row height for long events in the day view. */ #define DAY_VIEW_ROW_HEIGHT 14 #define CALC_DAY_VIEW_ROWS(divis) ((60 / divis) * 24) /* The width of the column with all the times in it. */ #define DAY_VIEW_TIME_COLUMN_WIDTH 36 /* The space on the right of each event. */ #define DAY_VIEW_EVENT_X_PAD 8 /* Allowance for small errors in floating point comparisons. */ #define EPSILON 0.01 /* The weird month of September 1752, where 3 Sep through 13 Sep were * eliminated due to the Gregorian reformation. */ static const gint sept_1752[42] = { 0, 0, 1, 2, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #define SEPT_1752_START 2 /* Start day within month */ #define SEPT_1752_END 20 /* End day within month */ struct pdinfo { gint days_shown; time_t day_starts[E_DAY_VIEW_MAX_DAYS + 1]; GArray *long_events; GArray *events[E_DAY_VIEW_MAX_DAYS]; gint start_hour; gint end_hour; gint start_minute_offset; gint end_minute_offset; gint rows; gint mins_per_row; guint8 cols_per_row[CALC_DAY_VIEW_ROWS (1)]; gboolean use_24_hour_format; icaltimezone *zone; }; struct psinfo { gint days_shown; time_t day_starts[E_WEEK_VIEW_MAX_WEEKS * 7 + 1]; GArray *events; gint rows_per_cell; gint rows_per_compressed_cell; GDateWeekday display_start_weekday; gboolean multi_week_view; gint weeks_shown; gint month; gboolean compress_weekend; gboolean use_24_hour_format; gdouble row_height; gdouble header_row_height; icaltimezone *zone; }; /* Convenience function to help the transition to timezone functions. * It converts a time_t to a struct tm. */ static void convert_timet_to_struct_tm (time_t time, icaltimezone *zone, struct tm *tm) { struct icaltimetype tt; /* Convert it to an icaltimetype. */ tt = icaltime_from_timet_with_zone (time, FALSE, zone); /* Fill in the struct tm. */ tm->tm_year = tt.year - 1900; tm->tm_mon = tt.month - 1; tm->tm_mday = tt.day; tm->tm_hour = tt.hour; tm->tm_min = tt.minute; tm->tm_sec = tt.second; tm->tm_isdst = tt.is_daylight; tm->tm_wday = time_day_of_week (tt.day, tt.month - 1, tt.year); } /* Fills the 42-element days array with the day numbers for the specified * month. Slots outside the bounds of the month are filled with zeros. * The starting and ending indexes of the days are returned in the start * and end arguments. */ static void build_month (ECalModel *model, gint month, gint year, gint *days, gint *start, gint *end) { gint i; gint d_month; gint d_week; GDateWeekday weekday; GDateWeekday week_start_day; /* Note that months are zero-based, so September is month 8 */ if ((year == 1752) && (month == 8)) { memcpy (days, sept_1752, 42 * sizeof (gint)); if (start) *start = SEPT_1752_START; if (end) *end = SEPT_1752_END; return; } for (i = 0; i < 42; i++) days[i] = 0; d_month = time_days_in_month (year, month); /* Get the start weekday in the month, 0=Sun to 6=Sat. */ d_week = time_day_of_week (1, month, year); /* Get the configuration setting specifying which weekday we put on * the left column. */ week_start_day = e_cal_model_get_week_start_day (model); weekday = e_weekday_from_tm_wday (d_week); for (i = 0; i < d_month; i++) days[d_week + i] = i + 1; if (start) *start = e_weekday_get_days_between (week_start_day, weekday); if (end) *end = d_week + d_month - 1; } static PangoFontDescription * get_font_for_size (gdouble height, PangoWeight weight) { PangoFontDescription *desc; gint size; #define MAGIC_SCALE_FACTOR (0.86) size = pango_units_from_double (height * MAGIC_SCALE_FACTOR); desc = pango_font_description_new (); pango_font_description_set_size (desc, size); pango_font_description_set_weight (desc, weight); pango_font_description_set_family_static (desc, FONT_FAMILY); return desc; } /* Prints a rectangle, with or without a border, filled or outline, and * possibly with triangular arrows at one or both horizontal edges. * width = width of border, -ve means no border. * red,green,blue = bgcolor to fill, -ve means no fill. * left_triangle_width, right_triangle_width = width from edge of rectangle to * point of triangle, or -ve for no triangle. */ static void print_border_with_triangles (GtkPrintContext *pc, gdouble x1, gdouble x2, gdouble y1, gdouble y2, gdouble line_width, gdouble red, gdouble green, gdouble blue, gdouble left_triangle_width, gdouble right_triangle_width) { cairo_t *cr = gtk_print_context_get_cairo_context (pc); cairo_save (cr); /* Fill in the interior of the rectangle, if desired. */ if (red >= -EPSILON && green >= -EPSILON && blue >= -EPSILON) { cairo_move_to (cr, x1, y1); if (left_triangle_width > 0.0) cairo_line_to ( cr, x1 - left_triangle_width, (y1 + y2) / 2); cairo_line_to (cr, x1, y2); cairo_line_to (cr, x2, y2); if (right_triangle_width > 0.0) cairo_line_to (cr, x2 + right_triangle_width, (y1 + y2) / 2); cairo_line_to (cr, x2, y1); cairo_close_path (cr); cairo_set_source_rgb (cr, red, green, blue); cairo_fill (cr); cairo_restore (cr); cairo_save (cr); } /* Draw the outline, if desired. */ if (line_width >= EPSILON) { cr = gtk_print_context_get_cairo_context (pc); cairo_move_to (cr, x1, y1); if (left_triangle_width > 0.0) cairo_line_to ( cr, x1 - left_triangle_width, (y1 + y2) / 2); cairo_line_to (cr, x1, y2); cairo_line_to (cr, x2, y2); if (right_triangle_width > 0.0) cairo_line_to ( cr, x2 + right_triangle_width, (y1 + y2) / 2); cairo_line_to (cr, x2, y1); cairo_close_path (cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_set_line_width (cr, line_width); cairo_stroke (cr); } cairo_restore (cr); } /* Prints a rectangle, with or without a border, and filled or outline. * width = width of border, -ve means no border. * fillcolor = shade of fill, -ve means no fill. */ static void print_border_rgb (GtkPrintContext *pc, gdouble x1, gdouble x2, gdouble y1, gdouble y2, gdouble line_width, gdouble red, gdouble green, gdouble blue) { print_border_with_triangles ( pc, x1, x2, y1, y2, line_width, red, green, blue, -1.0, -1.0); } static void print_border (GtkPrintContext *pc, gdouble x1, gdouble x2, gdouble y1, gdouble y2, gdouble line_width, gdouble fillcolor) { print_border_rgb ( pc, x1, x2, y1, y2, line_width, fillcolor, fillcolor, fillcolor); } static void print_rectangle (GtkPrintContext *context, gdouble x, gdouble y, gdouble width, gdouble height, gdouble red, gdouble green, gdouble blue) { cairo_t *cr = gtk_print_context_get_cairo_context (context); cairo_save (cr); cairo_rectangle (cr, x, y, width, height); cairo_set_source_rgb (cr, red, green, blue); cairo_fill (cr); cairo_restore (cr); } /* Recreate the layout by shrinking the text string if we have a line that's * too high. */ static PangoLayout * shrink_text_to_line (PangoLayout *layout, gint layout_width, gint layout_height, GtkPrintContext *context, PangoFontDescription *desc, const gchar *text, PangoAlignment alignment, gdouble x1, gdouble x2, gdouble y1, gdouble y2) { gint new_length; if (layout_width == 0 || x2 - x1 < EPSILON) return layout; /* Do nothing */ new_length = (gint) floor (pango_units_from_double (x2 - x1) / (gdouble) layout_width * (gdouble) strlen (text)); if (new_length < strlen (text)) { g_object_unref (layout); /* Destroy old layout */ layout = gtk_print_context_create_pango_layout (context); pango_layout_set_font_description (layout, desc); pango_layout_set_alignment (layout, alignment); pango_layout_set_text (layout, text, new_length); } return layout; } /* Prints 1 line of aligned text in a box. It is centered vertically, and * the horizontal alignment can be either PANGO_ALIGN_LEFT, PANGO_ALIGN_RIGHT, * or PANGO_ALIGN_CENTER. Text is truncated if too long for cell. */ static gdouble print_text_line (GtkPrintContext *context, PangoFontDescription *desc, const gchar *text, PangoAlignment alignment, gdouble x1, gdouble x2, gdouble y1, gdouble y2, gboolean shrink) { PangoLayout *layout; gint layout_width, layout_height; cairo_t *cr; cr = gtk_print_context_get_cairo_context (context); layout = gtk_print_context_create_pango_layout (context); pango_layout_set_font_description (layout, desc); pango_layout_set_alignment (layout, alignment); pango_layout_set_text (layout, text, -1); /* Grab the width before expanding the layout. */ pango_layout_get_size (layout, &layout_width, &layout_height); if (shrink && layout_width > pango_units_from_double (x2 - x1)) /* Too wide */ layout = shrink_text_to_line ( layout, layout_width, layout_height, context, desc, text, alignment, x1, x2, y1, y2); pango_layout_set_width (layout, pango_units_from_double (x2 - x1)); cairo_save (cr); /* Set a clipping rectangle. */ cairo_move_to (cr, x1, y1); cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1); cairo_clip (cr); cairo_new_path (cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_move_to (cr, x1, y1); pango_cairo_show_layout (cr, layout); cairo_stroke (cr); cairo_restore (cr); g_object_unref (layout); return pango_units_to_double (layout_width); } /* Prints 1 or more lines of aligned text in a box. It is centered vertically, and * the horizontal alignment can be either PANGO_ALIGN_LEFT, PANGO_ALIGN_RIGHT, * or PANGO_ALIGN_CENTER. */ static double print_text (GtkPrintContext *context, PangoFontDescription *desc, const gchar *text, PangoAlignment alignment, gdouble x1, gdouble x2, gdouble y1, gdouble y2) { return print_text_line ( context, desc, text, alignment, x1, x2, y1, y2, FALSE); } /* gets/frees the font for you, as a bold font */ static gdouble print_text_size_bold (GtkPrintContext *context, const gchar *text, PangoAlignment alignment, gdouble x1, gdouble x2, gdouble y1, gdouble y2) { PangoFontDescription *font; gdouble w; font = get_font_for_size (ABS (y2 - y1) * 0.5, PANGO_WEIGHT_BOLD); w = print_text (context, font, text, alignment, x1, x2, y1, y2); pango_font_description_free (font); return w; } /* gets/frees the font for you, as a bold font - absolute size parameter */ static double print_text_abs_bold (GtkPrintContext *context, const gchar *text, gdouble font_size, PangoAlignment alignment, gdouble x1, gdouble x2, gdouble y1, gdouble y2) { PangoFontDescription *font; gdouble w; font = get_font_for_size (font_size, PANGO_WEIGHT_BOLD); w = print_text_line (context, font, text, alignment, x1, x2, y1, y2, TRUE); pango_font_description_free (font); return w; } static void titled_box (GtkPrintContext *context, const gchar *text, PangoFontDescription *font, PangoAlignment alignment, gdouble *x1, gdouble *y1, gdouble *x2, gdouble *y2, gdouble linewidth) { gdouble size; size = evo_calendar_print_renderer_get_height (context, font, text); print_border (context, *x1, *x2, *y1, *y1 + size + 2, linewidth, 0.9); print_border (context, *x1, *x2, *y1 + size + 2, *y2, linewidth, -1.0); *x1 += 2; *x2 -= 2; *y2 += 2; print_text (context, font, text, alignment, *x1, *x2, *y1 + 1.0, *y1 + size); *y1 += size + 2; } static gboolean get_show_week_numbers (void) { GSettings *settings; gboolean show_week_numbers; settings = g_settings_new ("org.gnome.evolution.calendar"); show_week_numbers = g_settings_get_boolean (settings, "show-week-numbers"); g_object_unref (settings); return show_week_numbers; } enum datefmt { DATE_MONTH = 1 << 0, DATE_DAY = 1 << 1, DATE_DAYNAME = 1 << 2, DATE_YEAR = 1 << 3 }; static const gchar *days[] = { N_("1st"), N_("2nd"), N_("3rd"), N_("4th"), N_("5th"), N_("6th"), N_("7th"), N_("8th"), N_("9th"), N_("10th"), N_("11th"), N_("12th"), N_("13th"), N_("14th"), N_("15th"), N_("16th"), N_("17th"), N_("18th"), N_("19th"), N_("20th"), N_("21st"), N_("22nd"), N_("23rd"), N_("24th"), N_("25th"), N_("26th"), N_("27th"), N_("28th"), N_("29th"), N_("30th"), N_("31st") }; /* format the date 'nicely' and consistently for various headers */ static gchar * format_date (struct tm *tm, gint flags, gchar *buffer, gint bufflen) { GString *fmt = g_string_new (""); if (flags & DATE_DAYNAME) { g_string_append (fmt, "%A"); } if (flags & DATE_DAY) { if (flags & DATE_DAYNAME) g_string_append (fmt, " "); g_string_append (fmt, gettext (days[tm->tm_mday - 1])); } if (flags & DATE_MONTH) { if (flags & (DATE_DAY | DATE_DAYNAME)) g_string_append (fmt, " "); g_string_append (fmt, "%B"); if ((flags & (DATE_DAY | DATE_YEAR)) == (DATE_DAY | DATE_YEAR)) g_string_append (fmt, ","); } if (flags & DATE_YEAR) { if (flags & (DATE_DAY | DATE_DAYNAME | DATE_MONTH)) g_string_append (fmt, " "); g_string_append (fmt, "%Y"); } e_utf8_strftime (buffer, bufflen, fmt->str, tm); buffer[bufflen - 1] = '\0'; g_string_free (fmt, TRUE); return buffer; } static gboolean instance_cb (ECalComponent *comp, time_t instance_start, time_t instance_end, gpointer data) { gboolean *found = ((ECalModelGenerateInstancesData *) data)->cb_data; *found = TRUE; return FALSE; } const gchar *daynames[] = { /* Translators: These are workday abbreviations, * e.g. Su=Sunday and Th=thursday */ /* G_DATE_BAD_WEEKDAY */ "", /* G_DATE_MONDAY */ N_("Mo"), /* G_DATE_TUESDAY */ N_("Tu"), /* G_DATE_WEDNESDAY */ N_("We"), /* G_DATE_THURSDAY */ N_("Th"), /* G_DATE_FRIDAY */ N_("Fr"), /* G_DATE_SATURDAY */ N_("Sa"), /* G_DATE_SUNDAY */ N_("Su") }; static gdouble calc_small_month_width (GtkPrintContext *context, gdouble for_height) { PangoFontDescription *font_bold; gdouble res = 0.0; gint ii; font_bold = get_font_for_size (for_height / 7.4, PANGO_WEIGHT_BOLD); res = MAX (evo_calendar_print_renderer_get_width ( context, font_bold, "23"), res); for (ii = G_DATE_MONDAY; ii < G_N_ELEMENTS (daynames); ii++) { res = MAX (evo_calendar_print_renderer_get_width ( context, font_bold, _(daynames[ii])), res); } pango_font_description_free (font_bold); /* res is max cell width, so multiply it with column * count plus some space between columns. */ res = (res + 1.0) * (7 + (get_show_week_numbers () ? 1 : 0)) - 1.0; if (res < MIN_SMALL_MONTH_WIDTH) res = MIN_SMALL_MONTH_WIDTH; return res; } /* print out the month small, embolden any days with events. */ static void print_month_small (GtkPrintContext *context, GnomeCalendar *gcal, time_t month, gdouble x1, gdouble y1, gdouble x2, gdouble y2, gint titleflags, time_t greystart, time_t greyend, gint bordertitle) { icaltimezone *zone; PangoFontDescription *font, *font_bold, *font_normal; ECalModel *model; time_t now, next; gint x, y; gint day; gint days[42]; GDateWeekday weekday; GDateWeekday week_start_day; gchar buf[100]; struct tm tm; gdouble font_size; gdouble header_size, col_width, row_height, text_xpad, w; gdouble cell_top, cell_bottom, cell_left, cell_right, text_right; gboolean week_numbers; cairo_t *cr; model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); week_numbers = get_show_week_numbers (); /* Print the title, e.g. 'June 2001', in the top 16% of the area. */ convert_timet_to_struct_tm (month, zone, &tm); format_date (&tm, titleflags, buf, 100); header_size = ABS (y2 - y1) * 0.16; font = get_font_for_size (header_size, PANGO_WEIGHT_BOLD); if (bordertitle) print_border (context, x1, x2, y1, y1 + header_size, 1.0, 0.9); print_text ( context, font, buf, PANGO_ALIGN_CENTER, x1, x2, y1, y1 + header_size); pango_font_description_free (font); y1 += header_size; col_width = (x2 - x1) / (7 + (week_numbers ? 1 : 0)); /* The top row with the day abbreviations gets an extra bit of * vertical space around it. */ row_height = ABS (y2 - y1) / 7.4; /* First we need to calculate a reasonable font size. We start with a * rough guess of just under the height of each row. */ font_size = row_height; /* get month days */ convert_timet_to_struct_tm (month, zone, &tm); build_month (model, tm.tm_mon, tm.tm_year + 1900, days, NULL, NULL); font_normal = get_font_for_size (font_size, PANGO_WEIGHT_NORMAL); font_bold = get_font_for_size (font_size, PANGO_WEIGHT_BOLD); /* Get a reasonable estimate of the largest number we will need, * and use it to calculate the offset from the right edge of the * cell that we should put the numbers. */ w = evo_calendar_print_renderer_get_width (context, font_bold, "23"); text_xpad = (col_width - w) / 2; cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); /* Print the abbreviated day names across the top in bold. */ week_start_day = e_cal_model_get_week_start_day (model); weekday = week_start_day; for (x = 0; x < 7; x++) { print_text ( context, font_bold, _(daynames[weekday]), PANGO_ALIGN_RIGHT, x1 + (x + (week_numbers ? 1 : 0)) * col_width, x1 + (x + 1 + (week_numbers ? 1 : 0)) * col_width, y1, y1 + row_height * 1.4); weekday = e_weekday_get_next (weekday); } y1 += row_height * 1.4; now = time_month_begin_with_zone (month, zone); for (y = 0; y < 6; y++) { cell_top = y1 + y * row_height; cell_bottom = cell_top + row_height; if (week_numbers) { cell_left = x1; /* We add a 0.05 to make sure the cells meet up with * each other. Otherwise you sometimes get lines * between them which looks bad. Maybe I'm not using * coords in the way gnome-print expects. */ cell_right = cell_left + col_width + 0.05; text_right = cell_right - text_xpad; /* last week can be empty */ for (x = 0; x < 7; x++) { day = days[y * 7 + x]; if (day != 0) break; } if (day != 0) { time_t week_begin; gint wday; wday = e_weekday_to_tm_wday (week_start_day); week_begin = time_week_begin_with_zone ( now, wday, zone); convert_timet_to_struct_tm ( week_begin, zone, &tm); /* Month in e_calendar_item_get_week_number * is also zero-based. */ sprintf ( buf, "%d", e_calendar_item_get_week_number ( NULL, tm.tm_mday, tm.tm_mon, tm.tm_year + 1900)); print_text ( context, font_normal, buf, PANGO_ALIGN_RIGHT, cell_left, text_right, cell_top, cell_bottom); } } for (x = 0; x < 7; x++) { cell_left = x1 + (x + (week_numbers ? 1 : 0)) * col_width; /* We add a 0.05 to make sure the cells meet up with * each other. Otherwise you sometimes get lines * between them which looks bad. Maybe I'm not using * coords in the way gnome-print expects. */ cell_right = cell_left + col_width + 0.05; text_right = cell_right - text_xpad; day = days[y * 7 + x]; if (day != 0) { gboolean found = FALSE; sprintf (buf, "%d", day); /* this is a slow messy way to do this ... but easy ... */ e_cal_model_generate_instances_sync ( gnome_calendar_get_model (gcal), now, time_day_end_with_zone (now, zone), instance_cb, &found); font = found ? font_bold : font_normal; next = time_add_day_with_zone (now, 1, zone); if ((now >= greystart && now < greyend) || (greystart >= now && greystart < next)) { print_border ( context, cell_left, cell_right, cell_top, cell_bottom, -1.0, 0.75); } print_text ( context, font, buf, PANGO_ALIGN_RIGHT, cell_left, text_right, cell_top, cell_bottom); now = next; } } } pango_font_description_free (font_normal); pango_font_description_free (font_bold); } /* wraps text into the print context, not taking up more than its allowed space */ static gdouble bound_text (GtkPrintContext *context, PangoFontDescription *font, const gchar *text, gint len, gdouble x1, gdouble y1, gdouble x2, gdouble y2, gboolean can_wrap, gdouble *last_page_start, gint *pages) { PangoLayout *layout; gint layout_width, layout_height; cairo_t *cr; cr = gtk_print_context_get_cairo_context (context); layout = gtk_print_context_create_pango_layout (context); pango_layout_set_font_description (layout, font); pango_layout_set_text (layout, text, len); pango_layout_set_width (layout, pango_units_from_double (x2 - x1)); if (can_wrap) pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR); pango_layout_get_size (layout, &layout_width, &layout_height); if (last_page_start && y1 + pango_units_to_double (layout_height) > y2 + (*last_page_start)) { /* draw this on new page */ if (pages) *pages = *pages + 1; *last_page_start = *last_page_start + y2; y1 = *last_page_start + 10.0; } if (!last_page_start || (y1 >= 0.0 && y1 < y2)) { cairo_save (cr); /* Set a clipping rectangle. */ cairo_move_to (cr, x1, y1); cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1); cairo_clip (cr); cairo_new_path (cr); cairo_move_to (cr, x1, y1); pango_cairo_show_layout (cr, layout); cairo_stroke (cr); cairo_restore (cr); } g_object_unref (layout); return y1 + pango_units_to_double (layout_height); } /* Draw the borders, lines, and times down the left of the day view. */ static void print_day_background (GtkPrintContext *context, GnomeCalendar *gcal, time_t whence, struct pdinfo *pdi, gdouble left, gdouble right, gdouble top, gdouble bottom) { ECalModel *model; PangoFontDescription *font_hour, *font_minute; gdouble yinc, y; gdouble width = DAY_VIEW_TIME_COLUMN_WIDTH; gdouble font_size, max_font_size, hour_font_size, minute_font_size; gchar buf[20]; const gchar *minute; gboolean use_24_hour; gint i, hour, row; gdouble hour_minute_x, hour_minute_width; cairo_t *cr; model = gnome_calendar_get_model (gcal); use_24_hour = e_cal_model_get_use_24_hour_format (model); /* Fill the time column in light-gray. */ print_border (context, left, left + width, top, bottom, -1.0, 0.9); /* Draw the border around the entire view. */ cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); print_border (context, left, right, top, bottom, 1.0, -1.0); /* Draw the vertical line on the right of the time column. */ cr = gtk_print_context_get_cairo_context (context); cairo_set_line_width (cr, 0.0); cairo_move_to (cr, left + width, bottom); cairo_line_to (cr, left + width, top); cairo_stroke (cr); /* Calculate the row height. */ if (top > bottom) yinc = (top - bottom) / (pdi->end_hour - pdi->start_hour); else yinc = (bottom - top) / (pdi->end_hour - pdi->start_hour); /* Get the 2 fonts we need. */ font_size = yinc * 0.6; max_font_size = width * 0.45; hour_font_size = MIN (font_size, max_font_size); font_hour = get_font_for_size (hour_font_size, PANGO_WEIGHT_BOLD); font_size = yinc * 0.33; max_font_size = width * 0.2; minute_font_size = MIN (font_size, max_font_size); font_minute = get_font_for_size (minute_font_size, PANGO_WEIGHT_BOLD); hour_minute_width = evo_calendar_print_renderer_get_width ( context, font_minute, use_24_hour ? "00" : _("am")); if (!use_24_hour) hour_minute_width = MAX ( hour_minute_width, evo_calendar_print_renderer_get_width ( context, font_minute, _("pm"))); row = 0; hour_minute_x = left + width - hour_minute_width - 3; for (i = pdi->start_hour; i < pdi->end_hour; i++) { y = top + yinc * (row + 1); cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); if (use_24_hour) { hour = i; minute = "00"; } else { if (i < 12) minute = _("am"); else minute = _("pm"); hour = i % 12; if (hour == 0) hour = 12; } /* the hour label/minute */ sprintf (buf, "%d", hour); print_text ( context, font_hour, buf, PANGO_ALIGN_RIGHT, left, hour_minute_x, y - yinc, y - yinc + hour_font_size); print_text ( context, font_minute, minute, PANGO_ALIGN_LEFT, hour_minute_x, left + width - 3, y - yinc, y - yinc + minute_font_size); /* Draw the horizontal line between hours, across the entire width of the day view. */ cr = gtk_print_context_get_cairo_context (context); cairo_move_to (cr, left, y); cairo_line_to (cr, right, y); cairo_set_line_width (cr, 1); cairo_stroke (cr); /* Draw the horizontal line for the 1/2-hours, across the * entire width except for part of the time column. */ cairo_move_to (cr, left + width * 0.6, y - yinc / 2); cairo_line_to (cr, right, y - yinc / 2); cairo_set_line_width (cr, 1); cairo_stroke (cr); row++; } pango_font_description_free (font_hour); pango_font_description_free (font_minute); } /* This adds one event to the view, adding it to the appropriate array. */ static gint print_day_add_event (ECalModelComponent *comp_data, time_t start, time_t end, icaltimezone *zone, gint days_shown, time_t *day_starts, GArray *long_events, GArray **events) { EDayViewEvent event; gint day, offset; struct icaltimetype start_tt, end_tt; #if 0 g_print ("Day view lower: %s", ctime (&day_starts[0])); g_print ("Day view upper: %s", ctime (&day_starts[days_shown])); g_print ("Event start: %s", ctime (&start)); g_print ("Event end : %s\n", ctime (&end)); #endif /* Check that the event times are valid. */ g_return_val_if_fail (start <= end, -1); g_return_val_if_fail (start < day_starts[days_shown], -1); g_return_val_if_fail (end > day_starts[0], -1); start_tt = icaltime_from_timet_with_zone (start, FALSE, zone); end_tt = icaltime_from_timet_with_zone (end, FALSE, zone); event.comp_data = comp_data; event.start = start; event.end = end; event.canvas_item = NULL; /* Calculate the start & end minute, relative to the top of the * display. */ /*offset = day_view->first_hour_shown * 60 + day_view->first_minute_shown;*/ offset = 0; event.start_minute = start_tt.hour * 60 + start_tt.minute - offset; event.end_minute = end_tt.hour * 60 + end_tt.minute - offset; event.start_row_or_col = 0; event.num_columns = 0; /* Find out which array to add the event to. */ for (day = 0; day < days_shown; day++) { if (start >= day_starts[day] && end <= day_starts[day + 1]) { /* Special case for when the appointment ends at * midnight, i.e. the start of the next day. */ if (end == day_starts[day + 1]) { /* If the event last the entire day, then we * skip it here so it gets added to the top * canvas. */ if (start == day_starts[day]) break; event.end_minute = 24 * 60; } g_array_append_val (events[day], event); return day; } } /* The event wasn't within one day so it must be a long event, * i.e. shown in the top canvas. */ g_array_append_val (long_events, event); return E_DAY_VIEW_LONG_EVENT; } static gboolean print_day_details_cb (ECalComponent *comp, time_t istart, time_t iend, gpointer data) { ECalModelGenerateInstancesData *mdata = (ECalModelGenerateInstancesData *) data; struct pdinfo *pdi = (struct pdinfo *) mdata->cb_data; print_day_add_event ( mdata->comp_data, istart, iend, pdi->zone, pdi->days_shown, pdi->day_starts, pdi->long_events, pdi->events); return TRUE; } static void free_event_array (GArray *array) { EDayViewEvent *event; gint event_num; for (event_num = 0; event_num < array->len; event_num++) { event = &g_array_index (array, EDayViewEvent, event_num); if (event->canvas_item) g_object_run_dispose (G_OBJECT (event->canvas_item)); } g_array_set_size (array, 0); } static const gchar * get_type_as_string (icalparameter_cutype cutype) { const gchar *res; switch (cutype) { case ICAL_CUTYPE_NONE: res = NULL; break; case ICAL_CUTYPE_INDIVIDUAL: res = _("Individual"); break; case ICAL_CUTYPE_GROUP: res = _("Group"); break; case ICAL_CUTYPE_RESOURCE: res = _("Resource"); break; case ICAL_CUTYPE_ROOM: res = _("Room"); break; default: res = _("Unknown"); break; } return res; } static const gchar * get_role_as_string (icalparameter_role role) { const gchar *res; switch (role) { case ICAL_ROLE_NONE: res = NULL; break; case ICAL_ROLE_CHAIR: res = _("Chair"); break; case ICAL_ROLE_REQPARTICIPANT: res = _("Required Participant"); break; case ICAL_ROLE_OPTPARTICIPANT: res = _("Optional Participant"); break; case ICAL_ROLE_NONPARTICIPANT: res = _("Non-Participant"); break; default: res = _("Unknown"); break; } return res; } static gdouble print_attendees (GtkPrintContext *context, PangoFontDescription *font, cairo_t *cr, gdouble left, gdouble right, gdouble top, gdouble bottom, ECalComponent *comp, gint page_nr, gint *pages) { GSList *attendees = NULL, *l; g_return_val_if_fail (context != NULL, top); g_return_val_if_fail (font != NULL, top); g_return_val_if_fail (cr != NULL, top); e_cal_component_get_attendee_list (comp, &attendees); for (l = attendees; l; l = l->next) { ECalComponentAttendee *attendee = l->data; if (attendee && attendee->value && *attendee->value) { GString *text; const gchar *tmp; tmp = get_type_as_string (attendee->cutype); text = g_string_new (tmp ? tmp : ""); if (tmp) g_string_append (text, " "); if (attendee->cn && *attendee->cn) g_string_append (text, attendee->cn); else { /* it's usually in form of "mailto:email@domain" */ tmp = strchr (attendee->value, ':'); g_string_append (text, tmp ? tmp + 1 : attendee->value); } tmp = get_role_as_string (attendee->role); if (tmp) { g_string_append (text, " ("); g_string_append (text, tmp); g_string_append (text, ")"); } if (top > bottom) { top = 10.0; cairo_show_page (cr); } top = bound_text ( context, font, text->str, -1, left + 40.0, top, right, bottom, FALSE, NULL, pages); g_string_free (text, TRUE); } } e_cal_component_free_attendee_list (attendees); return top; } static gchar * get_summary_with_location (icalcomponent *icalcomp) { const gchar *summary, *location; gchar *text; g_return_val_if_fail (icalcomp != NULL, NULL); summary = icalcomponent_get_summary (icalcomp); if (summary == NULL) summary = ""; location = icalcomponent_get_location (icalcomp); if (location && *location) { text = g_strdup_printf ("%s (%s)", summary, location); } else { text = g_strdup (summary); } return text; } static void print_day_long_event (GtkPrintContext *context, PangoFontDescription *font, gdouble left, gdouble right, gdouble top, gdouble bottom, gdouble row_height, EDayViewEvent *event, struct pdinfo *pdi, ECalModel *model) { gdouble x1, x2, y1, y2; gdouble left_triangle_width = -1.0, right_triangle_width = -1.0; gchar *text; gchar buffer[32]; struct tm date_tm; gdouble red, green, blue; if (!is_comp_data_valid (event)) return; /* If the event starts before the first day being printed, draw a * triangle. (Note that I am assuming we are just showing 1 day at * the moment.) */ if (event->start < pdi->day_starts[0]) left_triangle_width = 4; /* If the event ends after the last day being printed, draw a * triangle. */ if (event->end > pdi->day_starts[1]) right_triangle_width = 4; x1 = left + 10; x2 = right - 10; y1 = top + event->start_row_or_col * row_height + 1; y2 = y1 + row_height - 1; red = green = blue = 0.95; e_cal_model_get_rgb_color_for_component ( model, event->comp_data, &red, &green, &blue); print_border_with_triangles ( context, x1, x2, y1, y2, 0.5, red, green, blue, left_triangle_width, right_triangle_width); /* If the event starts after the first day being printed, we need to * print the start time. */ if (event->start > pdi->day_starts[0]) { date_tm.tm_year = 2001; date_tm.tm_mon = 0; date_tm.tm_mday = 1; date_tm.tm_hour = event->start_minute / 60; date_tm.tm_min = event->start_minute % 60; date_tm.tm_sec = 0; date_tm.tm_isdst = -1; e_time_format_time (&date_tm, pdi->use_24_hour_format, FALSE, buffer, sizeof (buffer)); x1 += 4; x1 += print_text (context, font, buffer, PANGO_ALIGN_LEFT, x1, x2, y1, y2); } /* If the event ends before the end of the last day being printed, * we need to print the end time. */ if (event->end < pdi->day_starts[1]) { date_tm.tm_year = 2001; date_tm.tm_mon = 0; date_tm.tm_mday = 1; date_tm.tm_hour = event->end_minute / 60; date_tm.tm_min = event->end_minute % 60; date_tm.tm_sec = 0; date_tm.tm_isdst = -1; e_time_format_time (&date_tm, pdi->use_24_hour_format, FALSE, buffer, sizeof (buffer)); x2 -= 4; x2 -= print_text (context, font, buffer, PANGO_ALIGN_RIGHT, x1, x2, y1, y2); } /* Print the text. */ text = get_summary_with_location (event->comp_data->icalcomp); x1 += 4; x2 -= 4; print_text_line (context, font, text, PANGO_ALIGN_CENTER, x1, x2, y1, y2, TRUE); g_free (text); } static void print_day_event (GtkPrintContext *context, PangoFontDescription *font, gdouble left, gdouble right, gdouble top, gdouble bottom, EDayViewEvent *event, struct pdinfo *pdi, ECalModel *model) { gdouble x1, x2, y1, y2, col_width, row_height; gint start_offset, end_offset, start_row, end_row; gchar *text, start_buffer[32], end_buffer[32]; gboolean display_times = FALSE; struct tm date_tm; gdouble red, green, blue; if (!is_comp_data_valid (event)) return; if ((event->start_minute >= pdi->end_minute_offset) || (event->end_minute <= pdi->start_minute_offset)) return; start_offset = event->start_minute - pdi->start_minute_offset; end_offset = event->end_minute - pdi->start_minute_offset; start_row = start_offset / pdi->mins_per_row; start_row = MAX (0, start_row); end_row = (end_offset - 1) / pdi->mins_per_row; end_row = MIN (pdi->rows - 1, end_row); col_width = (right - left) / pdi->cols_per_row[event->start_minute / pdi->mins_per_row]; if (start_offset != start_row * pdi->mins_per_row || end_offset != (end_row + 1) * pdi->mins_per_row) display_times = TRUE; x1 = left + event->start_row_or_col * col_width; x2 = x1 + event->num_columns * col_width - DAY_VIEW_EVENT_X_PAD; row_height = (bottom - top) / pdi->rows; y1 = top + start_row * row_height; y2 = top + (end_row + 1) * row_height; #if 0 g_print ( "Event: %g,%g %g,%g\n row_height: %g start_row: %i top: %g rows: %i\n", x1, y1, x2, y2, row_height, start_row, top, pdi->rows); #endif red = green = blue = 0.95; e_cal_model_get_rgb_color_for_component ( model, event->comp_data, &red, &green, &blue); print_border_rgb (context, x1, x2, y1, y2, 1.0, red, green, blue); text = get_summary_with_location (event->comp_data->icalcomp); if (display_times) { gchar *t = NULL; date_tm.tm_year = 2001; date_tm.tm_mon = 0; date_tm.tm_mday = 1; date_tm.tm_hour = event->start_minute / 60; date_tm.tm_min = event->start_minute % 60; date_tm.tm_sec = 0; date_tm.tm_isdst = -1; e_time_format_time (&date_tm, pdi->use_24_hour_format, FALSE, start_buffer, sizeof (start_buffer)); date_tm.tm_hour = event->end_minute / 60; date_tm.tm_min = event->end_minute % 60; e_time_format_time (&date_tm, pdi->use_24_hour_format, FALSE, end_buffer, sizeof (end_buffer)); t = text; text = g_strdup_printf ( "%s - %s %s ", start_buffer, end_buffer, text); g_free (t); } bound_text (context, font, text, -1, x1 + 2, y1, x2 - 2, y2, FALSE, NULL, NULL); g_free (text); } static void print_day_details (GtkPrintContext *context, GnomeCalendar *gcal, time_t whence, gdouble left, gdouble right, gdouble top, gdouble bottom) { ECalModel *model; icaltimezone *zone; EDayViewEvent *event; PangoFontDescription *font; time_t start, end; struct pdinfo pdi = { 0 }; gint rows_in_top_display, i, rows_with_30_mins; gdouble font_size, max_font_size; cairo_t *cr; GdkPixbuf *pixbuf = NULL; #define LONG_DAY_EVENTS_TOP_SPACING 4 #define LONG_DAY_EVENTS_BOTTOM_SPACING 2 model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); start = time_day_begin_with_zone (whence, zone); end = time_day_end_with_zone (start, zone); pdi.days_shown = 1; pdi.day_starts[0] = start; pdi.day_starts[1] = end; pdi.long_events = g_array_new (FALSE, FALSE, sizeof (EDayViewEvent)); pdi.events[0] = g_array_new (FALSE, FALSE, sizeof (EDayViewEvent)); pdi.start_hour = e_cal_model_get_work_day_start_hour (model); pdi.end_hour = e_cal_model_get_work_day_end_hour (model); if (e_cal_model_get_work_day_end_minute (model) != 0) pdi.end_hour++; pdi.mins_per_row = get_day_view_time_divisions (); pdi.rows = (pdi.end_hour - pdi.start_hour) * (60 / pdi.mins_per_row); pdi.start_minute_offset = pdi.start_hour * 60; pdi.end_minute_offset = pdi.end_hour * 60; pdi.use_24_hour_format = e_cal_model_get_use_24_hour_format (model); pdi.zone = e_cal_model_get_timezone (model); /* Get the events from the server. */ e_cal_model_generate_instances_sync (model, start, end, print_day_details_cb, &pdi); qsort ( pdi.long_events->data, pdi.long_events->len, sizeof (EDayViewEvent), e_day_view_event_sort_func); qsort ( pdi.events[0]->data, pdi.events[0]->len, sizeof (EDayViewEvent), e_day_view_event_sort_func); /* Also print events outside of work hours */ if (pdi.events[0]->len > 0) { struct icaltimetype tt; event = &g_array_index (pdi.events[0], EDayViewEvent, 0); tt = icaltime_from_timet_with_zone (event->start, FALSE, zone); if (tt.hour < pdi.start_hour) pdi.start_hour = tt.hour; pdi.start_minute_offset = pdi.start_hour * 60; event = &g_array_index (pdi.events[0], EDayViewEvent, pdi.events[0]->len - 1); tt = icaltime_from_timet_with_zone (event->end, FALSE, zone); if (tt.hour > pdi.end_hour || tt.hour == 0) { pdi.end_hour = tt.hour ? tt.hour : 24; if (tt.minute > 0) pdi.end_hour++; } pdi.end_minute_offset = pdi.end_hour * 60; pdi.rows = (pdi.end_hour - pdi.start_hour) * (60 / pdi.mins_per_row); } /* Lay them out the long events, across the top of the page. */ e_day_view_layout_long_events ( pdi.long_events, pdi.days_shown, pdi.day_starts, &rows_in_top_display); /*Print the long events. */ font = get_font_for_size (12, PANGO_WEIGHT_NORMAL); /* We always leave space for DAY_VIEW_MIN_ROWS_IN_TOP_DISPLAY in the * top display, but we may have more rows than that, in which case * the main display area will be compressed. */ /* Limit long day event to half the height of the panel */ rows_in_top_display = MIN ( MAX (rows_in_top_display, DAY_VIEW_MIN_ROWS_IN_TOP_DISPLAY), (bottom - top) * 0.5 / DAY_VIEW_ROW_HEIGHT); if (rows_in_top_display > pdi.long_events->len) rows_in_top_display = pdi.long_events->len; for (i = 0; i < rows_in_top_display && i < pdi.long_events->len; i++) { event = &g_array_index (pdi.long_events, EDayViewEvent, i); print_day_long_event ( context, font, left, right, top + LONG_DAY_EVENTS_TOP_SPACING, bottom, DAY_VIEW_ROW_HEIGHT, event, &pdi, model); } if (rows_in_top_display < pdi.long_events->len) { /* too many events */ cairo_t *cr = gtk_print_context_get_cairo_context (context); gint x, y; if (!pixbuf) { const gchar **xpm = (const gchar **) jump_xpm; /* this ugly thing is here only to get rid of compiler warning * about unused 'jump_xpm_focused' */ if (pixbuf) xpm = (const gchar **) jump_xpm_focused; pixbuf = gdk_pixbuf_new_from_xpm_data (xpm); } /* Right align - 10 comes from print_day_long_event too */ x = right - gdk_pixbuf_get_width (pixbuf) * 0.5 - 10; /* Placing '...' over the last all day event entry printed. '-1 -1' comes from print_long_day_event (top / bottom spacing in each cell) */ y = top + LONG_DAY_EVENTS_TOP_SPACING + DAY_VIEW_ROW_HEIGHT * (i - 1) + (DAY_VIEW_ROW_HEIGHT - 1 - 1) * 0.5; cairo_save (cr); cairo_scale (cr, 0.5, 0.5); gdk_cairo_set_source_pixbuf (cr, pixbuf, x * 2.0, y * 2.0); cairo_paint (cr); cairo_restore (cr); } if (!rows_in_top_display) rows_in_top_display++; /* Draw the border around the long events. */ cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); print_border ( context, left, right, top, top + rows_in_top_display * DAY_VIEW_ROW_HEIGHT + LONG_DAY_EVENTS_TOP_SPACING + LONG_DAY_EVENTS_BOTTOM_SPACING, 1.0, -1.0); /* Adjust the area containing the main display. */ top += rows_in_top_display * DAY_VIEW_ROW_HEIGHT + LONG_DAY_EVENTS_TOP_SPACING + LONG_DAY_EVENTS_BOTTOM_SPACING; /* Draw the borders, lines, and times down the left. */ print_day_background ( context, gcal, whence, &pdi, left, right, top, bottom); /* Now adjust to get rid of the time column. */ left += DAY_VIEW_TIME_COLUMN_WIDTH; /* lay out the short events, within the day. */ e_day_view_layout_day_events ( pdi.events[0], CALC_DAY_VIEW_ROWS (pdi.mins_per_row), pdi.mins_per_row, pdi.cols_per_row, -1); /* use font like with 30 minutes time division */ rows_with_30_mins = (pdi.end_hour - pdi.start_hour) * (60 / 30); /* print the short events. */ if (top > bottom) max_font_size = ((top - bottom) / rows_with_30_mins) - 4; else max_font_size = ((bottom - top) / rows_with_30_mins) - 4; font_size = MIN (DAY_NORMAL_FONT_SIZE, max_font_size); font = get_font_for_size (font_size, PANGO_WEIGHT_NORMAL); for (i = 0; i < pdi.events[0]->len; i++) { event = &g_array_index (pdi.events[0], EDayViewEvent, i); print_day_event ( context, font, left, right, top, bottom, event, &pdi, model); } /* Free everything. */ if (pixbuf) g_object_unref (pixbuf); free_event_array (pdi.long_events); pango_font_description_free (font); g_array_free (pdi.long_events, TRUE); free_event_array (pdi.events[0]); g_array_free (pdi.events[0], TRUE); } /* Returns TRUE if the event is a one-day event (i.e. not a long event). */ static gboolean print_is_one_day_week_event (EWeekViewEvent *event, EWeekViewEventSpan *span, time_t *day_starts) { if (event->start == day_starts[span->start_day] && event->end == day_starts[span->start_day + 1]) return FALSE; if (span->num_days == 1 && event->start >= day_starts[span->start_day] && event->end <= day_starts[span->start_day + 1]) return TRUE; return FALSE; } static void print_week_long_event (GtkPrintContext *context, PangoFontDescription *font, struct psinfo *psi, gdouble x1, gdouble x2, gdouble y1, gdouble row_height, EWeekViewEvent *event, EWeekViewEventSpan *span, gchar *text, gdouble red, gdouble green, gdouble blue) { gdouble left_triangle_width = -1.0, right_triangle_width = -1.0; struct tm date_tm; gchar buffer[32]; /* If the event starts before the first day of the span, draw a * triangle to indicate it continues. */ if (event->start < psi->day_starts[span->start_day]) left_triangle_width = 4; /* If the event ends after the last day of the span, draw a * triangle. */ if (event->end > psi->day_starts[span->start_day + span->num_days]) right_triangle_width = 4; print_border_with_triangles ( context, x1 + 6, x2 - 6, y1, y1 + row_height, 0.0, red, green, blue, left_triangle_width, right_triangle_width); x1 += 6; x2 -= 6; /* If the event starts after the first day being printed, we need to * print the start time. */ if (event->start > psi->day_starts[span->start_day]) { date_tm.tm_year = 2001; date_tm.tm_mon = 0; date_tm.tm_mday = 1; date_tm.tm_hour = event->start_minute / 60; date_tm.tm_min = event->start_minute % 60; date_tm.tm_sec = 0; date_tm.tm_isdst = -1; e_time_format_time (&date_tm, psi->use_24_hour_format, FALSE, buffer, sizeof (buffer)); x1 += 2; x1 += print_text_line ( context, font, buffer, PANGO_ALIGN_LEFT, x1, x2 - 2, y1, y1 + row_height, TRUE); } /* If the event ends before the end of the last day being printed, * we need to print the end time. */ if (event->end < psi->day_starts[span->start_day + span->num_days]) { date_tm.tm_year = 2001; date_tm.tm_mon = 0; date_tm.tm_mday = 1; date_tm.tm_hour = event->end_minute / 60; date_tm.tm_min = event->end_minute % 60; date_tm.tm_sec = 0; date_tm.tm_isdst = -1; e_time_format_time (&date_tm, psi->use_24_hour_format, FALSE, buffer, sizeof (buffer)); x2 -= 2; x2 -= print_text_line ( context, font, buffer, PANGO_ALIGN_RIGHT, x1 + 2, x2, y1, y1 + row_height, TRUE); } x1 += 2; x2 -= 2; print_text_line (context, font, text, PANGO_ALIGN_CENTER, x1, x2, y1, y1 + row_height, TRUE); } static void print_week_day_event (GtkPrintContext *context, PangoFontDescription *font, struct psinfo *psi, gdouble x1, gdouble x2, gdouble y1, gdouble row_height, EWeekViewEvent *event, EWeekViewEventSpan *span, gchar *text, gdouble red, gdouble green, gdouble blue) { struct tm date_tm; gchar buffer[32]; date_tm.tm_year = 2001; date_tm.tm_mon = 0; date_tm.tm_mday = 1; date_tm.tm_hour = event->start_minute / 60; date_tm.tm_min = event->start_minute % 60; date_tm.tm_sec = 0; date_tm.tm_isdst = -1; e_time_format_time (&date_tm, psi->use_24_hour_format, FALSE, buffer, sizeof (buffer)); print_rectangle (context, x1 + 1, y1, x2 - x1 - 2, row_height, red, green, blue); x1 += print_text_line ( context, font, buffer, PANGO_ALIGN_LEFT, x1 + 2, x2 - 3, y1, y1 + row_height, TRUE) + 4; if (psi->weeks_shown <= 2) { date_tm.tm_hour = event->end_minute / 60; date_tm.tm_min = event->end_minute % 60; e_time_format_time (&date_tm, psi->use_24_hour_format, FALSE, buffer, sizeof (buffer)); x1 += print_text_line ( context, font, buffer, PANGO_ALIGN_LEFT, x1, x2 - 3, y1, y1 + row_height, TRUE) + 4; } print_text_line ( context, font, text, PANGO_ALIGN_LEFT, x1, x2 - 3, y1, y1 + row_height, TRUE); } static void print_week_event (GtkPrintContext *context, PangoFontDescription *font, struct psinfo *psi, gdouble left, gdouble top, gdouble cell_width, gdouble cell_height, ECalModel *model, EWeekViewEvent *event, GArray *spans) { EWeekViewEventSpan *span; gint span_num; gchar *text; gint num_days, start_x, start_y, start_h, end_x, end_y, end_h; gdouble x1, x2, y1; gdouble red, green, blue; GdkPixbuf *pixbuf = NULL; if (!is_comp_data_valid (event)) return; text = get_summary_with_location (event->comp_data->icalcomp); for (span_num = 0; span_num < event->num_spans; span_num++) { span = &g_array_index (spans, EWeekViewEventSpan, event->spans_index + span_num); if (e_week_view_layout_get_span_position ( event, span, psi->rows_per_cell, psi->rows_per_compressed_cell, psi->display_start_weekday, psi->multi_week_view, psi->compress_weekend, &num_days)) { e_week_view_layout_get_day_position (span->start_day, psi->multi_week_view, psi->weeks_shown, psi->display_start_weekday, psi->compress_weekend, &start_x, &start_y, &start_h); if (num_days == 1) { end_x = start_x; end_y = start_y; end_h = start_h; } else { e_week_view_layout_get_day_position (span->start_day + num_days - 1, psi->multi_week_view, psi->weeks_shown, psi->display_start_weekday, psi->compress_weekend, &end_x, &end_y, &end_h); } x1 = left + start_x * cell_width; x2 = left + (end_x + 1) * cell_width; y1 = top + start_y * cell_height + psi->header_row_height + span->row * (psi->row_height + 2); red = .9; green = .9; blue = .9; e_cal_model_get_rgb_color_for_component ( model, event->comp_data, &red, &green, &blue); if (print_is_one_day_week_event (event, span, psi->day_starts)) { print_week_day_event ( context, font, psi, x1, x2, y1, psi->row_height, event, span, text, red, green, blue); } else { print_week_long_event ( context, font, psi, x1, x2, y1, psi->row_height, event, span, text, red, green, blue); } } else { cairo_t *cr = gtk_print_context_get_cairo_context (context); e_week_view_layout_get_day_position ( span->start_day, psi->multi_week_view, psi->weeks_shown, psi->display_start_weekday, psi->compress_weekend, &start_x, &start_y, &start_h); y1 = top + start_y * cell_height + psi->header_row_height + psi->rows_per_cell * (psi->row_height + 2); if (span->row >= psi->rows_per_compressed_cell && psi->compress_weekend) { GDateWeekday end_weekday; gboolean end_on_weekend; end_weekday = e_weekday_add_days ( psi->display_start_weekday, span->start_day); end_on_weekend = (end_weekday == G_DATE_SATURDAY) || (end_weekday == G_DATE_SUNDAY); if (end_on_weekend) { y1 = top + start_y * cell_height + psi->header_row_height + psi->rows_per_compressed_cell * (psi->row_height + 2); } } if (!pixbuf) { const gchar **xpm = (const gchar **) jump_xpm; /* this ugly thing is here only to get rid of compiler warning * about unused 'jump_xpm_focused' */ if (pixbuf) xpm = (const gchar **) jump_xpm_focused; pixbuf = gdk_pixbuf_new_from_xpm_data (xpm); } x1 = left + (start_x + 1) * cell_width - 6 - gdk_pixbuf_get_width (pixbuf) * 0.5; cairo_save (cr); cairo_scale (cr, 0.5, 0.5); gdk_cairo_set_source_pixbuf (cr, pixbuf, x1 * 2.0, y1 * 2.0); cairo_paint (cr); cairo_restore (cr); } } if (pixbuf) g_object_unref (pixbuf); g_free (text); } static void print_week_view_background (GtkPrintContext *context, PangoFontDescription *font, struct psinfo *psi, gdouble left, gdouble top, gdouble cell_width, gdouble cell_height) { struct tm tm; gint day, day_x, day_y, day_h; gdouble x1, x2, y1, y2, font_size, fillcolor; const gchar *format_string; gchar buffer[128]; cairo_t *cr; font_size = get_font_size (font); for (day = 0; day < psi->days_shown; day++) { e_week_view_layout_get_day_position (day, psi->multi_week_view, psi->weeks_shown, psi->display_start_weekday, psi->compress_weekend, &day_x, &day_y, &day_h); x1 = left + day_x * cell_width; x2 = left + (day_x + 1) * cell_width; y1 = top + day_y * cell_height; y2 = y1 + day_h * cell_height; convert_timet_to_struct_tm (psi->day_starts[day], psi->zone, &tm); /* In the month view we draw a grey background for the end * of the previous month and the start of the following. */ fillcolor = -1.0; if (psi->multi_week_view && (tm.tm_mon != psi->month)) fillcolor = 0.9; print_border (context, x1, x2, y1, y2, 1.0, fillcolor); if (psi->multi_week_view) { if (tm.tm_mday == 1) format_string = _("%d %B"); else format_string = "%d"; } else { cr = gtk_print_context_get_cairo_context (context); cairo_move_to ( cr, x1 + 0.1 * cell_width, y1 + psi->header_row_height - 4); cairo_line_to ( cr, x2, y1 + psi->header_row_height - 4); cairo_set_source_rgb (cr, 0, 0, 0); cairo_set_line_width (cr, 0.5); cairo_stroke (cr); /* strftime format %A = full weekday name, %d = day of * month, %B = full month name. You can change the * order but don't change the specifiers or add * anything. */ format_string = _("%A %d %B"); } e_utf8_strftime (buffer, sizeof (buffer), format_string, &tm); print_text_line ( context, font, buffer, PANGO_ALIGN_RIGHT, x1, x2 - 4, y1 + 2, y1 + 2 + font_size, TRUE); } } /* This adds one event to the view, adding it to the appropriate array. */ static gboolean print_week_summary_cb (ECalComponent *comp, time_t start, time_t end, gpointer data) { EWeekViewEvent event; struct icaltimetype start_tt, end_tt; ECalModelGenerateInstancesData *mdata = (ECalModelGenerateInstancesData *) data; struct psinfo *psi = (struct psinfo *) mdata->cb_data; /* Check that the event times are valid. */ #if 0 g_print ( "View start:%li end:%li Event start:%li end:%li\n", psi->day_starts[0], psi->day_starts[psi->days_shown], start, end); #endif g_return_val_if_fail (start <= end, TRUE); g_return_val_if_fail (start < psi->day_starts[psi->days_shown], TRUE); g_return_val_if_fail (end > psi->day_starts[0], TRUE); start_tt = icaltime_from_timet_with_zone (start, FALSE, psi->zone); end_tt = icaltime_from_timet_with_zone (end, FALSE, psi->zone); event.comp_data = g_object_ref (mdata->comp_data); event.start = start; event.end = end; event.spans_index = 0; event.num_spans = 0; event.start_minute = start_tt.hour * 60 + start_tt.minute; event.end_minute = end_tt.hour * 60 + end_tt.minute; if (event.end_minute == 0 && start != end) event.end_minute = 24 * 60; g_array_append_val (psi->events, event); return TRUE; } static void print_week_summary (GtkPrintContext *context, GnomeCalendar *gcal, time_t whence, gboolean multi_week_view, gint weeks_shown, gint month, gdouble font_size, gdouble font_size_background, gdouble left, gdouble right, gdouble top, gdouble bottom) { icaltimezone *zone; EWeekViewEvent *event; struct psinfo psi = { 0 }; time_t day_start; gint rows_per_day[E_WEEK_VIEW_MAX_WEEKS * 7], day, event_num; GArray *spans; PangoFontDescription *font, *font_background; gdouble cell_width, cell_height; ECalModel *model; model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); psi.days_shown = weeks_shown * 7; psi.events = g_array_new (FALSE, FALSE, sizeof (EWeekViewEvent)); psi.multi_week_view = multi_week_view; psi.weeks_shown = weeks_shown; psi.month = month; psi.zone = zone; /* Get a few config settings. */ if (multi_week_view) psi.compress_weekend = e_cal_model_get_compress_weekend (model); else psi.compress_weekend = TRUE; psi.use_24_hour_format = e_cal_model_get_use_24_hour_format (model); psi.display_start_weekday = e_cal_model_get_week_start_day (model); /* If weekends are compressed then we can't start on a Sunday. */ if (psi.compress_weekend && psi.display_start_weekday == G_DATE_SUNDAY) psi.display_start_weekday = G_DATE_SATURDAY; day_start = time_day_begin_with_zone (whence, zone); for (day = 0; day <= psi.days_shown; day++) { psi.day_starts[day] = day_start; day_start = time_add_day_with_zone (day_start, 1, zone); } /* Get the events from the server. */ e_cal_model_generate_instances_sync ( model, psi.day_starts[0], psi.day_starts[psi.days_shown], print_week_summary_cb, &psi); qsort ( psi.events->data, psi.events->len, sizeof (EWeekViewEvent), e_week_view_event_sort_func); /* Layout the events. */ spans = e_week_view_layout_events ( psi.events, NULL, psi.multi_week_view, psi.weeks_shown, psi.compress_weekend, psi.display_start_weekday, psi.day_starts, rows_per_day); /* Calculate the size of the cells. */ if (multi_week_view) { cell_width = (right - left) / (psi.compress_weekend ? 6 : 7); cell_height = (bottom - top) / (weeks_shown * 2); } else { cell_width = (right - left) / 2; cell_height = (bottom - top) / 6; } /* Calculate the row height, using the normal font and with room for * space or a rectangle around it. */ psi.row_height = font_size * 1.2; psi.header_row_height = font_size * 1.5; /* Calculate how many rows we can fit into each type of cell. */ psi.rows_per_cell = ((cell_height * 2) - psi.header_row_height) / (psi.row_height + 2); psi.rows_per_compressed_cell = (cell_height - psi.header_row_height) / (psi.row_height + 2); /* Draw the grid and the day names/numbers. */ font_background = get_font_for_size (font_size_background, PANGO_WEIGHT_NORMAL); print_week_view_background ( context, font_background, &psi, left, top, cell_width, cell_height); pango_font_description_free (font_background); /* Print the events. */ font = get_font_for_size (font_size, PANGO_WEIGHT_NORMAL); for (event_num = 0; event_num < psi.events->len; event_num++) { event = &g_array_index (psi.events, EWeekViewEvent, event_num); print_week_event ( context, font, &psi, left, top, cell_width, cell_height, model, event, spans); } pango_font_description_free (font); /* Free everything. */ for (event_num = 0; event_num < psi.events->len; event_num++) { event = &g_array_index (psi.events, EWeekViewEvent, event_num); g_object_unref (event->comp_data); } g_array_free (psi.events, TRUE); g_array_free (spans, TRUE); } static void print_month_summary (GtkPrintContext *context, GnomeCalendar *gcal, time_t whence, gdouble left, gdouble right, gdouble top, gdouble bottom) { icaltimezone *zone; time_t date; struct tm tm; struct icaltimetype tt; gchar buffer[100]; ECalModel *model; PangoFontDescription *font; gboolean compress_weekend; gint columns, col, month, weeks; GDateWeekday weekday; gint wday; gdouble font_size, cell_width, x1, x2, y1, y2; model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); weekday = e_cal_model_get_week_start_day (model); compress_weekend = e_cal_model_get_compress_weekend (model); date = 0; weeks = 6; if (gnome_calendar_get_view (gcal) == GNOME_CAL_MONTH_VIEW) { GnomeCalendarViewType view_type; ECalendarView *calendar_view; EWeekView *week_view; GDate first_day_shown; gboolean multi_week_view; gint weeks_shown; view_type = gnome_calendar_get_view (gcal); calendar_view = gnome_calendar_get_calendar_view (gcal, view_type); week_view = E_WEEK_VIEW (calendar_view); weeks_shown = e_week_view_get_weeks_shown (week_view); multi_week_view = e_week_view_get_multi_week_view (week_view); e_week_view_get_first_day_shown (week_view, &first_day_shown); if (multi_week_view && !(weeks_shown >= 4 && g_date_valid (&first_day_shown))) { weeks = weeks_shown; date = whence; } } /* Remember which month we want. */ tt = icaltime_from_timet_with_zone (whence, FALSE, zone); month = tt.month - 1; /* Find the start of the month, and then the start of the week on * or before that day. */ if (!date) date = time_month_begin_with_zone (whence, zone); wday = e_weekday_to_tm_wday (weekday); date = time_week_begin_with_zone (date, wday, zone); /* If weekends are compressed then we can't start on a Sunday. */ if (compress_weekend && weekday == G_DATE_SUNDAY) date = time_add_day_with_zone (date, -1, zone); /* do day names ... */ /* We are only interested in outputting the weekday here, but we want * to be able to step through the week without worrying about * overflows making strftime choke, so we move near to the start of * the month. */ convert_timet_to_struct_tm (date, zone, &tm); tm.tm_mday = (tm.tm_mday % 7) + 7; font = get_font_for_size (MONTH_NORMAL_FONT_SIZE, PANGO_WEIGHT_BOLD); font_size = get_font_size (font); columns = compress_weekend ? 6 : 7; cell_width = (right - left) / columns; y1 = top; y2 = top + font_size * 1.5; for (col = 0; col < columns; col++) { if (tm.tm_wday == 6 && compress_weekend) g_snprintf ( buffer, sizeof (buffer), "%s/%s", e_get_weekday_name (G_DATE_SATURDAY, TRUE), e_get_weekday_name (G_DATE_SUNDAY, TRUE)); else g_snprintf ( buffer, sizeof (buffer), "%s", e_get_weekday_name ( tm.tm_wday ? tm.tm_wday : 7, FALSE)); x1 = left + cell_width * col; x2 = x1 + cell_width; print_border (context, x1, x2, y1, y2, 1.0, -1.0); print_text_line (context, font, buffer, PANGO_ALIGN_CENTER, x1, x2, y1, y2, TRUE); tm.tm_mday++; tm.tm_wday = (tm.tm_wday + 1) % 7; } pango_font_description_free (font); top = y2; print_week_summary ( context, gcal, date, TRUE, weeks, month, MONTH_NORMAL_FONT_SIZE, MONTH_NORMAL_FONT_SIZE, left, right, top, bottom); } static void print_todo_details (GtkPrintContext *context, GnomeCalendar *gcal, time_t start, time_t end, gdouble left, gdouble right, gdouble top, gdouble bottom) { PangoFontDescription *font_summary; gdouble y, yend, x, xend; struct icaltimetype *tt; GtkWidget *task_table; ETable *table; ECalModel *model; gint rows, row; cairo_t *cr; /* We get the tasks directly from the TaskPad ETable. This means we * get them filtered & sorted for free. */ task_table = gnome_calendar_get_task_table (gcal); table = E_TABLE (task_table); g_return_if_fail (table != NULL); model = e_task_table_get_model (E_TASK_TABLE (task_table)); font_summary = get_font_for_size (12, PANGO_WEIGHT_NORMAL); cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); cairo_set_line_width (cr, 0.0); top +=2; titled_box ( context, _("Tasks"), font_summary, PANGO_ALIGN_CENTER, &left, &top, &right, &bottom, 1.0); y = top; yend = bottom - 2; rows = e_table_model_row_count (E_TABLE_MODEL (model)); for (row = 0; row < rows; row++) { ECalModelComponent *comp_data; ECalComponent *comp; ECalComponentText summary; gint model_row; model_row = e_table_view_to_model_row (table, row); comp_data = e_cal_model_get_component_at (model, model_row); if (!comp_data) continue; comp = e_cal_component_new (); e_cal_component_set_icalcomponent ( comp, icalcomponent_new_clone (comp_data->icalcomp)); e_cal_component_get_summary (comp, &summary); if (!summary.value) { g_object_unref (comp); continue; } x = left; xend = right - 2; if (y > bottom) { g_object_unref (comp); break; } /* Print the box to put the tick in. */ print_border (context, x + 2, x + 8, y + 6, y + 15, 0.1, -1.0); /* If the task is complete, print a tick in the box. */ e_cal_component_get_completed (comp, &tt); if (tt) { e_cal_component_free_icaltimetype (tt); cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); cairo_move_to (cr, x + 3, y + 11); cairo_line_to (cr, x + 5, y + 14); cairo_line_to (cr, x + 7, y + 5.5); cairo_set_line_width (cr, 1); cairo_stroke (cr); } y = bound_text ( context, font_summary, summary.value, -1, x + 14, y + 4, xend, yend, FALSE, NULL, NULL); y += get_font_size (font_summary) - 5; cr = gtk_print_context_get_cairo_context (context); cairo_move_to (cr, x, y); cairo_line_to (cr, xend, y); cairo_set_line_width (cr, 1); cairo_stroke (cr); g_object_unref (comp); } pango_font_description_free (font_summary); } static void print_day_view (GtkPrintContext *context, GnomeCalendar *gcal, time_t date) { ECalModel *model; GtkPageSetup *setup; icaltimezone *zone; gint i, days = 1; gdouble todo, l, week_numbers_inc, small_month_width; gchar buf[100]; gdouble width, height; struct tm tm; model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); setup = gtk_print_context_get_page_setup (context); width = gtk_page_setup_get_page_width (setup, GTK_UNIT_POINTS); height = gtk_page_setup_get_page_height (setup, GTK_UNIT_POINTS); small_month_width = calc_small_month_width (context, HEADER_HEIGHT); week_numbers_inc = get_show_week_numbers () ? small_month_width / 7.0 : 0; for (i = 0; i < days; i++) { todo = width * 0.75; /* Print the main view with all the events in. */ print_day_details ( context, gcal, date, 0.0, todo - 2.0, HEADER_HEIGHT + 4, height); /* Print the TaskPad down the right. */ print_todo_details ( context, gcal, 0, INT_MAX, todo, width, HEADER_HEIGHT + 4, height); /* Print the filled border around the header. */ print_border ( context, 0.0, width, 0.0, HEADER_HEIGHT + 4, 1.0, 0.9); /* Print the 2 mini calendar-months. */ l = width - SMALL_MONTH_PAD - (small_month_width + week_numbers_inc) * 2 - SMALL_MONTH_SPACING; print_month_small ( context, gcal, date, l, 2, l + small_month_width + week_numbers_inc, HEADER_HEIGHT + 2, DATE_MONTH | DATE_YEAR, date, date, FALSE); l += SMALL_MONTH_SPACING + small_month_width + week_numbers_inc; print_month_small ( context, gcal, time_add_month_with_zone (date, 1, zone), l, 2, l + small_month_width + week_numbers_inc, HEADER_HEIGHT + 2, DATE_MONTH | DATE_YEAR, 0, 0, FALSE); /* Print the date, e.g. '8th May, 2001'. */ convert_timet_to_struct_tm (date, zone, &tm); format_date (&tm, DATE_DAY | DATE_MONTH | DATE_YEAR, buf, 100); print_text_size_bold ( context, buf, PANGO_ALIGN_LEFT, 4, todo, 4, 4 + 24); /* Print the day, e.g. 'Tuesday'. */ format_date (&tm, DATE_DAYNAME, buf, 100); print_text_size_bold ( context, buf, PANGO_ALIGN_LEFT, 4, todo, HEADER_HEIGHT + 9, HEADER_HEIGHT + 9 + 18); date = time_add_day_with_zone (date, 1, zone); } } static void print_work_week_background (GtkPrintContext *context, GnomeCalendar *gcal, time_t whence, struct pdinfo *pdi, gdouble left, gdouble right, gdouble top, gdouble bottom) { ECalModel *model; PangoFontDescription *font_hour, *font_minute; gdouble yinc, y; gdouble width = DAY_VIEW_TIME_COLUMN_WIDTH; gdouble day_width; gdouble font_size, max_font_size, hour_font_size, minute_font_size; gchar buf[20]; const gchar *minute; const gint LONG_EVENT_OFFSET = 6; gboolean use_24_hour; gint i, hour, row; gdouble hour_minute_xl, hour_minute_xr; cairo_t *cr; model = gnome_calendar_get_model (gcal); use_24_hour = e_cal_model_get_use_24_hour_format (model); /* Fill the left time column in light-gray. */ print_border (context, left, left + width, top, bottom, -1.0, 0.9); /* Fill the right time column in light-gray */ print_border (context, right - width, right, top, bottom, -1.0, 0.9); /* Draw the border around the entire view. */ cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); print_border (context, left, right, top, bottom, 1.0, -1.0); /* Draw the vertical line on the right of the time column. */ cr = gtk_print_context_get_cairo_context (context); cairo_set_line_width (cr, 0.0); cairo_move_to (cr, left + width, bottom); cairo_line_to (cr, left + width, top); cairo_stroke (cr); cairo_move_to (cr, right - width, bottom); cairo_line_to (cr, right - width, top); cairo_stroke (cr); /* Calculate the row height. */ if (top > bottom) yinc = (top - bottom) / (pdi->end_hour - pdi->start_hour); else yinc = (bottom - top) / (pdi->end_hour - pdi->start_hour); /* Get the 2 fonts we need. */ font_size = yinc * 0.6; max_font_size = width * 0.45; hour_font_size = MIN (font_size, max_font_size); font_hour = get_font_for_size (hour_font_size, PANGO_WEIGHT_BOLD); font_size = yinc * 0.33; max_font_size = width * 0.2; minute_font_size = MIN (font_size, max_font_size); font_minute = get_font_for_size (minute_font_size, PANGO_WEIGHT_BOLD); hour_minute_xr = evo_calendar_print_renderer_get_width ( context, font_minute, use_24_hour ? "00" : _("am")); if (!use_24_hour) hour_minute_xr = MAX ( hour_minute_xr, evo_calendar_print_renderer_get_width ( context, font_minute, _("pm"))); row = 0; hour_minute_xl = left + width - hour_minute_xr - 3; hour_minute_xr = right - hour_minute_xr - 3; for (i = pdi->start_hour; i < pdi->end_hour; i++) { y = top + yinc * (row + 1); cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); if (use_24_hour) { hour = i; minute = "00"; } else { if (i < 12) minute = _("am"); else minute = _("pm"); hour = i % 12; if (hour == 0) hour = 12; } /* the hour label/minute */ sprintf (buf, "%d", hour); print_text ( context, font_hour, buf, PANGO_ALIGN_RIGHT, left, hour_minute_xl, y - yinc, y - yinc + hour_font_size); print_text ( context, font_minute, minute, PANGO_ALIGN_LEFT, hour_minute_xl, left + width - 3, y - yinc, y - yinc + minute_font_size); /* To the right */ print_text ( context, font_hour, buf, PANGO_ALIGN_RIGHT, right - width, hour_minute_xr, y - yinc, y - yinc + hour_font_size); print_text ( context, font_minute, minute, PANGO_ALIGN_LEFT, hour_minute_xr, right - 3, y - yinc, y - yinc + minute_font_size); /* Draw the horizontal line between hours, across the entire width of the day view. */ cr = gtk_print_context_get_cairo_context (context); cairo_move_to (cr, left, y); cairo_line_to (cr, right, y); cairo_set_line_width (cr, 1); cairo_stroke (cr); /* Draw the horizontal line for the 1/2-hours, across the * entire width except for part of the time column. */ cairo_move_to (cr, left + width * 0.6, y - yinc / 2); cairo_line_to (cr, right, y - yinc / 2); cairo_set_line_width (cr, 1); cairo_stroke (cr); row++; } /* Draw the vertical lines for the days */ day_width = (right - left - 2 *width) / pdi->days_shown; for (i = 0; i < pdi->days_shown - 1; ++i) { cr = gtk_print_context_get_cairo_context (context); cairo_move_to (cr, left + width + day_width * (i + 1), top); cairo_line_to (cr, left + width + day_width * (i + 1), bottom); cairo_set_line_width (cr, 1); cairo_stroke (cr); } /* And now the ones from the border to the hours, looks weird otherwise */ cr = gtk_print_context_get_cairo_context (context); cairo_move_to (cr, left, HEADER_HEIGHT); cairo_line_to (cr, left, HEADER_HEIGHT + DAY_VIEW_ROW_HEIGHT + LONG_EVENT_OFFSET); cairo_move_to (cr, right, HEADER_HEIGHT); cairo_line_to (cr, right, HEADER_HEIGHT + DAY_VIEW_ROW_HEIGHT + LONG_EVENT_OFFSET); cairo_stroke (cr); pango_font_description_free (font_hour); pango_font_description_free (font_minute); } static void print_work_week_day_details (GtkPrintContext *context, GnomeCalendar *gcal, time_t whence, gdouble left, gdouble right, gdouble top, gdouble bottom, struct pdinfo *_pdi) { ECalModel *model; icaltimezone *zone; EDayViewEvent *event; PangoFontDescription *font; time_t start, end; struct pdinfo pdi = { 0 }; gint rows_in_top_display, i, rows_with_30_mins; gdouble font_size, max_font_size; cairo_t *cr; GdkPixbuf *pixbuf = NULL; #define LONG_DAY_EVENTS_TOP_SPACING 4 #define LONG_DAY_EVENTS_BOTTOM_SPACING 2 model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); start = time_day_begin_with_zone (whence, zone); end = time_day_end_with_zone (start, zone); pdi.days_shown = 1; pdi.day_starts[0] = start; pdi.day_starts[1] = end; pdi.long_events = g_array_new (FALSE, FALSE, sizeof (EDayViewEvent)); pdi.events[0] = g_array_new (FALSE, FALSE, sizeof (EDayViewEvent)); pdi.start_hour = e_cal_model_get_work_day_start_hour (model); pdi.end_hour = e_cal_model_get_work_day_end_hour (model); if (e_cal_model_get_work_day_end_minute (model) != 0) pdi.end_hour++; pdi.mins_per_row = get_day_view_time_divisions (); pdi.rows = (pdi.end_hour - pdi.start_hour) * (60 / pdi.mins_per_row); pdi.start_minute_offset = pdi.start_hour * 60; pdi.end_minute_offset = pdi.end_hour * 60; pdi.use_24_hour_format = e_cal_model_get_use_24_hour_format (model); pdi.zone = e_cal_model_get_timezone (model); /* Get the events from the server. */ e_cal_model_generate_instances_sync (model, start, end, print_day_details_cb, &pdi); qsort ( pdi.long_events->data, pdi.long_events->len, sizeof (EDayViewEvent), e_day_view_event_sort_func); qsort ( pdi.events[0]->data, pdi.events[0]->len, sizeof (EDayViewEvent), e_day_view_event_sort_func); pdi.start_hour = MIN (pdi.start_hour, _pdi->start_hour); pdi.end_hour = MAX (pdi.end_hour, _pdi->end_hour); /* TODO: This should be redundant */ /* Also print events outside of work hours */ if (pdi.events[0]->len > 0) { struct icaltimetype tt; event = &g_array_index (pdi.events[0], EDayViewEvent, 0); tt = icaltime_from_timet_with_zone (event->start, FALSE, zone); if (tt.hour < pdi.start_hour) pdi.start_hour = tt.hour; pdi.start_minute_offset = pdi.start_hour * 60; event = &g_array_index (pdi.events[0], EDayViewEvent, pdi.events[0]->len - 1); tt = icaltime_from_timet_with_zone (event->end, FALSE, zone); if (tt.hour > pdi.end_hour || tt.hour == 0) { pdi.end_hour = tt.hour ? tt.hour : 24; if (tt.minute > 0) pdi.end_hour++; } pdi.end_minute_offset = pdi.end_hour * 60; pdi.rows = (pdi.end_hour - pdi.start_hour) * (60 / pdi.mins_per_row); } /* Lay them out the long events, across the top of the page. */ e_day_view_layout_long_events ( pdi.long_events, pdi.days_shown, pdi.day_starts, &rows_in_top_display); /*Print the long events. */ font = get_font_for_size (12, PANGO_WEIGHT_NORMAL); /* We always leave space for DAY_VIEW_MIN_ROWS_IN_TOP_DISPLAY in the * top display, but we may have more rows than that, in which case * the main display area will be compressed. */ /* Limit long day event to half the height of the panel */ rows_in_top_display = MIN ( MAX (rows_in_top_display, DAY_VIEW_MIN_ROWS_IN_TOP_DISPLAY), (bottom - top) * 0.5 / DAY_VIEW_ROW_HEIGHT); if (rows_in_top_display > pdi.long_events->len) rows_in_top_display = pdi.long_events->len; for (i = 0; i < rows_in_top_display && i < pdi.long_events->len; i++) { event = &g_array_index (pdi.long_events, EDayViewEvent, i); print_day_long_event ( context, font, left, right, top + LONG_DAY_EVENTS_TOP_SPACING, bottom, DAY_VIEW_ROW_HEIGHT, event, &pdi, model); } if (rows_in_top_display < pdi.long_events->len) { /* too many events */ cairo_t *cr = gtk_print_context_get_cairo_context (context); gint x, y; if (!pixbuf) { const gchar **xpm = (const gchar **) jump_xpm; /* this ugly thing is here only to get rid of compiler warning * about unused 'jump_xpm_focused' */ if (pixbuf) xpm = (const gchar **) jump_xpm_focused; pixbuf = gdk_pixbuf_new_from_xpm_data (xpm); } /* Right align - 10 comes from print_day_long_event too */ x = right - gdk_pixbuf_get_width (pixbuf) * 0.5 - 10; /* Placing '...' over the last all day event entry printed. '-1 -1' comes from print_long_day_event (top / bottom spacing in each cell) */ y = top + LONG_DAY_EVENTS_TOP_SPACING + DAY_VIEW_ROW_HEIGHT * (i - 1) + (DAY_VIEW_ROW_HEIGHT - 1 - 1) * 0.5; cairo_save (cr); cairo_scale (cr, 0.5, 0.5); gdk_cairo_set_source_pixbuf (cr, pixbuf, x * 2.0, y * 2.0); cairo_paint (cr); cairo_restore (cr); } if (!rows_in_top_display) rows_in_top_display++; /* Draw the border around the long events. */ cr = gtk_print_context_get_cairo_context (context); cairo_set_source_rgb (cr, 0, 0, 0); print_border ( context, left, right, top, top + rows_in_top_display * DAY_VIEW_ROW_HEIGHT + LONG_DAY_EVENTS_TOP_SPACING + LONG_DAY_EVENTS_BOTTOM_SPACING, 1.0, -1.0); /* Adjust the area containing the main display. */ top += rows_in_top_display * DAY_VIEW_ROW_HEIGHT + LONG_DAY_EVENTS_TOP_SPACING + LONG_DAY_EVENTS_BOTTOM_SPACING; /* lay out the short events, within the day. */ e_day_view_layout_day_events ( pdi.events[0], CALC_DAY_VIEW_ROWS (pdi.mins_per_row), pdi.mins_per_row, pdi.cols_per_row, -1); /* use font like with 30 minutes time division */ rows_with_30_mins = (pdi.end_hour - pdi.start_hour) * (60 / 30); /* print the short events. */ if (top > bottom) max_font_size = ((top - bottom) / rows_with_30_mins) - 4; else max_font_size = ((bottom - top) / rows_with_30_mins) - 4; font_size = MIN (DAY_NORMAL_FONT_SIZE, max_font_size); font = get_font_for_size (font_size, PANGO_WEIGHT_NORMAL); for (i = 0; i < pdi.events[0]->len; i++) { event = &g_array_index (pdi.events[0], EDayViewEvent, i); print_day_event ( context, font, left, right, top, bottom, event, &pdi, model); } /* Free everything. */ if (pixbuf) g_object_unref (pixbuf); free_event_array (pdi.long_events); pango_font_description_free (font); g_array_free (pdi.long_events, TRUE); free_event_array (pdi.events[0]); g_array_free (pdi.events[0], TRUE); } /* Figure out what the overal hour limits are */ static gboolean print_work_week_view_cb (ECalComponent *comp, time_t istart, time_t iend, gpointer data) { ECalModelGenerateInstancesData *mdata = (ECalModelGenerateInstancesData *) data; struct pdinfo *pdi = (struct pdinfo *) mdata->cb_data; struct icaltimetype tt; tt = icaltime_from_timet_with_zone (istart, FALSE, pdi->zone); pdi->start_hour = MIN (pdi->start_hour, tt.hour); tt = icaltime_from_timet_with_zone (iend, FALSE, pdi->zone); /* If we're past the hour, use the next one */ pdi->end_hour = MAX (pdi->end_hour, tt.minute ? tt.hour + 1 : tt.hour); return TRUE; } static void print_work_week_view (GtkPrintContext *context, GnomeCalendar *gcal, time_t date) { GtkPageSetup *setup; icaltimezone *zone; time_t when, start, end; gdouble width, height, l; gdouble small_month_width; gdouble weeknum_inc; gint i, days = 5; gchar buf[100]; const gint LONG_EVENT_OFFSET = 6; struct pdinfo pdi = { 0 }; struct tm tm; gdouble day_width, day_x; ECalModel *model; model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); setup = gtk_print_context_get_page_setup (context); width = gtk_page_setup_get_page_width (setup, GTK_UNIT_POINTS); height = gtk_page_setup_get_page_height (setup, GTK_UNIT_POINTS); small_month_width = calc_small_month_width (context, HEADER_HEIGHT); weeknum_inc = get_show_week_numbers () ? small_month_width / 7.0 : 0; /* We always start on a Monday */ start = time_week_begin_with_zone (date, 1, zone); end = time_add_day_with_zone (start, days, zone); pdi.days_shown = days; pdi.start_hour = e_cal_model_get_work_day_start_hour (model); pdi.end_hour = e_cal_model_get_work_day_end_hour (model); pdi.zone = zone; e_cal_model_generate_instances_sync (model, start, end, print_work_week_view_cb, &pdi); print_work_week_background ( context, gcal, date, &pdi, 0.0, width, HEADER_HEIGHT + DAY_VIEW_ROW_HEIGHT + LONG_EVENT_OFFSET, height); print_border (context, 0.0, width, 0.0, HEADER_HEIGHT, 1.0, 0.9); /* Print the 2 mini calendar-months. */ l = width - SMALL_MONTH_PAD - (small_month_width + weeknum_inc) * 2 - SMALL_MONTH_SPACING; print_month_small ( context, gcal, start, l, 4, l + small_month_width + weeknum_inc, HEADER_HEIGHT + 4, DATE_MONTH | DATE_YEAR, start, end, FALSE); l += SMALL_MONTH_SPACING + small_month_width + weeknum_inc; print_month_small ( context, gcal, time_add_month_with_zone (start, 1, zone), l, 4, l + small_month_width + weeknum_inc, HEADER_HEIGHT + 4, DATE_MONTH | DATE_YEAR, 0, 0, FALSE); /* Print the start day of the week, e.g. '7th May 2001'. */ convert_timet_to_struct_tm (start, zone, &tm); format_date (&tm, DATE_DAY | DATE_MONTH | DATE_YEAR, buf, 100); print_text_size_bold ( context, buf, PANGO_ALIGN_LEFT, 3, width, 4, 4 + 24); /* Print the end day of the week, e.g. '13th May 2001'. */ /* We need to substract one or the wrong day will be printed */ convert_timet_to_struct_tm ( time_add_day_with_zone (end, -1, zone), zone, &tm); format_date (&tm, DATE_DAY | DATE_MONTH | DATE_YEAR, buf, 100); print_text_size_bold ( context, buf, PANGO_ALIGN_LEFT, 3, width, 24 + 3, 24 + 3 + 24); /* Now print each days' events */ day_width = (width - 2 *DAY_VIEW_TIME_COLUMN_WIDTH) / days; when = start; for (i = 0; i < days; ++i) { day_x = DAY_VIEW_TIME_COLUMN_WIDTH + day_width * i; /* Print the day, e.g. 'Tuesday'. */ convert_timet_to_struct_tm (when, zone, &tm); format_date (&tm, DATE_DAYNAME, buf, 100); print_text_size_bold ( context, buf, PANGO_ALIGN_LEFT, day_x + 4, day_x + day_width, HEADER_HEIGHT + 4, HEADER_HEIGHT + 4 + 18); print_work_week_day_details ( context, gcal, when, day_x, day_x + day_width, HEADER_HEIGHT, height, &pdi); when = time_add_day_with_zone (when, 1, zone); } } static void print_week_view (GtkPrintContext *context, GnomeCalendar *gcal, time_t date) { GtkPageSetup *setup; ECalModel *model; icaltimezone *zone; gdouble l, week_numbers_inc, small_month_width; gchar buf[100]; time_t when; GDateWeekday week_start_day; gint wday; struct tm tm; gdouble width, height; setup = gtk_print_context_get_page_setup (context); width = gtk_page_setup_get_page_width (setup, GTK_UNIT_POINTS); height = gtk_page_setup_get_page_height (setup, GTK_UNIT_POINTS); small_month_width = calc_small_month_width (context, HEADER_HEIGHT); week_numbers_inc = get_show_week_numbers () ? small_month_width / 7.0 : 0; model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); convert_timet_to_struct_tm (date, zone, &tm); week_start_day = e_cal_model_get_week_start_day (model); wday = e_weekday_to_tm_wday (week_start_day); when = time_week_begin_with_zone (date, wday, zone); /* If the week starts on a Sunday, we have to show the Saturday first, * since the weekend is compressed. */ if (week_start_day == G_DATE_SUNDAY) { if (tm.tm_wday == 6) when = time_add_day_with_zone (when, 6, zone); else when = time_add_day_with_zone (when, -1, zone); } /* Print the main week view. */ print_week_summary ( context, gcal, when, FALSE, 1, 0, WEEK_EVENT_FONT_SIZE, WEEK_SMALL_FONT_SIZE, 0.0, width, HEADER_HEIGHT + 20, height); /* Print the border around the main view. */ print_border ( context, 0.0, width, HEADER_HEIGHT , height, 1.0, -1.0); /* Print the border around the header area. */ print_border ( context, 0.0, width, 0.0, HEADER_HEIGHT + 2.0 + 20, 1.0, 0.9); /* Print the 2 mini calendar-months. */ l = width - SMALL_MONTH_PAD - (small_month_width + week_numbers_inc) * 2 - SMALL_MONTH_SPACING; print_month_small ( context, gcal, when, l, 4, l + small_month_width + week_numbers_inc, HEADER_HEIGHT + 10, DATE_MONTH | DATE_YEAR, when, time_add_week_with_zone (when, 1, zone), FALSE); l += SMALL_MONTH_SPACING + small_month_width + week_numbers_inc; print_month_small ( context, gcal, time_add_month_with_zone (when, 1, zone), l, 4, l + small_month_width + week_numbers_inc, HEADER_HEIGHT + 10, DATE_MONTH | DATE_YEAR, when, time_add_week_with_zone (when, 1, zone), FALSE); /* Print the start day of the week, e.g. '7th May 2001'. */ convert_timet_to_struct_tm (when, zone, &tm); format_date (&tm, DATE_DAY | DATE_MONTH | DATE_YEAR, buf, 100); print_text_abs_bold ( context, buf, WEEK_NORMAL_FONT_SIZE, PANGO_ALIGN_LEFT, 3, width, 4, 4 + 24); /* Print the end day of the week, e.g. '13th May 2001'. */ when = time_add_day_with_zone (when, 6, zone); convert_timet_to_struct_tm (when, zone, &tm); format_date (&tm, DATE_DAY | DATE_MONTH | DATE_YEAR, buf, 100); print_text_abs_bold ( context, buf, WEEK_NORMAL_FONT_SIZE, PANGO_ALIGN_LEFT, 3, width, 24 + 3, 24 + 3 + 24); } static void print_month_view (GtkPrintContext *context, GnomeCalendar *gcal, time_t date) { ECalModel *model; GtkPageSetup *setup; icaltimezone *zone; gchar buf[100]; gdouble width, height; gdouble l, week_numbers_inc, small_month_width; struct tm tm; model = gnome_calendar_get_model (gcal); zone = e_cal_model_get_timezone (model); setup = gtk_print_context_get_page_setup (context); width = gtk_page_setup_get_page_width (setup, GTK_UNIT_POINTS); height = gtk_page_setup_get_page_height (setup, GTK_UNIT_POINTS); small_month_width = calc_small_month_width (context, HEADER_HEIGHT); week_numbers_inc = get_show_week_numbers () ? small_month_width / 7.0 : 0; /* Print the main month view. */ print_month_summary (context, gcal, date, 0.0, width, HEADER_HEIGHT, height); /* Print the border around the header. */ print_border (context, 0.0, width, 0.0, HEADER_HEIGHT + 10, 1.0, 0.9); l = width - SMALL_MONTH_PAD - small_month_width - week_numbers_inc; /* Print the 2 mini calendar-months. */ print_month_small ( context, gcal, time_add_month_with_zone (date, 1, zone), l, 4, l + small_month_width + week_numbers_inc, HEADER_HEIGHT + 4, DATE_MONTH | DATE_YEAR, 0, 0, FALSE); print_month_small ( context, gcal, time_add_month_with_zone (date, -1, zone), SMALL_MONTH_PAD, 4, SMALL_MONTH_PAD + small_month_width + week_numbers_inc, HEADER_HEIGHT + 4, DATE_MONTH | DATE_YEAR, 0, 0, FALSE); /* Print the month, e.g. 'May 2001'. */ convert_timet_to_struct_tm (date, zone, &tm); format_date (&tm, DATE_MONTH | DATE_YEAR, buf, 100); print_text_size_bold ( context, buf, PANGO_ALIGN_CENTER, 3, width - 3, 3, 3 + 24); } static gboolean same_date (struct tm tm1, time_t t2, icaltimezone *zone) { struct tm tm2; convert_timet_to_struct_tm (t2, zone, &tm2); return tm1.tm_mday == tm2.tm_mday && tm1.tm_mon == tm2.tm_mon && tm1.tm_year == tm2.tm_year; } static void write_label_piece (time_t t, time_t *start_cmp, icaltimezone *zone, gboolean use_24_hour_format, gchar *buffer, gint size, gchar *stext, const gchar *etext) { struct tm tmp_tm; gint len; convert_timet_to_struct_tm (t, zone, &tmp_tm); if (stext != NULL) strcat (buffer, stext); len = strlen (buffer); if (start_cmp && same_date (tmp_tm, *start_cmp, zone)) e_time_format_time ( &tmp_tm, use_24_hour_format, FALSE, &buffer[len], size - len); else e_time_format_date_and_time ( &tmp_tm, use_24_hour_format, FALSE, FALSE, &buffer[len], size - len); if (etext != NULL) strcat (buffer, etext); } static icaltimezone * get_zone_from_tzid (ECalClient *client, const gchar *tzid) { icaltimezone *zone; /* Note that the timezones may not be on the server, so we try to get * the builtin timezone with the TZID first. */ zone = icaltimezone_get_builtin_timezone_from_tzid (tzid); if (!zone && tzid) { GError *error = NULL; e_cal_client_get_timezone_sync ( client, tzid, &zone, NULL, &error); if (error != NULL) { g_warning ( "Couldn't get timezone '%s' from server: %s", tzid ? tzid : "", error->message); g_error_free (error); } } return zone; } static void print_date_label (GtkPrintContext *context, ECalComponent *comp, ECalClient *client, icaltimezone *zone, gboolean use_24_hour_format, gdouble left, gdouble right, gdouble top, gdouble bottom) { icaltimezone *start_zone, *end_zone, *due_zone, *completed_zone; ECalComponentDateTime datetime; time_t start = 0, end = 0, complete = 0, due = 0; static gchar buffer[1024]; e_cal_component_get_dtstart (comp, &datetime); if (datetime.value) { start_zone = get_zone_from_tzid (client, datetime.tzid); if (!start_zone || datetime.value->is_date) start_zone = zone; start = icaltime_as_timet_with_zone ( *datetime.value, start_zone); } e_cal_component_free_datetime (&datetime); e_cal_component_get_dtend (comp, &datetime); if (datetime.value) { end_zone = get_zone_from_tzid (client, datetime.tzid); if (!end_zone || datetime.value->is_date) end_zone = zone; end = icaltime_as_timet_with_zone ( *datetime.value, end_zone); } e_cal_component_free_datetime (&datetime); e_cal_component_get_due (comp, &datetime); if (datetime.value) { due_zone = get_zone_from_tzid (client, datetime.tzid); if (!due_zone || datetime.value->is_date) due_zone = zone; due = icaltime_as_timet_with_zone ( *datetime.value, due_zone); } e_cal_component_free_datetime (&datetime); e_cal_component_get_completed (comp, &datetime.value); if (datetime.value) { completed_zone = icaltimezone_get_utc_timezone (); complete = icaltime_as_timet_with_zone ( *datetime.value, completed_zone); e_cal_component_free_icaltimetype (datetime.value); } buffer[0] = '\0'; if (start > 0) write_label_piece ( start, NULL, zone, use_24_hour_format, buffer, 1024, NULL, NULL); if (end > 0 && start > 0) { write_label_piece ( end, &start, zone, use_24_hour_format, /* Translators: This is part of "START to END" text, * where START and END are date/times. */ buffer, 1024, _(" to "), NULL); } if (complete > 0) { if (start > 0) { write_label_piece ( complete, NULL, zone, use_24_hour_format, /* Translators: This is part of "START to END * (Completed COMPLETED)", where COMPLETED is a * completed date/time. */ buffer, 1024, _(" (Completed "), ")"); } else { write_label_piece ( complete, &start, zone, use_24_hour_format, /* Translators: This is part of "Completed COMPLETED", * where COMPLETED is a completed date/time. */ buffer, 1024, _("Completed "), NULL); } } if (due > 0 && complete == 0) { if (start > 0) { write_label_piece ( due, NULL, zone, use_24_hour_format, /* Translators: This is part of "START (Due DUE)", * where START and DUE are dates/times. */ buffer, 1024, _(" (Due "), ")"); } else { write_label_piece ( due, &start, zone, use_24_hour_format, /* Translators: This is part of "Due DUE", * where DUE is a date/time due the event * should be finished. */ buffer, 1024, _("Due "), NULL); } } print_text_size_bold ( context, buffer, PANGO_ALIGN_LEFT, left, right, top, top + 24); } static void print_calendar_draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, PrintCalItem *pcali) { switch (gnome_calendar_get_view (pcali->gcal)) { case GNOME_CAL_DAY_VIEW: print_day_view (context, pcali->gcal, pcali->start); break; case GNOME_CAL_WORK_WEEK_VIEW: print_work_week_view (context, pcali->gcal, pcali->start); break; case GNOME_CAL_WEEK_VIEW: print_week_view (context, pcali->gcal, pcali->start); break; case GNOME_CAL_MONTH_VIEW: print_month_view (context, pcali->gcal, pcali->start); break; default: g_return_if_reached (); } } void print_calendar (GnomeCalendar *gcal, GtkPrintOperationAction action, time_t start) { GtkPrintOperation *operation; PrintCalItem pcali; g_return_if_fail (gcal != NULL); g_return_if_fail (GNOME_IS_CALENDAR (gcal)); if (gnome_calendar_get_view (gcal) == GNOME_CAL_MONTH_VIEW) { GnomeCalendarViewType view_type; ECalendarView *calendar_view; EWeekView *week_view; GDate date; gboolean multi_week_view; gint weeks_shown; view_type = gnome_calendar_get_view (gcal); calendar_view = gnome_calendar_get_calendar_view (gcal, view_type); week_view = E_WEEK_VIEW (calendar_view); weeks_shown = e_week_view_get_weeks_shown (week_view); multi_week_view = e_week_view_get_multi_week_view (week_view); e_week_view_get_first_day_shown (week_view, &date); if (multi_week_view && weeks_shown >= 4 && g_date_valid (&date)) { struct icaltimetype start_tt; g_date_add_days (&date, 7); start_tt = icaltime_null_time (); start_tt.is_date = TRUE; start_tt.year = g_date_get_year (&date); start_tt.month = g_date_get_month (&date); start_tt.day = g_date_get_day (&date); start = icaltime_as_timet (start_tt); } else if (multi_week_view) { start = week_view->day_starts[0]; } } pcali.gcal = (GnomeCalendar *) gcal; pcali.start = start; operation = e_print_operation_new (); gtk_print_operation_set_n_pages (operation, 1); g_signal_connect ( operation, "draw_page", G_CALLBACK (print_calendar_draw_page), &pcali); gtk_print_operation_run (operation, action, NULL, NULL); g_object_unref (operation); } /* returns number of required pages, when page_nr is -1 */ static gint print_comp_draw_real (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, PrintCompItem *pci) { GtkPageSetup *setup; PangoFontDescription *font; ECalClient *client; ECalComponent *comp; ECalComponentVType vtype; ECalComponentText text; GSList *desc, *l; GSList *contact_list, *elem; const gchar *title, *categories, *location; gchar *categories_string, *location_string, *summary_string; gdouble header_size; cairo_t *cr; gdouble width, height, page_start; gdouble top; gint pages = 1; setup = gtk_print_context_get_page_setup (context); width = gtk_page_setup_get_page_width (setup, GTK_UNIT_POINTS); height = gtk_page_setup_get_page_height (setup, GTK_UNIT_POINTS); top = 0.0; /* Either draw only the right page or do not draw * anything when calculating number of pages. */ if (page_nr != -1) top = top - ((page_nr) * height); else top = height; page_start = top; /* PrintCompItem structure contains elements to be used * with the Print Context , obtained in comp_draw_page */ client = pci->client; comp = pci->comp; vtype = e_cal_component_get_vtype (comp); /* We should only be asked to print VEVENTs, VTODOs, or VJOURNALs. */ if (vtype == E_CAL_COMPONENT_EVENT) title = _("Appointment"); else if (vtype == E_CAL_COMPONENT_TODO) title = _("Task"); else if (vtype == E_CAL_COMPONENT_JOURNAL) title = _("Memo"); else return pages; cr = gtk_print_context_get_cairo_context (context); /* Print the title in a box at the top of the page. */ font = get_font_for_size (18, PANGO_WEIGHT_BOLD); header_size = 40; if (page_nr == 0) { print_border ( context, 0.0, width, 0.0, header_size, 1.0, 0.9); print_text ( context, font, title, PANGO_ALIGN_CENTER, 0.0, width, 0.1, header_size - 0.1); pango_font_description_free (font); } top += header_size + 30; /* Summary */ font = get_font_for_size (18, PANGO_WEIGHT_BOLD); e_cal_component_get_summary (comp, &text); summary_string = g_strdup_printf (_("Summary: %s"), text.value); top = bound_text ( context, font, summary_string, -1, 0.0, top, width, height, FALSE, &page_start, &pages); g_free (summary_string); /* Location */ e_cal_component_get_location (comp, &location); if (location && location[0]) { location_string = g_strdup_printf ( _("Location: %s"), location); top = bound_text ( context, font, location_string, -1, 0.0, top + 3, width, height, FALSE, &page_start, &pages); g_free (location_string); } /* Date information */ if (page_nr == 0) print_date_label ( context, comp, client, pci->zone, pci->use_24_hour_format, 0.0, width, top + 3, top + 15); top += 20; /* Attendees */ if ((page_nr == 0) && e_cal_component_has_attendees (comp)) { top = bound_text ( context, font, _("Attendees: "), -1, 0.0, top, width, height, FALSE, &page_start, &pages); pango_font_description_free (font); font = get_font_for_size (12, PANGO_WEIGHT_NORMAL); top = print_attendees ( context, font, cr, 0.0, width, top, height, comp, page_nr, &pages); top += get_font_size (font) - 6; } pango_font_description_free (font); font = get_font_for_size (12, PANGO_WEIGHT_NORMAL); /* For a VTODO we print the Status, Priority, % Complete and URL. */ if (vtype == E_CAL_COMPONENT_TODO) { icalproperty_status status; const gchar *status_string = NULL; gint *percent; gint *priority; const gchar *url; /* Status */ e_cal_component_get_status (comp, &status); if (status != ICAL_STATUS_NONE) { switch (status) { case ICAL_STATUS_NEEDSACTION: status_string = _("Not Started"); break; case ICAL_STATUS_INPROCESS: status_string = _("In Progress"); break; case ICAL_STATUS_COMPLETED: status_string = _("Completed"); break; case ICAL_STATUS_CANCELLED: status_string = _("Canceled"); break; default: break; } if (status_string) { gchar *status_text = g_strdup_printf ( _("Status: %s"), status_string); top = bound_text ( context, font, status_text, -1, 0.0, top, width, height, FALSE, &page_start, &pages); top += get_font_size (font) - 6; g_free (status_text); } } /* Priority */ e_cal_component_get_priority (comp, &priority); if (priority && *priority >= 0) { gchar *pri_text; pri_text = g_strdup_printf ( _("Priority: %s"), e_cal_util_priority_to_string (*priority)); top = bound_text ( context, font, pri_text, -1, 0.0, top, width, height, FALSE, &page_start, &pages); top += get_font_size (font) - 6; g_free (pri_text); } if (priority) e_cal_component_free_priority (priority); /* Percent Complete */ e_cal_component_get_percent (comp, &percent); if (percent) { gchar *percent_string; percent_string = g_strdup_printf (_("Percent Complete: %i"), *percent); e_cal_component_free_percent (percent); top = bound_text ( context, font, percent_string, -1, 0.0, top, width, height, FALSE, &page_start, &pages); top += get_font_size (font) - 6; } /* URL */ e_cal_component_get_url (comp, &url); if (url && url[0]) { gchar *url_string; url_string = g_strdup_printf (_("URL: %s"), url); top = bound_text ( context, font, url_string, -1, 0.0, top, width, height, TRUE, &page_start, &pages); top += get_font_size (font) - 6; g_free (url_string); } } /* Categories */ e_cal_component_get_categories (comp, &categories); if (categories && categories[0]) { categories_string = g_strdup_printf ( _("Categories: %s"), categories); top = bound_text ( context, font, categories_string, -1, 0.0, top, width, height, TRUE, &page_start, &pages); top += get_font_size (font) - 6; g_free (categories_string); } /* Contacts */ e_cal_component_get_contact_list (comp, &contact_list); if (contact_list) { GString *contacts = g_string_new (_("Contacts: ")); for (elem = contact_list; elem; elem = elem->next) { ECalComponentText *t = elem->data; /* Put a comma between contacts. */ if (elem != contact_list) g_string_append (contacts, ", "); g_string_append (contacts, t->value); } e_cal_component_free_text_list (contact_list); top = bound_text ( context, font, contacts->str, -1, 0.0, top, width, height, TRUE, &page_start, &pages); top += get_font_size (font) - 6; g_string_free (contacts, TRUE); } top += 16; /* Description */ e_cal_component_get_description_list (comp, &desc); for (l = desc; l != NULL; l = l->next) { ECalComponentText *ptext = l->data; const gchar *line, *next_line; for (line = ptext->value; line != NULL; line = next_line) { next_line = strchr (line, '\n'); top = bound_text ( context, font, line, next_line ? next_line - line : -1, 0.0, top + 3, width, height, TRUE, &page_start, &pages); if (next_line) { next_line++; if (!*next_line) next_line = NULL; } } } e_cal_component_free_text_list (desc); pango_font_description_free (font); return pages; } static void print_comp_draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, PrintCompItem *pci) { print_comp_draw_real (operation, context, page_nr, pci); } static void print_comp_begin_print (GtkPrintOperation *operation, GtkPrintContext *context, PrintCompItem *pci) { gint pages; pages = print_comp_draw_real (operation, context, -1, pci); gtk_print_operation_set_n_pages (operation, pages); } void print_comp (ECalComponent *comp, ECalClient *cal_client, icaltimezone *zone, gboolean use_24_hour_format, GtkPrintOperationAction action) { GtkPrintOperation *operation; PrintCompItem pci; g_return_if_fail (E_IS_CAL_COMPONENT (comp)); pci.comp = comp; pci.client = cal_client; pci.zone = zone; pci.use_24_hour_format = use_24_hour_format; operation = e_print_operation_new (); gtk_print_operation_set_n_pages (operation, 1); g_signal_connect ( operation, "begin-print", G_CALLBACK (print_comp_begin_print), &pci); g_signal_connect ( operation, "draw-page", G_CALLBACK (print_comp_draw_page), &pci); gtk_print_operation_run (operation, action, NULL, NULL); g_object_unref (operation); } static void print_title (GtkPrintContext *context, const gchar *text, gdouble page_width) { PangoFontDescription *desc; PangoLayout *layout; cairo_t *cr; cr = gtk_print_context_get_cairo_context (context); desc = pango_font_description_from_string (FONT_FAMILY " Bold 18"); layout = gtk_print_context_create_pango_layout (context); pango_layout_set_text (layout, text, -1); pango_layout_set_font_description (layout, desc); pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); pango_layout_set_width (layout, pango_units_from_double (page_width)); cairo_save (cr); cairo_move_to (cr, 0.0, 0.0); pango_cairo_show_layout (cr, layout); cairo_translate (cr, 0.0, 18); cairo_save (cr); cairo_restore (cr); g_object_unref (layout); pango_font_description_free (desc); } struct print_opts { EPrintable *printable; const gchar *print_header; }; static void print_table_draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, struct print_opts *opts) { GtkPageSetup *setup; gdouble width; setup = gtk_print_context_get_page_setup (context); width = gtk_page_setup_get_page_width (setup, GTK_UNIT_POINTS); do { /* TODO Allow the user to customize the title. */ print_title (context, opts->print_header, width); if (e_printable_data_left (opts->printable)) e_printable_print_page ( opts->printable, context, width, 24, TRUE); } while (e_printable_data_left (opts->printable)); g_free (opts); } void print_table (ETable *table, const gchar *dialog_title, const gchar *print_header, GtkPrintOperationAction action) { GtkPrintOperation *operation; EPrintable *printable; struct print_opts *opts; printable = e_table_get_printable (table); g_object_ref_sink (printable); e_printable_reset (printable); operation = e_print_operation_new (); gtk_print_operation_set_n_pages (operation, 1); opts = g_malloc (sizeof (struct print_opts)); opts->printable = printable; opts->print_header = print_header; g_signal_connect ( operation, "draw_page", G_CALLBACK (print_table_draw_page), opts); gtk_print_operation_run (operation, action, NULL, NULL); g_object_unref (operation); }