TweetFollow Us on Twitter

A platform for protecting mail servers.

Volume Number: 20 (2004)
Issue Number: 6
Column Tag: Programming

QuickTime Toolkit

by Tim Monroe

Tickle Me

Developing QuickTime Applications with Tcl/Tk

Introduction

Tcl/Tk is a combination of two distinct elements: Tcl, which is a string-oriented scripting language and runtime library, and Tk, which is a toolkit for constructing graphical user interfaces. These elements were designed and implemented more or less concurrently at U.C. Berkeley beginning in 1988 by a team led by John Ousterhout. His goal was to develop an extensible language that various software tools could use to control the execution and processing of commands. Indeed, "Tcl" is an acronym for Tool Command Language (and is usually pronounced "tickle").

One distinctive feature of Tcl/Tk -- at least compared with the development environments we've considered in the past dozen articles -- is that its source code is publicly available and largely free of restrictions on its use. This has several important consequences. First, it means that Tcl can easily be embedded into an existing application to provide a scripting interface for that application; if necessary, Tk can also be embedded into that application to provide a graphical user interface. Second, it means that new capabilities can easily be added to Tcl and Tk by defining new commands and GUI objects. For present purposes, we'll be interested in a Tcl/Tk add-on called QuickTimeTcl, which allows us to work with QuickTime movies in a Tcl/Tk application. QuickTimeTcl is also freely available and can be incorporated into commercial and non-commercial products.

In this article and the next, I want to take a look at using Tcl/Tk and QuickTimeTcl to develop QuickTime applications. We'll construct an application, called TickLeez, that can open, display, edit, and play one or more QuickTime movies, while permitting all the standard sorts of user interaction. In this first article, we'll begin with an overview look at Tcl, Tk, and QuickTimeTcl. Then we'll see how to start up a Tcl/Tk application, how to construct and handle our application's menus, how to display dialog boxes, and how to open a QuickTime movie in a window. In the next article, we'll continue our investigation by seeing how to handle movie editing and document-related tasks.

All three important elements -- Tcl, Tk, and QuickTimeTcl -- are available for both Macintosh and Windows operating systems. We'll write our application script so that it is aware of the operating environment and behaves appropriately on each platform. As we saw in our previous articles on QuickTime for Java (in MacTech, January, February, and March 2004), this mainly involves making sure that the menus are arranged correctly (for instance, putting the Quit menu item in the File menu on Windows and in the Application menu on Mac OS X). There are also several other minor issues that require different treatment on each operating system. On the whole, however, we'll see that Tk, like Java's AWT and Swing, provides a very usable platform-independent graphical toolkit that allows us to script user interface objects with little or no concern as to whether they are being displayed on Mac or Windows computers.

Tcl Overview

As already noted, Tcl is a scripting language used by tools for processing commands. Tcl is refreshingly simple and readable, particularly when compared with other popular scripting languages (for instance, Perl). It's a procedural language that has a predominately C-like syntax. Tcl does not provide any object-oriented primitives, but there is an object-oriented extension of Tcl, called "[incr tcl]". (You'll learn shortly what the name signifies.) In these articles, we'll be content to work with standard Tcl.

Getting Started

A Tcl script is a series of one or more commands, each of which has this structure:

command arguments

The number of arguments is determined by the specific command. For instance, we can print the phrase "hello, world" with this line of script:

puts stdout "hello, world"

Here "puts" is the command, and "stdout" and "hello, world" are the two arguments for the command. Notice that the second argument is the entire 12-character sting enclosed by the quotation marks ("").

We can assign a value to a variable using the set command, and we can read a variable's current value by prefixing the $ character. For instance:

set message "hello, world"
puts stdout $message

These two lines produce exactly the same output as the single line shown above.

We can prevent substitution of variable values by prefixing the $ character with a backslash:

puts stdout \$message

This will produce the output "$message". Alternatively, we can use braces to delimit arguments whose variables are not to be substituted:

puts stdout {$message}

Once again, this will produce the output "$message".

Braces can also be used to delimit the names of variables when the variable name is embedded within a longer string. For instance:

puts stdout ${message}ling

This will produce the output "hello, worldling".

Evaluating Commands

A command occurring by itself on a line is evaluated and any value the command produces is returned as the command result. It's also possible to nest commands on a single line, using brackets. Here's an example:

   set x [expr [winfo screenwidth .myWindow]/2 - 180]

Here we call two nested commands, winfo (a Tk command that retrieves the specified information about a window, in this case the width of the screen that contains the window .myWindow) and expr (a Tcl command that evaluates the expression passed to it). Once this command line is executed, the variable x will contain the horizontal position at which we should position a window that is 360 pixels wide if we want to center it on the screen.

Tcl provides a large number of built-in commands. We've already encountered the set, puts, and expr commands. A handful of additional Tcl commands are unset, if, while, switch, array, and return. The incr command increments the value of a variable by a specified amount, or by 1 if no amount is specified. Thus, if the value of the variable xyz is 10, then the following command will set the value of xyz to 11:

incr xyz

In Tcl, any built-in commands can be redefined. In the following article, we'll need to redefine the built-in command exit to allow TickLeez a chance to clean up before the application terminates.

Defining Procedures

We can define procedures in Tcl using the proc command. The general form of a procedure definition is this:

proc name {arguments} {commands}

For instance, Listing 1 defines two procedures, min and max, which return the smaller or larger of the two values passed in as arguments.

Listing 1: Getting greater and lesser values

max, min
proc max {a b} {
   if {$a > $b} {set a} else {set b}
}
proc min {a b} {
   if {$a < $b} {set a} else {set b}
}

Notice that the argument list is enclosed in braces, to delay evaluation of the arguments until the procedure is called. Notice also that a value is returned to the caller as the side effect of executing the set command. (A procedure's return value is the return value of the last command executed in the procedure.) We could just as easily write the min procedure like this, which uses the return command:

proc min {a b} {
   if {$a < $b} {return $a} else {return $b}
}

There is one final item to keep in mind: there must be a space following the right brace that encloses the argument list; otherwise the Tcl interpreter will complain that there are extra characters after the closing brace.

Using Structured Data

The basic data type in Tcl is the string; all arguments are passed as strings, although certain commands (for instance incr) may interpret their input numerically. Tcl also provides two ways to combine strings into structures: lists and arrays. A list is roughly equivalent to a string whose elements are separated from one another by white space. Tcl provides a large number of commands to create and operate on lists, including list, lindex, llength, lappend, concat, and split. List indices start at 0; the word end can be used to pick out the last element in a list.

For large collections of data elements, it's often easier to use an array, which is a variable whose values are addressed using a string index. (Tcl arrays are also called associative arrays, dictionaries, or hashes.) In TickLeez, we will employ arrays extensively. In particular, we will store six pieces of global application data in an array called appData. The array element appData(winNo) will contain the number of the next window to open; we'll use this information to construct a unique internal name that identifies that window. The array element appData(nextNewWinNo) will contain the number of the next new window to open; we'll use this information to construct a movie name of the form "Untitledn.mov". The array element appData(os) will contain a string that identifies the current operating system. The array element appData(modKey) will contain the name of the OS-specific modifier key (namely, either "Command" or "Control"). The array element appData(winTag) will contain a unique tag we use to construct the names of our movie windows. Finally, the array element appData(tempDir) will contain the full pathname of a directory that holds any temporary files we need to create.

We'll also need to associate some data with each movie window that we open. For instance, we'll need to maintain information about the movie contained in a window, such as its edited state or the pathname of the file it was loaded from. Unfortunately, Tk does not provide a standard means to attach a list or array to a specific window (as we could do using the functions SetWRefCon and SetWindowLong in our C-based applications). We can work around this limitation by storing that data in the appData global associative array using array indices that contain the window's name. For instance, if the winName variable contains the name of a specific window, then we can use the string "$winName,fileName" as an index into the appData array; the value associated with that index is the full pathname of the movie file.

We'll use this little trick to store five pieces of information in the appData array for each open movie. The array element appData($winName,movie) will contain the QuickTimeTcl identifier for the movie. The array element appData($winName,dirty) will contain the character "1" or "0", indicating whether the movie has been edited or not. The array element appData($winName,undoLevel) will contain the undo level for the movie (that is, the number of undoable edits that have been applied to the movie). Finally, the array element appData($winName,undoOp,n) will contain a string indicating which edit operation was applied a the undo level n. We'll use that string to set the text of the Undo menu item, as we'll see in the next article.

In order for the elements of the appData associative array to be visible throughout our application, we need to include the following line at the beginning of any procedure that accesses the data in that array:

global appData

Note that we could have used individual variable names for the six pieces of global application data; by collecting them all into one array, we reduce the number of global statements we need to use in each procedure.

Tk Overview

Tk (short for "Toolkit" and usually pronounced "tee-kay") is a toolkit for constructing graphical user interfaces. We can use it to create and display the windows, alert boxes, dialog boxes, menus, and other visible elements of our application. Moreover, Tk handles events and user actions involving the elements in those windows, dialog boxes, and menus. In other words, it provides event-driven control flow for our applications. Originally Tk was designed to work in conjunction with the X Windowing System, but subsequently it has been modified to support other graphical user interface systems, including those on Mac OS and Windows. On Mac OS X, Tk uses the Aqua interface, although it currently has a few drawing and event-handling glitches.

Creating Windows

In Tk parlance, a widget is any visible user interface element. In TickLeez, we'll be mainly concerned with three kinds of widgets: windows, menus, and movies. It's very easy to create a new window. Here's part of the code we'll use to create our application's About box:

toplevel .about -width 360 -height 160

The toplevel command creates a new toplevel window, in this case one that is 360 by 160 pixels. The first argument to the toplevel command is the name of the new window. In Tk, widgets are arranged in a hierarchy, the root of which is a toplevel window whose name is ".". All new widgets must have names that are relative to some existing widget. In particular, all widgets must begin with the "." character. As you can see, the name of our About box is ".about".

The names of windows are reasonably arbitrary, as long as they too begin with the "." character. To make it easy to find all and only the movie windows, let's give all our movie windows names of the form ".winRTMn", for some integer n. So the first movie window we create will have the name ".winRTM1"; the second will be ".winRTM2", and so on. As noted earlier, the array element appData(winNo) holds the next available window number, so we can generate a movie window name like this:

set winName $appData(winTag)[incr appData(winNo)]

Based on this naming scheme, we can tell whether a given window is a movie window by passing its name to the isMovieWindow procedure, shown in Listing 2. isMovieWindow just looks to see whether the winName argument begins with the string "winRTM".

Listing 2: Finding movie windows

isMovieWindow
proc isMovieWindow {winName} {
   global appData
   if {[string first $appData(winTag) $winName] == 0} {
      return 1
   } else {
      return 0
   }
}

An alternative strategy is to set a movie window's class by including the "-class RTM" option when we create the movie window. In that case, the if line in Listing 2 would look like this:

   if {[string equal [winfo class .$winName] RTM]} {

In a large application with many kinds of windows, using window classes is probably preferable. In TickLeez, since we display only movie windows and the About box, we'll stick with the method that uses a set of special window names.

We can find the frontmost movie window by calling the topMovieWindow procedure defined in Listing 3. First we create a list of all open toplevel windows by executing the wm stackorder command. Then we look at each window, from top to bottom, until we find one that satisfies the isMovieWindow procedure.

Listing 3: Finding the frontmost movie window

topMovieWindow
proc topMovieWindow {} {
   global appData
   set winlist [wm stackorder .]
   set index [expr [llength $winlist] - 1]
   
   while {![isMovieWindow [string range \
      [lindex $winlist $index] 1 end]] && ($index >= 0)} {
      incr index -1
   }
   return [string range [lindex $winlist $index] 1 end]
}

The calls to range chop off the first character of the specified list element (that is, "."). If no movie windows are open, topMovieWindow returns an empty string.

Creating Menu Bars and Menus

We can use Tk to create and manage menu bars and menus, which can contain standard menu items, separator lines, checkbutton items, and radiobutton items. A checkbutton menu item toggles between two states, and radiobutton menu items are organized into groups such that exactly one item in the group is selected at any time. Menu items are in fact just buttons that behave in specific ways and that are grouped into menus.

A menu bar is always associated with a specific toplevel window; this means that we'll need to create a menu bar and a set of menus for each movie window opened by TickLeez. On Windows computers, the menu bar is contained in the movie window itself, as shown in Figure 1. On Macintosh computers, the menu bar is drawn at the top of the main screen, as usual.


Figure 1: A movie window displayed by TickLeez (Windows)

A menu bar is in fact itself a menu. In Tk this is known as a cascading menu, since the items of the menu cascade from left to right. We create a menu bar by executing the menu command:

set bar [menu ${w}mbar -tearoff 0]

Here the variable w is assumed to be the full name of a window. So, for instance, the movie window whose name is ".winRTM1" would have an associated menu bar whose name is ".winRTM1mbar". We associate a menu bar with a window like this:

$w configure -menu $bar

On Macintosh computers, we will create a menu bar and set of menus for the root window ".", even though we hide the root window during application startup. This allows TickLeez to display a menu bar even when no movie windows are open. On Windows, we don't create a menu bar and set of menus for the root window; instead, we create a new empty movie window (Figure 2) when the application starts up; the user can use the menus in that window to open movie files. (We'll see the code that does all this a bit later, in Listing 7.)


Figure 2: A new movie window displayed by TickLeez (Windows)

We can add menus to the menu bar with the add cascade command. Here's how we'll create a File menu and add it to the menu bar:

set m [menu $bar.file -tearoff 0]
$bar add cascade -label "File" -menu $m

And we can add menu items to a menu with the menu's add command command. Here's how we'll add the New item to the File menu:

$m add command -label "New" \
      -accelerator "$appData(modKey)-N" -command {newDoc}   

Notice that we specify the TickLeez procedure that is to be called when this menu item is selected by using the -command argument.

Listing 4 show the complete definition of the setupMenus procedure.

Listing 4: Setting up the menus and menu items

setupMenus
proc setupMenus {w} {
   global appData
   # create a new menu bar
   set bar [menu ${w}mbar -tearoff 0]
   
   # File menu   
   set m [menu $bar.file -tearoff 0]
   $bar add cascade -label "File" -menu $m
   
   $m add command -label "New" \
         -accelerator "$appData(modKey)-N" -command {newDoc}   
   $m add command -label "Open..." \
         -accelerator "$appData(modKey)-O" -command {openDoc}
   $m add command -label "Close" \
         -accelerator "$appData(modKey)-W" -command {closeDoc}
   
   $m add sep
   
   $m add command -label "Save" \
         -accelerator "$appData(modKey)-S" -command {saveDoc}
   $m add command -label "Save As..." \
         -accelerator "$appData(modKey)-Shift-S" \
         -command {saveAsDoc}
   
   if {[string equal $appData(os) "windows"]} {
      $m add sep
      $m add command -label "Exit" \
         -accelerator "$appData(modKey)-Q" -command {exit}
   }
   
   #   Edit menu
   set m [menu $bar.edit -tearoff 0]
   $bar add cascade -label "Edit" -menu $m
   
   $m add command -label "Undo" \
         -accelerator "$appData(modKey)-Z" -command {undoDoc}
   
   $m add sep
   
   $m add command -label "Cut" \
         -accelerator "$appData(modKey)-X" \
         -command {editDoc cut}
   $m add command -label "Copy" \
         -accelerator "$appData(modKey)-C" \
         -command {editDoc copy}
   $m add command -label "Paste" \
         -accelerator "$appData(modKey)-V" \
         -command {editDoc paste}
   $m add command -label "Clear" -command {editDoc clear}
   
   $m add sep
   
   $m add command -label "Select All" \
         -accelerator "$appData(modKey)-A" \
         -command {selectDoc all}
   $m add command -label "Select None" \
         -accelerator "$appData(modKey)-B" \
         -command {selectDoc none}
   
   #   Movie menu
   set m [menu $bar.movie -tearoff 0]
   $bar add cascade -label "Movie" -menu $m
   
   $m add command -label "Hide Controller Bar" \
         -accelerator "$appData(modKey)-1" \
         -command {puts "TO BE PROVIDED"}
   
   #   Help menu
   if {[string equal $appData(os) "windows"]} {
      set m [menu $bar.help -tearoff 0]
      $bar add cascade -label "Help" -menu $m
   } else {
      set m [menu $bar.apple -tearoff 0]
      $bar add cascade -menu $m 
   }
   
   $m add command -label "About TickLeez" \
         -command {showAboutBox}
   $w configure -menu $bar
}

We'll encounter the various procedures attached as commands later on in this article and the next.

Adding the About Box

Let's look at a real-life example of using Tk to display and manage a window on the screen, in this case our application's About box. The Windows version of the About box is shown in Figure 3, and the Macintosh version is shown in Figure 4.


Figure 3: The About box of TickLeez (Windows)


Figure 4: The About box of TickLeez (Macintosh)

You'll notice that the appearance of the Macintosh About box isn't quite right; Tk currently is unable to draw the horizontal stripes that form the background of an Aqua dialog box.

The first time the user selects the About TickLeez menu item to display the About box, we'll use the toplevel command to create a new toplevel window, as mentioned above. When the user closes the About box, however, we won't destroy the window. Rather, we'll simply hide the window (by issuing the withdraw command); if the user later decides to display the About box again, we can just show the hidden window with these commands:

wm state .about normal
raise .about

This saves us from having to create and delete the images in the About box each time it's displayed.

Our About box contains four widgets: the OK button, a static text field, and two additional buttons. We can create and position the OK button with this code:

place [button .about.ok -text OK -default active \ 
            -width 5 -command {wm withdraw .about}] \
            -x 284 -y 124

As you can see, the command associated with the OK button is to withdraw (that is, hide) the window .about, which is the About box itself.

The two additional buttons are used to display the images in the About box. In TickLeez, the About box is configured in such a way that clicking the penguin image changes that picture to the Tcl logo. Figure 5 shows the result of clicking the penguin image in Figure 4.


Figure 5: The About box of TickLeez (clicked)

We can load the penguin image like this:

set penguin [image create photo -file \
      [file join [pwd] penguin.jpeg]]

And we create and position the button like this:

place [button .about.pen -image $penguin \
      -width 76 -height 105 -relief flat \
      -command "raise .about.tcl .about.pen"] -x 20 -y 8

Toggling the two images is accomplished using the raise command; when passed two arguments, it causes the first image to be drawn on top of the second image. Since our two images are cleverly configured to be the same size, this causes one image to become visible and the other invisible.

Listing 5 shows our complete definition of the showAboutBox procedure.

Listing 5: Showing the About box

showAboutBox
proc showAboutBox {} {
   global appData
   # if the About box exists already, just show it and bring it to the front
   if {[winfo exists .about]} {
      wm state .about normal
      raise .about
      return
   }
   # otherwise, create the About box
   toplevel .about -width 360 -height 160
   
   wm resizable .about 0 0
   wm title .about "About TickLeez"
   
   if {[string match "mac*" $appData(os)]} {
      ::tk::unsupported::MacWindowStyle style .about \
         document {closeBox}
   }
   place [button .about.ok -text OK -default active \ 
            -width 5 -command {wm withdraw .about}] \
            -x 284 -y 124
   place [label .about.what -wraplength 220 -justify left \
            -text "A QuickTime movie player built with \
            Tcl/Tk.\n\n\u00a9 2004 by Tim Monroe"] -x 120 -y 12
   set penguin [image create photo -file \
      [file join [pwd] penguin.jpeg]]
   set feather [image create photo -file \
      [file join [pwd] tcl.jpeg]]
   place [button .about.pen -image $penguin \
            -width 76 -height 105 -relief flat \
            -command "raise .about.tcl .about.pen"] -x 20 -y 8
   place [button .about.tcl -image $feather \
            -width 76 -height 105 -relief flat \
            -command "raise .about.pen .about.tcl"] -x 20 -y 8
   raise .about.pen .about.tcl
   bind .about <Return> {.about.ok flash; .about.ok invoke}
   wm protocol .about WM_DELETE_WINDOW {.about.ok invoke}
   
   # center the About box on the screen
   set x [expr [winfo screenwidth .about]/2 - 180]
   set y [expr [winfo screenheight .about]/2 - 80]
   wm geom .about +$x+$y
}

Notice that Return key presses and clicks in the window's close box are configured to have the same effect as pressing the OK button.

We have laid out the widgets in this dialog box using the place command, by specifying item positions in terms of absolute coordinates in the dialog box. Here we are using the place geometry manager. Tk also provides the pack geometry manager, which lays out items in frames, which can be grouped and positioned relative to other frames. For complex widget positioning, the pack geometry manager is virtually always preferred. Indeed, using the place geometry manager with explicit coordinates is generally considered bad practice. We can get away with using it here because our About box is so simple.

QuickTimeTcl Overview

QuickTimeTcl is an extension to Tcl/Tk that we can use to open, display, and modify QuickTime movies. It was originally written by Bruce O'Neel, and has subsequently been significantly enhanced and extended by Mats Bengtsson. QuickTimeTcl supports QuickTime movie playback and editing on all three major platforms currently supported by QuickTime: classic Mac OS 8 and 9, Mac OS X, and Windows.

QuickTimeTcl extends Tcl/Tk by defining two new widgets, movie and seqgrabber, which support movie playback and editing, and audiovisual capture. In addition, QuickTimeTcl supports a handful of other commands that provide information or supplementary capabilities; for instance, it supports the function tk_getOpenFilePreview, which extends the Tk function tk_getOpenFile by adding a file preview to the file-opening dialog box and by automatically restricting the selectable files to those that can be opened by QuickTime. Here's how easy it is to open a file using QuickTimeTcl:

package require QuickTimeTcl 
set filename [tk_getOpenFilePreview]
movie .m -file $filename
pack .m

We indicate that the QuickTimeTcl package is required; we elicit a filename from the user; we configure a movie object in the root window; and then we pack the movie widget into the root window. Since the movie is the only widget in the window, the window is sized to exactly hold the movie.

QuickTimeTcl is a reasonably complete Tcl/Tk wrapper for the most common QuickTime APIs. It provides support for movie editing, importing and exporting, applying video effects to movies and images, displaying movies full screen, and creating movies from a series of images. QuickTimeTcl also provides a mechanism to intercept and monitor a large number of messages passed to the QuickTime movie controller, including notifications of key events, custom button clicks, time and volume changes, mouse button clicks, selection changes, and (for QuickTime VR movies) changes in the pan, tilt, or field of view angles. This mechanism stands in for several of QuickTime's callback procedures, in particular for the movie controller action filter procedure.

If we need to extend QuickTimeTcl to support capabilities that it does not currently provide (for instance, support for intermovie communication), we can download and modify the source code, which is freely available. The QuickTimeTcl extension is written in fairly vanilla C, and indeed relies in part on some of the sample code we've considered earlier in this QuickTime Toolkit series of articles. For instance, Listing 6 shows the routine that QuickTimeTcl uses to determine the height of the movie controller bar.

Listing 6: Finding the height of the controller bar

GetControllerBarHeight
short GetControllerBarHeight (MovieController mc) 
{
   Boolean         wasAttached = false;
   Rect               myRect;
   short            myHeight = 0;
   
   /* If the controller bar is attached, detach it (and remember we did so) */
   if (MCIsControllerAttached(mc) == 1) {
      wasAttached = true;
      MCSetControllerAttached(mc, false);
   }
   
   /* Get the rectangle of the controller */
   MCGetControllerBoundsRect(mc, &myRect);
   myHeight = myRect.bottom - myRect.top;
   
   /* Now reattach the controller bar, if it was originally attached */
   if (wasAttached) {
      MCSetControllerAttached(mc, true);
   }
   return(myHeight);
}

If you have a very good memory, you may recognize this as a slightly modified version of the function QTUtils_GetControllerBarHeight that we encountered in the article "Movie Controller Potpourri" (MacTech, February 2000).

Note that QuickTimeTcl can also be used as a pure scripting tool, with no user interface. This would allow us, for instance, to use Tcl scripts to automate various movie operations, such as creating a movie from a series of images or transcoding existing movies into new formats. In these articles, we will not investigate this way of using QuickTimeTcl.

Application Startup

Let's get started writing TickLeez, which we want to be able to open and display QuickTime movies, allow the user to edit those movies, and exhibit all the standard document-related behaviors. The TickLeez script begins with the "package require QuickTimeTcl" directive considered earlier. Then it defines several dozen procedures and ends with this command:

main

Our main procedure performs launch-time initializations and adds the six pieces of global information to the appData array. Listing 7 shows our implementation of the main procedure. We make sure that the Tk package is available and then set the appData(os) array element to either "windows", or "macintosh", or "macosx", depending on whether the script is executing on Windows, Mac OS 8 or 9, or Mac OS X.

Listing 7: Opening the application

main
proc main {} {
   global appData
   
   # make sure the resources we need are available
   if {[catch {package require Tk}]} {
        tk_messageBox -icon error -type ok \
                           -message {Tk did not initialize.}
      exit 1
   }
   # we use a variable 'appData(os)' that is more convenient for Mac OS X
   switch -- $::tcl_platform(platform) {
      unix {
         set appData(os) $::tcl_platform(platform)
         if {[package vcompare [info tclversion] 8.3] == 1} {   
            if {[string equal [tk windowingsystem] "aqua"]} {
               set appData(os) "macosx"
            }
         }
      }
      windows - macintosh {
         set appData(os) $::tcl_platform(platform)
      }
   }
   
   if {[string match "mac*" $appData(os)]}  {
      set appData(modKey) Command
   } else {
      set appData(modKey) Control
   }
   
   # initialize global variables
   set appData(winNo) 0
   set appData(nextNewWinNo) 0
   set appData(winTag) winRTM
   # create a new empty directory for temporary files
   if {[info exists ::env(TMPDIR)] && \
         [file isdirectory $::env(TMPDIR)]} {
      set appData(tempDir) \
            [file join $::env(TMPDIR) $appData(winTag)[pid]]
  } else {
      if {[string match "mac*" $appData(os)]} {
         set tmpDir /tmp
      } else {
         set tmpDir [pwd]
      }   
      
      set appData(tempDir) \
            [file join $tmpDir $appData(winTag)[pid]]
   }
   if {[file exists $appData(tempDir)]} {
      file delete -force $appData(tempDir)
   }
   
   file mkdir $appData(tempDir)
   # set the current directory
   cd [file dirname [info script]]
   # set up the menus
   if {[string match "mac*" $appData(os)]} {
      setupMenus .
      adjustMenus
      setupBindings ""
   } else {
      newDoc
   }
   # hide the root window and the Console window
   wm withdraw .
   console hide
}

If we're running on Windows, we call the newDoc procedure to open a new empty movie window. (See Figure 2 again.) Otherwise, we call the three procedures setupMenus, adjustMenus, and setupBindings, which we'll consider in depth in the next article. On both Mac and Windows, we hide the root window (whose name is ".") and the Console window.

Movie Windows

Earlier we saw how to use the toplevel command to create a window. A movie window in TickLeez is just a toplevel window that contains a movie widget. We can create a movie widget by specifying a unique pathname to the movie command:

movie .$winName.mov

And we can attach a movie file to that widget using the configure command:

.$winName.mov configure -file $fileName

In TickLeez, we want a movie window to be editable and resizable; we also want to be able to intercept movie controller actions targeted at the movie. So we'll call the configure command again, like this:

.$winName.mov configure -mccommand controllerProc \
               -mcedit 1 -resizable 1

In the next article, we'll see how to define the controllerProc procedure.

Next we need to resize the window to exactly contain the movie and its associated movie controller bar. We can use the pack command, which resizes a window to exactly contain the widgets inside it:

pack .$winName.mov

Listing 8 shows the complete definition of the openFileInWindow function. Given a full pathname for a movie file and a value that indicates whether the movie is a new movie, it opens that movie in a window on the screen. (Note that the -resizable option does not appear to work correctly on Windows. This may be fixed in a future QuickTimeTcl release.)

Listing 8: Opening a movie in a new window

openFileInWindow
proc openFileInWindow {fileName isNew} {
   global appData
   # create a unique Tk path for the new window
   set winName $appData(winTag)[incr appData(winNo)]
   
   # create a new window
   toplevel .$winName
   wm resizable .$winName 0 0
   wm title .$winName [file tail $fileName]
   
   wm protocol .$winName WM_DELETE_WINDOW \
               "attemptClose $winName closing"
   if {[string match "mac*" $appData(os)]} {
      ::tk::unsupported::MacWindowStyle style .$winName \
         document {closeBox horizontalZoom collapseBox}
   }
   movie .$winName.mov
   
   if {$isNew} {
      .$winName.mov new $fileName
   } else {
      .$winName.mov configure -file $fileName
   }
   
   .$winName.mov configure -mccommand controllerProc \
               -mcedit 1 -resizable 1
   pack .$winName.mov
   
   # save window-specific info in global array
   set appData($winName,movie) .$winName.mov   
   set appData($winName,dirty) 0   
   set appData($winName,fileName) $fileName   
   set appData($winName,undoLevel) 0
   
   # force the new window to be displayed
   update idletasks
   
   setupMenus .$winName
   adjustMenus
   
   setupBindings $winName
}

The openFileInWindow procedure is called by two menu-handling procedures, newDoc and openDoc. The newDoc procedure, shown in Listing 9, creates a filename for a non-existent file in our application's temporary directory. QuickTimeTcl requires that a file be associated with every local movie it manages, even for new empty movies.

Listing 9: Creating a new empty movie

newDoc
proc newDoc {} {
   global appData
   
   # new windows are entitled "Untitled.mov", "Untitled2.mov", "Untitled3.mov"....
   incr appData(nextNewWinNo)
   set visWinNum $appData(nextNewWinNo)
   if {$visWinNum == 1} {
      set visWinNum ""
   }
   
   # construct a new temp file name
   set filename [file join $appData(tempDir) \
                                       "Untitled${visWinNum}.mov"]
   
   openFileInWindow $filename 1
}

The openDoc function, shown in Listing 10, elicits a filename from the user by calling the tk_getOpenFile command. This command displays the standard file-opening dialog box and returns the full pathname of the selected file (or the empty string if no file was selected).

Listing 10: Handling the Open menu item

openDoc
proc openDoc {} {
   set filename [tk_getOpenFile -title "TickLeez: Open a \
                                                         Movie File"]
   if {$filename != ""} {
      openFileInWindow $filename 0
   }
}

Conclusion

In this article, we've taken a preliminary look at Tcl/Tk and the QuickTimeTcl add-on, which allows us to use the Tcl/Tk scripting language to build a QuickTime-savvy application. We've fashioned our menus and menu bars, and we've seen how to add a simple dialog box -- our application's About box -- to an application. Most important, we learned how to use QuickTimeTcl commands to open a movie file and display it in a movie on the screen.

In the next article, we'll continue working with Tcl/Tk as a delivery platform for QuickTime applications. We'll see how to bind certain events to our application's procedures, and we'll see how to handle standard movie editing. That of course will require that we also implement the standard document behaviors, such as prompting the user to save or discard unsaved changes to a movie that's being closed.

Acknowledgements and References

Special thanks are due to Jim Ingham and to Mats Bengtsson for reviewing a draft of this article and for providing invaluable feedback.

The latest Tcl/Tk release for Windows computers is available at the Tcl/Tk Developer Xchange site (http://www.tcl.tk). You can download the QuickTimeTcl extension at http://hem.fyristorg.com/matben/qt. For Macintosh OS X computers, you should use the all-inclusive Tcl/Tk distribution available at http://sourceforge.net/projects/tcltkaqua/.


Tim Monroe is a member of the QuickTime engineering team at Apple. You can contact him at monroe@mactech.com. The views expressed here are not necessarily shared by his employer.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.