1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008-2014 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/>. */
19 #include "ui/gui/psppire-output-view.h"
24 #include "libpspp/assertion.h"
25 #include "libpspp/string-map.h"
26 #include "output/cairo.h"
27 #include "output/driver-provider.h"
28 #include "output/driver.h"
29 #include "output/chart-item.h"
30 #include "output/message-item.h"
31 #include "output/output-item.h"
32 #include "output/table-item.h"
33 #include "output/text-item.h"
35 #include "gl/c-xvasprintf.h"
36 #include "gl/tmpdir.h"
37 #include "gl/xalloc.h"
40 #define _(msgid) gettext (msgid)
42 struct psppire_output_view
51 struct string_map render_opts;
52 GtkTreeView *overview;
53 GtkTreeIter cur_command;
58 struct output_item **items;
59 size_t n_items, allocated_items;
61 /* Variables pertaining to printing */
62 GtkPrintSettings *print_settings;
63 struct xr_driver *print_xrd;
71 COL_TITLE, /* Table title. */
72 COL_ADDR, /* Pointer to the table */
73 COL_Y, /* Y position of top of title. */
77 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
80 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
82 struct psppire_output_view *view = data;
83 struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
84 cairo_t *cr = gdk_cairo_create (widget->window);
86 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output));
88 PangoFontDescription *font_desc;
92 gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]);
94 string_map_replace (&view->render_opts, "foreground-color", fgc);
98 /* Use GTK+ default font as proportional font. */
99 font_name = pango_font_description_to_string (style->font_desc);
100 string_map_replace (&view->render_opts, "prop-font", font_name);
103 /* Derived emphasized font from proportional font. */
104 font_desc = pango_font_description_copy (style->font_desc);
105 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
106 font_name = pango_font_description_to_string (font_desc);
107 string_map_replace (&view->render_opts, "emph-font", font_name);
109 pango_font_description_free (font_desc);
111 xr_rendering_apply_options (r, &view->render_opts);
113 xr_rendering_draw (r, cr, event->area.x, event->area.y,
114 event->area.width, event->area.height);
121 free_rendering (gpointer rendering_)
123 struct xr_rendering *rendering = rendering_;
124 xr_rendering_destroy (rendering);
128 psppire_output_view_put (struct psppire_output_view *view,
129 const struct output_item *item)
131 GtkWidget *drawing_area;
132 struct xr_rendering *r;
140 if (view->n_items >= view->allocated_items)
141 view->items = x2nrealloc (view->items, &view->allocated_items,
142 sizeof *view->items);
143 view->items[view->n_items++] = output_item_ref (item);
145 if (is_text_item (item))
147 const struct text_item *text_item = to_text_item (item);
148 enum text_item_type type = text_item_get_type (text_item);
149 const char *text = text_item_get_text (text_item);
151 if (type == TEXT_ITEM_COMMAND_CLOSE)
153 view->in_command = false;
156 else if (text[0] == '\0')
160 cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
161 if (view->xr == NULL)
163 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output));
164 struct text_item *text_item;
165 PangoFontDescription *font_desc;
169 /* Set the widget's text color as the foreground color for the output driver */
170 gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]);
172 string_map_insert (&view->render_opts, "foreground-color", fgc);
175 /* Use GTK+ default font as proportional font. */
176 font_name = pango_font_description_to_string (style->font_desc);
177 string_map_insert (&view->render_opts, "prop-font", font_name);
180 /* Derived emphasized font from proportional font. */
181 font_desc = pango_font_description_copy (style->font_desc);
182 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
183 font_name = pango_font_description_to_string (font_desc);
184 string_map_insert (&view->render_opts, "emph-font", font_name);
186 pango_font_description_free (font_desc);
188 /* Pretend that the "page" has a reasonable width and a very big length,
189 so that most tables can be conveniently viewed on-screen with vertical
190 scrolling only. (The length should not be increased very much because
191 it is already close enough to INT_MAX when expressed as thousands of a
193 string_map_insert (&view->render_opts, "paper-size", "300x200000mm");
194 string_map_insert (&view->render_opts, "left-margin", "0");
195 string_map_insert (&view->render_opts, "right-margin", "0");
196 string_map_insert (&view->render_opts, "top-margin", "0");
197 string_map_insert (&view->render_opts, "bottom-margin", "0");
199 view->xr = xr_driver_create (cr, &view->render_opts);
201 text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
202 r = xr_rendering_create (view->xr, text_item_super (text_item), cr);
203 xr_rendering_measure (r, &font_width, &view->font_height);
204 /* xr_rendering_destroy (r); */
205 text_item_unref (text_item);
209 view->y += view->font_height / 2;
211 r = xr_rendering_create (view->xr, item, cr);
215 xr_rendering_measure (r, &tw, &th);
217 drawing_area = gtk_drawing_area_new ();
219 g_object_set_data_full (G_OBJECT (drawing_area), "rendering", r, free_rendering);
220 g_signal_connect (drawing_area, "realize",
221 G_CALLBACK (on_dwgarea_realize), view);
222 g_signal_connect (drawing_area, "expose_event",
223 G_CALLBACK (expose_event_callback), view);
225 gtk_widget_set_size_request (drawing_area, tw, th);
226 gtk_layout_put (view->output, drawing_area, 0, view->y);
228 gtk_widget_show (drawing_area);
231 && (!is_text_item (item)
232 || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
233 || !view->in_command))
235 store = GTK_TREE_STORE (gtk_tree_view_get_model (view->overview));
237 ds_init_empty (&title);
238 if (is_text_item (item)
239 && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
241 gtk_tree_store_append (store, &iter, NULL);
242 view->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
243 view->in_command = true;
247 GtkTreeIter *p = view->in_command ? &view->cur_command : NULL;
248 gtk_tree_store_append (store, &iter, p);
252 if (is_text_item (item))
253 ds_put_cstr (&title, text_item_get_text (to_text_item (item)));
254 else if (is_message_item (item))
256 const struct message_item *msg_item = to_message_item (item);
257 const struct msg *msg = message_item_get_msg (msg_item);
258 ds_put_format (&title, "%s: %s", _("Message"),
259 msg_severity_to_string (msg->severity));
261 else if (is_table_item (item))
263 const char *caption = table_item_get_caption (to_table_item (item));
265 ds_put_format (&title, "Table: %s", caption);
267 ds_put_cstr (&title, "Table");
269 else if (is_chart_item (item))
271 const char *s = chart_item_get_title (to_chart_item (item));
273 ds_put_format (&title, "Chart: %s", s);
275 ds_put_cstr (&title, "Chart");
277 gtk_tree_store_set (store, &iter,
278 COL_TITLE, ds_cstr (&title),
284 path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
285 gtk_tree_view_expand_row (view->overview, path, TRUE);
286 gtk_tree_path_free (path);
289 if (view->max_width < tw)
290 view->max_width = tw;
293 gtk_layout_set_size (view->output, view->max_width, view->y);
300 on_row_activate (GtkTreeView *overview,
302 GtkTreeViewColumn *column,
303 struct psppire_output_view *view)
311 model = gtk_tree_view_get_model (overview);
312 if (!gtk_tree_model_get_iter (model, &iter, path))
315 gtk_tree_model_get_value (model, &iter, COL_Y, &value);
316 y = g_value_get_long (&value);
317 g_value_unset (&value);
319 vadj = gtk_layout_get_vadjustment (view->output);
321 max = vadj->upper - vadj->page_size;
326 gtk_adjustment_set_value (vadj, y);
330 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
333 for (i = 0; i < 5; ++i)
335 gtk_widget_modify_bg (dest, i, >k_widget_get_style (src)->base[i]);
336 gtk_widget_modify_fg (dest, i, >k_widget_get_style (src)->text[i]);
340 /* Copy the base style from the parent widget to the container and all its
341 children. We do this because the container's primary purpose is to display
342 text. This way psppire appears to follow the chosen gnome theme. */
344 on_style_set (GtkWidget *toplevel, GtkStyle *prev,
345 struct psppire_output_view *view)
347 copy_base_to_bg (GTK_WIDGET (view->output), toplevel);
348 gtk_container_foreach (GTK_CONTAINER (view->output),
349 (GtkCallback) copy_base_to_bg, view->output);
353 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
355 copy_base_to_bg (dwg_area, gtk_widget_get_toplevel (dwg_area));
366 /* GNU Hurd doesn't have PATH_MAX. Use a fallback.
367 Temporary directory names are usually not that long. */
369 # define PATH_MAX 1024
373 clipboard_get_cb (GtkClipboard *clipboard,
374 GtkSelectionData *selection_data,
378 struct psppire_output_view *view = data;
382 struct output_driver *driver = NULL;
383 char dirname[PATH_MAX], *filename;
384 struct string_map options;
386 GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
387 GtkTreeModel *model = gtk_tree_view_get_model (view->overview);
389 GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
395 if (path_search (dirname, sizeof dirname, NULL, NULL, true)
396 || mkdtemp (dirname) == NULL)
398 msg_error (errno, _("failed to create temporary directory during clipboard operation"));
401 filename = xasprintf ("%s/clip.tmp", dirname);
403 string_map_init (&options);
404 string_map_insert (&options, "output-file", filename);
408 case SELECT_FMT_UTF8:
409 string_map_insert (&options, "box", "unicode");
412 case SELECT_FMT_TEXT:
413 string_map_insert (&options, "format", "txt");
416 case SELECT_FMT_HTML:
417 string_map_insert (&options, "format", "html");
418 string_map_insert (&options, "borders", "false");
419 string_map_insert (&options, "css", "false");
423 string_map_insert (&options, "format", "odt");
427 g_warning ("unsupported clip target\n");
432 driver = output_driver_create (&options);
438 GtkTreePath *path = n->data ;
440 struct output_item *item ;
442 gtk_tree_model_get_iter (model, &iter, path);
443 gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
445 driver->class->submit (driver, item);
450 if ( driver->class->flush)
451 driver->class->flush (driver);
454 /* Some drivers (eg: the odt one) don't write anything until they
456 output_driver_destroy (driver);
459 if ( g_file_get_contents (filename, &text, &length, NULL) )
461 gtk_selection_data_set (selection_data, selection_data->target,
463 (const guchar *) text, length);
469 output_driver_destroy (driver);
481 clipboard_clear_cb (GtkClipboard *clipboard,
486 static const GtkTargetEntry targets[] = {
488 { "STRING", 0, SELECT_FMT_TEXT },
489 { "TEXT", 0, SELECT_FMT_TEXT },
490 { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
491 { "text/plain", 0, SELECT_FMT_TEXT },
493 { "UTF8_STRING", 0, SELECT_FMT_UTF8 },
494 { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
496 { "text/html", 0, SELECT_FMT_HTML },
498 { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
502 on_copy (struct psppire_output_view *view)
504 GtkWidget *widget = GTK_WIDGET (view->overview);
505 GtkClipboard *cb = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
507 if (!gtk_clipboard_set_with_data (cb, targets, G_N_ELEMENTS (targets),
508 clipboard_get_cb, clipboard_clear_cb,
510 clipboard_clear_cb (cb, view);
514 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
516 /* The Copy action is available only if there is something selected */
517 gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
521 on_select_all (struct psppire_output_view *view)
523 GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
524 gtk_tree_view_expand_all (view->overview);
525 gtk_tree_selection_select_all (sel);
528 struct psppire_output_view *
529 psppire_output_view_new (GtkLayout *output, GtkTreeView *overview,
530 GtkAction *copy_action, GtkAction *select_all_action)
532 struct psppire_output_view *view;
533 GtkTreeViewColumn *column;
534 GtkCellRenderer *renderer;
535 GtkTreeSelection *sel;
538 view = xmalloc (sizeof *view);
540 view->font_height = 0;
541 view->output = output;
544 string_map_init (&view->render_opts);
545 view->overview = overview;
546 memset (&view->cur_command, 0, sizeof view->cur_command);
547 view->in_command = false;
548 view->toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output));
550 view->n_items = view->allocated_items = 0;
551 view->print_settings = NULL;
552 view->print_xrd = NULL;
553 view->print_item = 0;
554 view->print_n_pages = 0;
555 view->paginated = FALSE;
557 g_signal_connect (view->toplevel, "style-set", G_CALLBACK (on_style_set), view);
562 model = GTK_TREE_MODEL (gtk_tree_store_new (
564 G_TYPE_STRING, /* COL_TITLE */
565 G_TYPE_POINTER, /* COL_ADDR */
566 G_TYPE_LONG)); /* COL_Y */
567 gtk_tree_view_set_model (overview, model);
568 g_object_unref (model);
570 sel = gtk_tree_view_get_selection (overview);
571 gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
572 g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change),
575 column = gtk_tree_view_column_new ();
576 gtk_tree_view_append_column (GTK_TREE_VIEW (overview), column);
577 renderer = gtk_cell_renderer_text_new ();
578 gtk_tree_view_column_pack_start (column, renderer, TRUE);
579 gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
581 g_signal_connect (GTK_TREE_VIEW (overview),
582 "row-activated", G_CALLBACK (on_row_activate), view);
584 gtk_action_set_sensitive (copy_action, FALSE);
585 g_signal_connect_swapped (copy_action, "activate",
586 G_CALLBACK (on_copy), view);
587 g_signal_connect_swapped (select_all_action, "activate",
588 G_CALLBACK (on_select_all), view);
595 psppire_output_view_destroy (struct psppire_output_view *view)
602 g_signal_handlers_disconnect_by_func (view->toplevel,
603 G_CALLBACK (on_style_set), view);
605 string_map_destroy (&view->render_opts);
607 for (i = 0; i < view->n_items; i++)
608 output_item_unref (view->items[i]);
611 view->n_items = view->allocated_items = 0;
613 if (view->print_settings != NULL)
614 g_object_unref (view->print_settings);
616 xr_driver_destroy (view->xr);
624 psppire_output_view_export (struct psppire_output_view *view,
625 struct string_map *options)
627 struct output_driver *driver;
629 driver = output_driver_create (options);
634 for (i = 0; i < view->n_items; i++)
635 driver->class->submit (driver, view->items[i]);
636 output_driver_destroy (driver);
643 get_cairo_context_from_print_context (GtkPrintContext *context)
645 cairo_t *cr = gtk_print_context_get_cairo_context (context);
648 For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
651 double xres = gtk_print_context_get_dpi_x (context);
652 double yres = gtk_print_context_get_dpi_y (context);
654 /* This means that the cairo context now has its dimensions in Points */
655 cairo_scale (cr, xres / 72.0, yres / 72.0);
662 create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *view)
664 struct string_map options;
665 GtkPageSetup *page_setup;
666 double width, height;
670 double bottom_margin;
672 page_setup = gtk_print_context_get_page_setup (context);
673 width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
674 height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
675 left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
676 right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
677 top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
678 bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
680 string_map_init (&options);
681 string_map_insert_nocopy (&options, xstrdup ("paper-size"),
682 c_xasprintf("%.2fx%.2fmm", width, height));
683 string_map_insert_nocopy (&options, xstrdup ("left-margin"),
684 c_xasprintf ("%.2fmm", left_margin));
685 string_map_insert_nocopy (&options, xstrdup ("right-margin"),
686 c_xasprintf ("%.2fmm", right_margin));
687 string_map_insert_nocopy (&options, xstrdup ("top-margin"),
688 c_xasprintf ("%.2fmm", top_margin));
689 string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
690 c_xasprintf ("%.2fmm", bottom_margin));
692 view->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
694 string_map_destroy (&options);
698 paginate (GtkPrintOperation *operation,
699 GtkPrintContext *context,
700 struct psppire_output_view *view)
704 /* Sometimes GTK+ emits this signal again even after pagination is
705 complete. Don't let that screw up printing. */
708 else if ( view->print_item < view->n_items )
710 xr_driver_output_item (view->print_xrd, view->items[view->print_item++]);
711 while (xr_driver_need_new_page (view->print_xrd))
713 xr_driver_next_page (view->print_xrd, NULL);
714 view->print_n_pages ++;
720 gtk_print_operation_set_n_pages (operation, view->print_n_pages);
722 /* Re-create the driver to do the real printing. */
723 xr_driver_destroy (view->print_xrd);
724 create_xr_print_driver (context, view);
725 view->print_item = 0;
726 view->paginated = TRUE;
733 begin_print (GtkPrintOperation *operation,
734 GtkPrintContext *context,
735 struct psppire_output_view *view)
737 create_xr_print_driver (context, view);
739 view->print_item = 0;
740 view->print_n_pages = 1;
741 view->paginated = FALSE;
745 end_print (GtkPrintOperation *operation,
746 GtkPrintContext *context,
747 struct psppire_output_view *view)
749 xr_driver_destroy (view->print_xrd);
754 draw_page (GtkPrintOperation *operation,
755 GtkPrintContext *context,
757 struct psppire_output_view *view)
759 xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context));
760 while (!xr_driver_need_new_page (view->print_xrd)
761 && view->print_item < view->n_items)
762 xr_driver_output_item (view->print_xrd, view->items [view->print_item++]);
767 psppire_output_view_print (struct psppire_output_view *view,
768 GtkWindow *parent_window)
770 GtkPrintOperationResult res;
772 GtkPrintOperation *print = gtk_print_operation_new ();
774 if (view->print_settings != NULL)
775 gtk_print_operation_set_print_settings (print, view->print_settings);
777 g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), view);
778 g_signal_connect (print, "end_print", G_CALLBACK (end_print), view);
779 g_signal_connect (print, "paginate", G_CALLBACK (paginate), view);
780 g_signal_connect (print, "draw_page", G_CALLBACK (draw_page), view);
782 res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
783 parent_window, NULL);
785 if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
787 if (view->print_settings != NULL)
788 g_object_unref (view->print_settings);
789 view->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
792 g_object_unref (print);