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"
42 #include "gl/tmpdir.h"
43 #include "gl/xalloc.h"
44 #include "gl/c-xvasprintf.h"
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) msgid
54 COL_TITLE, /* Table title. */
55 COL_ADDR, /* Pointer to the table */
56 COL_Y, /* Y position of top of title. */
60 static void psppire_output_window_class_init (PsppireOutputWindowClass *class);
61 static void psppire_output_window_init (PsppireOutputWindow *window);
63 static void psppire_output_window_style_set (GtkWidget *window, GtkStyle *prev);
67 psppire_output_window_get_type (void)
69 static GType psppire_output_window_type = 0;
71 if (!psppire_output_window_type)
73 static const GTypeInfo psppire_output_window_info =
75 sizeof (PsppireOutputWindowClass),
77 (GBaseFinalizeFunc) NULL,
78 (GClassInitFunc)psppire_output_window_class_init,
79 (GClassFinalizeFunc) NULL,
81 sizeof (PsppireOutputWindow),
83 (GInstanceInitFunc) psppire_output_window_init,
86 psppire_output_window_type =
87 g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireOutputWindow",
88 &psppire_output_window_info, 0);
91 return psppire_output_window_type;
94 static GObjectClass *parent_class;
97 psppire_output_window_finalize (GObject *object)
99 string_map_destroy (&PSPPIRE_OUTPUT_WINDOW(object)->render_opts);
102 if (G_OBJECT_CLASS (parent_class)->finalize)
103 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
108 psppire_output_window_dispose (GObject *obj)
110 PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj);
113 if (viewer->dispose_has_run)
116 viewer->dispose_has_run = TRUE;
117 for (i = 0; i < viewer->n_items; i++)
118 output_item_unref (viewer->items[i]);
119 free (viewer->items);
120 viewer->items = NULL;
121 viewer->n_items = viewer->allocated_items = 0;
123 if (viewer->print_settings != NULL)
124 g_object_unref (viewer->print_settings);
126 /* Chain up to the parent class */
127 G_OBJECT_CLASS (parent_class)->dispose (obj);
131 psppire_output_window_class_init (PsppireOutputWindowClass *class)
133 GObjectClass *object_class = G_OBJECT_CLASS (class);
135 parent_class = g_type_class_peek_parent (class);
136 object_class->dispose = psppire_output_window_dispose;
138 GTK_WIDGET_CLASS (object_class)->style_set = psppire_output_window_style_set;
139 object_class->finalize = psppire_output_window_finalize;
144 /* Output driver class. */
146 struct psppire_output_driver
148 struct output_driver driver;
149 PsppireOutputWindow *viewer;
150 struct xr_driver *xr;
154 static struct output_driver_class psppire_output_class;
156 static struct psppire_output_driver *
157 psppire_output_cast (struct output_driver *driver)
159 assert (driver->class == &psppire_output_class);
160 return UP_CAST (driver, struct psppire_output_driver, driver);
163 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
166 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
168 PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (data);
169 struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
170 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
172 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
174 PangoFontDescription *font_desc;
178 gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (widget))]);
180 string_map_replace (&viewer->render_opts, "foreground-color", fgc);
184 /* Use GTK+ default font as proportional font. */
185 font_name = pango_font_description_to_string (style->font_desc);
186 string_map_replace (&viewer->render_opts, "prop-font", font_name);
189 /* Derived emphasized font from proportional font. */
190 font_desc = pango_font_description_copy (style->font_desc);
191 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
192 font_name = pango_font_description_to_string (font_desc);
193 string_map_replace (&viewer->render_opts, "emph-font", font_name);
195 pango_font_description_free (font_desc);
197 xr_rendering_apply_options (r, &viewer->render_opts);
199 xr_rendering_draw_all (r, cr);
208 psppire_output_submit (struct output_driver *this,
209 const struct output_item *item)
211 struct psppire_output_driver *pod = psppire_output_cast (this);
212 PsppireOutputWindow *viewer;
213 GtkWidget *drawing_area;
214 struct xr_rendering *r;
222 if (pod->viewer == NULL)
224 pod->viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
225 gtk_widget_show_all (GTK_WIDGET (pod->viewer));
226 pod->viewer->driver = pod;
228 viewer = pod->viewer;
230 if (viewer->n_items >= viewer->allocated_items)
231 viewer->items = x2nrealloc (viewer->items, &viewer->allocated_items,
232 sizeof *viewer->items);
233 viewer->items[viewer->n_items++] = output_item_ref (item);
235 if (is_text_item (item))
237 const struct text_item *text_item = to_text_item (item);
238 enum text_item_type type = text_item_get_type (text_item);
239 const char *text = text_item_get_text (text_item);
241 if (type == TEXT_ITEM_COMMAND_CLOSE)
243 viewer->in_command = false;
246 else if (text[0] == '\0')
250 cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (pod->viewer)));
253 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (viewer));
254 struct text_item *text_item;
255 PangoFontDescription *font_desc;
259 /* Set the widget's text color as the foreground color for the output driver */
260 gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (viewer))]);
262 string_map_insert (&pod->viewer->render_opts, "foreground-color", fgc);
265 /* Use GTK+ default font as proportional font. */
266 font_name = pango_font_description_to_string (style->font_desc);
267 string_map_insert (&pod->viewer->render_opts, "prop-font", font_name);
270 /* Derived emphasized font from proportional font. */
271 font_desc = pango_font_description_copy (style->font_desc);
272 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
273 font_name = pango_font_description_to_string (font_desc);
274 string_map_insert (&pod->viewer->render_opts, "emph-font", font_name);
276 pango_font_description_free (font_desc);
278 /* Pretend that the "page" has a reasonable width and a very big length,
279 so that most tables can be conveniently viewed on-screen with vertical
280 scrolling only. (The length should not be increased very much because
281 it is already close enough to INT_MAX when expressed as thousands of a
283 string_map_insert (&pod->viewer->render_opts, "paper-size", "300x200000mm");
284 string_map_insert (&pod->viewer->render_opts, "left-margin", "0");
285 string_map_insert (&pod->viewer->render_opts, "right-margin", "0");
286 string_map_insert (&pod->viewer->render_opts, "top-margin", "0");
287 string_map_insert (&pod->viewer->render_opts, "bottom-margin", "0");
289 pod->xr = xr_driver_create (cr, &pod->viewer->render_opts);
292 text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
293 r = xr_rendering_create (pod->xr, text_item_super (text_item), cr);
294 xr_rendering_measure (r, &font_width, &pod->font_height);
295 /* xr_rendering_destroy (r); */
296 text_item_unref (text_item);
299 pod->viewer->y += pod->font_height / 2;
301 r = xr_rendering_create (pod->xr, item, cr);
305 xr_rendering_measure (r, &tw, &th);
307 drawing_area = gtk_drawing_area_new ();
309 g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
310 g_signal_connect (drawing_area, "realize",
311 G_CALLBACK (on_dwgarea_realize), pod->viewer);
313 g_signal_connect (drawing_area, "expose_event",
314 G_CALLBACK (expose_event_callback), pod->viewer);
316 gtk_widget_set_size_request (drawing_area, tw, th);
317 gtk_layout_put (pod->viewer->output, drawing_area, 0, pod->viewer->y);
319 gtk_widget_show (drawing_area);
321 if (!is_text_item (item)
322 || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
323 || !viewer->in_command)
325 store = GTK_TREE_STORE (gtk_tree_view_get_model (viewer->overview));
327 ds_init_empty (&title);
328 if (is_text_item (item)
329 && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
331 gtk_tree_store_append (store, &iter, NULL);
332 viewer->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
333 viewer->in_command = true;
337 GtkTreeIter *p = viewer->in_command ? &viewer->cur_command : NULL;
338 gtk_tree_store_append (store, &iter, p);
342 if (is_text_item (item))
343 ds_put_cstr (&title, text_item_get_text (to_text_item (item)));
344 else if (is_message_item (item))
346 const struct message_item *msg_item = to_message_item (item);
347 const struct msg *msg = message_item_get_msg (msg_item);
348 ds_put_format (&title, "%s: %s", _("Message"),
349 msg_severity_to_string (msg->severity));
351 else if (is_table_item (item))
353 const char *caption = table_item_get_caption (to_table_item (item));
355 ds_put_format (&title, "Table: %s", caption);
357 ds_put_cstr (&title, "Table");
359 else if (is_chart_item (item))
361 const char *s = chart_item_get_title (to_chart_item (item));
363 ds_put_format (&title, "Chart: %s", s);
365 ds_put_cstr (&title, "Chart");
367 gtk_tree_store_set (store, &iter,
368 COL_TITLE, ds_cstr (&title),
374 path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
375 gtk_tree_view_expand_row (viewer->overview, path, TRUE);
376 gtk_tree_path_free (path);
379 if (pod->viewer->max_width < tw)
380 pod->viewer->max_width = tw;
381 pod->viewer->y += th;
383 gtk_layout_set_size (pod->viewer->output,
384 pod->viewer->max_width, pod->viewer->y);
386 gtk_window_set_urgency_hint (GTK_WINDOW (pod->viewer), TRUE);
392 static struct output_driver_class psppire_output_class =
394 "PSPPIRE", /* name */
396 psppire_output_submit, /* submit */
401 psppire_output_window_setup (void)
403 struct psppire_output_driver *pod;
404 struct output_driver *d;
406 pod = xzalloc (sizeof *pod);
408 output_driver_init (d, &psppire_output_class, "PSPPIRE",
409 SETTINGS_DEVICE_UNFILTERED);
410 output_driver_register (d);
415 /* Callback for the "delete" action (clicking the x on the top right
416 hand corner of the window) */
418 on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
420 PsppireOutputWindow *ow = PSPPIRE_OUTPUT_WINDOW (user_data);
422 gtk_widget_destroy (GTK_WIDGET (ow));
424 ow->driver->viewer = NULL;
432 cancel_urgency (GtkWindow *window, gpointer data)
434 gtk_window_set_urgency_hint (window, FALSE);
438 on_row_activate (GtkTreeView *overview,
440 GtkTreeViewColumn *column,
441 PsppireOutputWindow *window)
449 model = gtk_tree_view_get_model (overview);
450 if (!gtk_tree_model_get_iter (model, &iter, path))
453 gtk_tree_model_get_value (model, &iter, COL_Y, &value);
454 y = g_value_get_long (&value);
455 g_value_unset (&value);
457 vadj = gtk_layout_get_vadjustment (window->output);
458 min = gtk_adjustment_get_lower (vadj);
459 max = gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj);
464 gtk_adjustment_set_value (vadj, y);
467 static void psppire_output_window_print (PsppireOutputWindow *window);
471 export_output (PsppireOutputWindow *window, struct string_map *options,
474 struct output_driver *driver;
477 string_map_insert (options, "format", format);
478 driver = output_driver_create (options);
482 for (i = 0; i < window->n_items; i++)
483 driver->class->submit (driver, window->items[i]);
484 output_driver_destroy (driver);
506 #define N_EXTENSIONS (n_FT - 1)
508 struct file_types ft[n_FT] = {
509 {N_("Infer file type from extension"), NULL},
510 {N_("PDF (*.pdf)"), ".pdf"},
511 {N_("HTML (*.html)"), ".html"},
512 {N_("OpenDocument (*.odt)"), ".odt"},
513 {N_("Text (*.txt)"), ".txt"},
514 {N_("PostScript (*.ps)"), ".ps"},
515 {N_("Comma-Separated Values (*.csv)"), ".csv"}
520 on_combo_change (GtkFileChooser *chooser)
522 gboolean sensitive = FALSE;
523 GtkWidget *combo = gtk_file_chooser_get_extra_widget (chooser);
526 gchar *fn = gtk_file_chooser_get_filename (chooser);
528 if (combo && gtk_widget_get_realized (combo))
529 x = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
541 for (i = 1 ; i < N_EXTENSIONS ; ++i)
543 if ( g_str_has_suffix (fn, ft[i].ext))
553 gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT, sensitive);
558 on_file_chooser_change (GObject *w, GParamSpec *pspec, gpointer data)
561 GtkFileChooser *chooser = data;
562 const gchar *name = g_param_spec_get_name (pspec);
564 if ( ! gtk_widget_get_realized (GTK_WIDGET (chooser)))
567 /* Ignore this one. It causes recursion. */
568 if ( 0 == strcmp ("tooltip-text", name))
571 on_combo_change (chooser);
575 /* Recursively descend all the children of W, connecting
576 to their "notify" signal */
578 iterate_widgets (GtkWidget *w, gpointer data)
580 if ( GTK_IS_CONTAINER (w))
581 gtk_container_forall (GTK_CONTAINER (w), iterate_widgets, data);
583 g_signal_connect (w, "notify", G_CALLBACK (on_file_chooser_change), data);
588 static GtkListStore *
589 create_file_type_list (void)
593 GtkListStore *list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
595 for (i = 0 ; i < n_FT ; ++i)
597 gtk_list_store_append (list, &iter);
598 gtk_list_store_set (list, &iter,
599 0, gettext (ft[i].label),
608 psppire_output_window_export (PsppireOutputWindow *window)
614 GtkFileChooser *chooser;
616 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Export Output"),
618 GTK_FILE_CHOOSER_ACTION_SAVE,
619 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
620 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
623 g_object_set (dialog, "local-only", FALSE, NULL);
625 chooser = GTK_FILE_CHOOSER (dialog);
627 list = create_file_type_list ();
629 combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list));
633 /* Create text cell renderer */
634 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
635 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, FALSE );
637 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell, "text", 0);
640 g_signal_connect_swapped (combo, "changed", G_CALLBACK (on_combo_change), chooser);
642 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
644 gtk_file_chooser_set_extra_widget (chooser, combo);
646 /* This kludge is necessary because there is no signal to tell us
647 when the candidate filename of a GtkFileChooser has changed */
648 gtk_container_forall (GTK_CONTAINER (dialog), iterate_widgets, dialog);
651 gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
653 response = gtk_dialog_run (GTK_DIALOG (dialog));
655 if ( response == GTK_RESPONSE_ACCEPT )
657 gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
658 gchar *filename = gtk_file_chooser_get_filename (chooser);
659 struct string_map options;
661 g_return_if_fail (filename);
663 if (file_type == FT_AUTO)
665 /* If the "Infer file type from extension" option was chosen,
666 search for the respective type in the list.
667 (It's a O(n) search, but fortunately n is small). */
669 for (i = 1 ; i < N_EXTENSIONS ; ++i)
671 if ( g_str_has_suffix (filename, ft[i].ext))
678 else if (! g_str_has_suffix (filename, ft[file_type].ext))
680 /* If an explicit document format was chosen, and if the chosen
681 filename does not already have that particular "extension",
685 gchar *of = filename;
686 filename = g_strconcat (filename, ft[file_type].ext, NULL);
690 string_map_init (&options);
691 string_map_insert (&options, "output-file", filename);
696 export_output (window, &options, "pdf");
699 export_output (window, &options, "html");
702 export_output (window, &options, "odt");
705 export_output (window, &options, "ps");
708 export_output (window, &options, "csv");
712 string_map_insert (&options, "headers", "false");
713 string_map_insert (&options, "paginate", "false");
714 string_map_insert (&options, "squeeze", "true");
715 string_map_insert (&options, "emphasis", "none");
716 string_map_insert (&options, "charts", "none");
717 string_map_insert (&options, "top-margin", "0");
718 string_map_insert (&options, "bottom-margin", "0");
719 export_output (window, &options, "txt");
722 g_assert_not_reached ();
725 string_map_destroy (&options);
730 gtk_widget_destroy (dialog);
742 /* GNU Hurd doesn't have PATH_MAX. Use a fallback.
743 Temporary directory names are usually not that long. */
745 # define PATH_MAX 1024
749 clipboard_get_cb (GtkClipboard *clipboard,
750 GtkSelectionData *selection_data,
754 PsppireOutputWindow *window = data;
758 struct output_driver *driver = NULL;
759 char dirname[PATH_MAX], *filename;
760 struct string_map options;
762 GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
763 GtkTreeModel *model = gtk_tree_view_get_model (window->overview);
765 GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
771 if (path_search (dirname, sizeof dirname, NULL, NULL, true)
772 || mkdtemp (dirname) == NULL)
774 error (0, errno, _("failed to create temporary directory"));
777 filename = xasprintf ("%s/clip.tmp", dirname);
779 string_map_init (&options);
780 string_map_insert (&options, "output-file", filename);
784 case SELECT_FMT_UTF8:
785 string_map_insert (&options, "box", "unicode");
788 case SELECT_FMT_TEXT:
789 string_map_insert (&options, "format", "txt");
792 case SELECT_FMT_HTML:
793 string_map_insert (&options, "format", "html");
794 string_map_insert (&options, "borders", "false");
795 string_map_insert (&options, "css", "false");
799 string_map_insert (&options, "format", "odt");
803 g_warning ("unsupported clip target\n");
808 driver = output_driver_create (&options);
814 GtkTreePath *path = n->data ;
816 struct output_item *item ;
818 gtk_tree_model_get_iter (model, &iter, path);
819 gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
821 driver->class->submit (driver, item);
826 if ( driver->class->flush)
827 driver->class->flush (driver);
830 /* Some drivers (eg: the odt one) don't write anything until they
832 output_driver_destroy (driver);
835 if ( g_file_get_contents (filename, &text, &length, NULL) )
837 gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
839 (const guchar *) text, length);
845 output_driver_destroy (driver);
857 clipboard_clear_cb (GtkClipboard *clipboard,
862 static const GtkTargetEntry targets[] = {
864 { "STRING", 0, SELECT_FMT_TEXT },
865 { "TEXT", 0, SELECT_FMT_TEXT },
866 { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
867 { "text/plain", 0, SELECT_FMT_TEXT },
869 { "UTF8_STRING", 0, SELECT_FMT_UTF8 },
870 { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
872 { "text/html", 0, SELECT_FMT_HTML },
874 { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
878 on_copy (PsppireOutputWindow *window)
881 GtkClipboard *clipboard =
882 gtk_widget_get_clipboard (GTK_WIDGET (window),
883 GDK_SELECTION_CLIPBOARD);
885 if (!gtk_clipboard_set_with_data (clipboard, targets,
886 G_N_ELEMENTS (targets),
887 clipboard_get_cb, clipboard_clear_cb,
890 clipboard_clear_cb (clipboard, window);
895 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
897 /* The Copy action is available only if there is something selected */
898 gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
902 on_select_all (PsppireOutputWindow *window)
904 GtkTreeSelection *sel = gtk_tree_view_get_selection (window->overview);
905 gtk_tree_view_expand_all (window->overview);
906 gtk_tree_selection_select_all (sel);
911 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
914 for (i = 0; i < 5; ++i)
916 GdkColor *col = >k_widget_get_style (src)->base[i];
917 gtk_widget_modify_bg (dest, i, col);
919 col = >k_widget_get_style (src)->text[i];
920 gtk_widget_modify_fg (dest, i, col);
925 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
927 GtkWidget *viewer = GTK_WIDGET (data);
929 copy_base_to_bg (dwg_area, viewer);
934 psppire_output_window_style_set (GtkWidget *w, GtkStyle *prev)
936 GtkWidget *op = GTK_WIDGET (PSPPIRE_OUTPUT_WINDOW (w)->output);
938 /* Copy the base style from the parent widget to the container and
940 We do this, because the container's primary purpose is to
941 display text. This way psppire appears to follow the chosen
944 copy_base_to_bg (op, w);
945 gtk_container_foreach (GTK_CONTAINER (op), (GtkCallback) copy_base_to_bg,
946 PSPPIRE_OUTPUT_WINDOW (w)->output);
948 /* Chain up to the parent class */
949 GTK_WIDGET_CLASS (parent_class)->style_set (w, prev);
953 psppire_output_window_init (PsppireOutputWindow *window)
955 GtkTreeViewColumn *column;
956 GtkCellRenderer *renderer;
958 GtkAction *copy_action;
959 GtkAction *select_all_action;
960 GtkTreeSelection *sel;
963 string_map_init (&window->render_opts);
965 xml = builder_new ("output-viewer.ui");
967 copy_action = get_action_assert (xml, "edit_copy");
968 select_all_action = get_action_assert (xml, "edit_select-all");
970 gtk_action_set_sensitive (copy_action, FALSE);
972 g_signal_connect_swapped (copy_action, "activate", G_CALLBACK (on_copy), window);
974 g_signal_connect_swapped (select_all_action, "activate", G_CALLBACK (on_select_all), window);
976 gtk_widget_reparent (get_widget_assert (xml, "vbox1"), GTK_WIDGET (window));
978 window->output = GTK_LAYOUT (get_widget_assert (xml, "output"));
980 window->print_settings = NULL;
981 window->dispose_has_run = FALSE;
983 window->overview = GTK_TREE_VIEW (get_widget_assert (xml, "overview"));
985 sel = gtk_tree_view_get_selection (window->overview);
987 gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
989 g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), copy_action);
991 model = GTK_TREE_MODEL (gtk_tree_store_new (
993 G_TYPE_STRING, /* COL_TITLE */
994 G_TYPE_POINTER, /* COL_ADDR */
995 G_TYPE_LONG)); /* COL_Y */
996 gtk_tree_view_set_model (window->overview, model);
997 g_object_unref (model);
999 window->in_command = false;
1001 window->items = NULL;
1002 window->n_items = window->allocated_items = 0;
1004 column = gtk_tree_view_column_new ();
1005 gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
1006 renderer = gtk_cell_renderer_text_new ();
1007 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1008 gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
1010 g_signal_connect (GTK_TREE_VIEW (window->overview),
1011 "row-activated", G_CALLBACK (on_row_activate), window);
1015 g_signal_connect (window,
1017 G_CALLBACK (cancel_urgency),
1020 g_signal_connect (get_action_assert (xml,"windows_minimise-all"),
1022 G_CALLBACK (psppire_window_minimise_all),
1027 GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
1028 merge_help_menu (uim);
1030 w = gtk_ui_manager_get_widget (uim,"/ui/menubar/windows_menuitem/windows_minimise-all");
1032 PSPPIRE_WINDOW (window)->menu =
1033 GTK_MENU_SHELL (gtk_widget_get_parent (w));
1036 g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
1037 G_CALLBACK (psppire_output_window_export), window);
1040 g_signal_connect_swapped (get_action_assert (xml, "file_print"), "activate",
1041 G_CALLBACK (psppire_output_window_print), window);
1043 g_object_unref (xml);
1045 g_signal_connect (window, "delete-event",
1046 G_CALLBACK (on_delete), window);
1051 psppire_output_window_new (void)
1053 return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
1054 /* TRANSLATORS: This will form a filename. Please avoid whitespace. */
1055 "filename", _("Output"),
1056 "description", _("Output Viewer"),
1063 get_cairo_context_from_print_context (GtkPrintContext *context)
1065 cairo_t *cr = gtk_print_context_get_cairo_context (context);
1068 For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
1069 Windows returns 600.
1071 double xres = gtk_print_context_get_dpi_x (context);
1072 double yres = gtk_print_context_get_dpi_y (context);
1074 /* This means that the cairo context now has its dimensions in Points */
1075 cairo_scale (cr, xres / 72.0, yres / 72.0);
1082 create_xr_print_driver (GtkPrintContext *context, PsppireOutputWindow *window)
1084 struct string_map options;
1085 GtkPageSetup *page_setup;
1086 double width, height;
1088 double right_margin;
1090 double bottom_margin;
1092 page_setup = gtk_print_context_get_page_setup (context);
1093 width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
1094 height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
1095 left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
1096 right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
1097 top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
1098 bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
1100 string_map_init (&options);
1101 string_map_insert_nocopy (&options, xstrdup ("paper-size"),
1102 c_xasprintf("%.2fx%.2fmm", width, height));
1103 string_map_insert_nocopy (&options, xstrdup ("left-margin"),
1104 c_xasprintf ("%.2fmm", left_margin));
1105 string_map_insert_nocopy (&options, xstrdup ("right-margin"),
1106 c_xasprintf ("%.2fmm", right_margin));
1107 string_map_insert_nocopy (&options, xstrdup ("top-margin"),
1108 c_xasprintf ("%.2fmm", top_margin));
1109 string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
1110 c_xasprintf ("%.2fmm", bottom_margin));
1112 window->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
1114 string_map_destroy (&options);
1118 paginate (GtkPrintOperation *operation,
1119 GtkPrintContext *context,
1120 PsppireOutputWindow *window)
1122 if (window->paginated)
1124 /* Sometimes GTK+ emits this signal again even after pagination is
1125 complete. Don't let that screw up printing. */
1128 else if ( window->print_item < window->n_items )
1130 xr_driver_output_item (window->print_xrd, window->items[window->print_item++]);
1131 while (xr_driver_need_new_page (window->print_xrd))
1133 xr_driver_next_page (window->print_xrd, NULL);
1134 window->print_n_pages ++;
1140 gtk_print_operation_set_n_pages (operation, window->print_n_pages);
1142 /* Re-create the driver to do the real printing. */
1143 xr_driver_destroy (window->print_xrd);
1144 create_xr_print_driver (context, window);
1145 window->print_item = 0;
1146 window->paginated = TRUE;
1153 begin_print (GtkPrintOperation *operation,
1154 GtkPrintContext *context,
1155 PsppireOutputWindow *window)
1157 create_xr_print_driver (context, window);
1159 window->print_item = 0;
1160 window->print_n_pages = 1;
1161 window->paginated = FALSE;
1165 end_print (GtkPrintOperation *operation,
1166 GtkPrintContext *context,
1167 PsppireOutputWindow *window)
1169 xr_driver_destroy (window->print_xrd);
1174 draw_page (GtkPrintOperation *operation,
1175 GtkPrintContext *context,
1177 PsppireOutputWindow *window)
1179 xr_driver_next_page (window->print_xrd, get_cairo_context_from_print_context (context));
1180 while (!xr_driver_need_new_page (window->print_xrd)
1181 && window->print_item < window->n_items)
1182 xr_driver_output_item (window->print_xrd, window->items [window->print_item++]);
1187 psppire_output_window_print (PsppireOutputWindow *window)
1189 GtkPrintOperationResult res;
1191 GtkPrintOperation *print = gtk_print_operation_new ();
1193 if (window->print_settings != NULL)
1194 gtk_print_operation_set_print_settings (print, window->print_settings);
1196 g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), window);
1197 g_signal_connect (print, "end_print", G_CALLBACK (end_print), window);
1198 g_signal_connect (print, "paginate", G_CALLBACK (paginate), window);
1199 g_signal_connect (print, "draw_page", G_CALLBACK (draw_page), window);
1201 res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
1202 GTK_WINDOW (window), NULL);
1204 if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
1206 if (window->print_settings != NULL)
1207 g_object_unref (window->print_settings);
1208 window->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
1211 g_object_unref (print);