Comparison of MetaCard and Tcl/Tk Directory BrowsersThis article compares the "Directory Browser" application developed by Kevin Reichard and Eric F. Johnson for the article "Tickled Again", published in the March 1995 issue of Unix Review (Cross Thoughts, p 63-72), with an equivalent MetaCard stack. The scripts for both applications are included in this article, and can also be acquired via anonymous FTP. The MetaCard stack is directory-browser.mc, and the Tcl/Tk script is directory-browser.tcl.
The most significant difference between the two versions of the directory browser application is that scripts for the MetaCard version are less than 1/2 the length of the Tcl/Tk script (63 vs. 172 lines of uncommented code). This is primarily because in the MetaCard version, the UI was drawn instead of constructed by scripts. The time saved by being able to construct the interface using an IDT could not be calculated, but based on previous comparisons, savings of from 25% to 50% for an application of this size would be expected. Similarly, the MetaCard stack size is just over 1/2 the size of the Tcl script since the binary file format used for MetaCard stacks is much more efficient than the text-based description used by Tcl/Tk. The second major difference is that, although MetaCard does have functions for getting the files and the directories, a sort command, and wild-card expansion, there is no way to get file sizes. So the MetaCard version of this stack uses the "open process" command to run the "ls" command to get this information. Fortunately MetaCard has "word" chunk expressions so that it is very easy to get the various fields out of this listing. A third difference is in how the file size field is formatted. Tcl/Tk uses a "format" function which works like the C printf() function. This function can be used to right justify the text in the size field (note that the format function is not used to its best advantage in the Tcl script in the UR article, so padding the string must be done in a separate loop, whereas specifying %10.10s would have padded automatically). MetaCard also has a format function, but has something even better that Tk lacks: a property for right justifying text in a field. Not only is this easier than padding the string, but it also works with proportionally spaced fonts, and supports more appropriate behavior if the field is resized (i.e., there is no need to change the formatting code based on the field width). Fourth, note that the MetaTalk scripts can be understood even if you don't know the language. The Tcl scripts, on the other hand, are pretty much impenetrable without a Tcl manual. Finally, the MetaCard version is a Motif-compliant application, whereas the look and behavior of the Tcl/Tk version deviates from the Motif style guide in several places. For a more detailed analysis of the these two environments, see Interactive GUI Development Environments
MetaCard Scriptsscript for card "Browser": # handle the non-functional buttons, note that this calling # this handler is not done the same way as the analogous # "not_done" handler is called in Tcl. This handle takes # advantage of MetaCard's message passing hierarchy where an # event not handled by a button is automatically passed on # to the card. on mouseUp if word 1 of the target is "button" then put "Function not complete, sorry" into field "Status" end mouseUp on refresh set the title of this stack to the directory put "Reading" && the directory & "..." into field "Status" set the cursor to watch lock screen #to improve performance put ".." & return into field "Directories" put empty into f put empty into s put "ls -lo" into pname open process pname for read # skip "total" line read from process pname until return in 3 seconds repeat until it is empty # line read goes into "it" read from process pname until return in 1 second if char 1 of it is not "d" then put the last word of it & return after f put word 4 of it & return after s else put the last word of it & return\ after field "Directories" end repeat close process pname delete last char of field "Directories" delete last char of f delete last char of s put f into field "Files" put s into field "Sizes" set the thumbPos of scrollbar 1 to 0 set the endValue of scrollbar 1 \ to max(9, the number of lines in field "Files") + 1 put "Done reading" && the directory into field "Status" unlock screen end refresh # openCard it sent when the application is started up on openCard refresh end openCard script of button "Edit" on mouseUp put the selectedText of field "Files" into f if f is empty then put "No file to edit." into field "Status" else open process "/usr/bin/X11/xterm -geom 80x30 -e vi"\ && f for neither end mouseUp script of button "Run" on mouseUp put the selectedText of field "Files" into f if f is empty then put "No file to run." into field "Status" else open process f for neither end mouseUp script of button "Exit" on mouseUp quit end mouseUp script of field "Directory" on returnInField set the directory to me put empty into me refresh # call card script function to reload list boxes end returnInField script of field "Directories" on mouseUp # clicktext is word user clicked on set the directory to the clickText refresh # call card script function to reload list boxes end mouseUp script of scrollbar "dual" # Normally one would use tabstops to format items like this # in MetaCard, but Tk doesn't support tabstops, so they used # two fields and a scrollbar and so will we. on scrollbarDrag v set the scroll of field "Files" \ to v * the textHeight of field "Files" set the scroll of field "Sizes" \ to v *the textHeight of field "Sizes" end scrollbarDrag Tcl/Tk Script# # edir.tk # Tcl/tk script to browse disk directories. # # Eric Johnson # # # Globals: # dir_list - directory list # fil_list - file list # siz_list - list of file sizes # status - status area # cur_file - current selected file global dir_list fil_list siz_list \ status cur_file set dir_list .view.dir.frame.dir_list set fil_list .view.fil.frame.fil_list set siz_list .view.fil.frame.siz_list set status "edir version 0.0." set cur_file "" # Error-handling proc tkerror { err_str } { global status set status "Error: $err_str" # Show on screen. update idletasks } # Function to change dir. proc ch_dir { dir } { global dir_list fil_list \ siz_list status cur_file set status "Changing to $dir" cd $dir set new_dir [pwd] wm title . $new_dir set status "Reading $new_dir..." # Clear current file. set cur_file "" # Show on screen. update idletasks fill_lists $dir_list $fil_list $siz_list set status "Done reading $new_dir." } # Fills lists with files in dir proc fill_lists { dir_l fil_l siz_l } { global dir_list fil_list siz_list status # delete everything in the lists already. $dir_l delete 0 [$dir_l size] $fil_l delete 0 [$fil_l size] $siz_l delete 0 [$siz_l size] $dir_l insert end ".." foreach filename [lsort [glob *]] { set dir_flag [file isdirectory $filename] if { $dir_flag == 1 } { $dir_l insert end $filename } else { $fil_l insert end $filename set sz [file size $filename] set sz_str [format "%10s" $sz] # Fill out string. while { [string length $sz_str] < 10 } { set sz_str " $sz_str" } $siz_l insert end $sz_str } } } # Creates top area frame for lists. proc top_frame { base_name label_text } { frame $base_name -borderwidth 0 label $base_name.label -text $label_text frame $base_name.frame -relief groove \ -borderwidth 3 pack $base_name.label $base_name.frame } proc make_list { base_name scroll_name } { listbox $base_name -borderwidth 1 \ -relief raised \ -yscrollcommand " $scroll_name set" pack $base_name -side left } # # This proc scrolls a number of listboxes # all together from one scrollbar. # proc multi_scroll { my_scroll args } { # Get info on list of args. set len [llength $args] set elem [expr $len-1] # The amount to scroll is # the last element of args. set amount [lindex $args $elem] # Rest of args are widget names. set i 0 while { $i < $elem } { set temp_list [lindex $args $i] # Scroll list. $temp_list yview $amount incr i } } proc not_done { } { global status set status \ "Function not complete, sorry." } proc run_file { } { global cur_file if { ! [string compare "" $cur_file] } { set status "No file to run." } else { exec $cur_file & } } proc edit_file { } { global cur_file if { ! [string compare "" $cur_file] } { set status "No file to edit." } else { # Change the editor if desired. #exec /usr/bin/X11/xterm -geom 80x30 \ # -e vi $cur_file & exec nedit $cur_file & } } # # Create Interface. # # Toolbar area. # Completing these is left as an # exercise for the reader. # frame .toolbar -borderwidth 0 button .toolbar.edit -text "Edit" \ -command edit_file -padx 10 button .toolbar.exec -text "Run" \ -command run_file -padx 10 button .toolbar.copy -text "Copy" \ -command not_done -padx 10 button .toolbar.move -text "Move" \ -command not_done -padx 10 button .toolbar.rename -text "Rename" \ -command not_done -padx 10 button .toolbar.delete -text "Delete" \ -command not_done -padx 10 button .toolbar.exit -text "Exit" \ -command {exit} -padx 10 pack .toolbar.edit -side left pack .toolbar.exec .toolbar.copy \ .toolbar.move .toolbar.rename \ .toolbar.delete .toolbar.exit \ -side left # File/Dir view area. frame .view -borderwidth 0 # Area for directories top_frame .view.dir "Directories" make_list $dir_list .view.dir.frame.scroll scrollbar .view.dir.frame.scroll \ -command "$dir_list yview" pack .view.dir.frame.scroll \ -side right -fill y # Area for files top_frame .view.fil \ "Files and Sizes (In Bytes)" make_list $fil_list .view.fil.frame.scroll make_list $siz_list .view.fil.frame.scroll scrollbar .view.fil.frame.scroll \ -command {multi_scroll \ .view.fil.frame.scroll \ $fil_list $siz_list} pack .view.fil.frame.scroll \ -side right -fill y pack .view.dir -side left pack .view.fil -side right # Goto dir area. frame .goto -borderwidth 0 label .goto.label \ -text "Go to Directory:" -anchor w entry .goto.entry -borderwidth 3 \ -relief sunken bind .goto.entry <Return> { set data [%W get] %W delete 0 end ch_dir $data } pack .goto.label -side left pack .goto.entry -side right -fill x # Status area. label .show_status \ -textvariable status -anchor w # Set up generic list options. tk_listboxSingleSelect Listbox bind Listbox <B1-Motion> "" bind Listbox <Shift-B1-Motion> "" # Bindings for mouse buttons. bind $dir_list <1> { # Get selected item. %W select from [%W nearest %y] set elem [%W curselection] set data [%W get $elem] ch_dir $data } # Bindings for mouse buttons. bind $fil_list <1> { global cur_file # Get selected item. %W select from [%W nearest %y] set elem [%W curselection] # Store current filename in global. set cur_file [%W get $elem] } bind $siz_list <1> { global cur_file # Get selected item. %W select from [%W nearest %y] set elem [%W curselection] # Get fil_list element instead. # Store current filename in global. set cur_file [$fil_list get $elem] } # Start with current dir. ch_dir [pwd] # Display interface. wm iconname . edir wm geom . +100+100 pack .toolbar -fill x pack .view pack .goto pack .show_status -fill x# end of file edir.tk
Back to IDE Sample Application |