ExecLog

   

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 22.  Tk by Example


Our first example provides a simple user interface to running another program with the exec command. The interface consists of two buttons, Run it and Quit, an entry widget in which to enter a command, and a text widget in which to log the results of running the program. The script runs the program in a pipeline and uses the fileevent command to wait for output. This structure lets the user interface remain responsive while the program executes. You could use this to run make, for example, and it would save the results in the log. The complete example is given first, and then its commands are discussed in more detail.

Example 22-1 Logging the output of a program run with exec.

graphics/22fig01.gif

 #!/usr/local/bin/wish # execlog - run a program with exec and log the output # Set window title wm title . ExecLog # Create a frame for buttons and entry. frame .top -borderwidth 10 pack .top -side top -fill x # Create the command buttons. button .top.quit -text Quit -command exit set but [button .top.run -text "Run it" -command Run] pack .top.quit .top.run -side right # Create a labeled entry for the command label .top.l -text Command: -padx 0 entry .top.cmd -width 20 -relief sunken \    -textvariable command pack .top.l -side left pack .top.cmd -side left -fill x -expand true # Set up key binding equivalents to the buttons bind .top.cmd <Return> Run bind .top.cmd <Control-c> Stop focus .top.cmd # Create a text widget to log the output frame .t set log [text .t.log -width 80 -height 10 \    -borderwidth 2 -relief raised -setgrid true \    -yscrollcommand {.t.scroll set}] scrollbar .t.scroll -command {.t.log yview} pack .t.scroll -side right -fill y pack .t.log -side left -fill both -expand true pack .t -side top -fill both -expand true # Run the program and arrange to read its input proc Run {} {    global command input log but    if [catch {open "|$command |& cat"}input] {       $log insert end $input\n    } else {       fileevent $input readable Log       $log insert end $command\n       $but config -text Stop -command Stop    } } # Read and log output from the program proc Log {} {    global input log    if [eof $input] {       Stop    } else {       gets $input line       $log insert end $line\n       $log see end    } } # Stop the program and fix up the button proc Stop {} {    global input but    catch {close $input}    $but config -text "Run it" -command Run } 

Window Title

The first command sets the title that appears in the title bar implemented by the window manager. Recall that dot (i.e., .) is the name of the main window:

 wm title . ExecLog 

The wm command communicates with the window manager. The window manager is the program that lets you open, close, and resize windows. It implements the title bar for the window and probably some small buttons to close or resize the window. Different window managers have a distinctive look; the figure shows a title bar from twm, a window manager for X.

A Frame for Buttons

A frame is created to hold the widgets that appear along the top of the interface. The frame has a border to provide some space around the widgets:

 frame .top -borderwidth 10 

The frame is positioned in the main window. The default packing side is the top, so -side top is redundant here, but it is used for clarity. The -fill x packing option makes the frame fill out to the whole width of the main window:

 pack .top -side top -fill x 

Command Buttons

Two buttons are created: one to run the command, the other to quit the program. Their names, .top.quit and .top.run, imply that they are children of the .top frame. This affects the pack command, which positions widgets inside their parent by default:

 button .top.quit -text Quit -command exit set but [button .top.run -text "Run it" \     -command Run] pack .top.quit .top.run -side right 

A Label and an Entry

The label and entry are also created as children of the .top frame. The label is created with no padding in the X direction so that it can be positioned right next to the entry. The size of the entry is specified in terms of characters. The relief attribute gives the entry some looks to set it apart visually on the display. The contents of the entry widget are linked to the Tcl variable command:

 label .top.l -text Command: -padx 0 entry .top.cmd -width 20 -relief sunken \     -textvariable command 

The label and entry are positioned to the left inside the .top frame. The additional packing parameters to the entry allow it to expand its packing space and fill up that extra area with its display. The difference between packing space and display space is discussed in Chapter 23 on page 339:

 pack .top.l -side left pack .top.cmd -side left -fill x -expand true 

Key Bindings and Focus

Key bindings on the entry widget provide an additional way to invoke the functions of the application. The bind command associates a Tcl command with an event in a particular widget. The <Return> event is generated when the user presses the Return key on the keyboard. The <Control-c> event is generated when the letter c is typed while the Control key is already held down. For the events to go to the entry widget, .top.cmd, input focus must be given to the widget. By default, an entry widget gets the focus when you click the left mouse button in it. The explicit focus command is helpful for users with the focus-follows-mouse model. As soon as the mouse is over the main window the user can type into the entry:

 bind .top.cmd <Return> Run bind .top.cmd <Control-c> Stop focus .top.cmd 

A Resizable Text and Scrollbar

A text widget is created and packed into a frame with a scrollbar. The width and height of the text widget are specified in characters and lines, respectively. The setgrid attribute of the text widget is turned on. This restricts the resize so that only a whole number of lines and average-sized characters can be displayed.

The scrollbar is a separate widget in Tk, and it can be connected to different widgets using the same setup as is used here. The text's yscrollcommand updates the display of the scrollbar when the text widget is modified, and the scrollbar's command scrolls the associated widget when the user manipulates the scrollbar:

 frame .t set log [text .t.log -width 80 -height 10 \     -borderwidth 2 -relief raised -setgrid true\     -yscrollcommand {.t.scroll set}] scrollbar .t.scroll -command {.t.log yview} pack .t.scroll -side right -fill y pack .t.log -side left -fill both -expand true pack .t -side top -fill both -expand true 

A side effect of creating a Tk widget is the creation of a new Tcl command that operates on that widget. The name of the Tcl command is the same as the Tk pathname of the widget. In this script, the text widget command, .t.log, is needed in several places. However, it is a good idea to put the Tk pathname of an important widget into a variable because that pathname can change if you reorganize your user interface. The disadvantage of this is that you must declare the variable with global inside procedures. The variable log is used for this purpose in this example to demonstrate this style.

The Run Procedure

The Run procedure starts the program specified in the command entry. That value is available in the global command variable because of the textvariable attribute of the entry. The command is run in a pipeline so that it executes in the background. The leading | in the argument to open indicates that a pipeline is being created. The catch command guards against bogus commands. The variable input is set to an error message, or to the normal open return that is a file descriptor. The program is started like this:

 if [catch {open "|$command |& cat"}input] { 

graphics/tip_icon.gif

Trapping errors from pipelines.


The pipeline diverts error output from the command through the cat program. If you do not use cat like this, then the error output from the pipeline, if any, shows up as an error message when the pipeline is closed. In this example it turns out to be awkward to distinguish between errors generated from the program and errors generated because of the way the Stop procedure is implemented. Furthermore, some programs interleave output and error output, and you might want to see the error output in order instead of all at the end.

If the pipeline is opened successfully, then a callback is set up using the fileevent command. Whenever the pipeline generates output, then the script can read data from it. The Log procedure is registered to be called whenever the pipeline is readable:

 fileevent $input readable Log 

The command (or the error message) is inserted into the log. This is done using the name of the text widget, which is stored in the log variable, as a Tcl command. The value of the command is appended to the log, and a newline is added so that its output will appear on the next line.

 $log insert end $command\n 

The text widget's insert function takes two parameters: a mark and a string to insert at that mark. The symbolic mark end represents the end of the contents of the text widget.

The run button is changed into a stop button after the program begins. This avoids a cluttered interface and demonstrates the dynamic nature of a Tk interface. Again, because this button is used in a few different places in the script, its pathname has been stored in the variable but:

 $but config -text Stop -command Stop 

The Log Procedure

The Log procedure is invoked whenever data can be read from the pipeline, and when end of file has been reached. This condition is checked first, and the Stop procedure is called to clean things up. Otherwise, one line of data is read and inserted into the log. The text widget's see operation is used to position the view on the text so that the new line is visible to the user:

 if [eof $input] {    Stop } else {    gets $input line    $log insert end $line\n    $log see end } 

The Stop Procedure

The Stop procedure terminates the program by closing the pipeline. The close is wrapped up with a catch. This suppresses the errors that can occur when the pipeline is closed prematurely on the process. Finally, the button is restored to its run state so that the user can run another command:

 catch {close $input} $but config -text "Run it" -command Run 

In most cases, closing the pipeline is adequate to kill the job. On UNIX, this results in a signal, SIGPIPE, being delivered to the program the next time it does a write to its standard output. There is no built-in way to kill a process, but you can exec the UNIX kill program. The pid command returns the process IDs from the pipeline:

 foreach pid [pid $input] {    catch {exec kill $pid} } 

If you need more sophisticated control over another process, you should check out the expect Tcl extension, which is described in the book Exploring Expect (Don Libes, O'Reilly & Associates, Inc., 1995). Expect provides powerful control over interactive programs. You can write Tcl scripts that send input to interactive programs and pattern match on their output. Expect is designed to automate the use of programs that were designed for interactive use.

Cross-Platform Issues

This script will run on UNIX and Windows, but not on Macintosh because there is no exec command. One other problem is the binding for <Control-c> to cancel the job. This is UNIX-like, while Windows users might expect <Escape> to cancel a job, and Macintosh users expect <Command-.>. Platform_CancelEvent defines a virtual event, <<Cancel>>, and Stop is bound to it:

Example 22-2 A platform-specific cancel event.
 proc Platform_CancelEvent {} {    global tcl_platform    switch $tcl_platform(platform) {       unix {          event add <<Cancel>> <Control-c>       }       windows {          event add <<Cancel>> <Escape>       }       macintosh {          event add <<Cancel>> <Command-.>       }    } } bind .top.entry <<Cancel>> Stop 

There are other virtual events already defined by Tk. The event command and virtual events are described on page 380.


       
    Top
     



    Practical Programming in Tcl and Tk
    Practical Programming in Tcl and Tk (4th Edition)
    ISBN: 0130385603
    EAN: 2147483647
    Year: 1999
    Pages: 478

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net