Sometimes it is required that a value for a cell be changed without knowing the actual cell- or graph model class, and without adding the change to the command history. Therefore, a new method has been added to the GraphModel interface which allows a client to change the value (aka. user object) of a cell without making any typecasts or adding the change to the command history.
Without this method a client that didn't want to affect the command history while changing the value of a cell was forced to either cast to the custom graph model or to the custom cell type in order to change the value.
The advantage of this change is that it is now possible to change the value of a cell (eg. prior to insertion) without knowing the custom model or custom types it contains. This is useful for functionalities that create new cells (such as group actions, mouse tools and import routines), because these are typically unaware of the concrete graph model or cell types, or there may be more than one model or cell type throughout the lifecycle of these objects.
In such a setup, the cell passed to the functionality is refered to as a "prototype" and is prepared (cloned and updated) for use in a new model with the following lines of code:
GraphModel model = graph.getModel(); Object newCell = DefaultGraphModel.cloneCell(model, prototype); model.valueForCellChanged(cell, "Hello, world!");
Note that in this example, the prototype is an Object and no typecast is required to change the cell value. The value does not need to be a String, it can be any object.
Developers of custom models and custom graph cells do only need to change the modifier of the existing valueForCellChanged method. This method has been there before, but was not part of the graph model interface. Instead it was used internally by the handleAttributes method to encapsulate the actual changing of the value, so that it could be overridden by subclassers to handle custom user objects. This is still the same, but with the new interface the method can be called by external parties as well, which means the visiblity must be changed from protected to public.
This method does a cast to EdgeRenderer on the value returned by getRenderer, and may therefore cause exceptions for subclassers that do not inherit from EdgeRenderer to implement their edge rendering. We have fixed this so that the invocations of this method are limited to the EdgeView class, so that a subclasser can safely assume that no other invocations to this method will be made. It is therefore required to override all methods which rely on this method within the EdgeView class, namely:
getShape
getLabelBounds
getExtraLabelBounds
intersects
getBounds
The reason for closely coupling the EdgeView and EdgeRenderer is due to caching and separation of concerns between the EdgeRenderer and the EdgeView. For example: The EdgeRenderer is in charge of finding the actual bounds of the edge, whereas the EdgeView is in charge of caching these bounds until they need to be updated.
When implementing a custom edge renderer that does not inherit from EdgeRenderer then you need to make sure that these collaborations are clearly defined and then override the above methods with your custom code.
Note that there was one additional call to this method from within the BasicGraphUI (which was used to position the in-place editor). This call was fixed to use getRenderer and perform a typecast, using the top-left corner of the edge's bounding box as a default. You may want to override that getEditorLocation method to change this for a custom edge renderer.
The labels for self-references with no additional control points have been made moveable. The underlying functionality interprets the label position as an absolute vector in pixel coordinates in this special case, because one cannot use the normalized vector between to the end points of such loops (as they are at the same location and thus the resulting vector is 0).
The advantage of this change is that it is now possible to move labels on loops that have no control points. On the downside the labels will "jump" (because of the special coordinate system) once the loop is changed to become a link between two different ports.