psppire: Add File|Export command to output viewer window.
[pspp-builds.git] / src / ui / gui / psppire-output-window.c
index 1787271a528d011ad3505941688ff6b620821e1f..b2e821e2e5060a902f7555b9b7cc261416ff2a62 100644 (file)
 #include <gtk/gtkbox.h>
 #include "helper.h"
 
+#include <libpspp/cast.h>
 #include <libpspp/message.h>
+#include <libpspp/string-map.h>
 #include <output/cairo.h>
-#include <output/manager.h>
-#include <output/output.h>
-#include <output/table.h>
+#include <output/driver-provider.h>
+#include <output/output-item.h>
+#include <output/tab.h>
 #include <stdlib.h>
 
 #include "about.h"
@@ -93,10 +95,29 @@ psppire_output_window_finalize (GObject *object)
 }
 
 
+static void
+psppire_output_window_dispose (GObject *obj)
+{
+  PsppireOutputWindow *viewer = PSPPIRE_OUTPUT_WINDOW (obj);
+  size_t i;
+
+  for (i = 0; i < viewer->n_items; i++)
+    output_item_unref (viewer->items[i]);
+  free (viewer->items);
+  viewer->items = NULL;
+  viewer->n_items = viewer->allocated_items = 0;
+
+  /* Chain up to the parent class */
+  G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
 static void
 psppire_output_window_class_init (PsppireOutputWindowClass *class)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
   parent_class = g_type_class_peek_parent (class);
+  object_class->dispose = psppire_output_window_dispose;
 }
 
 
@@ -118,148 +139,112 @@ psppire_output_window_base_finalize (PsppireOutputWindowClass *class,
 \f
 /* Output driver class. */
 
-static PsppireOutputWindow *the_output_viewer = NULL;
+struct psppire_output_driver
+  {
+    struct output_driver driver;
+    PsppireOutputWindow *viewer;
+    struct xr_driver *xr;
+  };
+
+static struct output_driver_class psppire_output_class;
+
+static struct psppire_output_driver *
+psppire_output_cast (struct output_driver *driver)
+{
+  assert (driver->class == &psppire_output_class);
+  return UP_CAST (driver, struct psppire_output_driver, driver);
+}
 
 static gboolean
 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
 {
-  struct som_entity *entity = g_object_get_data (G_OBJECT (widget), "entity");
-  GdkWindow *window = widget->window;
-  cairo_t *cairo = gdk_cairo_create (GDK_DRAWABLE (window));
-  struct outp_driver *driver = xr_create_driver (cairo); /* XXX can fail */
-  struct tab_table *t = entity->ext;
-  void *rendering;
-
-  rendering = entity->class->render_init (entity, driver, tab_l (t),
-                                          tab_r (t), tab_t (t), tab_b (t));
-
-  entity->class->title (rendering, 0, 0,
-                        entity->table_num, entity->subtable_num,
-                        entity->command_name);
-  entity->class->render (rendering, tab_l (t), tab_t (t),
-                         tab_nc (t) - tab_r (t),
-                         tab_nr (t) - tab_b (t));
-
-  entity->class->render_free (rendering);
-  driver->class->close_driver (driver);
-  outp_free_driver (driver);
+  struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
+  cairo_t *cr;
+
+  cr = gdk_cairo_create (widget->window);
+  xr_rendering_draw (r, cr);
+  cairo_destroy (cr);
+
   return TRUE;
 }
 
 static void
-psppire_output_submit (struct outp_driver *this, struct som_entity *entity)
+psppire_output_submit (struct output_driver *this,
+                       const struct output_item *item)
 {
-  if (the_output_viewer == NULL)
+  struct psppire_output_driver *pod = psppire_output_cast (this);
+  PsppireOutputWindow *viewer;
+  GtkWidget *drawing_area;
+  struct xr_rendering *r;
+  cairo_t *cr;
+  int tw, th;
+
+  if (pod->viewer == NULL)
     {
-      the_output_viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
-      gtk_widget_show_all (GTK_WIDGET (the_output_viewer));
+      pod->viewer = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
+      gtk_widget_show_all (GTK_WIDGET (pod->viewer));
+      pod->viewer->driver = pod;
     }
+  viewer = pod->viewer;
 
-  if (entity->type == SOM_TABLE)
-    {
-      GdkWindow *window = GTK_WIDGET (the_output_viewer)->window;
-      cairo_t *cairo = gdk_cairo_create (GDK_DRAWABLE (window));
-      struct outp_driver *driver = xr_create_driver (cairo); /* XXX can fail */
-      struct tab_table *t = entity->ext;
-      GtkTreeStore *store;
-      GtkTreeIter item;
-      GtkTreePath *path;
-      GtkWidget *drawing_area;
-      void *rendering;
-      struct string title;
-      int tw, th;
-
-      tab_ref (t);
-      rendering = entity->class->render_init (entity, driver, tab_l (t),
-                                              tab_r (t), tab_t (t), tab_b (t));
-      entity->class->area (rendering, &tw, &th);
-
-      drawing_area = gtk_drawing_area_new ();
-      gtk_widget_modify_bg (GTK_WIDGET (drawing_area), GTK_STATE_NORMAL,
-                            &gtk_widget_get_style (drawing_area)->base[GTK_STATE_NORMAL]);
-      g_object_set_data (G_OBJECT (drawing_area),
-                         "entity", som_entity_clone (entity));
-      gtk_widget_set_size_request (drawing_area, tw / 1024, th / 1024);
-      gtk_layout_put (the_output_viewer->output, drawing_area,
-                      0, the_output_viewer->y);
-      gtk_widget_show (drawing_area);
-      g_signal_connect (G_OBJECT (drawing_area), "expose_event",
-                        G_CALLBACK (expose_event_callback), NULL);
-
-      entity->class->render_free (rendering);
-      driver->class->close_driver (driver);
-      outp_free_driver (driver);
-
-      store = GTK_TREE_STORE (gtk_tree_view_get_model (
-                                the_output_viewer->overview));
-
-      ds_init_empty (&title);
-      if (entity->table_num != the_output_viewer->last_table_num)
-        {
-          gtk_tree_store_append (store, &item, NULL);
-
-          ds_put_format (&title, "%d %s",
-                         entity->table_num, entity->command_name);
-          gtk_tree_store_set (store, &item,
-                              COL_TITLE, ds_cstr (&title),
-                              COL_Y, the_output_viewer->y,
-                              -1);
-
-          /* XXX shouldn't save a GtkTreeIter */
-          the_output_viewer->last_table_num = entity->table_num;
-          the_output_viewer->last_top_level = item;
-        }
+  cr = gdk_cairo_create (GTK_WIDGET (pod->viewer)->window);
+  if (pod->xr == NULL)
+    pod->xr = xr_create_driver (cr);
 
-      gtk_tree_store_append (store, &item,
-                             &the_output_viewer->last_top_level);
-      ds_clear (&title);
-      ds_put_format (&title, "%d.%d %s",
-                     entity->table_num, entity->subtable_num,
-                     t->title ? t->title : entity->command_name);
-      gtk_tree_store_set (store, &item,
-                          COL_TITLE, ds_cstr (&title),
-                          COL_Y, the_output_viewer->y,
-                          -1);
-      ds_destroy (&title);
-
-      path = gtk_tree_model_get_path (GTK_TREE_MODEL (store),
-                                      &the_output_viewer->last_top_level);
-      gtk_tree_view_expand_row (the_output_viewer->overview, path, TRUE);
-      gtk_tree_path_free (path);
-
-      if (tw / 1024 > the_output_viewer->max_width)
-        the_output_viewer->max_width = tw / 1024;
-      the_output_viewer->y += th / 1024;
-
-      gtk_layout_set_size (the_output_viewer->output,
-                           the_output_viewer->max_width, the_output_viewer->y);
-    }
+  r = xr_rendering_create (pod->xr, item, cr);
+  if (r == NULL)
+    goto done;
+
+  if (viewer->n_items >= viewer->allocated_items)
+    viewer->items = x2nrealloc (viewer->items, &viewer->allocated_items,
+                                sizeof *viewer->items);
+  viewer->items[viewer->n_items++] = output_item_ref (item);
+
+  xr_rendering_measure (r, &tw, &th);
 
-  gtk_window_set_urgency_hint (GTK_WINDOW (the_output_viewer), TRUE);
+  drawing_area = gtk_drawing_area_new ();
+  gtk_widget_modify_bg (
+    GTK_WIDGET (drawing_area), GTK_STATE_NORMAL,
+    &gtk_widget_get_style (drawing_area)->base[GTK_STATE_NORMAL]);
+  g_object_set_data (G_OBJECT (drawing_area), "rendering", r);
+  gtk_widget_set_size_request (drawing_area, tw, th);
+  gtk_layout_put (pod->viewer->output, drawing_area, 0, pod->viewer->y);
+  gtk_widget_show (drawing_area);
+  g_signal_connect (G_OBJECT (drawing_area), "expose_event",
+                     G_CALLBACK (expose_event_callback), NULL);
+
+  if (pod->viewer->max_width < tw)
+    pod->viewer->max_width = tw;
+  pod->viewer->y += th;
+
+  gtk_layout_set_size (pod->viewer->output,
+                       pod->viewer->max_width, pod->viewer->y);
+
+  gtk_window_set_urgency_hint (GTK_WINDOW (pod->viewer), TRUE);
+
+done:
+  cairo_destroy (cr);
 }
 
-static struct outp_class psppire_output_class =
+static struct output_driver_class psppire_output_class =
   {
     "PSPPIRE",                  /* name */
-    true,                       /* special */
-    NULL,                       /* open_driver */
-    NULL,                       /* close_driver */
-    NULL,                       /* open_page */
-    NULL,                       /* close_page */
-    NULL,                       /* flush */
+    NULL,                       /* create */
+    NULL,                       /* destroy */
     psppire_output_submit,      /* submit */
-    NULL,                       /* line */
-    NULL,                       /* text_metrics */
-    NULL,                       /* text_draw */
-    NULL,                       /* initialise_chart */
-    NULL,                       /* finalise_chart */
+    NULL,                       /* flush */
   };
 
 void
 psppire_output_window_setup (void)
 {
-  outp_register_driver (outp_allocate_driver (&psppire_output_class,
-                                              "PSPPIRE", 0));
+  struct psppire_output_driver *pod;
+  struct output_driver *d;
+
+  pod = xzalloc (sizeof *pod);
+  d = &pod->driver;
+  output_driver_init (d, &psppire_output_class, "PSPPIRE", 0);
+  output_driver_register (d);
 }
 \f
 int viewer_length = 16;
@@ -274,7 +259,7 @@ on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
 
   gtk_widget_destroy (GTK_WIDGET (ow));
 
-  the_output_viewer = NULL;
+  ow->driver->viewer = NULL;
 
   return FALSE;
 }
@@ -308,8 +293,8 @@ on_row_activate (GtkTreeView *overview,
   g_value_unset (&value);
 
   vadj = gtk_layout_get_vadjustment (window->output);
-  min = gtk_adjustment_get_lower (vadj);
-  max = gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj);
+  min = vadj->lower;
+  max = vadj->upper - vadj->page_size;
   if (y < min)
     y = min;
   else if (y > max)
@@ -317,6 +302,121 @@ on_row_activate (GtkTreeView *overview,
   gtk_adjustment_set_value (vadj, y);
 }
 
+static GtkFileFilter *
+add_filter (GtkFileChooser *chooser, const char *name, const char *pattern)
+{
+  GtkFileFilter *filter = gtk_file_filter_new ();
+  g_object_ref_sink (G_OBJECT (filter));
+  gtk_file_filter_set_name (filter, name);
+  gtk_file_filter_add_pattern (filter, pattern);
+  gtk_file_chooser_add_filter (chooser, filter);
+  return filter;
+}
+
+static void
+export_output (PsppireOutputWindow *window, struct string_map *options,
+               const char *class_name)
+{
+  struct output_driver *driver;
+  size_t i;
+
+  driver = output_driver_create (class_name, options);
+  if (driver == NULL)
+    return;
+
+  for (i = 0; i < window->n_items; i++)
+    driver->class->submit (driver, window->items[i]);
+  output_driver_destroy (driver);
+}
+
+static void
+psppire_output_window_export (PsppireOutputWindow *window)
+{
+  gint response;
+
+  GtkFileFilter *pdf_filter;
+  GtkFileFilter *html_filter;
+  GtkFileFilter *odt_filter;
+  GtkFileFilter *txt_filter;
+  GtkFileFilter *ps_filter;
+  GtkFileFilter *csv_filter;
+  GtkFileChooser *chooser;
+  GtkWidget *dialog;
+
+  dialog = gtk_file_chooser_dialog_new (_("Export Output"),
+                                        GTK_WINDOW (window),
+                                        GTK_FILE_CHOOSER_ACTION_SAVE,
+                                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                        GTK_STOCK_SAVE,   GTK_RESPONSE_ACCEPT,
+                                        NULL);
+  chooser = GTK_FILE_CHOOSER (dialog);
+
+  pdf_filter = add_filter (chooser, _("PDF Files (*.pdf)"), "*.pdf");
+  html_filter = add_filter (chooser, _("HTML Files (*.html)"), "*.html");
+  odt_filter = add_filter (chooser, _("OpenDocument Files (*.odt)"), "*.odt");
+  txt_filter = add_filter (chooser, _("Text Files (*.txt)"), "*.txt");
+  ps_filter = add_filter (chooser, _("PostScript Files (*.ps)"), "*.ps");
+  csv_filter = add_filter (chooser, _("Comma-Separated Value Files (*.csv)"),
+                           "*.csv");
+
+  gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
+  gtk_file_chooser_set_filter (chooser, pdf_filter);
+
+  response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+  if ( response == GTK_RESPONSE_ACCEPT )
+    {
+      char *filename = gtk_file_chooser_get_filename (chooser);
+      GtkFileFilter *filter = gtk_file_chooser_get_filter (chooser);
+      struct string_map options;
+
+      g_return_if_fail (filename);
+      g_return_if_fail (filter);
+
+      string_map_init (&options);
+      string_map_insert (&options, "output-file", filename);
+      if (filter == pdf_filter)
+        {
+          string_map_insert (&options, "output-type", "pdf");
+          export_output (window, &options, "cairo");
+        }
+      else if (filter == html_filter)
+        export_output (window, &options, "html");
+      else if (filter == odt_filter)
+        export_output (window, &options, "odf");
+      else if (filter == txt_filter)
+        {
+          string_map_insert (&options, "headers", "false");
+          string_map_insert (&options, "paginate", "false");
+          string_map_insert (&options, "squeeze", "true");
+          string_map_insert (&options, "emphasis", "none");
+          string_map_insert (&options, "chart-type", "none");
+          string_map_insert (&options, "top-margin", "0");
+          string_map_insert (&options, "bottom-margin", "0");
+          export_output (window, &options, "ascii");
+        }
+      else if (filter == ps_filter)
+        {
+          string_map_insert (&options, "output-type", "ps");
+          export_output (window, &options, "cairo");
+        }
+      else if (filter == csv_filter)
+        export_output (window, &options, "csv");
+      else
+        g_return_if_reached ();
+
+      free (filename);
+    }
+
+  g_object_unref (G_OBJECT (pdf_filter));
+  g_object_unref (G_OBJECT (html_filter));
+  g_object_unref (G_OBJECT (txt_filter));
+  g_object_unref (G_OBJECT (ps_filter));
+  g_object_unref (G_OBJECT (csv_filter));
+
+  gtk_widget_destroy (dialog);
+}
+
 static void
 psppire_output_window_init (PsppireOutputWindow *window)
 {
@@ -339,6 +439,9 @@ psppire_output_window_init (PsppireOutputWindow *window)
                                              G_TYPE_LONG))); /* COL_Y */
   window->last_table_num = -1;
 
+  window->items = NULL;
+  window->n_items = window->allocated_items = 0;
+
   column = gtk_tree_view_column_new ();
   gtk_tree_view_append_column (GTK_TREE_VIEW (window->overview), column);
   renderer = gtk_cell_renderer_text_new ();
@@ -380,6 +483,9 @@ psppire_output_window_init (PsppireOutputWindow *window)
       GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar1/windows_menuitem/windows_minimise-all")->parent);
   }
 
+  g_signal_connect_swapped (get_action_assert (xml, "file_export"), "activate",
+                            G_CALLBACK (psppire_output_window_export), window);
+
   g_object_unref (xml);
 
   g_signal_connect (window, "delete-event",