MetaCard Corporation

Comparison of MetaCard and Tcl/Tk Directory Browsers

This 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.

Screen Shot

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 Scripts

script 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