Warnings: missing initializer for value_tables and function type cast (GObject)
[pspp] / src / ui / gui / psppire-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2009, 2010, 2011, 2013, 2014, 2020 Free Software Foundation
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18 #include <gl/relocatable.h>
19
20 #include "psppire-window.h"
21 #include "psppire-window-base.h"
22
23 #include <gtk/gtk.h>
24
25 #include <stdlib.h>
26
27 #include <gettext.h>
28 #define _(msgid) gettext (msgid)
29 #define N_(msgid) msgid
30
31 #include "data/any-reader.h"
32 #include "data/file-handle-def.h"
33 #include "data/dataset.h"
34 #include "libpspp/version.h"
35 #include "output/group-item.h"
36 #include "output/pivot-table.h"
37 #include "output/spv/spv.h"
38 #include "output/spv/spv-output.h"
39 #include "output/spv/spv-select.h"
40
41 #include "helper.h"
42 #include "psppire-data-window.h"
43 #include "psppire-encoding-selector.h"
44 #include "psppire-syntax-window.h"
45 #include "psppire-window-register.h"
46
47 static void psppire_window_base_init     (PsppireWindowClass *class);
48 static void psppire_window_class_init    (PsppireWindowClass *class);
49 static void psppire_window_init          (PsppireWindow      *window);
50
51
52 static GObjectClass *parent_class;
53
54 GType
55 psppire_window_get_type (void)
56 {
57   static GType psppire_window_type = 0;
58
59   if (!psppire_window_type)
60     {
61       static const GTypeInfo psppire_window_info =
62       {
63         sizeof (PsppireWindowClass),
64         (GBaseInitFunc) (void (*)(void)) psppire_window_base_init,
65         (GBaseFinalizeFunc) NULL,
66         (GClassInitFunc) (void (*)(void)) psppire_window_class_init,
67         (GClassFinalizeFunc) NULL,
68         NULL,
69         sizeof (PsppireWindow),
70         0,
71         (GInstanceInitFunc) (void (*)(void)) psppire_window_init,
72         NULL /* value_table */
73       };
74
75       psppire_window_type =
76         g_type_register_static (PSPPIRE_TYPE_WINDOW_BASE, "PsppireWindow",
77                                 &psppire_window_info, G_TYPE_FLAG_ABSTRACT);
78     }
79
80   return psppire_window_type;
81 }
82
83
84 /* Properties */
85 enum
86 {
87   PROP_0,
88   PROP_FILENAME,
89   PROP_DESCRIPTION,
90   PROP_ID
91 };
92
93
94 static void
95 psppire_window_set_title (PsppireWindow *window)
96 {
97   GString *title = g_string_sized_new (80);
98
99   if (window->edited != NULL)
100     g_string_append_c (title, '*');
101
102   if (window->basename || window->id)
103     {
104       if (window->basename)
105         g_string_append_printf (title, "%s ", window->basename);
106
107       if (window->id)
108         g_string_append_printf (title, "[%s] ", window->id);
109
110       g_string_append_unichar (title, 0x2014); /* em dash */
111       g_string_append_c (title, ' '); /* em dash */
112     }
113
114   g_string_append_printf (title, "PSPPIRE %s", window->description);
115
116   int minor = 1;
117   sscanf (bare_version, "%*d.%d.%*d", &minor);
118   if (minor % 2)
119     g_string_append_printf (title, " - Test version! Please report bugs to %s", PACKAGE_BUGREPORT);
120
121   gtk_window_set_title (GTK_WINDOW (window), title->str);
122
123   g_string_free (title, TRUE);
124 }
125
126 static void
127 psppire_window_update_list_name (PsppireWindow *window)
128 {
129   PsppireWindowRegister *reg = psppire_window_register_new ();
130   GString *candidate = g_string_sized_new (80);
131   int n;
132
133   n = 1;
134   do
135     {
136       /* Compose a name. */
137       g_string_truncate (candidate, 0);
138       if (window->filename)
139         {
140           gchar *display_filename = g_filename_display_name (window->filename);
141           g_string_append (candidate, display_filename);
142           g_free (display_filename);
143
144           if (window->id)
145             g_string_append_printf (candidate, " [%s]", window->id);
146         }
147       else if (window->id)
148         g_string_append_printf (candidate, "[%s]", window->id);
149       else
150         g_string_append (candidate, window->description);
151
152       if (n++ > 1)
153         g_string_append_printf (candidate, " #%d", n);
154
155       if (window->list_name && !strcmp (candidate->str, window->list_name))
156         {
157           /* Keep the existing name. */
158           g_string_free (candidate, TRUE);
159           return;
160         }
161     }
162   while (psppire_window_register_lookup (reg, candidate->str));
163
164   if (window->list_name)
165     psppire_window_register_remove (reg, window->list_name);
166
167   g_free (window->list_name);
168   window->list_name = g_string_free (candidate, FALSE);
169
170   psppire_window_register_insert (reg, window, window->list_name);
171 }
172
173 static void
174 psppire_window_name_changed (PsppireWindow *window)
175 {
176   psppire_window_set_title (window);
177   psppire_window_update_list_name (window);
178 }
179
180 static void
181 psppire_window_set_property (GObject         *object,
182                              guint            prop_id,
183                              const GValue    *value,
184                              GParamSpec      *pspec)
185 {
186   PsppireWindow *window = PSPPIRE_WINDOW (object);
187
188   switch (prop_id)
189     {
190     case PROP_DESCRIPTION:
191       g_free (window->description);
192       window->description = g_value_dup_string (value);
193       psppire_window_set_title (window);
194       break;
195     case PROP_FILENAME:
196       g_free (window->filename);
197       window->filename = g_value_dup_string (value);
198       g_free (window->basename);
199       window->basename = (window->filename
200                           ? g_filename_display_basename (window->filename)
201                           : NULL);
202       psppire_window_name_changed (window);
203       break;
204     case PROP_ID:
205       g_free (window->id);
206       window->id = g_value_dup_string (value);
207       psppire_window_name_changed (window);
208       break;
209     default:
210       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
211       break;
212     };
213 }
214
215
216 static void
217 psppire_window_get_property (GObject         *object,
218                              guint            prop_id,
219                              GValue          *value,
220                              GParamSpec      *pspec)
221 {
222   PsppireWindow *window = PSPPIRE_WINDOW (object);
223
224   switch (prop_id)
225     {
226     case PROP_FILENAME:
227       g_value_set_string (value, window->filename);
228       break;
229     case PROP_DESCRIPTION:
230       g_value_set_string (value, window->description);
231       break;
232     case PROP_ID:
233       g_value_set_string (value, window->id);
234       break;
235     default:
236       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
237       break;
238     };
239 }
240
241
242 static void
243 psppire_window_finalize (GObject *object)
244 {
245   PsppireWindow *window = PSPPIRE_WINDOW (object);
246
247   PsppireWindowRegister *reg = psppire_window_register_new ();
248
249   if (window->edited)
250     g_date_time_unref (window->edited);
251
252   g_signal_handler_disconnect (reg, window->remove_handler);
253   g_signal_handler_disconnect (reg, window->insert_handler);
254   psppire_window_register_remove (reg, window->list_name);
255   g_free (window->filename);
256   g_free (window->basename);
257   g_free (window->id);
258   g_free (window->description);
259   g_free (window->list_name);
260
261   g_hash_table_destroy (window->menuitem_table);
262
263   if (G_OBJECT_CLASS (parent_class)->finalize)
264     G_OBJECT_CLASS (parent_class)->finalize (object);
265 }
266
267 static void
268 psppire_window_class_init (PsppireWindowClass *class)
269 {
270   GObjectClass *object_class = G_OBJECT_CLASS (class);
271
272   GParamSpec *description_spec =
273     null_if_empty_param ("description",
274                        "Description",
275                        "A string describing the usage of the window",
276                          NULL, /*Should be overridden by derived classes */
277                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
278
279   GParamSpec *filename_spec =
280     null_if_empty_param ("filename",
281                        "File name",
282                        "The name of the file associated with this window, if any",
283                          NULL,
284                          G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
285
286   GParamSpec *id_spec =
287     null_if_empty_param ("id",
288                          "Identifier",
289                          "The PSPP language identifier for the data associated "
290                          "with this window (e.g. dataset name)",
291                          NULL,
292                          G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
293
294   object_class->set_property = psppire_window_set_property;
295   object_class->get_property = psppire_window_get_property;
296
297   g_object_class_install_property (object_class,
298                                    PROP_DESCRIPTION,
299                                    description_spec);
300
301   g_object_class_install_property (object_class,
302                                    PROP_FILENAME,
303                                    filename_spec);
304
305   g_object_class_install_property (object_class,
306                                    PROP_ID,
307                                    id_spec);
308
309   parent_class = g_type_class_peek_parent (class);
310 }
311
312
313 static void
314 psppire_window_base_init (PsppireWindowClass *class)
315 {
316   GObjectClass *object_class = G_OBJECT_CLASS (class);
317
318   object_class->finalize = psppire_window_finalize;
319 }
320
321
322
323 static void
324 insert_menuitem_into_menu (PsppireWindow *window, gpointer key)
325 {
326   gchar *filename;
327   GtkWidget *item;
328   filename = g_filename_display_name (key);
329   item = gtk_check_menu_item_new_with_label (filename);
330   g_object_ref_sink (item);
331   g_free (filename);
332
333   g_hash_table_insert (window->menuitem_table, key, item);
334 }
335
336 static void
337 insert_item (gpointer key, gpointer value, gpointer data)
338 {
339   PsppireWindow *window = PSPPIRE_WINDOW (data);
340
341   if (NULL != g_hash_table_lookup (window->menuitem_table, key))
342     return;
343
344   insert_menuitem_into_menu (window, key);
345 }
346
347 /* Insert a new item into the window menu */
348 static void
349 insert_menuitem (GObject *reg, const gchar *key, gpointer data)
350 {
351   PsppireWindow *window = PSPPIRE_WINDOW (data);
352
353   insert_menuitem_into_menu (window, (gpointer) key);
354 }
355
356
357 static void
358 remove_menuitem (PsppireWindowRegister *reg, const gchar *key, gpointer data)
359 {
360   PsppireWindow *window = PSPPIRE_WINDOW (data);
361   g_hash_table_remove (window->menuitem_table, key);
362 }
363
364 static void
365 insert_existing_items (PsppireWindow *window)
366 {
367   psppire_window_register_foreach (psppire_window_register_new (), insert_item, window);
368 }
369
370
371 static gboolean
372 on_delete (PsppireWindow *w, GdkEvent *event, gpointer user_data)
373 {
374   PsppireWindowRegister *reg = psppire_window_register_new ();
375
376   if (w->edited != NULL)
377     {
378       gint response = psppire_window_query_save (w);
379
380       switch (response)
381         {
382         default:
383         case GTK_RESPONSE_CANCEL:
384           return TRUE;
385           break;
386         case GTK_RESPONSE_APPLY:
387           psppire_window_save (w);
388           if (w->edited != NULL)
389             {
390               /* Save failed, or user exited Save As dialog with Cancel. */
391               return TRUE;
392             }
393           break;
394         case GTK_RESPONSE_REJECT:
395           break;
396         }
397     }
398
399   if (1 == psppire_window_register_n_items (reg))
400     gtk_main_quit ();
401
402   return FALSE;
403 }
404
405
406 static void
407 psppire_window_init (PsppireWindow *window)
408 {
409   window->filename = NULL;
410   window->basename = NULL;
411   window->id = NULL;
412   window->description = NULL;
413   window->list_name = NULL;
414   window->edited = NULL;
415
416   window->menuitem_table  = g_hash_table_new_full (g_str_hash, g_str_equal,
417                                                    NULL, g_object_unref);
418
419
420   g_signal_connect (window,  "realize", G_CALLBACK (insert_existing_items), NULL);
421
422   PsppireWindowRegister *reg = psppire_window_register_new ();
423   window->insert_handler = g_signal_connect (reg,
424                                              "inserted",
425                                              G_CALLBACK (insert_menuitem),
426                                              window);
427
428   window->remove_handler = g_signal_connect (reg,
429                                              "removed",
430                                              G_CALLBACK (remove_menuitem),
431                                              window);
432
433   window->added_separator = FALSE;
434
435   g_signal_connect_swapped (window, "delete-event", G_CALLBACK (on_delete), window);
436
437   g_object_set (window, "icon-name", "pspp", NULL);
438 }
439
440 /*
441    Ask the user if the buffer should be saved.
442    Return the response.
443 */
444 gint
445 psppire_window_query_save (PsppireWindow *se)
446 {
447   gint response;
448   GtkWidget *dialog;
449   GtkWidget *cancel_button;
450
451   gchar *description;
452
453   GDateTime *now = g_date_time_new_now_utc ();
454   GTimeSpan timespan = g_date_time_difference (now, se->edited);
455   g_date_time_unref (now);
456
457   if (se->filename)
458     description = g_filename_display_basename (se->filename);
459   else if (se->id)
460     description = g_strdup (se->id);
461   else
462     description = g_strdup (se->description);
463   dialog =
464     gtk_message_dialog_new (GTK_WINDOW (se),
465                             GTK_DIALOG_MODAL,
466                             GTK_MESSAGE_WARNING,
467                             GTK_BUTTONS_NONE,
468                             _("Save the changes to `%s' before closing?"),
469                             description);
470   g_free (description);
471
472   g_object_set (dialog, "icon-name", "pspp", NULL);
473
474   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
475                                             _("If you don't save, changes from the last %ld seconds will be permanently lost."),
476                                             timespan / G_TIME_SPAN_SECOND);
477
478   gtk_dialog_add_button  (GTK_DIALOG (dialog),
479                           _("Close _without saving"),
480                           GTK_RESPONSE_REJECT);
481
482   cancel_button = gtk_dialog_add_button  (GTK_DIALOG (dialog),
483                                           _("Cancel"),
484                                           GTK_RESPONSE_CANCEL);
485
486   gtk_dialog_add_button  (GTK_DIALOG (dialog),
487                           _("Save"),
488                           GTK_RESPONSE_APPLY);
489
490   gtk_widget_grab_focus (cancel_button);
491
492   response = gtk_dialog_run (GTK_DIALOG (dialog));
493
494   gtk_widget_destroy (dialog);
495
496   return response;
497 }
498
499
500 /* The return value is encoded in the glib filename encoding. */
501 const gchar *
502 psppire_window_get_filename (PsppireWindow *w)
503 {
504   return w->filename;
505 }
506
507
508 /* FILENAME must be encoded in the glib filename encoding. */
509 void
510 psppire_window_set_filename (PsppireWindow *w, const gchar *filename)
511 {
512   g_object_set (w, "filename", filename, NULL);
513 }
514
515 void
516 psppire_window_set_unsaved (PsppireWindow *w)
517 {
518   if (w->edited == NULL)
519     w->edited = g_date_time_new_now_utc ();
520
521   psppire_window_set_title (w);
522 }
523
524 gboolean
525 psppire_window_get_unsaved (PsppireWindow *w)
526 {
527   return w->edited != NULL;
528 }
529
530
531 \f
532
533
534 static void
535 minimise_window (gpointer key, gpointer value, gpointer data)
536 {
537   gtk_window_iconify (GTK_WINDOW (value));
538 }
539
540
541 void
542 psppire_window_minimise_all (void)
543 {
544   PsppireWindowRegister *reg = psppire_window_register_new ();
545
546   g_hash_table_foreach (reg->name_table, minimise_window, NULL);
547 }
548
549
550 \f
551
552 GType
553 psppire_window_model_get_type (void)
554 {
555   static GType window_model_type = 0;
556
557   if (! window_model_type)
558     {
559       static const GTypeInfo window_model_info =
560       {
561         sizeof (PsppireWindowIface), /* class_size */
562         NULL,           /* base_init */
563         NULL,           /* base_finalize */
564         NULL,
565         NULL,           /* class_finalize */
566         NULL,           /* class_data */
567         0,
568         0,              /* n_preallocs */
569         NULL,
570         NULL            /* value_table */
571       };
572
573       window_model_type =
574         g_type_register_static (G_TYPE_INTERFACE, "PsppireWindowModel",
575                                 &window_model_info, 0);
576
577       g_type_interface_add_prerequisite (window_model_type, G_TYPE_OBJECT);
578     }
579
580   return window_model_type;
581 }
582
583
584 void
585 psppire_window_save (PsppireWindow *w)
586 {
587   PsppireWindowIface *i = PSPPIRE_WINDOW_MODEL_GET_IFACE (w);
588
589   g_assert (i);
590   g_return_if_fail (i->save);
591
592   if (w->filename == NULL)
593     psppire_window_save_as (w);
594   else
595     {
596       i->save (w);
597       if (w->edited)
598         g_date_time_unref (w->edited);
599       w->edited = NULL;
600
601       psppire_window_set_title (w);
602     }
603 }
604
605 void
606 psppire_window_save_as (PsppireWindow *w)
607 {
608   PsppireWindowIface *i = PSPPIRE_WINDOW_MODEL_GET_IFACE (w);
609   gchar *old_filename;
610
611   g_assert (i);
612   g_return_if_fail (i->pick_filename);
613
614   old_filename = w->filename;
615   w->filename = NULL;
616
617   i->pick_filename (w);
618   if (w->filename == NULL)
619     w->filename = old_filename;
620   else
621     {
622       g_free (old_filename);
623       psppire_window_save (w);
624     }
625 }
626
627 static void delete_recent (const char *file_name);
628
629 gboolean
630 psppire_window_load (PsppireWindow *w, const gchar *file,
631                      const gchar *encoding, gpointer hint)
632 {
633   gboolean ok;
634   PsppireWindowIface *i = PSPPIRE_WINDOW_MODEL_GET_IFACE (w);
635
636   g_assert (PSPPIRE_IS_WINDOW_MODEL (w));
637
638   g_assert (i);
639
640   g_return_val_if_fail (i->load, FALSE);
641
642   ok = i->load (w, file, encoding, hint);
643
644   if (ok)
645     {
646       psppire_window_set_filename (w, file);
647       if (w->edited)
648         g_date_time_unref (w->edited);
649       w->edited = NULL;
650     }
651   else
652     delete_recent (file);
653
654   return ok;
655 }
656
657
658 GtkWidget *
659 psppire_window_file_chooser_dialog (PsppireWindow *toplevel)
660 {
661   GtkFileFilter *filter = gtk_file_filter_new ();
662   GtkWidget *dialog =
663     gtk_file_chooser_dialog_new (_("Open"),
664                                  GTK_WINDOW (toplevel),
665                                  GTK_FILE_CHOOSER_ACTION_OPEN,
666                                  _("Cancel"), GTK_RESPONSE_CANCEL,
667                                  _("Open"), GTK_RESPONSE_ACCEPT,
668                                  NULL);
669
670   g_object_set (dialog, "local-only", FALSE, NULL);
671
672   gtk_file_filter_set_name (filter, _("Data and Syntax Files"));
673   gtk_file_filter_add_mime_type (filter, "application/x-spss-sav");
674   gtk_file_filter_add_mime_type (filter, "application/x-spss-por");
675   gtk_file_filter_add_mime_type (filter, "application/x-spss-spv");
676   gtk_file_filter_add_pattern (filter, "*.zsav");
677   gtk_file_filter_add_pattern (filter, "*.sps");
678   gtk_file_filter_add_pattern (filter, "*.SPS");
679   gtk_file_filter_add_pattern (filter, "*.spv");
680   gtk_file_filter_add_pattern (filter, "*.SPV");
681   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
682
683   filter = gtk_file_filter_new ();
684   gtk_file_filter_set_name (filter, _("System Files (*.sav, *.zsav)"));
685   gtk_file_filter_add_mime_type (filter, "application/x-spss-sav");
686   gtk_file_filter_add_pattern (filter, "*.zsav");
687   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
688
689   filter = gtk_file_filter_new ();
690   gtk_file_filter_set_name (filter, _("Portable Files (*.por) "));
691   gtk_file_filter_add_mime_type (filter, "application/x-spss-por");
692   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
693
694   filter = gtk_file_filter_new ();
695   gtk_file_filter_set_name (filter, _("Syntax Files (*.sps) "));
696   gtk_file_filter_add_pattern (filter, "*.sps");
697   gtk_file_filter_add_pattern (filter, "*.SPS");
698   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
699
700   filter = gtk_file_filter_new ();
701   gtk_file_filter_set_name (filter, _("Output Files (*.spv) "));
702   gtk_file_filter_add_pattern (filter, "*.spv");
703   gtk_file_filter_add_pattern (filter, "*.SPV");
704   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
705
706   filter = gtk_file_filter_new ();
707   gtk_file_filter_set_name (filter, _("All Files"));
708   gtk_file_filter_add_pattern (filter, "*");
709   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
710
711   if (toplevel->filename)
712     {
713       const gchar *filename = toplevel->filename;
714       gchar *dir_name;
715
716       if (! g_path_is_absolute (filename))
717         {
718           gchar *path =
719             g_build_filename (g_get_current_dir (), filename, NULL);
720           dir_name = g_path_get_dirname (path);
721           g_free (path);
722         }
723       else
724         {
725           dir_name = g_path_get_dirname (filename);
726         }
727       gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
728                                            dir_name);
729       free (dir_name);
730     }
731
732     gtk_file_chooser_set_extra_widget (
733       GTK_FILE_CHOOSER (dialog),
734       psppire_encoding_selector_new ("Auto", true));
735
736   return dialog;
737 }
738
739 struct item_path
740   {
741     const struct spv_item **nodes;
742     size_t n;
743
744 #define N_STUB 10
745     const struct spv_item *stub[N_STUB];
746   };
747
748 static void
749 swap_nodes (const struct spv_item **a, const struct spv_item **b)
750 {
751   const struct spv_item *tmp = *a;
752   *a = *b;
753   *b = tmp;
754 }
755
756 static void
757 get_path (const struct spv_item *item, struct item_path *path)
758 {
759   size_t allocated = 10;
760   path->nodes = path->stub;
761   path->n = 0;
762
763   while (item)
764     {
765       if (path->n >= allocated)
766         {
767           if (path->nodes == path->stub)
768             path->nodes = xmemdup (path->stub, sizeof path->stub);
769           path->nodes = x2nrealloc (path->nodes, &allocated,
770                                     sizeof *path->nodes);
771         }
772       path->nodes[path->n++] = item;
773       item = item->parent;
774     }
775
776   for (size_t i = 0; i < path->n / 2; i++)
777     swap_nodes (&path->nodes[i], &path->nodes[path->n - i - 1]);
778 }
779
780 static void
781 free_path (struct item_path *path)
782 {
783   if (path && path->nodes != path->stub)
784     free (path->nodes);
785 }
786
787 static void
788 dump_heading_transition (const struct spv_item *old,
789                          const struct spv_item *new)
790 {
791   if (old == new)
792     return;
793
794   struct item_path old_path, new_path;
795   get_path (old, &old_path);
796   get_path (new, &new_path);
797
798   size_t common = 0;
799   for (; common < old_path.n && common < new_path.n; common++)
800     if (old_path.nodes[common] != new_path.nodes[common])
801       break;
802
803   for (size_t i = common; i < old_path.n; i++)
804     group_close_item_submit (group_close_item_create ());
805   for (size_t i = common; i < new_path.n; i++)
806     group_open_item_submit (group_open_item_create (
807                               new_path.nodes[i]->command_id));
808
809   free_path (&old_path);
810   free_path (&new_path);
811 }
812
813 void
814 read_spv_file (const char *filename)
815 {
816   struct spv_reader *spv;
817   char *error = spv_open (filename, &spv);
818   if (error)
819     {
820       /* XXX */
821       fprintf (stderr, "%s\n", error);
822       return;
823     }
824
825   struct spv_item **items;
826   size_t n_items;
827   spv_select (spv, NULL, 0, &items, &n_items);
828   struct spv_item *prev_heading = spv_get_root (spv);
829   for (size_t i = 0; i < n_items; i++)
830     {
831       struct spv_item *heading
832         = items[i]->type == SPV_ITEM_HEADING ? items[i] : items[i]->parent;
833       dump_heading_transition (prev_heading, heading);
834       if (items[i]->type == SPV_ITEM_TEXT)
835         spv_text_submit (items[i]);
836       else if (items[i]->type == SPV_ITEM_TABLE)
837         pivot_table_submit (spv_item_get_table (items[i]));
838       prev_heading = heading;
839     }
840   dump_heading_transition (prev_heading, spv_get_root (spv));
841   free (items);
842   spv_close (spv);
843 }
844
845 /* Callback for the file_open action.
846    Prompts for a filename and opens it */
847 void
848 psppire_window_open (PsppireWindow *de)
849 {
850   GtkWidget *dialog = psppire_window_file_chooser_dialog (de);
851
852   gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), relocate (examples_dir), NULL);
853
854   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
855     {
856     case GTK_RESPONSE_ACCEPT:
857       {
858         gchar *name =
859           gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
860
861         const gchar **cs = NULL;
862         g_get_filename_charsets (&cs);
863
864         gchar *encoding = psppire_encoding_selector_get_encoding (
865           gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
866
867         struct file_handle *fh = fh_create_file (NULL, name, cs[0], fh_default_properties ());
868
869         int retval = any_reader_detect (fh, NULL);
870         if (retval == 1)
871           open_data_window (de, name, encoding, NULL);
872         else if (retval == 0)
873           {
874             char *error = spv_detect (name);
875             if (!error)
876               read_spv_file (name);
877             else
878               {
879                 free (error);
880                 open_syntax_window (name, encoding);
881               }
882           }
883
884         g_free (encoding);
885         fh_unref (fh);
886         g_free (name);
887       }
888       break;
889     default:
890       break;
891     }
892
893   gtk_widget_destroy (dialog);
894 }
895
896
897 /* Puts FILE_NAME (encoded in the glib file name encoding) into the recent list
898    with associated MIME_TYPE.  If it's already in the list, it moves it to the
899    top. */
900 void
901 add_most_recent (const char *file_name,
902                  const char *mime_type, const char *encoding)
903 {
904   gchar *uri = g_filename_to_uri  (file_name, NULL, NULL);
905   if (uri)
906     {
907       GtkRecentData recent_data;
908       gchar *full_mime_type;
909
910       if (encoding && encoding[0])
911         full_mime_type = g_strdup_printf ("%s; charset=%s",
912                                           mime_type, encoding);
913       else
914         full_mime_type = g_strdup (mime_type);
915
916       recent_data.display_name = NULL;
917       recent_data.description = NULL;
918       recent_data.mime_type = full_mime_type;
919       recent_data.app_name = CONST_CAST (gchar *, g_get_application_name ());
920       recent_data.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
921       recent_data.groups = NULL;
922       recent_data.is_private = FALSE;
923
924       gtk_recent_manager_add_full (gtk_recent_manager_get_default (),
925                                    uri, &recent_data);
926
927       g_free (recent_data.app_exec);
928       g_free (full_mime_type);
929     }
930
931   g_free (uri);
932 }
933
934
935
936 /*
937    If FILE_NAME exists in the recent list, then  delete it.
938  */
939 static void
940 delete_recent (const char *file_name)
941 {
942   gchar *uri = g_filename_to_uri  (file_name, NULL, NULL);
943
944   if (uri)
945     gtk_recent_manager_remove_item (gtk_recent_manager_get_default (), uri, NULL);
946
947   g_free (uri);
948 }
949