The Example Browser

   

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

Table of Contents
Chapter 22.  Tk by Example


Example 22-3 is a browser for the code examples that appear in this book. The basic idea is to provide a menu that selects the examples, and a text window to display the examples. Before you can use this sample program, you need to edit it to set the proper location of the exsource directory that contains all the example sources from the book. Example 22-4 on page 329 extends the browser with a shell that is used to test the examples.

Example 22-3 A browser for the code examples in the book.
 #!/usr/local/bin/wish #  Browser for the Tcl and Tk examples in the book. # browse(dir) is the directory containing all the tcl files # Please edit to match your system configuration. switch $tcl_platform(platform) {    "unix" {set browse(dir) /cdrom/tclbook2/exsource}    "windows" {set browse(dir) D:/exsource}    "macintosh" {set browse(dir) /tclbook2/exsource} } wm minsize . 30 5 wm title . "Tcl Example Browser" # Create a row of buttons along the top set f [frame .menubar] pack $f -fill x button $f.quit -text Quit -command exit button $f.next -text Next -command Next button $f.prev -text Previous -command Previous # The Run and Reset buttons use EvalEcho that # is defined by the Tcl shell in Example 22? on page 329 button $f.load -text Run -command Run button $f.reset -text Reset -command Reset pack $f.quit $f.reset $f.load $f.next $f.prev -side right # A label identifies the current example label $f.label -textvariable browse(current) pack $f.label -side right -fill x -expand true # Create the menubutton and menu menubutton $f.ex -text Examples -menu $f.ex.m pack $f.ex -side left set m [menu $f.ex.m] # Create the text to display the example # Scrolled_Text is defined in Example 30? on page 430 set browse(text) [Scrolled_Text .body \     -width 80 -height 10\     -setgrid true] pack .body -fill both -expand true # Look through the example files for their ID number. foreach f [lsort -dictionary [glob [file join $browse(dir) *]]] {    if [catch {open $f}in] {       puts stderr "Cannot open $f: $in"       continue    }    while {[gets $in line] >= 0} {       if [regexp {^# Example ([0-9]+)-([0-9]+)}$line \              x chap ex] {          lappend examples($chap) $ex          lappend browse(list) $f          # Read example title          gets $in line          set title($chap-$ex) [string trim $line "# "]          set file($chap-$ex) $f          close $in          break       }    } } # Create two levels of cascaded menus. # The first level divides up the chapters into chunks. # The second level has an entry for each example. option add *Menu.tearOff 0 set limit 8 set c 0; set i 0 foreach chap [lsort -integer [array names examples]] {    if {$i == 0} {       $m add cascade -label "Chapter $chap..." \          -menu $m.$c       set sub1 [menu $m.$c]       incr c    }    set i [expr ($i +1) % $limit]    $sub1 add cascade -label "Chapter $chap" -menu $sub1.sub$i    set sub2 [menu $sub1.sub$i ]    foreach ex [lsort -integer $examples($chap)] {       $sub2 add command -label "$chap-$ex $title($chap-$ex)" \          -command [list Browse $file($chap-$ex)]    } } # Display a specified file. The label is updated to # reflect what is displayed, and the text is left # in a read-only mode after the example is inserted. proc Browse { file } {    global browse    set browse(current) [file tail $file]    set browse(curix) [lsearch $browse(list) $file]    set t $browse(text)    $t config -state normal    $t delete 1.0 end    if [catch {open $file}in] {       $t insert end $in    } else {       $t insert end [read $in]       close $in    }    $t config -state disabled } # Browse the next and previous files in the list set browse(curix) -1 proc Next {} {    global browse    if {$browse(curix) < [llength $browse(list)] - 1} {       incr browse(curix)    }    Browse [lindex $browse(list) $browse(curix)] } proc Previous {} {    global browse    if {$browse(curix) > 0} {       incr browse(curix) -1    }    Browse [lindex $browse(list) $browse(curix)] } # Run the example in the shell proc Run {} {    global browse    EvalEcho [list source \       [file join $browse(dir) $browse(current)]] } # Reset the slave in the eval server proc Reset {} {    EvalEcho reset } 

More about Resizing Windows

This example uses the wm minsize command to put a constraint on the minimum size of the window. The arguments specify the minimum width and height. These values can be interpreted in two ways. By default they are pixel values. However, if an internal widget has enabled geometry gridding, then the dimensions are in grid units of that widget. In this case the text widget enables gridding with its setgrid attribute, so the minimum size of the window is set so that the text window is at least 30 characters wide by five lines high:

 wm minsize . 30 5 

In older versions of Tk, Tk 3.6, gridding also enabled interactive resizing of the window. Interactive resizing is enabled by default in Tk 4.0 and later.

Managing Global State

The example uses the browse array to collect its global variables. This makes it simpler to reference the state from inside procedures because only the array needs to be declared global. As the application grows over time and new features are added, that global command won't have to be adjusted. This style also serves to emphasize what variables are important. The browse array holds the name of the example directory (dir), the Tk pathname of the text display (text), and the name of the current file (current). The list and curix elements are used to implement the Next and Previous procedures.

Searching through Files

The browser searches the file system to determine what it can display. The tcl_platform(platform) variable is used to select a different example directory on different platforms. You may need to edit the on-line example to match your system. The example uses glob to find all the files in the exsource directory. The file join command is used to create the file name pattern in a platform-independent way. The result of glob is sorted explicitly so the menu entries are in the right order. Each file is read one line at a time with gets, and then regexp is used to scan for keywords. The loop is repeated here for reference:

 foreach f [lsort -dictionary [glob [file join $browse(dir) *]]] {    if [catch {open $f}in] {       puts stderr "Cannot open $f: $in"       continue    }    while {[gets $in line] >= 0} {       if [regexp {^# Example ([0-9]+)-([0-9]+)}$line \              x chap ex] {          lappend examples($chap) $ex          lappend browse(list) $f          # Read example title          gets $in line          set title($chap-$ex) [string trim $line "# "]          set file($chap-$ex) $f           close $in           break       }    } } 

The example files contain lines like this:

 # Example 1-1 # The Hello, World! program 

The regexp picks out the example numbers with the ([0-9]+)-([0-9]+) part of the pattern, and these are assigned to the chap and ex variables. The x variable is assigned the value of the whole match, which is more than we are interested in. Once the example number is found, the next line is read to get the description of the example. At the end of the foreach loop the examples array has an element defined for each chapter, and the value of each element is a list of the examples for that chapter.

Cascaded Menus

The values in the examples array are used to build up a cascaded menu structure. First a menubutton is created that will post the main menu. It is associated with the main menu with its menu attribute. The menu must be a child of the menubutton for its display to work properly:

 menubutton $f.ex -text Examples -menu $f.ex.m set m [menu $f.ex.m] 

There are too many chapters to put them all into one menu. The main menu has a cascade entry for each group of eight chapters. Each of these submenus has a cascade entry for each chapter in the group, and each chapter has a menu of all its examples. Once again, the submenus are defined as a child of their parent menu. Note the inconsistency between menu entries and buttons. Their text is defined with the -label option, not -text. Other than this they are much like buttons. Chapter 27 describes menus in more detail. The code is repeated here:

 set limit 8 ; set c 0 ; set i 0 foreach key [lsort -integer [array names examples]] {    if {$i == 0} {       $m add cascade -label "Chapter $key..." \          -menu $m.$c       set sub1 [menu $m.$c]       incr c    }    set i [expr ($i +1) % $limit]    $sub1 add cascade -label "Chapter $key" -menu $sub1.sub$i    set sub2 [menu $sub1.sub$i]    foreach ex [lsort -integer $examples($key)] {       $sub2 add command -label "$key-$ex $title($key-$ex)" \          -command [list Browse $file($key-$ex)]    } } 

A Read-Only Text Widget

The Browse procedure is fairly simple. It sets browse(current) to be the name of the file. This changes the main label because of its textvariable attribute that links it to this variable. The state attribute of the text widget is manipulated so that the text is read-only after the text is inserted. You have to set the state to normal before inserting the text; otherwise, the insert has no effect. Here are a few commands from the body of Browse:

 global browse set browse(current) [file tail $file] $t config -state normal $t insert end [read $in] $t config -state disabled 

       
    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