Enable journaling by default and add GUI feature for configuring it.
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 7 Oct 2023 21:31:50 +0000 (14:31 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 1 Nov 2023 03:04:23 +0000 (20:04 -0700)
25 files changed:
NEWS
doc/utilities.texi
src/data/file-name.c
src/data/file-name.h
src/language/commands/set.c
src/output/driver-provider.h
src/output/driver.c
src/output/journal.c
src/output/journal.h
src/ui/gui/main.c
src/ui/gui/options-dialog.c
src/ui/gui/options-dialog.h
src/ui/gui/options.ui
src/ui/gui/psppire-conf.c
src/ui/gui/psppire-conf.h
src/ui/gui/psppire-data-editor.c
src/ui/gui/psppire-dictview.c
src/ui/gui/psppire-output-window.c
src/ui/gui/psppire-window-base.c
src/ui/gui/psppire.c
src/ui/terminal/terminal-reader.c
tests/automake.mk
tests/output/journal.at [new file with mode: 0644]
tests/testsuite.in
tests/ui/terminal/main.at

diff --git a/NEWS b/NEWS
index 9e2516d1cce57c6807ef0c9d78f1350f8410432b..53361d85df8f962b3e0ffbaa3992c1b7321c4db1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,9 @@ Changes since 2.0.0-pre1:
 
  * Improved the search options in the syntax editor.
 
+ * Journaling is now enabled by default in interactive use.  In PSPPIRE,
+   journaling can be configured using Edit|Options.
+
 Changes from 1.6.2 to 2.0.0-pre1:
 
  * The CTABLES command is now implemented.
index f4aed354646fe4143ed79c72cc7824ec451805e0..92a00d0f4656006ed1bf7e76703c228047c78d57 100644 (file)
@@ -911,22 +911,22 @@ Options}).
 @cindex width
 @cindex tnumbers
 
+These subcommands affect journaling, also called logging.  When
+journaling is enabled, @pspp{} writes the commands that it executes,
+plus any errors or other diagostics that it outputs, to a text file,
+called the @dfn{journal} file.
 
-Logging subcommands affect logging of commands executed to external
-files.  These subcommands are
+@pspp{} enables journaling by default when it runs interactively in a
+terminal or in the PSPPIRE GUI.  In the GUI, use @clicksequence{Edit
+@click{} Options@dots{}} to view or override the default location or
+to disable journaling.  From syntax, use @code{SHOW JOURNAL} to see
+the journal's location and whether it is enabled.
 
 @table @asis
 @item JOURNAL
 @itemx LOG
-These subcommands, which are synonyms, control the journal.  The
-default is @subcmd{ON}, which causes commands entered interactively to be
-written to the journal file.  Commands included from syntax files that
-are included interactively and error messages printed by @pspp{} are also
-written to the journal file, prefixed by @samp{>}.  @subcmd{OFF} disables use
-of the journal.
-
-The journal is named @file{pspp.jnl} by default.  A different name may
-be specified.
+Specify @subcmd{ON} to enable the journal and @subcmd{OFF} to disable
+it.  Specify a file name to set the name of the journal file.
 @end table
 
 System file subcommands affect the default format of system files
index 43d0d3f2c61c2bc48492af05d21fbd98e59a78c0..18624fe33080eaad3b05b85eb16812cc18d70392 100644 (file)
@@ -43,6 +43,7 @@
 #include "gl/xmalloca.h"
 
 #include "gettext.h"
+#include "xvasprintf.h"
 #define _(msgid) gettext (msgid)
 
 \f
@@ -271,6 +272,12 @@ default_output_path (void)
   return path;
 }
 
+const char *
+default_log_path (void)
+{
+  return default_output_path ();
+}
+
 #else
 
 /* ... whereas the rest of the world just likes it to be
@@ -283,5 +290,32 @@ default_output_path (void)
   return current_dir;
 }
 
+const char *
+default_log_path (void)
+{
+  static char *log_path = NULL;
+
+  if (!log_path)
+    {
+      char *tmp = NULL;
+      const char *state_home = getenv ("XDG_STATE_HOME");
+      if (!state_home)
+        {
+          const char *home = getenv ("HOME");
+          state_home = tmp = xasprintf ("%s/.local/state", home ? home : "");
+        }
+
+      log_path = xasprintf ("%s/pspp/", state_home);
+
+      struct stat s;
+      if (!stat (state_home, &s) && stat (log_path, &s) && errno == ENOENT)
+        mkdir (log_path, 0700);
+
+      free (tmp);
+    }
+
+  return log_path;
+}
+
 #endif
 
index b4eee83e76aa1d596620f92f2f69d643ae115404..e9e3dce1844621f78cedd3b7740b8c30b851290c 100644 (file)
@@ -33,6 +33,7 @@ FILE *fn_open (const struct file_handle *fn, const char *mode);
 int fn_close (const struct file_handle *fn, FILE *file);
 
 const char * default_output_path (void);
+const char * default_log_path (void);
 
 #if defined _WIN32 || defined __WIN32__
 #define WIN32_LEAN_AND_MEAN  /* avoid including junk */
index a8d46e93ccc22a70500b9726213c6f549b38d7dc..c1792337b876fd2e865ee592104549b1cc8f0989 100644 (file)
@@ -29,6 +29,7 @@
 #include "data/dataset.h"
 #include "data/dictionary.h"
 #include "data/format.h"
+#include "data/identifier.h"
 #include "data/settings.h"
 #include "data/value.h"
 #include "data/variable.h"
@@ -602,24 +603,28 @@ show_INCLUDE (const struct dataset *ds UNUSED)
 static bool
 parse_JOURNAL (struct lexer *lexer)
 {
-  int b = parse_bool (lexer);
-  if (b == true)
-    journal_enable ();
-  else if (b == false)
-    journal_disable ();
-  else if (lex_is_string (lexer) || lex_token (lexer) == T_ID)
+  do
     {
-      char *filename = utf8_to_filename (lex_tokcstr (lexer));
-      journal_set_file_name (filename);
-      free (filename);
+      int b = parse_bool (lexer);
+      if (b == true)
+        journal_enable ();
+      else if (b == false)
+        journal_disable ();
+      else if (lex_is_string (lexer) || lex_token (lexer) == T_ID)
+        {
+          char *filename = utf8_to_filename (lex_tokcstr (lexer));
+          journal_set_file_name (filename);
+          free (filename);
 
-      lex_get (lexer);
-    }
-  else
-    {
-      lex_error (lexer, _("Syntax error expecting ON or OFF or a file name."));
-      return false;
+          lex_get (lexer);
+        }
+      else
+        {
+          lex_error (lexer, _("Syntax error expecting ON or OFF or a file name."));
+          return false;
+        }
     }
+  while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD);
   return true;
 }
 
@@ -1293,6 +1298,8 @@ show_system (const struct dataset *ds UNUSED)
   add_row (table, N_("Locale Directory"), relocate2 (locale_dir, &allocated));
   free (allocated);
 
+  add_row (table, N_("Journal File"), journal_get_file_name ());
+
   add_row (table, N_("Compiler Version"),
 #ifdef __VERSION__
            __VERSION__
index ffa483b4618db20297b00cde88a40aa47d9b1a12..6638b79e16d48ab2f4eeed392730bc368217b5a3 100644 (file)
@@ -44,6 +44,8 @@ const char *output_driver_get_name (const struct output_driver *);
 
 char *output_driver_substitute_heading_vars (const char *, int page_number);
 
+struct output_driver *output_driver_find (const struct output_driver_class *);
+
 /* One kind of output driver.
 
    Output driver implementations must not call msg() to report errors.  This
index 64854d47d7ed79c8e95c9fb857b6e7b04ed65b62..76f89969b662f41601e7763779f3ed0f52d4ce5b 100644 (file)
@@ -431,6 +431,21 @@ output_driver_get_name (const struct output_driver *driver)
 {
   return driver->name;
 }
+
+struct output_driver *
+output_driver_find (const struct output_driver_class *class)
+{
+  struct output_engine *e = engine_stack_top ();
+
+  struct llx *llx;
+  llx_for_each (llx, &e->drivers)
+    {
+      struct output_driver *d = llx_data (llx);
+      if (d->class == class)
+        return d;
+    }
+  return NULL;
+}
 \f
 static struct output_engine *
 output_driver_get_engine (const struct output_driver *driver)
index c00d94ebd39fd552008004fc854ed7398adc6c8f..dc3fc64cfa8ee2ba47da56b19f1d346bbdea00ea 100644 (file)
@@ -22,6 +22,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdbool.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
 
 #include "data/file-name.h"
 #include "libpspp/cast.h"
@@ -40,17 +43,14 @@ struct journal_driver
   {
     struct output_driver driver;
     FILE *file;
-
-    /* Name of journal file. */
     char *file_name;
-    bool destroyed;
-  };
+    bool newly_opened;
+};
 
 static const struct output_driver_class journal_class;
 
-/* Journal driver, if journaling is enabled. */
-static struct journal_driver journal;
-
+/* This persists even if the driver is destroyed and recreated. */
+static char *journal_file_name;
 
 static struct journal_driver *
 journal_driver_cast (struct output_driver *driver)
@@ -59,36 +59,51 @@ journal_driver_cast (struct output_driver *driver)
   return UP_CAST (driver, struct journal_driver, driver);
 }
 
-static void
-journal_close (void)
-{
-  if (journal.file != NULL)
-    {
-      if (fwriteerror (journal.file))
-        msg_error (errno, _("error writing output file `%s'"),
-                   journal.file_name);
-
-      }
-  journal.file = NULL;
-}
-
 static void
 journal_destroy (struct output_driver *driver)
 {
   struct journal_driver *j = journal_driver_cast (driver);
 
-  if (!j->destroyed)
-    journal_close ();
-
-  j->destroyed = true;
+  if (fwriteerror (j->file))
+    msg_error(errno, _("error writing output file `%s'"), j->file_name);
+  free (j->file_name);
+  free (j);
 }
 
 static void
-journal_output (struct journal_driver *j, char *s)
+journal_output (struct journal_driver *j, char *s, const char *prefix)
 {
   if (j->file)
     {
-      fprintf (j->file, "%s\n", s);
+      if (j->newly_opened)
+        {
+          j->newly_opened = false;
+
+          /* Unless this file is empty, start off with a blank line. */
+          struct stat s;
+          if (!fstat (fileno (j->file), &s) && s.st_size != 0)
+            putc ('\n', j->file);
+
+          /* Write the date and time. */
+          char buf[64];
+          time_t t = time (NULL);
+          struct tm *tm = localtime (&t);
+          strftime (buf, sizeof buf, "%Y-%m-%d %H:%M:%S", tm);
+          fprintf (j->file, "* New session at %s.\n", buf);
+        }
+
+      const char *p = s;
+      do
+        {
+          size_t len = strcspn (p, "\n");
+          fputs (prefix, j->file);
+          fwrite (p, len, 1, j->file);
+          putc ('\n', j->file);
+          p += len;
+          if (*p == '\n')
+            p++;
+        }
+      while (*p);
 
       /* Flush the journal in case the syntax we're about to write
          causes a crash.  Having the syntax already written to disk
@@ -107,12 +122,12 @@ journal_submit (struct output_driver *driver, const struct output_item *item)
   switch (item->type)
     {
     case OUTPUT_ITEM_MESSAGE:
-      journal_output (j, msg_to_string (item->message));
+      journal_output (j, msg_to_string (item->message), "> ");
       break;
 
     case OUTPUT_ITEM_TEXT:
       if (item->text.subtype == TEXT_ITEM_SYNTAX)
-        journal_output (j, text_item_get_plain_text (item));
+        journal_output (j, text_item_get_plain_text (item), "");
       break;
 
     case OUTPUT_ITEM_GROUP:
@@ -134,46 +149,50 @@ static const struct output_driver_class journal_class =
     .destroy = journal_destroy,
     .submit = journal_submit,
   };
-\f
 
-/* Enables journaling. */
-void
-journal_init (void)
+static struct journal_driver *
+get_journal_driver (void)
 {
-  journal = (struct journal_driver) {
-    .driver = {
-      .class = &journal_class,
-      .name = xstrdup ("journal"),
-      .device_type = SETTINGS_DEVICE_UNFILTERED,
-    }
-  };
-
-  output_driver_register (&journal.driver);
-  journal_enable ();
+  struct output_driver *d = output_driver_find (&journal_class);
+  return d ? journal_driver_cast (d) : NULL;
 }
 
 /* Disables journaling. */
 void
 journal_disable (void)
 {
-  journal_close ();
+  struct journal_driver *j = get_journal_driver ();
+  if (j)
+    output_driver_destroy (&j->driver);
 }
 
-
 /* Enable journaling. */
 void
 journal_enable (void)
 {
-  if (journal.file == NULL)
+  if (get_journal_driver ())
+    return;
+
+  const char *file_name = journal_get_file_name ();
+  FILE *file = fopen (file_name, "a");
+  if (file == NULL)
     {
-      journal.file = fopen (journal_get_file_name (), "a");
-      if (journal.file == NULL)
-        {
-          msg_error (errno, _("error opening output file `%s'"),
-                     journal_get_file_name ());
-          journal_close ();
-        }
+      msg_error (errno, _("error opening output file `%s'"), file_name);
+      return;
     }
+
+  struct journal_driver *j = xmalloc (sizeof *j);
+  *j = (struct journal_driver) {
+    .driver = {
+      .class = &journal_class,
+      .name = xstrdup ("journal"),
+      .device_type = SETTINGS_DEVICE_UNFILTERED,
+    },
+    .file = file,
+    .file_name = xstrdup (file_name),
+    .newly_opened = true,
+  };
+  output_driver_register (&j->driver);
 }
 
 
@@ -181,16 +200,25 @@ journal_enable (void)
 bool
 journal_is_enabled (void)
 {
-  return journal.file != NULL ;
+  return get_journal_driver () != NULL;
 }
 
 /* Sets the name of the journal file to FILE_NAME. */
 void
 journal_set_file_name (const char *file_name)
 {
-  journal_close ();
-  free (journal.file_name);
-  journal.file_name = xstrdup (file_name);
+  if (!strcmp (file_name, journal_get_file_name ()))
+    return;
+
+  bool enabled = journal_is_enabled ();
+  if (enabled)
+    journal_disable ();
+
+  free (journal_file_name);
+  journal_file_name = xstrdup (file_name);
+
+  if (enabled)
+    journal_enable ();
 }
 
 /* Returns the name of the journal file.  The caller must not modify or free
@@ -198,10 +226,20 @@ journal_set_file_name (const char *file_name)
 const char *
 journal_get_file_name (void)
 {
-  if (journal.file_name == NULL)
-    {
-      const char *output_path = default_output_path ();
-      journal.file_name = xasprintf ("%s%s", output_path, "pspp.jnl");
-    }
-  return journal.file_name;
+  if (!journal_file_name)
+    journal_file_name = xstrdup (journal_get_default_file_name ());
+  return journal_file_name;
+}
+
+/* Returns the name of the default journal file.  The caller must not modify or
+   free the returned string. */
+const char *
+journal_get_default_file_name (void)
+{
+  static char *default_file_name;
+
+  if (!default_file_name)
+    default_file_name = xasprintf ("%s%s", default_log_path (), "pspp.jnl");
+
+  return default_file_name;
 }
index 6571e95272965beaad7263fbd2c3f3f79a3d5d50..583debf6ddc2005f5ab6a072e8514992fe3faf11 100644 (file)
 
 #include <stdbool.h>
 
-void journal_init (void);
 void journal_enable (void);
 void journal_disable (void);
 bool journal_is_enabled (void);
 void journal_set_file_name (const char *);
 const char *journal_get_file_name (void);
+const char *journal_get_default_file_name (void);
 
 #endif /* output/journal.h */
index 07afa09a2de22701ef7c50dfa7069708b6d46d24..2950d38785b0b8c8883501b7178ff7ea65d05da5 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "pre-initialisation.h"
 
+#include "ui/gui/options-dialog.h"
 #include "ui/gui/psppire.h"
 
 #include <gtk/gtk.h>
@@ -195,10 +196,8 @@ static const char *tips[] =
 static void
 user_tip (GApplication *app)
 {
-  PsppireConf *conf = psppire_conf_new ();
-
   gboolean show_tip = TRUE;
-  psppire_conf_get_boolean (conf, "startup", "show-user-tips", &show_tip);
+  psppire_conf_get_boolean ("startup", "show-user-tips", &show_tip);
 
   if (!show_tip)
     return;
@@ -278,11 +277,8 @@ user_tip (GApplication *app)
     }
 
   show_tip = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check));
-  psppire_conf_set_boolean (conf,
-                            "startup", "show-user-tips",
-                            show_tip);
-
-  g_object_unref (conf);
+  psppire_conf_set_boolean ("startup", "show-user-tips", show_tip);
+  psppire_conf_save ();
 
   gtk_widget_destroy (d);
 }
@@ -561,5 +557,6 @@ main (int argc, char *argv[])
   }
 
   g_object_set (G_OBJECT (app), "register-session", TRUE, NULL);
+
   return g_application_run (G_APPLICATION (app), argc, argv);
 }
index 435ff4f7c0e656ecae1aeb2683b3c0d692721c20..5d1a4a0755c0aa9a297abc7f654f2574c751e33b 100644 (file)
 
 #include "options-dialog.h"
 
+#include "output/journal.h"
 #include "ui/gui/helper.h"
 #include "ui/gui/psppire-conf.h"
 #include "ui/gui/builder-wrapper.h"
+#include "ui/gui/psppire-data-store.h"
 #include "ui/gui/psppire-data-window.h"
 #include "ui/gui/psppire-dialog.h"
 
@@ -36,7 +38,6 @@ struct options_dialog
   GtkBuilder *xml;
   GtkWidget *show_labels;
   GtkWidget *show_names;
-  PsppireConf *conf;
 
   GtkWidget *sort_names;
   GtkWidget *sort_labels;
@@ -47,6 +48,11 @@ struct options_dialog
   GtkWidget *raise;
 
   GtkWidget *show_tips;
+
+  GtkWidget *journal_disable;
+  GtkWidget *journal_default;
+  GtkWidget *journal_custom;
+  GtkWidget *journal_custom_location;
 };
 
 GType
@@ -66,6 +72,23 @@ pspp_options_var_order_get_type (void)
   return etype;
 }
 
+GType
+pspp_options_journal_location_get_type (void)
+{
+  static GType etype = 0;
+  if (G_UNLIKELY(etype == 0)) {
+    static const GEnumValue values[] =
+      {
+        { PSPP_OPTIONS_JOURNAL_LOCATION_DISABLED, "PSPP_OPTIONS_JOURNAL_LOCATION_DISABLED", "disabled" },
+        { PSPP_OPTIONS_JOURNAL_LOCATION_DEFAULT, "PSPP_OPTIONS_JOURNAL_LOCATION_DEFAULT", "default" },
+        { PSPP_OPTIONS_JOURNAL_LOCATION_CUSTOM, "PSPP_OPTIONS_JOURNAL_LOCATION_CUSTOM", "custom" },
+        { 0, NULL, NULL }
+      };
+    etype = g_enum_register_static (g_intern_static_string ("PsppOptionsJournalLocation"), values);
+  }
+  return etype;
+}
+
 /*
    Pops up the Options dialog box
  */
@@ -96,12 +119,17 @@ options_dialog (PsppireDataWindow *de)
   fd.alert    = get_widget_assert (fd.xml, "checkbutton-alert");
   fd.raise    = get_widget_assert (fd.xml, "checkbutton-raise");
 
-  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
+  fd.journal_disable = get_widget_assert (fd.xml, "journal-disable");
+  fd.journal_default = get_widget_assert (fd.xml, "journal-default");
+  fd.journal_custom = get_widget_assert (fd.xml, "journal-custom");
+  fd.journal_custom_location = get_widget_assert (fd.xml, "journal-custom-location");
 
-  fd.conf = psppire_conf_new ();
+  GtkLabel *default_journal_location = GTK_LABEL (get_widget_assert (fd.xml, "default_journal_location"));
+  gtk_label_set_text (default_journal_location, journal_get_default_file_name ());
 
-  if (psppire_conf_get_boolean (fd.conf,
-                                "VariableLists", "display-labels", &disp_labels))
+  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
+
+  if (psppire_conf_get_boolean ("VariableLists", "display-labels", &disp_labels))
     {
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.show_labels),
                                     disp_labels);
@@ -110,16 +138,40 @@ options_dialog (PsppireDataWindow *de)
                                     !disp_labels);
     }
 
-  if (psppire_conf_get_boolean (fd.conf,
-                                "startup", "show-user-tips", &show_tips))
+  if (psppire_conf_get_boolean ("startup", "show-user-tips", &show_tips))
     {
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.show_tips),
                                     show_tips);
     }
 
+  int location = -1;
+  psppire_conf_get_enum ("Journal", "location",
+                         PSPP_TYPE_OPTIONS_JOURNAL_LOCATION, &location);
+  switch (location)
+    {
+    case PSPP_OPTIONS_JOURNAL_LOCATION_DISABLED:
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.journal_disable), true);
+      break;
+
+    case PSPP_OPTIONS_JOURNAL_LOCATION_DEFAULT:
+    default:
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.journal_default), true);
+      break;
+
+    case PSPP_OPTIONS_JOURNAL_LOCATION_CUSTOM:
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.journal_custom), true);
+      break;
+    }
+
+  char *custom_location;
+  if (psppire_conf_get_string ("Journal", "custom-location", &custom_location))
+    {
+      gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (fd.journal_custom_location), custom_location);
+      g_free (custom_location);
+    }
 
   int what = -1;
-  psppire_conf_get_enum (fd.conf, "VariableLists", "sort-order",
+  psppire_conf_get_enum ("VariableLists", "sort-order",
                          PSPP_TYPE_OPTIONS_VAR_ORDER, &what);
 
   switch (what)
@@ -137,21 +189,19 @@ options_dialog (PsppireDataWindow *de)
 
   {
     gboolean status;
-    if (psppire_conf_get_boolean (fd.conf, "OutputWindowAction", "maximize",
-                                  &status))
+    if (psppire_conf_get_boolean ("OutputWindowAction", "maximize", &status))
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.maximize), status);
   }
 
   {
     gboolean status = true;
-    psppire_conf_get_boolean (fd.conf, "OutputWindowAction", "alert", &status);
+    psppire_conf_get_boolean ("OutputWindowAction", "alert", &status);
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.alert), status);
   }
 
   {
     gboolean status;
-    if (psppire_conf_get_boolean (fd.conf, "OutputWindowAction", "raise",
-                                  &status))
+    if (psppire_conf_get_boolean ("OutputWindowAction", "raise", &status))
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd.raise), status);
   }
 
@@ -162,8 +212,7 @@ options_dialog (PsppireDataWindow *de)
       PsppOptionsVarOrder sort_order = -1;
       gboolean sl = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd.show_labels));
 
-      psppire_conf_set_boolean (fd.conf,
-                                "VariableLists", "display-labels", sl);
+      psppire_conf_set_boolean ("VariableLists", "display-labels", sl);
 
       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd.sort_labels)))
         {
@@ -178,27 +227,76 @@ options_dialog (PsppireDataWindow *de)
           sort_order = PSPP_OPTIONS_VAR_ORDER_UNSORTED;
         }
 
-      psppire_conf_set_enum (fd.conf,
-                             "VariableLists", "sort-order",
+      psppire_conf_set_enum ("VariableLists", "sort-order",
                              PSPP_TYPE_OPTIONS_VAR_ORDER,
                              sort_order);
 
-      psppire_conf_set_boolean (fd.conf, "OutputWindowAction", "maximize",
+      psppire_conf_set_boolean ("OutputWindowAction", "maximize",
                                 gtk_toggle_button_get_active
                                 (GTK_TOGGLE_BUTTON (fd.maximize)));
 
-      psppire_conf_set_boolean (fd.conf, "OutputWindowAction", "raise",
+      psppire_conf_set_boolean ("OutputWindowAction", "raise",
                                 gtk_toggle_button_get_active
                                 (GTK_TOGGLE_BUTTON (fd.raise)));
 
-      psppire_conf_set_boolean (fd.conf, "OutputWindowAction", "alert",
+      psppire_conf_set_boolean ("OutputWindowAction", "alert",
                                 gtk_toggle_button_get_active
                                 (GTK_TOGGLE_BUTTON (fd.alert)));
 
-      psppire_conf_set_boolean (fd.conf, "startup", "show-user-tips",
+      psppire_conf_set_boolean ("startup", "show-user-tips",
                                 gtk_toggle_button_get_active
                                 (GTK_TOGGLE_BUTTON (fd.show_tips)));
+
+      PsppOptionsJournalLocation journal_location;
+      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd.journal_disable)))
+        journal_location = PSPP_OPTIONS_JOURNAL_LOCATION_DISABLED;
+      else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd.journal_custom)))
+        journal_location = PSPP_OPTIONS_JOURNAL_LOCATION_CUSTOM;
+      else
+        journal_location = PSPP_OPTIONS_JOURNAL_LOCATION_DEFAULT;
+      psppire_conf_set_enum ("Journal", "location",
+                             PSPP_TYPE_OPTIONS_JOURNAL_LOCATION,
+                             journal_location);
+      gchar *custom_location = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (fd.journal_custom_location));
+      if (custom_location)
+        {
+          psppire_conf_set_string ("Journal", "custom-location", custom_location);
+          g_free (custom_location);
+        }
+      psppire_conf_save ();
+
+      options_init ();
     }
 
   g_object_unref (fd.xml);
 }
+
+void
+options_init (void)
+{
+  char *custom_location;
+  if (!psppire_conf_get_string ("Journal", "custom-location",
+                                &custom_location))
+    custom_location = g_strdup (journal_get_default_file_name ());
+
+  int location = -1;
+  psppire_conf_get_enum ("Journal", "location",
+                         PSPP_TYPE_OPTIONS_JOURNAL_LOCATION, &location);
+  switch (location) {
+  case PSPP_OPTIONS_JOURNAL_LOCATION_DISABLED:
+    journal_disable ();
+    break;
+
+  case PSPP_OPTIONS_JOURNAL_LOCATION_DEFAULT:
+  default:
+    journal_set_file_name (journal_get_default_file_name ());
+    journal_enable ();
+    break;
+
+  case PSPP_OPTIONS_JOURNAL_LOCATION_CUSTOM:
+    journal_set_file_name (custom_location);
+    journal_enable ();
+    break;
+  }
+  g_free (custom_location);
+}
index 0307b1559ab9431c0d5f9918e3141a2296b20f38..c59173261b904d2f722eaf09a0c65b6efd201f37 100644 (file)
@@ -30,8 +30,19 @@ typedef enum
 GType pspp_options_var_order_get_type (void) G_GNUC_CONST;
 #define PSPP_TYPE_OPTIONS_VAR_ORDER (pspp_options_var_order_get_type ())
 
+typedef enum
+  {
+    PSPP_OPTIONS_JOURNAL_LOCATION_DISABLED,
+    PSPP_OPTIONS_JOURNAL_LOCATION_DEFAULT,
+    PSPP_OPTIONS_JOURNAL_LOCATION_CUSTOM,
+  } PsppOptionsJournalLocation;
+
+GType pspp_options_journal_location_get_type (void) G_GNUC_CONST;
+#define PSPP_TYPE_OPTIONS_JOURNAL_LOCATION (pspp_options_journal_location_get_type ())
 
 /* Pops up the Options dialog box */
 void options_dialog (PsppireDataWindow *);
 
+void options_init (void);
+
 #endif
index 2d087c79fb3644c24ce520d3932384d62e989fbd..9ed34dc530f96468973742611e071107d6d628aa 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.38.2 -->
+<!-- Generated with glade 3.40.0 -->
 <!-- PSPP - a program for statistical analysis. -->
 <!-- Copyright (C) 2017, 2021 Free Software Foundation, Inc. -->
 <!-- This program is free software: you can redistribute it and/or modify -->
 <interface>
   <requires lib="gtk+" version="3.22"/>
   <requires lib="psppire" version="2053.63976"/>
+  <object class="GtkFileFilter" id="journal-file-filter">
+    <patterns>
+      <pattern>*.jnl</pattern>
+    </patterns>
+  </object>
   <object class="PsppireDialog" id="options-dialog">
     <property name="can-focus">False</property>
     <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
         <property name="visible">True</property>
         <property name="can-focus">False</property>
         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-        <property name="margin-left">5</property>
         <property name="margin-bottom">5</property>
         <property name="spacing">5</property>
         <child>
-          <object class="GtkFrame" id="variable-lists-frame">
+          <object class="GtkBox">
             <property name="visible">True</property>
             <property name="can-focus">False</property>
-            <property name="label-xalign">0</property>
-            <property name="shadow-type">in</property>
+            <property name="orientation">vertical</property>
             <child>
               <object class="GtkBox">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
-                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkButtonBox">
+                  <object class="GtkFrame" id="variable-lists-frame">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">5</property>
-                    <property name="layout-style">start</property>
+                    <property name="label-xalign">0</property>
+                    <property name="shadow-type">in</property>
                     <child>
-                      <object class="GtkRadioButton" id="radiobutton-labels">
-                        <property name="label" translatable="yes">Display _Labels</property>
+                      <object class="GtkBox">
                         <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
-                        <property name="draw-indicator">True</property>
-                        <property name="group">radiobutton-names</property>
+                        <property name="can-focus">False</property>
+                        <property name="orientation">vertical</property>
+                        <child>
+                          <!-- n-columns=1 n-rows=6 -->
+                          <object class="GtkGrid">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <child>
+                              <object class="GtkRadioButton" id="radiobutton-labels">
+                                <property name="label" translatable="yes">Display _Labels</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="draw-indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkRadioButton" id="radiobutton-names">
+                                <property name="label" translatable="yes">Display _Names</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="active">True</property>
+                                <property name="draw-indicator">True</property>
+                                <property name="group">radiobutton-labels</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkSeparator">
+                                <property name="visible">True</property>
+                                <property name="can-focus">False</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">2</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkRadioButton" id="radiobutton-sort-by-label">
+                                <property name="label" translatable="yes">Sort by L_abel</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="active">True</property>
+                                <property name="draw-indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">3</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkRadioButton" id="radiobutton-sort-by-name">
+                                <property name="label" translatable="yes">Sort by Na_me</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="active">True</property>
+                                <property name="draw-indicator">True</property>
+                                <property name="group">radiobutton-sort-by-label</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">4</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkRadioButton" id="radiobutton-unsorted">
+                                <property name="label" translatable="yes">Do not S_ort</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="active">True</property>
+                                <property name="draw-indicator">True</property>
+                                <property name="group">radiobutton-sort-by-label</property>
+                              </object>
+                              <packing>
+                                <property name="left-attach">0</property>
+                                <property name="top-attach">5</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
                     </child>
-                    <child>
-                      <object class="GtkRadioButton" id="radiobutton-names">
-                        <property name="label" translatable="yes">Display _Names</property>
+                    <child type="label">
+                      <object class="GtkLabel">
                         <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
-                        <property name="active">True</property>
-                        <property name="draw-indicator">True</property>
+                        <property name="can-focus">False</property>
+                        <property name="label" translatable="yes">Variable Lists</property>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can-focus">False</property>
+                    <property name="orientation">vertical</property>
                     <child>
-                      <object class="GtkSeparator">
+                      <object class="GtkFrame">
                         <property name="visible">True</property>
                         <property name="can-focus">False</property>
+                        <property name="label-xalign">0</property>
+                        <property name="shadow-type">in</property>
+                        <child>
+                          <object class="GtkButtonBox">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="orientation">vertical</property>
+                            <property name="layout-style">start</property>
+                            <child>
+                              <object class="GtkCheckButton" id="checkbutton-maximize">
+                                <property name="label" translatable="yes">Ma_ximize</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="draw-indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkCheckButton" id="checkbutton-raise">
+                                <property name="label" translatable="yes">_Raise</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="draw-indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkCheckButton" id="checkbutton-alert">
+                                <property name="label" translatable="yes">Aler_t</property>
+                                <property name="visible">True</property>
+                                <property name="can-focus">True</property>
+                                <property name="receives-default">False</property>
+                                <property name="use-underline">True</property>
+                                <property name="draw-indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">2</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                        <child type="label">
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="label" translatable="yes">Output Window Action</property>
+                          </object>
+                        </child>
                       </object>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkRadioButton" id="radiobutton-sort-by-label">
-                        <property name="label" translatable="yes">Sort by L_abel</property>
-                        <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
-                        <property name="draw-indicator">True</property>
-                        <property name="group">radiobutton-sort-by-name</property>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkRadioButton" id="radiobutton-sort-by-name">
-                        <property name="label" translatable="yes">Sort by Na_me</property>
-                        <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
-                        <property name="active">True</property>
-                        <property name="draw-indicator">True</property>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
                         <property name="fill">True</property>
-                        <property name="position">4</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkRadioButton" id="radiobutton-unsorted">
-                        <property name="label" translatable="yes">Do not S_ort</property>
+                      <object class="GtkFrame">
                         <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
-                        <property name="active">True</property>
-                        <property name="draw-indicator">True</property>
-                        <property name="group">radiobutton-sort-by-name</property>
+                        <property name="can-focus">False</property>
+                        <property name="label-xalign">0</property>
+                        <child>
+                          <object class="GtkCheckButton" id="checkbutton-show-tips">
+                            <property name="label" translatable="yes">Show Tips</property>
+                            <property name="visible">True</property>
+                            <property name="can-focus">True</property>
+                            <property name="receives-default">False</property>
+                            <property name="draw-indicator">True</property>
+                          </object>
+                        </child>
+                        <child type="label">
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="label" translatable="yes">Startup Options</property>
+                          </object>
+                        </child>
                       </object>
                       <packing>
-                        <property name="expand">True</property>
+                        <property name="expand">False</property>
                         <property name="fill">True</property>
-                        <property name="position">5</property>
+                        <property name="position">1</property>
                       </packing>
                     </child>
                   </object>
                   <packing>
-                    <property name="expand">False</property>
+                    <property name="expand">True</property>
                     <property name="fill">True</property>
-                    <property name="position">2</property>
+                    <property name="position">1</property>
                   </packing>
                 </child>
               </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
             </child>
-            <child type="label">
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="label" translatable="yes">Variable Lists</property>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="PsppireButtonBox" id="options-buttonbox">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-            <property name="border-width">5</property>
-            <property name="orientation">vertical</property>
-            <property name="buttons">PSPPIRE_BUTTON_OK_MASK | PSPPIRE_BUTTON_CANCEL_MASK | PSPPIRE_BUTTON_HELP_MASK</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="pack-type">end</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="orientation">vertical</property>
             <child>
               <object class="GtkFrame">
                 <property name="visible">True</property>
                 <property name="label-xalign">0</property>
                 <property name="shadow-type">in</property>
                 <child>
-                  <object class="GtkButtonBox">
+                  <!-- n-columns=1 n-rows=3 -->
+                  <object class="GtkGrid">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <property name="layout-style">spread</property>
                     <child>
-                      <object class="GtkCheckButton" id="checkbutton-maximize">
-                        <property name="label" translatable="yes">Ma_ximize</property>
+                      <object class="GtkRadioButton" id="journal-disable">
+                        <property name="label" translatable="yes">None</property>
                         <property name="visible">True</property>
                         <property name="can-focus">True</property>
                         <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
+                        <property name="hexpand">True</property>
+                        <property name="active">True</property>
                         <property name="draw-indicator">True</property>
                       </object>
                       <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">0</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkCheckButton" id="checkbutton-raise">
-                        <property name="label" translatable="yes">_Raise</property>
+                      <object class="GtkBox">
                         <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
-                        <property name="draw-indicator">True</property>
+                        <property name="can-focus">False</property>
+                        <child>
+                          <object class="GtkRadioButton" id="journal-default">
+                            <property name="label" translatable="yes">Default</property>
+                            <property name="visible">True</property>
+                            <property name="can-focus">True</property>
+                            <property name="receives-default">False</property>
+                            <property name="active">True</property>
+                            <property name="draw-indicator">True</property>
+                            <property name="group">journal-disable</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="default_journal_location">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="ellipsize">end</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
                       </object>
                       <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">1</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkCheckButton" id="checkbutton-alert">
-                        <property name="label" translatable="yes">Aler_t</property>
+                      <object class="GtkBox">
                         <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="use-underline">True</property>
-                        <property name="draw-indicator">True</property>
+                        <property name="can-focus">False</property>
+                        <child>
+                          <object class="GtkRadioButton" id="journal-custom">
+                            <property name="label" translatable="yes">Custom</property>
+                            <property name="visible">True</property>
+                            <property name="can-focus">True</property>
+                            <property name="receives-default">False</property>
+                            <property name="active">True</property>
+                            <property name="draw-indicator">True</property>
+                            <property name="group">journal-disable</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkFileChooserButton" id="journal-custom-location">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="do-overwrite-confirmation">True</property>
+                            <property name="filter">journal-file-filter</property>
+                            <property name="title" translatable="yes"/>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
                       </object>
                       <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">2</property>
                       </packing>
                     </child>
                   </object>
                   <object class="GtkLabel">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
-                    <property name="label" translatable="yes">Output Window Action</property>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkFrame">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="label-xalign">0</property>
-                <child>
-                  <object class="GtkAlignment">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="left-padding">12</property>
-                    <child>
-                      <object class="GtkCheckButton" id="checkbutton-show-tips">
-                        <property name="label" translatable="yes">Show Tips</property>
-                        <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
-                        <property name="draw-indicator">True</property>
-                      </object>
-                    </child>
-                  </object>
-                </child>
-                <child type="label">
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="label" translatable="yes">Startup Options</property>
+                    <property name="label" translatable="yes">Journal File</property>
                   </object>
                 </child>
               </object>
           <packing>
             <property name="expand">True</property>
             <property name="fill">True</property>
-            <property name="position">2</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="PsppireButtonBox" id="options-buttonbox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="border-width">5</property>
+            <property name="orientation">vertical</property>
+            <property name="buttons">PSPPIRE_BUTTON_OK_MASK | PSPPIRE_BUTTON_CANCEL_MASK | PSPPIRE_BUTTON_HELP_MASK</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="pack-type">end</property>
+            <property name="position">1</property>
           </packing>
         </child>
       </object>
index 4540e32796a5c5d1f0a741354d24f7215bf11d3d..dee6ff2f62590a1bd9841e8fc59126c8de853ab5 100644 (file)
@@ -35,46 +35,42 @@ static void psppire_conf_dispose        (GObject   *object);
 
 static GObjectClass *parent_class = NULL;
 
-static void
-conf_read (PsppireConf *conf)
-{
-  g_key_file_load_from_file (conf->keyfile,
-                             conf->filename,
-                             G_KEY_FILE_KEEP_COMMENTS,
-                             NULL);
-}
+static PsppireConf *psppire_conf_get (void);
 
-static gboolean
-flush_conf (PsppireConf *conf)
+void
+psppire_conf_save (void)
 {
+  PsppireConf *conf = psppire_conf_get ();
+  if (!conf->dirty)
+    return;
+  conf->dirty = FALSE;
+
   gsize length = 0;
 
-  gchar *kf = g_key_file_to_data  (conf->keyfile, &length, NULL);
-  GError *err = NULL;
+  gchar *new_contents = g_key_file_to_data  (conf->keyfile, &length, NULL);
 
-  if (! g_file_set_contents (conf->filename, kf, length, &err))
+  GError *err = NULL;
+  if (g_strcmp0 (new_contents, conf->contents)
+      && ! g_file_set_contents (conf->filename, new_contents, length, &err))
     {
       g_warning ("Cannot open %s for writing: %s", conf->filename, err->message);
       g_error_free (err);
     }
 
-  g_free (kf);
-  conf->idle = 0;
-  return FALSE;
+  g_free (conf->contents);
+  conf->contents = new_contents;
 }
 
 static void
-conf_write (PsppireConf *conf)
+conf_dirty (PsppireConf *conf)
 {
-  if (conf->idle == 0)
-    conf->idle = g_idle_add_full (G_PRIORITY_LOW,
-                                  (GSourceFunc) flush_conf, conf, NULL);
+  conf->dirty = TRUE;
 }
 
-
 static void
 psppire_conf_dispose  (GObject *object)
 {
+  G_OBJECT_CLASS (parent_class)->dispose (object);
 }
 
 static void
@@ -138,26 +134,31 @@ psppire_conf_init (PsppireConf *conf)
   conf->filename = g_strdup_printf ("%s/%s", dirname, "psppirerc");
 
   conf->keyfile = g_key_file_new ();
+  g_key_file_load_from_file (conf->keyfile,
+                             conf->filename,
+                             G_KEY_FILE_KEEP_COMMENTS,
+                             NULL);
 
-  conf->idle = 0;
+  conf->dirty = FALSE;
 }
 
-
-PsppireConf *
-psppire_conf_new (void)
+/* Gets the singleton PsppireConf object.  The caller should not unref the
+   object.  The caller should call psppire_conf_save() if it makes changes. */
+static PsppireConf *
+psppire_conf_get (void)
 {
-  return g_object_new (psppire_conf_get_type (), NULL);
+  return (the_instance
+          ? the_instance
+          : g_object_new (psppire_conf_get_type (), NULL));
 }
 
-
-
 gboolean
-psppire_conf_get_int (PsppireConf *conf, const gchar *base,
-                      const gchar *name, gint *value)
+psppire_conf_get_int (const gchar *base, const gchar *name, gint *value)
 {
+  PsppireConf *conf = psppire_conf_get ();
+
   gboolean ok;
   GError *err = NULL;
-  conf_read (conf);
   *value = g_key_file_get_integer (conf->keyfile,
                                    base,
                                    name, &err);
@@ -170,13 +171,13 @@ psppire_conf_get_int (PsppireConf *conf, const gchar *base,
 }
 
 gboolean
-psppire_conf_get_boolean (PsppireConf *conf, const gchar *base,
-                          const gchar *name, gboolean *value)
+psppire_conf_get_boolean (const gchar *base, const gchar *name, gboolean *value)
 {
+  PsppireConf *conf = psppire_conf_get ();
+
   gboolean ok;
   gboolean b;
   GError *err = NULL;
-  conf_read (conf);
   b = g_key_file_get_boolean (conf->keyfile,
                               base,
                               name, &err);
@@ -194,13 +195,12 @@ psppire_conf_get_boolean (PsppireConf *conf, const gchar *base,
 
 
 gboolean
-psppire_conf_get_string (PsppireConf *conf, const gchar *base,
-                         const gchar *name, gchar **value)
+psppire_conf_get_string (const gchar *base, const gchar *name, gchar **value)
 {
+  PsppireConf *conf = psppire_conf_get ();
   gboolean ok;
   gchar *b;
   GError *err = NULL;
-  conf_read (conf);
   b = g_key_file_get_string (conf->keyfile,
                              base,
                              name, &err);
@@ -219,13 +219,12 @@ psppire_conf_get_string (PsppireConf *conf, const gchar *base,
 
 
 gboolean
-psppire_conf_get_variant (PsppireConf *conf, const gchar *base,
-                          const gchar *name, GVariant **v)
+psppire_conf_get_variant (const gchar *base, const gchar *name, GVariant **v)
 {
+  PsppireConf *conf = psppire_conf_get ();
   gboolean ok;
   gchar *b;
   GError *err = NULL;
-  conf_read (conf);
   b = g_key_file_get_string (conf->keyfile,
                              base,
                              name, &err);
@@ -244,15 +243,12 @@ psppire_conf_get_variant (PsppireConf *conf, const gchar *base,
 }
 
 gboolean
-psppire_conf_get_enum (PsppireConf *conf, const gchar *base,
-                       const gchar *name,
-                       GType t,
-                       int *v)
+psppire_conf_get_enum (const gchar *base, const gchar *name, GType t, int *v)
 {
+  PsppireConf *conf = psppire_conf_get ();
   gboolean ok;
   gchar *b;
   GError *err = NULL;
-  conf_read (conf);
   b = g_key_file_get_string (conf->keyfile,
                              base,
                              name, &err);
@@ -274,50 +270,49 @@ psppire_conf_get_enum (PsppireConf *conf, const gchar *base,
 }
 
 void
-psppire_conf_set_int (PsppireConf *conf,
-                      const gchar *base, const gchar *name,
+psppire_conf_set_int (const gchar *base, const gchar *name,
                       gint value)
 {
+  PsppireConf *conf = psppire_conf_get ();
   g_key_file_set_integer (conf->keyfile, base, name, value);
-  conf_write (conf);
+  conf_dirty (conf);
 }
 
 void
-psppire_conf_set_boolean (PsppireConf *conf,
-                          const gchar *base, const gchar *name,
+psppire_conf_set_boolean (const gchar *base, const gchar *name,
                           gboolean value)
 {
+  PsppireConf *conf = psppire_conf_get ();
   g_key_file_set_boolean (conf->keyfile, base, name, value);
-  conf_write (conf);
+  conf_dirty (conf);
 }
 
 
 void
-psppire_conf_set_string (PsppireConf *conf,
-                         const gchar *base, const gchar *name,
+psppire_conf_set_string (const gchar *base, const gchar *name,
                          const gchar *value)
 {
+  PsppireConf *conf = psppire_conf_get ();
   g_key_file_set_string (conf->keyfile, base, name, value);
-  conf_write (conf);
+  conf_dirty (conf);
 }
 
 void
-psppire_conf_set_variant (PsppireConf *conf,
-                               const gchar *base, const gchar *name,
-                               GVariant *value)
+psppire_conf_set_variant (const gchar *base, const gchar *name, GVariant *value)
 {
+  PsppireConf *conf = psppire_conf_get ();
   gchar *v = g_variant_print (value, FALSE);
   g_key_file_set_string (conf->keyfile, base, name, v);
-  conf_write (conf);
+  conf_dirty (conf);
   g_free (v);
 }
 
 void
-psppire_conf_set_enum (PsppireConf *conf,
-                       const gchar *base, const gchar *name,
+psppire_conf_set_enum (const gchar *base, const gchar *name,
                        GType enum_type,
                        int value)
 {
+  PsppireConf *conf = psppire_conf_get ();
   GEnumClass *ec = g_type_class_ref (enum_type);
   GEnumValue *ev = g_enum_get_value (ec, value);
 
@@ -326,39 +321,37 @@ psppire_conf_set_enum (PsppireConf *conf,
 
   g_type_class_unref (ec);
 
-  conf_write (conf);
+  conf_dirty (conf);
 }
 
 
 
 /*
-  A convenience function to set the geometry of a
+  A convenience function to get the geometry of a
   window from from a saved config
 */
 void
-psppire_conf_set_window_geometry (PsppireConf *conf,
-                                  const gchar *base,
-                                  GtkWindow *window)
+psppire_conf_get_window_geometry (const gchar *base, GtkWindow *window)
 {
   gint height, width;
   gint x, y;
   gboolean maximize;
 
-  if (psppire_conf_get_int (conf, base, "height", &height)
+  if (psppire_conf_get_int (base, "height", &height)
       &&
-      psppire_conf_get_int (conf, base, "width", &width))
+      psppire_conf_get_int (base, "width", &width))
     {
       gtk_window_set_default_size (window, width, height);
     }
 
-  if (psppire_conf_get_int (conf, base, "x", &x)
+  if (psppire_conf_get_int (base, "x", &x)
        &&
-       psppire_conf_get_int (conf, base, "y", &y))
+       psppire_conf_get_int (base, "y", &y))
     {
       gtk_window_move (window, x, y);
     }
 
-  if (psppire_conf_get_boolean (conf, base, "maximize", &maximize))
+  if (psppire_conf_get_boolean (base, "maximize", &maximize))
     {
       if (maximize)
         gtk_window_maximize (window);
@@ -374,9 +367,7 @@ psppire_conf_set_window_geometry (PsppireConf *conf,
    "configure-event" and "window-state-event" signal handlers
  */
 void
-psppire_conf_save_window_geometry (PsppireConf *conf,
-                                   const gchar *base,
-                                   GtkWindow *gtk_window)
+psppire_conf_set_window_geometry (const gchar *base, GtkWindow *gtk_window)
 {
   gboolean maximized;
   GdkWindow *w;
@@ -386,7 +377,7 @@ psppire_conf_save_window_geometry (PsppireConf *conf,
     return;
 
   maximized = (gdk_window_get_state (w) & GDK_WINDOW_STATE_MAXIMIZED) != 0;
-  psppire_conf_set_boolean (conf, base, "maximize", maximized);
+  psppire_conf_set_boolean (base, "maximize", maximized);
 
   if (!maximized)
     {
@@ -397,9 +388,9 @@ psppire_conf_save_window_geometry (PsppireConf *conf,
 
       gdk_window_get_position (w, &x, &y);
 
-      psppire_conf_set_int (conf, base, "height", height);
-      psppire_conf_set_int (conf, base, "width", width);
-      psppire_conf_set_int (conf, base, "x", x);
-      psppire_conf_set_int (conf, base, "y", y);
+      psppire_conf_set_int (base, "height", height);
+      psppire_conf_set_int (base, "width", width);
+      psppire_conf_set_int (base, "x", x);
+      psppire_conf_set_int (base, "y", y);
     }
 }
index 31f8d3d0c91f6d0fb77f45bb47f32c9e1849f125..d998adbe889f9fd422383fafb0197aa8024e2ba0 100644 (file)
@@ -61,7 +61,8 @@ struct _PsppireConf
 
   GKeyFile *keyfile;
   gchar *filename;
-  guint idle;
+  gchar *contents;
+  gboolean dirty;
 };
 
 
@@ -73,58 +74,42 @@ struct _PsppireConfClass
 
 GType psppire_conf_get_type (void) G_GNUC_CONST;
 
-PsppireConf * psppire_conf_new (void);
+void psppire_conf_save (void);
 
-gboolean psppire_conf_get_int (PsppireConf *,
-                               const gchar *, const gchar *, int *);
+gboolean psppire_conf_get_int (const gchar *, const gchar *, int *);
 
-gboolean psppire_conf_get_string (PsppireConf *,
-                               const gchar *, const gchar *, gchar **);
+gboolean psppire_conf_get_string (const gchar *, const gchar *, gchar **);
 
-gboolean psppire_conf_get_boolean (PsppireConf *,
-                                   const gchar *, const gchar *, gboolean *);
+gboolean psppire_conf_get_boolean (const gchar *, const gchar *, gboolean *);
 
 
-gboolean psppire_conf_get_variant (PsppireConf *,
-                                   const gchar *, const gchar *, GVariant **);
+gboolean psppire_conf_get_variant (const gchar *, const gchar *, GVariant **);
 
 
-gboolean psppire_conf_get_enum (PsppireConf *conf, const gchar *base,
-                                const gchar *name,
-                                GType t,
-                                int *v);
+gboolean psppire_conf_get_enum (const gchar *base, const gchar *name,
+                                GType t, int *v);
 
-void psppire_conf_set_int (PsppireConf *conf,
-                           const gchar *base, const gchar *name,
+void psppire_conf_set_int (const gchar *base, const gchar *name,
                            gint value);
 
-void psppire_conf_set_boolean (PsppireConf *conf,
-                               const gchar *base, const gchar *name,
+void psppire_conf_set_boolean (const gchar *base, const gchar *name,
                                gboolean value);
 
-void psppire_conf_set_string (PsppireConf *conf,
-                               const gchar *base, const gchar *name,
+void psppire_conf_set_string (const gchar *base, const gchar *name,
                               const gchar *value);
 
 
-void psppire_conf_set_variant (PsppireConf *conf,
-                               const gchar *base, const gchar *name,
+void psppire_conf_set_variant (const gchar *base, const gchar *name,
                                GVariant *value);
 
 
-void psppire_conf_set_enum (PsppireConf *conf,
-                            const gchar *base, const gchar *name,
+void psppire_conf_set_enum (const gchar *base, const gchar *name,
                             GType enum_type,
                             int value);
 
-void psppire_conf_set_window_geometry (PsppireConf *conf,
-                                       const gchar *base,
-                                       GtkWindow *window);
-
-void psppire_conf_save_window_geometry (PsppireConf *,
-                                        const gchar *,
-                                        GtkWindow *);
+void psppire_conf_get_window_geometry (const gchar *base, GtkWindow *window);
 
+void psppire_conf_set_window_geometry (const gchar *, GtkWindow *);
 
 G_END_DECLS
 
index 6d9f0694672c8355da835d31ecb9a3ee9216dfe7..3747fd6e5ae58fd41832535cbf0c5b5f09503327 100644 (file)
@@ -514,9 +514,7 @@ psppire_data_editor_init (PsppireDataEditor *de)
 
   g_object_set (de, "can-focus", FALSE, NULL);
 
-  if (psppire_conf_get_string (psppire_conf_new (),
-                           "Data Editor", "font",
-                                &fontname))
+  if (psppire_conf_get_string ("Data Editor", "font", &fontname))
     {
       de->font = pango_font_description_from_string (fontname);
       g_free (fontname);
@@ -595,9 +593,9 @@ psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_
   de->font = pango_font_description_copy (font_desc);
   font_name = pango_font_description_to_string (de->font);
 
-  psppire_conf_set_string (psppire_conf_new (),
-                           "Data Editor", "font",
-                           font_name);
+  psppire_conf_set_string ("Data Editor", "font", font_name);
+  psppire_conf_save ();
+
   g_free (font_name);
 }
 
index ff8af1b1462cac4d36b862641c15dffb8ee0c8dc..e5d12693c9964d90cee8a51eeeaac2e129da7ee1 100644 (file)
@@ -152,7 +152,7 @@ default_sort (GtkTreeModel *model,
      gpointer user_data)
 {
   int what = -1;
-  psppire_conf_get_enum (psppire_conf_new (), "VariableLists", "sort-order",
+  psppire_conf_get_enum ("VariableLists", "sort-order",
                          PSPP_TYPE_OPTIONS_VAR_ORDER, &what);
 
   switch (what)
@@ -320,8 +320,7 @@ use_labels (PsppireDictView *dv)
   if (gtk_check_menu_item_get_inconsistent (GTK_CHECK_MENU_ITEM
                                             (dv->override_button)))
     {
-      psppire_conf_get_boolean (psppire_conf_new (),
-                                "VariableLists", "display-labels", &disp_labels);
+      psppire_conf_get_boolean ("VariableLists", "display-labels", &disp_labels);
     }
   else
     {
@@ -551,8 +550,7 @@ toggle_label_preference (GtkCheckMenuItem *checkbox, gpointer data)
   PsppireDictView *dv = PSPPIRE_DICT_VIEW (data);
 
   gboolean global_setting = TRUE;
-  psppire_conf_get_boolean (psppire_conf_new (),
-                            "VariableLists", "display-labels", &global_setting);
+  psppire_conf_get_boolean ("VariableLists", "display-labels", &global_setting);
 
   if (gtk_check_menu_item_get_inconsistent (checkbox))
     gtk_check_menu_item_set_active (checkbox, !global_setting);
index a485c6d173245bbaf55dcbab58073526c38a1a54..6b3adb3d298c0e153f0c4ab64100360d6837a340 100644 (file)
@@ -132,25 +132,23 @@ psppire_output_submit (struct output_driver *this,
       gtk_widget_show_all (GTK_WIDGET (pod->window));
     }
 
-  PsppireConf *conf = psppire_conf_new ();
   {
     gboolean status = true;
-    psppire_conf_get_boolean (conf, "OutputWindowAction", "alert",
-                              &status);
+    psppire_conf_get_boolean ("OutputWindowAction", "alert", &status);
     gtk_window_set_urgency_hint (GTK_WINDOW (pod->window), status);
   }
 
   {
     gboolean status ;
-    if (psppire_conf_get_boolean (conf, "OutputWindowAction", "maximize",
-                                  &status) && status)
+    if (psppire_conf_get_boolean ("OutputWindowAction", "maximize", &status)
+        && status)
       gtk_window_maximize (GTK_WINDOW (pod->window));
   }
 
   {
     gboolean status ;
-    if (psppire_conf_get_boolean (conf, "OutputWindowAction", "raise",
-                                  &status) && status)
+    if (psppire_conf_get_boolean ("OutputWindowAction", "raise", &status)
+        && status)
       gtk_window_present (GTK_WINDOW (pod->window));
   }
 }
index 1db54c7d124e800c46b558f6d35e158eb8adaa69..de987568f94b199495227d1e2ccf4fa7b17c04fd 100644 (file)
@@ -59,9 +59,7 @@ get_window_id (GtkWidget *wb)
 static void
 realize (GtkWidget *wb)
 {
-  PsppireConf *conf = psppire_conf_new ();
-
-  psppire_conf_set_window_geometry (conf, get_window_id (wb), GTK_WINDOW (wb));
+  psppire_conf_get_window_geometry (get_window_id (wb), GTK_WINDOW (wb));
 
   if (GTK_WIDGET_CLASS (psppire_window_base_parent_class)->realize)
     GTK_WIDGET_CLASS (psppire_window_base_parent_class)->realize (wb) ;
@@ -76,9 +74,8 @@ configure_event (GtkWidget *wb, GdkEventConfigure *event)
 {
   if (gtk_widget_get_mapped (wb))
     {
-      PsppireConf *conf = psppire_conf_new ();
-
-      psppire_conf_save_window_geometry (conf, get_window_id (wb), GTK_WINDOW (wb));
+      psppire_conf_set_window_geometry (get_window_id (wb), GTK_WINDOW (wb));
+      psppire_conf_save ();
     }
 
   if (GTK_WIDGET_CLASS (psppire_window_base_parent_class)->configure_event)
index e77c449315a5c9e6d59ea33371f3b7f95ed30c52..cecb60bb57ed3cb9be4e67fe80d831b9b3ba6ce9 100644 (file)
@@ -43,6 +43,7 @@
 
 #include "ui/gui/dict-display.h"
 #include "ui/gui/executor.h"
+#include "ui/gui/options-dialog.h"
 #include "ui/gui/psppire-data-store.h"
 #include "ui/gui/psppire-data-window.h"
 #include "ui/gui/psppire-dict.h"
@@ -113,7 +114,7 @@ initialize (const struct init_source *is)
         }
       break;
     case 9:
-      journal_init ();
+      options_init ();
       break;
     case 10:
       textdomain (PACKAGE);
index f028def094937f0345d12faafb378652a2ee3e5e..8b55720741c02e592088a15514409fe57b633557 100644 (file)
@@ -91,7 +91,7 @@ welcome (void)
          "conditions.\nThere is ABSOLUTELY NO WARRANTY for PSPP; type \"show "
          "warranty.\" for details.\n", stdout);
   puts (announced_version);
-  journal_init ();
+  journal_enable ();
 }
 
 static struct terminal_reader *
index 43c2f890786968aec4887e0cadd73f4f03342596..31634ff9336337dbc8774d5e232d45cab48955d5 100644 (file)
@@ -466,6 +466,7 @@ TESTSUITE_AT = \
        tests/output/ascii.at \
        tests/output/charts.at \
        tests/output/html.at \
+       tests/output/journal.at \
        tests/output/output.at \
        tests/output/paper-size.at \
        tests/output/pivot-table.at \
diff --git a/tests/output/journal.at b/tests/output/journal.at
new file mode 100644 (file)
index 0000000..07247bd
--- /dev/null
@@ -0,0 +1,49 @@
+AT_BANNER([journal])
+
+AT_SETUP([enable and disable journal])
+AT_DATA([journal.sps], [dnl
+set journal='pspp.jnl' on.
+data list notable /x y 1-2.
+begin data.
+12
+end data.
+set journal=off.
+
+print.
+
+execute.
+set journal=on.
+])
+
+AT_CHECK([pspp journal.sps])
+AT_CHECK([sed 's/ at.*/./' pspp.jnl], [0], [dnl
+* New session.
+set journal='pspp.jnl' on.
+data list notable /x y 1-2.
+begin data.
+12
+end data.
+
+* New session.
+set journal=on.
+])
+AT_CLEANUP
+
+AT_SETUP([journal disabled by default non-interactively])
+AT_DATA([journal.sps], [dnl
+data list notable /x y 1-2.
+])
+AT_CHECK([XDG_STATE_HOME=$PWD pspp journal.sps])
+AT_CHECK([test ! -e pspp/pspp.jnl])
+AT_CLEANUP
+
+AT_SETUP([journal enabled by default interactively])
+AT_SKIP_IF([test "$SQUISH_PTY" = no])
+AT_CHECK([echo 'data list notable /x y 1-2.
+finish.' | XDG_STATE_HOME=$PWD $SQUISH_PTY pspp], [0], [ignore])
+AT_CHECK([sed 's/New session at .*/New session./' pspp/pspp.jnl], [0], [dnl
+* New session.
+data list notable /x y 1-2.
+finish.
+])
+AT_CLEANUP
index 7d1c6df4afe253fef78407ab5929e03ee15f5b05..08390d01bdc6dd31be760db73521b657dd645b89 100644 (file)
@@ -1,4 +1,4 @@
-dnl PSPP - a program for statistical analysis.   -*- autotest -*-
+nl PSPP - a program for statistical analysis.   -*- autotest -*-
 dnl Copyright (C) 2017 Free Software Foundation, Inc.
 dnl
 dnl This program is free software: you can redistribute it and/or modify
@@ -63,3 +63,20 @@ if test X"$RUNNER" != X; then
     PATH=$wrapper_dir:$PATH
 fi
 ])
+
+m4_divert_text([PREPARE_TESTS], [dnl
+dnl ptys are pretty system-dependent and it's hard to test them
+dnl everywhere.  For example, on Mac OS the SHOW N and FINISH command
+dnl text doesn't appear in the output.  So we'll just skip them
+dnl other than on the OS we know best.
+AS_CASE([$host],
+  [*-linux*],
+  [dnl Make sure that squish-pty works.
+   SQUISH_PTY="$PYTHON3 $abs_top_srcdir/tests/ui/terminal/squish-pty.py"
+   if $SQUISH_PTY true </dev/null >/dev/null 2>/dev/null; then
+       :
+   else
+       SQUISH_PTY=no
+   fi],
+  [SQUISH_PTY=no])
+])
index c7569fded4355a27a69fd6309b87bc2babbb5a47..254a5ac6d83bf82582b6628e6522cb202100e584 100644 (file)
@@ -74,20 +74,9 @@ dnl Bug #63910 reported that command output was delayed until the
 dnl next command was supplied.  This checks for regression against
 dnl that bug.
 AT_SETUP([interactive output appears immediately])
-dnl ptys are pretty system-dependent and it's hard to test them
-dnl everywhere.  For example, on Mac OS the SHOW N and FINISH command
-dnl text doesn't appear in the output.  So we'll just skip them
-dnl other than on the OS we know best.
-AT_CHECK([case $host in #(
-  *-linux*) ;; #(
-  *) exit 77
-esac])
-dnl We have to use squish-pty to make PSPP think that we're running
-dnl interactively.  First make sure that squish-pty works at all.
-SQUISH_PTY="$PYTHON3 $top_srcdir/tests/ui/terminal/squish-pty.py"
-AT_CHECK([$SQUISH_PTY true </dev/null >/dev/null 2>/dev/null || exit 77])
-dnl Then do the real test.  The crucial thing to notice here is
-dnl that the SHOW output must appear before the prompt for FINISH.
+AT_SKIP_IF([test "$SQUISH_PTY" = no])
+dnl The crucial thing to notice below is that the SHOW output
+dnl must appear before the prompt for FINISH.
 AT_CHECK([echo 'SHOW N.
 FINISH.' | $SQUISH_PTY pspp], [0], [stdout])
 AT_CHECK([sed -n 's/\r$//