1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016,
3 2021 Free Software Foundation
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20 #include "ui/gui/psppire-output-window.h"
26 #include <sys/types.h>
29 #include "libpspp/cast.h"
30 #include "libpspp/message.h"
31 #include "libpspp/string-map.h"
32 #include "output/driver-provider.h"
33 #include "output/output-item.h"
34 #include "ui/gui/help-menu.h"
35 #include "ui/gui/builder-wrapper.h"
36 #include "ui/gui/psppire-output-view.h"
37 #include "ui/gui/psppire-conf.h"
38 #include "ui/gui/windows-menu.h"
40 #include "gl/xalloc.h"
45 #define _(msgid) gettext (msgid)
46 #define N_(msgid) msgid
48 G_DEFINE_TYPE (PsppireOutputWindow, psppire_output_window, PSPPIRE_TYPE_WINDOW)
50 static GObjectClass *parent_class;
53 psppire_output_window_finalize (GObject *object)
55 if (G_OBJECT_CLASS (parent_class)->finalize)
56 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
61 psppire_output_window_dispose (GObject *obj)
63 PsppireOutputWindow *window = PSPPIRE_OUTPUT_WINDOW (obj);
65 if (window->dispose_has_run)
68 window->dispose_has_run = TRUE;
69 psppire_output_view_destroy (window->view);
72 /* Chain up to the parent class */
73 G_OBJECT_CLASS (parent_class)->dispose (obj);
77 psppire_output_window_class_init (PsppireOutputWindowClass *class)
79 GObjectClass *object_class = G_OBJECT_CLASS (class);
81 parent_class = g_type_class_peek_parent (class);
82 object_class->dispose = psppire_output_window_dispose;
84 object_class->finalize = psppire_output_window_finalize;
87 /* Output driver class. */
89 struct psppire_output_driver
91 struct output_driver driver;
92 PsppireOutputWindow *window;
95 static struct output_driver_class psppire_output_class;
97 static struct psppire_output_driver *
98 psppire_output_cast (struct output_driver *driver)
100 assert (driver->class == &psppire_output_class);
101 return UP_CAST (driver, struct psppire_output_driver, driver);
105 psppire_output_submit (struct output_driver *this,
106 const struct output_item *item)
108 struct psppire_output_driver *pod = psppire_output_cast (this);
109 PsppireOutputWindow *window;
112 new = pod->window == NULL;
115 pod->window = PSPPIRE_OUTPUT_WINDOW (psppire_output_window_new ());
116 GApplication *app = g_application_get_default ();
117 gtk_application_add_window (GTK_APPLICATION (app),
118 GTK_WINDOW (pod->window));
120 pod->window->driver = pod;
122 window = pod->window;
124 psppire_output_view_put (window->view, item);
128 /* We could have called this earlier in the previous "if (new)" block,
129 but doing it here finds, in a plain GTK+ environment, a bug that
130 otherwise only showed up on an Ubuntu Unity desktop. See bug
132 gtk_widget_show_all (GTK_WIDGET (pod->window));
135 PsppireConf *conf = psppire_conf_new ();
137 gboolean status = true;
138 psppire_conf_get_boolean (conf, "OutputWindowAction", "alert",
140 gtk_window_set_urgency_hint (GTK_WINDOW (pod->window), status);
145 if (psppire_conf_get_boolean (conf, "OutputWindowAction", "maximize",
147 gtk_window_maximize (GTK_WINDOW (pod->window));
152 if (psppire_conf_get_boolean (conf, "OutputWindowAction", "raise",
154 gtk_window_present (GTK_WINDOW (pod->window));
158 static struct output_driver_class psppire_output_class =
161 .submit = psppire_output_submit,
162 .handles_groups = true,
163 .handles_show = true,
167 psppire_output_window_setup (void)
169 struct psppire_output_driver *pod = xmalloc (sizeof *pod);
170 *pod = (struct psppire_output_driver) {
172 .class = &psppire_output_class,
173 .name = xstrdup ("PSPPIRE"),
174 .device_type = SETTINGS_DEVICE_UNFILTERED,
177 output_driver_register (&pod->driver);
182 /* Callback for the "delete" action (clicking the x on the top right
183 hand corner of the window) */
185 on_delete (GtkWidget *w, GdkEvent *event, gpointer user_data)
187 PsppireOutputWindow *ow = PSPPIRE_OUTPUT_WINDOW (user_data);
189 gtk_widget_destroy (GTK_WIDGET (ow));
191 ow->driver->window = NULL;
199 cancel_urgency (GtkWindow *window, gpointer data)
201 gtk_window_set_urgency_hint (window, FALSE);
204 static void psppire_output_window_print (PsppireOutputWindow *window);
208 export_output (PsppireOutputWindow *window, struct string_map *options,
211 string_map_insert (options, "format", format);
212 psppire_output_view_export (window->view, options);
238 static const struct file_types ft[n_FT] = {
239 {N_("Infer file type from extension"), NULL},
240 {N_("SPSS Viewer (*.spv)"), ".spv"},
241 {N_("PDF (*.pdf)"), ".pdf"},
242 {N_("HTML (*.html)"), ".html"},
243 {N_("OpenDocument (*.odt)"), ".odt"},
244 {N_("Text (*.txt)"), ".txt"},
245 {N_("Text [plain] (*.txt)"), ".txt"},
246 {N_("PostScript (*.ps)"), ".ps"},
247 {N_("Comma-Separated Values (*.csv)"), ".csv"},
248 {N_("Portable Network Graphics (*.png)"), ".png"},
249 {N_("Scalable Vector Graphics (*.svg)"), ".svg"}
254 on_combo_change (GtkFileChooser *chooser)
256 gboolean sensitive = FALSE;
257 GtkWidget *combo = gtk_file_chooser_get_extra_widget (chooser);
259 int file_type = FT_AUTO;
260 gchar *fn = gtk_file_chooser_get_filename (chooser);
262 if (combo && gtk_widget_get_realized (combo))
263 file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
265 if (fn != NULL && file_type == FT_AUTO)
267 for (gint i = 1 ; i < n_FT ; ++i)
269 if (g_str_has_suffix (fn, ft[i].ext))
277 sensitive = (fn != NULL);
281 gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT, sensitive);
285 static GtkListStore *
286 create_file_type_list (void)
290 GtkListStore *list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
292 for (i = 0 ; i < n_FT ; ++i)
294 gtk_list_store_append (list, &iter);
295 gtk_list_store_set (list, &iter,
296 0, gettext (ft[i].label),
305 psppire_output_window_export (PsppireOutputWindow *window)
311 GtkFileChooser *chooser;
313 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Export Output"),
315 GTK_FILE_CHOOSER_ACTION_SAVE,
316 _("Cancel"), GTK_RESPONSE_CANCEL,
317 _("Save"), GTK_RESPONSE_ACCEPT,
320 g_object_set (dialog, "local-only", FALSE, NULL);
322 chooser = GTK_FILE_CHOOSER (dialog);
324 list = create_file_type_list ();
326 combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list));
330 /* Create text cell renderer */
331 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
332 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, FALSE);
334 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cell, "text", 0);
337 g_signal_connect_swapped (combo, "changed", G_CALLBACK (on_combo_change), chooser);
339 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
341 gtk_file_chooser_set_extra_widget (chooser, combo);
343 g_signal_connect (chooser, "selection-changed", G_CALLBACK (on_combo_change), NULL);
344 gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
346 response = gtk_dialog_run (GTK_DIALOG (dialog));
348 if (response == GTK_RESPONSE_ACCEPT)
350 gint file_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
351 gchar *filename = gtk_file_chooser_get_filename (chooser);
352 struct string_map options;
354 g_return_if_fail (filename);
356 if (file_type == FT_AUTO)
358 /* If the "Infer file type from extension" option was chosen,
359 search for the respective type in the list.
360 (It's a O(n) search, but fortunately n is small). */
362 for (i = 1 ; i < n_FT ; ++i)
364 if (g_str_has_suffix (filename, ft[i].ext))
371 else if (! g_str_has_suffix (filename, ft[file_type].ext))
373 /* If an explicit document format was chosen, and if the chosen
374 filename does not already have that particular "extension",
378 gchar *of = filename;
379 filename = g_strconcat (filename, ft[file_type].ext, NULL);
383 string_map_init (&options);
384 string_map_insert (&options, "output-file", filename);
389 export_output (window, &options, "spv");
392 export_output (window, &options, "pdf");
395 export_output (window, &options, "html");
398 export_output (window, &options, "odt");
401 export_output (window, &options, "ps");
404 export_output (window, &options, "csv");
407 export_output (window, &options, "png");
410 export_output (window, &options, "svg");
414 string_map_insert (&options, "box", "unicode");
418 string_map_insert (&options, "charts", "none");
419 export_output (window, &options, "txt");
422 g_assert_not_reached ();
425 string_map_destroy (&options);
430 gtk_widget_destroy (dialog);
434 psppire_output_window_init (PsppireOutputWindow *window)
436 GtkBuilder *xml = builder_new ("output-window.ui");
437 GtkApplication *app = GTK_APPLICATION (g_application_get_default());
438 GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
439 gtk_container_add (GTK_CONTAINER (window), box);
441 GtkWidget *paned = get_widget_assert (xml, "paned1");
443 window->dispose_has_run = FALSE;
445 window->view = psppire_output_view_new (
446 GTK_LAYOUT (get_widget_assert (xml, "output")),
447 GTK_TREE_VIEW (get_widget_assert (xml, "overview")));
449 g_signal_connect (window,
451 G_CALLBACK (cancel_urgency),
454 GObject *menu = get_object_assert (xml, "output-window-menu", G_TYPE_MENU);
455 GtkWidget *menubar = gtk_menu_bar_new_from_model (G_MENU_MODEL (menu));
456 gtk_box_pack_start (GTK_BOX (box), menubar, FALSE, FALSE, 0);
457 gtk_box_pack_start (GTK_BOX (box), paned, TRUE, TRUE, 0);
459 gtk_menu_shell_append (GTK_MENU_SHELL (menubar),
460 create_windows_menu (GTK_WINDOW (window)));
462 gtk_menu_shell_append (GTK_MENU_SHELL (menubar),
463 create_help_menu (GTK_WINDOW (window)));
466 GSimpleAction *print = g_simple_action_new ("print", NULL);
467 g_signal_connect_swapped (print, "activate", G_CALLBACK (psppire_output_window_print), window);
468 g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (print));
471 const gchar *accels[2] = { "<Primary>P", NULL};
472 gtk_application_set_accels_for_action (app,
479 GSimpleAction *export = g_simple_action_new ("export", NULL);
480 g_signal_connect_swapped (export, "activate", G_CALLBACK (psppire_output_window_export), window);
481 g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (export));
485 GSimpleAction *select_all = g_simple_action_new ("select-all", NULL);
486 g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (select_all));
490 GSimpleAction *copy = g_simple_action_new ("copy", NULL);
491 g_action_map_add_action (G_ACTION_MAP (window), G_ACTION (copy));
493 const gchar *accels[2] = { "<Primary>C", NULL};
494 gtk_application_set_accels_for_action (app,
500 g_object_unref (xml);
502 g_signal_connect (window, "delete-event",
503 G_CALLBACK (on_delete), window);
508 psppire_output_window_new (void)
510 return GTK_WIDGET (g_object_new (psppire_output_window_get_type (),
511 /* TRANSLATORS: This will be part of a filename. Please avoid whitespace. */
512 "filename", _("Output"),
513 "description", _("Output Viewer"),
518 psppire_output_window_print (PsppireOutputWindow *window)
520 psppire_output_view_print (window->view, GTK_WINDOW (window));