X Window System Programming with Tcl and Tk
The first menu entry on the File menu, ”Save PostScript...“, executes the command get_ps, which is a procedure which we have defined earlier in the script. The proc command is used to define procedures; the syntax is:
proc <procedure name> <arguments> <body>
where <procedure name> is, of couse, the name of the procedure to define, <arguments> is a bracketed list of arguments to the procedure, and <body> is the script to execute when the procedure is called.
Take a look at the get_ps procedure. It uses the canvas widget postscript subcommand, which saves a PostScript image of the canvas widget to a file. This is a very handy feature, which we can use to ”save“ our drawing (possibly to print out or view with Ghostview).
The get_ps command displays a dialog box asking for the name of the file to save, and two buttons, Okay and Cancel (see Figure 4, page 28).
The dialog box, which is a separate window, is actually a toplevel widget. We use the -class option to toplevel to change the window class; this has to do with the X resource database settings for the new window (not an important detail here). We name our new toplevel widget .ask, and use the wm (window manager) command to set the title for the window.
Within the toplevel widget, we create two frames, .ask.top and .ask.bottom. These will serve to group widgets together. We wish to have a label and a text entry widget in the top frame, and the two buttons in the bottom frame. (This is for visual effect only: using frames is a very good way to group widgets together when using pack). Therefore, we create the frames using the frame command and pack them into the toplevel task. Nothing new here.
Within the top frame, we create a label and an entry. This is equivalent to our first example script, edit.tcl. Note that the label (.ask.top.l) and the entry (.ask.top.e) are children of the frame widget, which is in turn a child of .ask. Also, we bind the Return key in the entry widget to execute the postscript subcommand of the canvas widget, .c (which we will create later in the script), and destroy the .ask widget. This has the effect of ”popping down“ the dialog box.
In the lower frame, we create two button widgets and bind similar commands to them. This should be selfexplanatory. Finally, we use the grab command. This causes mouse and keyboard events to be confined to the dialog box window. Otherwise, you would be able to continue drawing within the main application window while the Save PostScnpt dialog box was active; we certainly wouldn't want that.
Having dealt with the menus, we are ready to tackle the canvas widget, which will be used for drawing. First, we create the widget and pack it into the application window. Next, we create two event bindings within the canvas: one for <ButtonPress-1> (executed when mouse button 1 is depressed) and one for <B1-Motion> (executed when the mouse is moved while button 1 is pressed).
The event names used with bind are the standard X11 event specifiers. These are described in any book on X11 programming (as well as good X user guides). There are too many types of X events to enumerate here; see the header file /usr/include/X11/X.h for a list of event names.
When button 1 is depressed in the canvas widget, we wish to start drawing an object of the type specified by the object_type variable, in the color thecolor. First, we set the global variables orig_x and orig_y to the original position of the mouse click; this defines the upper-left-hand comer of the object to draw. As the comments say, the pseudo-variables sx and sy refer to the %x and %y coordinates of the event.
Next, we use the canvas create subcommand to create an object. The syntax is:
<canvas name> create `(type> <xl> <yl> <x2> <y2>: \ [ <options> ... ]
This will create an object of type <type> with upper-lefthand comer at <xl> ,<yl> and lower-right-hand corner at <x2>, <y27gt;. The valid object types are arc, bitmap, line, oval, polygon, rectangle, text, and window. The Tk canvas man page describes them all.
The canvas create subcommand returns a unique identifier (just an integer) for the object just created. A Tcl/Tk command contained within square brackets ( [ . . . ]) is used to run a sub-script, the retum value of which will be substituted in its place. We assign the retum value of the create subcommand to the variable theitem. We will use this value, later, to resize the object when the mouse is dragged.
The binding for <B1-Motion> is very similar. First, we delete the item with the identifier given by the variable theitem, and then re-create the item with the new lower-right-hand comer defined by the current position of the mouse. The original upper-left-hand corner has
been saved in the variables orig_x and orig_y, and we re-use them here. We save the new object identifier back in the theitem variable. What we have essentially done is deleted the current object, and re-created it with a new size based on the mouse position during the drag. The visual effect of this is that the item is resized while we drag the mouse.
The last few lines of our script invoke the first entries in the Object and Color menus. This enables the oval object type, and the color of red, just as if we had selected these menu items with the mouse. If we did not do this, no object type or color would be selected when the application started. Of course, we could have set the variables object_type and thecolorby hand; however, the radiobutton entries in the menu would not be highlighted to correspond with those variable settings. Using the menu item invoke subcommand solves both problems at once.
There you have it! A complete X drawing application, complete with colors, menus, and PostScript capabilities, all in a few hundred lines of interpreted Tcl/Tk script.
Along with the man pages and the information in this article, you should be ready to explore Tcl and Tk on your own.
As you can see, Tcl/Tk programming is easy; it's an ideal way to write simple X applications, or add X frontends to your favorite utilities. There's a complete text edit widget which will allow you to interface with other textbased applications, as well. And Tcl/Tk is extremely customizable; everything from the keyboard and mouse widget bindings to the fonts and highlight colors can be modified.
Writing entire applications as Tcl/Tk scripts may not suit your needs, however. In next month's article, I'm going to descibe how to use the Tcl/Tk interpreter, wish, as a ”server" for X interface requests from a C or Perl program. (You can even draw directly to Tk windows using lower-level Xlib function calls from C.) This will allow you to write complicated X-based programs without having to dabble in the Xt Intrinsics or Motif.
Matt Welsh (email@example.com) is a writer and code grunt working with the Linux Documentation Project and the Debian development team. The author welcomes questions and comments.