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 psppire_output_view_put (struct psppire_output_view *view,
122 const struct output_item *item)
124 GtkWidget *drawing_area;
125 struct xr_rendering *r;
133 if (view->n_items >= view->allocated_items)
134 view->items = x2nrealloc (view->items, &view->allocated_items,
135 sizeof *view->items);
136 view->items[view->n_items++] = output_item_ref (item);
138 if (is_text_item (item))
140 const struct text_item *text_item = to_text_item (item);
141 enum text_item_type type = text_item_get_type (text_item);
142 const char *text = text_item_get_text (text_item);
144 if (type == TEXT_ITEM_COMMAND_CLOSE)
146 view->in_command = false;
149 else if (text[0] == '\0')
153 cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
154 if (view->xr == NULL)
156 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output));
157 struct text_item *text_item;
158 PangoFontDescription *font_desc;
162 /* Set the widget's text color as the foreground color for the output driver */
163 gchar *fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]);
165 string_map_insert (&view->render_opts, "foreground-color", fgc);
168 /* Use GTK+ default font as proportional font. */
169 font_name = pango_font_description_to_string (style->font_desc);
170 string_map_insert (&view->render_opts, "prop-font", font_name);
173 /* Derived emphasized font from proportional font. */
174 font_desc = pango_font_description_copy (style->font_desc);
175 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
176 font_name = pango_font_description_to_string (font_desc);
177 string_map_insert (&view->render_opts, "emph-font", font_name);
179 pango_font_description_free (font_desc);
181 /* Pretend that the "page" has a reasonable width and a very big length,
182 so that most tables can be conveniently viewed on-screen with vertical
183 scrolling only. (The length should not be increased very much because
184 it is already close enough to INT_MAX when expressed as thousands of a
186 string_map_insert (&view->render_opts, "paper-size", "300x200000mm");
187 string_map_insert (&view->render_opts, "left-margin", "0");
188 string_map_insert (&view->render_opts, "right-margin", "0");
189 string_map_insert (&view->render_opts, "top-margin", "0");
190 string_map_insert (&view->render_opts, "bottom-margin", "0");
192 view->xr = xr_driver_create (cr, &view->render_opts);
194 text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
195 r = xr_rendering_create (view->xr, text_item_super (text_item), cr);
196 xr_rendering_measure (r, &font_width, &view->font_height);
197 /* xr_rendering_destroy (r); */
198 text_item_unref (text_item);
202 view->y += view->font_height / 2;
204 r = xr_rendering_create (view->xr, item, cr);
208 xr_rendering_measure (r, &tw, &th);
210 drawing_area = gtk_drawing_area_new ();
212 g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
213 g_signal_connect (drawing_area, "realize",
214 G_CALLBACK (on_dwgarea_realize), view);
215 g_signal_connect (drawing_area, "expose_event",
216 G_CALLBACK (expose_event_callback), view);
218 gtk_widget_set_size_request (drawing_area, tw, th);
219 gtk_layout_put (view->output, drawing_area, 0, view->y);
221 gtk_widget_show (drawing_area);
224 && (!is_text_item (item)
225 || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
226 || !view->in_command))
228 store = GTK_TREE_STORE (gtk_tree_view_get_model (view->overview));
230 ds_init_empty (&title);
231 if (is_text_item (item)
232 && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
234 gtk_tree_store_append (store, &iter, NULL);
235 view->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
236 view->in_command = true;
240 GtkTreeIter *p = view->in_command ? &view->cur_command : NULL;
241 gtk_tree_store_append (store, &iter, p);
245 if (is_text_item (item))
246 ds_put_cstr (&title, text_item_get_text (to_text_item (item)));
247 else if (is_message_item (item))
249 const struct message_item *msg_item = to_message_item (item);
250 const struct msg *msg = message_item_get_msg (msg_item);
251 ds_put_format (&title, "%s: %s", _("Message"),
252 msg_severity_to_string (msg->severity));
254 else if (is_table_item (item))
256 const char *caption = table_item_get_caption (to_table_item (item));
258 ds_put_format (&title, "Table: %s", caption);
260 ds_put_cstr (&title, "Table");
262 else if (is_chart_item (item))
264 const char *s = chart_item_get_title (to_chart_item (item));
266 ds_put_format (&title, "Chart: %s", s);
268 ds_put_cstr (&title, "Chart");
270 gtk_tree_store_set (store, &iter,
271 COL_TITLE, ds_cstr (&title),
277 path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
278 gtk_tree_view_expand_row (view->overview, path, TRUE);
279 gtk_tree_path_free (path);
282 if (view->max_width < tw)
283 view->max_width = tw;
286 gtk_layout_set_size (view->output, view->max_width, view->y);
293 on_row_activate (GtkTreeView *overview,
295 GtkTreeViewColumn *column,
296 struct psppire_output_view *view)
304 model = gtk_tree_view_get_model (overview);
305 if (!gtk_tree_model_get_iter (model, &iter, path))
308 gtk_tree_model_get_value (model, &iter, COL_Y, &value);
309 y = g_value_get_long (&value);
310 g_value_unset (&value);
312 vadj = gtk_layout_get_vadjustment (view->output);
314 max = vadj->upper - vadj->page_size;
319 gtk_adjustment_set_value (vadj, y);
323 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
326 for (i = 0; i < 5; ++i)
328 gtk_widget_modify_bg (dest, i, >k_widget_get_style (src)->base[i]);
329 gtk_widget_modify_fg (dest, i, >k_widget_get_style (src)->text[i]);
333 /* Copy the base style from the parent widget to the container and all its
334 children. We do this because the container's primary purpose is to display
335 text. This way psppire appears to follow the chosen gnome theme. */
337 on_style_set (GtkWidget *toplevel, GtkStyle *prev,
338 struct psppire_output_view *view)
340 copy_base_to_bg (GTK_WIDGET (view->output), toplevel);
341 gtk_container_foreach (GTK_CONTAINER (view->output),
342 (GtkCallback) copy_base_to_bg, view->output);
346 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
348 copy_base_to_bg (dwg_area, gtk_widget_get_toplevel (dwg_area));
359 /* GNU Hurd doesn't have PATH_MAX. Use a fallback.
360 Temporary directory names are usually not that long. */
362 # define PATH_MAX 1024
366 clipboard_get_cb (GtkClipboard *clipboard,
367 GtkSelectionData *selection_data,
371 struct psppire_output_view *view = data;
375 struct output_driver *driver = NULL;
376 char dirname[PATH_MAX], *filename;
377 struct string_map options;
379 GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
380 GtkTreeModel *model = gtk_tree_view_get_model (view->overview);
382 GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
388 if (path_search (dirname, sizeof dirname, NULL, NULL, true)
389 || mkdtemp (dirname) == NULL)
391 msg_error (errno, _("failed to create temporary directory during clipboard operation"));
394 filename = xasprintf ("%s/clip.tmp", dirname);
396 string_map_init (&options);
397 string_map_insert (&options, "output-file", filename);
401 case SELECT_FMT_UTF8:
402 string_map_insert (&options, "box", "unicode");
405 case SELECT_FMT_TEXT:
406 string_map_insert (&options, "format", "txt");
409 case SELECT_FMT_HTML:
410 string_map_insert (&options, "format", "html");
411 string_map_insert (&options, "borders", "false");
412 string_map_insert (&options, "css", "false");
416 string_map_insert (&options, "format", "odt");
420 g_warning ("unsupported clip target\n");
425 driver = output_driver_create (&options);
431 GtkTreePath *path = n->data ;
433 struct output_item *item ;
435 gtk_tree_model_get_iter (model, &iter, path);
436 gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
438 driver->class->submit (driver, item);
443 if ( driver->class->flush)
444 driver->class->flush (driver);
447 /* Some drivers (eg: the odt one) don't write anything until they
449 output_driver_destroy (driver);
452 if ( g_file_get_contents (filename, &text, &length, NULL) )
454 gtk_selection_data_set (selection_data, selection_data->target,
456 (const guchar *) text, length);
462 output_driver_destroy (driver);
474 clipboard_clear_cb (GtkClipboard *clipboard,
479 static const GtkTargetEntry targets[] = {
481 { "STRING", 0, SELECT_FMT_TEXT },
482 { "TEXT", 0, SELECT_FMT_TEXT },
483 { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
484 { "text/plain", 0, SELECT_FMT_TEXT },
486 { "UTF8_STRING", 0, SELECT_FMT_UTF8 },
487 { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
489 { "text/html", 0, SELECT_FMT_HTML },
491 { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
495 on_copy (struct psppire_output_view *view)
497 GtkWidget *widget = GTK_WIDGET (view->overview);
498 GtkClipboard *cb = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
500 if (!gtk_clipboard_set_with_data (cb, targets, G_N_ELEMENTS (targets),
501 clipboard_get_cb, clipboard_clear_cb,
503 clipboard_clear_cb (cb, view);
507 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
509 /* The Copy action is available only if there is something selected */
510 gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
514 on_select_all (struct psppire_output_view *view)
516 GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
517 gtk_tree_view_expand_all (view->overview);
518 gtk_tree_selection_select_all (sel);
521 struct psppire_output_view *
522 psppire_output_view_new (GtkLayout *output, GtkTreeView *overview,
523 GtkAction *copy_action, GtkAction *select_all_action)
525 struct psppire_output_view *view;
526 GtkTreeViewColumn *column;
527 GtkCellRenderer *renderer;
528 GtkTreeSelection *sel;
531 view = xmalloc (sizeof *view);
533 view->font_height = 0;
534 view->output = output;
537 string_map_init (&view->render_opts);
538 view->overview = overview;
539 memset (&view->cur_command, 0, sizeof view->cur_command);
540 view->in_command = false;
541 view->toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output));
543 view->n_items = view->allocated_items = 0;
544 view->print_settings = NULL;
545 view->print_xrd = NULL;
546 view->print_item = 0;
547 view->print_n_pages = 0;
548 view->paginated = FALSE;
550 g_signal_connect (view->toplevel, "style-set", G_CALLBACK (on_style_set), view);
555 model = GTK_TREE_MODEL (gtk_tree_store_new (
557 G_TYPE_STRING, /* COL_TITLE */
558 G_TYPE_POINTER, /* COL_ADDR */
559 G_TYPE_LONG)); /* COL_Y */
560 gtk_tree_view_set_model (overview, model);
561 g_object_unref (model);
563 sel = gtk_tree_view_get_selection (overview);
564 gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
565 g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change),
568 column = gtk_tree_view_column_new ();
569 gtk_tree_view_append_column (GTK_TREE_VIEW (overview), column);
570 renderer = gtk_cell_renderer_text_new ();
571 gtk_tree_view_column_pack_start (column, renderer, TRUE);
572 gtk_tree_view_column_add_attribute (column, renderer, "text", COL_TITLE);
574 g_signal_connect (GTK_TREE_VIEW (overview),
575 "row-activated", G_CALLBACK (on_row_activate), view);
577 gtk_action_set_sensitive (copy_action, FALSE);
578 g_signal_connect_swapped (copy_action, "activate",
579 G_CALLBACK (on_copy), view);
580 g_signal_connect_swapped (select_all_action, "activate",
581 G_CALLBACK (on_select_all), view);
588 psppire_output_view_destroy (struct psppire_output_view *view)
595 g_signal_handlers_disconnect_by_func (view->toplevel,
596 G_CALLBACK (on_style_set), view);
598 string_map_destroy (&view->render_opts);
600 for (i = 0; i < view->n_items; i++)
601 output_item_unref (view->items[i]);
604 view->n_items = view->allocated_items = 0;
606 if (view->print_settings != NULL)
607 g_object_unref (view->print_settings);
615 psppire_output_view_export (struct psppire_output_view *view,
616 struct string_map *options)
618 struct output_driver *driver;
620 driver = output_driver_create (options);
625 for (i = 0; i < view->n_items; i++)
626 driver->class->submit (driver, view->items[i]);
627 output_driver_destroy (driver);
634 get_cairo_context_from_print_context (GtkPrintContext *context)
636 cairo_t *cr = gtk_print_context_get_cairo_context (context);
639 For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
642 double xres = gtk_print_context_get_dpi_x (context);
643 double yres = gtk_print_context_get_dpi_y (context);
645 /* This means that the cairo context now has its dimensions in Points */
646 cairo_scale (cr, xres / 72.0, yres / 72.0);
653 create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *view)
655 struct string_map options;
656 GtkPageSetup *page_setup;
657 double width, height;
661 double bottom_margin;
663 page_setup = gtk_print_context_get_page_setup (context);
664 width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
665 height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
666 left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
667 right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
668 top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
669 bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
671 string_map_init (&options);
672 string_map_insert_nocopy (&options, xstrdup ("paper-size"),
673 c_xasprintf("%.2fx%.2fmm", width, height));
674 string_map_insert_nocopy (&options, xstrdup ("left-margin"),
675 c_xasprintf ("%.2fmm", left_margin));
676 string_map_insert_nocopy (&options, xstrdup ("right-margin"),
677 c_xasprintf ("%.2fmm", right_margin));
678 string_map_insert_nocopy (&options, xstrdup ("top-margin"),
679 c_xasprintf ("%.2fmm", top_margin));
680 string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
681 c_xasprintf ("%.2fmm", bottom_margin));
683 view->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
685 string_map_destroy (&options);
689 paginate (GtkPrintOperation *operation,
690 GtkPrintContext *context,
691 struct psppire_output_view *view)
695 /* Sometimes GTK+ emits this signal again even after pagination is
696 complete. Don't let that screw up printing. */
699 else if ( view->print_item < view->n_items )
701 xr_driver_output_item (view->print_xrd, view->items[view->print_item++]);
702 while (xr_driver_need_new_page (view->print_xrd))
704 xr_driver_next_page (view->print_xrd, NULL);
705 view->print_n_pages ++;
711 gtk_print_operation_set_n_pages (operation, view->print_n_pages);
713 /* Re-create the driver to do the real printing. */
714 xr_driver_destroy (view->print_xrd);
715 create_xr_print_driver (context, view);
716 view->print_item = 0;
717 view->paginated = TRUE;
724 begin_print (GtkPrintOperation *operation,
725 GtkPrintContext *context,
726 struct psppire_output_view *view)
728 create_xr_print_driver (context, view);
730 view->print_item = 0;
731 view->print_n_pages = 1;
732 view->paginated = FALSE;
736 end_print (GtkPrintOperation *operation,
737 GtkPrintContext *context,
738 struct psppire_output_view *view)
740 xr_driver_destroy (view->print_xrd);
745 draw_page (GtkPrintOperation *operation,
746 GtkPrintContext *context,
748 struct psppire_output_view *view)
750 xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context));
751 while (!xr_driver_need_new_page (view->print_xrd)
752 && view->print_item < view->n_items)
753 xr_driver_output_item (view->print_xrd, view->items [view->print_item++]);
758 psppire_output_view_print (struct psppire_output_view *view,
759 GtkWindow *parent_window)
761 GtkPrintOperationResult res;
763 GtkPrintOperation *print = gtk_print_operation_new ();
765 if (view->print_settings != NULL)
766 gtk_print_operation_set_print_settings (print, view->print_settings);
768 g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), view);
769 g_signal_connect (print, "end_print", G_CALLBACK (end_print), view);
770 g_signal_connect (print, "paginate", G_CALLBACK (paginate), view);
771 g_signal_connect (print, "draw_page", G_CALLBACK (draw_page), view);
773 res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
774 parent_window, NULL);
776 if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
778 if (view->print_settings != NULL)
779 g_object_unref (view->print_settings);
780 view->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
783 g_object_unref (print);