/* * xed-print-preview.c * * Copyright (C) 2008 Paolo Borelli * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * Modified by the xed Team, 1998-2006. See the AUTHORS file for a * list of people on the xed Team. * See the ChangeLog files for a list of changes. * * $Id: xed-commands-search.c 5931 2007-09-25 20:05:40Z pborelli $ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "xed-print-preview.h" #define PRINTER_DPI (72.) struct _XedPrintPreviewPrivate { GtkPrintOperation *operation; GtkPrintContext *context; GtkPrintOperationPreview *gtk_preview; GtkWidget *layout; GtkWidget *scrolled_window; GtkWidget *next; GtkWidget *prev; GtkWidget *page_entry; GtkWidget *last; GtkWidget *multi; GtkWidget *zoom_one; GtkWidget *zoom_fit; GtkWidget *zoom_in; GtkWidget *zoom_out; /* real size of the page in inches */ double paper_w; double paper_h; double dpi; double scale; /* size of the tile of a page (including padding * and drop shadow) in pixels */ gint tile_w; gint tile_h; GtkPageOrientation orientation; /* multipage support */ gint rows; gint cols; guint n_pages; guint cur_page; }; G_DEFINE_TYPE (XedPrintPreview, xed_print_preview, GTK_TYPE_BOX) static void xed_print_preview_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { //XedPrintPreview *preview = XED_PRINT_PREVIEW (object); switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void xed_print_preview_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { //XedPrintPreview *preview = XED_PRINT_PREVIEW (object); switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void xed_print_preview_finalize (GObject *object) { //XedPrintPreview *preview = XED_PRINT_PREVIEW (object); G_OBJECT_CLASS (xed_print_preview_parent_class)->finalize (object); } static void xed_print_preview_grab_focus (GtkWidget *widget) { XedPrintPreview *preview; preview = XED_PRINT_PREVIEW (widget); gtk_widget_grab_focus (GTK_WIDGET (preview->priv->layout)); } static void xed_print_preview_class_init (XedPrintPreviewClass *klass) { GObjectClass *object_class; GtkWidgetClass *widget_class; object_class = G_OBJECT_CLASS (klass); widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = xed_print_preview_get_property; object_class->set_property = xed_print_preview_set_property; object_class->finalize = xed_print_preview_finalize; widget_class->grab_focus = xed_print_preview_grab_focus; g_type_class_add_private (object_class, sizeof(XedPrintPreviewPrivate)); } static void update_layout_size (XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; priv = preview->priv; /* force size of the drawing area to make the scrolled window work */ gtk_layout_set_size (GTK_LAYOUT (priv->layout), priv->tile_w * priv->cols, priv->tile_h * priv->rows); gtk_widget_queue_draw (preview->priv->layout); } static void set_rows_and_cols (XedPrintPreview *preview, gint rows, gint cols) { /* TODO: set the zoom appropriately */ preview->priv->rows = rows; preview->priv->cols = cols; update_layout_size (preview); } /* get the paper size in points: these must be used only * after the widget has been mapped and the dpi is known */ static double get_paper_width (XedPrintPreview *preview) { return preview->priv->paper_w * preview->priv->dpi; } static double get_paper_height (XedPrintPreview *preview) { return preview->priv->paper_h * preview->priv->dpi; } #define PAGE_PAD 12 #define PAGE_SHADOW_OFFSET 5 /* The tile size is the size of the area where a page * will be drawn including the padding and idependent * of the orientation */ /* updates the tile size to the current zoom and page size */ static void update_tile_size (XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; gint w, h; priv = preview->priv; w = 2 * PAGE_PAD + floor (priv->scale * get_paper_width (preview) + 0.5); h = 2 * PAGE_PAD + floor (priv->scale * get_paper_height (preview) + 0.5); if ((priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) || (priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE)) { priv->tile_w = h; priv->tile_h = w; } else { priv->tile_w = w; priv->tile_h = h; } } /* Zoom should always be set with one of these two function * so that the tile size is properly updated */ static void set_zoom_factor (XedPrintPreview *preview, double zoom) { XedPrintPreviewPrivate *priv; priv = preview->priv; priv->scale = zoom; update_tile_size (preview); update_layout_size (preview); } static void set_zoom_fit_to_size (XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; double width, height; double p_width, p_height; double zoomx, zoomy; priv = preview->priv; g_object_get (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (priv->layout)), "page-size", &width, NULL); g_object_get (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->layout)), "page-size", &height, NULL); width /= priv->cols; height /= priv->rows; if ((priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) || (priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE)) { p_width = get_paper_height (preview); p_height = get_paper_width (preview); } else { p_width = get_paper_width (preview); p_height = get_paper_height (preview); } zoomx = MAX (1, width - 2 * PAGE_PAD) / p_width; zoomy = MAX (1, height - 2 * PAGE_PAD) / p_height; if (zoomx <= zoomy) { priv->tile_w = width; priv->tile_h = floor (0.5 + width * (p_height / p_width)); priv->scale = zoomx; } else { priv->tile_w = floor (0.5 + height * (p_width / p_height)); priv->tile_h = height; priv->scale = zoomy; } update_layout_size (preview); } #define ZOOM_IN_FACTOR (1.2) #define ZOOM_OUT_FACTOR (1.0 / ZOOM_IN_FACTOR) static void zoom_in (XedPrintPreview *preview) { set_zoom_factor (preview, preview->priv->scale * ZOOM_IN_FACTOR); } static void zoom_out (XedPrintPreview *preview) { set_zoom_factor (preview, preview->priv->scale * ZOOM_OUT_FACTOR); } static void goto_page (XedPrintPreview *preview, gint page) { gchar c[32]; g_snprintf (c, 32, "%d", page + 1); gtk_entry_set_text (GTK_ENTRY (preview->priv->page_entry), c); gtk_widget_set_sensitive (GTK_WIDGET (preview->priv->prev), (page > 0) && (preview->priv->n_pages > 1)); gtk_widget_set_sensitive (GTK_WIDGET (preview->priv->next), (page != (preview->priv->n_pages - 1)) && (preview->priv->n_pages > 1)); if (page != preview->priv->cur_page) { preview->priv->cur_page = page; if (preview->priv->n_pages > 0) { gtk_widget_queue_draw (preview->priv->layout); } } } static void prev_button_clicked (GtkWidget *button, XedPrintPreview *preview) { GdkEvent *event; gint page; event = gtk_get_current_event (); if (event->button.state & GDK_SHIFT_MASK) { page = 0; } else { page = preview->priv->cur_page - preview->priv->rows * preview->priv->cols; } goto_page (preview, MAX (page, 0)); gdk_event_free (event); } static void next_button_clicked (GtkWidget *button, XedPrintPreview *preview) { GdkEvent *event; gint page; event = gtk_get_current_event (); if (event->button.state & GDK_SHIFT_MASK) { page = preview->priv->n_pages - 1; } else { page = preview->priv->cur_page + preview->priv->rows * preview->priv->cols; } goto_page (preview, MIN (page, preview->priv->n_pages - 1)); gdk_event_free (event); } static void page_entry_activated (GtkEntry *entry, XedPrintPreview *preview) { const gchar *text; gint page; text = gtk_entry_get_text (entry); page = CLAMP (atoi (text), 1, preview->priv->n_pages) - 1; goto_page (preview, page); gtk_widget_grab_focus (GTK_WIDGET (preview->priv->layout)); } static void page_entry_insert_text (GtkEditable *editable, const gchar *text, gint length, gint *position) { gunichar c; const gchar *p; const gchar *end; p = text; end = text + length; while (p != end) { const gchar *next; next = g_utf8_next_char (p); c = g_utf8_get_char (p); if (!g_unichar_isdigit (c)) { g_signal_stop_emission_by_name (editable, "insert-text"); break; } p = next; } } static gboolean page_entry_focus_out (GtkWidget *widget, GdkEventFocus *event, XedPrintPreview *preview) { const gchar *text; gint page; text = gtk_entry_get_text (GTK_ENTRY (widget)); page = atoi (text) - 1; /* Reset the page number only if really needed */ if (page != preview->priv->cur_page) { gchar *str; str = g_strdup_printf ("%d", preview->priv->cur_page + 1); gtk_entry_set_text (GTK_ENTRY (widget), str); g_free (str); } return FALSE; } static void on_1x1_clicked (GtkMenuItem *i, XedPrintPreview *preview) { set_rows_and_cols (preview, 1, 1); } static void on_1x2_clicked (GtkMenuItem *i, XedPrintPreview *preview) { set_rows_and_cols (preview, 1, 2); } static void on_2x1_clicked (GtkMenuItem *i, XedPrintPreview *preview) { set_rows_and_cols (preview, 2, 1); } static void on_2x2_clicked (GtkMenuItem *i, XedPrintPreview *preview) { set_rows_and_cols (preview, 2, 2); } static void multi_button_clicked (GtkWidget *button, XedPrintPreview *preview) { GtkWidget *m, *i; m = gtk_menu_new (); gtk_widget_show (m); g_signal_connect (m, "selection_done", G_CALLBACK (gtk_widget_destroy), m); i = gtk_menu_item_new_with_label ("1x1"); gtk_widget_show (i); gtk_menu_attach (GTK_MENU (m), i, 0, 1, 0, 1); g_signal_connect (i, "activate", G_CALLBACK (on_1x1_clicked), preview); i = gtk_menu_item_new_with_label ("2x1"); gtk_widget_show (i); gtk_menu_attach (GTK_MENU (m), i, 0, 1, 1, 2); g_signal_connect (i, "activate", G_CALLBACK (on_2x1_clicked), preview); i = gtk_menu_item_new_with_label ("1x2"); gtk_widget_show (i); gtk_menu_attach (GTK_MENU (m), i, 1, 2, 0, 1); g_signal_connect (i, "activate", G_CALLBACK (on_1x2_clicked), preview); i = gtk_menu_item_new_with_label ("2x2"); gtk_widget_show (i); gtk_menu_attach (GTK_MENU (m), i, 1, 2, 1, 2); g_signal_connect (i, "activate", G_CALLBACK (on_2x2_clicked), preview); gtk_menu_popup (GTK_MENU (m), NULL, NULL, NULL, preview, 0, GDK_CURRENT_TIME); } static void zoom_one_button_clicked (GtkWidget *button, XedPrintPreview *preview) { set_zoom_factor (preview, 1); } static void zoom_fit_button_clicked (GtkWidget *button, XedPrintPreview *preview) { set_zoom_fit_to_size (preview); } static void zoom_in_button_clicked (GtkWidget *button, XedPrintPreview *preview) { zoom_in (preview); } static void zoom_out_button_clicked (GtkWidget *button, XedPrintPreview *preview) { zoom_out (preview); } static void close_button_clicked (GtkWidget *button, XedPrintPreview *preview) { gtk_widget_destroy (GTK_WIDGET (preview)); } static void create_bar (XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; GtkWidget *toolbar; GtkToolItem *i; AtkObject *atko; GtkWidget *status; GtkWidget *box; GtkWidget *close_button; priv = preview->priv; toolbar = gtk_toolbar_new (); gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), "inline-toolbar"); gtk_widget_show (toolbar); gtk_box_pack_start (GTK_BOX (preview), toolbar, FALSE, FALSE, 0); i = gtk_tool_item_new (); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_style_context_add_class (gtk_widget_get_style_context (box), "linked"); gtk_container_add (GTK_CONTAINER (i), box); priv->prev = gtk_button_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (box), priv->prev, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (priv->prev, _("Show the previous page")); g_signal_connect (priv->prev, "clicked", G_CALLBACK (prev_button_clicked), preview); priv->next = gtk_button_new_from_icon_name ("go-next-symbolic", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (box), priv->next, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (priv->next, _("Show the next page")); g_signal_connect (priv->next, "clicked", G_CALLBACK (next_button_clicked), preview); gtk_widget_show_all (GTK_WIDGET (i)); i = gtk_separator_tool_item_new (); gtk_widget_show (GTK_WIDGET (i)); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); status = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); priv->page_entry = gtk_entry_new (); gtk_entry_set_width_chars (GTK_ENTRY (priv->page_entry), 3); gtk_entry_set_max_length (GTK_ENTRY (priv->page_entry), 6); gtk_widget_set_tooltip_text (priv->page_entry, _("Current page (Alt+P)")); g_signal_connect (priv->page_entry, "activate", G_CALLBACK (page_entry_activated), preview); g_signal_connect (priv->page_entry, "insert-text", G_CALLBACK (page_entry_insert_text), NULL); g_signal_connect (priv->page_entry, "focus-out-event", G_CALLBACK (page_entry_focus_out), preview); gtk_box_pack_start (GTK_BOX (status), priv->page_entry, FALSE, FALSE, 0); /* gtk_label_set_mnemonic_widget ((GtkLabel *) l, mp->priv->page_entry); */ /* We are displaying 'XXX of XXX'. */ /* Translators: the "of" from "1 of 19" in print preview. */ gtk_box_pack_start (GTK_BOX (status), gtk_label_new (_("of")), FALSE, FALSE, 0); priv->last = gtk_label_new (""); gtk_box_pack_start (GTK_BOX (status), priv->last, FALSE, FALSE, 0); atko = gtk_widget_get_accessible (priv->last); atk_object_set_name (atko, _("Page total")); atk_object_set_description (atko, _("The total number of pages in the document")); gtk_widget_show_all (status); i = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (i), status); gtk_widget_show (GTK_WIDGET (i)); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); i = gtk_separator_tool_item_new (); gtk_widget_show (GTK_WIDGET (i)); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); i = gtk_tool_item_new (); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_add (GTK_CONTAINER (i), box); priv->multi = gtk_button_new_from_icon_name ("view-grid-symbolic", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (box), priv->multi, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (priv->multi, _("Show multiple pages")); g_signal_connect (priv->multi, "clicked", G_CALLBACK (multi_button_clicked), preview); gtk_widget_show_all (GTK_WIDGET (i)); i = gtk_separator_tool_item_new (); gtk_widget_show (GTK_WIDGET (i)); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); i = gtk_tool_item_new (); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_style_context_add_class (gtk_widget_get_style_context (box), "linked"); gtk_container_add (GTK_CONTAINER (i), box); priv->zoom_one = gtk_button_new_from_icon_name ("zoom-original-symbolic", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (box), priv->zoom_one, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (priv->zoom_one, _("Zoom 1:1")); g_signal_connect (priv->zoom_one, "clicked", G_CALLBACK (zoom_one_button_clicked), preview); priv->zoom_fit = gtk_button_new_from_icon_name ("zoom-fit-best-symbolic", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (box), priv->zoom_fit, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (priv->zoom_fit, _("Zoom to fit the whole page")); g_signal_connect (priv->zoom_fit, "clicked", G_CALLBACK (zoom_fit_button_clicked), preview); priv->zoom_in = gtk_button_new_from_icon_name ("zoom-in-symbolic", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (box), priv->zoom_in, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (priv->zoom_in, _("Zoom the page in")); g_signal_connect (priv->zoom_in, "clicked", G_CALLBACK (zoom_in_button_clicked), preview); priv->zoom_out = gtk_button_new_from_icon_name ("zoom-out-symbolic", GTK_ICON_SIZE_MENU); gtk_box_pack_start (GTK_BOX (box), priv->zoom_out, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (priv->zoom_out, _("Zoom the page out")); g_signal_connect (priv->zoom_out, "clicked", G_CALLBACK (zoom_out_button_clicked), preview); gtk_widget_show_all (GTK_WIDGET (i)); i = gtk_separator_tool_item_new (); gtk_widget_show (GTK_WIDGET (i)); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); i = gtk_tool_item_new (); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_add (GTK_CONTAINER (i), box); close_button = gtk_button_new_with_mnemonic (_("_Close preview")); gtk_box_pack_start (GTK_BOX (box), close_button, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (close_button, _("Close print preview")); g_signal_connect (close_button, "clicked", G_CALLBACK (close_button_clicked), preview); gtk_widget_show_all (GTK_WIDGET (i)); } static gint get_first_page_displayed (XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; priv = preview->priv; return priv->cur_page - priv->cur_page % (priv->cols * priv->rows); } /* returns the page number (starting from 0) or -1 if no page */ static gint get_page_at_coords (XedPrintPreview *preview, gint x, gint y) { XedPrintPreviewPrivate *priv; GtkAdjustment *hadj, *vadj; gint r, c, pg; priv = preview->priv; if (priv->tile_h <= 0 || priv->tile_h <= 0) { return -1; } hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (priv->layout)); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->layout)); x += gtk_adjustment_get_value (hadj); y += gtk_adjustment_get_value (vadj); r = 1 + y / (priv->tile_h); c = 1 + x / (priv->tile_w); if (c > priv->cols) { return -1; } pg = get_first_page_displayed (preview) - 1; pg += (r - 1) * priv->cols + c; if (pg >= priv->n_pages) { return -1; } /* FIXME: we could try to be picky and check * if we actually are inside the page */ return pg; } static gboolean preview_layout_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip, XedPrintPreview *preview) { gint pg; gchar *tip; pg = get_page_at_coords (preview, x, y); if (pg < 0) { return FALSE; } tip = g_strdup_printf (_("Page %d of %d"), pg + 1, preview->priv->n_pages); gtk_tooltip_set_text (tooltip, tip); g_free (tip); return TRUE; } static gint preview_layout_key_press (GtkWidget *widget, GdkEventKey *event, XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; GtkAdjustment *hadj, *vadj; double x, y; guint h, w; double hlower, hupper, vlower, vupper; double hpage, vpage; double hstep, vstep; gboolean domove = FALSE; gboolean ret = TRUE; priv = preview->priv; hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (priv->layout)); vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->layout)); x = gtk_adjustment_get_value (hadj); y = gtk_adjustment_get_value (vadj); g_object_get (hadj, "lower", &hlower, "upper", &hupper, "page-size", &hpage, NULL); g_object_get (vadj, "lower", &vlower, "upper", &vupper, "page-size", &vpage, NULL); gtk_layout_get_size (GTK_LAYOUT (priv->layout), &w, &h); hstep = 10; vstep = 10; switch (event->keyval) { case '1': set_zoom_fit_to_size (preview); break; case '+': case '=': case GDK_KEY_KP_Add: zoom_in (preview); break; case '-': case '_': case GDK_KEY_KP_Subtract: zoom_out (preview); break; case GDK_KEY_KP_Right: case GDK_KEY_Right: if (event->state & GDK_SHIFT_MASK) x = hupper - hpage; else x = MIN (hupper - hpage, x + hstep); domove = TRUE; break; case GDK_KEY_KP_Left: case GDK_KEY_Left: if (event->state & GDK_SHIFT_MASK) x = hlower; else x = MAX (hlower, x - hstep); domove = TRUE; break; case GDK_KEY_KP_Up: case GDK_KEY_Up: if (event->state & GDK_SHIFT_MASK) goto page_up; y = MAX (vlower, y - vstep); domove = TRUE; break; case GDK_KEY_KP_Down: case GDK_KEY_Down: if (event->state & GDK_SHIFT_MASK) goto page_down; y = MIN (vupper - vpage, y + vstep); domove = TRUE; break; case GDK_KEY_KP_Page_Up: case GDK_KEY_Page_Up: case GDK_KEY_Delete: case GDK_KEY_KP_Delete: case GDK_KEY_BackSpace: page_up: if (y <= vlower) { if (preview->priv->cur_page > 0) { goto_page (preview, preview->priv->cur_page - 1); y = (vupper - vpage); } } else { y = vlower; } domove = TRUE; break; case GDK_KEY_KP_Page_Down: case GDK_KEY_Page_Down: case ' ': page_down: if (y >= (vupper - vpage)) { if (preview->priv->cur_page < preview->priv->n_pages - 1) { goto_page (preview, preview->priv->cur_page + 1); y = vlower; } } else { y = (vupper - vpage); } domove = TRUE; break; case GDK_KEY_KP_Home: case GDK_KEY_Home: goto_page (preview, 0); y = 0; domove = TRUE; break; case GDK_KEY_KP_End: case GDK_KEY_End: goto_page (preview, preview->priv->n_pages - 1); y = 0; domove = TRUE; break; case GDK_KEY_Escape: gtk_widget_destroy (GTK_WIDGET (preview)); break; case 'c': if (event->state & GDK_MOD1_MASK) { gtk_widget_destroy (GTK_WIDGET (preview)); } break; case 'p': if (event->state & GDK_MOD1_MASK) { gtk_widget_grab_focus (preview->priv->page_entry); } break; default: /* by default do not stop the default handler */ ret = FALSE; } if (domove) { gtk_adjustment_set_value (hadj, x); gtk_adjustment_set_value (vadj, y); gtk_adjustment_value_changed (hadj); gtk_adjustment_value_changed (vadj); } return ret; } static void create_preview_layout (XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; AtkObject *atko; priv = preview->priv; priv->layout = gtk_layout_new (NULL, NULL); // gtk_widget_set_double_buffered (priv->layout, FALSE); atko = gtk_widget_get_accessible (GTK_WIDGET (priv->layout)); atk_object_set_name (atko, _("Page Preview")); atk_object_set_description (atko, _("The preview of a page in the document to be printed")); gtk_widget_add_events (priv->layout, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK); gtk_widget_set_can_focus (priv->layout, TRUE); g_signal_connect (priv->layout, "key-press-event", G_CALLBACK (preview_layout_key_press), preview); g_object_set (priv->layout, "has-tooltip", TRUE, NULL); g_signal_connect (priv->layout, "query-tooltip", G_CALLBACK (preview_layout_query_tooltip), preview); priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->layout); gtk_box_pack_end (GTK_BOX (preview), priv->scrolled_window, TRUE, TRUE, 0); gtk_widget_show_all (GTK_WIDGET (priv->scrolled_window)); gtk_widget_grab_focus (GTK_WIDGET (priv->layout)); } static void xed_print_preview_init (XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; priv = G_TYPE_INSTANCE_GET_PRIVATE (preview, XED_TYPE_PRINT_PREVIEW, XedPrintPreviewPrivate); preview->priv = priv; priv->operation = NULL; priv->context = NULL; priv->gtk_preview = NULL; gtk_orientable_set_orientation (GTK_ORIENTABLE (preview), GTK_ORIENTATION_VERTICAL); create_bar (preview); create_preview_layout (preview); // FIXME priv->cur_page = 0; priv->paper_w = 0; priv->paper_h = 0; priv->dpi = PRINTER_DPI; priv->scale = 1.0; priv->rows = 1; priv->cols = 1; } static void draw_page_content (cairo_t *cr, gint page_number, XedPrintPreview *preview) { /* scale to the desired size */ cairo_scale (cr, preview->priv->scale, preview->priv->scale); /* rotate acording to page orientation if needed */ if ((preview->priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) || (preview->priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE)) { cairo_matrix_t matrix; cairo_matrix_init (&matrix, 0, -1, 1, 0, 0, get_paper_width (preview)); cairo_transform (cr, &matrix); } gtk_print_context_set_cairo_context (preview->priv->context, cr, preview->priv->dpi, preview->priv->dpi); gtk_print_operation_preview_render_page (preview->priv->gtk_preview, page_number); } /* For the frame, we scale and rotate manually, since * the line width should not depend on the zoom and * the drop shadow should be on the bottom right no matter * the orientation */ static void draw_page_frame (cairo_t *cr, XedPrintPreview *preview) { double w, h; w = get_paper_width (preview); h = get_paper_height (preview); if ((preview->priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) || (preview->priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE)) { double tmp; tmp = w; w = h; h = tmp; } w *= preview->priv->scale; h *= preview->priv->scale; /* drop shadow */ cairo_set_source_rgb (cr, 0, 0, 0); cairo_rectangle (cr, PAGE_SHADOW_OFFSET, PAGE_SHADOW_OFFSET, w, h); cairo_fill (cr); /* page frame */ cairo_set_source_rgb (cr, 1, 1, 1); cairo_rectangle (cr, 0, 0, w, h); cairo_fill_preserve (cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_set_line_width (cr, 1); cairo_stroke (cr); } static void draw_page (cairo_t *cr, double x, double y, gint page_number, XedPrintPreview *preview) { cairo_save (cr); /* move to the page top left corner */ cairo_translate (cr, x + PAGE_PAD, y + PAGE_PAD); draw_page_frame (cr, preview); draw_page_content (cr, page_number, preview); cairo_restore (cr); } static gboolean preview_draw (GtkWidget *widget, cairo_t *cr, XedPrintPreview *preview) { XedPrintPreviewPrivate *priv; GdkWindow *bin_window; gint pg; gint i, j; priv = preview->priv; bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (priv->layout)); if (!gtk_cairo_should_draw_window (cr, bin_window)) { return TRUE; } cairo_save (cr); gtk_cairo_transform_to_window (cr, widget, bin_window); /* get the first page to display */ pg = get_first_page_displayed (preview); for (i = 0; i < priv->cols; ++i) { for (j = 0; j < priv->rows; ++j) { if (!gtk_print_operation_preview_is_selected (priv->gtk_preview, pg)) { continue; } if (pg == priv->n_pages) { break; } draw_page (cr, j * priv->tile_w, i * priv->tile_h, pg, preview); ++pg; } } cairo_restore (cr); return TRUE; } static double get_screen_dpi (XedPrintPreview *preview) { GdkScreen *screen; double dpi; screen = gtk_widget_get_screen (GTK_WIDGET (preview)); dpi = gdk_screen_get_resolution (screen); if (dpi < 30. || 600. < dpi) { g_warning ("Invalid the x-resolution for the screen, assuming 96dpi"); dpi = 96.; } return dpi; } static void set_n_pages (XedPrintPreview *preview, gint n_pages) { gchar *str; preview->priv->n_pages = n_pages; // FIXME: count the visible pages str = g_strdup_printf ("%d", n_pages); gtk_label_set_markup (GTK_LABEL (preview->priv->last), str); g_free (str); } static void preview_ready (GtkPrintOperationPreview *gtk_preview, GtkPrintContext *context, XedPrintPreview *preview) { gint n_pages; g_object_get (preview->priv->operation, "n-pages", &n_pages, NULL); set_n_pages (preview, n_pages); goto_page (preview, 0); /* figure out the dpi */ preview->priv->dpi = get_screen_dpi (preview); set_zoom_factor (preview, 1.0); /* let the default gtklayout handler clear the background */ g_signal_connect_after (preview->priv->layout, "draw", G_CALLBACK (preview_draw), preview); gtk_widget_queue_draw (preview->priv->layout); } static void update_paper_size (XedPrintPreview *preview, GtkPageSetup *page_setup) { GtkPaperSize *paper_size; paper_size = gtk_page_setup_get_paper_size (page_setup); preview->priv->paper_w = gtk_paper_size_get_width (paper_size, GTK_UNIT_INCH); preview->priv->paper_h = gtk_paper_size_get_height (paper_size, GTK_UNIT_INCH); preview->priv->orientation = gtk_page_setup_get_orientation (page_setup); } static void preview_got_page_size (GtkPrintOperationPreview *gtk_preview, GtkPrintContext *context, GtkPageSetup *page_setup, XedPrintPreview *preview) { update_paper_size (preview, page_setup); } /* HACK: we need a dummy surface to paginate... can we use something simpler? */ static cairo_status_t dummy_write_func (G_GNUC_UNUSED gpointer closure, G_GNUC_UNUSED const guchar *data, G_GNUC_UNUSED guint length) { return CAIRO_STATUS_SUCCESS; } #define PRINTER_DPI (72.) static cairo_surface_t * create_preview_surface_platform (GtkPaperSize *paper_size, double *dpi_x, double *dpi_y) { double width, height; cairo_surface_t *sf; width = gtk_paper_size_get_width (paper_size, GTK_UNIT_POINTS); height = gtk_paper_size_get_height (paper_size, GTK_UNIT_POINTS); *dpi_x = *dpi_y = PRINTER_DPI; sf = cairo_pdf_surface_create_for_stream (dummy_write_func, NULL, width, height); return sf; } static cairo_surface_t * create_preview_surface (XedPrintPreview *preview, double *dpi_x, double *dpi_y) { GtkPageSetup *page_setup; GtkPaperSize *paper_size; page_setup = gtk_print_context_get_page_setup (preview->priv->context); /* gtk_page_setup_get_paper_size swaps width and height for landscape */ paper_size = gtk_page_setup_get_paper_size (page_setup); return create_preview_surface_platform (paper_size, dpi_x, dpi_y); } GtkWidget * xed_print_preview_new (GtkPrintOperation *op, GtkPrintOperationPreview *gtk_preview, GtkPrintContext *context) { XedPrintPreview *preview; GtkPageSetup *page_setup; cairo_surface_t *surface; cairo_t *cr; double dpi_x, dpi_y; g_return_val_if_fail (GTK_IS_PRINT_OPERATION (op), NULL); g_return_val_if_fail (GTK_IS_PRINT_OPERATION_PREVIEW (gtk_preview), NULL); preview = g_object_new (XED_TYPE_PRINT_PREVIEW, NULL); preview->priv->operation = g_object_ref (op); preview->priv->gtk_preview = g_object_ref (gtk_preview); preview->priv->context = g_object_ref (context); /* FIXME: is this legal?? */ gtk_print_operation_set_unit (op, GTK_UNIT_POINTS); g_signal_connect (gtk_preview, "ready", G_CALLBACK (preview_ready), preview); g_signal_connect (gtk_preview, "got-page-size", G_CALLBACK (preview_got_page_size), preview); page_setup = gtk_print_context_get_page_setup (preview->priv->context); update_paper_size (preview, page_setup); /* FIXME: we need a cr to paginate... but we can't get the drawing * area surface because it's not there yet... for now I create * a dummy pdf surface */ surface = create_preview_surface (preview, &dpi_x, &dpi_y); cr = cairo_create (surface); gtk_print_context_set_cairo_context (context, cr, dpi_x, dpi_y); cairo_destroy (cr); cairo_surface_destroy (surface); return GTK_WIDGET (preview); }