From e195fccfab97205acb29f90fd1168488d49f1573 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 29 Apr 2011 21:45:15 -0700 Subject: [PATCH 1/1] Implement DATASET commands. --- NEWS | 5 + doc/combining.texi | 10 +- doc/data-io.texi | 96 +++++- doc/files.texi | 32 +- doc/language.texi | 62 ++-- doc/transformation.texi | 7 +- src/data/any-reader.c | 6 +- src/data/any-writer.c | 8 +- src/data/automake.mk | 12 +- src/data/caseinit.c | 31 +- src/data/caseinit.h | 3 +- .../{scratch-reader.c => dataset-reader.c} | 33 +- .../{scratch-reader.h => dataset-reader.h} | 10 +- .../{scratch-writer.c => dataset-writer.c} | 60 ++-- .../{scratch-writer.h => dataset-writer.h} | 10 +- src/data/dataset.c | 155 ++++++++- src/data/dataset.h | 29 +- src/data/dictionary.c | 4 +- src/data/file-handle-def.c | 59 ++-- src/data/file-handle-def.h | 13 +- src/data/scratch-handle.c | 36 --- src/data/scratch-handle.h | 32 -- src/data/session.c | 180 +++++++++++ src/data/session.h | 47 +++ src/language/command.c | 3 + src/language/command.def | 12 +- src/language/data-io/automake.mk | 1 + src/language/data-io/combine-files.c | 2 +- src/language/data-io/data-list.c | 2 +- src/language/data-io/dataset.c | 279 ++++++++++++++++ src/language/data-io/file-handle.h | 17 +- src/language/data-io/file-handle.q | 154 +++++---- src/language/data-io/get-data.c | 2 +- src/language/data-io/get.c | 2 +- src/language/data-io/inpt-pgm.c | 2 +- src/language/data-io/print-space.c | 2 +- src/language/data-io/print.c | 2 +- src/language/data-io/save-translate.c | 2 +- src/language/data-io/save.c | 2 +- src/language/dictionary/apply-dictionary.c | 2 +- src/language/dictionary/sys-file-info.c | 2 +- src/language/expressions/evaluate.c | 2 +- src/language/stats/aggregate.c | 2 +- src/language/utilities/include.c | 4 +- src/ui/gui/data-editor.ui | 7 + src/ui/gui/executor.c | 129 ++++++-- src/ui/gui/psppire-data-window.c | 193 ++++++++--- src/ui/gui/psppire-data-window.h | 20 +- src/ui/gui/psppire-syntax-window.c | 39 +-- src/ui/gui/psppire-window.c | 288 +++++++++++------ src/ui/gui/psppire-window.h | 10 +- src/ui/gui/psppire.c | 3 +- src/ui/terminal/main.c | 13 +- tests/automake.mk | 1 + tests/language/data-io/dataset.at | 302 ++++++++++++++++++ tests/language/stats/aggregate.at | 16 +- 56 files changed, 1855 insertions(+), 602 deletions(-) rename src/data/{scratch-reader.c => dataset-reader.c} (61%) rename src/data/{scratch-reader.h => dataset-reader.h} (79%) rename src/data/{scratch-writer.c => dataset-writer.c} (65%) rename src/data/{scratch-writer.h => dataset-writer.h} (79%) delete mode 100644 src/data/scratch-handle.c delete mode 100644 src/data/scratch-handle.h create mode 100644 src/data/session.c create mode 100644 src/data/session.h create mode 100644 src/language/data-io/dataset.c create mode 100644 tests/language/data-io/dataset.at diff --git a/NEWS b/NEWS index 86c88fc8..70020475 100644 --- a/NEWS +++ b/NEWS @@ -71,6 +71,11 @@ Changes from 0.7.3 to 0.7.7: PROMPT, CPROMPT, and DPROMPT subcommands. The defaults are now the only supported values. + * The dataset commands DATASET ACTIVATE, DATASET DECLARE, DATASET + CLOSE, DATASET COPY, DATASET NAME, DATASET DISPLAY are now + implemented. These commands replace the "scratch file" PSPP + extension, which is no longer supported. + Changes from 0.7.2 to 0.7.3: * Charts are now produced with Cairo and Pango, instead of libplot. diff --git a/doc/combining.texi b/doc/combining.texi index 8fe7b209..a4f683d5 100644 --- a/doc/combining.texi +++ b/doc/combining.texi @@ -2,7 +2,7 @@ @chapter Combining Data Files This chapter describes commands that allow data from system files, -portable files, scratch files, and the active dataset to be combined to +portable files, and open datasets to be combined to form a new active dataset. These commands can combine data files in the following ways: @@ -63,10 +63,10 @@ them. The command's output becomes the new active dataset. The input files are not changed on disk. The syntax of each command begins with a specification of the files to -be read as input. For each input file, specify FILE with a system, -portable, or scratch file's name as a string or a file handle -(@pxref{File Handles}), or specify an asterisk (@samp{*}) to use the -active dataset as input. Use of portable or scratch files on FILE is a +be read as input. For each input file, specify FILE with a system +file or portable file's name as a string, a dataset (@pxref{Datasets}) +or file handle name, (@pxref{File Handles}), or an asterisk (@samp{*}) +to use the active dataset as input. Use of portable files on FILE is a PSPP extension. At least two FILE subcommands must be specified. If the active dataset diff --git a/doc/data-io.texi b/doc/data-io.texi index 81bdd477..cfb99eb7 100644 --- a/doc/data-io.texi +++ b/doc/data-io.texi @@ -26,6 +26,7 @@ actually be read until a procedure is executed. * BEGIN DATA:: Embed data within a syntax file. * CLOSE FILE HANDLE:: Close a file handle. * DATAFILE ATTRIBUTE:: Set custom attributes on data files. +* DATASET:: Manage multiple datasets. * DATA LIST:: Fundamental data reading command. * END CASE:: Output the current case. * END FILE:: Terminate the current input program. @@ -78,12 +79,6 @@ given file. The only specification is the name of the handle to close. Afterward @cmd{FILE HANDLE}. -If the file handle name refers to a scratch file, then the storage -associated with the scratch file in memory or on disk will be freed. -If the scratch file is in use, e.g.@: it has been specified on a -@cmd{GET} command whose execution has not completed, then freeing is -delayed until it is no longer in use. - The file named INLINE, which represents data entered between @cmd{BEGIN DATA} and @cmd{END DATA}, cannot be closed. Attempts to close it with @cmd{CLOSE FILE HANDLE} have no effect. @@ -137,6 +132,84 @@ with the entire active dataset, use @cmd{VARIABLE ATTRIBUTE} by conditional and looping structures such as @cmd{DO IF} or @cmd{LOOP}. +@node DATASET +@section DATASET commands +@vindex DATASET + +@display +DATASET NAME name [WINDOW=@{ASIS,FRONT@}]. +DATASET ACTIVATE name [WINDOW=@{ASIS,FRONT@}]. +DATASET COPY name [WINDOW=@{MINIMIZED,HIDDEN,FRONT@}]. +DATASET DECLARE name [WINDOW=@{MINIMIZED,HIDDEN,FRONT@}]. +DATASET CLOSE @{name,*,ALL@}. +DATASET DISPLAY. +@end display + +The @cmd{DATASET} commands simplify use of multiple datasets within a +PSPP session. They allow datasets to be created and destroyed. At +any given time, most PSPP commands work with a single dataset, called +the active dataset. + +@vindex DATASET NAME +The DATASET NAME command gives the active dataset the specified name, or +if it already had a name, it renames it. If another dataset already +had the given name, that dataset is deleted. + +@vindex DATASET ACTIVATE +The DATASET ACTIVATE command selects the named dataset, which must +already exist, as the active dataset. Before switching the active +dataset, any pending transformations are executed, as if @cmd{EXECUTE} +had been specified. If the active dataset is unnamed before +switching, then it is deleted and becomes unavailable after switching. + +@vindex DATASET COPY +The DATASET COPY command creates a new dataset with the specified +name, whose contents are a copy of the active dataset. Any pending +transformations are executed, as if @cmd{EXECUTE} had been specified, +before making the copy. If a dataset with the given name already +exists, it is replaced. If the name is the name of the active +dataset, then the active dataset becomes unnamed. + +@vindex DATASET DECLARE +The DATASET DECLARE command creates a new dataset that is initially +``empty,'' that is, it has no dictionary or data. If a dataset with +the given name already exists, this has no effect. The new dataset +can be used with commands that support output to a dataset, +e.g. AGGREGATE (@pxref{AGGREGATE}). + +@vindex DATASET CLOSE +The DATASET CLOSE command deletes a dataset. If the active dataset is +specified by name, or if @samp{*} is specified, then the active +dataset becomes unnamed. If a different dataset is specified by name, +then it is deleted and becomes unavailable. Specifying ALL deletes +all datasets except for the active dataset, which becomes unnamed. + +@vindex DATASET DISPLAY +The DATASET DISPLAY command lists all the currently defined datasets. + +Many DATASET commands accept an optional WINDOW subcommand. In the +PSPPIRE GUI, the value given for this subcommand influences how the +dataset's window is displayed. Outside the GUI, the WINDOW subcommand +has no effect. The valid values are: + +@table @asis +@item ASIS +Do not change how the window is displayed. This is the default for +DATASET NAME and DATASET ACTIVATE. + +@item FRONT +Raise the dataset's window to the top. Make it the default dataset +for running syntax. + +@item MINIMIZED +Display the window ``minimized'' to an icon. Prefer other datasets +for running syntax. This is the default for DATASET COPY and DATASET +DECLARE. + +@item HIDDEN +Hide the dataset's window. Prefer other datasets for running syntax. +@end table + @node DATA LIST @section DATA LIST @vindex DATA LIST @@ -512,10 +585,6 @@ For binary files encoded in EBCDIC: /MODE=360 /RECFORM=@{FIXED,VARIABLE,SPANNED@} [/LRECL=rec_len] - -To explicitly declare a scratch handle: - FILE HANDLE handle_name - /MODE=SCRATCH @end display Use @cmd{FILE HANDLE} to associate a file handle name with a file and @@ -645,13 +714,6 @@ set, which are then translated from EBCDIC to the native character set. Thus, when the host's native character set is based on ASCII, these fields are effectively padded with character @code{X'80'}. This wart is implemented for compatibility. - -@item -SCRATCH mode is a PSPP extension that designates the file handle as a -scratch file handle. -Its use is usually unnecessary because file handle names that begin with -@samp{#} are assumed to refer to scratch files. @pxref{File Handles}, -for more information. @end itemize The NAME subcommand specifies the name of the file associated with the diff --git a/doc/files.texi b/doc/files.texi index b4a24469..4fc92e1b 100644 --- a/doc/files.texi +++ b/doc/files.texi @@ -30,10 +30,10 @@ and missing values taken from a file to corresponding variables in the active dataset. In some cases it also updates the weighting variable. -Specify a system file, portable file, or scratch file with a file name -string or as a file handle (@pxref{File Handles}). The dictionary in the -file will be read, but it will not replace the active dataset dictionary. -The file's data will not be read. +Specify a system file or portable file's name, a data set name +(@pxref{Datasets}), or a file handle name (@pxref{File Handles}). The +dictionary in the file will be read, but it will not replace the +active dataset's dictionary. The file's data will not be read. Only variables with names that exist in both the active dataset and the system file are considered. Variables with the same name but different @@ -100,7 +100,7 @@ EXPORT @end display The @cmd{EXPORT} procedure writes the active dataset's dictionary and -data to a specified portable file or scratch file. +data to a specified portable file. By default, cases excluded with FILTER are written to the file. These can be excluded by specifying DELETE on the UNSELECTED @@ -116,7 +116,7 @@ subcommand may be used to specify the number of decimal digits of precision to write. DIGITS applies only to non-integers. The OUTFILE subcommand, which is the only required subcommand, specifies -the portable file or scratch file to be written as a file name string or +the portable file to be written as a file name string or a file handle (@pxref{File Handles}). DROP, KEEP, and RENAME follow the same format as the SAVE procedure @@ -145,7 +145,7 @@ GET replaces them with the dictionary and data from a specified file. The FILE subcommand is the only required subcommand. Specify the system -file, portable file, or scratch file to be read as a string file name or +file or portable file to be read as a string file name or a file handle (@pxref{File Handles}). By default, all the variables in a file are read. The DROP @@ -174,8 +174,7 @@ is affected by these subcommands. @cmd{GET} does not cause the data to be read, only the dictionary. The data is read later, when a procedure is executed. -Use of @cmd{GET} to read a portable file or scratch file is a PSPP -extension. +Use of @cmd{GET} to read a portable file is a PSPP extension. @node GET DATA @section GET DATA @@ -633,8 +632,8 @@ IMPORT The @cmd{IMPORT} transformation clears the active dataset dictionary and data and -replaces them with a dictionary and data from a system, portable file, -or scratch file. +replaces them with a dictionary and data from a system file or +portable file. The FILE subcommand, which is the only required subcommand, specifies the portable file to be read as a file name string or a file handle @@ -647,8 +646,7 @@ DROP, KEEP, and RENAME follow the syntax used by @cmd{GET} (@pxref{GET}). @cmd{IMPORT} does not cause the data to be read, only the dictionary. The data is read later, when a procedure is executed. -Use of @cmd{IMPORT} to read a system file or scratch file is a PSPP -extension. +Use of @cmd{IMPORT} to read a system file is a PSPP extension. @node SAVE @section SAVE @@ -670,10 +668,10 @@ SAVE The @cmd{SAVE} procedure causes the dictionary and data in the active dataset to -be written to a system file or scratch file. +be written to a system file. -OUTFILE is the only required subcommand. Specify the system file or -scratch file to be written as a string file name or a file handle +OUTFILE is the only required subcommand. Specify the system file +to be written as a string file name or a file handle (@pxref{File Handles}). By default, cases excluded with FILTER are written to the system file. @@ -921,7 +919,7 @@ XSAVE @end display The @cmd{XSAVE} transformation writes the active dataset's dictionary and -data to a system file or scratch file. It is similar to the @cmd{SAVE} +data to a system file. It is similar to the @cmd{SAVE} procedure, with two differences: @itemize diff --git a/doc/language.texi b/doc/language.texi index a81d4e2e..a5ef4b69 100644 --- a/doc/language.texi +++ b/doc/language.texi @@ -13,7 +13,7 @@ Later chapters will describe individual commands in detail. * Types of Commands:: Commands come in several flavors. * Order of Commands:: Commands combine to form syntax files. * Missing Observations:: Handling missing observations. -* Variables:: The unit of data storage. +* Datasets:: Data organization. * Files:: Files used by PSPP. * File Handles:: How files are named. * BNF:: How command syntax is described. @@ -380,19 +380,29 @@ for that variable. However, most of the time user-missing values are treated in the same way as the system-missing value. For more information on missing values, see the following sections: -@ref{Variables}, @ref{MISSING VALUES}, @ref{Expressions}. See also the +@ref{Datasets}, @ref{MISSING VALUES}, @ref{Expressions}. See also the documentation on individual procedures for information on how they handle missing values. -@node Variables -@section Variables -@cindex variables +@node Datasets +@section Datasets +@cindex dataset +@cindex variable @cindex dictionary -Variables are the basic unit of data storage in PSPP. All the -variables in a file taken together, apart from any associated data, are -said to form a @dfn{dictionary}. -Some details of variables are described in the sections below. +PSPP works with data organized into @dfn{datasets}. A dataset +consists of a set of @dfn{variables}, which taken together are said to +form a @dfn{dictionary}, and one or more @dfn{cases}, each of which +has one value for each variable. + +At any given time PSPP has exactly one distinguished dataset, called +the @dfn{active dataset}. Most PSPP commands work only with the +active dataset. In addition to the active dataset, PSPP also supports +any number of additional open datasets. The @cmd{DATASET} commands +can choose a new active dataset from among those that are open, as +well as create and destroy datasets (@pxref{DATASET}). + +The sections below describe variables in more detail. @menu * Attributes:: Attributes of variables. @@ -1307,14 +1317,6 @@ run. The output files receive the tables and charts produced by statistical procedures. The output files may be in any number of formats, depending on how PSPP is configured. -@cindex active file -@cindex file, active -@item active file -The active file is the ``file'' on which all PSPP procedures are -performed. The active file consists of a dictionary and a set of cases. -The active file is not necessarily a disk file: it is stored in memory -if there is room. - @cindex system file @cindex file, system @item system file @@ -1327,24 +1329,14 @@ cases. @cmd{GET} and @cmd{SAVE} read and write system files. Portable files are files in a text-based format that store a dictionary and a set of cases. @cmd{IMPORT} and @cmd{EXPORT} read and write portable files. - -@cindex scratch file -@cindex file, scratch -@item scratch file -Scratch files consist of a dictionary and cases and may be stored in -memory or on disk. Most procedures that act on a system file or -portable file can use a scratch file instead. The contents of scratch -files persist within a single PSPP session only. @cmd{GET} and -@cmd{SAVE} can be used to read and write scratch files. Scratch files -are a PSPP extension. @end table @node File Handles @section File Handles @cindex file handles -A @dfn{file handle} is a reference to a data file, system file, portable -file, or scratch file. Most often, a file handle is specified as the +A @dfn{file handle} is a reference to a data file, system file, or +portable file. Most often, a file handle is specified as the name of a file as a string, that is, enclosed within @samp{'} or @samp{"}. @@ -1365,15 +1357,6 @@ the syntax later to use a different file. Use of @cmd{FILE HANDLE} is also required to read data files in binary formats. @xref{FILE HANDLE}, for more information. -PSPP assumes that a file handle name that begins with @samp{#} refers to -a scratch file, unless the name has already been declared on @cmd{FILE -HANDLE} to refer to another kind of file. A scratch file is similar to -a system file, except that it persists only for the duration of a given -PSPP session. Most commands that read or write a system or portable -file, such as @cmd{GET} and @cmd{SAVE}, also accept scratch file -handles. Scratch file handles may also be declared explicitly with -@cmd{FILE HANDLE}. Scratch files are a PSPP extension. - In some circumstances, PSPP must distinguish whether a file handle refers to a system file or a portable file. When this is necessary to read a file, e.g.@: as an input file for @cmd{GET} or @cmd{MATCH FILES}, @@ -1389,8 +1372,7 @@ file'' embedded into the syntax file between @cmd{BEGIN DATA} and The file to which a file handle refers may be reassigned on a later @cmd{FILE HANDLE} command if it is first closed using @cmd{CLOSE FILE -HANDLE}. The @cmd{CLOSE FILE HANDLE} command is also useful to free the -storage associated with a scratch file. @xref{CLOSE FILE HANDLE}, for +HANDLE}. @xref{CLOSE FILE HANDLE}, for more information. @node BNF diff --git a/doc/transformation.texi b/doc/transformation.texi index 57c9a49a..f1d1cbf2 100644 --- a/doc/transformation.texi +++ b/doc/transformation.texi @@ -37,11 +37,12 @@ variables called @dfn{break variables}. Several functions are available for summarizing case contents. The OUTFILE subcommand is required and must appear first. Specify a -system file, portable file, or scratch file by file name or file -handle (@pxref{File Handles}). +system file or portable file by file name or file +handle (@pxref{File Handles}), or a dataset by its name +(@pxref{Datasets}). The aggregated cases are written to this file. If @samp{*} is specified, then the aggregated cases replace the active dataset's data. -Use of OUTFILE to write a portable file or scratch file is a PSPP extension. +Use of OUTFILE to write a portable file is a PSPP extension. If OUTFILE=@samp{*} is given, then the subcommand MODE may also be specified. diff --git a/src/data/any-reader.c b/src/data/any-reader.c index 03a7b23b..50feb689 100644 --- a/src/data/any-reader.c +++ b/src/data/any-reader.c @@ -24,10 +24,10 @@ #include #include +#include "data/dataset-reader.h" #include "data/file-handle-def.h" #include "data/file-name.h" #include "data/por-file-reader.h" -#include "data/scratch-reader.h" #include "data/sys-file-reader.h" #include "libpspp/assertion.h" #include "libpspp/message.h" @@ -111,8 +111,8 @@ any_reader_open (struct file_handle *handle, struct dictionary **dict) msg (SE, _("The inline file is not allowed here.")); return NULL; - case FH_REF_SCRATCH: - return scratch_reader_open (handle, dict); + case FH_REF_DATASET: + return dataset_reader_open (handle, dict); } NOT_REACHED (); } diff --git a/src/data/any-writer.c b/src/data/any-writer.c index b398c70f..61d6ffc8 100644 --- a/src/data/any-writer.c +++ b/src/data/any-writer.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2006, 2011 Free Software Foundation, Inc. + Copyright (C) 2006, 2010, 2011 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,10 +24,10 @@ #include #include +#include "data/dataset-writer.h" #include "data/file-handle-def.h" #include "data/file-name.h" #include "data/por-file-writer.h" -#include "data/scratch-writer.h" #include "data/sys-file-writer.h" #include "libpspp/assertion.h" #include "libpspp/message.h" @@ -67,8 +67,8 @@ any_writer_open (struct file_handle *handle, struct dictionary *dict) msg (ME, _("The inline file is not allowed here.")); return NULL; - case FH_REF_SCRATCH: - return scratch_writer_open (handle, dict); + case FH_REF_DATASET: + return dataset_writer_open (handle, dict); } NOT_REACHED (); diff --git a/src/data/automake.mk b/src/data/automake.mk index f7ee43f6..81a9d9c5 100644 --- a/src/data/automake.mk +++ b/src/data/automake.mk @@ -50,6 +50,10 @@ src_data_libdata_la_SOURCES = \ src/data/data-out.h \ src/data/dataset.c \ src/data/dataset.h \ + src/data/dataset-reader.c \ + src/data/dataset-reader.h \ + src/data/dataset-writer.c \ + src/data/dataset-writer.h \ src/data/datasheet.c \ src/data/datasheet.h \ src/data/dict-class.c \ @@ -84,12 +88,8 @@ src_data_libdata_la_SOURCES = \ src/data/por-file-writer.h \ src/data/psql-reader.c \ src/data/psql-reader.h \ - src/data/scratch-handle.c \ - src/data/scratch-handle.h \ - src/data/scratch-reader.c \ - src/data/scratch-reader.h \ - src/data/scratch-writer.c \ - src/data/scratch-writer.h \ + src/data/session.c \ + src/data/session.h \ src/data/settings.c \ src/data/settings.h \ src/data/short-names.c \ diff --git a/src/data/caseinit.c b/src/data/caseinit.c index 69a0f018..021db396 100644 --- a/src/data/caseinit.c +++ b/src/data/caseinit.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2007, 2009, 2011 Free Software Foundation, Inc. + Copyright (C) 2007, 2009, 2010, 2011 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,7 +53,7 @@ struct init_list /* A bitmap of the "left" status of variables. */ enum leave_class { - LEAVE_REINIT = 0x001, /* Reinitalize for every case. */ + LEAVE_REINIT = 0x001, /* Reinitialize for every case. */ LEAVE_LEFT = 0x002 /* Keep the value from one case to the next. */ }; @@ -65,6 +65,22 @@ init_list_create (struct init_list *list) list->cnt = 0; } +/* Initializes NEW as a copy of OLD. */ +static void +init_list_clone (struct init_list *new, const struct init_list *old) +{ + size_t i; + + new->values = xmemdup (old->values, old->cnt * sizeof *old->values); + new->cnt = old->cnt; + + for (i = 0; i < new->cnt; i++) + { + struct init_value *iv = &new->values[i]; + value_clone (&iv->value, &iv->value, iv->width); + } +} + /* Frees the storage associated with LIST. */ static void init_list_destroy (struct init_list *list) @@ -198,6 +214,17 @@ caseinit_create (void) return ci; } +/* Creates and returns a copy of OLD. */ +struct caseinit * +caseinit_clone (struct caseinit *old) +{ + struct caseinit *new = xmalloc (sizeof *new); + init_list_clone (&new->preinited_values, &old->preinited_values); + init_list_clone (&new->reinit_values, &old->reinit_values); + init_list_clone (&new->left_values, &old->left_values); + return new; +} + /* Clears the contents of case initializer CI. */ void caseinit_clear (struct caseinit *ci) diff --git a/src/data/caseinit.h b/src/data/caseinit.h index dbff8567..9f566218 100644 --- a/src/data/caseinit.h +++ b/src/data/caseinit.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007, 2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,6 +36,7 @@ struct ccase; /* Creation and destruction. */ struct caseinit *caseinit_create (void); +struct caseinit *caseinit_clone (struct caseinit *); void caseinit_clear (struct caseinit *); void caseinit_destroy (struct caseinit *); diff --git a/src/data/scratch-reader.c b/src/data/dataset-reader.c similarity index 61% rename from src/data/scratch-reader.c rename to src/data/dataset-reader.c index 5def728e..b679342a 100644 --- a/src/data/scratch-reader.c +++ b/src/data/dataset-reader.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2006, 2011 Free Software Foundation, Inc. + Copyright (C) 2006, 2010, 2011 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,15 +16,15 @@ #include -#include "data/scratch-reader.h" +#include "data/dataset-reader.h" #include #include "data/case.h" #include "data/casereader.h" +#include "data/dataset.h" #include "data/dictionary.h" #include "data/file-handle-def.h" -#include "data/scratch-handle.h" #include "libpspp/assertion.h" #include "libpspp/message.h" @@ -33,31 +33,30 @@ #include "gettext.h" #define _(msgid) gettext (msgid) -/* Opens FH, which must have referent type FH_REF_SCRATCH, and - returns a scratch_reader for it, or a null pointer on - failure. Stores the dictionary for the scratch file into - *DICT. */ +/* Opens FH, which must have referent type FH_REF_DATASET, and returns a + dataset_reader for it, or a null pointer on failure. Stores a copy of the + dictionary for the dataset file into *DICT. The caller takes ownership of + the casereader and the dictionary. */ struct casereader * -scratch_reader_open (struct file_handle *fh, struct dictionary **dict) +dataset_reader_open (struct file_handle *fh, struct dictionary **dict) { - struct scratch_handle *sh; + struct dataset *ds; /* We don't bother doing fh_lock or fh_ref on the file handle, as there's no advantage in this case, and doing these would require us to keep track of the "struct file_handle" and "struct fh_lock" and undo our work later. */ - assert (fh_get_referent (fh) == FH_REF_SCRATCH); + assert (fh_get_referent (fh) == FH_REF_DATASET); - sh = fh_get_scratch_handle (fh); - if (sh == NULL || sh->casereader == NULL) + ds = fh_get_dataset (fh); + if (ds == NULL || !dataset_has_source (ds)) { - msg (SE, _("Scratch file handle %s has not yet been written, " - "using SAVE or another procedure, so it cannot yet " - "be used for reading."), + msg (SE, _("Cannot read from dataset %s because no dictionary or data " + "has been written to it yet."), fh_get_name (fh)); return NULL; } - *dict = dict_clone (sh->dictionary); - return casereader_clone (sh->casereader); + *dict = dict_clone (dataset_dict (ds)); + return casereader_clone (dataset_source (ds)); } diff --git a/src/data/scratch-reader.h b/src/data/dataset-reader.h similarity index 79% rename from src/data/scratch-reader.h rename to src/data/dataset-reader.h index a5ee005e..420b6b1b 100644 --- a/src/data/scratch-reader.h +++ b/src/data/dataset-reader.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2006, 2009 Free Software Foundation, Inc. + Copyright (C) 2006, 2009, 2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,14 +14,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#ifndef SCRATCH_READER_H -#define SCRATCH_READER_H 1 +#ifndef DATASET_READER_H +#define DATASET_READER_H 1 #include struct dictionary; struct file_handle; -struct casereader *scratch_reader_open (struct file_handle *, +struct casereader *dataset_reader_open (struct file_handle *, struct dictionary **); -#endif /* scratch-reader.h */ +#endif /* dataset-reader.h */ diff --git a/src/data/scratch-writer.c b/src/data/dataset-writer.c similarity index 65% rename from src/data/scratch-writer.c rename to src/data/dataset-writer.c index 2c545c7d..c810cf2a 100644 --- a/src/data/scratch-writer.c +++ b/src/data/dataset-writer.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2006, 2009, 2011 Free Software Foundation, Inc. + Copyright (C) 2006, 2009-2011 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ #include -#include "data/scratch-writer.h" +#include "data/dataset-writer.h" #include @@ -25,9 +25,9 @@ #include "data/casereader.h" #include "data/casewriter-provider.h" #include "data/casewriter.h" +#include "data/dataset.h" #include "data/dictionary.h" #include "data/file-handle-def.h" -#include "data/scratch-handle.h" #include "data/variable.h" #include "libpspp/compiler.h" #include "libpspp/taint.h" @@ -36,41 +36,41 @@ #define N_(msgid) (msgid) -/* A scratch file writer. */ -struct scratch_writer +/* A dataset file writer. */ +struct dataset_writer { - struct file_handle *fh; /* Underlying file handle. */ + struct dataset *ds; /* Underlying dataset. */ struct fh_lock *lock; /* Exclusive access to file handle. */ struct dictionary *dict; /* Dictionary for subwriter. */ struct case_map *compactor; /* Compacts into dictionary. */ struct casewriter *subwriter; /* Data output. */ }; -static const struct casewriter_class scratch_writer_casewriter_class; +static const struct casewriter_class dataset_writer_casewriter_class; -/* Opens FH, which must have referent type FH_REF_SCRATCH, and - returns a scratch_writer for it, or a null pointer on - failure. Cases stored in the scratch_writer will be expected +/* Opens FH, which must have referent type FH_REF_DATASET, and + returns a dataset_writer for it, or a null pointer on + failure. Cases stored in the dataset_writer will be expected to be drawn from DICTIONARY. */ struct casewriter * -scratch_writer_open (struct file_handle *fh, +dataset_writer_open (struct file_handle *fh, const struct dictionary *dictionary) { - struct scratch_writer *writer; + struct dataset_writer *writer; struct casewriter *casewriter; struct fh_lock *lock; /* Get exclusive write access to handle. */ /* TRANSLATORS: this fragment will be interpolated into messages in fh_lock() that identify types of files. */ - lock = fh_lock (fh, FH_REF_SCRATCH, N_("scratch file"), FH_ACC_WRITE, true); + lock = fh_lock (fh, FH_REF_DATASET, N_("dataset"), FH_ACC_WRITE, true); if (lock == NULL) return NULL; /* Create writer. */ writer = xmalloc (sizeof *writer); writer->lock = lock; - writer->fh = fh_ref (fh); + writer->ds = fh_get_dataset (fh); writer->dict = dict_clone (dictionary); dict_delete_scratch_vars (writer->dict); @@ -85,7 +85,7 @@ scratch_writer_open (struct file_handle *fh, writer->subwriter = autopaging_writer_create (dict_get_proto (writer->dict)); casewriter = casewriter_create (dict_get_proto (writer->dict), - &scratch_writer_casewriter_class, writer); + &dataset_writer_casewriter_class, writer); taint_propagate (casewriter_get_taint (writer->subwriter), casewriter_get_taint (casewriter)); return casewriter; @@ -93,35 +93,24 @@ scratch_writer_open (struct file_handle *fh, /* Writes case C to WRITER. */ static void -scratch_writer_casewriter_write (struct casewriter *w UNUSED, void *writer_, +dataset_writer_casewriter_write (struct casewriter *w UNUSED, void *writer_, struct ccase *c) { - struct scratch_writer *writer = writer_; + struct dataset_writer *writer = writer_; casewriter_write (writer->subwriter, case_map_execute (writer->compactor, c)); } /* Closes WRITER. */ static void -scratch_writer_casewriter_destroy (struct casewriter *w UNUSED, void *writer_) +dataset_writer_casewriter_destroy (struct casewriter *w UNUSED, void *writer_) { - static unsigned int next_unique_id = 0x12345678; - - struct scratch_writer *writer = writer_; + struct dataset_writer *writer = writer_; struct casereader *reader = casewriter_make_reader (writer->subwriter); if (!casereader_error (reader)) { - /* Destroy previous contents of handle. */ - struct scratch_handle *sh = fh_get_scratch_handle (writer->fh); - if (sh != NULL) - scratch_handle_destroy (sh); - - /* Create new contents. */ - sh = xmalloc (sizeof *sh); - sh->unique_id = ++next_unique_id; - sh->dictionary = writer->dict; - sh->casereader = reader; - fh_set_scratch_handle (writer->fh, sh); + dataset_set_dict (writer->ds, writer->dict); + dataset_set_source (writer->ds, reader); } else { @@ -130,13 +119,12 @@ scratch_writer_casewriter_destroy (struct casewriter *w UNUSED, void *writer_) } fh_unlock (writer->lock); - fh_unref (writer->fh); free (writer); } -static const struct casewriter_class scratch_writer_casewriter_class = +static const struct casewriter_class dataset_writer_casewriter_class = { - scratch_writer_casewriter_write, - scratch_writer_casewriter_destroy, + dataset_writer_casewriter_write, + dataset_writer_casewriter_destroy, NULL, }; diff --git a/src/data/scratch-writer.h b/src/data/dataset-writer.h similarity index 79% rename from src/data/scratch-writer.h rename to src/data/dataset-writer.h index a9c7a4d9..429d6886 100644 --- a/src/data/scratch-writer.h +++ b/src/data/dataset-writer.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2006, 2009 Free Software Foundation, Inc. + Copyright (C) 2006, 2009, 2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,14 +14,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#ifndef SCRATCH_WRITER_H -#define SCRATCH_WRITER_H 1 +#ifndef DATASET_WRITER_H +#define DATASET_WRITER_H 1 #include struct dictionary; struct file_handle; -struct casewriter *scratch_writer_open (struct file_handle *, +struct casewriter *dataset_writer_open (struct file_handle *, const struct dictionary *); -#endif /* scratch-writer.h */ +#endif /* dataset-writer.h */ diff --git a/src/data/dataset.c b/src/data/dataset.c index 466696c7..10009f4a 100644 --- a/src/data/dataset.c +++ b/src/data/dataset.c @@ -32,6 +32,7 @@ #include "data/casewriter.h" #include "data/dictionary.h" #include "data/file-handle-def.h" +#include "data/session.h" #include "data/transformations.h" #include "data/variable.h" #include "libpspp/deque.h" @@ -44,6 +45,15 @@ #include "gl/xalloc.h" struct dataset { + /* A dataset is usually part of a session. Within a session its name must + unique. The name must either be a valid PSPP identifier or the empty + string. (It must be unique within the session even if it is the empty + string; that is, there may only be a single dataset within a session with + the empty string as its name.) */ + struct session *session; + char *name; + enum dataset_display display; + /* Cases are read from source, their transformation variables are initialized, pass through permanent_trns_chain (which transforms them into @@ -96,8 +106,8 @@ struct dataset { const struct dataset_callbacks *callbacks; void *cb_data; - /* Default encoding for reading syntax files. */ - char *syntax_encoding; + /* Uniquely distinguishes datasets. */ + unsigned int seqno; }; static void dataset_changed__ (struct dataset *); @@ -116,35 +126,83 @@ dict_callback (struct dictionary *d UNUSED, void *ds_) dataset_changed__ (ds); } -/* Creates and returns a new dataset. The dataset initially has an empty - dictionary and no data source. */ +static void +dataset_create_finish__ (struct dataset *ds, struct session *session) +{ + static unsigned int seqno; + + dict_set_change_callback (ds->dict, dict_callback, ds); + proc_cancel_all_transformations (ds); + dataset_set_session (ds, session); + ds->seqno = ++seqno; +} + +/* Creates a new dataset named NAME, adds it to SESSION, and returns it. If + SESSION already contains a dataset named NAME, it is deleted and replaced. + The dataset initially has an empty dictionary and no data source. */ struct dataset * -dataset_create (void) +dataset_create (struct session *session, const char *name) { struct dataset *ds; ds = xzalloc (sizeof *ds); + ds->name = xstrdup (name); + ds->display = DATASET_FRONT; ds->dict = dict_create (get_default_encoding ()); - dict_set_change_callback (ds->dict, dict_callback, ds); ds->caseinit = caseinit_create (); - proc_cancel_all_transformations (ds); - ds->syntax_encoding = xstrdup ("Auto"); + + dataset_create_finish__ (ds, session); + return ds; } +/* Creates and returns a new dataset that has the same data and dictionary as + OLD named NAME, adds it to the same session as OLD, and returns the new + dataset. If SESSION already contains a dataset named NAME, it is deleted + and replaced. + + OLD must not have any active transformations or temporary state and must + not be in the middle of a procedure. + + Callbacks are not cloned. */ +struct dataset * +dataset_clone (struct dataset *old, const char *name) +{ + struct dataset *new; + + assert (old->proc_state == PROC_COMMITTED); + assert (trns_chain_is_empty (old->permanent_trns_chain)); + assert (old->permanent_dict == NULL); + assert (old->sink == NULL); + assert (old->temporary_trns_chain == NULL); + + new = xzalloc (sizeof *new); + new->name = xstrdup (name); + new->display = DATASET_FRONT; + new->source = casereader_clone (old->source); + new->dict = dict_clone (old->dict); + new->caseinit = caseinit_clone (old->caseinit); + new->last_proc_invocation = old->last_proc_invocation; + new->ok = old->ok; + + dataset_create_finish__ (new, old->session); + + return new; +} + /* Destroys DS. */ void dataset_destroy (struct dataset *ds) { if (ds != NULL) { + dataset_set_session (ds, NULL); dataset_clear (ds); dict_destroy (ds->dict); caseinit_destroy (ds->caseinit); trns_chain_destroy (ds->permanent_trns_chain); dataset_transformations_changed__ (ds, false); - free (ds->syntax_encoding); free (ds); } } @@ -166,6 +224,55 @@ dataset_clear (struct dataset *ds) proc_cancel_all_transformations (ds); } +const char * +dataset_name (const struct dataset *ds) +{ + return ds->name; +} + +void +dataset_set_name (struct dataset *ds, const char *name) +{ + struct session *session = ds->session; + bool active = false; + + if (session != NULL) + { + active = session_active_dataset (session) == ds; + if (active) + session_set_active_dataset (session, NULL); + dataset_set_session (ds, NULL); + } + + free (ds->name); + ds->name = xstrdup (name); + + if (session != NULL) + { + dataset_set_session (ds, session); + if (active) + session_set_active_dataset (session, ds); + } +} + +struct session * +dataset_session (const struct dataset *ds) +{ + return ds->session; +} + +void +dataset_set_session (struct dataset *ds, struct session *session) +{ + if (session != ds->session) + { + if (ds->session != NULL) + session_remove_dataset (ds->session, ds); + if (session != NULL) + session_add_dataset (session, ds); + } +} + /* Returns the dictionary within DS. This is always nonnull, although it might not contain any variables. */ struct dictionary * @@ -229,6 +336,15 @@ dataset_steal_source (struct dataset *ds) return reader; } +/* Returns a number unique to DS. It can be used to distinguish one dataset + from any other within a given program run, even datasets that do not exist + at the same time. */ +unsigned int +dataset_seqno (const struct dataset *ds) +{ + return ds->seqno; +} + void dataset_set_callbacks (struct dataset *ds, const struct dataset_callbacks *callbacks, @@ -238,17 +354,16 @@ dataset_set_callbacks (struct dataset *ds, ds->cb_data = cb_data; } -void -dataset_set_default_syntax_encoding (struct dataset *ds, const char *encoding) +enum dataset_display +dataset_get_display (const struct dataset *ds) { - free (ds->syntax_encoding); - ds->syntax_encoding = xstrdup (encoding); + return ds->display; } -const char * -dataset_get_default_syntax_encoding (const struct dataset *ds) +void +dataset_set_display (struct dataset *ds, enum dataset_display display) { - return ds->syntax_encoding; + ds->display = display; } /* Returns the last time the data was read. */ @@ -813,3 +928,11 @@ dataset_transformations_changed__ (struct dataset *ds, bool non_empty) if (ds->callbacks != NULL && ds->callbacks->transformations_changed != NULL) ds->callbacks->transformations_changed (non_empty, ds->cb_data); } + +/* Private interface for use by session code. */ + +void +dataset_set_session__ (struct dataset *ds, struct session *session) +{ + ds->session = session; +} diff --git a/src/data/dataset.h b/src/data/dataset.h index b2aa8bcf..84450947 100644 --- a/src/data/dataset.h +++ b/src/data/dataset.h @@ -21,17 +21,24 @@ #include #include "data/transformations.h" -#include "libpspp/compiler.h" struct casereader; struct dataset; struct dictionary; +struct session; -struct dataset *dataset_create (void); +struct dataset *dataset_create (struct session *, const char *); +struct dataset *dataset_clone (struct dataset *, const char *); void dataset_destroy (struct dataset *); void dataset_clear (struct dataset *); +const char *dataset_name (const struct dataset *); +void dataset_set_name (struct dataset *, const char *); + +struct session *dataset_session (const struct dataset *); +void dataset_set_session (struct dataset *, struct session *); + struct dictionary *dataset_dict (const struct dataset *); void dataset_set_dict (struct dataset *, struct dictionary *); @@ -40,8 +47,7 @@ bool dataset_has_source (const struct dataset *ds); bool dataset_set_source (struct dataset *, struct casereader *); struct casereader *dataset_steal_source (struct dataset *); -void dataset_set_default_syntax_encoding (struct dataset *, const char *); -const char *dataset_get_default_syntax_encoding (const struct dataset *); +unsigned int dataset_seqno (const struct dataset *); struct dataset_callbacks { @@ -58,6 +64,17 @@ struct dataset_callbacks void dataset_set_callbacks (struct dataset *, const struct dataset_callbacks *, void *aux); + +/* Dataset GUI window display status. */ +enum dataset_display + { + DATASET_ASIS, /* Current state unchanged. */ + DATASET_FRONT, /* Display and raise to top. */ + DATASET_MINIMIZED, /* Display as icon. */ + DATASET_HIDDEN /* Do not display. */ + }; +enum dataset_display dataset_get_display (const struct dataset *); +void dataset_set_display (struct dataset *, enum dataset_display); /* Transformations. */ @@ -93,5 +110,9 @@ bool dataset_end_of_command (struct dataset *); const struct ccase *lagged_case (const struct dataset *ds, int n_before); void dataset_need_lag (struct dataset *ds, int n_before); + +/* Private interface for use by session code. */ + +void dataset_set_session__(struct dataset *, struct session *); #endif /* dataset.h */ diff --git a/src/data/dictionary.c b/src/data/dictionary.c index c8f58516..4a0afc73 100644 --- a/src/data/dictionary.c +++ b/src/data/dictionary.c @@ -181,7 +181,9 @@ dict_create (const char *encoding) dictionary. If the new dictionary won't be used to access cases produced with the old dictionary, then the new dictionary's case indexes should be compacted with - dict_compact_values to save space. */ + dict_compact_values to save space. + + Callbacks are not cloned. */ struct dictionary * dict_clone (const struct dictionary *s) { diff --git a/src/data/file-handle-def.c b/src/data/file-handle-def.c index 2b8e40ce..6ca6977c 100644 --- a/src/data/file-handle-def.c +++ b/src/data/file-handle-def.c @@ -23,15 +23,15 @@ #include #include +#include "data/dataset.h" +#include "data/file-name.h" +#include "data/variable.h" #include "libpspp/compiler.h" #include "libpspp/hmap.h" #include "libpspp/i18n.h" #include "libpspp/message.h" #include "libpspp/str.h" #include "libpspp/hash-functions.h" -#include "data/file-name.h" -#include "data/variable.h" -#include "data/scratch-handle.h" #include "gl/xalloc.h" @@ -56,8 +56,8 @@ struct file_handle size_t record_width; /* Length of fixed-format records. */ size_t tab_width; /* Tab width, 0=do not expand tabs. */ - /* FH_REF_SCRATCH only. */ - struct scratch_handle *sh; /* Scratch file data. */ + /* FH_REF_DATASET only. */ + struct dataset *ds; /* Dataset. */ }; /* All "struct file_handle"s with nonnull 'id' member. */ @@ -110,7 +110,6 @@ free_handle (struct file_handle *handle) free (handle->id); free (handle->name); free (handle->file_name); - scratch_handle_destroy (handle->sh); free (handle); } @@ -243,13 +242,19 @@ fh_create_file (const char *id, const char *file_name, /* Creates a new file handle with the given ID, which must be unique among existing file identifiers. The new handle is - associated with a scratch file (initially empty). */ + associated with a dataset file (initially empty). */ struct file_handle * -fh_create_scratch (const char *id) +fh_create_dataset (struct dataset *ds) { + const char *name; struct file_handle *handle; - handle = create_handle (id, xstrdup (id), FH_REF_SCRATCH); - handle->sh = NULL; + + name = dataset_name (ds); + if (name[0] == '\0') + name = _("active dataset"); + + handle = create_handle (NULL, xstrdup (name), FH_REF_DATASET); + handle->ds = ds; return handle; } @@ -334,22 +339,13 @@ fh_get_legacy_encoding (const struct file_handle *handle) return (handle->referent == FH_REF_FILE ? handle->encoding : C_ENCODING); } -/* Returns the scratch file handle associated with HANDLE. - Applicable to only FH_REF_SCRATCH files. */ -struct scratch_handle * -fh_get_scratch_handle (const struct file_handle *handle) +/* Returns the dataset handle associated with HANDLE. + Applicable to only FH_REF_DATASET files. */ +struct dataset * +fh_get_dataset (const struct file_handle *handle) { - assert (handle->referent == FH_REF_SCRATCH); - return handle->sh; -} - -/* Sets SH to be the scratch file handle associated with HANDLE. - Applicable to only FH_REF_SCRATCH files. */ -void -fh_set_scratch_handle (struct file_handle *handle, struct scratch_handle *sh) -{ - assert (handle->referent == FH_REF_SCRATCH); - handle->sh = sh; + assert (handle->referent == FH_REF_DATASET); + return handle->ds; } /* Returns the current default handle. */ @@ -382,7 +378,7 @@ struct fh_lock union { struct file_identity *file; /* FH_REF_FILE only. */ - unsigned int unique_id; /* FH_REF_SCRATCH only. */ + unsigned int unique_id; /* FH_REF_DATASET only. */ } u; enum fh_access access; /* Type of file access. */ @@ -590,11 +586,8 @@ make_key (struct fh_lock *lock, const struct file_handle *h, lock->access = access; if (lock->referent == FH_REF_FILE) lock->u.file = fn_get_identity (fh_get_file_name (h)); - else if (lock->referent == FH_REF_SCRATCH) - { - struct scratch_handle *sh = fh_get_scratch_handle (h); - lock->u.unique_id = sh != NULL ? sh->unique_id : 0; - } + else if (lock->referent == FH_REF_DATASET) + lock->u.unique_id = dataset_seqno (fh_get_dataset (h)); } /* Frees the key fields in LOCK. */ @@ -616,7 +609,7 @@ compare_fh_locks (const struct fh_lock *a, const struct fh_lock *b) return a->access < b->access ? -1 : 1; else if (a->referent == FH_REF_FILE) return fn_compare_file_identities (a->u.file, b->u.file); - else if (a->referent == FH_REF_SCRATCH) + else if (a->referent == FH_REF_DATASET) return (a->u.unique_id < b->u.unique_id ? -1 : a->u.unique_id > b->u.unique_id); else @@ -630,7 +623,7 @@ hash_fh_lock (const struct fh_lock *lock) unsigned int basis; if (lock->referent == FH_REF_FILE) basis = fn_hash_identity (lock->u.file); - else if (lock->referent == FH_REF_SCRATCH) + else if (lock->referent == FH_REF_DATASET) basis = lock->u.unique_id; else basis = 0; diff --git a/src/data/file-handle-def.h b/src/data/file-handle-def.h index 2df85f98..11898ef5 100644 --- a/src/data/file-handle-def.h +++ b/src/data/file-handle-def.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997-9, 2000, 2005, 2006, 2011 Free Software Foundation, Inc. + Copyright (C) 1997-9, 2000, 2005, 2006, 2010, 2011 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ #include #include +struct dataset; + /* What a file handle refers to. (Ordinarily only a single value is allowed, but fh_open() and fh_parse() take a mask.) */ @@ -27,7 +29,7 @@ enum fh_referent { FH_REF_FILE = 001, /* Ordinary file (the most common case). */ FH_REF_INLINE = 002, /* The inline file. */ - FH_REF_SCRATCH = 004 /* Temporary dataset. */ + FH_REF_DATASET = 004 /* Dataset. */ }; /* File modes. */ @@ -63,7 +65,7 @@ void fh_done (void); struct file_handle *fh_create_file (const char *handle_name, const char *file_name, const struct fh_properties *); -struct file_handle *fh_create_scratch (const char *handle_name); +struct file_handle *fh_create_dataset (struct dataset *); const struct fh_properties *fh_default_properties (void); /* Reference management. */ @@ -90,9 +92,8 @@ size_t fh_get_record_width (const struct file_handle *); size_t fh_get_tab_width (const struct file_handle *); const char *fh_get_legacy_encoding (const struct file_handle *); -/* Properties of FH_REF_SCRATCH file handles. */ -struct scratch_handle *fh_get_scratch_handle (const struct file_handle *); -void fh_set_scratch_handle (struct file_handle *, struct scratch_handle *); +/* Properties of FH_REF_DATASET file handles. */ +struct dataset *fh_get_dataset (const struct file_handle *); /* Mutual exclusion for access . */ struct fh_lock *fh_lock (struct file_handle *, enum fh_referent mask, diff --git a/src/data/scratch-handle.c b/src/data/scratch-handle.c deleted file mode 100644 index a95f2cc2..00000000 --- a/src/data/scratch-handle.c +++ /dev/null @@ -1,36 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2006, 2011 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - -#include - -#include "data/scratch-handle.h" - -#include - -#include "data/casereader.h" -#include "data/dictionary.h" - -/* Destroys HANDLE. */ -void -scratch_handle_destroy (struct scratch_handle *handle) -{ - if (handle != NULL) - { - dict_destroy (handle->dictionary); - casereader_destroy (handle->casereader); - free (handle); - } -} diff --git a/src/data/scratch-handle.h b/src/data/scratch-handle.h deleted file mode 100644 index c7775f4d..00000000 --- a/src/data/scratch-handle.h +++ /dev/null @@ -1,32 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2006 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - -#ifndef SCRATCH_HANDLE_H -#define SCRATCH_HANDLE_H 1 - -#include - -/* A scratch file. */ -struct scratch_handle - { - unsigned int unique_id; /* Identifies this scratch file. */ - struct dictionary *dictionary; /* Dictionary. */ - struct casereader *casereader; /* Cases. */ - }; - -void scratch_handle_destroy (struct scratch_handle *); - -#endif /* scratch-handle.h */ diff --git a/src/data/session.c b/src/data/session.c new file mode 100644 index 00000000..24c22b24 --- /dev/null +++ b/src/data/session.c @@ -0,0 +1,180 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2010, 2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "data/session.h" + +#include +#include + +#include "data/dataset.h" +#include "libpspp/assertion.h" +#include "libpspp/cast.h" +#include "libpspp/hash-functions.h" +#include "libpspp/str.h" +#include "libpspp/hmapx.h" + +#include "gl/xalloc.h" + +struct session + { + struct hmapx datasets; + struct dataset *active; + char *syntax_encoding; /* Default encoding for syntax files. */ + }; + +static struct hmapx_node *session_lookup_dataset__ (const struct session *, + const char *name); + +struct session * +session_create (void) +{ + struct session *s; + + s = xmalloc (sizeof *s); + hmapx_init (&s->datasets); + s->active = NULL; + s->syntax_encoding = xstrdup ("Auto"); + return s; +} + +void +session_destroy (struct session *s) +{ + if (s != NULL) + { + struct hmapx_node *node, *next; + struct dataset *ds; + + s->active = NULL; + HMAPX_FOR_EACH_SAFE (ds, node, next, &s->datasets) + dataset_destroy (ds); + free (s->syntax_encoding); + free (s); + } +} + +struct dataset * +session_active_dataset (struct session *s) +{ + return s->active; +} + +void +session_set_active_dataset (struct session *s, struct dataset *ds) +{ + assert (ds == NULL || dataset_session (ds) == s); + s->active = ds; +} + +void +session_add_dataset (struct session *s, struct dataset *ds) +{ + struct dataset *old; + + old = session_lookup_dataset (s, dataset_name (ds)); + if (old == s->active) + s->active = ds; + if (old != NULL) + session_remove_dataset (s, old); + + hmapx_insert (&s->datasets, ds, hash_case_string (dataset_name (ds), 0)); + if (s->active == NULL) + s->active = ds; + + dataset_set_session__ (ds, s); +} + +void +session_remove_dataset (struct session *s, struct dataset *ds) +{ + assert (ds != s->active); + hmapx_delete (&s->datasets, session_lookup_dataset__ (s, dataset_name (ds))); + dataset_set_session__ (ds, NULL); +} + +struct dataset * +session_lookup_dataset (const struct session *s, const char *name) +{ + struct hmapx_node *node = session_lookup_dataset__ (s, name); + return node != NULL ? node->data : NULL; +} + +struct dataset * +session_lookup_dataset_assert (const struct session *s, const char *name) +{ + struct dataset *ds = session_lookup_dataset (s, name); + assert (ds != NULL); + return ds; +} + +void +session_set_default_syntax_encoding (struct session *s, const char *encoding) +{ + free (s->syntax_encoding); + s->syntax_encoding = xstrdup (encoding); +} + +const char * +session_get_default_syntax_encoding (const struct session *s) +{ + return s->syntax_encoding; +} + +size_t +session_n_datasets (const struct session *s) +{ + return hmapx_count (&s->datasets); +} + +void +session_for_each_dataset (const struct session *s, + void (*cb) (struct dataset *, void *aux), + void *aux) +{ + struct hmapx_node *node, *next; + struct dataset *ds; + + HMAPX_FOR_EACH_SAFE (ds, node, next, &s->datasets) + cb (ds, aux); +} + +struct dataset * +session_get_dataset_by_seqno (const struct session *s, unsigned int seqno) +{ + struct hmapx_node *node; + struct dataset *ds; + + HMAPX_FOR_EACH (ds, node, &s->datasets) + if (dataset_seqno (ds) == seqno) + return ds; + return NULL; +} + +static struct hmapx_node * +session_lookup_dataset__ (const struct session *s_, const char *name) +{ + struct session *s = CONST_CAST (struct session *, s_); + struct hmapx_node *node; + struct dataset *ds; + + HMAPX_FOR_EACH_WITH_HASH (ds, node, hash_case_string (name, 0), &s->datasets) + if (!strcasecmp (dataset_name (ds), name)) + return node; + + return NULL; +} diff --git a/src/data/session.h b/src/data/session.h new file mode 100644 index 00000000..f45cceb0 --- /dev/null +++ b/src/data/session.h @@ -0,0 +1,47 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2010, 2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef SESSION_H +#define SESSION_H 1 + +#include + +struct dataset; + +struct session *session_create (void); +void session_destroy (struct session *); + +struct dataset *session_active_dataset (struct session *); +void session_set_active_dataset (struct session *, struct dataset *); + +void session_add_dataset (struct session *, struct dataset *); +void session_remove_dataset (struct session *, struct dataset *); +struct dataset *session_lookup_dataset (const struct session *, const char *); +struct dataset *session_lookup_dataset_assert (const struct session *, + const char *); + +void session_set_default_syntax_encoding (struct session *, const char *); +const char *session_get_default_syntax_encoding (const struct session *); + +size_t session_n_datasets (const struct session *); +void session_for_each_dataset (const struct session *, + void (*cb) (struct dataset *, void *aux), + void *aux); + +struct dataset *session_get_dataset_by_seqno (const struct session *, + unsigned int seqno); + +#endif /* session.h */ diff --git a/src/language/command.c b/src/language/command.c index f5db9731..ff2b030c 100644 --- a/src/language/command.c +++ b/src/language/command.c @@ -26,6 +26,7 @@ #include "data/casereader.h" #include "data/dataset.h" #include "data/dictionary.h" +#include "data/session.h" #include "data/settings.h" #include "data/variable.h" #include "language/lexer/command-name.h" @@ -129,10 +130,12 @@ enum cmd_result cmd_parse_in_state (struct lexer *lexer, struct dataset *ds, enum cmd_state state) { + struct session *session = dataset_session (ds); int result; result = do_parse_command (lexer, ds, state); + ds = session_active_dataset (session); assert (!proc_is_open (ds)); unset_cmd_algorithm (); dict_clear_aux (dataset_dict (ds)); diff --git a/src/language/command.def b/src/language/command.def index 955a8ac5..016afcbb 100644 --- a/src/language/command.def +++ b/src/language/command.def @@ -18,6 +18,12 @@ DEF_CMD (S_ANY, F_ENHANCED, "CLOSE FILE HANDLE", cmd_close_file_handle) DEF_CMD (S_ANY, 0, "CACHE", cmd_cache) DEF_CMD (S_ANY, 0, "CD", cmd_cd) +DEF_CMD (S_ANY, 0, "DATASET ACTIVATE", cmd_dataset_activate) +DEF_CMD (S_ANY, 0, "DATASET DECLARE", cmd_dataset_declare) +DEF_CMD (S_ANY, 0, "DATASET CLOSE", cmd_dataset_close) +DEF_CMD (S_ANY, 0, "DATASET COPY", cmd_dataset_copy) +DEF_CMD (S_ANY, 0, "DATASET NAME", cmd_dataset_name) +DEF_CMD (S_ANY, 0, "DATASET DISPLAY", cmd_dataset_display) DEF_CMD (S_ANY, 0, "DO REPEAT", cmd_do_repeat) DEF_CMD (S_ANY, 0, "END REPEAT", cmd_end_repeat) DEF_CMD (S_ANY, 0, "ECHO", cmd_echo) @@ -172,12 +178,6 @@ UNIMPL_CMD ("CSSELECT", "Select complex samples") UNIMPL_CMD ("CSTABULATE", "Tabulate complex samples") UNIMPL_CMD ("CTABLES", "Display complex samples") UNIMPL_CMD ("CURVEFIT", "Fit curve to line plot") -UNIMPL_CMD ("DATASET ACTIVATE", "Switch to alternate data set") -UNIMPL_CMD ("DATASET CLOSE", "Delete alternate data set") -UNIMPL_CMD ("DATASET COPY", "Duplicate alternate data set") -UNIMPL_CMD ("DATASET DECLARE", "Start alternate data set") -UNIMPL_CMD ("DATASET DISPLAY", "List alternate data sets") -UNIMPL_CMD ("DATASET NAME", "Give the current data set a name") UNIMPL_CMD ("DATE", "Create time series data") UNIMPL_CMD ("DEFINE", "Syntax macros") UNIMPL_CMD ("DETECTANOMALY", "Find unusual cases") diff --git a/src/language/data-io/automake.mk b/src/language/data-io/automake.mk index 63c2c96a..0695bd5d 100644 --- a/src/language/data-io/automake.mk +++ b/src/language/data-io/automake.mk @@ -13,6 +13,7 @@ language_data_io_sources = \ src/language/data-io/data-reader.h \ src/language/data-io/data-writer.c \ src/language/data-io/data-writer.h \ + src/language/data-io/dataset.c \ src/language/data-io/file-handle.h \ src/language/data-io/get-data.c \ src/language/data-io/get.c \ diff --git a/src/language/data-io/combine-files.c b/src/language/data-io/combine-files.c index 5f82d151..e09b36ec 100644 --- a/src/language/data-io/combine-files.c +++ b/src/language/data-io/combine-files.c @@ -223,7 +223,7 @@ combine_files (enum comb_command_type command, } else { - file->handle = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH); + file->handle = fh_parse (lexer, FH_REF_FILE, dataset_session (ds)); if (file->handle == NULL) goto error; diff --git a/src/language/data-io/data-list.c b/src/language/data-io/data-list.c index 8ab75884..9beaea9c 100644 --- a/src/language/data-io/data-list.c +++ b/src/language/data-io/data-list.c @@ -101,7 +101,7 @@ cmd_data_list (struct lexer *lexer, struct dataset *ds) { lex_match (lexer, T_EQUALS); fh_unref (fh); - fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE); + fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE, NULL); if (fh == NULL) goto error; } diff --git a/src/language/data-io/dataset.c b/src/language/data-io/dataset.c new file mode 100644 index 00000000..dbf0d355 --- /dev/null +++ b/src/language/data-io/dataset.c @@ -0,0 +1,279 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2010, 2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "language/command.h" + +#include "data/dataset.h" +#include "data/session.h" +#include "language/lexer/lexer.h" +#include "libpspp/message.h" +#include "output/tab.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +static int +parse_window (struct lexer *lexer, unsigned int allowed, + enum dataset_display def) +{ + if (!lex_match_id (lexer, "WINDOW")) + return def; + lex_match (lexer, T_EQUALS); + + if (allowed & (1 << DATASET_MINIMIZED) && lex_match_id (lexer, "MINIMIZED")) + return DATASET_MINIMIZED; + else if (allowed & (1 << DATASET_ASIS) && lex_match_id (lexer, "ASIS")) + return DATASET_ASIS; + else if (allowed & (1 << DATASET_FRONT) && lex_match_id (lexer, "FRONT")) + return DATASET_FRONT; + else if (allowed & (1 << DATASET_HIDDEN) && lex_match_id (lexer, "HIDDEN")) + return DATASET_HIDDEN; + + lex_error (lexer, NULL); + return -1; +} + +static struct dataset * +parse_dataset_name (struct lexer *lexer, struct session *session) +{ + struct dataset *ds; + + if (!lex_force_id (lexer)) + return NULL; + + ds = session_lookup_dataset (session, lex_tokcstr (lexer)); + if (ds != NULL) + lex_get (lexer); + else + msg (SE, _("There is no dataset named %s."), lex_tokcstr (lexer)); + return ds; +} + +int +cmd_dataset_name (struct lexer *lexer, struct dataset *active) +{ + int display; + + if (!lex_force_id (lexer)) + return CMD_FAILURE; + dataset_set_name (active, lex_tokcstr (lexer)); + lex_get (lexer); + + display = parse_window (lexer, (1 << DATASET_ASIS) | (1 << DATASET_FRONT), + DATASET_ASIS); + if (display < 0) + return CMD_FAILURE; + else if (display != DATASET_ASIS) + dataset_set_display (active, display); + + return CMD_SUCCESS; +} + +int +cmd_dataset_activate (struct lexer *lexer, struct dataset *active) +{ + struct session *session = dataset_session (active); + struct dataset *ds; + int display; + + ds = parse_dataset_name (lexer, session); + if (ds == NULL) + return CMD_FAILURE; + + if (ds != active) + { + proc_execute (active); + session_set_active_dataset (session, ds); + if (dataset_name (active)[0] == '\0') + dataset_destroy (active); + return CMD_SUCCESS; + } + + display = parse_window (lexer, (1 << DATASET_ASIS) | (1 << DATASET_FRONT), + DATASET_ASIS); + if (display < 0) + return CMD_FAILURE; + else if (display != DATASET_ASIS) + dataset_set_display (ds, display); + + return CMD_SUCCESS; +} + +int +cmd_dataset_copy (struct lexer *lexer, struct dataset *old) +{ + struct session *session = dataset_session (old); + struct dataset *new; + int display; + char *name; + + /* Parse the entire command first. proc_execute() can attempt to parse + BEGIN DATA...END DATA and it will fail confusingly if we are in the + middle of the command at the point. */ + if (!lex_force_id (lexer)) + return CMD_FAILURE; + name = xstrdup (lex_tokcstr (lexer)); + lex_get (lexer); + + display = parse_window (lexer, ((1 << DATASET_MINIMIZED) + | (1 << DATASET_HIDDEN) + | (1 << DATASET_FRONT)), + DATASET_MINIMIZED); + if (display < 0) + { + free (name); + return CMD_FAILURE; + } + + if (session_lookup_dataset (session, name) == old) + { + new = old; + dataset_set_name (old, ""); + } + else + { + proc_execute (old); + new = dataset_clone (old, name); + } + dataset_set_display (new, display); + + free (name); + return CMD_SUCCESS; +} + +int +cmd_dataset_declare (struct lexer *lexer, struct dataset *ds) +{ + struct session *session = dataset_session (ds); + struct dataset *new; + int display; + + if (!lex_force_id (lexer)) + return CMD_FAILURE; + + new = session_lookup_dataset (session, lex_tokcstr (lexer)); + if (new == NULL) + new = dataset_create (session, lex_tokcstr (lexer)); + lex_get (lexer); + + display = parse_window (lexer, ((1 << DATASET_MINIMIZED) + | (1 << DATASET_HIDDEN) + | (1 << DATASET_FRONT)), + DATASET_MINIMIZED); + if (display < 0) + return CMD_FAILURE; + dataset_set_display (new, display); + + return CMD_SUCCESS; +} + +static void +dataset_close_cb (struct dataset *ds, void *session_) +{ + struct session *session = session_; + + if (ds != session_active_dataset (session)) + dataset_destroy (ds); +} + +int +cmd_dataset_close (struct lexer *lexer, struct dataset *ds) +{ + struct session *session = dataset_session (ds); + + if (lex_match (lexer, T_ALL)) + { + session_for_each_dataset (session, dataset_close_cb, session); + dataset_set_name (session_active_dataset (session), ""); + } + else + { + if (!lex_match (lexer, T_ASTERISK)) + { + ds = parse_dataset_name (lexer, session); + if (ds == NULL) + return CMD_FAILURE; + } + + if (ds == session_active_dataset (session)) + dataset_set_name (ds, ""); + else + dataset_destroy (ds); + } + + return CMD_SUCCESS; +} + +static void +dataset_display_cb (struct dataset *ds, void *p_) +{ + struct dataset ***p = p_; + **p = ds; + (*p)++; +} + +static int +sort_datasets (const void *a_, const void *b_) +{ + struct dataset *const *a = a_; + struct dataset *const *b = b_; + + return strcmp (dataset_name (*a), dataset_name (*b)); +} + +int +cmd_dataset_display (struct lexer *lexer UNUSED, struct dataset *ds) +{ + struct session *session = dataset_session (ds); + struct dataset **datasets, **p; + struct tab_table *t; + size_t i, n; + + n = session_n_datasets (session); + datasets = xmalloc (n * sizeof *datasets); + p = datasets; + session_for_each_dataset (session, dataset_display_cb, &p); + qsort (datasets, n, sizeof *datasets, sort_datasets); + + t = tab_create (1, n + 1); + tab_headers (t, 0, 0, 1, 0); + tab_box (t, TAL_1, TAL_1, -1, TAL_1, 0, 0, tab_nc (t) - 1, tab_nr (t) - 1); + tab_hline (t, TAL_2, 0, 0, 1); + tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("Dataset")); + for (i = 0; i < n; i++) + { + struct dataset *ds = datasets[i]; + const char *name; + + name = dataset_name (ds); + if (name[0] == '\0') + name = _("unnamed dataset"); + + if (ds == session_active_dataset (session)) + tab_text_format (t, 0, i + 1, TAB_LEFT, "%s %s", + name, _("(active dataset)")); + else + tab_text (t, 0, i + 1, TAB_LEFT, name); + } + tab_title (t, "Open datasets."); + tab_submit (t); + + free (datasets); + + return CMD_SUCCESS; +} diff --git a/src/language/data-io/file-handle.h b/src/language/data-io/file-handle.h index 2dfb5e16..e2a2b56e 100644 --- a/src/language/data-io/file-handle.h +++ b/src/language/data-io/file-handle.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997-9, 2000, 2011 Free Software Foundation, Inc. + Copyright (C) 1997-9, 2000, 2010, 2011 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,16 +14,19 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#if !file_handle_h -#define file_handle_h 1 +#ifndef LANGUAGE_DATA_IO_FILE_HANDLE_H +#define LANGUAGE_DATA_IO_FILE_HANDLE_H 1 -/* File handles. */ +/* Parsing file handles. */ #include #include #include "data/file-handle-def.h" -struct lexer ; -struct file_handle *fh_parse (struct lexer *, enum fh_referent); +struct lexer; +struct session; -#endif /* !file_handle.h */ +struct file_handle *fh_parse (struct lexer *, enum fh_referent, + struct session *); + +#endif /* language/data-io/file-handle.h */ diff --git a/src/language/data-io/file-handle.q b/src/language/data-io/file-handle.q index 786955bc..9bf6a6bf 100644 --- a/src/language/data-io/file-handle.q +++ b/src/language/data-io/file-handle.q @@ -21,6 +21,7 @@ #include #include "data/file-name.h" +#include "data/session.h" #include "language/command.h" #include "language/data-io/file-handle.h" #include "language/lexer/lexer.h" @@ -43,7 +44,7 @@ name=string; lrecl=integer; tabwidth=integer "x>=0" "%s must be nonnegative"; - mode=mode:!character/binary/image/360/scratch; + mode=mode:!character/binary/image/360; recform=recform:fixed/f/variable/v/spanned/vs. */ /* (declarations) */ @@ -52,6 +53,7 @@ int cmd_file_handle (struct lexer *lexer, struct dataset *ds) { + struct fh_properties properties; struct cmd_file_handle cmd; struct file_handle *handle; enum cmd_result result; @@ -81,71 +83,65 @@ cmd_file_handle (struct lexer *lexer, struct dataset *ds) if (lex_end_of_command (lexer) != CMD_SUCCESS) goto exit_free_cmd; - if (cmd.mode != FH_SCRATCH) + properties = *fh_default_properties (); + if (cmd.s_name == NULL) { - struct fh_properties properties = *fh_default_properties (); + lex_sbc_missing (lexer, "NAME"); + goto exit_free_cmd; + } - if (cmd.s_name == NULL) + switch (cmd.mode) + { + case FH_CHARACTER: + properties.mode = FH_MODE_TEXT; + if (cmd.sbc_tabwidth) + properties.tab_width = cmd.n_tabwidth[0]; + break; + case FH_IMAGE: + properties.mode = FH_MODE_FIXED; + break; + case FH_BINARY: + properties.mode = FH_MODE_VARIABLE; + break; + case FH_360: + properties.encoding = "EBCDIC-US"; + if (cmd.recform == FH_FIXED || cmd.recform == FH_F) + properties.mode = FH_MODE_FIXED; + else if (cmd.recform == FH_VARIABLE || cmd.recform == FH_V) { - lex_sbc_missing (lexer, "NAME"); - goto exit_free_cmd; + properties.mode = FH_MODE_360_VARIABLE; + properties.record_width = 8192; } - - switch (cmd.mode) + else if (cmd.recform == FH_SPANNED || cmd.recform == FH_VS) { - case FH_CHARACTER: - properties.mode = FH_MODE_TEXT; - if (cmd.sbc_tabwidth) - properties.tab_width = cmd.n_tabwidth[0]; - break; - case FH_IMAGE: - properties.mode = FH_MODE_FIXED; - break; - case FH_BINARY: - properties.mode = FH_MODE_VARIABLE; - break; - case FH_360: - properties.encoding = "EBCDIC-US"; - if (cmd.recform == FH_FIXED || cmd.recform == FH_F) - properties.mode = FH_MODE_FIXED; - else if (cmd.recform == FH_VARIABLE || cmd.recform == FH_V) - { - properties.mode = FH_MODE_360_VARIABLE; - properties.record_width = 8192; - } - else if (cmd.recform == FH_SPANNED || cmd.recform == FH_VS) - { - properties.mode = FH_MODE_360_SPANNED; - properties.record_width = 8192; - } - else - { - msg (SE, _("RECFORM must be specified with MODE=360.")); - goto exit_free_cmd; - } - break; - default: - NOT_REACHED (); + properties.mode = FH_MODE_360_SPANNED; + properties.record_width = 8192; } - - if (properties.mode == FH_MODE_FIXED || cmd.n_lrecl[0] != LONG_MIN) + else { - if (cmd.n_lrecl[0] == LONG_MIN) - msg (SE, _("The specified file mode requires LRECL. " - "Assuming %zu-character records."), - properties.record_width); - else if (cmd.n_lrecl[0] < 1 || cmd.n_lrecl[0] >= (1UL << 31)) - msg (SE, _("Record length (%ld) must be between 1 and %lu bytes. " - "Assuming %zu-character records."), - cmd.n_lrecl[0], (1UL << 31) - 1, properties.record_width); - else - properties.record_width = cmd.n_lrecl[0]; + msg (SE, _("RECFORM must be specified with MODE=360.")); + goto exit_free_cmd; } + break; + default: + NOT_REACHED (); + } - fh_create_file (handle_name, cmd.s_name, &properties); + if (properties.mode == FH_MODE_FIXED || cmd.n_lrecl[0] != LONG_MIN) + { + if (cmd.n_lrecl[0] == LONG_MIN) + msg (SE, _("The specified file mode requires LRECL. " + "Assuming %zu-character records."), + properties.record_width); + else if (cmd.n_lrecl[0] < 1 || cmd.n_lrecl[0] >= (1UL << 31)) + msg (SE, _("Record length (%ld) must be between 1 and %lu bytes. " + "Assuming %zu-character records."), + cmd.n_lrecl[0], (1UL << 31) - 1, properties.record_width); + else + properties.record_width = cmd.n_lrecl[0]; } - else - fh_create_scratch (handle_name); + + fh_create_file (handle_name, cmd.s_name, &properties); result = CMD_SUCCESS; @@ -182,25 +178,47 @@ referent_name (enum fh_referent referent) return _("file"); case FH_REF_INLINE: return _("inline file"); - case FH_REF_SCRATCH: - return _("scratch file"); + case FH_REF_DATASET: + return _("dataset"); default: NOT_REACHED (); } } -/* Parses a file handle name, which may be a file name as a string - or a file handle name as an identifier. The allowed types of - file handle are restricted to those in REFERENT_MASK. Returns - the file handle when successful, a null pointer on failure. +/* Parses a file handle name: - The caller is responsible for fh_unref()'ing the returned - file handle when it is no longer needed. */ + - If SESSION is nonnull, then the parsed syntax may be the name of a + dataset within SESSION. Dataset names take precedence over file handle + names. + + - If REFERENT_MASK includes FH_REF_FILE, the parsed syntax may be a file + name as a string or a file handle name as an identifier. + + - If REFERENT_MASK includes FH_REF_INLINE, the parsed syntax may be the + identifier INLINE to represent inline data. + + Returns the file handle when successful, a null pointer on failure. + + The caller is responsible for fh_unref()'ing the returned file handle when + it is no longer needed. */ struct file_handle * -fh_parse (struct lexer *lexer, enum fh_referent referent_mask) +fh_parse (struct lexer *lexer, enum fh_referent referent_mask, + struct session *session) { struct file_handle *handle; + if (session != NULL && lex_token (lexer) == T_ID) + { + struct dataset *ds; + + ds = session_lookup_dataset (session, lex_tokcstr (lexer)); + if (ds != NULL) + { + lex_get (lexer); + return fh_create_dataset (ds); + } + } + if (lex_match_id (lexer, "INLINE")) handle = fh_inline_file (); else @@ -215,14 +233,8 @@ fh_parse (struct lexer *lexer, enum fh_referent referent_mask) if (lex_token (lexer) == T_ID) handle = fh_from_id (lex_tokcstr (lexer)); if (handle == NULL) - { - if (lex_token (lexer) != T_ID || lex_tokcstr (lexer)[0] != '#' - || settings_get_syntax () != ENHANCED) handle = fh_create_file (NULL, lex_tokcstr (lexer), fh_default_properties ()); - else - handle = fh_create_scratch (lex_tokcstr (lexer)); - } lex_get (lexer); } diff --git a/src/language/data-io/get-data.c b/src/language/data-io/get-data.c index d7927527..e3e6c5d4 100644 --- a/src/language/data-io/get-data.c +++ b/src/language/data-io/get-data.c @@ -293,7 +293,7 @@ parse_get_txt (struct lexer *lexer, struct dataset *ds) if (!lex_force_match_id (lexer, "FILE")) goto error; lex_force_match (lexer, T_EQUALS); - fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE); + fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE, NULL); if (fh == NULL) goto error; diff --git a/src/language/data-io/get.c b/src/language/data-io/get.c index 466d2a52..0e542ef0 100644 --- a/src/language/data-io/get.c +++ b/src/language/data-io/get.c @@ -82,7 +82,7 @@ parse_read_command (struct lexer *lexer, struct dataset *ds, enum reader_command lex_match (lexer, T_EQUALS); fh_unref (fh); - fh = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH); + fh = fh_parse (lexer, FH_REF_FILE, NULL); if (fh == NULL) goto error; } diff --git a/src/language/data-io/inpt-pgm.c b/src/language/data-io/inpt-pgm.c index 99cc17a0..eac1f06f 100644 --- a/src/language/data-io/inpt-pgm.c +++ b/src/language/data-io/inpt-pgm.c @@ -290,7 +290,7 @@ cmd_reread (struct lexer *lexer, struct dataset *ds) { lex_match (lexer, T_EQUALS); fh_unref (fh); - fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE); + fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE, NULL); if (fh == NULL) { expr_free (e); diff --git a/src/language/data-io/print-space.c b/src/language/data-io/print-space.c index fd0d4cf7..b7245e62 100644 --- a/src/language/data-io/print-space.c +++ b/src/language/data-io/print-space.c @@ -56,7 +56,7 @@ cmd_print_space (struct lexer *lexer, struct dataset *ds) { lex_match (lexer, T_EQUALS); - handle = fh_parse (lexer, FH_REF_FILE); + handle = fh_parse (lexer, FH_REF_FILE, NULL); if (handle == NULL) return CMD_FAILURE; } diff --git a/src/language/data-io/print.c b/src/language/data-io/print.c index 169b6a5d..7b795f1a 100644 --- a/src/language/data-io/print.c +++ b/src/language/data-io/print.c @@ -156,7 +156,7 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds, { lex_match (lexer, T_EQUALS); - fh = fh_parse (lexer, FH_REF_FILE); + fh = fh_parse (lexer, FH_REF_FILE, NULL); if (fh == NULL) goto error; } diff --git a/src/language/data-io/save-translate.c b/src/language/data-io/save-translate.c index 17293cf6..f6487c57 100644 --- a/src/language/data-io/save-translate.c +++ b/src/language/data-io/save-translate.c @@ -96,7 +96,7 @@ cmd_save_translate (struct lexer *lexer, struct dataset *ds) lex_match (lexer, T_EQUALS); - handle = fh_parse (lexer, FH_REF_FILE); + handle = fh_parse (lexer, FH_REF_FILE, NULL); if (handle == NULL) goto error; } diff --git a/src/language/data-io/save.c b/src/language/data-io/save.c index 48645fc2..cf847361 100644 --- a/src/language/data-io/save.c +++ b/src/language/data-io/save.c @@ -196,7 +196,7 @@ parse_write_command (struct lexer *lexer, struct dataset *ds, lex_match (lexer, T_EQUALS); - handle = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH); + handle = fh_parse (lexer, FH_REF_FILE, NULL); if (handle == NULL) goto error; } diff --git a/src/language/dictionary/apply-dictionary.c b/src/language/dictionary/apply-dictionary.c index 78824cf8..c2de9318 100644 --- a/src/language/dictionary/apply-dictionary.c +++ b/src/language/dictionary/apply-dictionary.c @@ -50,7 +50,7 @@ cmd_apply_dictionary (struct lexer *lexer, struct dataset *ds) lex_match_id (lexer, "FROM"); lex_match (lexer, T_EQUALS); - handle = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH); + handle = fh_parse (lexer, FH_REF_FILE, dataset_session (ds)); if (!handle) return CMD_FAILURE; reader = any_reader_open (handle, &dict); diff --git a/src/language/dictionary/sys-file-info.c b/src/language/dictionary/sys-file-info.c index 3f00106e..27b27858 100644 --- a/src/language/dictionary/sys-file-info.c +++ b/src/language/dictionary/sys-file-info.c @@ -77,7 +77,7 @@ cmd_sysfile_info (struct lexer *lexer, struct dataset *ds UNUSED) lex_match_id (lexer, "FILE"); lex_match (lexer, T_EQUALS); - h = fh_parse (lexer, FH_REF_FILE); + h = fh_parse (lexer, FH_REF_FILE, NULL); if (!h) return CMD_FAILURE; diff --git a/src/language/expressions/evaluate.c b/src/language/expressions/evaluate.c index 56deb9a5..1736ab80 100644 --- a/src/language/expressions/evaluate.c +++ b/src/language/expressions/evaluate.c @@ -155,7 +155,7 @@ cmd_debug_evaluate (struct lexer *lexer, struct dataset *dsother UNUSED) if ( ds == NULL ) { - ds = dataset_create (); + ds = dataset_create (NULL, ""); d = dataset_dict (ds); } diff --git a/src/language/stats/aggregate.c b/src/language/stats/aggregate.c index 0dee9b44..fed76569 100644 --- a/src/language/stats/aggregate.c +++ b/src/language/stats/aggregate.c @@ -182,7 +182,7 @@ cmd_aggregate (struct lexer *lexer, struct dataset *ds) lex_match (lexer, T_EQUALS); if (!lex_match (lexer, T_ASTERISK)) { - out_file = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH); + out_file = fh_parse (lexer, FH_REF_FILE, dataset_session (ds)); if (out_file == NULL) goto error; } diff --git a/src/language/utilities/include.c b/src/language/utilities/include.c index 81a064d1..bcee162c 100644 --- a/src/language/utilities/include.c +++ b/src/language/utilities/include.c @@ -24,6 +24,7 @@ #include "data/dataset.h" #include "data/file-name.h" +#include "data/session.h" #include "language/command.h" #include "language/lexer/include-path.h" #include "language/lexer/lexer.h" @@ -81,7 +82,8 @@ do_insert (struct lexer *lexer, struct dataset *ds, enum variant variant) error_mode = LEX_ERROR_CONTINUE; cd = false; status = CMD_FAILURE; - encoding = xstrdup (dataset_get_default_syntax_encoding (ds)); + encoding = xstrdup (session_get_default_syntax_encoding ( + dataset_session (ds))); while ( T_ENDCMD != lex_token (lexer)) { if (lex_match_id (lexer, "ENCODING")) diff --git a/src/ui/gui/data-editor.ui b/src/ui/gui/data-editor.ui index 6a37b023..498f3fce 100644 --- a/src/ui/gui/data-editor.ui +++ b/src/ui/gui/data-editor.ui @@ -54,6 +54,12 @@ gtk-convert + + + rename_dataset + _Rename Dataset... + + gtk-save @@ -480,6 +486,7 @@ + diff --git a/src/ui/gui/executor.c b/src/ui/gui/executor.c index 6a3f1e15..8fb4c260 100644 --- a/src/ui/gui/executor.c +++ b/src/ui/gui/executor.c @@ -20,6 +20,7 @@ #include "data/dataset.h" #include "data/lazy-casereader.h" +#include "data/session.h" #include "language/command.h" #include "language/lexer/lexer.h" #include "libpspp/cast.h" @@ -35,38 +36,80 @@ create_casereader_from_data_store (void *data_store_) return psppire_data_store_get_reader (data_store); } +static void +new_pdw_cb (struct dataset *ds, void *aux UNUSED) +{ + PsppireDataWindow *pdw = psppire_data_window_for_dataset (ds); + if (pdw == NULL) + pdw = PSPPIRE_DATA_WINDOW (psppire_data_window_new (ds)); + + switch (dataset_get_display (ds)) + { + case DATASET_ASIS: + break; + + case DATASET_FRONT: + gtk_widget_show (GTK_WIDGET (pdw)); + gtk_window_deiconify (GTK_WINDOW (pdw)); + gdk_window_raise (gtk_widget_get_window (GTK_WIDGET (pdw))); + psppire_data_window_set_default (pdw); + break; + + case DATASET_MINIMIZED: + gtk_window_iconify (GTK_WINDOW (pdw)); + gtk_widget_show (GTK_WIDGET (pdw)); + psppire_data_window_undefault (pdw); + break; + + case DATASET_HIDDEN: + gtk_widget_hide (GTK_WIDGET (pdw)); + psppire_data_window_undefault (pdw); + break; + } + dataset_set_display (ds, DATASET_ASIS); +} + gboolean execute_syntax (PsppireDataWindow *window, struct lex_reader *lex_reader) { struct lexer *lexer; gboolean retval = TRUE; - struct casereader *reader; - const struct caseproto *proto; - casenumber case_cnt; - unsigned long int lazy_serial; - - /* When the user executes a number of snippets of syntax in a - row, none of which read from the active dataset, the GUI becomes - progressively less responsive. The reason is that each syntax - execution encapsulates the active dataset data in another - datasheet layer. The cumulative effect of having a number of - layers of datasheets wastes time and space. - - To solve the problem, we use a "lazy casereader", a wrapper - around the casereader obtained from the data store, that - only actually instantiates that casereader when it is - needed. If the data store casereader is never needed, then - it is reused the next time syntax is run, without wrapping - it in another layer. */ - proto = psppire_data_store_get_proto (window->data_store); - case_cnt = psppire_data_store_get_case_count (window->data_store); - reader = lazy_casereader_create (proto, case_cnt, - create_casereader_from_data_store, - window->data_store, &lazy_serial); - dataset_set_source (window->dataset, reader); - - g_return_val_if_fail (dataset_has_source (window->dataset), FALSE); + PsppireDataWindow *pdw, *next_pdw; + + ll_for_each (pdw, PsppireDataWindow, ll, &all_data_windows) + { + const struct caseproto *proto; + struct casereader *reader; + casenumber case_cnt; + + /* When the user executes a number of snippets of syntax in a + row, none of which read from the active dataset, the GUI becomes + progressively less responsive. The reason is that each syntax + execution encapsulates the active dataset data in another + datasheet layer. The cumulative effect of having a number of + layers of datasheets wastes time and space. + + To solve the problem, we use a "lazy casereader", a wrapper + around the casereader obtained from the data store, that + only actually instantiates that casereader when it is + needed. If the data store casereader is never needed, then + it is reused the next time syntax is run, without wrapping + it in another layer. */ + proto = psppire_data_store_get_proto (pdw->data_store); + case_cnt = psppire_data_store_get_case_count (pdw->data_store); + reader = lazy_casereader_create (proto, case_cnt, + create_casereader_from_data_store, + pdw->data_store, &pdw->lazy_serial); + dataset_set_source (pdw->dataset, reader); + + if (pdw == window) + session_set_active_dataset (the_session, pdw->dataset); + + g_return_val_if_fail (dataset_has_source (pdw->dataset), FALSE); + + pdw->dataset_seqno = dataset_seqno (pdw->dataset); + } lexer = lex_create (); psppire_set_lexer (lexer); @@ -74,7 +117,8 @@ execute_syntax (PsppireDataWindow *window, struct lex_reader *lex_reader) for (;;) { - enum cmd_result result = cmd_parse (lexer, window->dataset); + struct dataset *ds = session_active_dataset (the_session); + enum cmd_result result = cmd_parse (lexer, ds); if ( cmd_result_is_failure (result)) { @@ -87,14 +131,33 @@ execute_syntax (PsppireDataWindow *window, struct lex_reader *lex_reader) break; } - proc_execute (window->dataset); + ll_for_each_safe (pdw, next_pdw, PsppireDataWindow, ll, &all_data_windows) + { + struct dataset *ds; + + ds = session_get_dataset_by_seqno (the_session, pdw->dataset_seqno); + if (ds != NULL) + { + struct casereader *reader; + + pdw->dataset = ds; + proc_execute (pdw->dataset); - psppire_dict_replace_dictionary (window->data_store->dict, - dataset_dict (window->dataset)); + psppire_dict_replace_dictionary (pdw->data_store->dict, + dataset_dict (pdw->dataset)); + + reader = dataset_steal_source (pdw->dataset); + if (!lazy_casereader_destroy (reader, pdw->lazy_serial)) + psppire_data_store_set_reader (pdw->data_store, reader); + + g_object_set (G_OBJECT (pdw), "id", dataset_name (pdw->dataset), + (void *) NULL); + } + else + gtk_widget_destroy (GTK_WIDGET (pdw)); + } - reader = dataset_steal_source (window->dataset); - if (!lazy_casereader_destroy (reader, lazy_serial)) - psppire_data_store_set_reader (window->data_store, reader); + session_for_each_dataset (the_session, new_pdw_cb, NULL); /* Destroy the lexer only after obtaining the dataset, because the dataset might depend on the lexer, if the casereader specifies inline data. (In diff --git a/src/ui/gui/psppire-data-window.c b/src/ui/gui/psppire-data-window.c index a463ee43..ef06d83a 100644 --- a/src/ui/gui/psppire-data-window.c +++ b/src/ui/gui/psppire-data-window.c @@ -20,6 +20,7 @@ #include #include "data/dataset.h" +#include "data/session.h" #include "language/lexer/lexer.h" #include "libpspp/message.h" #include "ui/gui/aggregate-dialog.h" @@ -30,6 +31,7 @@ #include "ui/gui/correlation-dialog.h" #include "ui/gui/crosstabs-dialog.h" #include "ui/gui/descriptives-dialog.h" +#include "ui/gui/entry-dialog.h" #include "ui/gui/examine-dialog.h" #include "ui/gui/executor.h" #include "ui/gui/factor-dialog.h" @@ -61,11 +63,14 @@ #include "ui/gui/weight-cases-dialog.h" #include "ui/syntax-gen.h" +#include "gl/xvasprintf.h" + #include #define _(msgid) gettext (msgid) #define N_(msgid) msgid -static PsppireDataWindow *the_data_window; +struct session *the_session; +struct ll_list all_data_windows = LL_INITIALIZER (all_data_windows); static void psppire_data_window_class_init (PsppireDataWindowClass *class); static void psppire_data_window_init (PsppireDataWindow *data_editor); @@ -466,10 +471,11 @@ sysfile_info (PsppireDataWindow *de) } -/* Callback for data_save_as action. Prompt for a filename and save */ +/* PsppireWindow 'pick_filename' callback: prompt for a filename to save as. */ static void -data_save_as_dialog (PsppireDataWindow *de) +data_pick_filename (PsppireWindow *window) { + PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (window); GtkWidget *button_sys; GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Save"), @@ -515,6 +521,9 @@ data_save_as_dialog (PsppireDataWindow *de) gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), vbox); } + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), + TRUE); + switch (gtk_dialog_run (GTK_DIALOG (dialog))) { case GTK_RESPONSE_ACCEPT: @@ -538,8 +547,6 @@ data_save_as_dialog (PsppireDataWindow *de) psppire_window_set_filename (PSPPIRE_WINDOW (de), filename->str); - save_file (PSPPIRE_WINDOW (de)); - g_string_free (filename, TRUE); } break; @@ -550,32 +557,68 @@ data_save_as_dialog (PsppireDataWindow *de) gtk_widget_destroy (dialog); } - -/* Callback for data_save action. - */ -static void -data_save (PsppireWindow *de) +static bool +confirm_delete_dataset (PsppireDataWindow *de, + const char *old_dataset, + const char *new_dataset, + const char *existing_dataset) { - const gchar *fn = psppire_window_get_filename (de); + GtkWidget *dialog; + int result; - if ( NULL != fn) - psppire_window_save (de); - else - data_save_as_dialog (PSPPIRE_DATA_WINDOW (de)); -} + dialog = gtk_message_dialog_new ( + GTK_WINDOW (de), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", + _("Delete Existing Dataset?")); + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), + _("Renaming \"%s\" to \"%s\" will delete destroy the existing " + "dataset named \"%s\". Are you sure that you want to do this?"), + old_dataset, new_dataset, existing_dataset); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_DELETE, GTK_RESPONSE_OK, + NULL); + + g_object_set (dialog, "icon-name", "psppicon", NULL); + + result = gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + return result == GTK_RESPONSE_OK; +} -/* Callback for data_new action. - Performs the NEW FILE command */ static void -new_file (PsppireDataWindow *de) +on_rename_dataset (PsppireDataWindow *de) { - execute_const_syntax_string (de, "NEW FILE."); - psppire_window_set_filename (PSPPIRE_WINDOW (de), NULL); + struct dataset *ds = de->dataset; + struct session *session = dataset_session (ds); + const char *old_name = dataset_name (ds); + struct dataset *existing_dataset; + char *new_name; + char *prompt; + + prompt = xasprintf (_("Please enter a new name for dataset \"%s\":"), + old_name); + new_name = entry_dialog_run (GTK_WINDOW (de), _("Rename Dataset"), prompt, + old_name); + free (prompt); + + if (new_name == NULL) + return; + + existing_dataset = session_lookup_dataset (session, new_name); + if (existing_dataset == NULL || existing_dataset == ds + || confirm_delete_dataset (de, old_name, new_name, + dataset_name (existing_dataset))) + g_free (execute_syntax_string (de, g_strdup_printf ("DATASET NAME %s.", + new_name))); + + free (new_name); } - - static void on_edit_paste (PsppireDataWindow *de) { @@ -688,8 +731,6 @@ file_quit (PsppireDataWindow *de) /* FIXME: Need to be more intelligent here. Give the user the opportunity to save any unsaved data. */ - g_object_unref (de->data_store); - psppire_quit (); } @@ -919,15 +960,17 @@ psppire_data_window_finish_init (PsppireDataWindow *de, connect_action (de, "edit_cut", G_CALLBACK (on_edit_cut)); - connect_action (de, "file_new_data", G_CALLBACK (new_file)); + connect_action (de, "file_new_data", G_CALLBACK (create_data_window)); connect_action (de, "file_import-text", G_CALLBACK (text_data_import_assistant)); - connect_action (de, "file_save", G_CALLBACK (data_save)); + connect_action (de, "file_save", G_CALLBACK (psppire_window_save)); connect_action (de, "file_open", G_CALLBACK (psppire_window_open)); - connect_action (de, "file_save_as", G_CALLBACK (data_save_as_dialog)); + connect_action (de, "file_save_as", G_CALLBACK (psppire_window_save_as)); + + connect_action (de, "rename_dataset", G_CALLBACK (on_rename_dataset)); connect_action (de, "file_information_working-file", G_CALLBACK (display_dict)); @@ -1145,7 +1188,7 @@ psppire_data_window_finish_init (PsppireDataWindow *de, gtk_widget_show (GTK_WIDGET (de->data_editor)); gtk_widget_show (box); - the_data_window = de; + ll_push_head (&all_data_windows, &de->ll); } static void @@ -1159,10 +1202,26 @@ psppire_data_window_dispose (GObject *object) dw->builder = NULL; } - if (the_data_window == dw) - the_data_window = NULL; + if (dw->var_store) + { + g_object_unref (dw->var_store); + dw->var_store = NULL; + } - G_OBJECT_CLASS (parent_class)->dispose (object); + if (dw->data_store) + { + g_object_unref (dw->data_store); + dw->data_store = NULL; + } + + if (dw->ll.next != NULL) + { + ll_remove (&dw->ll); + dw->ll.next = NULL; + } + + if (G_OBJECT_CLASS (parent_class)->dispose) + G_OBJECT_CLASS (parent_class)->dispose (object); } static void @@ -1203,33 +1262,89 @@ psppire_data_window_get_property (GObject *object, }; } - GtkWidget* psppire_data_window_new (struct dataset *ds) { - return GTK_WIDGET ( + GtkWidget *dw; + + if (the_session == NULL) + the_session = session_create (); + + if (ds == NULL) + { + static int n_datasets; + char *dataset_name; + + dataset_name = xasprintf ("DataSet%d", ++n_datasets); + ds = dataset_create (the_session, dataset_name); + free (dataset_name); + } + assert (dataset_session (ds) == the_session); + + dw = GTK_WIDGET ( g_object_new ( psppire_data_window_get_type (), /* TRANSLATORS: This will form a filename. Please avoid whitespace. */ - "filename", _("PSPP-data"), "description", _("Data Editor"), "dataset", ds, NULL)); + + if (dataset_name (ds) != NULL) + g_object_set (dw, "id", dataset_name (ds), (void *) NULL); + + return dw; } +bool +psppire_data_window_is_empty (PsppireDataWindow *dw) +{ + return psppire_var_store_get_var_cnt (dw->var_store) == 0; +} static void psppire_data_window_iface_init (PsppireWindowIface *iface) { iface->save = save_file; + iface->pick_filename = data_pick_filename; iface->load = load_file; } - PsppireDataWindow * psppire_default_data_window (void) { - if (the_data_window == NULL) - gtk_widget_show (psppire_data_window_new (dataset_create ())); - return the_data_window; + if (ll_is_empty (&all_data_windows)) + create_data_window (); + return ll_data (ll_head (&all_data_windows), PsppireDataWindow, ll); +} + +void +psppire_data_window_set_default (PsppireDataWindow *pdw) +{ + ll_remove (&pdw->ll); + ll_push_head (&all_data_windows, &pdw->ll); +} + +void +psppire_data_window_undefault (PsppireDataWindow *pdw) +{ + ll_remove (&pdw->ll); + ll_push_tail (&all_data_windows, &pdw->ll); +} + +PsppireDataWindow * +psppire_data_window_for_dataset (struct dataset *ds) +{ + PsppireDataWindow *pdw; + + ll_for_each (pdw, PsppireDataWindow, ll, &all_data_windows) + if (pdw->dataset == ds) + return pdw; + + return NULL; +} + +void +create_data_window (void) +{ + gtk_widget_show (psppire_data_window_new (NULL)); } diff --git a/src/ui/gui/psppire-data-window.h b/src/ui/gui/psppire-data-window.h index 01002543..64fe076d 100644 --- a/src/ui/gui/psppire-data-window.h +++ b/src/ui/gui/psppire-data-window.h @@ -1,5 +1,5 @@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008, 2010 Free Software Foundation + Copyright (C) 2008, 2010, 2011 Free Software Foundation This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,11 +23,10 @@ #include #include +#include "libpspp/ll.h" #include "ui/gui/psppire-window.h" #include "ui/gui/psppire-data-editor.h" -struct dataset; - G_BEGIN_DECLS #define PSPPIRE_DATA_WINDOW_TYPE (psppire_data_window_get_type ()) @@ -65,6 +64,10 @@ struct _PsppireDataWindow gboolean save_as_portable; + + struct ll ll; /* In global 'all_data_windows' list. */ + unsigned long int lazy_serial; + unsigned int dataset_seqno; }; struct _PsppireDataWindowClass @@ -72,9 +75,20 @@ struct _PsppireDataWindowClass PsppireWindowClass parent_class; }; +extern struct session *the_session; +extern struct ll_list all_data_windows; + GType psppire_data_window_get_type (void); GtkWidget* psppire_data_window_new (struct dataset *); + PsppireDataWindow *psppire_default_data_window (void); +void psppire_data_window_set_default (PsppireDataWindow *); +void psppire_data_window_undefault (PsppireDataWindow *); + +PsppireDataWindow *psppire_data_window_for_dataset (struct dataset *); + +bool psppire_data_window_is_empty (PsppireDataWindow *); +void create_data_window (void); G_END_DECLS diff --git a/src/ui/gui/psppire-syntax-window.c b/src/ui/gui/psppire-syntax-window.c index 2e90ba76..a61e56e5 100644 --- a/src/ui/gui/psppire-syntax-window.c +++ b/src/ui/gui/psppire-syntax-window.c @@ -483,9 +483,9 @@ save_editor_to_file (PsppireSyntaxWindow *se, } -/* Callback for the File->SaveAs menuitem */ +/* PsppireWindow 'pick_Filename' callback. */ static void -syntax_save_as (PsppireWindow *se) +syntax_pick_filename (PsppireWindow *se) { GtkFileFilter *filter; gint response; @@ -515,16 +515,9 @@ syntax_save_as (PsppireWindow *se) if ( response == GTK_RESPONSE_ACCEPT ) { - GError *err = NULL; char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog) ); - - if ( ! save_editor_to_file (PSPPIRE_SYNTAX_WINDOW (se), filename, &err) ) - { - msg ( ME, "%s", err->message ); - g_error_free (err); - } - + psppire_window_set_filename (se, filename); free (filename); } @@ -532,23 +525,17 @@ syntax_save_as (PsppireWindow *se) } -/* Callback for the File->Save menuitem */ +/* PsppireWindow 'save' callback. */ static void syntax_save (PsppireWindow *se) { const gchar *filename = psppire_window_get_filename (se); - - if ( filename == NULL ) - syntax_save_as (se); - else + GError *err = NULL; + save_editor_to_file (PSPPIRE_SYNTAX_WINDOW (se), filename, &err); + if ( err ) { - GError *err = NULL; - save_editor_to_file (PSPPIRE_SYNTAX_WINDOW (se), filename, &err); - if ( err ) - { - msg (ME, "%s", err->message); - g_error_free (err); - } + msg (ME, "%s", err->message); + g_error_free (err); } } @@ -652,12 +639,10 @@ psppire_syntax_window_init (PsppireSyntaxWindow *window) g_signal_connect_swapped (get_action_assert (xml,"file_new_syntax"), "activate", G_CALLBACK (create_syntax_window), NULL); -#if 0 g_signal_connect (get_action_assert (xml,"file_new_data"), "activate", G_CALLBACK (create_data_window), window); -#endif g_signal_connect_swapped (get_action_assert (xml, "file_open"), "activate", @@ -666,12 +651,12 @@ psppire_syntax_window_init (PsppireSyntaxWindow *window) g_signal_connect_swapped (get_action_assert (xml, "file_save"), "activate", - G_CALLBACK (syntax_save), + G_CALLBACK (psppire_window_save), window); g_signal_connect_swapped (get_action_assert (xml, "file_save_as"), "activate", - G_CALLBACK (syntax_save_as), + G_CALLBACK (psppire_window_save_as), window); g_signal_connect (get_action_assert (xml,"file_quit"), @@ -745,7 +730,6 @@ psppire_syntax_window_new (void) { return GTK_WIDGET (g_object_new (psppire_syntax_window_get_type (), /* TRANSLATORS: This will form a filename. Please avoid whitespace. */ - "filename", _("Syntax"), "description", _("Syntax Editor"), NULL)); } @@ -825,6 +809,7 @@ static void psppire_syntax_window_iface_init (PsppireWindowIface *iface) { iface->save = syntax_save; + iface->pick_filename = syntax_pick_filename; iface->load = syntax_load; } diff --git a/src/ui/gui/psppire-window.c b/src/ui/gui/psppire-window.c index 332abe37..399b020b 100644 --- a/src/ui/gui/psppire-window.c +++ b/src/ui/gui/psppire-window.c @@ -79,7 +79,8 @@ enum { PROP_0, PROP_FILENAME, - PROP_DESCRIPTION + PROP_DESCRIPTION, + PROP_ID }; @@ -94,18 +95,82 @@ psppire_window_set_title (PsppireWindow *window) { GString *title = g_string_sized_new (80); - g_string_printf (title, "%s ", window->basename ? window->basename : ""); - g_string_append_unichar (title, 0x2014); /* em dash */ - g_string_printf (title, " PSPPIRE %s", window->description); - if (window->dirty) - g_string_prepend_c (title, '*'); + g_string_append_c (title, '*'); + + if (window->basename || window->id) + { + if (window->basename) + g_string_append_printf (title, "%s ", window->basename); + + if (window->id != '\0') + g_string_append_printf (title, "[%s] ", window->id); + + g_string_append_unichar (title, 0x2014); /* em dash */ + g_string_append_c (title, ' '); /* em dash */ + } + + g_string_append_printf (title, "PSPPIRE %s", window->description); gtk_window_set_title (GTK_WINDOW (window), title->str); g_string_free (title, TRUE); } +static void +psppire_window_update_list_name (PsppireWindow *window) +{ + PsppireWindowRegister *reg = psppire_window_register_new (); + GString *candidate = g_string_sized_new (80); + int n; + + n = 1; + do + { + /* Compose a name. */ + g_string_truncate (candidate, 0); + if (window->filename) + { + gchar *display_filename = g_filename_display_name (window->filename); + g_string_append (candidate, display_filename); + g_free (display_filename); + + if (window->id) + g_string_append_printf (candidate, " [%s]", window->id); + } + else if (window->id) + g_string_append_printf (candidate, "[%s]", window->id); + else + g_string_append (candidate, window->description); + + if (n++ > 1) + g_string_append_printf (candidate, " #%d", n); + + if (window->list_name && !strcmp (candidate->str, window->list_name)) + { + /* Keep the existing name. */ + g_string_free (candidate, TRUE); + return; + } + } + while (psppire_window_register_lookup (reg, candidate->str)); + + if (window->list_name) + psppire_window_register_remove (reg, window->list_name); + + g_free (window->list_name); + window->list_name = g_string_free (candidate, FALSE); + + psppire_window_register_insert (reg, window, window->list_name); +} + +static void +psppire_window_name_changed (PsppireWindow *window) +{ + psppire_window_set_title (window); + psppire_window_update_list_name (window); +} + static void psppire_window_set_property (GObject *object, guint prop_id, @@ -117,50 +182,24 @@ psppire_window_set_property (GObject *object, switch (prop_id) { case PROP_DESCRIPTION: + g_free (window->description); window->description = g_value_dup_string (value); psppire_window_set_title (window); break; case PROP_FILENAME: - { - PsppireWindowRegister *reg = psppire_window_register_new (); - - gchar *candidate_name ; - - { - const gchar *name = g_value_get_string (value); - int x = 0; - GValue def = {0}; - g_value_init (&def, pspec->value_type); - - if ( NULL == name) - { - g_param_value_set_default (pspec, &def); - name = g_value_get_string (&def); - } - - candidate_name = xstrdup (name); - - while ( psppire_window_register_lookup (reg, candidate_name)) - { - free (candidate_name); - candidate_name = uniquify (name, &x); - } - - window->basename = g_filename_display_basename (candidate_name); - - g_value_unset (&def); - } - - psppire_window_set_title (window); - - if ( window->name) - psppire_window_register_remove (reg, window->name); - - free (window->name); - window->name = candidate_name; - - psppire_window_register_insert (reg, window, window->name); - } + g_free (window->filename); + window->filename = g_value_dup_string (value); + g_free (window->basename); + window->basename = (window->filename + ? g_filename_display_basename (window->filename) + : NULL); + psppire_window_name_changed (window); + break; + break; + case PROP_ID: + g_free (window->id); + window->id = g_value_dup_string (value); + psppire_window_name_changed (window); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -180,11 +219,14 @@ psppire_window_get_property (GObject *object, switch (prop_id) { case PROP_FILENAME: - g_value_set_string (value, window->name); + g_value_set_string (value, window->filename); break; case PROP_DESCRIPTION: g_value_set_string (value, window->description); break; + case PROP_ID: + g_value_set_string (value, window->id); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -210,9 +252,12 @@ psppire_window_finalize (GObject *object) PsppireWindowRegister *reg = psppire_window_register_new (); - psppire_window_register_remove (reg, window->name); - free (window->name); - free (window->description); + psppire_window_register_remove (reg, window->list_name); + g_free (window->filename); + g_free (window->basename); + g_free (window->id); + g_free (window->description); + g_free (window->list_name); g_signal_handler_disconnect (psppire_window_register_new (), window->remove_handler); @@ -226,6 +271,17 @@ psppire_window_finalize (GObject *object) G_OBJECT_CLASS (parent_class)->finalize (object); } +static GParamSpec * +null_if_empty_param (const gchar *name, const gchar *nick, + const gchar *blurb, const gchar *default_value, + GParamFlags flags) +{ + GParamSpec *param; + + param = g_param_spec_string (name, nick, blurb, default_value, flags); + ((GParamSpecString *) param)->null_fold_if_empty = TRUE; + return param; +} static void psppire_window_class_init (PsppireWindowClass *class) @@ -233,18 +289,25 @@ psppire_window_class_init (PsppireWindowClass *class) GObjectClass *object_class = G_OBJECT_CLASS (class); GParamSpec *description_spec = - g_param_spec_string ("description", + null_if_empty_param ("description", "Description", "A string describing the usage of the window", - "??????", /*Should be overridden by derived classes */ + NULL, /*Should be overridden by derived classes */ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); GParamSpec *filename_spec = - g_param_spec_string ("filename", + null_if_empty_param ("filename", "File name", "The name of the file associated with this window, if any", - /* TRANSLATORS: This will form a filename. Please avoid whitespace. */ - _("Untitled"), + NULL, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE); + + GParamSpec *id_spec = + null_if_empty_param ("id", + "Identifier", + "The PSPP language identifier for the data associated " + "with this window (e.g. dataset name)", + NULL, G_PARAM_CONSTRUCT | G_PARAM_READWRITE); object_class->set_property = psppire_window_set_property; @@ -258,6 +321,10 @@ psppire_window_class_init (PsppireWindowClass *class) PROP_FILENAME, filename_spec); + g_object_class_install_property (object_class, + PROP_ID, + id_spec); + parent_class = g_type_class_peek_parent (class); } @@ -398,6 +465,11 @@ on_delete (PsppireWindow *w, GdkEvent *event, gpointer user_data) break; case GTK_RESPONSE_APPLY: psppire_window_save (w); + if (w->dirty) + { + /* Save failed, or user exited Save As dialog with Cancel. */ + return TRUE; + } break; case GTK_RESPONSE_REJECT: break; @@ -414,9 +486,12 @@ on_delete (PsppireWindow *w, GdkEvent *event, gpointer user_data) static void psppire_window_init (PsppireWindow *window) { - window->name = NULL; window->menu = NULL; - window->description = xstrdup (""); + window->filename = NULL; + window->basename = NULL; + window->id = NULL; + window->description = NULL; + window->list_name = NULL; window->menuitem_table = g_hash_table_new (g_str_hash, g_str_equal); @@ -450,33 +525,30 @@ psppire_window_init (PsppireWindow *window) gint psppire_window_query_save (PsppireWindow *se) { - gchar *fn; gint response; GtkWidget *dialog; GtkWidget *cancel_button; - const gchar *description; - const gchar *filename = psppire_window_get_filename (se); + gchar *description; GTimeVal time; g_get_current_time (&time); - g_object_get (se, "description", &description, NULL); - - g_return_val_if_fail (filename != NULL, GTK_RESPONSE_NONE); - - - fn = g_filename_display_basename (filename); - + if (se->filename) + description = g_filename_display_basename (se->filename); + else if (se->id) + description = g_strdup (se->id); + else + description = g_strdup (se->description); dialog = gtk_message_dialog_new (GTK_WINDOW (se), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("Save the changes to `%s' before closing?"), - fn); - g_free (fn); + description); + g_free (description); g_object_set (dialog, "icon-name", "psppicon", NULL); @@ -506,7 +578,7 @@ psppire_window_query_save (PsppireWindow *se) } - +/* The return value is encoded in the glib filename encoding. */ const gchar * psppire_window_get_filename (PsppireWindow *w) { @@ -514,6 +586,7 @@ psppire_window_get_filename (PsppireWindow *w) } +/* FILENAME must be encoded in the glib filename encoding. */ void psppire_window_set_filename (PsppireWindow *w, const gchar *filename) { @@ -595,16 +668,39 @@ psppire_window_save (PsppireWindow *w) { PsppireWindowIface *i = PSPPIRE_WINDOW_MODEL_GET_IFACE (w); - g_assert (PSPPIRE_IS_WINDOW_MODEL (w)); - g_assert (i); - g_return_if_fail (i->save); - i->save (w); + if (w->filename == NULL) + psppire_window_save_as (w); + else + { + i->save (w); + w->dirty = FALSE; + psppire_window_set_title (w); + } +} - w->dirty = FALSE; - psppire_window_set_title (w); +void +psppire_window_save_as (PsppireWindow *w) +{ + PsppireWindowIface *i = PSPPIRE_WINDOW_MODEL_GET_IFACE (w); + gchar *old_filename; + + g_assert (i); + g_return_if_fail (i->pick_filename); + + old_filename = w->filename; + w->filename = NULL; + + i->pick_filename (w); + if (w->filename == NULL) + w->filename = old_filename; + else + { + g_free (old_filename); + psppire_window_save (w); + } } extern GtkRecentManager *the_recent_mgr; @@ -686,26 +782,26 @@ psppire_window_file_chooser_dialog (PsppireWindow *toplevel) gtk_file_filter_add_pattern (filter, "*"); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); - { - gchar *dir_name; - gchar *filename = NULL; - g_object_get (toplevel, "filename", &filename, NULL); - - if ( ! g_path_is_absolute (filename)) - { - gchar *path = - g_build_filename (g_get_current_dir (), filename, NULL); - dir_name = g_path_get_dirname (path); - g_free (path); - } - else - { - dir_name = g_path_get_dirname (filename); - } - gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), - dir_name); - free (dir_name); - } + if (toplevel->filename) + { + const gchar *filename = toplevel->filename; + gchar *dir_name; + + if ( ! g_path_is_absolute (filename)) + { + gchar *path = + g_build_filename (g_get_current_dir (), filename, NULL); + dir_name = g_path_get_dirname (path); + g_free (path); + } + else + { + dir_name = g_path_get_dirname (filename); + } + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), + dir_name); + free (dir_name); + } return dialog; } diff --git a/src/ui/gui/psppire-window.h b/src/ui/gui/psppire-window.h index f65e9e3c..0aa913c1 100644 --- a/src/ui/gui/psppire-window.h +++ b/src/ui/gui/psppire-window.h @@ -60,9 +60,11 @@ struct _PsppireWindow GtkWindow parent; /* */ - gchar *name; - gchar *description; - gchar *basename; + gchar *filename; /* File name, in file name encoding, or NULL. */ + gchar *basename; /* Last component of filename, in UTF-8 */ + gchar *id; /* Dataset name, or NULL. */ + gchar *description; /* e.g. "Data Editor" */ + gchar *list_name; /* Name for "Windows" menu list. */ GHashTable *menuitem_table; GtkMenuShell *menu; @@ -86,6 +88,7 @@ struct _PsppireWindowIface GTypeInterface g_iface; void (*save) (PsppireWindow *w); + void (*pick_filename) (PsppireWindow *); gboolean (*load) (PsppireWindow *w, const gchar *); }; @@ -106,6 +109,7 @@ gboolean psppire_window_get_unsaved (PsppireWindow *); gint psppire_window_query_save (PsppireWindow *); void psppire_window_save (PsppireWindow *w); +void psppire_window_save_as (PsppireWindow *w); gboolean psppire_window_load (PsppireWindow *w, const gchar *file); void psppire_window_open (PsppireWindow *de); GtkWidget *psppire_window_file_chooser_dialog (PsppireWindow *toplevel); diff --git a/src/ui/gui/psppire.c b/src/ui/gui/psppire.c index 80983e01..5963803f 100644 --- a/src/ui/gui/psppire.c +++ b/src/ui/gui/psppire.c @@ -28,6 +28,7 @@ #include "data/file-handle-def.h" #include "data/file-name.h" #include "data/por-file-reader.h" +#include "data/session.h" #include "data/settings.h" #include "data/sys-file-reader.h" @@ -45,12 +46,12 @@ #include "ui/gui/psppire-data-store.h" #include "ui/gui/psppire-data-window.h" #include "ui/gui/psppire-dict.h" +#include "ui/gui/psppire.h" #include "ui/gui/psppire-output-window.h" #include "ui/gui/psppire-selector.h" #include "ui/gui/psppire-var-store.h" #include "ui/gui/psppire-var-view.h" #include "ui/gui/psppire-window-register.h" -#include "ui/gui/psppire.h" #include "ui/gui/widgets.h" #include "ui/source-init-opts.h" #include "ui/syntax-gen.h" diff --git a/src/ui/terminal/main.c b/src/ui/terminal/main.c index 805681d9..c19a1c78 100644 --- a/src/ui/terminal/main.c +++ b/src/ui/terminal/main.c @@ -34,6 +34,7 @@ #include "data/dictionary.h" #include "data/file-handle-def.h" #include "data/file-name.h" +#include "data/session.h" #include "data/settings.h" #include "data/variable.h" #include "gsl/gsl_errno.h" @@ -53,6 +54,7 @@ #include "ui/terminal/terminal-opts.h" #include "ui/terminal/terminal-reader.h" #include "ui/terminal/terminal.h" +#include "ui/terminal/terminal-opts.h" #include "gl/fatal-signal.h" #include "gl/progname.h" @@ -61,7 +63,7 @@ #include "gettext.h" #define _(msgid) gettext (msgid) -static struct dataset *the_dataset; +static struct session *the_session; static void add_syntax_reader (struct lexer *, const char *file_name, const char *encoding, enum lex_syntax_mode); @@ -96,7 +98,8 @@ main (int argc, char **argv) random_init (); lexer = lex_create (); - the_dataset = dataset_create (); + the_session = session_create (); + dataset_create (the_session, ""); parser = argv_parser_create (); terminal_opts = terminal_opts_init (parser, &syntax_mode, &process_statrc, @@ -108,7 +111,7 @@ main (int argc, char **argv) argv_parser_destroy (parser); msg_set_handler (output_msg, lexer); - dataset_set_default_syntax_encoding (the_dataset, syntax_encoding); + session_set_default_syntax_encoding (the_session, syntax_encoding); /* Add syntax files to source stream. */ if (process_statrc) @@ -134,7 +137,7 @@ main (int argc, char **argv) lex_get (lexer); for (;;) { - int result = cmd_parse (lexer, the_dataset); + int result = cmd_parse (lexer, session_active_dataset (the_session)); if (result == CMD_EOF || result == CMD_FINISH) break; @@ -159,7 +162,7 @@ main (int argc, char **argv) } - dataset_destroy (the_dataset); + session_destroy (the_session); random_done (); settings_done (); diff --git a/tests/automake.mk b/tests/automake.mk index 1145d645..d62ef637 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -293,6 +293,7 @@ TESTSUITE_AT = \ tests/language/data-io/add-files.at \ tests/language/data-io/data-list.at \ tests/language/data-io/data-reader.at \ + tests/language/data-io/dataset.at \ tests/language/data-io/file-handle.at \ tests/language/data-io/get-data-gnm.at \ tests/language/data-io/get-data-psql.at \ diff --git a/tests/language/data-io/dataset.at b/tests/language/data-io/dataset.at new file mode 100644 index 00000000..7d5e928e --- /dev/null +++ b/tests/language/data-io/dataset.at @@ -0,0 +1,302 @@ +AT_BANNER([DATASET commands]) + +AT_SETUP([DATASET COPY]) +AT_DATA([dataset.pspp], [dnl +DATASET NAME initial. +DATA LIST NOTABLE /x 1. +COMPUTE x = x + 1. +DATASET COPY clone. +BEGIN DATA. +1 +2 +3 +4 +5 +END DATA. + +NEW FILE. +DATA LIST NOTABLE /y 1. +BEGIN DATA. +6 +7 +8 +END DATA. +LIST. +DATASET DISPLAY. + +DATASET ACTIVATE clone. +DATASET DISPLAY. +LIST. + +DATASET ACTIVATE initial. +DATASET DISPLAY. +LIST. + +COMPUTE z=y. +DATASET COPY clone. + +DATASET ACTIVATE clone. +LIST. +DATASET COPY clone. +DATASET DISPLAY. + +DATASET CLOSE initial. +DATASET DISPLAY. +]) +AT_CHECK([pspp -O format=csv dataset.pspp], [0], [dnl +Table: Data List +y +6 +7 +8 + +Table: Open datasets. +Dataset +clone +initial (active dataset) + +Table: Open datasets. +Dataset +clone (active dataset) +initial + +Table: Data List +x +2 +3 +4 +5 +6 + +Table: Open datasets. +Dataset +clone +initial (active dataset) + +Table: Data List +y +6 +7 +8 + +Table: Data List +y,z +6,6.00 +7,7.00 +8,8.00 + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) +initial + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) +]) +AT_CLEANUP + +AT_SETUP([DATASET DECLARE]) +AT_DATA([dataset.pspp], [dnl +DATASET DECLARE second. +DATASET DISPLAY. +DATA LIST NOTABLE/x 1. +BEGIN DATA. +1 +END DATA. +LIST. +DATASET ACTIVATE second. +DATASET DISPLAY. +LIST. +]) +AT_CHECK([pspp -O format=csv dataset.pspp], [1], [dnl +Table: Open datasets. +Dataset +unnamed dataset (active dataset) +second + +Table: Data List +x +1 + +Table: Open datasets. +Dataset +second (active dataset) + +dataset.pspp:10: error: LIST: LIST is allowed only after the active dataset has been defined. +]) +AT_CLEANUP + +AT_SETUP([DATASET NAME deletes duplicate name]) +AT_DATA([dataset.pspp], [dnl +DATASET NAME a. +DATASET DECLARE b. +DATASET DECLARE c. +DATASET DISPLAY. + +DATASET NAME b. +DATASET NAME c. +DATASET DISPLAY. +]) +AT_CHECK([pspp -O format=csv dataset.pspp], [0], [dnl +Table: Open datasets. +Dataset +a (active dataset) +b +c + +Table: Open datasets. +Dataset +c (active dataset) +]) +AT_CLEANUP + +AT_SETUP([DATASET ACTIVATE deletes unnamed dataset]) +AT_DATA([dataset.pspp], [dnl +DATASET DECLARE x. +DATASET DISPLAY. + +DATASET ACTIVATE x. +DATASET DISPLAY. +]) +AT_CHECK([pspp -O format=csv dataset.pspp], [0], [dnl +Table: Open datasets. +Dataset +unnamed dataset (active dataset) +x + +Table: Open datasets. +Dataset +x (active dataset) +]) +AT_CLEANUP + +AT_SETUP([DATASET ACTIVATE executes pending transformations]) +AT_DATA([dataset.pspp], [dnl +DATASET NAME one. +DATASET DECLARE another. +DATASET DISPLAY. + +DATA LIST NOTABLE /x 1. +PRINT/x. +DATASET ACTIVATE another. +BEGIN DATA. +1 +2 +3 +4 +5 +END DATA. + +LIST. + +DATASET ACTIVATE one. +LIST. +]) +AT_CHECK([pspp -O format=csv dataset.pspp], [1], [dnl +Table: Open datasets. +Dataset +another +one (active dataset) + +1 @&t@ + +2 @&t@ + +3 @&t@ + +4 @&t@ + +5 @&t@ + +dataset.pspp:16: error: LIST: LIST is allowed only after the active dataset has been defined. + +Table: Data List +x +1 +2 +3 +4 +5 +]) +AT_CLEANUP + +AT_SETUP([DATASET CLOSE]) +AT_DATA([dataset.pspp], [dnl +DATASET DISPLAY +DATASET CLOSE *. +DATASET DISPLAY. + +DATASET NAME this. +DATASET DISPLAY. +DATASET CLOSE this. +DATASET DISPLAY. + +DATASET NAME this. +DATASET DISPLAY. +DATASET CLOSE *. +DATASET DISPLAY. + +DATASET DECLARE that. +DATASET DECLARE theother. +DATASET DECLARE yetanother. +DATASET DISPLAY. +DATASET CLOSE ALL. +DATASET DISPLAY. + +DATASET NAME this. +DATASET DECLARE that. +DATASET DECLARE theother. +DATASET DECLARE yetanother. +DATASET DISPLAY. +DATASET CLOSE ALL. +DATASET DISPLAY. +]) +AT_CHECK([pspp -O format=csv dataset.pspp], [0], [dnl +Table: Open datasets. +Dataset +unnamed dataset (active dataset) + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) + +Table: Open datasets. +Dataset +this (active dataset) + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) + +Table: Open datasets. +Dataset +this (active dataset) + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) +that +theother +yetanother + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) + +Table: Open datasets. +Dataset +that +theother +this (active dataset) +yetanother + +Table: Open datasets. +Dataset +unnamed dataset (active dataset) +]) +AT_CLEANUP diff --git a/tests/language/stats/aggregate.at b/tests/language/stats/aggregate.at index 1c3a7e17..ae7a38ad 100644 --- a/tests/language/stats/aggregate.at +++ b/tests/language/stats/aggregate.at @@ -4,7 +4,7 @@ dnl CHECK_AGGREGATE(OUTFILE, SORT, MISSING) dnl dnl Checks the AGGREGATE procedure with the specified combination of: dnl -dnl - OUTFILE: One of "scratch", "active", or "external" according to +dnl - OUTFILE: One of "dataset", "active", or "external" according to dnl where AGGREGATE's output should be directed. dnl dnl - SORT: Either "presorted" or "unsorted" according to whether @@ -33,11 +33,12 @@ m4_define([CHECK_AGGREGATE], [ [DATA LIST NOTABLE FILE='aggregate.data' /G N 1-2 S 3(a) W 4. WEIGHT BY w. MISSING VALUES n(4) s('4'). +m4_if([$1], [dataset], [DATASET DECLARE aggregate.]) m4_if([$2], [presorted], [SORT CASES BY g.]) AGGREGATE dnl m4_if([$1], [active], [OUTFILE=*], [$1], [external], [OUTFILE='aggregate.sys'], - [outfile=@%:@AGGREGATE]) dnl + [outfile=aggregate]) dnl m4_if([$2], [presorted], [/PRESORTED]) dnl m4_if([$3], [columnwise], [/MISSING=COLUMNWISE]) /DOCUMENT @@ -119,7 +120,7 @@ m4_if([$3], [columnwise], [/MISSING=COLUMNWISE]) /NSUM = sum(n) /NSUMI = sum.(n). m4_if([$1], [external], [GET FILE='aggregate.sys'.], - [$1], [scratch], [GET FILE=@%:@AGGREGATE.]) + [$1], [dataset], [DATASET ACTIVATE aggregate.]) LIST. ]) AT_CHECK([pspp -O format=csv aggregate.sps], [0], [stdout]) @@ -157,10 +158,10 @@ G,N,NI,NU,NUI,NFGT2,NFGT2I,SFGT2,SFGT2I,NFIN23,NFIN23I,SFIN23,SFIN23I,NFLT2,NFLT ])]) AT_CLEANUP]) -CHECK_AGGREGATE([scratch], [presorted], [itemwise]) -CHECK_AGGREGATE([scratch], [presorted], [columnwise]) -CHECK_AGGREGATE([scratch], [unsorted], [itemwise]) -CHECK_AGGREGATE([scratch], [unsorted], [columnwise]) +CHECK_AGGREGATE([dataset], [presorted], [itemwise]) +CHECK_AGGREGATE([dataset], [presorted], [columnwise]) +CHECK_AGGREGATE([dataset], [unsorted], [itemwise]) +CHECK_AGGREGATE([dataset], [unsorted], [columnwise]) CHECK_AGGREGATE([active], [presorted], [itemwise]) CHECK_AGGREGATE([active], [presorted], [columnwise]) CHECK_AGGREGATE([active], [unsorted], [itemwise]) @@ -285,5 +286,4 @@ AT_CHECK([pspp -O format=csv dup-variables.sps], [1], ["dup-variables.sps:24: error: AGGREGATE: Variable name N_BREAK is not unique within the aggregate file dictionary, which contains the aggregate variables and the break variables." ]) - AT_CLEANUP -- 2.30.2