1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
23 #include <sys/types.h>
26 #include "libpspp/cast.h"
27 #include "libpspp/message.h"
28 #include "libpspp/string-map.h"
29 #include "output/cairo.h"
30 #include "output/chart-item.h"
31 #include "output/driver-provider.h"
32 #include "output/message-item.h"
33 #include "output/output-item.h"
34 #include "output/tab.h"
35 #include "output/table-item.h"
36 #include "output/text-item.h"
37 #include "ui/gui/help-menu.h"
38 #include "ui/gui/builder-wrapper.h"
39 #include "ui/gui/psppire-output-window.h"
41 #include "gl/tmpdir.h"
42 #include "gl/xalloc.h"
43 #include "gl/c-xvasprintf.h"
48 #define _(msgid) gettext (msgid)
49 #define N_(msgid) msgid
53 COL_TITLE, /* Table title. */
54 COL_ADDR, /* Pointer to the table */
55 COL_Y, /* Y position of top of title. */
59 static void psppire_output_window_class_init (PsppireOutputWindowClass *class);
60 static void psppire_output_window_init (PsppireOutputWindow *window);
62 static void psppire_output_window_style_set (GtkWidget *window, GtkStyle *prev);
66 psppire_output_window_get_type (void)
68 static GType psppire_output_window_type = 0;
70 if (!psppire_output_window_type)
72 static const GTypeInfo psppire_output_window_info =
74 sizeof (PsppireOutputWindowClass),
76 (GBaseFinalizeFunc) NULL,
77 (GClassInitFunc)psppire_output_window_class_init,
78 (GClassFinalizeFunc) NULL,
80 sizeof (PsppireOutputWindow),
82 (GInstanceInitFunc) psppire_output_window_init,
85 psppire_output_window_type =
86 g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireOutputWindow",
87 &psppire_output_window_info, 0);
90 return psppire_output_window_type;
93 static GObjectClass *parent_class;
96 psppire_output_window_finalize (GObject *object)
98 string_map_destroy (&PSPPIRE_OUTPUT_WINDOW(object)->render_opts);
101 if (G_OBJECT_CLASS (parent_class)->finalize)
102 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
107 psppire_output_window_dispose (GObject *obj)
109 PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj);
112 if (viewer->dispose_has_run)
115 viewer->dispose_has_run = TRUE;
116 for (i = 0; i < viewer->n_items; i++)
117 output_item_unref (viewer->items[i]);
118 free (viewer->items);
119 viewer->items = NULL;
120 viewer->n_items = viewer->allocated_items = 0;
122 if (viewer->print_settings != NULL)
123 g_object_unref (viewer->print_settings);
125 /* Chain up to the parent class */
126 G_OBJECT_CLASS (parent_class)->dispose (obj);
130 psppire_output_window_class_init (PsppireOutputWindowClass *class)
132 GObjectClass *object_class = G_OBJECT_CLASS (class);
134 parent_class = g_type_class_peek_parent (class);
135 object_class->dispose = psppire_output_window_dispose;
137 GTK_WIDGET_CLASS (object_class)->style_set = psppire_output_window_style_set;
138 object_class->finalize = psppire_output_window_finalize;
143 /* Output driver class. */
145 struct psppire_output_driver
147 struct output_driver driver;
148 PsppireOutputWindow *viewer;
149 struct xr_driver *xr;
153 static struct output_driver_class psppire_output_class;
155 static struct psppire_output_driver *
156 psppire_output_cast (struct output_driver *driver)
158 assert (driver->class == &psppire_output_class);
159 return UP_CAST (driver, struct psppire_output_driver, driver);
162 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
165 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
167 PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (data);
168 struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
169 cairo_t *cr = gdk_cairo_create (widget->window);
171 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
173 PangoFontDescription *font_desc;
177 gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (widget))]);
179 string_map_replace (&viewer->render_opts, "foreground-color", fgc);
183 /* Use GTK+ default font as proportional font. */
184 font_name = pango_font_description_to_string (style->font_desc);
185 string_map_replace (&viewer->render_opts, "prop-font", font_name);
188 /* Derived emphasized font from proportional font. */
189 font_desc = pango_font_description_copy (style->font_desc);
190 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
191 font_name = pango_font_description_to_string (font_desc);
192 string_map_replace (&viewer->render_opts, "emph-font", font_name);
194 pango_font_description_free (font_desc);
196 xr_rendering_apply_options (r, &viewer->render_opts);
198 xr_rendering_draw (r, cr, event->area.x, event->area.y,
199 event->area.width, event->area.height);
206 psppire_output_submit (struct output_driver *this,
207 const struct output_item *item)
209 struct psppire_output_driver *pod = psppire_output_cast (this);
210 PsppireOutputWindow *viewer;
211 GtkWidget *drawing_area;
212 struct xr_rendering *r;
220 if (pod->viewer == NULL)
222 pod->viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
223 gtk_widget_show_all (GTK_WIDGET (pod->viewer));
224 pod->viewer->driver = pod;
226 viewer = pod->viewer;
228 if (viewer->n_items >= viewer->allocated_items)
229 viewer->items = x2nrealloc (viewer->items, &viewer->allocated_items,
230 sizeof *viewer->items);
231 viewer->items[viewer->n_items++] = output_item_ref (item);
233 if (is_text_item (item))
235 const struct text_item *text_item = to_text_item (item);
236 enum text_item_type type = text_item_get_type (text_item);
237 const char *text = text_item_get_text (text_item);
239 if (type == TEXT_ITEM_COMMAND_CLOSE)
241 viewer->in_command = false;
244 else if (text[0] == '\0')
248 cr = gdk_cairo_create (GTK_WIDGET (pod->viewer)->window);
251 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
252 struct text_item *text_item;
253 PangoFontDescription *font_desc;
257 /* Set the widget's text color as the foreground color for the output driver */
258 gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (viewer))]);
260 string_map_insert (&pod->viewer->render_opts, "foreground-color", fgc);
263 /* Use GTK+ default font as proportional font. */
264 font_name = pango_font_description_to_string (style->font_desc);
265 string_map_insert (&pod->viewer->render_opts, "prop-font", font_name);
268 /* Derived emphasized font from proportional font. */
269 font_desc = pango_font_description_copy (style->font_desc);
270 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
271 font_name = pango_font_description_to_string (font_desc);
272 string_map_insert (&pod->viewer->render_opts, "emph-font", font_name);
274 pango_font_description_free (font_desc);
276 /* Pretend that the "page" has a reasonable width and a very big length,
277 so that most tables can be conveniently viewed on-screen with vertical
278 scrolling only. (The length should not be increased very much because
279 it is already close enough to INT_MAX when expressed as thousands of a
281 string_map_insert (&pod->viewer->render_opts, "paper-size", "300x200000mm");
282 string_map_insert (&pod->viewer->render_opts, "left-margin", "0");
283 string_map_insert (&pod->viewer->render_opts, "right-margin", "0");
284 string_map_insert (&pod->viewer->render_opts, "top-margin", "0");
285 string_map_insert (&pod->viewer->render_opts, "bottom-margin", "0");
287 pod->xr = xr_driver_create (cr, &pod->viewer->render_opts);
290 text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
291 r = xr_rendering_create (pod->xr, text_item_super (text_item), cr);
292 xr_rendering_measure (r, &font_width, &pod->font_height);
293 /* xr_rendering_destroy (r); */
294 text_item_unref (text_item);
297 pod->viewer->y += pod->font_height / 2;
299 r = xr_rendering_create (pod->xr, item, cr);
303 xr_rendering_measure (r, &tw, &th);
305 drawing_area = gtk_drawing_area_new ();
307 g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
308 g_signal_connect (drawing_area, "realize",
309 G_CALLBACK (on_dwgarea_realize), pod->viewer);
311 g_signal_connect (drawing_area, "expose_event",
312 G_CALLBACK (expose_event_callback), pod->viewer);
314 gtk_widget_set_size_request (drawing_area, tw, th);
315 gtk_layout_put (pod->viewer->output, drawing_area, 0, pod->viewer->y);
317 gtk_widget_show (drawing_area);
319 if (!is_text_item (item)
320 || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
321 || !viewer->in_command)
323 store = GTK_TREE_STORE (gtk_tree_view_get_model (viewer->overview));
325 ds_init_empty (&title);
326 if (is_text_item (item)
327 && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
329 gtk_tree_store_append (store, &iter, NULL);
330 viewer->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
331 viewer->in_command = true;
335 GtkTreeIter *p = viewer->in_command ? &viewer->cur_command : NULL;
336 gtk_tree_store_append (store, &iter, p);
340 if (is_text_item (item))
341 ds_put_cstr (&title, text_item_get_text (to_text_item (item)));
342 else if (is_message_item (item))
344 const struct message_item *msg_item = to_message_item (item);
345 const struct msg *msg = message_item_get_msg (msg_item);
346 ds_put_format (&title, "%s: %s", _("Message"),
347 msg_severity_to_string (msg->severity));
349 else if (is_table_item (item))
351 const char *caption = table_item_get_caption (to_table_item (item));
353 ds_put_format (&title, "Table: %s", caption);
355 ds_put_cstr (&title, "Table");
357 else if (is_chart_item (item))
359 const char *s = chart_item_get_title (to_chart_item (item));
361 ds_put_format (&title, "Chart: %s", s);
363 ds_put_cstr (&title, "Chart");
365 gtk_tree_store_set (store, &iter,
366 COL_TITLE, ds_cstr (&title),
372 path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
373 gtk_tree_view_expand_row (viewer->overview, path, TRUE);
374 gtk_tree_path_free (path);
377 if (pod->viewer->max_width < tw)
378 pod->viewer->max_width = tw;
379 pod->viewer->y += th;
381 gtk_layout_set_size (pod->viewer->output,
382 pod->viewer->max_width, pod->viewer->y);
384 gtk_window_set_urgency_hint (GTK_WINDOW (pod->viewer), TRUE);
390 static struct output_driver_class psppire_output_class =
392 "PSPPIRE", /* name */
394 psppire_output_submit, /* submit */
399 psppire_output_window_setup (void)
401 struct psppire_output_driver *pod;
402 struct output_driver *d;
404 pod = xzalloc (sizeof *pod);
406 output_driver_init (d, &psppire_output_class, "PSPPIRE",
407 SETTINGS_DEVICE_UNFILTERED);
408 output_driver_register (d);
413 /* Callback for the "delete" action (clicking the x on the top right
414 hand corner of the window) */
416 on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
418 PsppireOutputWindow *ow = PSPPIRE_OUTPUT_WINDOW (user_data);
420 gtk_widget_destroy (GTK_WIDGET (ow));
422 ow->driver->viewer = NULL;
430 cancel_urgency (GtkWindow *window, gpointer data)
432 gtk_window_set_urgency_hint (window, FALSE);
436 on_row_activate (GtkTreeView *overview,
438 GtkTreeViewColumn *column,
439 PsppireOutputWindow *window)
447 model = gtk_tree_view_get_model (overview);
448 if (!gtk_tree_model_get_iter (model, &iter, path))
451 gtk_tree_model_get_value (model, &iter, COL_Y, &value);
452 y = g_value_get_long (&value);
453 g_value_unset (&value);
455 vadj = gtk_layout_get_vadjustment (window->output);
457 max = vadj->upper - vadj->page_size;
462 gtk_adjustment_set_value (vadj, y);
465 static void psppire_output_window_print (PsppireOutputWindow *window);
469 export_output (PsppireOutputWindow *window, struct string_map *options,
472 struct output_driver *driver;
475 string_map_insert (options, "format", format);
476 driver = output_driver_create (options);
480 for (i = 0; i < window->n_items; i++)
481 driver->class->submit (driver, window->items[i]);
482 output_driver_destroy (driver);
504 #define N_EXTENSIONS (n_FT - 1)
506 struct file_types ft[n_FT] = {
507 {N_("Infer file type from extension"), NULL},
508 {N_("PDF (*.pdf)"), ".pdf"},
509 {N_("HTML (*.html)"), ".html"},
510 {N_("OpenDocument (*.odt)"), ".odt"},
511 {N_("Text (*.txt)"), ".txt"},
512 {N_("PostScript (*.ps)"), ".ps"},
513 {N_("Comma-Separated Values (*.csv)"), ".csv"}
518 on_combo_change (GtkFileChooser *chooser)
520 gboolean sensitive = FALSE;
521 GtkWidget *combo = gtk_file_chooser_get_extra_widget (chooser);
524 gchar *fn = gtk_file_chooser_get_filename (chooser);
526 if (combo && gtk_widget_get_realized (combo))
527 x = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
539 for (i = 1 ; i < N_EXTENSIONS ; ++i)
541 if ( g_str_has_suffix (fn, ft[i].ext))
551 gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT, sensitive);
556 on_file_chooser_change (GObject *w, GParamSpec *pspec, gpointer data)
559 GtkFileChooser *chooser = data;
560 const gchar *name = g_param_spec_get_name (pspec);
562 if ( ! gtk_widget_get_realized (GTK_WIDGET (chooser)))
565 /* Ignore this one. It causes recursion. */
566 if ( 0 == strcmp ("tooltip-text", name))
569 on_combo_change (chooser);
573 /* Recursively descend all the children of W, connecting
574 to their "notify" signal */
576 iterate_widgets (GtkWidget *w, gpointer data)
578 if ( GTK_IS_CONTAINER (w))
579 gtk_container_forall (GTK_CONTAINER (w), iterate_widgets, data);
581 g_signal_connect (w, "notify", G_CALLBACK (on_file_chooser_change), data);
586 static GtkListStore *
587 create_file_type_list (void)
591 GtkListStore *list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
593 for (i = 0 ; i < n_FT ; ++i)
595 gtk_list_store_append (list, &iter);
596 gtk_list_store_set (list, &iter,
597 0, gettext (ft[i].label),
606 psppire_output_window_export (PsppireOutputWindow *window)
612 GtkFileChooser *chooser;
614 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Export Output"),
616 GTK_FILE_CHOOSER_ACTION_SAVE,
617 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
618 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
621 g_object_set (dialog, "local-only", FALSE, NULL);
623 chooser = GTK_FILE_CHOOSER (dialog);
625 list = create_file_type_list ();
627 combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list));
631 /* Create text cell renderer */
632 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
633 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, FALSE );
635 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell, "text", 0);
638 g_signal_connect_swapped (combo, "changed", G_CALLBACK (on_combo_change), chooser);
640 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
642 gtk_file_chooser_set_extra_widget (chooser, combo);
644 /* This kludge is necessary because there is no signal to tell us
645 when the candidate filename of a GtkFileChooser has changed */
646 gtk_container_forall (GTK_CONTAINER (dialog), iterate_widgets, dialog);
649 gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
651 response = gtk_dialog_run (GTK_DIALOG (dialog));
653 if ( response == GTK_RESPONSE_ACCEPT )
655 gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
656 gchar *filename = gtk_file_chooser_get_filename (chooser);
657 struct string_map options;
659 g_return_if_fail (filename);
661 if (file_type == FT_AUTO)
663 /* If the "Infer file type from extension" option was chosen,
664 search for the respective type in the list.
665 (It's a O(n) search, but fortunately n is small). */
667 for (i = 1 ; i < N_EXTENSIONS ; ++i)
669 if ( g_str_has_suffix (filename, ft[i].ext))
676 else if (! g_str_has_suffix (filename, ft[file_type].ext))
678 /* If an explicit document format was chosen, and if the chosen
679 filename does not already have that particular "extension",
683 gchar *of = filename;
684 filename = g_strconcat (filename, ft[file_type].ext, NULL);
688 string_map_init (&options);
689 string_map_insert (&options, "output-file", filename);
694 export_output (window, &options, "pdf");
697 export_output (window, &options, "html");
700 export_output (window, &options, "odt");
703 export_output (window, &options, "ps");
706 export_output (window, &options, "csv");
710 string_map_insert (&options, "headers", "false");
711 string_map_insert (&options, "paginate", "false");
712 string_map_insert (&options, "squeeze", "true");
713 string_map_insert (&options, "emphasis", "none");
714 string_map_insert (&options, "charts", "none");
715 string_map_insert (&options, "top-margin", "0");
716 string_map_insert (&options, "bottom-margin", "0");
717 export_output (window, &options, "txt");
720 g_assert_not_reached ();
723 string_map_destroy (&options);
728 gtk_widget_destroy (dialog);
740 /* GNU Hurd doesn't have PATH_MAX. Use a fallback.
741 Temporary directory names are usually not that long. */
743 # define PATH_MAX 1024
747 clipboard_get_cb (GtkClipboard *clipboard,
748 GtkSelectionData *selection_data,
752 PsppireOutputWindow *window = data;
756 struct output_driver *driver = NULL;
757 char dirname[PATH_MAX], *filename;
758 struct string_map options;
760 GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
761 GtkTreeModel *model = gtk_tree_view_get_model (window->overview);
763 GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
769 if (path_search (dirname, sizeof dirname, NULL, NULL, true)
770 || mkdtemp (dirname) == NULL)
772 msg_error (errno, _("failed to create temporary directory during clipboard operation"));
775 filename = xasprintf ("%s/clip.tmp", dirname);
777 string_map_init (&options);
778 string_map_insert (&options, "output-file", filename);
782 case SELECT_FMT_UTF8:
783 string_map_insert (&options, "box", "unicode");
786 case SELECT_FMT_TEXT:
787 string_map_insert (&options, "format", "txt");
790 case SELECT_FMT_HTML:
791 string_map_insert (&options, "format", "html");
792 string_map_insert (&options, "borders", "false");
793 string_map_insert (&options, "css", "false");
797 string_map_insert (&options, "format", "odt");
801 g_warning ("unsupported clip target\n");
806 driver = output_driver_create (&options);
812 GtkTreePath *path = n->data ;
814 struct output_item *item ;
816 gtk_tree_model_get_iter (model, &iter, path);
817 gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
819 driver->class->submit (driver, item);
824 if ( driver->class->flush)
825 driver->class->flush (driver);
828 /* Some drivers (eg: the odt one) don't write anything until they
830 output_driver_destroy (driver);
833 if ( g_file_get_contents (filename, &text, &length, NULL) )
835 gtk_selection_data_set (selection_data, selection_data->target,
837 (const guchar *) text, length);
843 output_driver_destroy (driver);
855 clipboard_clear_cb (GtkClipboard *clipboard,
860 static const GtkTargetEntry targets[] = {
862 { "STRING", 0, SELECT_FMT_TEXT },
863 { "TEXT", 0, SELECT_FMT_TEXT },
864 { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
865 { "text/plain", 0, SELECT_FMT_TEXT },
867 { "UTF8_STRING", 0, SELECT_FMT_UTF8 },
868 { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
870 { "text/html", 0, SELECT_FMT_HTML },
872 { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
876 on_copy (PsppireOutputWindow *window)
879 GtkClipboard *clipboard =
880 gtk_widget_get_clipboard (GTK_WIDGET (window),
881 GDK_SELECTION_CLIPBOARD);
883 if (!gtk_clipboard_set_with_data (clipboard, targets,
884 G_N_ELEMENTS (targets),
885 clipboard_get_cb, clipboard_clear_cb,
888 clipboard_clear_cb (clipboard, window);
893 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
895 /* The Copy action is available only if there is something selected */
896 gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
900 on_select_all (PsppireOutputWindow *window)
902 GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
903 gtk_tree_view_expand_all (window->overview);
904 gtk_tree_selection_select_all (sel);
909 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
912 for (i = 0; i < 5; ++i)
914 GdkColor *col = >k_widget_get_style (src)->base[i];
915 gtk_widget_modify_bg (dest, i, col);
917 col = >k_widget_get_style (src)->text[i];
918 gtk_widget_modify_fg (dest, i, col);
923 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
925 GtkWidget *viewer = GTK_WIDGET (data);
927 copy_base_to_bg (dwg_area, viewer);
932 psppire_output_window_style_set (GtkWidget *w, GtkStyle *prev)
934 GtkWidget *op = GTK_WIDGET (PSPPIRE_OUTPUT_WINDOW (w)->output);
936 /* Copy the base style from the parent widget to the container and
938 We do this, because the container's primary purpose is to
939 display text. This way psppire appears to follow the chosen
942 copy_base_to_bg (op, w);
943 gtk_container_foreach (GTK_CONTAINER (op), (GtkCallback) copy_base_to_bg,
944 PSPPIRE_OUTPUT_WINDOW (w)->output);
946 /* Chain up to the parent class */
947 GTK_WIDGET_CLASS (parent_class)->style_set (w, prev);
951 psppire_output_window_init (PsppireOutputWindow *window)
953 GtkTreeViewColumn *column;
954 GtkCellRenderer *renderer;
956 GtkAction *copy_action;
957 GtkAction *select_all_action;
958 GtkTreeSelection *sel;
961 string_map_init (&window->render_opts);
963 xml = builder_new ("output-viewer.ui");
965 copy_action = get_action_assert (xml, "edit_copy");
966 select_all_action = get_action_assert (xml, "edit_select-all");
968 gtk_action_set_sensitive (copy_action, FALSE);
970 g_signal_connect_swapped (copy_action, "activate", G_CALLBACK (on_copy), window);
972 g_signal_connect_swapped (select_all_action, "activate", G_CALLBACK (on_select_all), window);
974 gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window));
976 window->output = GTK_LAYOUT (get_widget_assert (xml, "output"));
978 window->print_settings = NULL;
979 window->dispose_has_run = FALSE;
981 window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview"));
983 sel = gtk_tree_view_get_selection (window->overview);
985 gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
987 g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), copy_action);
989 model = GTK_TREE_MODEL (gtk_tree_store_new (
991 G_TYPE_STRING, /* COL_TITLE */
992 G_TYPE_POINTER, /* COL_ADDR */
993 G_TYPE_LONG)); /* COL_Y */
994 gtk_tree_view_set_model (window->overview, model);
995 g_object_unref (model);
997 window->in_command = false;
999 window->items = NULL;
1000 window->n_items = window->allocated_items = 0;
1002 column = gtk_tree_view_column_new ();
1003 gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
1004 renderer = gtk_cell_renderer_text_new ();
1005 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1006 gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
1008 g_signal_connect (GTK_TREE_VIEW (window->overview),
1009 "row-activated", G_CALLBACK (on_row_activate), window);
1013 g_signal_connect (window,
1015 G_CALLBACK (cancel_urgency),
1018 g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
1020 G_CALLBACK (psppire_window_minimise_all),
1024 GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
1025 merge_help_menu (uim);
1027 PSPPIRE_WINDOW (window)->menu =
1028 GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all")->parent);
1031 g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
1032 G_CALLBACK (psppire_output_window_export), window);
1035 g_signal_connect_swapped (get_action_assert (xml, "file_print"), "activate",
1036 G_CALLBACK (psppire_output_window_print), window);
1038 g_object_unref (xml);
1040 g_signal_connect (window, "delete-event",
1041 G_CALLBACK (on_delete), window);
1046 psppire_output_window_new (void)
1048 return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
1049 /* TRANSLATORS: This will form a filename. Please avoid whitespace. */
1050 "filename", _("Output"),
1051 "description", _("Output Viewer"),
1058 get_cairo_context_from_print_context (GtkPrintContext *context)
1060 cairo_t *cr = gtk_print_context_get_cairo_context (context);
1063 For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
1064 Windows returns 600.
1066 double xres = gtk_print_context_get_dpi_x (context);
1067 double yres = gtk_print_context_get_dpi_y (context);
1069 /* This means that the cairo context now has its dimensions in Points */
1070 cairo_scale (cr, xres / 72.0, yres / 72.0);
1077 create_xr_print_driver (GtkPrintContext *context, PsppireOutputWindow *window)
1079 struct string_map options;
1080 GtkPageSetup *page_setup;
1081 double width, height;
1083 double right_margin;
1085 double bottom_margin;
1087 page_setup = gtk_print_context_get_page_setup (context);
1088 width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
1089 height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
1090 left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
1091 right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
1092 top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
1093 bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
1095 string_map_init (&options);
1096 string_map_insert_nocopy (&options, xstrdup ("paper-size"),
1097 c_xasprintf("%.2fx%.2fmm", width, height));
1098 string_map_insert_nocopy (&options, xstrdup ("left-margin"),
1099 c_xasprintf ("%.2fmm", left_margin));
1100 string_map_insert_nocopy (&options, xstrdup ("right-margin"),
1101 c_xasprintf ("%.2fmm", right_margin));
1102 string_map_insert_nocopy (&options, xstrdup ("top-margin"),
1103 c_xasprintf ("%.2fmm", top_margin));
1104 string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
1105 c_xasprintf ("%.2fmm", bottom_margin));
1107 window->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
1109 string_map_destroy (&options);
1113 paginate (GtkPrintOperation *operation,
1114 GtkPrintContext *context,
1115 PsppireOutputWindow *window)
1117 if (window->paginated)
1119 /* Sometimes GTK+ emits this signal again even after pagination is
1120 complete. Don't let that screw up printing. */
1123 else if ( window->print_item < window->n_items )
1125 xr_driver_output_item (window->print_xrd, window->items[window->print_item++]);
1126 while (xr_driver_need_new_page (window->print_xrd))
1128 xr_driver_next_page (window->print_xrd, NULL);
1129 window->print_n_pages ++;
1135 gtk_print_operation_set_n_pages (operation, window->print_n_pages);
1137 /* Re-create the driver to do the real printing. */
1138 xr_driver_destroy (window->print_xrd);
1139 create_xr_print_driver (context, window);
1140 window->print_item = 0;
1141 window->paginated = TRUE;
1148 begin_print (GtkPrintOperation *operation,
1149 GtkPrintContext *context,
1150 PsppireOutputWindow *window)
1152 create_xr_print_driver (context, window);
1154 window->print_item = 0;
1155 window->print_n_pages = 1;
1156 window->paginated = FALSE;
1160 end_print (GtkPrintOperation *operation,
1161 GtkPrintContext *context,
1162 PsppireOutputWindow *window)
1164 xr_driver_destroy (window->print_xrd);
1169 draw_page (GtkPrintOperation *operation,
1170 GtkPrintContext *context,
1172 PsppireOutputWindow *window)
1174 xr_driver_next_page (window->print_xrd, get_cairo_context_from_print_context (context));
1175 while (!xr_driver_need_new_page (window->print_xrd)
1176 && window->print_item < window->n_items)
1177 xr_driver_output_item (window->print_xrd, window->items [window->print_item++]);
1182 psppire_output_window_print (PsppireOutputWindow *window)
1184 GtkPrintOperationResult res;
1186 GtkPrintOperation *print = gtk_print_operation_new ();
1188 if (window->print_settings != NULL)
1189 gtk_print_operation_set_print_settings (print, window->print_settings);
1191 g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), window);
1192 g_signal_connect (print, "end_print", G_CALLBACK (end_print), window);
1193 g_signal_connect (print, "paginate", G_CALLBACK (paginate), window);
1194 g_signal_connect (print, "draw_page", G_CALLBACK (draw_page), window);
1196 res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
1197 GTK_WINDOW (window), NULL);
1199 if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
1201 if (window->print_settings != NULL)
1202 g_object_unref (window->print_settings);
1203 window->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
1206 g_object_unref (print);