A GUI front end
Normally I'm more a command line person. Still, the ACK has some idosyncracies that may be flattened out by a simple front end. And since Graphical seems to be the word of this era (how else would you like to spoil all these GHz-es and CPU cores?) I set out to create a GUI front end for the ack. To get this accomplished there are some methods:
Version 001 : get it going
To the right, you see how the version 001 presents itself on screen.
Below is the source code for the first version of the GUI front end. It is called 'gack'. It cannot do much
sensible things. It is a mere mockup for the user interface and some command line printing.
The GUI front end as is should
#! /usr/bin/wish # GUI for the ACK set version 001 set name {} set fontf20 {times 18 bold} set fontf12 {times 12 bold} proc init { } { global modules switches set fileID [open modules.list r] while { [ gets $fileID line] > 0 } { lappend modules $line } close $fileID set fileID [open switches.list r] while { [ gets $fileID line] > 0 } { lappend switches $line } close $fileID } proc testit {} { puts stdout "Testit pressed" } proc editswit {} { puts stdout "edit switches" } proc editmods {} { puts stdout "edit modules" } proc compile {} { puts stdout "compile it" } proc edit {file} { puts stdout $file } proc run { } { global name version fontf20 fontf12 modules wm title . "ACK Control version $version" frame .bott frame .topp frame .qtext -borderwidth 4 frame .topq -borderwidth 5 frame .center frame .center.left -relief groove -borderwidth 5 frame .center.right -relief groove -borderwidth 5 # topmost section label .topp.label -text "gack version $version" -font $fontf12 pack .topp.label -pady 5 -fill x # not so topmost section label .topq.label -font $fontf12 -text "Source : [lindex $modules 0]" label .topq.exe -font $fontf12 -text "Target : [lindex $modules 0]" pack .topq.label -side left -padx 5 pack .topq.exe -side right -padx 5 # Center section listbox .center.left.mods -listvariable modules -height 6 listbox .center.right.swits -listvariable switches -height 6 button .center.left.butt -text Modules -borderwidth 3 -bg grey -command editmods button .center.right.butt -text Switches -borderwidth 3 -bg grey50 -command editswit pack .center.left.butt .center.left.mods -side top -padx 5m -pady 2m pack .center.right.butt .center.right.swits -side top -padx 5m -pady 2m pack .center.left -side left -padx 5m pack .center.right -side right -padx 5m # Lower section text .qtext.log -borderwidth 3 -relief groove pack .qtext.log -padx 10 -pady 10 # Master control section button .bott.comp -text Compile -borderwidth 4 -bg pink \ -command compile button .bott.run -text Run -borderwidth 4 -bg green \ -command testit button .bott.edit -text Edit -borderwidth 4 -bg yellow \ -command { edit [lindex $modules [.center.left.mods curselection]] } button .bott.stop -text Quit -borderwidth 4 -bg cyan -command exit pack .bott.comp .bott.edit -side left -padx 5m -pady 2m -fill x pack .bott.stop .bott.run -side right -padx 5m -pady 2m -fill x # Final packing up pack .topp .topq .center .bott -side top -fill x } init runMost of this all is rather straight forward. Create buttons, lists and populate frames. It's the surroundings however that make things difficult. There are no real rules when to use a '$' prefix for variables. That's hard to work with for a Wirthina programmer like I am.
gack version 002: that's more like it
To the right you see gack version 002. The most important differences with gack001 were:
jan@Beryllium:~/develop/ack/GUI$ gack.tcl exec kate Plov030.mod ack Plov030.mod Sio.mod NumConv.mod -w -t -v -gdb -o Plov030 exec Plov030 edit switches edit modules exec kate NumConv.mod
gack version 002 : the source
Below is the source code of gack version 002. It is split up in sections with some comments, when required.
#! /usr/bin/wish # GUI for the ACK set version 002This is how a variable is declared and filled. In fact, 'set' is equivalent to the 'LET' keyword of early versions of BASIC.
set fname {} set fontf20 {times 18 bold} set fontf12 {times 12 bold}The curly braces are a bit tricky to use. Sometimes you need a {} pair, sometimes a pair of double quotes. Sometimes a pair of []. In these cases, the variable 'fontf20' holds the (ASCII) value "times 18 bold" which specifies a letter type (font face and size).
proc init { } { global modules switches fname set fileID [open modules.list r] while { [ gets $fileID line] > 0 } { lappend modules $line } close $fileID set fileID [open switches.list r] while { [ gets $fileID line] > 0 } { lappend switches $line } close $fileID set name [lindex $modules 0] set nr [string last . $name] incr nr -1 set fname [string range $name 0 $nr] }Init does just what the name says. It takes no arguments (hence the first empty '{ }' braces). Next it needs a 'gobal' line to tell the procedure which variables are in scope. The fact that a variable is declarec globally does not mean it is in scope...
proc testit { } { global fname set exname "exec " append exname $fname puts stdout $exname }This is the command that is executed when the 'Run' button is clicked
proc editswit {} { puts stdout "edit switches" } proc editmods {} { puts stdout "edit modules" } proc compile { } { global fname modules switches set runname "ack " foreach i $modules { append runname $i append runname " " } foreach i $switches { append runname $i append runname " " } append runname "-o " append runname $fname puts stdout $runname }Same for the buttons 'Modules', 'Switches' and 'Compile'. This kind of Tcl is rather fun to do.
proc edit {file} { set nr [.center.left.mods curselection] set editname "exec kate " if {$nr >= 0} { if {$nr <= 9} { append editname $file } } puts stdout $editname }The two nested if's were an experiment to prevent an action when no module is selected. For some rason I could not find out what the value of 'nr' is when nothing is selected. I did some experiments. When the top field was selected, 'nr' was zero (as was to be expected). Yet, when nothing was selected, the 'nr' variable was '', i.e. plain old NOTHING, an empty set.
proc run { } { global name version fontf20 fontf12 modules fname wm title . "ACK Control version $version"The quoted text is first processed by filling in the value of the variable 'version' before passing the string to the 'wm' command. If a word has a dollar sign up front, it is treated as a variable that needs to be inserted right there. Since the variable contains text, the text is inserted and afterwards, the full text is processed. So, if a variable is '18' it is not the number eightteen, but just the two ASCII digits '1' and '8'. Which makes up for the following line of Tcl code:
if {"0xa" = "10"} { puts stdout "0xa = 10" }The puts command is executed. Hexadecimal 'A' indeed is decimal '10' so Tcl sees this as true...
frame .bott frame .topp frame .qtext -borderwidth 4 frame .topq -borderwidth 5 frame .center frame .center.left -relief groove -borderwidth 5 frame .center.right -relief groove -borderwidth 5A frame is a container to hold widgets (graphical elements). By carefully creating and filling frames, you can make the user interface appear nice on screen.
# topmost section label .topp.label -text "gack version $version" -font $fontf12 pack .topp.label -pady 5 -fill x # not so topmost section label .topq.label -font $fontf12 -text "Source : [lindex $modules 0]" label .topq.exe -font $fontf12 -text "Target : $fname" pack .topq.label -side left -padx 5 pack .topq.exe -side right -padx 5with 'pack' you put widgets in a frame (or subframe).
# Center section listbox .center.left.mods -listvariable modules -height 6 -relief groove -borderwidth 3 .center.left.mods selection set 0 listbox .center.right.swits -listvariable switches -height 6 button .center.left.butt -text Modules -borderwidth 3 -bg grey -command editmods button .center.right.butt -text Switches -borderwidth 3 -bg grey50 -command editswitSome buttons and listboxes are created.
pack .center.left.butt .center.left.mods -side top -padx 5m -pady 2m pack .center.right.butt .center.right.swits -side top -padx 5m -pady 2m pack .center.left -side left -padx 5m pack .center.right -side right -padx 5m # Lower section text .qtext.log -borderwidth 3 -relief groove pack .qtext.log -padx 10 -pady 10 # Master control section button .bott.comp -text Compile -borderwidth 4 -bg pink \ -command compile button .bott.run -text Run -borderwidth 4 -bg green \ -command testit button .bott.edit -text Edit -borderwidth 4 -bg yellow \ -command { edit [lindex $modules [.center.left.mods curselection]] }This last line of source was like HELL to come up with. After all, it -was- in the documentations. Yet so little attention was paid to it that it was very hard to extract.
button .bott.stop -text Quit -borderwidth 4 -bg cyan -command exit pack .bott.comp .bott.edit -side left -padx 5m -pady 2m -fill x pack .bott.stop .bott.run -side right -padx 5m -pady 2m -fill x # Final packing up pack .topp .topq .center .bott .qtext -side top -fill x }from here on is the main routine....
init runInitially I could not use the 'exec' command to run external programs. So I looked up some previous Tcl/Tk scripts and everything looked normal. Then I saw that the 'exec' procedure was used more than once... So now I changed the source such that the 'proc edit {file}' has been taken out altogether. Instead, I changed the line in the 'run' procedure to look like:
button .bott.edit -text Edit -borderwidth 4 -bg yellow \ -command { exec kate [lindex $modules [.center.left.mods curselection]] }and this runs fine! Kate is one of the few editors that has syntax highlighting for Modula-2 (and all other languages of any significance).
Fixing the intricate problems
Tcl is nitpickers programming dream. Either you do it 100.000% the Tcl wy, or you don't. 99/99% is not enough. This section describes how very annoying Tcl can be, if you are familiar with a real programming language.
proc compile { } { global fname modules switches set runname " " foreach i $modules { append runname $i append runname " " } foreach i $switches { append runname $i append runname " " } append runname "-o " append runname $fname exec ack $runname }When you print $runname, you get exactly what you would have types in at a command prompt:
Plov030.mod Sio.mod NumConv.mod -w -t -o Plov030and still the Tcl program complains that it cannot find the source file. The error here is, that the 'exec' does not try to execute a line like
ack Plov030.mod Sio.mod NumConv.mod -w -t -o Plov030but rather the line
ack "Plov030.mod Sio.mod NumConv.mod -w -t -o Plov030"The full command tail is handed over as one and only one argument.... The error was fixed by adding a new keyword to the exec line:
eval exec ack $runnamedoes the trick. The 'eval' function (or property or method or whatever) splits up the different sections of the 'runname' variable. At these moments, I am glad my main programming language is Modula-2. Modula-2 is predictable. It requires but little trial and error. Tcl is based on trial and error.
jan@Beryllium:~/develop/ack/GUI$ ls -l Plov030* -rwxr-xr-x 1 jan users 59164 2010-09-29 18:29 Plov030* -rw-r--r-- 1 jan users 47361 2010-09-29 18:29 Plov030.k -rw-r--r-- 1 jan users 40106 2010-09-29 18:29 Plov030.m -rw-r--r-- 1 jan users 40489 2010-09-28 02:00 Plov030.mod -rw-r--r-- 1 jan users 60053 2010-09-29 18:29 Plov030.o -rw-r--r-- 1 jan users 67574 2010-09-29 18:29 Plov030.out -rw-r--r-- 1 jan users 106568 2010-09-29 18:29 Plov030.sSee the time stamp? It's today 29 september 2010 and the time is 18:30. YES, the gack works!
button .bott.run -text Run -borderwidth 4 -bg green -command { exec $fname }and when I ran it, nothing happened. No Tcl error but also no Plov030 error... I blame it on the log file not yet working. Time to fix that first.
OK, the version number is listed as '5' but in effect it is version '4' with a few small clean ups. All buttons and functions work:
gack version 004 : the source
Below is the source code of the version 004 (which is labeled 005 for stupidity reasons) .he source code is broken up in parts. Use the download section to get to the full file.
#! /usr/bin/wish # GUI for the ACK set version 005 set fname {} set args {} set fontf20 {monospace 20 bold} set fontf12 {monospace 12 bold} set fontf10 {monospace 10 bold} proc init { } { global modules switches fname set fileID [open modules.list r] while { [ gets $fileID line] > 0 } { lappend modules $line } close $fileID set fileID [open switches.list r] while { [ gets $fileID line] > 0 } { lappend switches $line } close $fileID set name [lindex $modules 0] set nr [string last . $name] incr nr -1 set fname [string range $name 0 $nr] }Init does just what it says: it reads the files that were created during the previous session.
proc save {} { global switches modules set fileID [open modules.list w] foreach i $modules { puts $fileID $i } close $fileID set fileID [open switches.list w] foreach i $switches { puts $fileID $i } close $fileID exit }This is the complement of the Init section: it saves the state of the compiling session. The 'exit' returns to the operating system and closes the GUI.
proc unixcomm { command } { # Execute an (operating system) command global input log unicom if [catch {open "| $command |& cat"} input] { $log insert end $input\n } else { fileevent $input readable Log $log insert end $command\n } set unicom {} }I borrowed this code from a code example in the Welch and Jones book (page 377). It works. Parts of it I understand. But the fileevent and Log 6thingies are obscure to me.
proc elless { } { # Carry out an 'ls -l' command global input log set runname "ls -l" if [catch {open "| $runname |& cat"} input] { $log insert end $input\n } else { fileevent $input readable Log $log insert end $runname\n } } proc testit { } { # Run the freshly created executable global log fname input args set command "./" append command $fname if { [string length $args] > 0 } { ;# check for arguments append command " " append command $args } if [catch {open "| $command |& cat"} input] { $log insert end $input\n } else { fileevent $input readable Log $log insert end $command\n } } proc Log { } { # Copied from 'Practical Tcl/Tk programming' global input log if [eof $input] { catch { close $input } } else { gets $input line $log insert end $line\n $log see end } }I haven't got a clue what it does. Let's say it is the appendix of gack programming...
proc compile { } { global fname modules switches log input set runname "ack " ;# Compose start of command foreach i $modules { ;# Process external Modules append runname $i ;# Add Module name append runname " " ;# Add some whitespace } foreach i $switches { ;# Process compiler switches append runname $i append runname " " } append runname "-o " ;# Prepare to declare append runname $fname ;# name of executable if [catch {open "| $runname |& cat"} input] { ;# Catch the screen output from $log insert end $input\n ;# the command } else { ;# or fileevent $input readable Log $log insert end $runname\n ;# echo the command itself } }What follows is the main routine of the gack.
proc run { } { global name version fontf20 fontf12 fontf10 modules fname log args wm title . "ACK Control version $version" frame .row1 ;# GACK version frame .row2 -borderwidth 5 ;# Summary frame .row3 ;# Lists section frame .row3.left -relief groove -borderwidth 5 ;# Modules to compile frame .row3.mid -relief raised -borderwidth 5 ;# Add/Delete elements frame .row3.right -relief groove -borderwidth 5 ;# Compiler switches frame .row4 ;# Button row frame .row5 ;# Unix command frame .row6 -borderwidth 4 ;# Logging screen # ROW 1 section : GACK version label .row1.label -text "gack version $version" -font $fontf20 pack .row1.label -pady 5 -fill x # ROW 2 section : Summary label .row2.src -font $fontf12 -text "Source : [lindex $modules 0]" label .row2.exe -font $fontf12 -text "Target : $fname" label .row2.args -font $fontf12 -text "Arguments : $args" pack .row2.src -side left -padx 5 pack .row2.exe -side right -padx 5 # ROW 3 section : Listboxes listbox .row3.left.mods -listvariable modules -height 6 -relief groove -borderwidth 3 \ -yscrollcommand { .row3.left.scroll set } scrollbar .row3.left.scroll -command { .row3.left.mods yview } .row3.left.mods selection set 0 listbox .row3.right.swits -listvariable switches -height 6 -relief groove -borderwidth 3 \ -yscrollcommand { .row3.right.scroll set } scrollbar .row3.right.scroll -command { .row3.left.swits yview } label .row3.mid.label -text "List control" -font $fontf12 button .row3.mid.del -text Delete -borderwidth 2 -bg blue4 -fg yellow \ -font $fontf10 -command { set nr [.row3.left.mods curselection] ;# nr := index in list if { $nr > 0 } { ;# if 'nr' valid index set modules [lreplace $modules $nr $nr] ;# delete entry and update list } set nr [.row3.right.swits curselection] ;# same as above if { $nr >= 0 } { set switches [lreplace $switches $nr $nr] } } button .row3.mid.add -text Add -borderwidth 2 -bg gold \ -font $fontf10 -command { set nr [.row3.left.mods curselection] if { $nr >= 0 } { incr nr set modules [linsert $modules $nr $unicom] } set nr [.row3.right.swits curselection] if { $nr >= 0 } { incr nr set switches [linsert $switches $nr $unicom] } } pack .row3.left.mods .row3.left.scroll -side left -padx 1 -pady 2m -fill y pack .row3.mid.label .row3.mid.add .row3.mid.del -side top -padx 5m -pady 1m pack .row3.right.scroll .row3.right.swits -side right -padx 1 -pady 2m -fill y pack .row3.left .row3.mid .row3.right -side left -padx 5m -fill both # ROW 4 section : Master control button .row4.comp -text Compile -borderwidth 2 -bg pink -command compile -font $fontf12 button .row4.ls -text "ls -l" -borderwidth 2 -bg orange -command elless -font $fontf12 button .row4.run -text Run -borderwidth 2 -bg green -command testit -font $fontf12 button .row4.stop -text Quit -borderwidth 2 -bg cyan -command save -font $fontf12 button .row4.args -text "Set args" -borderwidth 2 -bg OliveDrab -fg white -font $fontf12 \ -command { set args $unicom $log insert end "Arguments : $args\n" set unicom "" } button .row4.edit -text Edit -borderwidth 2 -bg yellow -font $fontf12 \ -command { exec kate [lindex $modules [.row3.left.mods curselection]] } pack .row4.comp .row4.edit -side left -padx 3m -pady 2m -fill x pack .row4.stop .row4.run .row4.ls .row4.args -side right -padx 3m -pady 2m -fill x # ROW 5 section : Unix command label .row5.label -text "Text entry :" -font $fontf12 button .row5.clr -text Clear -font $fontf12 -bg DarkSalmon -borderwidth 3 \ -command {set unicom "" } button .row5.butt -text "Execute" -font $fontf12 -bg coral -borderwidth 3 \ -command { unixcomm $unicom } entry .row5.entry -width 40 -relief sunken -borderwidth 3 -textvariable unicom bind .row5.entry <Return> { unixcomm $unicom } pack .row5.label .row5.entry -side left -pady 2m -padx 2m pack .row5.clr .row5.butt -side right -fill x -padx 2m -pady 2m # ROW 6 section : LOG screen set log [ text .row6.llog -width 80 -height 15 -borderwidth 4 -relief groove \ -yscrollcommand { .row6.scroll set } -setgrid true ] scrollbar .row6.scroll -command { .row6.llog yview } pack .row6.llog -side left -fill both -expand true -padx 2 -pady 2m pack .row6.scroll -side right -fill y -padx 2 -pady 2m # Final packing up pack .row1 .row2 .row3 .row4 .row5 .row6 -side top -fill x } init run
Additions in version 005
The main differences for version 005 are:
proc askfile { } { global mainfile fontf10 fontf12 modules toplevel .rx wm title .rx "Ask for a module name" label .rx.conf -text "Please enter the name of the main source " -font $fontf12 entry .rx.entry -width 40 -relief sunken -borderwidth 3 -textvariable mainfile -font $fontf12 button .rx.butt -text Accept -borderwidth 2 -bg pink -font $fontf12 \ -command { set modules [list $mainfile] destroy .rx } pack .rx.conf .rx.entry .rx.butt -side left -padx 2m -pady 5m }
proc init { } { global modules switches fnameFor the rest, versions 4 and 5 are comparable.mainfile if {[file exists modules.list] == 1} { ;# if file exists, set fileID [open modules.list r] ;# open it and read it while { [ gets $fileID line] > 0 } { lappend modules $line } close $fileID} else { ;# otherwise, askfile ;# ask for a filename vwait modules ;# and wait for the askfile window to close } if {[file exists switches.list] == 1} { set fileID [open switches.list r] while { [ gets $fileID line] > 0 } { lappend switches $line } close $fileID } else { lappend switches "-w" } set name [lindex $modules 0] set nr [string last . $name] incr nr -1 set fname [string range $name 0 $nr] }
Page created on 27 September 2010 and