+GtkWidget *
+psppire_window_file_chooser_dialog (PsppireWindow *toplevel)
+{
+ GtkFileFilter *filter = gtk_file_filter_new ();
+ GtkWidget *dialog =
+ gtk_file_chooser_dialog_new (_("Open"),
+ GTK_WINDOW (toplevel),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ g_object_set (dialog, "local-only", FALSE, NULL);
+
+ gtk_file_filter_set_name (filter, _("Data and Syntax Files"));
+ gtk_file_filter_add_mime_type (filter, "application/x-spss-sav");
+ gtk_file_filter_add_mime_type (filter, "application/x-spss-por");
+ gtk_file_filter_add_mime_type (filter, "application/x-spss-spv");
+ gtk_file_filter_add_pattern (filter, "*.zsav");
+ gtk_file_filter_add_pattern (filter, "*.sps");
+ gtk_file_filter_add_pattern (filter, "*.SPS");
+ gtk_file_filter_add_pattern (filter, "*.spv");
+ gtk_file_filter_add_pattern (filter, "*.SPV");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("System Files (*.sav, *.zsav)"));
+ gtk_file_filter_add_mime_type (filter, "application/x-spss-sav");
+ gtk_file_filter_add_pattern (filter, "*.zsav");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("Portable Files (*.por) "));
+ gtk_file_filter_add_mime_type (filter, "application/x-spss-por");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("Syntax Files (*.sps) "));
+ gtk_file_filter_add_pattern (filter, "*.sps");
+ gtk_file_filter_add_pattern (filter, "*.SPS");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("Output Files (*.spv) "));
+ gtk_file_filter_add_pattern (filter, "*.spv");
+ gtk_file_filter_add_pattern (filter, "*.SPV");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("All Files"));
+ gtk_file_filter_add_pattern (filter, "*");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ if (toplevel->filename)
+ {
+ const gchar *filename = toplevel->filename;
+ gchar *dir_name;
+
+ if (! g_path_is_absolute (filename))
+ {
+ gchar *path =
+ g_build_filename (g_get_current_dir (), filename, NULL);
+ dir_name = g_path_get_dirname (path);
+ g_free (path);
+ }
+ else
+ {
+ dir_name = g_path_get_dirname (filename);
+ }
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
+ dir_name);
+ free (dir_name);
+ }
+
+ gtk_file_chooser_set_extra_widget (
+ GTK_FILE_CHOOSER (dialog),
+ psppire_encoding_selector_new ("Auto", true));
+
+ return dialog;
+}
+
+struct item_path
+ {
+ const struct spv_item **nodes;
+ size_t n;
+
+#define N_STUB 10
+ const struct spv_item *stub[N_STUB];
+ };
+
+static void
+swap_nodes (const struct spv_item **a, const struct spv_item **b)
+{
+ const struct spv_item *tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+static void
+get_path (const struct spv_item *item, struct item_path *path)
+{
+ size_t allocated = 10;
+ path->nodes = path->stub;
+ path->n = 0;
+
+ while (item)
+ {
+ if (path->n >= allocated)
+ {
+ if (path->nodes == path->stub)
+ path->nodes = xmemdup (path->stub, sizeof path->stub);
+ path->nodes = x2nrealloc (path->nodes, &allocated,
+ sizeof *path->nodes);
+ }
+ path->nodes[path->n++] = item;
+ item = item->parent;
+ }
+
+ for (size_t i = 0; i < path->n / 2; i++)
+ swap_nodes (&path->nodes[i], &path->nodes[path->n - i - 1]);
+}
+
+static void
+free_path (struct item_path *path)
+{
+ if (path && path->nodes != path->stub)
+ free (path->nodes);
+}
+
+static void
+dump_heading_transition (const struct spv_item *old,
+ const struct spv_item *new)
+{
+ if (old == new)
+ return;
+
+ struct item_path old_path, new_path;
+ get_path (old, &old_path);
+ get_path (new, &new_path);
+
+ size_t common = 0;
+ for (; common < old_path.n && common < new_path.n; common++)
+ if (old_path.nodes[common] != new_path.nodes[common])
+ break;
+
+ for (size_t i = common; i < old_path.n; i++)
+ group_close_item_submit (group_close_item_create ());
+ for (size_t i = common; i < new_path.n; i++)
+ group_open_item_submit (group_open_item_create (
+ new_path.nodes[i]->command_id));
+
+ free_path (&old_path);
+ free_path (&new_path);
+}
+