From d2ae5e635da62ff2819f2be62eae50da76997a0f Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 7 Oct 2023 14:31:50 -0700 Subject: [PATCH] Enable journaling by default and add GUI feature for configuring it. --- NEWS | 3 + doc/utilities.texi | 22 +- src/data/file-name.c | 34 ++ src/data/file-name.h | 1 + src/language/commands/set.c | 37 ++- src/output/driver-provider.h | 2 + src/output/driver.c | 15 + src/output/journal.c | 162 ++++++---- src/output/journal.h | 2 +- src/ui/gui/main.c | 13 +- src/ui/gui/options-dialog.c | 140 ++++++-- src/ui/gui/options-dialog.h | 11 + src/ui/gui/options.ui | 494 ++++++++++++++++++----------- src/ui/gui/psppire-conf.c | 147 ++++----- src/ui/gui/psppire-conf.h | 47 +-- src/ui/gui/psppire-data-editor.c | 10 +- src/ui/gui/psppire-dictview.c | 8 +- src/ui/gui/psppire-output-window.c | 12 +- src/ui/gui/psppire-window-base.c | 9 +- src/ui/gui/psppire.c | 3 +- src/ui/terminal/terminal-reader.c | 2 +- tests/automake.mk | 1 + tests/output/journal.at | 49 +++ tests/testsuite.in | 19 +- tests/ui/terminal/main.at | 17 +- 25 files changed, 812 insertions(+), 448 deletions(-) create mode 100644 tests/output/journal.at diff --git a/NEWS b/NEWS index 9e2516d1cc..53361d85df 100644 --- 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. diff --git a/doc/utilities.texi b/doc/utilities.texi index f4aed35464..92a00d0f46 100644 --- a/doc/utilities.texi +++ b/doc/utilities.texi @@ -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 diff --git a/src/data/file-name.c b/src/data/file-name.c index 43d0d3f2c6..18624fe330 100644 --- a/src/data/file-name.c +++ b/src/data/file-name.c @@ -43,6 +43,7 @@ #include "gl/xmalloca.h" #include "gettext.h" +#include "xvasprintf.h" #define _(msgid) gettext (msgid) @@ -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 diff --git a/src/data/file-name.h b/src/data/file-name.h index b4eee83e76..e9e3dce184 100644 --- a/src/data/file-name.h +++ b/src/data/file-name.h @@ -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 */ diff --git a/src/language/commands/set.c b/src/language/commands/set.c index a8d46e93cc..c1792337b8 100644 --- a/src/language/commands/set.c +++ b/src/language/commands/set.c @@ -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__ diff --git a/src/output/driver-provider.h b/src/output/driver-provider.h index ffa483b461..6638b79e16 100644 --- a/src/output/driver-provider.h +++ b/src/output/driver-provider.h @@ -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 diff --git a/src/output/driver.c b/src/output/driver.c index 64854d47d7..76f89969b6 100644 --- a/src/output/driver.c +++ b/src/output/driver.c @@ -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; +} static struct output_engine * output_driver_get_engine (const struct output_driver *driver) diff --git a/src/output/journal.c b/src/output/journal.c index c00d94ebd3..dc3fc64cfa 100644 --- a/src/output/journal.c +++ b/src/output/journal.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #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, }; - -/* 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; } diff --git a/src/output/journal.h b/src/output/journal.h index 6571e95272..583debf6dd 100644 --- a/src/output/journal.h +++ b/src/output/journal.h @@ -26,11 +26,11 @@ #include -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 */ diff --git a/src/ui/gui/main.c b/src/ui/gui/main.c index 07afa09a2d..2950d38785 100644 --- a/src/ui/gui/main.c +++ b/src/ui/gui/main.c @@ -19,6 +19,7 @@ #include "pre-initialisation.h" +#include "ui/gui/options-dialog.h" #include "ui/gui/psppire.h" #include @@ -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); } diff --git a/src/ui/gui/options-dialog.c b/src/ui/gui/options-dialog.c index 435ff4f7c0..5d1a4a0755 100644 --- a/src/ui/gui/options-dialog.c +++ b/src/ui/gui/options-dialog.c @@ -18,9 +18,11 @@ #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); +} diff --git a/src/ui/gui/options-dialog.h b/src/ui/gui/options-dialog.h index 0307b1559a..c59173261b 100644 --- a/src/ui/gui/options-dialog.h +++ b/src/ui/gui/options-dialog.h @@ -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 diff --git a/src/ui/gui/options.ui b/src/ui/gui/options.ui index 2d087c79fb..9ed34dc530 100644 --- a/src/ui/gui/options.ui +++ b/src/ui/gui/options.ui @@ -1,5 +1,5 @@ - + @@ -15,6 +15,11 @@ + + + *.jnl + + False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -25,163 +30,264 @@ True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 5 5 - + True False - 0 - in + vertical True False - vertical - + True False - vertical - 5 - start + 0 + in - - Display _Labels + True - True - False - True - True - radiobutton-names + False + vertical + + + + True + False + + + Display _Labels + True + True + False + True + True + + + 0 + 0 + + + + + Display _Names + True + True + False + True + True + True + radiobutton-labels + + + 0 + 1 + + + + + True + False + + + 0 + 2 + + + + + Sort by L_abel + True + True + False + True + True + True + + + 0 + 3 + + + + + Sort by Na_me + True + True + False + True + True + True + radiobutton-sort-by-label + + + 0 + 4 + + + + + Do not S_ort + True + True + False + True + True + True + radiobutton-sort-by-label + + + 0 + 5 + + + + + False + True + 0 + + - - True - True - 0 - - - - Display _Names + + True - True - False - True - True - True + False + Variable Lists - - True - True - 1 - + + + False + True + 0 + + + + + True + False + vertical - + True False + 0 + in + + + True + False + start + vertical + start + + + Ma_ximize + True + True + False + True + True + + + True + True + 0 + + + + + _Raise + True + True + False + True + True + + + True + True + 1 + + + + + Aler_t + True + True + False + True + True + + + True + True + 2 + + + + + + + True + False + Output Window Action + + False - False - 2 - - - - - Sort by L_abel - True - True - False - True - True - radiobutton-sort-by-name - - - True - True - 3 - - - - - Sort by Na_me - True - True - False - True - True - True - - - True True - 4 + 0 - - Do not S_ort + True - True - False - True - True - True - radiobutton-sort-by-name + False + 0 + + + Show Tips + True + True + False + True + + + + + True + False + Startup Options + + - True + False True - 5 + 1 - False + True True - 2 + 1 + + False + True + 0 + - - - True - False - Variable Lists - - - - - True - True - 0 - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - vertical - PSPPIRE_BUTTON_OK_MASK | PSPPIRE_BUTTON_CANCEL_MASK | PSPPIRE_BUTTON_HELP_MASK - - - False - False - end - 1 - - - - - True - False - vertical True @@ -189,54 +295,101 @@ 0 in - + + True False - vertical - spread - - Ma_ximize + + None True True False - True + True + True True - True - True - 0 + 0 + 0 - - _Raise + True - True - False - True - True + False + + + Default + True + True + False + True + True + journal-disable + + + False + True + 0 + + + + + True + False + end + + + True + True + 1 + + - True - True - 1 + 0 + 1 - - Aler_t + True - True - False - True - True + False + + + Custom + True + True + False + True + True + journal-disable + + + False + True + 0 + + + + + True + False + True + journal-file-filter + + + + True + True + 1 + + - True - True - 2 + 0 + 2 @@ -245,42 +398,7 @@ True False - Output Window Action - - - - - True - True - 0 - - - - - True - False - 0 - - - True - False - 12 - - - Show Tips - True - True - False - True - - - - - - - True - False - Startup Options + Journal File @@ -294,7 +412,23 @@ True True - 2 + 0 + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + vertical + PSPPIRE_BUTTON_OK_MASK | PSPPIRE_BUTTON_CANCEL_MASK | PSPPIRE_BUTTON_HELP_MASK + + + False + False + end + 1 diff --git a/src/ui/gui/psppire-conf.c b/src/ui/gui/psppire-conf.c index 4540e32796..dee6ff2f62 100644 --- a/src/ui/gui/psppire-conf.c +++ b/src/ui/gui/psppire-conf.c @@ -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); } } diff --git a/src/ui/gui/psppire-conf.h b/src/ui/gui/psppire-conf.h index 31f8d3d0c9..d998adbe88 100644 --- a/src/ui/gui/psppire-conf.h +++ b/src/ui/gui/psppire-conf.h @@ -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 diff --git a/src/ui/gui/psppire-data-editor.c b/src/ui/gui/psppire-data-editor.c index 6d9f069467..3747fd6e5a 100644 --- a/src/ui/gui/psppire-data-editor.c +++ b/src/ui/gui/psppire-data-editor.c @@ -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); } diff --git a/src/ui/gui/psppire-dictview.c b/src/ui/gui/psppire-dictview.c index ff8af1b146..e5d12693c9 100644 --- a/src/ui/gui/psppire-dictview.c +++ b/src/ui/gui/psppire-dictview.c @@ -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); diff --git a/src/ui/gui/psppire-output-window.c b/src/ui/gui/psppire-output-window.c index a485c6d173..6b3adb3d29 100644 --- a/src/ui/gui/psppire-output-window.c +++ b/src/ui/gui/psppire-output-window.c @@ -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)); } } diff --git a/src/ui/gui/psppire-window-base.c b/src/ui/gui/psppire-window-base.c index 1db54c7d12..de987568f9 100644 --- a/src/ui/gui/psppire-window-base.c +++ b/src/ui/gui/psppire-window-base.c @@ -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) diff --git a/src/ui/gui/psppire.c b/src/ui/gui/psppire.c index e77c449315..cecb60bb57 100644 --- a/src/ui/gui/psppire.c +++ b/src/ui/gui/psppire.c @@ -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); diff --git a/src/ui/terminal/terminal-reader.c b/src/ui/terminal/terminal-reader.c index f028def094..8b55720741 100644 --- a/src/ui/terminal/terminal-reader.c +++ b/src/ui/terminal/terminal-reader.c @@ -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 * diff --git a/tests/automake.mk b/tests/automake.mk index 43c2f89078..31634ff933 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -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 index 0000000000..07247bd50c --- /dev/null +++ b/tests/output/journal.at @@ -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 diff --git a/tests/testsuite.in b/tests/testsuite.in index 7d1c6df4af..08390d01bd 100644 --- a/tests/testsuite.in +++ b/tests/testsuite.in @@ -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 2>/dev/null; then + : + else + SQUISH_PTY=no + fi], + [SQUISH_PTY=no]) +]) diff --git a/tests/ui/terminal/main.at b/tests/ui/terminal/main.at index c7569fded4..254a5ac6d8 100644 --- a/tests/ui/terminal/main.at +++ b/tests/ui/terminal/main.at @@ -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 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/ $// -- 2.30.2