Back to Homepage / Tutorial

Customizing Swing widgets


SwingStates allows to redefine interaction with traditional AWT/Swing widgets. It allows to use state machines and tags over a Java graphical container to easily program new interaction features.


Handling events among widgets using the JStateMachine class

In SwingStates, attaching a JStateMachine to a graphical container allows to design interaction techniques among the set of widgets it contains. When a JStateMachine is attached to a container, events among the widgets it contained can be handled using *OnComponent transitions. For example, the machine of the following applet colors the background of widgets in yellow when the mouse cursor is over them and prints the location of mouse event relative to main container or relative to the picked widget.

smWidgets = new JStateMachine() {
    
    Color initColor;
    
    public State out = new State() {
        Transition enter = new EnterOnComponent(">> in") {
            public void action() {
                initColor = getComponent().getBackground();
                getComponent().setBackground(Color.YELLOW);
            }
        };
    };
			
    public State in = new State() {
        Transition getClass = new ClickOnComponent(BUTTON1) {
            public void action() {
                label1.setText("This widget is a "+getComponent().getClass().getName());
                label2.setText("Coordinates in top level container: ("+getPoint().getX()+", "+getPoint().getY()+")");
                label3.setText("Coordinates in local component    : ("+getPointInComponent(getComponent()).getX()+", "+getPointInComponent(getComponent()).getY()+")");
            }
        };
        Transition enter = new LeaveOnComponent(">> out") {
            public void action() {
                getComponent().setBackground(initColor);
            }
        };
    };
};
...
smWidgets.attachTo(getContentPane());

Failed to load applet. No Java 2 SDK, Standard Edition v 1.5.0 support for applet.

smClasses.jpg

See applet sources.

JStateMachine and glasspane

Picking a component in a JStateMachine means retrieving the deepest visible component at a given location. This property allows to easily draw among sets of widgets using a SwingStates' canvas running in the glasspane (a feature of Swing, see here). For example, we use this mechanism to implement a pie menu that sends color events to the machine that handles widget picking. (See this applet on this page and sources in CVS).

smMenu is the machine that runs the pie menu and fires color events while smWidgets is the machine that manages widget picking and color events. To make smWidgets listen events fired by smMenu, we use addStateMachineListener method. (For further explanations on communication between state machines, see here).

CStateMachine smMenu;
JStateMachine smWidgets;

// Add a canvas to the swing glasspane to draw the pie menu.
setGlassPane(canvas);

// Attach pie menu machine to this canvas
smMenu.attachTo(canvas);
// Attach smWidgets to the whole applet window
smWidgets.attachTo(this);
// smMenu listens events fired by smMenu
smMenu.addStateMachineListener(smWidgets);

smMenu listens for mouse events occurring on the glasspane to implement item selection in a circular menu. The circular menu is composed of four pie shapes (CPolyLine) to represent the four menu items and one circular shape to represent the menu center. Each pie shape is tagged by an extensional tag of type ColorTag that has a color field.

smMenu displays the menu when the user presses the left mouse button. When the user releases the mouse button, it hides the menu and fires a color event corresponding to the color of the tag hold by the last visited item or a cancel event if the last visited shape is the menu center.

// Each menu item has a tag of class ColorTag
itemBackground.addTag(new ColorTag(color));
...
smMenu =  new CStateMachine() {
    
    public State start = new State("start") {
        Transition menu = new Press(BUTTON3, ">> menuOn") {
            public void action() {
                showMenu(getPoint());
            }
        };
    };
    
    public State menuOn = new State() {
    
        Color color = null;
        
        // Each menu item has a tag of class ColorTag
        Transition enterMenuItem = new EnterOnTag(ColorTag.class) {
            public void action() {
                color = ((ColorTag)getTag()).color;
            }
        };
        
        // In this particular case, the only shape that is not tagged in the canvas is the menu center. We use it to cancel a menu invocation: releasing the mouse button on the menu center just hides the menu without selecting any item.
        Transition test = new EnterOnShape() {
            public void action() {
                color = null;
            }
        };
        
        Transition select = new Release(BUTTON3, ">> start") {
            public void action() {
                // smMenu fires color and cancel events
                if(color != null) fireEvent(new ColorEvent(smMenu, color));
                else fireEvent(new VirtualEvent("cancel"));
            }
        };
        
        public void leave() {
            hideMenu();
        }
    };
			
};

And, finally, smWidgets machine (JStateMachine) listens for:

smWidgets = new JStateMachine() {

    Component selected = null;
    
    public State start = new State() {
        // Pick the component under mouse press
        Transition select = new PressOnComponent(BUTTON3, ">> componentSelected") {
            public void action() {
                selected = getComponent();
            }
        };
    };
    
    // listen for cancel and color events incoming from smMenu
    public State componentSelected = new State() {
    
        Transition select = new Event("cancel", ">> start") { };
        Transition color = new Event(ColorEvent.class, ">> start") {
            public void action() {
                selected.setBackground(((ColorEvent)getEvent()).color);
            }
        };
				
    };
};

Tags for Swing widgets: grouping and interaction

As graphical objects in a canvas (see here), tags can be attached to Java widgets. In the previous example, we make all widgets being colorable except background panel by using a tag. We attach a named tag, let's say "colorable", to all widgets except background and we use a PressOnTag transition instead of a PressOnComponent transitions for picking in smWidgets so this transition will be triggered only when a mouse press occurs on a "colorable" widget:

// Add a tag colorable to widgets except background
JNamedTag colorable = new JNamedTag("colorable");
colorable.addTo(button);
colorable.addTo(checkbox);
colorable.addTo(textfield);
...
smWidgets = new JStateMachine() {

    Component selected = null;
    
    public State start = new State() {
        // Here, we use a PressOnTag transition
        Transition select = new PressOnTag("colorable", BUTTON3, ">> componentSelected") {
            public void action() {
                selected = getComponent();
            }
        };
    };
    
   ...
};

By default, in a JStateMachine, any Java component has a default tag containing its class name: a JButton has a tag "javax.swing.JButton", a JCheckBox has a tag "javax.swing.JCheckBox", etc. In the following example, we use it to make every checkbox of a container being crossable. This means that, when the mouse button is pressed, users can select a checkbox by simply entering and leaving it (i.e. crossing it) instead of having to stop and click on it.

JStateMachine crossSM = new JStateMachine() {
    
    public State crossOff = new State() {
        Transition press = new Press(BUTTON1, ">> out") { };
    };
    
    public State out = new State() {
        Transition enter = new EnterOnTag("javax.swing.JCheckBox", ">> in") { };
        Transition release = new Release(BUTTON1, ">> crossOff") { };
    };
    
    public State in = new State() {
        Transition leave = new LeaveOnTag("javax.swing.JCheckBox", ">> out") {
            public void action() {
                invoke(getComponent());
            }
        };
        Transition release = new Release(BUTTON1, ">> crossOff") { };
    };

};
crossSM.attachTo(panel);

Failed to load applet. No Java 2 SDK, Standard Edition v 1.5.0 support for applet.

smClasses.jpg

See applet sources: CrossCheckBoxApplet and CrossingInteraction.