Screenshot CONTENTS Screenshot

GUI Development

Java graphics chic01.gif The primary toolkits for developing graphical user interfaces (GUIs) in Jython are Java's Abstract Windowing Toolkit (AWT) and Swing classes. These toolkits are the logical choice considering that Jython is written in Java, and excels at using Java classes within Python's syntax. This doesn't mean that the AWT and Swing are the only choices for implementing GUIs. An increasing number of bindings to other toolkits also allow Java, and thus Jython, to use those toolkits as well. This chapter, however, is limited to AWT and Swing GUIs written with Jython. This chapter does not cover AWT and Swing basics. Previous Java knowledge is assumed of the reader, so basics are unnecessary, but a bigger reason is that the scope of such and undertaking grossly exceeds the bounds of this tutorial. Fortunately, shelves at local bookstores appear fully saturated with quality AWT and Swing material. What you can expect from this tutorial are details concerning what Jython brings to developing GUI apps with the AWT and Swing toolkits. Jython brings automatic bean properties, event properties, convenient keyword arguments and some convenience tools located in Jython's pawt package. Translating Java GUI examples into Jython code is the fastest way to understand Jython GUI development; therefore, Java-to-Jython translations begins this chapter. Additionally, Jython has two valuable contributions to ease and accelerate development with the AWT and Swing classes: automatic bean attributes and the pawt package. Both of these are detailed in this chapter. The remaining sections develop larger examples to help reinforce techniques used in writing Jython GUIs.

Comparing a Java and Jython GUI

Writing a GUI in Jython is very similar to doing so in Java. Most any Java GUI is easily translated to Jython with minimal effort and will work as expected. Once accustomed to using the AWT and Swing from Jython, the prototyping of GUIs in Jython, and later translating into Java (as needed) becomes more appealing. Listing 10.1 shows a simple Java GUI followed by Listing 10.2, which shows a similar GUI written in Jython.

Listing 10.1 A Simple Java GUI
// file SimpleJavaGUI.java import java.awt.*; import java.awt.event.*; class SimpleJavaGUI implements ActionListener {
 private Button mybutton = new Button("OK"); private Label mylabel = new Label("A Java GUI", Label.CENTER); public SimpleJavaGUI() {
 Frame top_frame = new Frame(); Panel panel = new Panel(); top_frame.setTitle("A Basic Jython GUI"); top_frame.setBackground(Color.yellow); //WindowListener needed for window close event top_frame.addWindowListener(
 new WindowAdapter() {
 public void windowClosing(windowEvent e) {
 System.exit(0); } }); mybutton.addActionListener(this); panel.add(mylabel); panel.add(mybutton); top_frame.add(panel); // pack and show top_frame.pack(); Dimension winSz = Toolkit.getDefaultToolkit().getScreenSize(); top_frame.setLocation(winSz.width/2 - top__frame.getWidth()/2, winSz.height/2 - top__frame.height()/2); top_frame.setVisible(true); } public static void main(String[] args)) {
 SimpleJavaGUI s = new SimpleJavaGUI(); } public void actionPerformed(ActionEvent event) {
 System.exit(0); } } 


The GUI produced from running the class in Listing 10.1 is shown in Screenshot.

Screenshot A simple Java GUI.

Java graphics 10fig01.gif


Note the use of AWT classes, bean properties, and bean events in Listing 10.1. The example uses the Frame, Panel, Button, and Label classes from the AWT. These classes employ bean attribute assignments such as the .setVisible() method. Additionally, the example sets event listeners with methods such as the .addWindowListener method. These elements are important points of comparison with Jython. Implementing the same GUI in Jython requires the obvious changes to syntax. Java's type declarations, access modifiers, semicolons, and braces are absent from Jython. Take the instantiation of the Button component in Java:

import java.awt.*; private Button mybutton = new Button("OK"); 


In Jython, this becomes the following:

from java import awt mybutton = awt.Button("OK") 


Instantiating the button in Jython uses the awt package prefix. The reason why is because from java.awt import * is discouraged in Jython (we know this from ,"Modules and Packages"), so you would instead import the package or explicit class names. Using from java import awt is the more common approach, but this requires that the package name be used in subsequent references to AWT classes, such as awt.Button. Jython does automatic type conversion to and from Java types, and translating Listing 10.1 in Jython requires attention to the type of object used in Jython. The setVisible method, for example, requires a Java Boolean argument. Jython converts a PyInteger of 1 to Boolean true, and 0 to a Boolean false. This means that the visible property should be set to 1 in Jython. An interesting point that foreshadows the discussion of Jython's automatic bean properties is that Jython is not required to use the setVisible method, but can instead assign 1 to the visible property. A class is not required in Jython. A Jython class would certainly work, and might be a more accurate translation of Listing 10.1, but a class is certainly optional in Jython. Listing 10.2 chooses to not use a class, yet implements a GUI nearly identical to that in Listing 10.1. If a class had been used, it would be important to point out how Jython methods require an explicit self parameter in the first parameter slot. Previous chapters make this clear, but it is common enough of a slip when converting Java code to Jython that it warrants another reminder. Listing 10.2 does not define a class just to show how it is possible to implement a GUI without a class; however, the nature of the AWT and Swing classes make complex classless GUIs difficult to implement. The window event and button event in Listing 10.1 are simple tasks within a very basic AWT app; however, these tasks already begin to tie the proverbial Gordian knot. An anonymous inner class implements WindowAdapter so that it may handle the windowClosing event. The SimpleJavaGUI class itself implements the ActionListener interface so that it may handle the button action in the actionPerformed method. The addWindowListener and addActionListener methods set the appropriate instances for the handling of these events. Now look at Listing 10.2 and notice that WindowAdapter, addWindowListener, and addActionListener do not even exist in the SimpleJythonGUI ! Instead, the Jython version uses assignments to handle these properties and events. These assignments are another first-rate device for simplicity that Jython offers by automatically adding bean properties and events as class attributes.

Listing 10.2 A Simple Jython GUI
# File: simpleJythonGUI.py import java from java import awt # Make an exit function def exit(e): java.lang.System.exit(0) #Make root frame and the panel it will contain top_frame = awt.Frame(title="A Basic Jython GUI", background = awt.Color.yellow, windowClosing=exit) panel = awt.Panel() #Add stuff to look at inside frame mylabel = awt.Label("A Jython GUI", awt.Label.CENTER) mybutton = awt.Button("OK", actionPerformed=exit) panel.add(mylabel) panel.add(mybutton) top_frame.add(panel) # pack and show top_frame.pack() # set location toolkit = awt.Toolkit.getDefaultToolkit() winSz = toolkit.screenSize top_frame.setLocation(winSz.width/2 - top__frame.size.width/2, winSz.height/2 - top__frame.height/2) top_frame.visible = 1 


The GUI produced from running the Jython implementation is visually similar to that in Screenshot only with the appropriate name change. Figure 10.2 shows the Jython implementation of this simple GUI.

Screenshot A simple Jython GUI.

Java graphics 10fig02.gif


Bean Properties and Events

A quick review of beans is in order before describing Jython automatic bean attributes. A bean is any class that follows appropriate conventions for methods, properties, and events. A bean's methods are all of the methods within the bean designated as public. A bean's properties are data objects manipulated through get and set methods. A bean defines events by designating listener objects with add and remove methods. The class below can be read as "bean A with the read-only property name."

public class A {
 private String name; public String getName() {
 return name; } } 


Whereas Java would directly use the getName() method in the preceding bean, Jython adds a valuable shortcut—not just for properties like the name property above, but for properties and events. Jython uses inspection to determine bean properties and events, and then it allows you to use these bean properties and events as instance attributes. Java would use the following to retrieve the above bean's name property:

import A; bean = new A(); String name = bean.getName(); 


Within Jython, you can simply treat the name property as an attribute, and you can therefore access the name property with just the following:

>>> import A >>> b = A() >>> name = b.name 


You need not call any methods to retrieve the name property because Jython automatically treats bean properties as class attributes. You still may use the getName() method from within Jython, but you do not have to because its functionality is done behind the scenes with Jython's automatic bean properties. The same simplicity applies to setter methods. A setName method in bean A would translate into the following Java usage:

import A; A bean = new A(); bean.setName("new name"); 


In Jython, however, it is only an attribute assignment:

import A bean = A() bean.name = "new name" 


Although these examples are with simple properties, the principle is the same for events. This principle, combined with Jython's keyword parameters simplifies many aspects of GUI's. The following lines from Listing 10.2 use Jython's keyword arguments in a constructor to set a couple properties in a component's state:

top_frame = awt.Frame(title="A Basic Jython GUI", background = awt.Color.yellow, windowClosing=exit) mybutton = awt.Button("OK", actionPerformed=exit) 


Jython's automatic bean property and bean event attributes are a shortcut, as noted earlier, but this should not imply they are less valid than using the methods themselves as Java does. The use of automatic bean properties is very apropos in Jython's syntax, and they are especially valuable when working with GUIs because beans permeate the AWT and Swing classes. The feature that makes them so apropos within Jython is keyword parameters. Using keyword parameters to set bean properties works exceptionally well, although this only works in Java constructors. Following is Java pseudo-code and Jython code that sets up a frame. Notice the difference that keyword parameters make.

// In Java (pseudo-code) import java.awt.*; Frame myframe = new Frame("A test Frame"); myframe.setBackground(Color.gray); myframe.addWindowListener(new SomeWindowListenerClass()); myframe.setBounds(20, 20, 200, 300); # In Jython from java import awt myframe = awt.Frame("A test Frame", background=awt.Color.gray, bounds=(20, 20, 200, 300), actionPerformed=someCallableObject) 


Although this doesn't always save space, lines, or typing, it is more legible due to the close visual association properties and events have with their component when set with keyword arguments. Looking back at Listing 10.1, we see how Java classes interact with bean properties by explicitly using get and set methods. Listing 10.1 demonstrated this with the following methods:

Dimension winSz = Toolkit.getDefaultToolkit().getScreenSize(); top_frame.setLocation(winSz.width ... winSz.height); top_frame.setTitle("A Basic Jython GUI"); top_frame.setBackground(Color.yellow); top_frame.setVisible(true); 


Additionally, Java specifies bean events with add and remove methods, of which, the add method appears twice in Listing 10.1.

top_frame.addWindowListener(new exitClass()); mybutton.addActionListener(this); 


Jython could use the same methods as the Java app does. top_frame.setBounds(...) would work the same in Jython along with all the others mentioned above. Jython, however, adds the ability to work with bean properties and events as if they were normal Jython class attributes. Looking back at Listing 10.2 we see the attribute assignments that have supplanted bean property methods:

winSz = toolkit.screenSize title="A Basic Jython GUI" background = awt.Color.yellow actionPerformed=exit 


Additionally, Listing 10.2 registers event handlers with simple attribute assignments that specify a Jython function:

windowClosing=exit actionPerformed=exit 


The justification for this long diversion into bean properties lies in the prominence bean properties have in AWT and Swing classes. Just looking at the java.awt.Frame class shows how basic bean properties are in the AWT. The Frame class itself has thirteen bean property methods listed in the jkd1.3 API documentation:

getAccessibleContext, getCursorType, getFrames, getIconImage, getMenuBar, getState, getTitle, setCursor, setIconImage, setMenuBar, setResizable, setState, setTitle. 


The Frame class also inherits 10 additional accessor methods from the java.awt.Window class:

getFocusOwner, getGraphicsConfiguration, getInputContext, getListeners, getLocale, getOwnedWindows, getOwner, getToolkit, getWarningString, setCursor 


Frame also inherits fourteen accessor methods from the java.awt.Container class:

getAlignmentX, getAlignmentY, getComponent, getComponentAt, getComponentAt, getComponentCount, getComponents, getInsets, getLayout, getMaximumSize, getMinimumSize, getPreferredSize, setFont, setLayout 


This class also inherits thirty-nine methods from the java.awt.Component class:

getBackground, getBounds, getBounds, getColorModel, getComponentOrientation, getCursor, getDropTarget, getFont, getFontMetrics, getForeground, getGraphics, getHeight, getInputMethodRequests, getLocation, getLocation, getLocationOnScreen, getName, getParent, getPeer, getSize, getSize, getTreeLock, getWidth, getX, getY, setBackground, setBounds, setBounds, setComponentOrientation, setDropTarget, setEnabled, setForeground, setLocale, setLocation, setLocation, setName, setSize, setSize, setVisible 


And let's not forget the most basic of bean properties defined in

java.lang.Objects: getClass 


Interacting with just a simple Frame container involves a repletion of bean properties that work just like class attributes in Jython. The following interactive example demonstrates what Jython does with the Frame object's bean properties. Of special interest is the keyword arguments used in instantiating the frame class.

>>> import java, sys >>> myframe = java.awt.Frame(title="Test", background=(220,220,4)) >>> myframe.bounds=(40, 30, 200, 200) >>> myframe.visible=1 >>> myframe.background = 150, 240,25 # Change color interactively >>> # Try clicking on the window close icon here- it does nothing. >>> myframe.windowClosing = lambda x: sys.exit(0) >>> # now try closing the frame- it works. 


Jython's keyword arguments make it possible to establish numerous bean properties and events in a component's constructor. Even ignoring the keyword parameters, the Frame container doesn't have a stock constructor that allows such arguments. Instead, Jython's magic bean attributes make it possible to establish so much of the bean state in the constructor. Better yet is what Jython does with tuple arguments like those assigned to bounds and background in the preceding interactive example.

Bean Properties

Beans are mostly about convention. A bean property called extent has the associated accessor and mutator methods getExtent and setExtent. Notice the capitalization convention. The property and its pair of associated methods are lowercase-first with each successive word capitalized. This convention determines the name of the bean attributes defined in Jython. If a Java class has a method named setAMultiWordName, then Jython adds an automatic bean property name aMultiWordName. A bean property is read-only if it only defines the get method, whereas a property with only an associated set method becomes write-only. Defining both creates a read-write property. The get and set method signatures need to complement each other for the full read and write access. This means that if a set method's signature has a string parameter, then the complimentary get method must return a string object. The following Java bean wrongly sets the setName parameter to the generic Object.

public class A {
 private String name = ""; public String getName() {
 System.out.println("Getting name"); return name; } public void setName(Object s) {
 name = (String)s; System.out.println("Setting name"); } } 


Using the preceding bean with Jython's automatic bean attributes creates the following output:

>>> import A >>> b = A() >>> b.name Getting name '' >>> b.name = "this" Traceback (innermost last): File "<console>", line 1, in ? AttributeError: read-only attr: name 


If the class were rewritten to have the appropriate complementary signatures, it would then interact properly with Jython's bean attribute assignments:

public class A {
 private String name = "'; public String getName() {
 System.out.println("Getting name"); return name; } public void setName(String s) {
 name = s; System.out.println("Setting name"); } } 


A quick interactive test of the new bean confirms proper read-write access to the name property.

>>> import A >>> b = A() >>> b.name = "New name" Setting name >>> b.name Getting name 'New name' 


Bean properties may also be a sequence. This creates four accessor methods if you desire full read-write access to a sequence, or indexed bean. The four methods would be as follows:

setProperty([]) // Sets the entire sequence setProperty(int, Object) // Sets a specific index getProperty() // Gets the entire sequence getProperty(int) // Gets a specific index 


However, Jython's automatic bean properties only use the getProperty() and setProperty([]) methods of the four. Listing 10.3 is a Java bean with an indexed property called items.

Listing 10.3 A Bean with an Indexed Property
// file: ItemBean.java public class ItemBean {
 private String[] data = {"A", "B", "C"}; public ItemBean() { ; } public String[] getItems(() {
 return data; } public String getItems(int i) {
 return data[i]; } public void setItems(String[] items)) {
 data = items; } public void setItems(int index, String item) {
 data[index] = item; } } 


The following interactive console session demonstrates the interaction with the items property from Listing 10.3.

>>> import ItemBean >>> ib = ItemBean() >>> >>> # This calls ib.getItems() >>> ib.items array(['A', 'B', 'C'], java.lang.String) >>> >>> # The following does not call ib.getItems(int i), but instead >>> # calls ib.getItems() and applies the index to the returned array >>> >>> # The following calls ib.setItems([]) >>> ib.items = ["1", "2", "3"] 


Bean Properties and Tuples

Jython automatically interprets tuples assigned to bean properties as constructors for the type of property being set. The three-member tuple assigned to the background property automatically becomes constructor values to the Java.awt.Color class because the bean property type is java.awt.Color. The tuple assignment to the bounds property automatically becomes a Rectangle type. This automatic property type construction further simplifies Jython GUI code. Again, note that Jython's automatic bean attributes are most effective in conjunction with Jython's keyword parameters. The difference between f.setVisible(1) and f.visible = 1 is negligible, but the keyword parameters often makes a valuable difference.

// In Java (pseudo-code) import java.awt.*; Frame f = new Frame(); Label L = new Label("This is a Label"); L.setBackground( new Color(50, 160, 40)); L.setForeground( new Color(50, 255, 50)); L.setFont(new Font("Helvetica Bold", 18, 24)); f.add(L); f.setVisible(true); # In Jython from java import awt f = awt.Frame() L = awt.Label("This is a Label", background=(50, 160, 40), foreground=(50, 255, 50), font=("Helvetica Bold", 18, 24)) f.add(L) f.visible = 1 


Bean Events

Bean events are those events registered with addEvent Listener and removeEvent Listener .(Event is a placeholder for the actual type of event). For example, the java.awt.Window class, or Window bean, registers window events with the addWindowListener method. The addWindowListener method requires a class that implements the WindowListener interface as an argument. If you wish to handle an event in a Java GUI, you need to add a class that implements an event listener for that type of event. The windowClosing event , for example, is defined in the interface WindowListener. Therefore, a Java app must use the bean event addWindowListener to add a class that implements the WindowListener interface or extend a class that does. This class would then be required to implement the method windowClosing. Java also allows an anonymous inner class to satisfy the interface, and therefore handle the event. Jython, however, uses automatic event properties as attributes. This allows Jython to leverage its first-class functions. Although using first-class functions is the common implementation of this, it actually works with any callable Jython object. Jython's automatic bean event attributes let you assign a callable object to an event's bean name. To implement the windowClosing event, just assign a callable object to the container's windowClosing attribute. The callable object should accept one parameter, the event. The event action does not need to be a class, nor must it have a specific name. In other words, an event's name acts like a class attribute in Jython. Events such as windowClosing, windowOpened, and windowActivated are simple, assignable attributes in Jython as demonstrated in the following interactive interpreter example:

>>> def wclose(e): ... print "Processing the windowClosing Event" ... e.getSource().dispose() ... >>> def wopen(e): ... print "Processing the windowOpened Event" ... >>> def wact(e): ... print "Processing the windowActivated event" ... >>> import java >>> f = java.awt.Frame("Jython AWT Example") >>> f.windowClosing = wclose >>> f.windowOpened = wopen >>> f.windowActivated = wact >>> f.visible = 1 >>> Processing the windowActivated event Processing the windowOpened Event # click out of window here Processing the windowActivated event # click on window here Processing the windowClosing Event # click on window close box 


All the event attribute assignments may be included in the constructor as keyword arguments as well:

f = java.awt.Frame("Jython AWT Example", windowOpened=wopen, windowClosing=wclose, windowActivated=wact) 


Name Priorities

Jython supports instance methods, class-static fields, bean properties, and bean event properties. This allows for potential naming conflicts, where a method may have the same name as a bean property or event property. What happens in cases where multiple properties share the same name? Jython resolves name conflicts by assigning priorities. Preference is given to instance methods. A method name will always precede other conflicting property names. Class-static attributes appear next in the hierarchy. The following Jython class defines a class-static property called windowClosing. This is a class field, not an event, and it will precede the event of the same name because of its priority:

>>> class f(java.awt.Frame): ... windowClosing="This" ... >>> myf = f() >>> myf.windowClosing = lambda x: java.lang.System.exit(0) >>> myf.visible=1 # try to close the window now 


Event properties and bean properties follow in the hierarchy, in that order. The summary of this order is as follows:

(1) Methods (2) Class-static attributes (3) Event Properties (4) Bean properties 


pawt Package

Jython's pawt package adds a few convenience modules for using AWT and Swing in Jython. GridBag, colors, test, and swing are the import functionality within the pawt package.

GridBag

The pawt.GridBag class is a wrapper class designed to help with java.awt.GridBagLayout and java.awt.GridBagConstraints. There are two methods in the GridBag class: add and addRow. Working with most layout managers, such as BorderLayout, CardLayout, FlowLayout, and GridLayout, within Jython varies little from working with the same layout managers in Java. Jython does allow the shortcut assignment of a layout manager in a container's constructor. The following snippet shows how to establish a container's layout manager with keywords in a constructor:

from java import awt from java.lang import System f = awt.Frame("Test", bounds=(100,100,200,200), layout=awt.FlowLayout(), windowClosing=lambda e: System.exit(0) ) 


The GridBagLayout does differ from other layouts in Jython because Jython has a class that adds convenience to this complicated layout manager. Components display according to their attached layout constraints with the GridBagLayout, but working with both the GridBagLayout and GridBagConstraints is awkward. Bean properties are again useful in reducing this awkwardness, but Jython's pawt.GridBag class helps reduce this awkwardness. To use the pawt.GridBag class with a Java container, supply an instance of the container to the pawt.GridBag constructor. The following lines create a java.awt.Frame instance and add that frame to the pawt.GridBag class. Once added, the frame uses the GridBagLayout:

from java import awt import pawt top_frame = awt.Frame("GridBag frame") bag = pawt.GridBag(top_frame) 


The instantiation of the pawt.GridBag class optionally accepts keyword arguments for default constraints. The following instantiation of the pawt.GridBag class sets fill to GridBagConstraints.VERTICAL as a default. Note the use of "VERTICAL" in quotes as opposed to the more verbose

GridBagConstraints.VERTICAL. bag = pawt.GridBag(top_frame, fill="VERTICAL") 


The pawt.GridBag class uses two methods to add components to a container that uses the GridBagLayout: add and addRow. To add a label and a TextField in Java with GridBagConstraints looks like the following (pseudo-code):

import java.awt.*; Panel pane = new Panel(); GridBagLayout bag = new GridBagLayout(); GridBagConstraints bagconstraints = new GridBagConstraints(); pane.setLayout(bag); Label nameLabel = new Label("name: "); bagconstraints.anchor = GridBagConstraints.WEST; bagconstraints.fill = GridBagConstraints.NONE; bagconstraints.gridwidth = 1; pane.add(nameLabel, bagconstraints); TextField nameField = new textField(25); bagconstraints.anchor = GridBagConstraints.WEST; bagconstraints.fill = GridBagConstraints.HORIZONTAL; bagconstraints.gridwidth = 3; pane.add(nameLabel, bagconstraints); 


With Jython's pawt.GridBag class, you can shorten this to the following (pseudocode):

from java import awt from pawt import GridBag pane = awt.Panel() bag = GridBag(pane, fill='HORIZONTAL') nameLabel = awt.Label("Name: ") nameField = awt.TextField(25) bag.add(nameLabel, anchor="WEST", fill="NONE", gridwidth=1) bag.addRow(nameField, anchor="WEST", fill="HORIZONTAL", gridwidth=3) 


The add method adds the specified component with the constraints specified as keyword arguments. The addRow method does the same except that it also completes the row, with the subsequent component beginning on the following row. Listing 10.4 uses the pawt.GridBag module to simplify the setup of a address tutorial entry. The listing requires only minimal use of keyword constraints. The container that uses the GridBaglayout is the first argument to the pawt.GridBag class. Following this are default constraints, and the fill constraint is set to "HORIZONTAL" by default in this example. The addition of the category choice component is the only component that uses keyword constraints. To set the fill constraint to NONE, the keyword parameter fill is set to the string "NONE" instead of to GridBagConstraints.NONE. This abbreviation applies to all of the GridBagConstraint fields.This allows the simple keyword argument anchor="WEST" to set a component's anchor constraint.

Listing 10.4 GridBagConstraints the Easy Way: pawt.GridBag
from java import awt from java.awt.event import ActionListener from pawt import GridBag from java.lang import System frame = awt.Frame("GridBag Test", visible=1,size=(200,200), windowClosing=lambda e: System.exit(0)) pane=awt.Panel(background=(200,200,255)) frame.add(pane) bag = GridBag(pane, fill="HORIZONTAL") labelFactory = lambda x: awt.Label(x, background=awt.Color.yellow) title = awt.Label("Jython GridBag Address Book", awt.Label.CENTER) bag.addRow(title) category = awt.Choice() map(category.add, ["Family", "Friends", "Business"]) bag.add(labelFactory("Category: ")) bag.addRow(category, anchor="WEST", fill="NONE") name = awt.TextField(25) bag.add(labelFactory("Name: ")) bag.addRow(name) address = awt.TextField(35) bag.add(labelFactory("Address: ")) bag.addRow(address) city = awt.TextField(25) bag.add(labelFactory("City: ")) bag.add(city) state = awt.TextField(2) bag.add(labelFactory("State: ")) bag.add(state) zip = awt.TextField(5) bag.add(labelFactory("zip: ")) bag.addRow(zip) homephone_areacode = awt.TextField(3) homephone_prefix = awt.TextField(3) homephone_suffix = awt.TextField(4) cellphone_areacode = awt.TextField(3) cellphone_prefix = awt.TextField(3) cellphone_suffix = awt.TextField(4) frame.pack() 


Executing the module in Listing 10.4 produces the output shown in Screenshot.

Screenshot Address tutorial entry with pawt.GridBag.

Java graphics 10fig03.gif


colors

The colors module within the pawt package is a set of many named colors. Because people often remember names better than numbers, these predefined colors add names just for their convenience. For example, the color papayawhip is very easy to remember, but its RGB component (255, 239, 213) is less memorable. To get a full listing of the colors defined within pawt.colors, examine the contents of the sys.prefix/Lib/pawt/colors.py file, or execute the following in Jython's interactive interpreter:

>>> import pawt >>> dir(pawt.colors) ['__doc__', '__file__', '__name__', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', ... 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen'] 


test

The test function within the pawt package allows a simple test of graphic components. To test a button color without having to use a Frame or Panel to see it, use pawt.test.

>>> import java >>> import pawt >>> b = java.awt.Button("help", background=(212,144,100)) >>> pawt.test(b) 


The test function optionally accepts a size argument. The size argument should be a tuple or list with integers for width and height:

>>> from java.awt import Label >>> import pawt >>> L = Label("Test", background=(255,10,10), foreground=(10, 10, 100)) >>> pawt.test(L, (200,140)) 


The test function returns the root frame used so that you may continue to interact with the test:

>>> from java.awt import Label >>> import pawt >>> L = Label("Test", background=(110,10,10), foreground=(10,110,100)) >>> f = pawt.test(L, (200,250)) >>> f.resize(100,140) >>> L.font="Helvetica Bold", 20,24 >>> f.dispose() >>> # note the prompt is still available for further tests this way 


pawt.swing

The pawt.swing module does two things: It selects the appropriate Swing library for your JDK, and it has a test function specific to Swing components. Retrieving the correct Swing library is useful for those who work in both 1.1 and 1.2 JDKs. Its need diminishes with time, but it allows developers to write Jython Swing code that works in both java1.1 and java1.2 without changes. This module also adds in a test function equivalent to pawt.test, but using Swing widgets:

>>> import pawt >>> cb = pawt.swing.JCheckBox("Test Check Box") >>> myframe = pawt.swing.test(cb) >>> myframe.dispose() >>> myframe = pawt.swing.test(cb, (100,200)) 


There are drawbacks to using the pawt.swing module however. It is a very dynamic module, which creates problems in compiled Jython. Because of this, it is best not to use the pawt.swing module when you subclass a swing component, or when you are compiling the Jython app into class files. Instead, use javax.swing directly.

Examples

This section contains a number of examples that clarify using the AWT and Swing toolkits in Jython.

Simple AWT Graphics

Listing 10.5 is a simple banner drawn in the java.awt.Canvas's paint method. The paint method receives a Graphics2D object, and the listing uses that object to print the Jython banner. Listing 10.5 uses Jython's automatic bean properties for the canvas's height and width properties, and the Graphics' font and color properties. It also uses Jython's automatic event properties to establish the windowClosing event in the Frame's constructor.

Listing 10.5 A Jython Banner
# file: jythonBanner.py from java import awt import java class Banner(awt.Canvas): def paint(self, g): g.color = 204, 204, 204 g.fillRect(15, 15, self.width-30, self.height-30) g.color = 102, 102, 153 g.font = "Helvetica Bold", awt.Font.BOLD, 28 message = ">>> Jython" g.drawString(message, self.width/2 - len(message)*10, #approx. center self.height/2 + 14) #same here top_frame = awt.Frame("Jython Banner", size=(350, 150), windowClosing=lambda e: java.lang.System.exit(0)) top_frame.add( Banner() ) top_frame.visible = 1 


Running the jythonBanner.py script should produce a banner similar to Screenshot. The paint method is called on each resize event. You can resize the banner to notice how it adjusts.

Screenshot A Jython banner.

Java graphics 10fig04.gif


Adding Events

Events in Jython, as noted earlier, are simple attribute assignments. Previous examples demonstrate this with the windowClosing event and a button's actionPerformed event. Listing 10.6, however, demonstrates the use of mouse events defined in java.awt.event.MouseListener and java.awt.event.MouseMotionListener interfaces. Implementing event handlers for these events still acts just like an attribute assignment thanks to Jython's automatic event properties. Listing 10.6 is a polar graph that displays the radius (r) and angle (theta) of the mouse location as the mouse traverses a graph canvas. The PolarCanvas class is the component that implements the graph, and it is a subclass of java.awt.Canvas. Subclassing the java.awt.Canvas class allows PolarCanvas to implement the paint method, which draws the graph. The intent of the app is to display coordinates in labels when the mouse is over the PolarCanvas component. This means that the PolarCanvas class is the one that should register the mouse motion listener. The following line is what defines the mouseMoved event handler:

self.graph.mouseMoved = self.updateCoords 


The assignment in the preceding line means that whenever the mouse moves over the PolarCanvas component, the Main class's updateCoords method is called. The updateCoords method is what displays the desired coordinates at the top of the window. In addition to displaying mouse coordinates, Listing 10.6 displays points registered by the release of the mouse button. The mouseReleased event, which registers a display point, is set in the PolarCanvas constructor. The mouseReleased event is handled by the onClick method, which adds a display point and repaints the canvas.

Listing 10.6 Graphing Polar Coordinates
# file: PolarGraph.py import java from java.lang import System from java import awt from java.lang import Math class Main(awt.Frame): def __init__(self): self.background=awt.Color.gray self.bounds=(50,50,400,400) self.windowClosing=lambda e: System.exit(0) self.r = awt.Label("r: ") self.theta = awt.Label("theta: ") infoPanel = awt.Panel() infoPanel.add(self.r) infoPanel.add(self.theta) self.graph = PolarCanvas() self.add("North", infoPanel) self.add("Center", self.graph) self.visible = 1 self.graph.mouseMoved = self.updateCoords def updateCoords(self, e): limit = self.graph.limit width = self.graph.width height = self.graph.height x = (2 * e.x * limit)/width - limit y = limit - (2 * e.y * limit)/height r = Math.sqrt(x**2 + y**2) if x == 0: theta = 0 else: theta = Math.atan(Math.abs(y)/Math.abs(x)) if x < 0 and y > 0: theta = Math.PI - theta elif x < 0 and y <= 0: theta = theta + Math.PI elif x > 0 and y < 0: theta = 2 * Math.PI - theta self.r.text = "r: %0.2f" %% r self.theta.text = "theta: %0.2f" %% theta class PolarCanvas(awt.Canvas): def __init__(self, interval=100, limit=400): self.background=awt.Color.white self.mouseReleased=self.onClick self.interval = interval self.limit = limit self.points = [] def onClick(self, e): x = (2 * e.x * self.limit)/self.width - self.limit y = self.limit - (2 * e.y * self.limit)/self.height self.points.append(awt.Point(x, y)) self.repaint() def paint(self, g): rings = self.limit/self.interval step = (self.width/(rings*2), self.height/(rings*2)) # Draw rings for x in range(1, rings + 1): r = awt.Rectangle(x*step[0], x*step[1], step[0] **(rings-x)*2, step[1]*(rings-x)*2) lambda x, y: max(y - x ** 20, 10) g.color = (max(140-x*20,10), max(200-x*20,10), max(240-x*20,10)) g.fillOval(r.x, r.y, r.width, r.height) g.color = awt.Color.black g.drawOval(r.x, r.y, r.width, r.height) g.drawString(str((rings*self.interval)-(x*self.interval)), r.x - 8, self.height/2 + 12) # draw center dot g.fillOval(self.width/2-2, self.height/2-2, 4, 4) # draw points g.color = awt.Color.red for p in self.points: x = (p.x * self.width)/(2 * self.limit) + self.width/2 y = self.height/2 - (p.y * self.height)/(2 * self.limit) g.fillOval(x, y, 4, 4) if __name__ == '__main__': app = Main() 


Screenshot shows the results from running the PolarGraph.py module.

Screenshot Mouse events and a polar graph.

Java graphics 10fig05.gif


Images

Although there is little innovation in displaying images with Jython, it makes for yet another good comparison with Java. Listing 10.7 displays a simple image in an AWT frame. The implementation in Listing 10.7 is similar to how it would be done in Java, especially considering how the app's top frame is a class that subclasses the java.awt.Frame. This is done to make use of the component's paint method, which is difficult to use without creating a subclass because of the way update and repaint work. Aspects that differ from Java are again the bean properties, event properties, and keyword assignments that appear in the jyimage class constructor. Listing 10.7 displays an image designated by a name in the jyimage constructor. The image displayed in this example is the logo from Jython's website (http://www.jython.org). You can download this image to reproduce the same results as Listing 10.7. Listing 10.7 assumes that this image is within the current working directory so that it can just use the file name without qualifying the path. Interesting points in Listing 10.7 are the getDefaultToolkit() method and the MediaTracker object. It is common to assume that getDefaultToolkit() translates into a bean property accessible with awt.Toolkit.defaultToolkit; however, it doesn't qualify as a static class method, so you must use the full call awt.Toolkit.getDefaultToolkit(). Listing 10.7 uses the default toolkit to load the displayed image. To make sure that the entire image is loaded before painting it, Listing 10.7 uses the awt.MediaTracker. This makes it unnecessary to define an imageUpdate and other issues related to incremental loading of images. A Java app would require a try/catch statement surrounding the mt.waitForID() method, and while this is likely good practice in Jython, it is not required. It is potentially common knowledge in the Java community, but it's worth repeating the use of addNotify and insets. The (0,0) coordinate of a frame is under the title bar. This necessitates using the frames inset information to determine the total dimensions of the frame, and getInsets is only usable after creating a peer with the addNotify() method.

Listing 10.7 Displaying Images
# file jyimage.py from java.lang import System from java import awt class jyimage(awt.Frame): def __init__(self, im): self.im = awt.Toolkit.getDefaultToolkit().getImage(im) self.bounds=100,100,200,200 self.title="Jython Image Display" self.windowClosing=lambda e: System.exit(0) mt = awt.MediaTracker(self) mt.addImage(self.im, 0) mt.waitForID(0) self.addNotify() # add peer to get insets i = self.insets self.resize(self.im.width + i.left + i.right, self.im.height + i.top + i.bottom) def paint(self, g): g.drawImage(self.im, self.insets.left, self.insets.top, self) if __name__ == '__main__': f = jyimage("jython-new-small.gif") f.visible = 1 


Screenshot shows the results of executing the imagedisplay.py.

Screenshot Displaying an image with Jython.

Java graphics 10fig06.gif


Menus and Menu Events

Frames have a menu bar property that designates which instance of the java.awt.MenuBar class acts as its menu bar. The instance of java.awt.MenuBar contains instances of the java.awt.MenuItem class for each menu item you wish to implement. The steps to create a menu bar in Jython are fill a MenuBar instance with MenuItem instance(s), then assign the MenuBar to the frame's menuBar property. Implementing menu item actions requires only an assignment to each MenuItem 's actionPerformed property. Listing 10.8 shows the ease in which menus and their associated actions are added to a frame in Jython. The use of Jython's getattr function in this listing makes tying menu actions to methods a snap.

Listing 10.8 AWT Menus
# file: jythonmenus.py from java import awt from java.awt import event from java.lang import System menus = [
 ('File', ['New', 'Open', 'Save', 'Saveas', 'Close']), ('Edit', ['Copy', 'Cut', 'Paste']), ] class MenuTest(awt.Frame): def __init__(self): bar = awt.MenuBar() for menu, menuitems in menus: menu = awt.Menu(menu) for menuitem in menuitems: method = getattr(self, 'on%s' %% menuitem) item = awt.MenuItem(menuitem, actionPerformed=method) menu.add(item) bar.add(menu) self.menuBar = bar self.windowClosing = lambda e: System.exit(0) self.eventLabel = awt.Label("Event: ") self.bounds = 100, 100, 200, 100 self.add(self.eventLabel) def onNew(self, e): self.eventLabel.text = "Event: onNew" def onOpen(self, e): self.eventLabel.text = "Event: onOpen" def onSave(self, e): self.eventLabel.text = "Event: onSave" def onSaveas(self, e): self.eventLabel.text = "Event: onSaveas" def onClose(self, e): self.eventLabel.text = "Event: onClose" System.exit(0) def onCopy(self, e): self.eventLabel.text = "Event: onCopy" def onCut(self, e): self.eventLabel.text = "Event: onCut" def onPaste(self, e): self.eventLabel.text = "Event: onPaste" f = MenuTest() f.visible = 1 


Figures 10.7 and 10.8 show how the jythonmenus module from Listing 10.8 works. Screenshot shows the File menu pulled down, and Screenshot show the updated label as a result of the menu selection.

Screenshot AWT Menu and Events File menu.

Java graphics 10fig07.gif


Screenshot AWT Menu and Events After the label update.

Java graphics 10fig08.gif


Drag and Drop

Jython has no abbreviation mechanism for implementing drag and drop (DnD) functionality. Just as in Java, the implementation involves implementing the required DnD interfaces: DragGestureListener, DragSourceListener, and DropTargetListener. The class(es) that implement these interfaces then establish a drag source and a drop target. Establishing a drag source requires two steps: instantiating the java.awt.dnd.DragSource class, and using that class to create the drag source recognizer. The Jython code for these two steps is as follows:

from java.awt import dnd myDragSource = dnd.DragSource() myDragRecognizer = myDragSource.createDefaultDragGestureRecognizer(
 some_component, dnd.DnDConstants.ACTION_COPY_OR_MOVE, implementation_of_dnd.DragGestureListener) 


The three arguments to create the recognizer are the drag source component, the action and the drag listener. The drag source is any component that you wish to drag things from. The action is a value designating which actions are appropriate for the drag gestures. The action values are in the java.awt.dnd.DnDConstants class. The third argument is an instance of a class that implements the java.awt.dnd.DragGestureListener interface. The JythonDnD class in Listing 10.9 instantiates the gesture recognizer with a java.awt.List for the drag source, copy or move for the action, and itself (self) as the DragGestureListener. Once a drag source and drag gesture recognizer are established, the app needs a place to drop items. Listing 10.9 uses a class that extends java.awt.List appropriately called DropList. The drop portion of DnD requires a class that implements the java.awt.dnd.DropTargetListener, which the DropList class in Listing 10.9 does.

Listing 10.9 Drag and Drop Lists
# file: ListDnD.py from java import awt from java.awt import dnd from java.lang import System from java.awt import datatransfer class JythonDnD(awt.Frame, dnd.DragSourceListener, dnd.DragGestureListener): def __init__(self): self.title="Jython Drag -n- Drop Implementation" self.bounds = 100, 100, 300, 200 self.windowClosing = lambda e: System.exit(0) self.draglist = awt.List() map(self.draglist.add, ["1","2","3","4","5"]) self.droplist = droplist([]) self.dropTarget = dnd.DropTarget(self, dnd.DnDConstants.ACTION_COPY_OR_MOVE, self.droplist) self.layout = awt.GridLayout(1, 2, 2, 2) self.add(self.draglist) self.add(self.droplist) self.dragSource = dnd.DragSource() self.recognize = self.dragSource.createDefaultDragGestureRecognizer(
 self.draglist, dnd.DnDConstants.ACTION_COPY_OR_MOVE, self) def dragGestureRecognized(self, e): item = self.draglist.getSelectedItem() e.startDrag(self.dragSource.DefaultCopyDrop, datatransfer.StringSelection(item), self) def dragEnter(self, e): pass def dragOver(self, e): pass def dragExit(self, e): pass def dragDropEnd(self, e): pass def dropActionChanged(self, e): pass class droplist(awt.List, dnd.DropTargetListener): def __init__(self, datalist): map(self.add, datalist) self.dropTarget = dnd.DropTarget(self, 3, self) def drop(self, e): transfer = e.getTransferable() data = transfer.getTransferData(datatransfer.DataFlavor.stringFlavor) self.add(data) e.dropComplete(1) def dragEnter(self, e): e.acceptDrag(dnd.DnDConstants.ACTION_COPY_OR_MOVE) def dragExit(self, e): pass def dragOver(self, e): pass def dropActionChanged(self, e): pass win = JythonDnD() win.visible=1 


The product of Listing 10.9 is two lists. The leftmost list contains items that you can drag and drop into the empty, rightmost list. Screenshot shows the GUI produced by this DnD example after a few items have already been dropped on the rightmost list.

Screenshot Drag and drop lists.

Java graphics 10fig09.gif


Swing

Using the Swing classes are not very different from using the AWT. Swing is a substantial change to the internal implementation of GUIs and provides an overwhelming wealth of components to choose from, but it does not fundamentally change how graphical apps are built. This combined with the continued prevalence of beans makes Jython equally effective for developing Swing apps. Writing a Swing app in Jython still benefits from automatic bean properties and event properties. These combined with the keyword parameters drastically improve working with Swing components just as they do for AWT components. Listing 10.10 shows a simple tree display in a Swing app. The only subtle difference between the JFrame in Listing 10.10 and earlier AWT Frame examples is the use of the JFrame's contentPane. Besides this subtle difference, studying this example mostly reinforces the similarities between working with the AWT and the Swing. Jython's role is expediting component properties and events that a component fires with automatic bean properties and event properties.

Listing 10.10 A Swing Tree
# file: SwingTree.py from javax import swing from javax.swing import tree import sys top_frame = swing.JFrame("A Simple Tree in Jython and Swing", windowClosing=lambda e: sys.exit(0), background=(180,180,200), ) data = tree.DefaultMutableTreeNode("Root") data.add(tree.DefaultMutableTreeNode("a leaf")) childNode = tree.DefaultMutableTreeNode("a node") childNode.add(tree.DefaultMutableTreeNode("another leaf")) data.add(childNode) t = swing.JTree(data) t.putClientProperty("JTree.lineStyle", "Angled") top_frame.contentPane.add(t) top_frame.bounds = 100, 100, 200, 200 top_frame.visible = 1 


The graphics produced from Listing 10.10 appear in Screenshot.

Screenshot Jython tree display.

Java graphics 10fig10.gif


 

Screenshot CONTENTS Screenshot
Comments