psppire-output-view: Avoid doubling output when exporting or printing.
[pspp] / src / ui / gui / psppire-output-view.c
index f48b1ade4f5e8fa88a057d6e3e66a3cf56ec1e15..5cc20b2609582387f8d77f03095126f00c7ec478 100644 (file)
 #include <errno.h>
 #include <stdbool.h>
 
-#if HAVE_RSVG
-#include "librsvg/rsvg.h"
-#endif
 #include "libpspp/assertion.h"
 #include "libpspp/string-map.h"
 #include "output/cairo-fsm.h"
 #include "output/cairo-pager.h"
 #include "output/driver-provider.h"
 #include "output/driver.h"
-#include "output/chart-item.h"
-#include "output/group-item.h"
-#include "output/message-item.h"
 #include "output/output-item.h"
-#include "output/output-item-provider.h"
-#include "output/table-item.h"
-#include "output/text-item.h"
+#include "output/pivot-table.h"
 
 #include "gl/c-xvasprintf.h"
 #include "gl/minmax.h"
@@ -51,6 +43,8 @@ struct output_view_item
   {
     struct output_item *item;
     GtkWidget *drawing_area;
+    int width, height;
+    int nesting_depth;
   };
 
 struct psppire_output_view
@@ -64,7 +58,6 @@ struct psppire_output_view
     glong y;
 
     GtkTreeView *overview;
-    GtkTreePath *cur_group;
 
     GtkWidget *toplevel;
 
@@ -87,9 +80,9 @@ struct psppire_output_view
 
 enum
   {
-    COL_NAME,                   /* Table name. */
+    COL_LABEL,                  /* Output item label. */
     COL_ADDR,                   /* Pointer to the table */
-    COL_Y,                      /* Y position of top of name. */
+    COL_Y,                      /* Y position of top of object. */
     N_COLS
   };
 
@@ -154,20 +147,15 @@ get_xr_fsm_style (struct psppire_output_view *view)
 
   PangoFontDescription *pf;
   gtk_style_context_get (context, state, "font", &pf, NULL);
-  PangoFontDescription *ff = pango_font_description_from_string ("Monospace");
-  pango_font_description_set_size (ff, pango_font_description_get_size (pf));
 
   struct xr_fsm_style *style = xmalloc (sizeof *style);
   *style = (struct xr_fsm_style) {
     .ref_cnt = 1,
     .size = { [TABLE_HORZ] = xr_width, [TABLE_VERT] = INT_MAX },
     .min_break = { [TABLE_HORZ] = xr_width / 2, [TABLE_VERT] = 0 },
-    .fonts = {
-      [XR_FONT_PROPORTIONAL] = pf,
-      [XR_FONT_FIXED] = ff,
-    },
+    .font = pf,
     .use_system_colors = true,
-    .transparent = true,
+    .object_spacing = XR_POINT * 12,
     .font_resolution = 96.0,
   };
 
@@ -338,10 +326,10 @@ rerender (struct psppire_output_view *view)
       if (view->y > 0)
         view->y += view->object_spacing;
 
-      if (is_group_open_item (item->item))
+      if (item->item->type == OUTPUT_ITEM_GROUP)
         continue;
 
-      r = xr_fsm_create (item->item, view->style, cr);
+      r = xr_fsm_create_for_scrolling (item->item, view->style, cr);
       if (r == NULL)
         {
           g_warn_if_reached ();
@@ -365,6 +353,10 @@ rerender (struct psppire_output_view *view)
           gtk_layout_move (view->output, item->drawing_area, xpos, view->y);
         }
 
+      if (item->item->type == OUTPUT_ITEM_TABLE)
+        gtk_widget_set_tooltip_text (item->drawing_area,
+                                     item->item->table->notes);
+
       {
        gint minw;
        gint minh;
@@ -396,50 +388,20 @@ rerender (struct psppire_output_view *view)
   cairo_region_destroy (region);
 }
 
-
-void
-psppire_output_view_put (struct psppire_output_view *view,
-                         const struct output_item *item)
+static bool
+init_output_view_item (struct output_view_item *view_item,
+                       struct psppire_output_view *view,
+                       const struct output_item *item,
+                       int nesting_depth)
 {
-  struct output_view_item *view_item;
-  GtkWidget *drawing_area;
-  struct string name;
-  int tw, th;
-
-  if (is_group_close_item (item))
-    {
-      if (view->cur_group)
-        {
-          if (!gtk_tree_path_up (view->cur_group))
-            {
-              gtk_tree_path_free (view->cur_group);
-              view->cur_group = NULL;
-            }
-        }
-      return;
-    }
-  else if (is_text_item (item))
-    {
-      const struct text_item *text_item = to_text_item (item);
-      const char *text = text_item_get_text (text_item);
-      if (text[0] == '\0')
-        return;
-    }
-
-  if (view->n_items >= view->allocated_items)
-    view->items = x2nrealloc (view->items, &view->allocated_items,
-                                sizeof *view->items);
-  view_item = &view->items[view->n_items++];
-  view_item->item = output_item_ref (item);
-  view_item->drawing_area = NULL;
+  *view_item = (struct output_view_item) {
+    .item = output_item_ref (item),
+    .nesting_depth = nesting_depth
+  };
 
   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (view->output));
-  if (is_group_open_item (item))
-    tw = th = 0;
-  else if (win)
+  if (win && item->type != OUTPUT_ITEM_GROUP)
     {
-      view_item->drawing_area = drawing_area = gtk_drawing_area_new ();
-
       if (!view->style)
         view->style = get_xr_fsm_style (view);
 
@@ -450,98 +412,97 @@ psppire_output_view_put (struct psppire_output_view *view,
       if (view->y > 0)
         view->y += view->object_spacing;
 
-      struct xr_fsm *r = xr_fsm_create (item, view->style, cr);
+      struct xr_fsm *r = xr_fsm_create_for_scrolling (item, view->style, cr);
       if (r == NULL)
        {
          gdk_window_end_draw_frame (win, ctx);
          cairo_region_destroy (region);
-         return;
+
+          output_item_unref (view_item->item);
+         return false;
        }
 
-      xr_fsm_measure (r, cr, &tw, &th);
-      create_drawing_area (view, drawing_area, r, tw, th, item);
+      xr_fsm_measure (r, cr, &view_item->width, &view_item->height);
+      view_item->drawing_area = gtk_drawing_area_new ();
+      create_drawing_area (view, view_item->drawing_area, r, view_item->width,
+                           view_item->height, item);
       gdk_window_end_draw_frame (win, ctx);
       cairo_region_destroy (region);
     }
-  else
-    tw = th = 0;
 
+  return true;
+}
+
+static void
+psppire_output_view_put__ (struct psppire_output_view *view,
+                           const struct output_item *item,
+                           GtkTreePath *parent_path)
+{
+  if (item->type == OUTPUT_ITEM_TEXT)
+    {
+      char *text = text_item_get_plain_text (item);
+      bool text_is_empty = text[0] == '\0';
+      free (text);
+      if (text_is_empty)
+        return;
+    }
+
+  if (view->n_items >= view->allocated_items)
+    view->items = x2nrealloc (view->items, &view->allocated_items,
+                              sizeof *view->items);
+  struct output_view_item *view_item = &view->items[view->n_items];
+  if (!init_output_view_item (view_item, view, item,
+                              gtk_tree_path_get_depth (parent_path)))
+    return;
+  view->n_items++;
+
+  GtkTreePath *path = NULL;
   if (view->overview)
     {
       GtkTreeStore *store = GTK_TREE_STORE (
         gtk_tree_view_get_model (view->overview));
 
-      ds_init_empty (&name);
-
       /* Create a new node in the tree and puts a reference to it in 'iter'. */
       GtkTreeIter iter;
       GtkTreeIter parent;
-      if (view->cur_group
-          && gtk_tree_path_get_depth (view->cur_group) > 0
+      if (parent_path
+          && gtk_tree_path_get_depth (parent_path) > 0
           && gtk_tree_model_get_iter (GTK_TREE_MODEL (store),
-                                      &parent, view->cur_group))
+                                      &parent, parent_path))
         gtk_tree_store_append (store, &iter, &parent);
       else
         gtk_tree_store_append (store, &iter, NULL);
 
-      if (is_group_open_item (item))
-        {
-          gtk_tree_path_free (view->cur_group);
-          view->cur_group = gtk_tree_model_get_path (GTK_TREE_MODEL (store),
-                                                     &iter);
-        }
-
-      ds_clear (&name);
-      if (is_text_item (item))
-        {
-          const struct text_item *text_item = to_text_item (item);
-          ds_put_cstr (&name, text_item_type_to_string (
-                         text_item_get_type (text_item)));
-        }
-      else if (is_message_item (item))
-        {
-          const struct message_item *msg_item = to_message_item (item);
-          const struct msg *msg = message_item_get_msg (msg_item);
-          ds_put_format (&name, "%s: %s", _("Message"),
-                         msg_severity_to_string (msg->severity));
-        }
-      else if (is_table_item (item))
-        {
-          const struct table_item_text *title
-            = table_item_get_title (to_table_item (item));
-          if (title != NULL)
-            ds_put_format (&name, "Table: %s", title->content);
-          else
-            ds_put_cstr (&name, "Table");
-        }
-      else if (is_chart_item (item))
-        {
-          const char *s = chart_item_get_title (to_chart_item (item));
-          if (s != NULL)
-            ds_put_format (&name, "Chart: %s", s);
-          else
-            ds_put_cstr (&name, "Chart");
-        }
-      else if (is_group_open_item (item))
-        ds_put_cstr (&name, to_group_open_item (item)->command_name);
       gtk_tree_store_set (store, &iter,
-                          COL_NAME, ds_cstr (&name),
+                          COL_LABEL, output_item_get_label (item),
                          COL_ADDR, item,
-                          COL_Y, (view->y),
+                          COL_Y, view->y,
                           -1);
-      ds_destroy (&name);
 
-      GtkTreePath *path = gtk_tree_model_get_path (
+      /* Get the path of the new row. */
+      path = gtk_tree_model_get_path (
         GTK_TREE_MODEL (store), &iter);
       gtk_tree_view_expand_row (view->overview, path, TRUE);
-      gtk_tree_path_free (path);
     }
 
-  if (view->max_width < tw)
-    view->max_width = tw;
-  view->y += th;
+  if (view->max_width < view_item->width)
+    view->max_width = view_item->width;
+  view->y += view_item->height;
 
   gtk_layout_set_size (view->output, view->max_width, view->y);
+
+  if (item->type == OUTPUT_ITEM_GROUP)
+    for (size_t i = 0; i < item->group.n_children; i++)
+      psppire_output_view_put__ (view, item->group.children[i], path);
+
+  gtk_tree_path_free (path);
+}
+
+void
+psppire_output_view_put (struct psppire_output_view *view,
+                         const struct output_item *item)
+{
+  psppire_output_view_put__ (view, item, NULL);
 }
 
 static void
@@ -603,32 +564,16 @@ enum {
   SELECT_FMT_ODT
 };
 
-/* Returns a pixbuf from a svg file      */
-/* You must unref the pixbuf after usage */
-static GdkPixbuf *
-derive_pixbuf_from_svg (const char *filename)
+static void
+clear_rectangle (cairo_surface_t *surface,
+                 double x0, double y0, double x1, double y1)
 {
-  GError *err = NULL;
-  GdkPixbuf *pixbuf = NULL;
-#if HAVE_RSVG
-  RsvgHandle *handle = rsvg_handle_new_from_file (filename, &err);
-  if (err == NULL)
-    {
-      rsvg_handle_set_dpi (handle, 300.0);
-      pixbuf = rsvg_handle_get_pixbuf (handle);
-      g_object_unref (handle);
-    }
-#else
-  pixbuf = gdk_pixbuf_new_from_file (filename, &err);
-#endif
-  if (err != NULL)
-    {
-      msg (ME, _("Could not open file %s during copy operation: %s"),
-          filename, err->message);
-      g_error_free (err);
-      return NULL;
-    }
-  return pixbuf;
+  cairo_t *cr = cairo_create (surface);
+  cairo_set_source_rgb (cr, 1, 1, 1);
+  cairo_new_path (cr);
+  cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
+  cairo_fill (cr);
+  cairo_destroy (cr);
 }
 
 static void
@@ -668,6 +613,7 @@ clipboard_get_cb (GtkClipboard     *clipboard,
 
     case SELECT_FMT_TEXT:
       string_map_insert (&options, "format", "txt");
+      string_map_insert (&options, "width", "1000");
       break;
 
     case SELECT_FMT_HTML:
@@ -708,16 +654,25 @@ clipboard_get_cb (GtkClipboard     *clipboard,
       gdk_window_end_draw_frame (win, ctx);
       cairo_region_destroy (region);
 
-      cairo_surface_t *surface = cairo_svg_surface_create (filename, w, h);
-      if (surface)
+      cairo_surface_t *surface
+        = (info == SELECT_FMT_SVG
+           ? cairo_svg_surface_create (filename, w, h)
+           : cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h));
+      clear_rectangle (surface, 0, 0, w, h);
+      cairo_t *cr2 = cairo_create (surface);
+      xr_fsm_draw_all (fsm, cr2);
+      cairo_destroy (cr2);
+      if (info == SELECT_FMT_IMG)
         {
-          cairo_t *cr = cairo_create (surface);
-          xr_fsm_draw_all (fsm, cr);
-          cairo_destroy (cr);
-          cairo_surface_destroy (surface);
+          GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface (surface,
+                                                           0, 0, w, h);
+          if (pixbuf)
+            {
+              gtk_selection_data_set_pixbuf (selection_data, pixbuf);
+              g_object_unref (pixbuf);
+            }
         }
-      else
-        g_error ("Could not create cairo svg surface with file %s", filename);
+      cairo_surface_destroy (surface);
     }
   else
     {
@@ -736,19 +691,11 @@ clipboard_get_cb (GtkClipboard     *clipboard,
       driver = NULL;
     }
 
-  if (info == SELECT_FMT_IMG)
-    {
-      GdkPixbuf *pixbuf = derive_pixbuf_from_svg (filename);
-      if (pixbuf)
-       {
-         gtk_selection_data_set_pixbuf (selection_data, pixbuf);
-         g_object_unref (pixbuf);
-       }
-    }
-  else if (g_file_get_contents (filename, &text, &length, NULL))
-    gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
-                           8,
-                           (const guchar *) text, length);
+  if (info != SELECT_FMT_IMG
+      && g_file_get_contents (filename, &text, &length, NULL))
+    gtk_selection_data_set (selection_data,
+                            gtk_selection_data_get_target (selection_data),
+                            8, (const guchar *) text, length);
 
  finish:
 
@@ -795,8 +742,7 @@ build_target_list (const struct output_item *item)
 {
   GtkTargetList *tl = gtk_target_list_new (targets, G_N_ELEMENTS (targets));
   g_return_val_if_fail (tl, NULL);
-  if (is_table_item (item) ||
-      is_chart_item (item))
+  if (item->type == OUTPUT_ITEM_TABLE || item->type == OUTPUT_ITEM_CHART)
     gtk_target_list_add_image_targets (tl, SELECT_FMT_IMG, TRUE);
   return tl;
 }
@@ -857,10 +803,6 @@ struct psppire_output_view *
 psppire_output_view_new (GtkLayout *output, GtkTreeView *overview)
 {
   struct psppire_output_view *view;
-  GtkTreeViewColumn *column;
-  GtkCellRenderer *renderer;
-
-  GtkTreeModel *model;
 
   view = xmalloc (sizeof *view);
   *view = (struct psppire_output_view) {
@@ -887,19 +829,19 @@ psppire_output_view_new (GtkLayout *output, GtkTreeView *overview)
     {
       g_signal_connect (overview, "realize", G_CALLBACK (on_realize), view);
 
-      model = GTK_TREE_MODEL (gtk_tree_store_new (
+      GtkTreeModel *model = GTK_TREE_MODEL (gtk_tree_store_new (
                                 N_COLS,
-                                G_TYPE_STRING,  /* COL_NAME */
+                                G_TYPE_STRING,  /* COL_LABEL */
                                 G_TYPE_POINTER, /* COL_ADDR */
                                 G_TYPE_LONG));  /* COL_Y */
       gtk_tree_view_set_model (overview, model);
       g_object_unref (model);
 
-      column = gtk_tree_view_column_new ();
+      GtkTreeViewColumn *column = gtk_tree_view_column_new ();
       gtk_tree_view_append_column (GTK_TREE_VIEW (overview), column);
-      renderer = gtk_cell_renderer_text_new ();
+      GtkCellRenderer * renderer = gtk_cell_renderer_text_new ();
       gtk_tree_view_column_pack_start (column, renderer, TRUE);
-      gtk_tree_view_column_add_attribute (column, renderer, "text", COL_NAME);
+      gtk_tree_view_column_add_attribute (column, renderer, "text", COL_LABEL);
 
       g_signal_connect (GTK_TREE_VIEW (overview),
                         "row-activated", G_CALLBACK (on_row_activate), view);
@@ -930,31 +872,9 @@ psppire_output_view_destroy (struct psppire_output_view *view)
   if (view->print_settings != NULL)
     g_object_unref (view->print_settings);
 
-  if (view->cur_group)
-    gtk_tree_path_free (view->cur_group);
-
   free (view);
 }
 
-void
-psppire_output_view_clear (struct psppire_output_view *view)
-{
-  size_t i;
-
-  view->max_width = 0;
-  view->y = 0;
-
-  for (i = 0; i < view->n_items; i++)
-    {
-      gtk_container_remove (GTK_CONTAINER (view->output),
-                            view->items[i].drawing_area);
-      output_item_unref (view->items[i].item);
-    }
-  free (view->items);
-  view->items = NULL;
-  view->n_items = view->allocated_items = 0;
-}
-
 /* Export. */
 
 void
@@ -969,7 +889,8 @@ psppire_output_view_export (struct psppire_output_view *view,
       size_t i;
 
       for (i = 0; i < view->n_items; i++)
-        driver->class->submit (driver, view->items[i].item);
+        if (view->items[i].nesting_depth == 0)
+          driver->class->submit (driver, view->items[i].item);
       output_driver_destroy (driver);
     }
 }
@@ -1007,11 +928,6 @@ create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *vi
   for (int a = 0; a < TABLE_N_AXES; a++)
     size[a] = paper[a] - margins[a][0] - margins[a][1];
 
-  PangoFontDescription *proportional_font
-    = pango_font_description_from_string ("Sans Serif 10");
-  PangoFontDescription *fixed_font
-    = pango_font_description_from_string ("Monospace 10");
-
   view->page_style = xmalloc (sizeof *view->page_style);
   *view->page_style = (struct xr_page_style) {
     .ref_cnt = 1,
@@ -1020,9 +936,7 @@ create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *vi
       [H] = { margins[H][0], margins[H][1] },
       [V] = { margins[V][0], margins[V][1] },
     },
-    .bg = { .alpha = 0 },
     .initial_page_number = 1,
-    .object_spacing = 12 * XR_POINT,
   };
 
   view->fsm_style = xmalloc (sizeof *view->fsm_style);
@@ -1031,13 +945,10 @@ create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *vi
 
     .size = { [H] = size[H], [V] = size[V] },
     .min_break = { [H] = size[H] / 2, [V] = size[V] / 2 },
-    .fonts = {
-      [XR_FONT_PROPORTIONAL] = proportional_font,
-      [XR_FONT_FIXED] = fixed_font,
-    },
+    .font = pango_font_description_from_string ("Sans Serif 10"),
     .fg = CELL_COLOR_BLACK,
     .use_system_colors = false,
-    .transparent = false,
+    .object_spacing = 12 * XR_POINT,
     .font_resolution = 72.0
   };
 
@@ -1055,29 +966,32 @@ paginate (GtkPrintOperation *operation,
          complete.  Don't let that screw up printing. */
       return TRUE;
     }
-  else if (view->print_item < view->n_items)
+
+  while (view->print_item < view->n_items)
     {
-      xr_pager_add_item (view->pager, view->items[view->print_item++].item);
-      while (xr_pager_needs_new_page (view->pager))
-       {
-         xr_pager_add_page (view->pager,
-                             get_cairo_context_from_print_context (context));
-         view->print_n_pages ++;
-       }
-      return FALSE;
+      const struct output_view_item *item = &view->items[view->print_item++];
+      if (item->nesting_depth == 0)
+        {
+          xr_pager_add_item (view->pager, item->item);
+          while (xr_pager_needs_new_page (view->pager))
+            {
+              xr_pager_add_page (view->pager,
+                                 get_cairo_context_from_print_context (context));
+              view->print_n_pages ++;
+            }
+          return FALSE;
+        }
     }
-  else
-    {
-      gtk_print_operation_set_n_pages (operation, MAX (1, view->print_n_pages));
 
-      /* Re-create the driver to do the real printing. */
-      xr_pager_destroy (view->pager);
-      view->pager = xr_pager_create (view->page_style, view->fsm_style);
-      view->print_item = 0;
-      view->paginated = TRUE;
+  gtk_print_operation_set_n_pages (operation, MAX (1, view->print_n_pages));
 
-      return TRUE;
-    }
+  /* Re-create the driver to do the real printing. */
+  xr_pager_destroy (view->pager);
+  view->pager = xr_pager_create (view->page_style, view->fsm_style);
+  view->print_item = 0;
+  view->paginated = TRUE;
+
+  return TRUE;
 }
 
 static void
@@ -1112,7 +1026,11 @@ draw_page (GtkPrintOperation *operation,
                      get_cairo_context_from_print_context (context));
   while (!xr_pager_needs_new_page (view->pager)
          && view->print_item < view->n_items)
-    xr_pager_add_item (view->pager, view->items [view->print_item++].item);
+    {
+      const struct output_view_item *item = &view->items [view->print_item++];
+      if (item->nesting_depth == 0)
+        xr_pager_add_item (view->pager, item->item);
+    }
 }
 
 
@@ -1147,47 +1065,3 @@ psppire_output_view_print (struct psppire_output_view *view,
 
   g_object_unref (print);
 }
-\f
-struct psppire_output_view_driver
-  {
-    struct output_driver driver;
-    struct psppire_output_view *view;
-  };
-
-static struct psppire_output_view_driver *
-psppire_output_view_driver_cast (struct output_driver *driver)
-{
-  return UP_CAST (driver, struct psppire_output_view_driver, driver);
-}
-
-static void
-psppire_output_view_submit (struct output_driver *this,
-                            const struct output_item *item)
-{
-  struct psppire_output_view_driver *povd = psppire_output_view_driver_cast (this);
-
-  if (is_table_item (item))
-    psppire_output_view_put (povd->view, item);
-}
-
-static struct output_driver_class psppire_output_view_driver_class =
-  {
-    "PSPPIRE Output View",      /* name */
-    NULL,                       /* destroy */
-    psppire_output_view_submit, /* submit */
-    NULL,                       /* flush */
-  };
-
-void
-psppire_output_view_register_driver (struct psppire_output_view *view)
-{
-  struct psppire_output_view_driver *povd;
-  struct output_driver *d;
-
-  povd = xzalloc (sizeof *povd);
-  povd->view = view;
-  d = &povd->driver;
-  output_driver_init (d, &psppire_output_view_driver_class, "PSPPIRE Output View",
-                      SETTINGS_DEVICE_UNFILTERED);
-  output_driver_register (d);
-}