Glynn Clements wrote:
> But now I suppose I betray my ignorance. If I call a normal GRASS 5.7
> command with no argument (i.e., it is parsed by G_gui() and autogenerates an
> interactive tcltk dialog) through either the spawn or run procedures it
> *appears* to work the same as if I called it via the execute procedure. Is
> there a problem in doing this? Why can't I just use run, for example, to
> call all GRASS programs except those that require an xterm?
Future expansion.
Essentially, I want to allow for the possibility of adding a feature
to G_parser/G_gui such that, when the program is being run from
tcltkgrass, rather than G_gui() generating Tcl/Tk code and feeding it
directly to wish, it would write it to stdout, and tcltkgrass would
read and execute the generated code.
This would allow tcltkgrass to implement features such as scripting,
command history, etc, as tcltkgrass would get to see the user's
argument values.
Implementing this would essentially boil down to:
1. Adding an option to G_gui() to write the code to stdout rather than
calling popen("$GRASS_WISH").
2. Adding a global switch (e.g. --tcltk) to G_parser(), which makes
use of 1.
3. Copying the relevant portions of lib/gis/gui.tcl into tcltkgrass'
gui.tcl, with any necessary modifications.
4. Changing the execute procedure to e.g.:
proc execute {cmd} {
eval [exec -- $cmd --tcltk 2>@ stderr]
}
So, execute would simply invoke the program to get the Tcl/Tk code for
the dialog, then evaluate it. The actual execution of the program
(with arguments) would occur when the user clicked the dialog's "Run"
button.
In short, while execute currently looks very much like spawn,
ultimately it could look very different.
I've committed the changes to implement the above. The actual changes
to tcltkgrass were trivial, essentially just adding:
source $env(GISBASE)/etc/gui.tcl
and changing the execute procedure to:
proc execute {cmd} {
global dlg path
set code [exec -- $cmd --tcltk]
set path .dialog$dlg
toplevel $path
eval $code
}
The changes to lib/gis/parser.c were also fairly straightforward; the
bulk of G_gui() has been moved into a separate function, generate_tcl.
G_gui() spawns wish, writes "source $env(GISBASE)/etc/gui.tcl" then
calls generate_tcl() with the pipe as the destination. A new function,
G_tcltk simply calls generate_tcl() with stdout as the destination.
Also, G_parser() now supports --ui (call G_gui()) and --tcltk (call
G_tcltk()) switches.
Most of the work was in making lib/gis/gui.tcl compatible with
tcltkgrass, primarily allowing multiple dialogs to coexist
simultaneously. One side effect of this is that tcltkgrass' memory
usage will grow with use. Each dialog adds new entries to the global
opt() array; at present, these entries aren't removed when the dialog
is closed.
While I can still remember how this all works, I'll provide a few
notes regarding the structure of lib/gis/gui.tcl:
1. The central data store is the opt() array. Keys into this array
always start with the dialog ID. Per-dialog keys consist of the dialog
ID and the name:
opt($dlg,path) # prefix for widget paths
opt($dlg,root) # top-level widget path
opt($dlg,suf) # prefix for ScrollableFrame children
opt($dlg,outtext) # path to the output widget
opt($dlg,pgm_name) # program name
opt($dlg,nopt) # number of options/flags
while per-field keys consist of the dialog ID, followed by the field
ID followed by the name:
opt($dlg,$optn,answer) # string: opt->answer
opt($dlg,$optn,class) # "multi", "opt" or "flag"
opt($dlg,$optn,desc) # string: opt->description
opt($dlg,$optn,multi) # boolean: opt->multiple
opt($dlg,$optn,name) # string: opt->key or flag->key
opt($dlg,$optn,nmulti) # integer: number of items in opt->options
opt($dlg,$optn,options) # string: opt->options
opt($dlg,$optn,prompt) # string: opt->gisprompt
opt($dlg,$optn,required)# boolean: opt->required
opt($dlg,$optn,type) # "integer", "float" or "string": opt->type
opt($dlg,$optn,val) # contents of entry/combo widget (-textvariable)
The class and nmulti entries are computed within gui.tcl, while the
val entries are set by the widget via -textvariable. The rest come
directly from parser.c (i.e. from the optlist argument to add_option,
or from the key/desc arguments to add_flag).
Also, in the checkbox case (opt->multiple && opt->options != NULL),
there are per-option keys, consisting of the dialog ID, the field ID,
the name then the option ID:
opt($dlg,$optn,val,$i) # checkbox state (-variable)
opt($dlg,$optn,valname,$i) # corresponding item from opt->options
2. There are two other global variables: dlg and path. $dlg is the
current dialog ID (incremented for each call to begin_dialog), while
$path is the widget path prefix for children of the top-level widget.
For the G_gui() case, $path will always be null, so e.g. $path.run
will evaluate to just ".run". This value is stored in opt($dlg,path)
when the dialog is created, while opt($dlg,root) holds the actual path
to the top-level widget (if the path entry is null, the root entry
will be ".", otherwise the two will be identical).
For tcltkgrass, path is set to the path to the new top-level widget
which is created for that dialog prior to executing the code from
"<cmd> --tcltk".
3. lib/gis/parser.c uses the following four procedures from the bottom
of gui.tcl:
proc begin_dialog {pgm desc}
proc add_option {optn optlist} (for each option)
proc add_flag {optn key desc} (for each flag)
proc end_dialog {n}
In turn, these are the only procedures which reference the dlg and
path global variables; everything else has them passed as parameters.
The only global variables used by the other procedures are $opt() and
$env().
Any changes to this interface need to be synchronised between parser.c
and gui.tcl.
4. Ultimately, as additional features are added to tcltkgrass, some of
the code in gui.tcl will need to diverge into standalone (G_gui()) and
tcltkgrass versions. As that occurs, it would be a good idea to give
some thought to sharing as much code as is practical (rather than just
copying the whole of gui.tcl into tcltkgrass).
--
Glynn Clements <glynn.clements@virgin.net>