PAGE - Python GUI Builder

  INTRODUCTION
    Visual Tcl
    Basic Operation of Visual Tcl and PAGE
    Kind Words for Tcl
    The Use of Tix
    Status of PAGE
  Installation
    Installation on Linux
    Installation on Win32
    Testing the installation
  USING PAGE
    Summary of Usage
    Using Page
    Fix Up
    Setting PAGE Preferences
    Selecting and Placing Widgets
    Widget Aliases
    Adding Balloon Help to Widgets
    Linking Events to Actions
    Defining Functions
    Special Widget Processing
      Menu Specification
      TixNoteBook Specification
    Using emacsclient
    Running the Generated Gui
      The init() function
    Loading Python Files
    Default Consideration
  A Real Example
    Classical.py
  Final Recommendations
  Acknowledgements

INTRODUCTION

This is a short document about PAGE - a Python Automated GUI buildEr. Not a great acronym but I got three out of four letters at least. I don't think much of acronyms. I saw UNIVAC in the '50s and thought that was clever; ever since, I have regarded acronyms as imitations. The only other names that came to mind were guiPy and PyG.

I have felt that Python suffers from two serious admissions. One is an easily used GUI building tool. PAGE is an attempt to provide such a tool.

PAGE is a tool which helps to create GUI interfaces for use with Python programs. I find that I write a number of small programs in Python but find adding any kind of a GUI is a bit of a strain. Python ships with Tkinter which is an interface to the Tk package. But there are several drawbacks to day to day use. First of all there is no real documentation shipped with the product. Recently, Grayson's book "Python and Tkinter Programming", Manning, 2000 appeared and it does help considerably but there is a lot of good information there (>600 pages) that I don't have time to dig out on a continuous basis. I only want to process it once. The other problem is that there are no tools which will bury some of the Tkinter details. It is that tool lack that I have attempted to fill. Grayson's book made it possible.

PAGE is not an end-all, be-all tool, but rather one that attempts to ease the burden on the Python programmer. It is aimed at the user who will put up with a less-than-general GUI capability in order to get an easily generated GUI. It is a helper tool. It does not build an entire application but rather is aimed at building a single GUI window.

PAGE is based on Stewart Allen's Visual Tcl program. In fact, it is vTcl, with an additional module that generates Python code. And yes, it is written in Tcl! Why not? Tcl seems ideal for implementing this program and since Tcl/Tk is needed to run the GUI anyway, there is no additional software support required. Further, it leaves the door open to use the same techniques for GUI builders in other languages which support Tk. The disadvantage of using a Tcl implementation is that one is restricted to widget sets which are available to both Tcl and Python. That seems to mean Tix or Iwidgets and I had a problem with Iwidgets. Thus one cannot use BWidgets or Pmw. At some point soon, the program should be translated to Python.

A small point. I am so intimidated by programming language designers who won't include 'go to' in a programming language, I feel that I must apologize for its use in this document describing a programming tool. There must be a better way to organize the material than I have found.

Visual Tcl

I believe that Stewart Allen wrote Visual Tcl in the 1996-1998 time period to aid in the development of Tcl-Tk windows. Constantin Teodorescu modified vTcl to generate Java code using SWING as the widget set. It was looking at the Java generator that inspired me to try doing this. Let me say that Visual Tcl seems to be very well written. I was able to discern its behavior in spite of the nearly total lack of documentation. When I needed to do something I usually found an existing procedure.

I did change a number of things I think were bugs and there are several things that I ignored because I couldn't figure them out. I ignored all that stuff dealing with compound widgets because it is not my desire to build compound widgets using PAGE; the menu button for compounds has been removed. I am getting all my compound widgets from the Tix package. There is a new alpha level version that may correct some of the Visual Tcl problems, but it is too early to serve as a base for this code.

Note that the Mode switching from between TEST and EDIT is not used and has been removed from the program. The Tcl interpreter is good, but it can't handle functions written in Python. So you can see that I used only what I could figure out and exploit, and tried to leave all the other stuff intact.

Finally, I support only the placer geometry manager. In the past, I have worked with Visual Basic and the placer geometry manager seemed comfortable and got me results that I wanted. (As far as VB is concerned I find the V almost incredible and the B really putrid.) As further justification, Constantin Teodorescu also limited the Java generation to the placer geometry manager. It seems really natural with the drag and drop paradigm. One disadvantage is that some of the sizes could be wrong if fonts change. This is because several Tk widgets have sizing based on font parameters. This has implications if the GUI is generated with different fonts or resolution than are active during execution. There may also be problems if the generated GUI is moved to other platforms or environments.

Basic Operation of Visual Tcl and PAGE

Obviously, I used only the basic operations and this is what I think they are: One creates a window and selects a widget type from the tool bar. A widget of that type then is instantiated in the window and placed in the upper left hand corner. One then uses the mouse to put the widget where it is wanted and to size the beast correctly. To modify other attributes there are a set of windows tailored to editing attributes. The main one is the Attribute Editor for changing attributes such as color, relief, labels, text, etc. Another allows one to add supporting functions and there is one for specifying widget bindings.

If you wish to change a GUI by opening in Visual Tcl an existing tcl file, Visual Tcl will source the specified file and change all the bindings for all the widgets to allow things like resizing, selection, deletion, etc. I never figured out how to manipulate bindings for itcl widgets and so I have excluded them from my consideration. I tried a question to the itcl newsgroup, but never got an answer. The key to the operation of Visual Tcl and PAGE is that the mouse bindings for the widgets can be changed so that you can drag them around, resize them, bring up special purpose menues, etc.

The mechanism for instantiating widgets is to generate tcl/tk code and execute it on the fly. Very nice indeed. It even allows some undo capability.

The power of Tcl/Tk makes it unnecessary to build a data base for the widgets and their attributes and bindings or worry about default settings. Tk does it all for you. It has handy primitives like 'widget configure' which coughs up all the attributes and their defaults for filling in the Attribute Editor fields so that the author only had to build a framework for constructing the Attribute Editor to take advantage of the Tk facilities. When the window/widget manipulation is completed Visual Tcl generates a true Tcl program. If you later want to modify the program all that is necessary is to 'source' the generated file and you are ready to carry on - an essential feature. When it comes to emitting code for functions, the 'body' primitive provides input token stream for the proc including '\n' characters. Event binding are easily handled with the 'bind' command.

Basically, all I had to do was write a few pages of code in one module to process a list of widgets known to Tcl/Tk, extract their attributes using the 'configure' primitive and put out calls to Tkinter. One can trivially tell whether an attribute differs from the default or not. Similarly, the 'bind' command give access to bindings set for widget events. It was easy to deal with subwidgets and parent widgets using 'parent' and 'subwidget' primitives. The Visual Tcl implementation contains a well designed global data structure using a small number of associative arrays which make it easy to add new widgets and manipulate widgets. Again, I found it very easy to understand and manipulate in spite of a nearly total lack of documentation.

Kind Words for Tcl

In building PAGE, I rebuilt considerable respect for Tcl. About five years ago my company had a very bad experience involving an exceptional programmer who built a very complex program using Tcl extensions cascaded upon Tcl extensions. The last extension in the chain was his own. He then left the company and the whole structure collapsed.

This project has elevated Tcl in my esteem. All the primitives that I needed were available. Some touches are nice even in an unintended way. For instance, Visual Tcl adopted the convention of starting every procedure name with 'vTcl:' thereby inadvertently avoiding any possibility of name clashes with Python since ':' is not a legal character of a Python name.

The one aspect of the Tk implementation that truly surprised me was that when a Tcl file is invoked by 'source' the syntax of a proc body is not analyzed. This means that it is possible when using Visual Tcl to specify functions with Python syntax or any other language. You can then save the file as a Tcl file and source it. For me that meant that I could generate an interface in Visual Tcl including support functions written in Python and generate a Python program, go off and debug the generated program including the Python functions and then go back to Visual Tcl to update the interface and generate an updated Python program which preserved my changes to Python functions. All I had to do was write a very small utility to transfer the functions from the Python program back into the Tcl version.

Here is an example of a Tcl procedure with Python syntax:

proc {self.list_handler_2(self,} {} {
def list_handler_2(self, x):
      index = self.lbox.curselection()
      greeting(index)

The proc statement seems to be enough to get thru the 'source' operation so that the 'body' command will yield:

def list_handler_2(self, x):
      index = self.lbox.curselection()
      greeting(index)

At first, I thought this must be an old version of Tcl before they put in a compiler. Later, I came to realize that this behavior is the consequence of the defined behavior of the Tcl 'proc' command. If so, then the approach that I took to generate Python interfaces could be used to generate code for any language that support interfaces to Tk.

I like the fact that there are many commands which with a proper argument string will commit a value and with fewer arguments will query status.

Finally, the process for placing a widget or changing its configuration is accomplished by generating a fragment of Tcl and executing it on the fly.

Lest you infer that I think Tcl is perfect, I feel that I would have gotten PAGE working quicker if Tcl had a source level debugger. I am always disappointed when language designers neglect to build in a decent debugger. This is especially true with interpreted languages where all the facilities are there for the hooking in a debugger. (I have had a similar conversation with Guido and may turn my attention in that direction next.) The only explanation that I can think of is that the authors are much better programmers than I am. That is certainly true of Ousterhout and van Rossum. In a similar vein, the Tcl trace back could be more easily interpreted by telling the user file and the line number from the top of the file and not an offset from a branch point within a proc. I am willing to believe that the interpreter makes the sort of thing I want harder, but that's what computers are for. Of course, Don Libs has put a debugger in expect but it has never been picked up by the Tcl folks.

(I have just tried the TclPro debugger which recently went Open Source. Two interesting points with the TclPro debugger are (1) the debugger is slow. It's first step is to instrument the tcl modules constituting the program. It seems to involve a syntax analysis which probably takes half a minute or so with Page. Also, (2) the debugger doesn't work with Page, because the generated tcl modules contain functions with Python syntax. That's OK with the tcl interpreter because there is no attempt to execute them. However the TclPro debugger assumes that when such a file is 'sourced' the functions will be executed so it does an analysis and barfs on the Python routines and halts. I think that is another indication that a debugger should be built into the tcl interpreter.)

The other thing I would like to see in Tcl/Tk is the adoption of more widgets. Tix, BWidgets, Pwm were all developed in answer to the same problem - the paucity of Tk widgets. Once shown the way, the Tk folks should have included them.

I found "Practical Programming in Tcl and Tk" by Brent B. Welch very helpful.

The Use of Tix

I certainly wanted a wider variety of widgets than the standard Tk widget set and for the reasons above I needed a package which was available in both Tcl/Tk and Python. That came down to Tix which was written for Tcl/Tk and PyTix which is a Python wrapper for Tix. What I attempted to do was to add a subset of tix widgets to those supported by Visual Tcl. In particluar, I wanted the tixLabelFrame, tixLabelEntry, and the Scrolled widgets. In addition, I wanted tixMeter which is a progress bar and the tixNoteBook. I also include capability for balloon help. I would have included the tixPanedWindow, if I could have gotten it to work. Others are welcome to include other widgets if they are so inclined. Since Python includes things like file selection widgets and dialog boxes, I skipped over those Tix widgets for PAGE.

I had problems using Tix. For a while it appeared to have disappeared from the web, however, it turned up again on SourceForge.net. I spent much time getting many features of the TixNoteBook working. It seemed like a really nice widget, one that would conserve real estate in a GUI, so I persevered. I figured out a method to allow the PAGE user to change the number of pages in the tixNoteBook. Another widget I really wanted was a progress bar which is implemented in Tix, even though it's absent from the overall man page. Even worse, it was missing from PyTix package; I added it to PyTix. The meager documentation in Visual Tcl suggests that Tix plays undesirable games with fonts, colors, and so forth. Fortunately, this has been change in the latest version. I modified Tix to avoid the problem through the use the TkoptionsDB file. In the 2.2 version I have abandoned that artifact because Tix now uses the WmDefault package which makes a Tix program more nearly portable across environments. There is a later section which discusses defaults and the desirability of using them. I would have liked to include the tixPanedWindow in PAGE but I was unable to get it working correctly. The code which I experimented with is still in the package, in case someone is willing to look at it and tell me where I am wrong.

Status of PAGE

I have shown PAGE to colleagues and they have recommended that I post it on the net. PAGE is built on version 1.2.2 of Visual Tcl, version 8.1.1 of Tix, and Tcl/Tk version 8.3.2. It works with Python 2.0. I briefly looked at Visual Tcl 1.5.1b, but felt that it was not yet ready for hard use.

I believe that PAGE is currently useful, I have used it for several of my Python projects. It is time for some feed back. Clearly more widgets should be included. I have concentrated on ones that I think that I want to use.

PAGE supports many of the Tk widgets and many of the Tix widgets. In deference to my tired old eyes, I have enlarged the icons on the tool box of those widgets that I handle. The other widgets seem to be of lower priority to me. For instance, hooking up scroll bars to widgets confuses to me so I skipped it in favor of Tix scrolled widgets like scrolled list box, scrolled text box, and scrolled tree. (I really like trees. Use them all the time.) There seems to be no need for both horizontal and vertical scales since they differ by an attribute.

As mentioned above the TEST mode doesn't make much sense in a Python context and I have totally ignored the Compound menu. Also, I have done nothing with the Options menu other than play with the Bindings choice and I may have that wrong. I prefer to have aliases set within the attribute editor.

You may notice that the File menu is augmented to include an 'Open Py' choice. I will get into that later, for now it suffices to say that this is used when we want to modify a window that we have made and have already changed some of the python functions.

I have put a fair amount of time into providing the tixNotebook widget. Most of the desired function works. The pages inside the notebook appear to be frames but one cannot utilize all of the options of a frame. For instance, one cannot change the color of an individual page.

I would like to have supported the paned window, but I had problems. So it is not included in this release.

I have 'added' two new attributes. Tk, for instance, has a special attribute for specifying the action to be taken when a widget is selected. It is called 'command'. I imagine that they treat it differently from other actions because so many GUI operations depend on selecting a widget with Button-1. So, I felt that there are many widgets which require loading like list boxes, text boxes, etc., that I added for those widgets a 'loadcommand' attribute. Also added was bindcommand to make it easy to bind a command to Double-Button-1. I added 'bindcommand' before I fully understood the Bindings window which is supposedly a much more general mechanism for adding bindings. It is, except it doesn't appear to work with Tix subwidgets. So it is fortunate that I put in bind command.

Installation

Installation on Linux

Installing PAGE used to be a fiddle. Python 2.2 now includes Tix as well as Tcl so all that one installs is PAGE. So about all you do is unpact the distribution. Just be sure that you have a working Python2.2 and Tix 8.1.3. It is necessary to have these particular versions of Python and Tix and you certainly want an 8.3.2 are later for Tcl/Tk.

The distribution is a single file, page-2.2.tgz. Just untar it in your home directory. All files will be put in /page. Put /page in your PATH environmental variable

The entry module for PAGE is /page/page.tcl. A shell script 'pg' invokes the program. Be sure to check that the values of the two variables in that script reflect the proper locations. To start the program, execute the command 'pg'. If you supply a file name as the one allowable parameter, that file will be opened.

inc.py is a little utility that takes the functions from a '.py' file and stuffs them into the corresponding '.tcl' file. It is incorporated in the 'Open Py' file menu pick.  Just let lie in the install directory. I hope to replace this soon with a less confusing alternative.

The steps for installing PAGE are:

  1. Untar the distribution file in your home directory. You can probably use 'tar zxvf pageXXX.tgz. This will put all the distribution in /page.
  2. Check the variables in the pg script.

Installation on Win32

Installing under Win32 is a bit easier than was the case in previous releases. See Installation on Win32 for the gory details. The main points are to install recent copies of the programs upon which PAGE depends, execute page-2.2.exe and to set a couple of variables in winpg.bat.

Testing the installation

In the examples subdirectory of PAGE there are two files which can give you some confidence about the installation of the various necessary software components. After you have everything, Python, Tcl, Tix, and PAGE, go to the examples subdirectory in the page installation directory.

In the examples/tix subdirectory try running tixwidgets.tcl. The command is:

      tixwish8.1.8.3 tixwidgets.tcl

under linux or

      tix8183 tixwidgets.tcl

under Win32.
If it works, you will see a very nice Tix window with a notebook widget which demonstrates most of the Tix widgets in the different pages. If it doesn't work you better check your installation and path specification. No point in going on if it doesn't work.

The next test is in examples/PyTix. Here try:

      python tixwidgets.py
The result should be very similar to the previous test example. Your are now ready to go on and seriously try PAGE.

USING PAGE

Summary of Usage

One uses the page facilities to generate a GUI as follows:

Using Page

Start PAGE by executing pg on Linux, winpg.bat in a DOS window, or activating the PAGE icon on the windows desktop. The scripts may specify a filename. That file is usually has an extension of '.tcl'.

There is a tutorial text file for Visual Tcl in the doc directory. Go ahead and read it. Do the examples and then come back to here. Note that the tutorial guides you thru the generation of an entire Tcl/Tk project rather than just how to build a single GUI window as is the intent with PAGE, but it will give you a feel for the intent and use of Visual Tcl to construct individual windows.

One of the tutorial examples has the user building an example with two interacting windows. I use PAGE for building individual windows to be used by an encapsulating program. So there is a bit of emphasis change involved. I don't want to write my whole program using PAGE, just a GUI file that I import from the real application. That is, the basic function of PAGE is to facilitate the creation of individual GUI windows. It allows one to select widgets from a menu and place them in the window using the mouse. One can then modify the attributes of the widget, such as location and size, with the mouse and modify the other attributes with the Attribute Editor. Its operation is similar to that of VB. It is not meant for the creation of extremely sophisticated graphical presentations. Rather, it is for people like me; I find that Tkinter represents a body of knowledge that is difficult to acquire, retain, and keep at my finger tips.

The way I use PAGE is to build a GUI module in Python that is very sparse and import that module in my application. The structure of the GUI module is very highly stylized. The PAGE package contains a fairly extravagant example, t1.tcl. As I was building PAGE, I added all the widgets to a single window. There are weird colors and font sizes to verify that I was able to change attributes of those widgets. Bare in mind that later and for good reasons, I will recommend that one not play much with custom colors or fonts. A sanitized version of t1.py is t3.py. t1.py is still interesting because it shows a bit more that can be done even if that is not always a good idea. t1 and t3 can be found in the examples subdirectory.

To see it, start PAGE, File->Open t1.py, depress Button-3 over the window and select Generate Python from the popup menu. You will get the generated code below without some of the annotations with multiple '#' characters which I added later.

To execute the generated Python module, just click on the Run button of the 'Python Window'. If you click on many of the widgets and menus thing will happen to show that there is a connection between the widget and the program. If you look at the code generated you will see various things to try. One to try is typing 'q' into the plum colored entry box. Stop the cursor over the scrolled listbox (the green one) or the IDE menu button to see examples of balloon help. Go through the pages of the notebook and press the demo button in the last page.

The block comments with three preceding #'s are annotations I added for this guide.

--------------------- beginning of t1.py ------------------------
#! /usr/bin/env python
# Generated by PAGE v 0.1.1
from Tkinter import *
import Tix, sys, os.path
root = Tix.Tk()

def greeting(str):
    import Dialog
    Dialog.Dialog(title="greetings",
                text=str,
                bitmap="",default=0,strings=("cont",))


def init():
    pass


def load_listbox(o):
    for i in range(50):
        o.insert(END, i)


def load_scrolled_text(o):
    o.insert(END, "\n1. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n2. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n3. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n4. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n5. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n6. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n7. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n8. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n9. This is a long bit of text that I want to put inside" + " this text box.")
    o.insert(END, "\n0. This is a long bit of text that I want to put inside" + " this text box.")


def load_tree(o):
    o.configure(separator="/")
    o.add("/",text="/")
    o.add("/home",text="home")
    o.add("/home/ioi",text="ioi")
    o.add("/home/foo",text="foo")
    o.add("/usr",text="usr")
    o.add("/usr/lib",text="lib")
    o.add("/home/rozen", text="rozen")
    o.add("/home/rozen/pkg", text="pkg")
    o.add("/home/rozen/pkg/PyTix", text="PyTix")
    o.add("/home/rozen/vp", text="vp")
    o.add("/home/rozen/vp/lib", text='lib')
    o.add("/usr/bin", text='bin')
    o.add("/usr/man", text='man')


def open_file():
    import tkFileDialog
    tkFileDialog.askopenfilename(filetypes=[("all files", "*")])


def quit():
    root.destroy()


def quit_a(event):
       print 'quit_a: ' + event.type
       quit()


def run_demo():
    import time
    for i in range(20):
        x = (5.0 * (i+1)) /100
        print "str(x) =", str(x)
        w.tix34_nbframe_page4_tix30_border_frame_tix31.configure(value=str(x))
        w.tix34.update_idletasks()
        time.sleep(.3)


def tree_browse(x):
    greeting(str(x))



class New_Toplevel_1:
    def __init__(self, master=None):



        self.fra17 = Frame (master)
        self.fra17.place(in_=master,x=40,y=80)
        self.fra17.configure(relief=GROOVE)
        self.fra17.configure(background="green")
        self.fra17.configure(borderwidth="2")
        self.fra17.configure(height="75")
        self.fra17.configure(relief="groove")
        self.fra17.configure(width="125")

        self.fra17_but18 = Button (self.fra17)
        self.fra17_but18.place(in_=self.fra17,x=20,y=20)
        self.fra17_but18.configure(activebackground="black")
        self.fra17_but18.configure(activeforeground="ivory")
        self.fra17_but18.configure(background="red")
        self.fra17_but18.configure(command=quit)
        self.fra17_but18.configure(font="helvetica 14 bold")
        self.fra17_but18.configure(foreground="black")
        self.fra17_but18.configure(text="Quit")

        self.lab17 = Label (master)
        self.lab17.place(in_=master,x=25,y=40)
        self.lab17.configure(background="wheat")
        self.lab17.configure(borderwidth="1")
        self.lab17.configure(font="helvetica 14 bold")
        self.lab17.configure(foreground="black")
        self.lab17.configure(relief="raised")
        self.lab17.configure(text="Don's Demo")

        self.tex17 = Text (master)
        self.tex17.place(in_=master,x=200,y=50)
        self.tex17.configure(background="Wheat")
        self.tex17.configure(font="helvetica 14 bold")
        self.tex17.configure(foreground="black")
        self.tex17.configure(height="8")
        self.tex17.configure(insertbackground="black")
        self.tex17.configure(selectbackground="black")
        self.tex17.configure(selectforeground="ivory")
        self.tex17.configure(width="20")
        load_scrolled_text(self.tex17) # ldcmd .top17.tex17

        self.ent17 = Entry (master)
        self.ent17.place(in_=master,x=50,y=200)
        self.ent17.configure(background="plum")
        self.ent17.configure(font="helvetica 14 bold")
        self.ent17.configure(foreground="black")
        self.ent17.configure(insertbackground="black")
        self.ent17.configure(selectbackground="black")
        self.ent17.configure(selectforeground="ivory")
        self.hello = StringVar()
        self.ent17.configure(textvariable=self.hello)
        self.ent17.configure(width="35")
        self.ent17.bind('q',lambda e: greeting('q'))

        self.fra19 = Frame (master)
        self.fra19.place(in_=master,x=200,y=240)
        self.fra19.configure(relief=GROOVE)
        self.fra19.configure(background="wheat")
        self.fra19.configure(borderwidth="4")
        self.fra19.configure(height="115")
        self.fra19.configure(relief="groove")
        self.fra19.configure(width="125")

        self.fra19_rad20 = Radiobutton (self.fra19)
        self.fra19_rad20.place(in_=self.fra19,x=5,y=40)
        self.fra19_rad20.configure(activebackground="black")
        self.fra19_rad20.configure(activeforeground="ivory")
        self.fra19_rad20.configure(background="wheat")
        self.fra19_rad20.configure(command=lambda : greeting("Radio 2 "))
        self.fra19_rad20.configure(font="helvetica 14 bold")
        self.fra19_rad20.configure(foreground="black")
        self.fra19_rad20.configure(selectcolor="red")
        self.fra19_rad20.configure(text="radio 1")

        self.fra19_rad22 = Radiobutton (self.fra19)
        self.fra19_rad22.place(in_=self.fra19,x=5,y=75)
        self.fra19_rad22.configure(activebackground="black")
        self.fra19_rad22.configure(activeforeground="ivory")
        self.fra19_rad22.configure(background="wheat")
        self.fra19_rad22.configure(command=lambda : greeting("Radio 2 "))
        self.fra19_rad22.configure(font="helvetica 14 bold")
        self.fra19_rad22.configure(foreground="black")
        self.fra19_rad22.configure(selectcolor="red")
        self.fra19_rad22.configure(text="radio 2")
        self.fra19_rad22.configure(value="2")

        self.fra19_lab19 = Label (self.fra19)
        self.fra19_lab19.place(in_=self.fra19,x=10,y=10)
        self.fra19_lab19.configure(background="wheat")
        self.fra19_lab19.configure(borderwidth="1")
        self.fra19_lab19.configure(font="helvetica 14 bold")
        self.fra19_lab19.configure(foreground="black")
        self.fra19_lab19.configure(text="Radio Buttons")

        self.fra18 = Frame (master)
        self.fra18.place(in_=master,x=30,y=245)
        self.fra18.configure(relief=GROOVE)
        self.fra18.configure(background="wheat")
        self.fra18.configure(borderwidth="4")
        self.fra18.configure(height="110")
        self.fra18.configure(relief="groove")
        self.fra18.configure(width="125")

        self.fra18_che20 = Checkbutton (self.fra18)
        self.fra18_che20.place(in_=self.fra18,x=5,y=40)
        self.fra18_che20.configure(activebackground="black")
        self.fra18_che20.configure(activeforeground="ivory")
        self.fra18_che20.configure(background="wheat")
        self.fra18_che20.configure(command=lambda :greeting("Check Button 1"))
        self.fra18_che20.configure(font="helvetica 14 bold")
        self.fra18_che20.configure(foreground="black")
        self.fra18_che20.configure(selectcolor="blue")
        self.fra18_che20.configure(text="check 1")

        self.fra18_che21 = Checkbutton (self.fra18)
        self.fra18_che21.place(in_=self.fra18,x=5,y=75)
        self.fra18_che21.configure(activebackground="black")
        self.fra18_che21.configure(activeforeground="ivory")
        self.fra18_che21.configure(background="wheat")
        self.fra18_che21.configure(command=lambda :greeting("Check Button 2"))
        self.fra18_che21.configure(font="helvetica 14 bold")
        self.fra18_che21.configure(foreground="black")
        self.fra18_che21.configure(selectcolor="blue")
        self.fra18_che21.configure(text="check 2")

        self.fra18_lab22 = Label (self.fra18)
        self.fra18_lab22.place(in_=self.fra18,x=5,y=5)
        self.fra18_lab22.configure(background="wheat")
        self.fra18_lab22.configure(borderwidth="1")
        self.fra18_lab22.configure(font="helvetica 14 bold")
        self.fra18_lab22.configure(foreground="black")
        self.fra18_lab22.configure(text="Check Buttons")

        self.lis17 = Listbox (master)
        self.lis17.place(in_=master,x=390,y=50)
        self.lis17.configure(background="wheat")
        self.lis17.configure(font="helvetica 14 bold")
        self.lis17.configure(foreground="black")
        self.lis17.configure(relief="raised")
        self.lis17.configure(selectbackground="black")
        self.lis17.configure(selectforeground="ivory")
        load_listbox(self.lis17) # ldcmd .top17.lis17
        self.lis17.bind('',self.list_handler) # bindcmd .top17.lis17
        self.lis17.bind('',lambda e: quit_a(e))

        self.sca17 = Scale (master)
        self.sca17.place(in_=master,x=380,y=320)
        self.sca17.configure(background="Wheat")
        self.sca17.configure(font="helvetica 18")
        self.sca17.configure(length="271")
        self.sca17.configure(orient="horizontal")
        self.sca17.configure(tickinterval="10.0")
        self.tootsie = DoubleVar()
        self.sca17.configure(variable=self.tootsie)

        self.tix28 = Tix.LabelFrame(master)
        self.tix28.place(in_=master,x=620,y=40)
        self.tix28.configure(label="Outside Frame")
        self.tix28.configure(background="red")
        self.tix28.configure(borderwidth="2")
        self.tix28.configure(highlightbackground="#d9d9d9")
        self.tix28.configure(highlightcolor="Black")
        self.tix28_border_frame = self.tix28.subwidget_list["frame"]
        self.tix28_border_frame.configure(background="blue")
        self.tix28_border_frame.configure(height="200")
        self.tix28_border_frame.configure(width="150")
        self.tix28_label = self.tix28.subwidget_list["label"]
        self.tix28_label.configure(background="green")
        self.tix28_label.configure(font="helvetica 18")
        self.tix28_label.configure(relief="sunken")
        self.tix28_label.configure(text="Outside Frame")

        self.tix28_border_frame_tix29 = Tix.LabelFrame(self.tix28_border_frame)
        self.tix28_border_frame_tix29.place(in_=self.tix28_border_frame,x=10,y=125)
        self.tix28_border_frame_tix29.configure(label="Frame A")
        self.tix28_border_frame_tix29.configure(background="wheat")
        self.tix28_border_frame_tix29.configure(borderwidth="2")
        self.tix28_border_frame_tix29.configure(highlightbackground="#d9d9d9")
        self.tix28_border_frame_tix29.configure(highlightcolor="Black")
        self.tix28_border_frame_tix29_border_frame = self.tix28_border_frame_tix29.subwidget_list["frame"]
        self.tix28_border_frame_tix29_border_frame.configure(background="wheat")
        self.tix28_border_frame_tix29_border_frame.configure(height="30")
        self.tix28_border_frame_tix29_border_frame.configure(width="30")
        self.tix28_border_frame_tix29_label = self.tix28_border_frame_tix29.subwidget_list["label"]
        self.tix28_border_frame_tix29_label.configure(background="wheat")
        self.tix28_border_frame_tix29_label.configure(font="helvetica 18")
        self.tix28_border_frame_tix29_label.configure(text="Frame A")

        self.tix28_border_frame_tix30 = Tix.LabelFrame(self.tix28_border_frame)
        self.tix28_border_frame_tix30.place(in_=self.tix28_border_frame,x=20,y=25)
        self.tix28_border_frame_tix30.configure(label="Frame B")
        self.tix28_border_frame_tix30.configure(background="wheat")
        self.tix28_border_frame_tix30.configure(borderwidth="2")
        self.tix28_border_frame_tix30.configure(highlightbackground="#d9d9d9")
        self.tix28_border_frame_tix30.configure(highlightcolor="Black")
        self.tix28_border_frame_tix30_border_frame = self.tix28_border_frame_tix30.subwidget_list["frame"]
        self.tix28_border_frame_tix30_border_frame.configure(background="wheat")
        self.tix28_border_frame_tix30_border_frame.configure(height="44")
        self.tix28_border_frame_tix30_border_frame.configure(width="105")
        self.tix28_border_frame_tix30_label = self.tix28_border_frame_tix30.subwidget_list["label"]
        self.tix28_border_frame_tix30_label.configure(background="wheat")
        self.tix28_border_frame_tix30_label.configure(font="helvetica 18")
        self.tix28_border_frame_tix30_label.configure(text="Frame B")

        self.tix28_border_frame_tix30_border_frame_but29 = Button (self.tix28_border_frame_tix30_border_frame)
        self.tix28_border_frame_tix30_border_frame_but29.place(in_=self.tix28_border_frame_tix30_border_frame,x=5,y=0)
        self.tix28_border_frame_tix30_border_frame_but29.configure(background="pink")
        self.tix28_border_frame_tix30_border_frame_but29.configure(command=quit)
        self.tix28_border_frame_tix30_border_frame_but29.configure(font="helvetica 18")
        self.tix28_border_frame_tix30_border_frame_but29.configure(text="exit")

        self.tix29 = Tix.ScrolledListBox(master)
        self.tix29.place(in_=master,x=410,y=440)
        self.tix29.configure(background="wheat")
        self.tix29.configure(highlightbackground="#d9d9d9")
        self.tix29.configure(highlightcolor="Black")
        self.tix29_listbox = self.tix29.subwidget_list["listbox"]
        self.tix29_listbox.configure(background="#08db24")
        self.tix29_listbox.configure(font="helvetica 18")
        self.list_x = self.tix29_listbox
        load_listbox(self.tix29_listbox) # ldcmd .top17.tix29.listbox
        self.tix29_listbox.bind('',self.list_handler_2) # bindcmd .top17.tix29.listbox

        self.lab30 = Label (master)
        self.lab30.place(in_=master,x=420,y=400)
        self.lab30.configure(background="wheat")
        self.lab30.configure(borderwidth="1")
        self.lab30.configure(font="helvetica 18")
        self.lab30.configure(relief="raised")
        self.lab30.configure(text="TixScrolledListBox")
        self.lab30.configure(width="17")

        self.tix31 = Tix.OptionMenu(master)
        self.tix31.place(in_=master,x=50,y=370)
        self.tix31.configure(label="OptionMenu: ")
        self.tix31.configure(background="wheat")
        self.tix31.configure(highlightbackground="#d9d9d9")
        self.tix31.configure(highlightcolor="Black")
        self.tix31.add_command("z_3",label="Option 1",underline=0)
        self.tix31.add_command("z_4",label="Option 2",underline=0)
        self.tix31.add_separator("z_5")

        self.tix30 = Tix.LabelEntry(master)
        self.tix30.place(in_=master,x=350,y=280)
        self.tix30.configure(disabledforeground="#a3a3a3")
        self.tix30.configure(label="LabelEntry:")
        self.tix30.configure(background="wheat")
        self.tix30.configure(highlightbackground="#d9d9d9")
        self.tix30.configure(highlightcolor="Black")
        self.tix30_frame_entry = self.tix30.subwidget_list["entry"]
        self.tix30_frame_entry.configure(background="wheat")
        self.tix30_frame_entry.configure(font="helvetica 18")
        self.vu_file = StringVar()
        self.tix30_frame_entry.configure(textvariable=self.vu_file)
        self.tix30_frame_entry.configure(width="5")
        self.tix30_label = self.tix30.subwidget_list["label"]
        self.tix30_label.configure(background="wheat")
        self.tix30_label.configure(font="Helvetica -18 bold")
        self.tix30_label.configure(text="LabelEntry:")

        self.tix32 = Tix.ComboBox(master,dropdown=1,editable=0,fancy=0)
        self.tix32.place(in_=master,x=50,y=430)
        self.tix32.configure(arrowbitmap="@/usr/local/lib/tix8.1/bitmaps/cbxarrow.xbm")
        self.tix32.configure(crossbitmap="@/usr/local/lib/tix8.1/bitmaps/cross.xbm")
        self.tix32.configure(disabledforeground="#a3a3a3")
        self.tix32.configure(history="0")
        self.tix32.configure(prunehistory="1")
        self.tix32.configure(tickbitmap="@/usr/local/lib/tix8.1/bitmaps/tick.xbm")
        self.tix32.configure(background="wheat")
        self.tix32.configure(highlightbackground="#d9d9d9")
        self.tix32.configure(highlightcolor="Black")
        load_listbox(self.tix32) # ldcmd .top17.tix32

        self.tix33 = Tix.Tree(master,options='\
                hlist.background "pink"\
                hlist.borderWidth "0"\
                hlist.drawBranch "1"\
                hlist.font "Helvetica -18 bold"\
                hlist.highlightThickness "0"\
                hlist.selectBackground "#c3c3c3"\
                hlist.wideSelection "1"')
        self.tix33.place(in_=master,x=690,y=300)
        self.tix33.configure(browsecmd=lambda entry=None: tree_browse(entry))
        self.tix33.configure(ignoreinvoke="0")
        self.tix33.configure(scrollbar="auto")
        self.tix33.configure(background="wheat")
        self.tix33.configure(borderwidth="1")
        self.tix33.configure(height="167")
        self.tix33.configure(width="212")
        self.T1 = self.tix33.subwidget_list['hlist']
        load_tree(self.T1) # ldcmd .top17.tix33
        self.tix33.autosetmode()





        self.tix34 = Tix.NoteBook(master)
        self.tix34.place(in_=master,x=40,y=490)
        self.tix34.configure(background="wheat")
        self.tix34.configure(height="213")
        self.tix34.configure(highlightbackground="#d9d9d9")
        self.tix34.configure(highlightcolor="Black")
        self.tix34.configure(width="335")
        self.tix34_nbframe = self.tix34.subwidget_list["nbframe"]
        self.tix34_nbframe.configure(background="wheat")
        self.tix34_nbframe.configure(font="helvetica 14")
        self.tix34_nbframe.configure(inactivebackground="ivory")
        self.tix34_nbframe.configure(relief="raised")
        self.tix34_nbframe.configure(tabpadx="8")
        self.tix34.add("page1",anchor="center",label="Page 1")
        self.tix34.add("page2",anchor="center",label="Page 2")
        self.tix34.add("page3",anchor="center",label="Page 3")
        self.tix34.add("page4",anchor="center",label="Page 4")
        self.page1 = self.tix34.subwidget_list["page1"]

        self.tix34_nbframe_page1_but36 = Button (self.page1)
        self.tix34_nbframe_page1_but36.place(in_=self.page1,x=20,y=65)
        self.tix34_nbframe_page1_but36.configure(background="red")
        self.tix34_nbframe_page1_but36.configure(command=quit)
        self.tix34_nbframe_page1_but36.configure(font="helvetica 18")
        self.tix34_nbframe_page1_but36.configure(text="So Long")
        self.page2 = self.tix34.subwidget_list["page2"]

        self.tix34_nbframe_page2_but38 = Button (self.page2)
        self.tix34_nbframe_page2_but38.place(in_=self.page2,x=230,y=65)
        self.tix34_nbframe_page2_but38.configure(background="green")
        self.tix34_nbframe_page2_but38.configure(command=quit)
        self.tix34_nbframe_page2_but38.configure(font="helvetica 18")
        self.tix34_nbframe_page2_but38.configure(text="Good By")
        self.page3 = self.tix34.subwidget_list["page3"]

        self.tix34_nbframe_page3_but39 = Button (self.page3)
        self.tix34_nbframe_page3_but39.place(in_=self.page3,x=210,y=120)
        self.tix34_nbframe_page3_but39.configure(activeforeground="black")
        self.tix34_nbframe_page3_but39.configure(background="Blue")
        self.tix34_nbframe_page3_but39.configure(command=quit)
        self.tix34_nbframe_page3_but39.configure(font="helvetica 18")
        self.tix34_nbframe_page3_but39.configure(foreground="white")
        self.tix34_nbframe_page3_but39.configure(text="Go Away")
        self.page4 = self.tix34.subwidget_list["page4"]

        self.tix34_nbframe_page4_tix30 = Tix.LabelFrame(self.page4)
        self.tix34_nbframe_page4_tix30.place(in_=self.page4,x=5,y=5)
        self.tix34_nbframe_page4_tix30.configure(label="label-me")
        self.tix34_nbframe_page4_tix30.configure(background="wheat")
        self.tix34_nbframe_page4_tix30.configure(borderwidth="2")
        self.tix34_nbframe_page4_tix30.configure(highlightbackground="#d9d9d9")
        self.tix34_nbframe_page4_tix30.configure(highlightcolor="Black")
        self.tix34_nbframe_page4_tix30_border_frame = self.tix34_nbframe_page4_tix30.subwidget_list["frame"]
        self.tix34_nbframe_page4_tix30_border_frame.configure(background="wheat")
        self.tix34_nbframe_page4_tix30_border_frame.configure(height="108")
        self.tix34_nbframe_page4_tix30_border_frame.configure(width="309")
        self.tix34_nbframe_page4_tix30_label = self.tix34_nbframe_page4_tix30.subwidget_list["label"]
        self.tix34_nbframe_page4_tix30_label.configure(background="wheat")
        self.tix34_nbframe_page4_tix30_label.configure(font="helvetica 18")
        self.tix34_nbframe_page4_tix30_label.configure(text="Progress Bar")

        self.tix34_nbframe_page4_tix30_border_frame_tix31 = Tix.Meter(self.tix34_nbframe_page4_tix30_border_frame)
        self.tix34_nbframe_page4_tix30_border_frame_tix31.place(in_=self.tix34_nbframe_page4_tix30_border_frame,x=130,y=40)
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(foreground="Black")
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(value=".3")
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(background="wheat")
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(borderwidth="2")
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(highlightbackground="#d9d9d9")
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(highlightcolor="Black")
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(relief="sunken")
        self.tix34_nbframe_page4_tix30_border_frame_tix31.configure(width="150")

        self.tix34_nbframe_page4_tix30_border_frame_but32 = Button (self.tix34_nbframe_page4_tix30_border_frame)
        self.tix34_nbframe_page4_tix30_border_frame_but32.place(in_=self.tix34_nbframe_page4_tix30_border_frame,x=10,y=30)
        self.tix34_nbframe_page4_tix30_border_frame_but32.configure(background="wheat")
        self.tix34_nbframe_page4_tix30_border_frame_but32.configure(command=lambda : run_demo())
        self.tix34_nbframe_page4_tix30_border_frame_but32.configure(font="helvetica 18")
        self.tix34_nbframe_page4_tix30_border_frame_but32.configure(text="Demo")

        self.tix35 = Tix.ScrolledText(master)
        self.tix35.place(in_=master,x=660,y=530)
        self.tix35.configure(background="wheat")
        self.tix35.configure(borderwidth="1")
        self.tix35.configure(height="141")
        self.tix35.configure(highlightbackground="#d9d9d9")
        self.tix35.configure(highlightcolor="Black")
        self.tix35.configure(width="254")
        self.tix35_text = self.tix35.subwidget_list["text"]
        load_scrolled_text(self.tix35_text) # ldcmd .top17.tix35
        self.tix35_text.configure(background="wheat")
        self.tix35_text.configure(font="helvetica 18")
        self.tix35_text.configure(takefocus="1")
        self.tix35_text.configure(wrap="none")

        self.tix36 = Tix.Meter(master)
        self.tix36.place(in_=master,x=710,y=480)
        self.tix36.configure(foreground="Black")
        self.tix36.configure(value=".3")
        self.tix36.configure(background="wheat")
        self.tix36.configure(borderwidth="2")
        self.tix36.configure(highlightbackground="#d9d9d9")
        self.tix36.configure(highlightcolor="Black")
        self.tix36.configure(relief="sunken")
        self.tix36.configure(width="150")


        self.menubar = Frame(master,relief=RAISED, bd=2)
        self.menubar.pack(side=TOP,fill=X)
        self.men18 = Menubutton(self.menubar, underline = 0)
        self.men18.pack(side=LEFT)
        self.men18.configure(text="File")
        self.men18_m1 = Menu(self.men18)
        self.men18_m1.add_command(label="Open",command=open_file,underline=0)
        self.men18_m1.add_command(label="Close",command=lambda :greeting("close"),underline=0)
        self.men18_m1.add_command(label="Exit",command=quit,underline=0)
        self.men18['menu'] = self.men18_m1
        self.men17 = Menubutton(self.menubar, underline = 0)
        self.men17.pack(side=LEFT)
        self.men17.configure(text="Edit")
        self.men17_m2 = Menu(self.men17)
        self.men17_m2.add_command(label="Cut",command=lambda:greeting("Cut"),underline=0)
        self.men17_m2.add_command(label="Paste",command=lambda :greeting("Paste"),underline=0)
        self.men17_m2.add_command(label="Copy",command=lambda : greeting("Copy"),underline=0)
        self.men17_m2_cas2 = Menu(self.men17_m2)
        self.men17_m2.add_cascade(label="Advanced",menu=self.men17_m2_cas2)
        self.men17_m2_cas2.add_command(label="ToUpper",command=lambda :greeting("ToUpper"),underline=0)
        self.men17_m2_cas2.add_command(label="ToLower",command=lambda : greeting("ToLower"),underline=0)
        self.men17_m2.add_command(label="Delete",command=lambda : greeting("Delete"),underline=0)
        self.men17['menu'] = self.men17_m2
        self.men28 = Menubutton(self.menubar, underline = 0)
        self.men28.pack(side=LEFT)
        self.men28.configure(text="Font")
        self.men28_m3 = Menu(self.men28)
        self.men28_m3.add_checkbutton(label="Courier",command=lambda : greeting("Courier"))
        self.men28_m3.add_checkbutton(label="Times Roman",command=lambda : greeting("Times Roman"))
        self.men28_m3.add_checkbutton(label="Bodini Book",command=lambda : greeting("Bodini Book"))
        self.men28['menu'] = self.men28_m3
        self.men29 = Menubutton(self.menubar, underline = 0)
        self.men29.pack(side=LEFT)
        self.men29.configure(text="IDE")
        self.men29_m4 = Menu(self.men29)
        self.men29_m4.add_radiobutton(label="Emacs",command=lambda : greeting("Emacs"))
        self.men29_m4.add_radiobutton(label="IDLE",command=lambda : greeting("IDLE"))
        self.men29['menu'] = self.men29_m4
        self.men23 = Menubutton(self.menubar, underline = 0)
        self.men23.pack(side=RIGHT)
        self.men23.configure(text="Help")
        self.men23_m5 = Menu(self.men23)
        self.men23['menu'] = self.men23_m5

        self.balloon = Tix.Balloon(master)
        self.balloon.bind_widget(self.tix29_listbox,balloonmsg="An example of balloon help message.")
        self.balloon.bind_widget(self.men29,balloonmsg="Select the environemt that you wish to use.")
        self.balloon.configure(bg='blue', borderwidth=3)

    def list_handler(self, x):
      index = self.tix29_listbox.curselection()
      greeting(index)

    def list_handler_2(self, x):
      index = self.list_x.curselection()
      greeting(index)


def vp_start_gui():
    global w
    root.title('New_Toplevel_1')
    root.geometry('934x736+85+24')
    w = New_Toplevel_1 (root)
    init()
    root.mainloop()

if __name__ == '__main__':
    vp_start_gui()


Fix Up

When you are generating a GUI you are working with two files having the same root name and extensions of tcl and py. For instance, if you are using PAGE to build 'gui.py', then the file manipulated by PAGE is 'gui.tcl'. Again, the example above involves 't1.tcl' and its companion 't1.py' When you depress button 3 over the top level window, a popup menu will give you the option to 'Generate Python' at which point the file 'gui.py' will be generated and displayed in a new window. That window will allow you save the py file or execute it, etc. You must exercise care to keep the two files in sync to allow future changes. One way to do that, is to use the menu item File->Save. Currently, PAGE is doing this for you. The first time a save is attempted, the user will be asked to specify a name and directory for storing the 'gui.tcl' file. (I am not too happy doing this automatically because it some sense commits experimentation. However, when the tcl file is saved, the previous version is renamed with the 'tcl.bak' suffix.)

Often, you are not satisfied with the first Python file generated by PAGE. As I am debugging the GUI or extending the application, I will change the functions that appear in the py file and then I discover that I need an additional widget. At that point, I want to go back to PAGE to spruce up the GUI. I open PAGE and in the File menu I select 'Open Py', which invokes a utility, written in Python which merges any function or method changes from 'gui.py' into 'gui.tcl' before PAGE sources 'gui.tcl'. I then change the appearance of the GUI. When I then 'Generate Python' the new program 'gui.py' will have the new version of the GUI plus all of the debugging changes that had been made to the previous version of 'gui.py'.

Setting PAGE Preferences

The file menu has a Preferences option which allows you to change a number of options. There is a very small amount of information in 'tutorial.txt'. I just want to mention two things: (1) I added an option, and (2) I wish the Font Settings did more. The options are saved in ~/.vctlrc.

Constantin Teodorescu added a special window for displaying and execution the java program that his code generates. I preserved this. But, a good tool of this variety should be coupled with an IDE. It was suggested to me by Guido that I should couple this with the Python IDE. And, of course, he is right. But PAGE should be translated to Python first and I would like to make it available before that is done. Further, I feel that EMACS is a fine IDE in general. It was not difficult to have PAGE use emacsclient for editing procedures and displaying the generated program. The option that enables that capability is labeled 'Use Emacsclient'.

Again, in deference to aged eyes I wish the Font Settings would change the fonts used in all the windows and menus but it doesn't. After much guidance from Mike Clarkson, I have come to accept the WmDefaults package as the best way of handling the color schemes and fonts. I recommend that you do also, even though there is the capability for overwriting those attributes in the generated GUI.

One has to be careful with the resolution that one uses when constructing a GUI.  For instance, I made the mistake of  creating the example GUI on a screen with the resolution set to 1024x768.  If run on a screen with less resolution then the whole GUI will not fit inside the top level window and scroll bars are not provided.

Selecting and Placing Widgets

The starting operation is to select the toplevel button from the Widget Toolbar. This is the window that you are building for your GUI. Use Button-1 to position it on the screen and to size it appropriately. (Most documentation talks about left-mouse-buttons and right-mouse-buttons but I am left-handed and Button-1 is really on the right and right-handed people expect it to be the left button.)

Select the top-level window with Button-1 and then select the widget you would like to put in the top-level window. It will magically appear in the upper left hand corner of the top-level window. Again, use Button-1 to resize it and to move it to the desired location. When you select a widget, the Attribute Editor window will display all configuration options available for that widget and display the default values of those widgets. I think it is rather clear how to modify most of the options.

Things get more interesting when it comes to placing Tix megawidgets because they are collections of widgets and clicking Button-1 in the area of the widget may hit any one of the contained widgets. To move or resize a Megawidget it is necessary to grab the outer widget. This can be done by pressing Control-Button-1.

The final point about placing widgets is that some widgets are container widgets such as frame widgets and TixNamedFrame. You are allowed to place widgets inside of these widgets. That is done by selecting the container widget with Button-1 and then selecting the desired widget from the Widget Toolbar.

When you want to drag a Tix widget around or resize it, you have to be careful to get the top of the Tix. For instance, if the mouse is over the center of a TixScrolledListBox when you press Button-1 you don't select the TixScrolledListBox you select the listbox subwidget. Now that's fine if you want to change one of its attributes that you cannot change from the TixScrolledListBox attribute list. But it is not what you need to have selected to move or resize the TixScrolledListBox. To select the TixScrolledListBox move the mouse over any part of the TixScrolledListBox and press Control-Button-1. Modifying Attributes of Widgets

Specifying and modifying the the attributes with PAGE is very easy. All one has to do is to select the widget with Button-1 and then 'go to' the Attribute Editor and modify the attributes as desired. One can easily change the colors, fonts, labels, text, relief, and many that I don't even recognize.

There are different ways to change different things. Where there is limited list of choices, the change is selected from a drop down list. For colors one can type in the X color name, RGB in hex, or hit the little button next to the field and a color selection window appears. Labels and text fields can just be typed in. Where a command is specified, it can just be typed in or the button with ellipsis can be selected and a text window opens where the command can be entered. More on specifying command later.

Keep in mind: The generated Python module does respect the TkoptionDB file. So the executing Python program may appear slightly different from the PAGE template. In Tk many dimensions are in units of average character widths and thanks to Tix the PAGE fonts are of limited variation in size. So if you have specified a large font size in TkoptionDB, some of the widgets will be larger. Also, Tix will limit the color scheme so the window background will change from the Page template to generated GUI execution. Again, by using my TkoptionDB for both PAGE and the application encapsulating the finished GUI, I avoid both problems.

Widget Aliases

It is sometimes desirable to use the name of a widget in a bind command or an procedure. The names assigned to a widget placed by PAGE can be rather complicated. I don't like going thru the listing looking for the generated name for the object self.tix29_listbox so that I can type it elsewhere.

So, I use an alias which may be simpler to remember. So, as I am specifying attributes for the TixScrolledListBox, I also specify the alias should be 'list_x', the following line of code is generated:

self.list_x = self.tix29_listbox

allowing me to write the event handler as:

    def list_handler_2(self, x):
      index = self.list_x.curselection()
      greeting(index)

Visual TCL as I got it allowed you to 'go to' the options menu and select Alias. At that point a window opens up to receive the alias. I changed the Attribute editor so that you can enter or change an alias there as well. Partly, I did things like that to convince myself that I was following what was going on.

Adding Balloon Help to Widgets

Balloon help can be very useful and Tix supports balloon help. To specify balloon help for a widget or a menu button merely select that widget or button, hit Control-Button-2 and a window will pop up allowing you to add the help phrase. I don't support putting balloon help into a status bar.

Linking Events to Actions

As mentioned a couple of times, there are two special attributes, command and loadcommand related to event-action interactions. Tcl/Tk supports the first and I added the second. Command is the attribute that defines the code to be executed when a widget is selected with Button-1. To specify that command, one selects the widget and then goes to the attribute editor and locates the entry filed labeled 'command'. Now you can type in the command or select the button with the ellipsis. Doing the latter brings up a window where you can type in the command. Visual Tcl allows you to enter a block of code, but since we are generating Python, you better stick to one-liners. An example, is 'root.destroy()' which kills the whole GUI. Another example is 'quit' which invokes the quit function which contains 'root.destroy()' in the example above. If you want to invoke a function and pass parameters to it, you use a lambda expression. Please see section 6.4 of Grayson's book for a fine explanation of the use of lambda expressions in this context. Let say that if you want to call the function foo and pass it 3 as an argument, what you enter as command is

lambda : foo(3)

not 

foo(3)

Please see the examples above for command=... arguments.

The loadcommand event handling is very similar to the command event. In the above example, I used it to load the two list boxes with the same data. It was very convenient to pass to load_listbox the list to be loaded. In loading the first command, I actually put in the name of the object, 'self.lis17'. By the time I got to the TixScrolledListBox, I smartened up and specified '%me' as the object and the code generator converted that to the actual name of the object.

So much for the 'special' events. There is a whole group of events that can be linked to code. See the Tk man pages and Chapter 6 of Grayson,. They include responding to the different mouse button pressings or releases, a window getting or loosing focus, etc.. If you want to link code to one of those events, select the widget with Button-1, 'go to' the Options Menu and select Bindings. A Widget Binding window will open. I only recently began to figure this one out myself. The top line lets you specify the event. (As I got Visual TCL, things always blew up when you selected one of the last buttons because there were no entries behind them. I add a random few.) I believe the purpose of the Type entry field to the left is to enter the keys to which linkage is desired. (For instance in my t1.example, look at the entry field with the plum background. It responds to press the key 'q'). The multi-line entry below that is where you enter the command. As above, you want a one-line command, perhaps a lambda expression. There is a difference here, because when the event happens an argument 'e' is passed to the callback specifying the event. This means that the command is something like:

lambda e: foo_bar(1,3,5)

I believe that the Bindings window is only partially completed. I am able to bind events to the simple Tk widgets, but have been unable to do so for Tix subwidgets. For instance, I don't see a way to bind a mouse event to the listbox subwidet of the TixScrolledListbox. Again, I hate to spend any time fixing Tix widgets when I should be concentrating on a Python version of PAGE.

Keep in mind: To add binding one works with the 'Widget Bindings' window. Consider the Tags entry. It defaults to contain the word 'all'. I think that that means that all the widgets get the binding? Anyway, I habitually remove the all when associating bindings with widgets.

Defining Functions

If you name a function in a menu or an event binding or something similar the you will need to define that function. Do that by means of the function list window which is usually lying around or can be made visible from the Window menu. To create a function hit add. The normal thing to happen is that yet another window will appear. The first step is to type the function name in the top field. If you want a global function named fun_x just type fun_x there. Forget the arguments entry. In the main window type the Python function you want to create using Python syntax including the def statement complete with arguments. The rule is that you enter the function just as you want to see it in the Python code.

def method(self, a, b, c = None):
   print a
   print b
   if c != None:
      print c

One other point, if you need to import a module in order to execute a function, then put the import statement inside of the function.

Special Widget Processing

Menu Specification

There is a special menu for creating menu for menu buttons. It can be entered by double clicking on the menubutton. Thus, to create a menu, select a menu button from the tool bar and put it on the window just like any other widget. Then 'go to' the Attribute editor and modify the attributes as desired. From there, you can click on the menu attribute or double click on the widget itself.

Once in the menu window, you can select various types of entries like command, radio button, check box, or separator. Select add to put them in the list. If you select command, then it will say something like <command>command.

Put the mouse on that line and Double-Button-1 and yet another window opens up where you can type the command. You probably want to restrain your self to a one liner, perhaps a function call.

In Visual Tcl, I have not found a mechanism for specifying a menu-bar. You can place menu buttons anywhere in the window. I have taken a more restricted view, creating a menu-bar and putting all of the menu button inside. This means, for instance, that you cannot put a menu button inside a page of a tixNotebook. 

Menubutton placement is one of the very few places where I use pack, putting the menubuttons to the left of the menubar with the exception of a "Help" menubutton which goes to the right. One thing this means is that you do not have to use a lot of care in placing the menu button in the PAGE window. When the Python code is generated, the menu buttons are sorted according to their 'leftness' (in ascending order of the x value of the placement) and accordingly packed into the menu bar. This allows you to change the order of the menubuttons easily.

TixNoteBook Specification

One can create a TixNoteBook one on the GUI by (1) selecting the root window, and (2)selecting the notebook icon on the tool bar. Then drag it to it its destination on the GUI and resize it by (1) selecting the widget with Control-Button-1, (2) holding down the buttons while dragging it to its home, and (3) select it again with Control-Button-1 and drag a handle (one of those 8 black globs around the edge).

The widget as placed will have 2 blank pages in which other widgets can be placed. Page 1 is on top and widgets can be added. To add other pages to the note book or to bring up other pages for manipulation select the note book with Button-3, a pop up menu allows you to add a page or select one of the existing pages.

I have added many different widgets to a page of a note book, but I ran into trouble putting a TixNoteBook in a page of a TixNoteBook. I want to get this out rather than fix up that pathological case.

Using emacsclient

As I mentioned above, I use emacs as my preferred IDE and so I set up PAGE to use emacs for displaying and debugging the generated python code as well as for writing the functions for the GUI.

To use it, one executes the emacs command (server-start) and then goes in to the File menu of PAGE and selects Preferences and set the Use Emacsclient checkbox. If you do that, when it comes to Selecting the Generate Python from the pop up menu, the resulting code will appear almost magically in an emacs buffer. There it is possible to edit file, execute it, print it, etc.. When one wants to return to PAGE, just issue again the (server-start) command.

Running the Generated Gui

The generated GUI is written in such a way that it will run stand alone as a script. The final lines of the module are:

if __name__ == '__main__':
    vp_start_gui()

which will call vp_start_gui when the the script is exercised. The key is vp_start_gui which is generated automatically. This is the function which your application modules should call. I recommend that you do not modify it. It contains:

def vp_start_gui():
    global w
    root.title('New_Toplevel_1')
    root.geometry('934x975+86+69')
    w = New_Toplevel_1 (root)
    init()
    root.mainloop()

When you select the top level widget and Generate Python from the popup menu, the Python Window will appear be filled with the generated code. You can push the run button and execution will be attempted. This will automatically save the generated code in a py file where the root name matches the tcl file. Another thing you can do is save the python module, 'go to' a terminal window, and run the program there. What I prefer doing is to have emacsclient turned on so that the generated code will appear in an emacs buffer where I can execute it and usually I have to debug it.

When running in Python Window, line output from the GUI is directed to the lower window of the Python Window.

A final word about running in the Python Window is that the tcl file will be automatically saved as well as the python file. If no name has been selected for the project, then a file output window will open and you will be presented with a way to choose the tcl file name, the root of which will be used for the python file name. I have not implemented a 'save all' in the python window. To do that, 'go to' the main PAGE menu and do a save all, return to the Python Window, and select save or run.

Be sure to see the real live example.

The init() function

PAGE generates several functions for you automatically. the function init is generated conditionally. Since the startup function vp_start_gui calls init, init better exist. If you don't define one, then PAGE will generate one for you containing only the pass statement. You can use the same mechanism for defining init as any other function. PAGE will supply the following null init function in the absence of one you define.

def init():
    pass
To get some real function you can either define the init function in PAGE or edit the null init after GUI generation.

I recommend that you place all of your global specification stuff in init, including the imports that you need for the GUI. See classical.py. This is important to the current fix up.

Loading Python Files

As mentioned above, as you debug, in python, the generated GUI you will likely make changes to the functions in it and later you may want to modify the GUI. It is very important that you do not have to manually reenter information about the procedures into the tcl file.

Obviously, you can't load just any python file; but PAGE will take the function information from a PAGE generated python file and put it into the corresponding tcl file which can be loaded.

You will see in the File menu the 'Load Python' selection. How can a Tcl program manipulate Python code?, you ask. Of course, it can't. What happens is that a program 'inc.py' is invoked which reads the tcl file and replaces all of the user defined procedures in it with corresponding functions in the corresponding py file. inc.py does not analyze the generated program to look for things like global imports or global variables. I rely on your putting that sort of thing in the init function. It really keeps things clean and allows you greater flexibility because I do not have to anticipate them.

I would like to replace 'inc.py' with a program which will take the generated Python program and generate from it a corresponding tcl module that could be sourced. That would be good because it would obviate the need to keep the python and tcl versions of the program in step and would preserver manual changes to the widget invocation and configuration.  The down side is that it would have to be very stylized and thus might be a bit fragile.

I have now coded such a translator in Python; it is p2t.py and have included it in the distribution. As yet it is not fully debugged. I will work on that next. It is up to you to decide whether you would prefer to keep the tcl version around or to try the regenerated version. One invokes p2t with:

python p2t.py python_file_name

The trick to building p2t was to realize that the resulting tcl program did not have to execute.  All it has to do is set up a few data structures when it is sourced.  I would be very surprised if there were not a bunch of errors in p2t.  So, my recommendation is to continue to keep the 'tcl' companion file around.  But if you loose it try p2t, and let me know about the problems that you face.  When I get more confidence in p2t, I will incorporate it replacing inc. p2t.py is to be found in the main page installation directory. I believe that there are still problem in the program and I promise to look into them.

Default Consideration

In earlier versions of PAGE, I recommended use of the TkoptionsDB as a way of getting uniform non standard fonts and background colors. However, this came with a lot of problems when trying to move among different operating environments. This occurs because some of the widget sizes are based on character widths and they will differ between operating systems. Also, people will choose different color schemes and a nice looking GUI in one scheme may become very strange in another.

Thanks to Mike Clarkson, I am now convinced that the best thing to do if you want to have a reasonable GUI amomg different environments is to stick to the new WmDefaults approach and not try to override it with TkoptionsDB.

A Real Example

The example above is primarily a geewhiz example that includes many of the widgets that I am able to handle with PAGE. This is a simpler working example that shows how the main python application works with the generated GUI.

Classical.py

Classical.py is a very simple MP3 player for Linux that is geared to playing classical music. It was inspired by cplay. The main difference between this player and other players is the way randomness is handled. Most players allow one to play a random selection of music tracks but with classical music, one does not want a random selection of tracks but rather a random selection of CD's the tracks of which are played in order. This is what classical does. It is a complete program that I am running while writing this.

Classical.py is made up of two modules - classic.py and classicgui.py; the application and the GUI. Obviously, the GUI was created using PAGE. Since they are both small I have include them here. The files are also in the examples subdirectory. I think it is self explanatory but worth including. I often wish for more examples in documentation.


==================== classical.py ========================
#!/usr/bin/env python

# My simple program to play mp3 files. It is made up of
# this file and classgui.py.
#
# It starts simple and may grow up.
# Most of the programs for playing MP3's I have seen do not handle
# classical music very well.  The trouble is that they
# are more than happy to play a ramdom selection of tracks.
# With classical music I want to play ablum in ramdom
# order but with the tracks in each album played in track order.

# This program is basically a wrapper for mpg123 and at this point
# I have only run it on Linux. It is based on cplay.  I want to use
# PAGE rather than curses.

# There are a number of ways the program could evolve.  However,
# I want to put this on the site as a real working example before
# I expand it.


import sys
import os
import os.path
import random
import time
import thread
import classgui

# For starters, I will look at only one directory of albums.

home = os.environ['HOME']
dir = home + '/mp3/cdrip' # where my mp3's are stored.


# This is not used as yet.
#for MIXER in ["/dev/sound/mixer", "/dev/mixer"]:
#    if os.path.exists(MIXER): break

def read_albums():
    ''' Read list of albums. '''
    global albums
    albums = os.listdir(dir)

def pick_album():
    global pick
    no = len(albums)
    pick = random.randrange(len(albums))
    # Put album nameinto gui.
    classgui.set_label(albums[pick])

def get_track_list():
    global tracks
    # Just does an 'ls -rt' to get tracks in order.
    cmd = 'ls -rt %s/%s/*.mp3' % (dir,albums[pick])
    tracks = []
    tracks = os.popen(cmd).readlines()
    for i in range(len(tracks)):
        tracks[i] = tracks[i].strip()
    # Put the list into the gui.
    classgui.load_listbox(tracks)

def play_tracks():
    index = 0
    old_index = 0
    for i in tracks:
        # Highlight track in gui.
        classgui.highlight_selection(index)
        if index != old_index:
            classgui.deselect(old_index)
        old_index = index
        t = i.split('/')
        cmd = 'mpg123 -q -v -g 70 %s' % tracks[index]
        os.system(cmd)
        index += 1
        # See if any of the GUI buttons have been pushed.
        # Their actions are implemented here.
        if classgui.stop == 1:
            # This is how I quit.
            sys.exit()
        if classgui.new_disk == 1:
            # This is how I go to new disk.
            classgui.new_disk = 0
            return
        if classgui.repeat == 1:
            classgui.repeat = 0
            index -= 1
        if classgui.next_track == 1:
            classgui.next_track = 0
            index += 1
        if classgui.previous_track == 1:
            classgui.previous_track = 0
            if index > 1:
                index -= 2
            else:
                index -= 1


def main():
    random.seed()
    t = 1
    while t:
        read_albums()
        pick_album()
        get_track_list()
        if tracks == []:
            continue
        play_tracks()
        #t = 0

def start():
    # Shenanigans to start the gui which will call main()
    classgui.vp_start_gui()

if __name__ == "__main__":
    start()

======================== classgui.py ==================

#! /usr/bin/env python
from Tkinter import *
import Tix, sys
root = Tix.Tk()


def deselect(index):
    # Turn off the highlighting of previous track
    global bg
    listbox.itemconfigure(index, background='%s'%bg)


def highlight_selection(index):
    # Turn on highlighting of current track.
    listbox.itemconfigure(index, background="white")
    root.update()


def init():
    # Set up any and all globals.
    global stop, new_disk, no_in_listbox, repeat, new_disk, previous_track
    global thread, time, os, classical
    import thread
    import time
    import os
    # Here I am importing the file that imported this one.
    # It seems to be needed so that I can cummicate back and forth.
    import classical
    stop = 0
    new_disk = 0
    no_in_listbox = 0
    repeat = 0
    new_disk = 0
    previous_track = 0

    # Start new thread which will refresh the GUI periodically.
    thread.start_new_thread(refresh, ())

    # After we create the GUI go back to the main routine which will
    # do the actual playing.
    classical.main()


def load_listbox(tracks):
    global no_in_listbox
    # Clear listbox from previous stuff.
    listbox.delete(0, no_in_listbox)
    no_in_listbox = 0
    for i in tracks:
        ip = i.split('/')
        show = ip[len(ip)-1]              # Get just the end
        show = show.split('.')[0]         # Leave the mp3 behind
        # The stuff that follows is there because whin I rip the CD's
        # I put into the file name first a track number and a random
        # integer.
        show = show.split('_', 2)
        show = show[0] + ' ' + show[2]    # Leave out the random number
        show = show.replace('_', ' ')     # Get rid of dashes.
        listbox.insert(END, show)         # Put in the list box.
        no_in_listbox += 1



def new_disk():
    # Like above but set global variable that is inspected and
    # causes main routine to select another disk at random.
    global new_disk
    new_disk = 1
    os.system('killall mpg123')


def next_track():
    # This kills the player and the main routine will go on
    # to next track or disk.
    os.system('killall mpg123')


def previous_track():
    global previous_track
    previous_track = 1
    os.system('killall mpg123')

def repeat_track():
    global repeat
    repeat = 1
    os.system('killall mpg123')

def quit():
    # Kill the player, set a global variable for the main program
    # to query.
    global stop
    stop = 1
    os.system('killall mpg123')
    sys.exit()


def refresh():
    # This is present so that I can go to another virtual desk top
    # or obscure the window and then when I return my gui will be
    # refreshed.
    while 1:
        root.update()
        time.sleep(0.5)


def save_object(o):
    # Just keeps a handle to the listbox in global space so that
    # it can easily be updated as the disks are changed.
    # I also retain the background color.
    global listbox
    global bg
    listbox = o
    bg= listbox.cget('bg')

def set_label(album):
    # Display the name of the disk.
    album = album.replace('_', ' ')
    w.lab24.configure(text='%s' % album)



class New_Toplevel_1:
    def __init__(self, master=None):

        self.lab24 = Label (master)
        self.lab24.place(in_=master,x=20,y=30)
        self.lab24.configure(activebackground="black")
        self.lab24.configure(activeforeground="#f4ddb2")
        self.lab24.configure(background="#f4ddb2")
        self.lab24.configure(borderwidth="1")
        self.lab24.configure(font="helvetica -12")
        self.lab24.configure(foreground="#000000")
        self.lab24.configure(highlightbackground="ivory")
        self.lab24.configure(relief="raised")
        self.lab24.configure(text="Album")
        self.lab24.configure(width="62")

        self.tix25 = Tix.ScrolledListBox(master)
        self.tix25.place(in_=master,x=20,y=60)
        self.tix25.configure(scrollbar="auto")
        self.tix25.configure(background="#f4ddb2")
        self.tix25.configure(borderwidth="1")
        self.tix25.configure(height="259")
        self.tix25.configure(highlightbackground="ivory")
        self.tix25.configure(highlightcolor="Black")
        self.tix25.configure(width="431")
        self.tix25_listbox = self.tix25.subwidget_list["listbox"]
        self.tix25_listbox.configure(background="#ffe7ba")
        self.tix25_listbox.configure(font="helvetica -12")
        self.tix25_listbox.configure(highlightbackground="ivory")
        self.tix25_listbox.configure(highlightthickness="2")
        self.tix25_listbox.configure(selectbackground="#800000")
        self.tix25_listbox.configure(selectforeground="#ffffff")
        self.tix25_listbox.configure(takefocus="1")
        save_object(self.tix25_listbox) # ldcmd .top24.tix25.listbox

        self.but26 = Button (master)
        self.but26.place(in_=master,x=30,y=360)
        self.but26.configure(background="#f4ddb2")
        self.but26.configure(command=next_track)
        self.but26.configure(disabledforeground="#808080")
        self.but26.configure(font="helvetica -12")
        self.but26.configure(foreground="#000000")
        self.but26.configure(highlightbackground="ivory")
        self.but26.configure(text="Next Track")

        self.but27 = Button (master)
        self.but27.place(in_=master,x=210,y=360)
        self.but27.configure(background="#f4ddb2")
        self.but27.configure(command=quit)
        self.but27.configure(disabledforeground="#808080")
        self.but27.configure(font="helvetica -12")
        self.but27.configure(foreground="#000000")
        self.but27.configure(highlightbackground="ivory")
        self.but27.configure(text="Quit")

        self.but28 = Button (master)
        self.but28.place(in_=master,x=360,y=360)
        self.but28.configure(background="#f4ddb2")
        self.but28.configure(command=new_disk)
        self.but28.configure(disabledforeground="#808080")
        self.but28.configure(font="helvetica -12")
        self.but28.configure(foreground="#000000")
        self.but28.configure(highlightbackground="ivory")
        self.but28.configure(text="New Disk")

        self.but23 = Button (master)
        self.but23.place(in_=master,x=30,y=400)
        self.but23.configure(background="#f4ddb2")
        self.but23.configure(command=repeat_track)
        self.but23.configure(disabledforeground="#808080")
        self.but23.configure(font="helvetica -12")
        self.but23.configure(foreground="#000000")
        self.but23.configure(highlightbackground="ivory")
        self.but23.configure(text="Repeat Track")

        self.but24 = Button (master)
        self.but24.place(in_=master,x=30,y=440)
        self.but24.configure(background="#f4ddb2")
        self.but24.configure(command=previous_track)
        self.but24.configure(disabledforeground="#808080")
        self.but24.configure(font="helvetica -12")
        self.but24.configure(foreground="#000000")
        self.but24.configure(highlightbackground="ivory")
        self.but24.configure(text="Previous Track")

        self.balloon = Tix.Balloon(master)
        self.balloon.configure(bg='blue', borderwidth=3)


def vp_start_gui():
    global w
    root.title('New_Toplevel_1')
    root.geometry('473x477+0+100')
    w = New_Toplevel_1 (root)
    init()
    root.mainloop()

if __name__ == '__main__':
    vp_start_gui()

Final Recommendations

Save often.
Spring for Grayson's book.
Save often.
 

Acknowledgements

First thanks to HP for assigning the rights of PAGE to me which allows me to make this available to the Python community.

Again, let me acknowledge that PAGE is built on top of Visual Tcl.  Without that work I probably would not have known how to get started.

I had difficulty understanding the new WmDefault package and how to exploit it in PAGE. Mike Clarkson gave me a lot of help. Mike also helped me enormously in getting a reasonable installation package together for Win32.

Finally, I got a much essential help and very fast response from Ioi Lam, the author of Tix.