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.
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());
See applet sources.
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:
smMenu
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); } }; }; };
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);
See applet sources: CrossCheckBoxApplet and CrossingInteraction.