Visual Library 2.0 - Documentation

Index

Abstract

This is a documentation for the Visual Library 2.0 API. The library is a successor of the Graph Library 1.0. Its provides general-purpose (not graph-oriented only) visualization.

WWW: http://graph.netbeans.org
CVS: :pserver:anoncvs@cvs.netbeans.org:/cvs checkout graph
Issues: NetBeans Issuezilla - graph component

Installation

To install the library, just set a dependency on org.netbeans.api.visual module. The source code of Visual Library API is located at graph/lib directory in the NetBeans CVS repository.

Introduction

The programming style is similar to Swing. You are building and modifying a tree of visual elements that are called Widgets. The root of the tree is represented by a Scene class which holds all visual data of the scene. Since neither Widget nor Scene is an AWT/Swing component you have to call Scene.createView method for creating a Swing component which renders the scene. The created JComponent could be used anywhere.

Example: First Scene

Following code creates an empty scene, creates a JComponent view of the scene and embeds it into a dialog with scrollable view.

Scene scene = new Scene ();
JComponent sceneView = scene.createView ();
JScrollPane panel = new JScrollPane (sceneView);

JDialog dia = new JDialog (new JDialog (), true);
dia.add (panel, BorderLayout.CENTER);
dia.setSize (800, 600);
dia.setVisible (true);
dia.dispose ();

Widget

Widget is a primitive visual element - similar to what JComponent is for Swing. It holds information about its location, boundary, preferred location/boundary, preferred/minimal/maximal sizes, layout, border, foreground, background, font, cursor, tooltip , accessible context, ...

Widget placement is defined by:

Note that the boundary is specified as a rectangle (and not as Swing-like Dimension). This allows to have baseline ability by the definition.

A widget placement is resolved by a layout associated to its parent widget. The placement is usually based on a preferred location and boundary. The preferred boundary can be set or calculated from the widget needs. Then it can be processed by preferred/minimum/maximum size. See Layout section for details.

Widget Methods

MethodDescription
getScene Returns a scene where the widget belongs. The scene instance is assigned in the constructor and could not be changed anymore.
getParentWidget
getChildren
addChild
removeChild
removeFromParent
addChildren
removeChildren
bringToFront
bringToBack
Methods for manipulation with the tree hierarchy of widgets.
getChildConstraint
setChildConstraint
Controls constraints assigned to child widgets. Used by Layouts similarly to Swing. See Layout section.
isEnabled
setEnabled
Controls whether events are processed by actions assigned to the widget or any of its children. If the widget is disabled, then events are not processed by children widgets too.
getActions() Returns a default actions chain. See WidgetAction section.
getActions(String tool)
createActions(String tool)
Returns (and optionally creates) a actions chain for an appropriate tool. See Action Tools section.
getLookup Returns lookup. Not used right now - it will be used later for extending the Widget functionality when the API is stable and incompatible API changes are forbidden.
addDependency
removeDependency
getDependencies
Allows to assigned a Widget.Dependency listener to the widget for notifying about widget location or boundary changes. Widely used by Anchors. See: Widget Dependency section.
isVisible
setVisible
Controls visibility of the widget. If not visible, then the widget is not painted and do not process events. True by default.
isOpaque
setOpaque
Controls opacity of the widget. If opaque, then background is not painted. False by default.
getBackground
setBackground
getForeground
setForeground
Controls the background paint and foreground color. Solid white background and black foreground by default.
getFont
setFont
Controls the font. By default or if set to null, then getFont returns value of Scene.getDefaultFont method.
getBorder
setBorder
Controls the widget border. EmptyBorder by default. See Border section.
getCursor
setCursor
Controls the widget cursor. null by default.
getLayout
setLayout
Controls the widget layout. The layout defines placement of children. AbsoluteLayout by default. See Layout section.
getMinimumSize
setMinimumSize
getMaximumSize
setMaximumSize
getPreferredSize
setPreferredSize
Controls minimum, maximum and preferred size. Null (means not set) by default. See Layout section.
getPreferredLocation
setPreferredLocation
Controls preferred location. Widely used when parent widget has AbsoluteLayout, then the preferred location is used as the evaluated location. See Layout section. Null (means not set) by default.
getPreferredBounds
setPreferredBounds
isPreferredBoundsSet
Controls preferred boundaries. Not set by default not set. getPreferredBounds method always returns non-null boundaries - if the preferred bounds are not set, then it automatically calculate the boundaries from boundaries of its children, assigned layout and internal boundaries. The returned boundaries are checked for minimum, maximum and preferred sizes. See Layout section.
isCheckClipping
setCheckClipping
Defines whether the clipping should be used for widget painting. False by default.
getToolTipText
setToolTipText
Controls the tool tip text.
getState
setState
Defines the widget state. State.createNormal by default. See Object State section.
convertLocalToScene
convertSceneToLocal
Converts local coordination system to scene coordination system and vice versa. See Coordination System section.
getLocation Returns evaluated widget location specified relatively to the parent widget coordination system. [0,0] by default. See Layout and Coordination System section.
getBounds Returns evaluated widget boundaries specified relatively to the widget (local) coordination system. Null by default. See Layout and Coordination System section.
resolveBounds (Point location, Rectangle bounds) Sets evaluated widget location and boundary. If location is null, then [0,0] is automatically used instead. If bounds are null, then return from getPreferredBounds is automatically used instead. This method should be called from implementation of Layout.layout method only. See Layout and Coordination System section.
getClientArea Returns boundary from getBounds without (means: decreased by) insets of a border assigned to the widget.
isHitAt Returns whether the widget is "hit" at specified location in local coordination system. The "hit" usually means that there is a pixel at the location controlled/painted by the widget. Widely used for mouse event processing. E.g. A circle-shape widget has rectangular boundary but it is hit on at points that are located inside the circle area.
repaint Schedules the widget for painting. Usually called from setters method of the Widget. Usually the developer should call setter methods and the setter methods should call repaint or revalidate (depends whether they affect widget location/boundary). See Validation Process section.
isValidated Returns whether the widget is validated. See Validation Process section.
revalidate() Schedules the widget for validation. Usually called from setter methods of the Widget. Usually the developer should call setter methods and the setter methods should call repaint or revalidate (depends whether they affect widget location/boundary). See Validation Process section.
revalidate(boolean repaintOnly) Calls repaint or revalidate method based on repaintOnly parameter.
paint Paints the widget using the Graphics2D instance acquired by getGraphics method. Usually you should not call this method directly. It is called automatically by the library. For repainting the widget, use repaint method instead.
protected getGraphics Returns Graphics2D instance that could be used for painting and calculating internal boundary. It is non-null only when the scene has a JComponent view created and it is added into AWT components hierarchy (means: after JComponent.addNotify method call). See Custom Widget section.
protected calculateClientArea Returns calculated client area required by the widget itself without children. E.g. LabelWidget has the client area equal to the boundary of its label. Do not include children location and boundary - they are included automatically later. See Custom Widget section.
protected paintBackground Paints the widget background only. Do not invoke painting of children in this method. See Custom Widget section.
protected paintBorder Paints the widget border only. Do not invoke painting of children in this method. See Custom Widget section.
protected paintWidget Paints the widget only. Do not invoke painting of children in this method. See Custom Widget section.
protected paintChildren Paints the widget children. Default implementation of Widget.paintChildren method should to be called to for invoke painting of the children. You can also selectively call paint method on each child. Usually this is used for introducing painting effects on children. E.g. smooth alpha blending in LevelOfDetailsWidget or applying convolution in ConvolveWidget. See Custom Widget section.
protected notifyStateChanged Called by setState method when the state is changed. This method should control the widget appearance based on the widget-specific state. E.g. change the background color based on the result of ObjectState.isSelected method. See ObjectState and Custom Widget sections.
protected isRepaintRequiredForRevalidating Returns whether the repaint of the whole widget (specified by the widget boundaries) has to be repainted during validation. Used for speed optimalization. By default return true for everything except Scene and LayerWidget. See Custom Widget section.
protected notifyAdded
protected notifyRemoved
Called to notify that a widget is being show or hidden in the view. Within these methods, do not modify the tree hierarchy of scene.
protected getCursorAt(Point localLocation) Called to get a cursor of a specified local location of the widget.

LabelWidget

The LabelWidget represents a single line text. You can manipulate with the text using getLabel and setLabel methods or using a constructor parameter. The widget is not-opaque by default and uses foreground, background and font properties for rendering. The text could be aligned to the left (by default), right or center within the widget. The widget origin is a point on the left side of the baseline of the text.

Since version 2.1 the LabelWidget allows rendering vertical labels. It can be set by calling LabelWidget.setOrientation method. Default value is LabelWidget.Orientation.NORMAL which renders labels horizontally. The other value is LabelWidget.Orientation.ROTATE_90 which makes the label to render vertically.

Since version 2.7 the LabelWidget allows to use glyph-vector for rendering. This allows accurate text rendering independently on a scene zoom-factor (no more clipped text while zooming onto a scene).

ImageWidget

The ImageWidget represents an image. You can manipulate with the image using getImage and setImage methods or using a constructor parameter. The widget is not-opaque by default. The widget origin is top-left corner of the image.

The ImageWidget supports animated images. For proper work, do not load images using Utilities.loadImage because the method is producing static images. Instead use standard Toolkit.createImage method for loading.

Widget Dependency

Sometimes you want to listen on a widget move or resize changes. The listener is defined by Widget.Dependency interface. Listener could be (un)registered using Widget.addDependency and Widget.removeDependency methods.

Note 1: The Widget.Dependency.revalidateDependency method is not called directly from Widget.resolveBounds method. Instead it is called indirectly during widget or scene revalidation process (means from Widget.revalidate and Scene.validate method). See Validation Process and Layout sections for details.

Note 2: Widget.Dependency.revalidateDependency method is called to notify all dependent objects that the widget is scheduled for revalidation and therefore all dependent objects should schedule their revalidation using dependentWidget.revalidate method call too.
E.g.: An anchor depends on a widget where it is assigned. When Widget.Dependency.revalidateDependency is called, then the anchor clears its caches and invokes Widget.revalidate method on all ConnectionWidgets (resp. Anchor.Entry).
Therefore no bounds of any widget is valid at the time of the method call. The valid bounds can be obtained only in Layout interface:

If you want to listen on scene boundary changes, the easiest way is to add scene-listener using Scene.addSceneListener method and put your code into SceneListener.sceneValidated method implementation.

Example: Icon Node Widget

Following code creates the iconNode instance that has an image with a label.

// create a scene
Scene scene = new Scene ();
// create the icon node widget
Widget iconNode = new Widget (scene);
// use a vertical layout
iconNode.setLayout (LayoutFactory.createVerticalFlowLayout (LayoutFactory.SerialAlignment.CENTER, 4));
// add a image child widget
iconNode.addChild (new ImageWidget (scene, Utilities.loadImage ("path/to/my/image.png")));
// add a label child widget
iconNode.addChild (new LabelWidget (scene, "My Label"));
// add it to the scene
scene.addChild (iconNode);

Border

Each widget have a border. By default it has EmptyBorder. The border could be set using Widget.setBorder method. The border is always painted after the clearing the widget background and before the Widget.paintWidget and Widget.paintChildren methods. Therefore the border could be used for painting complex widget backgrounds. The border can be usually created using BorderFactory.create*Border methods. Single instance of border class could be shared and assigned to more widgets. There are following borders available:

Scene

This widget represents a root node of the tree hierarchy of widgets. It controls the whole scene, its views, repainting and tree validation. It is extended by ObjectScene, GraphScene, GraphPinScene class which adds additional functionally for object-oriented and graph-oriented modeling. Scene class is derived from Widget class and contains additional methods.

Scene Methods

MethodDescription
createView Creates a single JComponent-based view of the scene. Could be called once only. The JComponent could be used anywhere in AWT/Swing. Scene and widgets starts to be validated/painted only when the view is created and added to the AWT/Swing components hierarchy (JComponent.addNotify is called).
getView Returns the JComponent instance created by createView.
createSatelliteView Creates a satellite view of the scene. It is showing an overview of the scene. In case the JComponent view is placed in a JScrollPane and the scene does not fit into the panel, then the satellite view paints an viewport rectangle which representing visible area. It also allows user to move with the actual viewport of the scene.
createBirdView Creates a bird view of the scene. It is enough to create one bird view and reuse it all the time. The bird view is tracking mouse cursor and when the cursor is over visible area of the scene view, then an additional always-on-top window is shown and shows pointed part of the scene with specified zoom factor. The method returns BirdViewController which allows control over the bird view. When the bird view is enabled, then it consumes all events of the scene view.
getGraphics Returns an instance of Graphics2D used by whole scene. The instance is the one that was available during the last JComponent.addNotify or JComponent.paint method call on the JComponent view.
paint(Graphics2D) Paints the scene using Graphics2D instance. This method could be invoked in AWT-thread only.
isValidated Returns true if it is validated as using Widget.isValidated and there is no region or widget scheduled for repainting. See Layout section.
validate This method validates whole scene with all widgets. Usually it is called automatically e.g. at the end of event processing. See Validation Process section.
validate(Graphics2D) This method validates whole scene with all widgets using specified Graphics2D. This is used for off-screen rendering when you do not have a main scene view created yet. Be aware that the scene is going to remain validated with specified font metrics after the method call. This may break scene layout when your main scene view is finally created and shown. See Validation Process section.
getMaximumBounds
setMaximumBounds
Controls the maximum bounds of the scene. By default it is [-Integer.MAX_VALUE/2,-Integer.MAX_VALUE/2,Integer.MAX_VALUE,Integer.MAX_VALUE]. This is used to limit the scene bounds e.g. to positive values only.
getZoomFactor
setZoomFactor
Controls the zoom factor of the scene.
getSceneAnimator Returns a scene animator that controls animations on the scene. See Animator section.
getLookFeel
setLookFeel
Controls the scene look and feel. LookFeel.createDefaultLookFeel by default. See Look and Feel section.
getInputBindinds Returns input bindings used by the scene. See Look and Feel section.
getActiveTool
setActiveTool
Controls the active action tool of the scene. See Action Tools section.
getKeyEventProcessingType
setKeyEventProcessingType
Controls the processing type used for key events. See Event Processing Types section.
getPriorActions Returns a chain of prior actions. These actions are executed because any other action of the scene. See AWT Event Processing section.
getFocusedWidget
setFocusedWidget
Controls a focused widget of the scene. Only one widget could be focused at the same time. The focused widget could be used for key event processing. See Event Processing Types section.
addSceneListener
removeSceneListener
(Un)registers scene listener for notification about scene validation and repaint. See SceneListener section.
convertSceneToView
convertViewToScene
Converts location and boundary between scene coordination system and JComponent view coordination system. See Coordination System section.
createWidgetHoverAction Creates a widget-specific hover action. See HoverAction section.

SceneListener

This listener allows you to receive following events:

MethodDescription
sceneRepaint Called when scene should be repainted.
sceneValidating Called at the beginning of Scene.validate method when the scene is going to be validated. See Validation Process section.
sceneValidated Called at the end of Scene.validate method when the scene is validated. See Validation Process section.

This listener could be (un)registered using Scene.addSceneListener and Scene.removeSceneListener methods. Usually it is used by animators, scene layouts, Swing bindings and satellite views.

Coordination System

There are 3 coordination systems used in the library:

Converting from local to scene coordination system:

Point Widget.convertLocalToScene (Widget widget, Point localSystemPoint) {
    Point sceneSystemPoint = new Point (localSystemPoint);
    while (widget != null  &&  widget != widget.getScene ()) {
        sceneSystemPoint += widget.getLocation ();
        widget = widget.getParentWidget ();
    }
    return sceneSystemPoint;
}

Converting from scene to view coordination system:

Point Scene.convertSceneToView (Scene scene, Point sceneSystemPoint) {
    return new Point ((scene.getLocation () + sceneSystemPoint) * scene.getZoomFactor ());
}

Layout

Each widget has a layout assigned (AbsoluteLayout by default). Layout interface cares about placement of children widgets of the widget where the layout is assigned. For details how layout is involved in the validation process, see Validation Process section. Almost all layouts implementations can be reused by more widgets at the same time.

Widget also keeps constraints assigned to children widgets. Layout can obtain a constraint using Widget.getChildConstraint (childWidget) method call.

Widget could be invisible too. In that case, the layout assigned to its parent widget must act like the invisible widget is not there. It means it must resolve location and bounds to value which does not affect the parent widget boundary. If it is not possible, then the location and the boundary of invisible child must be resolved as [0,0] and [0,0,0,0].

AbsoluteLayout

Created by: LayoutFactory.createAbsoluteLayout

Placement algorithm:

FlowLayout

Created by: LayoutFactory.createVerticalFlowLayout, LayoutFactory.createHorizontalFlowLayout

This layout places children serially in vertical or horizontal line - one widget after another. It allows top-left, center, bottom-right and justify alignments and specifying gaps between children. It used childWidget.getPreferredBounds for resolving boundary. The layout will use the widest/highest boundaries for a cell dimension.

The layout allows to work with Number-class constraints. The Number constraint has to be a positive number assigned to a child widget. Then it represents a ratio in with the remaining space (height for VerticalFlowLayout and width for HorizontalFlowLayout) is split and assigned to a particular widget. See test.layout.WeightFlowLayoutTest for example.

CardLayout

Created by: LayoutFactory.createCardLayout

This layout has a single childWidget as active one. The active widget will be evaluated using activeChildWidget.getPreferredLocation (if null, then [0,0] point is used instead) and activeChildWidget.getPreferredBounds. The other children are evaluated with [0,0,0,0] rectangle as boundary and placed at the top-left corner of activeChildWidget.

For switching active child widget, use LayoutFactory.setActiveCard (widget, newActiveChildWidget) method.

OverlayLayout

Created by: LayoutFactory.createOverlayLayout

It finds the "minimal-encapsulation" (ME) boundary which contains all the children boundaries. Then it sets boundary of each child as the calculated ME one. The widget boundary is set to ME boundary too. During justification, the boundary of each child is sets to the boundary of the widget. Children are placed one over another - the last child is painted on top and receives events as the first child.

Validation Process

When any widget is added/removed in the tree hierarchy of a scene or a widget is changed the way that affects its placement or boundary, then the widget (and the scene too) has to be validated again.

The process is done in following steps:

  1. Developer modifies a widget by directly (or indirectly) calling Widget.revalidate method. The method automatically schedules the widget for revalidation.
  2. After all modifications, scene validation is invoked by calling Scene.validate method. Usually it is invoked automatically at the end of Swing event processing.
  3. Validation process searches for invalid widgets.
  4. All widget dependencies are notified about revalidation using Widget.Dependency interface.
  5. Each modified widget evaluates its location and boundary using its layout.
  6. Justification is invoked on each widget that need to justify its boundary using its layout.
  7. If any new modification were introduced during the validation process, then the validation process is repeated again.

The process is described in details in next sub-sections.

Revalidation

Each Widget has two flags: requiresFullValidation (marks modified widget) and requiresPartValidation (used for back-tracing the tree from the root/scene widget down to all modified widgets). When a widget is asked to be scheduled for revalidation using Widget.revalidate method, then the widget is marked with requiresFullValidation and requiresPartValidation. All widgets upto the scene are marked as a requiresPartValidation. Now the scene can trace-down all modified widgets without storing their references. Each widget with requiresPartValidation flag also fires an event on its Widget.Dependency listeners because they could be affected by a change of some child widget.

Scene Validation

When Scene.validate method is invoked, then it repeatedly validates scene until there is no modification - means Scene.isValidated returns true.

The validation process deep dives into the tree hierarchy and evaluates location and boundary using assigned layout of widgets that have requiresPartValidation set to true. Then it fires an event on all Widget.Dependency listeners for all children (recursively) of the widget with requiredFullValidation set to true.

Now all widgets are evaluated for new data. Because of used layout algorithms, it resolves boundary as the minimal "packed" boundary of the widget and its children - because each widget calculates its boundaries from its children boundary too.

In some cases you would like to extend boundary of some widgets e.g. when a widget with FlowLayout (with "justify" alignment) is a child of another widget with FlowLayout (with "justify" alignment). This can be done in this moment since all widgets are evaluated for minimal boundary and they can just expand. Therefore the validation process is deep-diving into the tree hierarchy (the depth is controlled by result of Layout.requiresJustification method of each widget. For each widget (from the root to the bottom) it reevaluates the boundary again and calculates children boundaries based on the widget boundary.

This 2-pass validation process allows to have similar layout support as Swing has.

Repainting

When a widget is scheduled for repaint, then its current (before reevaluation) boundary in the view coordination system is scheduled for repaint and the widget itself is added into the list of widgets scheduled for repaint.

After the scene is validated completely, then all widgets in repaint-list are scheduled for repaint too. It takes their current (newly evaluated) view coordination system boundaries.

SceneLayout

Layout (assigned to a widget) defines placement of its children widgets. But in some cases you would like to do a single-time layout based on different layout algorithm (it could be even high-level layout of graph-oriented data).

The possible solution could be to temporarily assign the desired layout to the widget and to start revalidation. The problem is that you cannot temporarily assign widget because you cannot assure when the layout and scene-validation will be invoked. Also you would not be able to invoke "initial" layout of a scene when the Scene is going to appear for the first time on the screen. It is because the JComponent is not initialized yet and therefore the scene is not validated yet and therefore widget boundaries (which are usually required by the layout algorithms) are not resolved yet too.

Therefore SceneLayout class has been introduced. When the SceneLayout is invoked by the developer, it starts listening on the scene to be validated (using SceneListener). When the SceneListener.sceneValidated method is called then the listener is removed and SceneLayout.performLayout method is invoked.

There is a LayoutFactory.createDevolveWidgetLayout method that creates a SceneLayout with performLayout method implementation which temporarily sets the desired layout and invokes the specified layout at the end of scene validation process. This allows you use a LayerWidget with default AbsoluteLayout, so the widgets inside will be able to freely move using MoveAction and from time to time (e.g. started by user action) the layer widget will be layout using the specified temporary layout to organize the layer.

WidgetAction

The widgets are visual elements that just know where they are on the scene and how to draw themself. By default they do not know how to behave. For defining the behavior there are WidgetAction.

You can create various actions usually using ActionFactory.create*Action methods. For example: ActionFactory.createMoveAction method creates an action which allows user to move a widget (where the action is assigned to).

Factory methods usually requires some parameters. There are two kinds of parameters: Decorators and Providers. Decorator is used for specifying the visual appearance of objects temporarily created by an action. E.g. RectangularSelectAction has RectangularSelectDecorator for acquiring the widget that will represent actual selection rectangle. Provider is used for specifying the custom behavior of an action. E.g. EditAction has EditProvider with edit method which is called when an user double-clicks on a widget where the action assigned to. It is upto the EditProvider implementation whether it invokes an editor or run an application or does something totally different. Usually there are default implementations of the decorators and providers available in ActionFactory class.

Usually single instance of a particular action could be reused and assigned to more Widgets at the same time.

Assignment

Actions could be grouped into chains. The chain is an action that distributes the user-events to appropriate actions that are stored inside. Each Widget has its own chain that can be acquired by calling Widget.getActions method. Chain allows to manage and query the list of its actions using addAction, removeAction, getActions methods.

Action Tools

Sometimes you would like to have a scene which will behave differently based on a scene state. E.g. you have a toolbar with modes: "Select" (when active then user can only select object), "Connect" (when active then user can only create connection between objects), "Move" (when active then user can only move objects in a scene), ...

For this purpose there are action tools. A scene has a single active action tool specified. It is null by default and could be changed by Scene.setActiveTool method. The active tool specified as a String used for the tool identification. Each widget has Widget.createActions (String tool) method for creating an action chain for the specified tool. When a chain is created for particular tool, then you can use Widget.getActions (String tool) for faster access to the chain.

AWT Event Processing

Each mouse/keyboard/drag'n'drop/focus event that comes from the AWT has to be processed. The processing it done by walking through the tree hierarchy of widgets. For mouse events, the widget boundaries and Widget.isHitAt methods are used for checking before dispatching the event. The AWT-event is converted into the library-specific event (which is very similar to AWT event. The library-specific event has event.getPoint method which location in local coordination system of the widget where an appropriate action is attached to. For each valid widget, all default actions (from Widget.getActions ()) are processed. The processing continues until an event is consumed by an action.

The walk through process for a widget is done by:

  1. Take the widget (start it with Scene first) and check its boundary.
  2. If the event is a mouse-event and mouse-cursor is NOT inside boundary, then return back.
  3. Process the event for each its child widget (start from the last child to the first child) for the widget.
  4. If the event is consumed by any of the children then stop processing the event and return back.
  5. Check if Widget.isHitAt (mouseCursor) method returns true. If so, then process the event with default action chain of the widget and then optionally with an action chain appropriate for the tool returned by Scene.getActiveTool method.
  6. If event is consumed then stop processing the event.
  7. Return back.

There is the Scene.getPriorActions code which returns an actions chain. All actions in this prior chain are processing an AWT event before any other actions used in the scene. If any of these prior actions consumes the event, the event processing is stopped. A prior action cannot lock even processing for itself. For example: see test.tool.CtrlKeySwitchToolTest example which is switching active tool of a scene based on a state of Ctrl key.

The Widget.isEnabled controls whether events can be processed by actions assigned to the widget or its children. The same behavior implemented for all types of events.

It is important to have correct order of actions assigned to a widget because some actions requires locking. Usually the "locking" actions are the one that requires mouse-dragging e.g. MoveAction, ResizeAction, ConnectAction, ... Usually it is enough to put HoverAction and SelectAction as the first two actions and others.

Actions usually require various parameters including for their creation e.g. LayerWidget. See LayerWidget section for details.

Action State

The action is processing library-specific events and returns the event status at the end. Based on the status the library decides whether the event is consumed, locked, ... There are following statuses:

Event Processing Types

All Swing events are processed by all widgets by default. Key-events has to processed differently because they do not contain a "location" information. Therefore all widgets would have to process those events.

Key events are usually not desired to be processed by all widgets. Therefore you can specify a type of event processing using Scene.setKeyEventProcessingType method. There are following types specified by EventProcessingType enum:

The focused widget is set by Scene.setFocusedWidget method. This method is not called automatically and it has to be called by a developer. Anyway there are a few places which are calling it automatically when:

ActionMapAction

Created by: ActionFactory.createActionMapAction (), ActionFactory.createActionMapAction (InputMap, ActionMap)

The action handles key events and popup menu creation. The keys and actions are obtained from specified InputMap and ActionMap parameters. The method without parameters uses the InputMap and ActionMap assigned to a JComponent view of a scene.

AcceptAction

Created by: ActionFactory.createAcceptAction (AcceptProvider)

The action allows to handle D'n'D drop operation. AcceptProvider.isAcceptable method is called for resolving whether the drop can happen and AcceptProvider.accept is called for handling the drop operation.

AddRemoveControlPointAction

Created by: ActionFactory.createAddRemoveControlPointAction

The action allows user to add a control point by double-clicking on path or remove control point by double-clicking on it. The action has routingPolicy which is automatically set to ConnectionWidget to prevent discarding of user changes by router assigned to the connection widget. For fixing moving-anchor problem use FreeRectangularAnchor.

AlignWithMoveAction

Created by: ActionFactory.createAlignWithMoveAction (AlignWithMoveDecorator, AlignWithWidgetCollector)

The action is similar to MoveAction but it allows snapping feature similar to Form Editor module. The AlignWithMoveDecorator supplies a graphic for snapping lines and the AlignWithWidgetCollector gathers all widgets that the snapping has to be checked against.

The outerBounds boolean parameter allows to specify whether client area or bounds of widgets is going to be checked.

AlignWithResizeAction

Created by: ActionFactory.createAlignWithResizeAction (AlignWithMoveDecorator, AlignWithWidgetCollector)

The action is similar to ResizeAction but it allows snapping feature similar to Form Editor module. The AlignWithMoveDecorator supplies a graphic for snapping lines and the AlignWithWidgetCollector gathers all widgets that the snapping has to be checked against.

The outerBounds boolean parameter allows to specify whether client area or bounds of widgets is going to be checked.

CycleFocusAction

Created by: ActionFactory.createCycleFocusAction(CycleFocusProvider)

The action is usually assigned to the scene widget and allows cycling focus using "Tab" key. The behavior of the cycling is defined by CycleFocusProvider interface which has two methods for cycling forward and backward.

If the action is created using ActionFactory.createCycleObjectSceneFocusAction method then using "Tab" key a focused object is cycling through all objects on an object scene. It is using the ObjectScene.getIdentityCode method for resolving the order of objects on a scene. For details, see Object Identity Code section.

ConnectAction

Created by: ActionFactory.createConnectAction (ConnectDecorator, ConnectProvider)

The action should be assigned to the widget from which a connection should be created. The ConnectDecorator supplies the graphics for the guiding connection line. The ConnectProvider handles a connection creation and the connection source and target checking.

EditAction

Created by: ActionFactory.createEditAction (EditProvider)

The action allows to handle double-clicking on assigned widget. EditProvider handles the edit.

HoverAction

Created by: ActionFactory.createHoverAction (HoverProvider) or ActionFactory.createHoverAction (TwoStatedHoverProvider)

The action allows to handle "hovering" effect when a mouse is hovering a widget where the action is assigned to. The first method invokes HoverProvider always when a mouse-cursor moves over assigned widget. The TwoStatedHoverProvider allows handling of an old and a new hovered-widget and provides unsetHovering (Widget) and setHovering (Widget) methods.

Because there is no Widget-related focus event generated by the library, therefore the hover action is not unsetting the hover after the mouse cursor leaves the related widget space. For fixing this, the same instance of the hover action has to be assigned to the Scene instance too. This will tell the library that when you are over a widget with hover actions assigned, then it is set into hovered state. When there is no widget (with hover actions assigned) under mouse cursor, then the scene itself is in hovered state and therefore the unsetHovering method is called in the previous widget.

There is a Scene.createWidgetHoverAction method which creates a widget-specific hover action. The actual hovered widget is held by the Scene instance and it only calls Widget.setState method with an appropriate parameter.

There is also ObjectScene.createObjectHoverAction method which creates an object-specific hover action. The hovering is based actual hovered object (not a hovered widget) in the ObjectScene instance. It means the mouse cursor is changed against widget that are representing objects in ObjectScene. The actual hovered object is held by ObjectScene instance. See ObjectScene section.

HoverAction vs. ObjectSceneHoverAction

A regular HoverAction (with your own implementation of HoverProvider or TwoStatedHoverProvider interfaces) and ObjectHoverAction can be used exclusively only.

How to use HoverAction:

How to use ObjectSceneHoverAction:

InplaceEditorAction

Created by: ActionFactory.createInplaceEditorAction (InplaceEditorProvider)

The action is a specialized case of EditAction. InplaceEditorProvider is called to supply the in-place editor represented as a Swing JComponent. The editor can be controlled using InplaceEditorProvider.EditorController interface. There is also ActionFactory.createTextFieldInplaceEditorAction (TextFieldInplaceEditor) method that allows text in-place editor using JTextField.

Using ActionFactory.getInplaceEditorController method you can obtain an EditorController for a specific action. The EditorController allows e.g. to programatically open in-place editor using EditorController.openEditor method.

The action also allows to specify an expansion directions using InplaceEditorProvider.getExpansionDirections method. Everytime an editor component is changed, it should call InplaceEditorProvider.EditorController.notifyEditorComponentBoundsChanged method to invoke recalculation of editor placement based on the initial bounds and expansion directions. Expansion direction of for built-in text-field based in-place editor there is an additional ActionFactory.createTextFieldInplaceEditorAction (TextFieldInplaceEditor,EnumSet<InplaceEditorProvider.ExpansionDirection>) factory method created. In this case the initial bounds of the editor component are resolved as the bounds of the related widget.

MoveAction

Created by: ActionFactory.createMoveAction (MoveStrategy, MoveProvider)

When the action is assigned to a widget, it allows to drag/move the widget. The movement is done by calling Widget.setPreferredLocation. It usually requires to have AbsoluteLayout to be set on the parent widget. MoveStrategy allows processing of the new suggested location and therefore supplying snap-to-grid or lock axis movement strategy. The MoveProvider interface allows to define behavior of movement e.g. you can move another widget than the widget that has the MoveAction assigned. There are built-in strategies and providers in ActionFactory too. For more details, see Strategies section.

MoveControlPointAction

Created by: ActionFactory.createMoveControlPointAction (MoveControlPointProvider)

The action allows to move control points of a ConnectionWidget. There are built-in providers in ActionFactory too.

PanAction

Created by: ActionFactory.createPanAction (), ActionFactory.createWheelPanAction ()

The action allows to scroll the view using mouse movement while mouse-middle-button is pressed. It requires to have JComponent view placed into a JScrollPane. This action is usually assigned to a Scene only.

The WheelPanAction allows to scroll ascene view using mouse-wheel. Then no key modifier is pressed, then the view is scrolled vertically. If shift key is held, then the view is scrolled horizontally.

PopupMenuAction

Created by: ActionFactory.createPopupMenuAction (PopupMenuProvider)

The action allows to invoke popup-menu that is supplied by the provider.

RectangularSelectAction

Created by: ActionFactory.createRectangularSelectAction (ObjectScene, LayerWidget)

The action allows to handle object-selection using a rectangle that is created by dragging the widget where the action is supplied. This action is usually assigned to an ObjectScene or LayerWidget. See ObjectScene section.

ReconnectAction

Created by: ActionFactory.createReconnectAction (ReconnectDecorator, ReconnectProvider)

The action should be assigned to the ConnectionWidget and allows reconnecting its source and target. ReconnectDecorator supplies the graphics of temporary reconnecting line. ReconnectProvider handles the reconnection and the connection source and target checking.

ResizeAction

Created by: ActionFactory.createResizeAction (ResizeStrategy, ResizeControlPointResolver, ResizeProvider)

It is similar to MoveAction but used Widget.setPreferredBounds. Also there are ResizeStrategy and ResizeProvider interfaces. There are built-in providers in ActionFactory class too. For more details, see Strategies section.

ResizeControlPointResolver allows customization of control points for resize action - resolves what kind of resizing is available at particular location of related widget.

SelectAction

Created by: ActionFactory.createSelectAction (SelectProvider)

The action allows to handle the single-click. It is similar to EditAction. It also allowed to condition the selection and allows using "aimed" object state.

ZoomAction

Created by: ActionFactory.createZoomAction (zoomFactorMultiplier, animated), ActionFactory.createCenteredZoomAction (zoomFactorMultiplier), ActionFactory.createMouseCenteredZoomAction (zoomFactorMultiplier)

The action allows to zoom the view using scrolling mouse-middle-button. The action is usually assigned to a Scene only. For correct functionality of the action, the JComponent has to be put into scrollable panel like JScrollPane.

While the CenteredZoomAction is zooming, a view is still centered to the center of the view. While the MouseCenteredZoomAction is zooming, a view is still centered to mouse cursor.

Example: Using Actions

Following example allows zooming and panning and allows to have a IconNodeWidget moving and widget-specific hovering.

Scene scene = new Scene (); // has AbsoluteLayout by default
IconNodeWidget iconNode = new IconNodeWidget (scene);
iconNode.setImage (Utilities.loadImage ("path/to/my/image.png"));
iconNode.setLabel ("MyIconNode");
scene.addChild (iconNode);

scene.getActions ().addAction (ActionFactory.createCenteredZoomAction (1.1));
scene.getActions ().addAction (ActionFactory.createPanAction ());

// assign HoverAction - the actual hovered widget is held by the scene
iconNode.getActions ().addAction (scene.createWidgetHoverAction ());

// MoveAction has to be after the hover action
// otherwise MoveAction will always consume the event and HoverAction will never be invoked
iconNode.getActions ().addAction (ActionFactory.createMoveAction ()); 

Order of Actions

The actions are created with a specific invocation for users. For example: The MoveAction is invoked by pressing mouse button on a widget. Then the action consumes all the events until the mouse button is released. Therefore you have to be aware of the order of the actions that you are assigning to the widget.

Here is usually order when a select, in-place editor, hover, move actions.

WidgetAction.Chain actions = nodeWidget.getActions ();

// creates an in-place editor action
// opens the in-place editor when user double-clicks on the node widget
// this action consumes only double-click events
actions.addAction (ActionFactory.createTextFieldInplaceEditor (...));

// creates a object-based select action
// just selects the node when mouse-button is pressed on the node widget
// this action does not consume any event
actions.addAction (myObjectScene.createSelectAction ());

// creates a move action
// this action moves the node widget
// this action consumes all events when the mouse is dragged
// (means: while the mouse button is pressed on the node widget)
actions.addAction (ActionFactory.createMoveAction ());

// creates a object-based hover action
// this action controls the hover state of the node (and node widget)
// this action consumes all events while the mouse is over the node widget
// (means: almost everytime) - therefore use it as the last action.
actions.addAction (myObjectScene.createObjectHoverAction ());

If you would like to use the connect action together with move action, you have to use the extended connect action. It is because the connect action is invoked exactly the same way as the move action and therefore the invocation is clashing. This means that the action, which is added first, locks the event processing to itself and the second action will not be invoked at any time. This could be prevented by making two different invocation. This is defined by the extended connect action. The extended connect action is invoked only if the Ctrl key is pressed while you are pressing the mouse button and then dragging the mouse. Now these two action invocations are clashing and you can use them both. Also in this case the order of the actions does not matter.

Action Creation

There are various methods where instances of actions can be created. Usually it done by a method in the ActionFactory class. These actions are generic.

The Scene class has a createWidgetHoverAction method for creating a widget specific hover action. Call this method after you have assigned all actions to the scene (since this action is internally added to the actions chain of the scene and the nature of the action is that it consumes all events while a mouse cursor is over the scene which usually happens all the time).

The ObjectScene class has a few methods for creating object-specific actions that controls selection, hovering, ... They are defined on the object scene since they are tightly coupled with the object scene. The select action created by object scene also sets the focused object.

In the future, methods that are defined on a scene and object scene could be moved to the ActionFactory class too.

Strategies

Sometimes you want to restrict the widget movement or resizing. For that purpose there is a MoveStrategy or ResizeStrategy interfaces which implementations could be passed as an argument to ActionFactory.createMoveAction and ActionFactory.createResizeAction methods.

The interfaces contains a single method which is called when a new location or boundary is going to be set. The suggested value is passed to the method as argument and developer has to implement the logic and return the location or boundary which is going to be set.

Following code shows a few examples for strategies:

public final class SnapTo16x16GridMoveStrategy implements MoveStrategy {
    public Point locationSuggested (Widget widget, Point originalLocation, Point suggestedLocation) {
        return new Point (suggestedLocation.x - suggestedLocation.x % 16, suggestedLocation.y - suggestedLocation.y % 16);
    }
}
public final class PositiveCoordinatesOnlyMoveStrategy implements MoveStrategy {
    public Point locationSuggested (Widget widget, Point originalLocation, Point suggestedLocation) {
        return new Point (Math.max (suggestedLocation.x, 0), Math.max (suggestedLocation.y, 0));
    }
}

ConnectionWidget

This widget represents a connection/path between source and target point. The source and target point is defined by Anchor. The path is defined by control points which are resolved by assigned Router. By default it is DirectRouter which routes the path as a straight line between the source and target point. The source and target points are not specified directly but by using anchors.

The ConnectionWidget has properties for setting source and target point shapes, control points shape, ability to calculate which segment of path or control point is hit by a specific point, ...

Line color is defined by the foregroundColor property.
Note: If you are changing a state of a ConnectionWidget (e.g. using it as a representation of an object in ObjectScene, GraphScene or GraphPinScene classes, then the ConnectionWidget.notifyStateChanged method is automatically called. See ObjectScene section for details. The built-in implementation of this method overrides value of foregroundColor property based on a new state of the widget (the particular color is resolved by the LookFeel of the scene).

Also there is the lineColor property. By default it is null. If it is non-null value, then it overrides the object-state-based behavior of the ConnectionWidget. Therefore the lineColor value to be written foregroundColor property and uses as strictly defined color of the line.

A connection can have assigned a source and a target AnchorShape and a end-point and control-point PointShape. Source AnchorShape is used for defining a shape of a source point. Target anchor shape is used for defining a shape of a target point. An end point shape is used defining a shape of first or last control point. A control point shape is used for defining a shape of other control points except first and last. There are 4 predefined anchor shapes: AnchorShape.NONE, AnchorShape.TRIANGLE_HOLLOW, AnchorShape.TRIANGLE_FILLED, AnchorShape.TRIANGLE_OUT. Other built-in anchor shapes can be created by AnchorShapeFactory class. There are 2 predefined point shapes: PointShape.SQUARE_FILLED_BIG, PointShape.SQUARE_FILLED_SMALL. Other built-in point shapes can be created using PointShapeFactory class.

There is a controlPointsCursor property which defines a mouse cursor when the mouse is over a visible control point.

There is a controlPointCutDistance property. If set to positive number (by default 0), then the connection path is cut at each control point to make smoother corners. The smooth corners are not used during calculation whether a mouse-cursor hits the path.

Router

Router cares about creating paths. There are following built-in implementations:

ConnectionWidgetLayout

ConnectionWidget allows developer to attach children widgets too. Those children could be attached relatively to the path using constraints. The constraint could be set using ConnectionWidget.setConstraint method for each child widget. If a child widget has non-null preferred location then this preferred location will be added to the relative location.

See test.connectionlabels.ConnectionLabelsTest for details.

Routing Policy

Since version 2.9, the ConnectionWidget class has additional routingPolicy property. It ease management of control points changed by users. There are 4 values:
  1. ALWAYS_ROUTE - This is default value. The router is always invoked when a ConnectionWidget is changed and needs to be re-routed.
  2. UPDATE_END_POINTS_ONLY - The router is not invoked. Instead location of the first and the last control points are changed/updated to location computed by source and target anchors.
  3. DISABLE_ROUTING_UNTIL_END_POINT_IS_MOVED - The router is not invoked until the first or the last control points is moved (means it has different location from one computed by source or target anchor).
  4. DISABLE_ROUTING - The router is not invoked. The control points are freezed at the same locations.
These routing policy are very useful when you are using modifying control points: