Description of the old implementation of the Property Panels
The PropPanels for the diagrams are in org.argouml.uml.diagram.ui and the property panels for UML objects are in org.argouml.uml.ui.UML path.
Receiving target change and modification events
Proppanels are created when their UML object becomes the target for the first time.
Whenever the target changes to another type of UML object, another proppanel is activated, which redraws itself according the target modelelement.
Whenever the target changes to another instance of the same kind of modelelement, the (current) proppanel updates its rendering based on this new target.
Whenever the model changes for the current target, without this invoking a target change, the proppanel updates.
Whenever the model changes for another modelelement than the current target, usually nothing should happen - especially not for proppanels that are not the current one - since they do not know which will be their next target anyhow. Unless e.g. the name of the namespace of the current class changes, since this namespace is shown on the current proppanel. In such case, the proppanel of a class is listening to modelchange events from more than only the class itself.
Adding the property panel
Property Panels for UML model elements are found as class ?PropPanelXXX.java, where XXX is the UML meta-class. They are in sub-packages of org.argouml.uml.ui corresponding to the UML package which contains the ''XXX'' metaclass in the UML specification.
So for our example we create a new class PropPanelExtend in package org.argouml.uml.ui.behavior.use_cases.
Any associated classes that do not fall into the UML classification are provided in org.argouml.uml.ui.
Typically the constructor for the new proppanel class invokes the parent constructor, and then builds the fields required on the property tab. The parent constructor may need an icon. If you need a new icon, a call to lookupIcon() should be made (note that this is a utility method of the parent PropPanel class). For our example we had to add Extend.gif.
You will need to make an icon, in .gif format, 16 X 16 pixels, with the transparent background color set to white. Place this file in the org.argouml.Images directory (it must be named like Name.gif). This icon will automatically be used in the toolbar and in the Navigation pane.
Finally the property panel must be added to the list of property panels in the run() method of the ?TabProps class, with a new call of panels.put(). If you don't do this, navigation listeners won't know about it!
The content of the property panel is created as a grid with columns (1 column if there are only a few fields, 2 or 3 if there are more). Each row of each column contains a caption (i.e. label) and its corresponding field.
A caption and its field may be added with one of a small number of utility methods which shield you from the layout stuff: addField() and addSeperator().
A button may be added to the toolbar with the utility method addButton().
Every field is built from Java Swing components. However these are extended by ArgoUML to help in the provision of action methods for fields in the property tab. Several fields involve lists, and these require in addition list models to compute the members of the list.
The fields that you might add to a property panel include:
Simple editable text. For example the Name field. Supported through the UMLTextField2 class.
A drop down box (aka combobox) of options that can be selected. Supported by the UMLComboBox2 class. Used e.g. for the type of a parameter.
A check box. This one does not use a seperate model class, thanks to the simplicity of the represented boolean value. Supported by the UMLCheckBox2 class. Used e.g. for the concurrency checkbox on a composite state.
A radio button. These always come in a group. Supported by the UMLRadioButtonPanel class. Used e.g. for selecting the visibility on the properties panel of a class.
A list. Used e.g. for the Generalizations field on the proppanel of a class. The non-editable list is supported by the UMLList2 class and its child UMLLinkedList. The latter also exists in the form of UMLMutableLinkedList, which allows adding, creation and deleting elements by popup menu. Used e.g. for the subvertex list for a composite state.
The list model is usually provided by a sub-class of UMLModelElementListModel2. There is a variant UMLModelElementOrderedListModel2 intended for ordered links, which adds a few items to the pop-up menu, allowing sorting. This latter model is used e.g. for attributes of a class.
- A drop down box of options that can be selected. This one exists in several versions, each having different possibilities. The most simple version is the UMLComboBox2.
The UMLEditableComboBox allows editing the selected item.
The UMLSearchableComboBox allows editing the selected item. See e.g. the Operation combobox on the callevent properties panel.
Then there is a variant with a seperate button for navigation to the property panel for the currently selected item. This is supported by the UMLComboBoxNavigator class. Used e.g. for the stereotype field.
An editable multiline text area. Supported by the UMLTextArea2 class. Used e.g. for the text field of a UML Comment.
Examples of these fields in more detail follow below.
Adding a simple list field
For example we need to add a field to the use case property panel for the extends relationships that derive from this use case.
This field consists of a label and a scrollable pane (JScrollPane) containing the list (JList), which may be empty, or contain extend relationships from this use case.
Rather than a straight JList, we use its child, UMLLinkedList, which adds several features to the standard JList specifically for ArgoUML's properties panels.
The constructor for UMLLinkedList requires two arguments, a list model and a flag to indicate whether to show an icon.
The list model should be a subclass of UMLModelElementListModel2, a subclass of the Swing DefaultListModel which implements AbstractListModel. The UMLModelElementListModel2 implements two interfaces: one that listens to target changes, and one that listens to UML model changes.
The list model
In our example we create UMLUseCaseExtendListModel. Its constructor takes no arguments. However, we need to provide the parent class with a Model subsystem event name by invoking the constructor of the parent class, with the event name as parameter.
A string naming a <<Subsystem>> Model subsystem event that should force a refresh of the list model. A null value will cause all events to trigger a refresh. The name of the event is the same as the name of the associated attribute or association end from the UML 1.4 metamodel.
This list model should then be provided with a number of methods. The following are mandatory, since they are declared abstract in the parent.
protected void buildModelList()
(Re)Builds the list of elements. Called from targetChanged every time the target of the proppanel is changed.
protected boolean isValidElement(Object/*MBase*/ o) Returns true if the given element is valid, i.e. it may be added to the list of elements. This function is called for many UML elements, to determine if it fits in the list. Remark: The indication /*MBase*/ is a remainder from the time that ArgoUML included direct references to the NSUML model all over the code. Now it is a practical reminder of what we are dealing with.
CategoryFix: The following description is old and the property panels have undergone some fundamental changes since it was written. It would be good if someone that knows how it works now could write a description on how it works now.
The following are sometimes provided as an override of the parent, although for many uses the default is fine.
public void open(int index)
- Perform the action associated with the “open” pop-up menu on the element at the given index. The default provided in the parent just navigates to that element.
public boolean buildPopup(JPopupMenu popup, int index)
- Build a pop-up menu for the list and return whether it should be displayed. Any actions will be associated with the item at the given index in the list. This is built using UMLListMenuItem, which can record the index, rather than plain JListItem. The default provides open, add, delete, move up and move down, with add disabled if there are already as many elements as the upper bound (if any) for the list, open and delete disabled if there are no elements and move up and move down disabled if they cannot be invoked on the given element. The default implementation always returns true.
The following should be declared as needed to support particular pop-up functions.
public void add(int index)
Perform the actions associated with the “add” pop-up menu on the element at the given index. There is no default provided, so this must be given if the “add” operation is supported. The addAtUtil() method (see below) may prove helpful. In this routine you may create a new Model subsytem entity. The best way to do this is using a buildXXX method from the appropriate factory so that the appropriate initialization gets done, but you can also use a createXXX method and set it up (don't forget e.g namespace etc) yourself. Remember also to change anything that references the newly created entity.
NOTE: The following was written regarding NSUML. It may be generally true for the <<Subsystem>> Model subsystem, but this hasn't been verified. CategoryFix: Verify this. NSUML routines generally set up the “other” end of a relationship automatically if you set up one end. If you try to do both (on a NxM relationship) you will probably end up doing it twice. If you do encounter this, the rule of thumb is to explicitly set the ordered end (if you do it the other way round, NSUML will assume you mean the "other" end to be at the end of its ordered list).
public void delete(int index)
- Perform the actions associated with the “delete” pop-up menu on the element at the given index. There is no default provided, so this must be given if the “delete” operation is supported.
public void moveUp(int index)
- Perform the actions associated with the “move up” pop-up menu on the element at the given index. There is no default provided, so this must be given if the “move up” operation is supported.
public void moveDown(int index)
- Perform the actions associated with the “move down” pop-up menu on the element at the given index. There is no default provided, so this must be given if the “move down” operation is supported.
The following normally use the default method, but may be declared to override methods in the parent
public void resetSize()
Called when an external event may have changed the size of the list. The default just sets a flag, which will ensure recalcModelElementSize (see above) is invoked as needed.
public Object formatElement(MModelElement element)
- Return an object (invariably a String) that represents an element. The default provided in the parent defers this to the container, which in turn defers it to the current profile. This is usually perfectly satisfactory.
public void targetChanged()
- Called when the number of elements in the displayed list (including “none”) may have changed. Default invokes the necessary Swing operations to advise of a change in list size.
public void targetReasserted()
- Called when the navigation history has been changed (and navigation buttons may need changing). Not clear why anything is needed, but default recomputes the list size, and invokes the necessary Swing operations.
public void roleAdded(final MElementEvent event)
This describes the old event interface. CategoryFix: It needs to be updated.
part of the NSUML ?EventListener interface. Called when an add event happens, i.e. some Model subsystem object has been added. The default provided looks to see if the event is the role name we declared, or we are listening to all events, and if so looks to see if it relates to an element in our list. If so Swing is notified that the element has been added.
public void roleRemoved(final MElementEvent event)
This describes the old event interface. CategoryFix: It needs to be updated.
part of the NSUML ?EventListener interface. Called when a remove event happens, i.e. some Model subsystem object has been removed. The default provided looks to see if the event is the role name we declared, or we are listening to all events, and if so looks to see if it relates to an element in our list. If so Swing is notified that the element has been removed.
public void recovered(final MElementEvent p1), public void listRoleItemSet(final MElementEvent p1), public void removed(final MElementEvent p1), and public void propertySet(final MElementEvent p1)
This describes the old event interface. CategoryFix: It needs to be updated.
these are all required as part of the NSUML ?EventListener interface, which is not well documented. In each case the default implementation recomputes the size, and advises Swing that the entire list has changed. Needs more investigation.
public void navigateTo(MModelElement modelElement)
The following utility routines are also provided in the parent. They are not normally overridden.
public int getUpperBound()
- get any upper bound (-1 is used if there is none).
public void setUpperBound(int newBound)
- set the upper bound (-1 is used if there is none).
public final String getProperty()
- returns the Model subsystem event name being monitored (null if all are being monitored).
protected final int getModelElementSize()
returns the number of elements in the list. Invokes recalcModelElementSize() (see above) if necessary.
final Object getTarget()
returns the Model subsystem object associated with the container (some child of ?PropPanel usually) that holds this list model.
final UMLUserInterfaceContainer getContainer()
returns the the container (some child of ?PropPanel usually) that holds this list model.
public int getSize()
- returns the size of the list. Including if there are no elements in the model, but the list has a default text when empty.
public Object getElementAt(int index)
- returns the element at the given index in the list.
static protected Collection addAtUtil(Collection oldCollection, MModelElement newItem, int index)
- helps in writing the “add” function. newItem is added at the specified index in the given oldCollection.
static protected java.util.List moveUpUtil(Collection oldCollection, int index)
- helps in writing the “move up” function. Swaps the elements at offsets index and index-1. Not clear why it doesn't return a Collection.
static protected java.util.List moveDownUtil(Collection oldCollection, int index)
- helps in writing the “move down” function. Swaps the elements at offsets index and index-1. Not clear why it doesn't return a Collection.
static protected MModelElement elementAtUtil(Collection collection, int index, Class requiredClass)
- helps in writing the getElementAt(). Finds the element at a specific index. The last argument is ignored!
Building the field
By convention the background of the list is set to the same as the background of the PropPanel and the foreground to Color.blue.
The list is then added to a JScrollPane. Although ArgoUML has historically not used scrollbars (JScrollPane.VERTICAL_SCROLLBAR_NEVER and JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), it is more helpful to permit at least a vertical scrollbar where needed (JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED and JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED).
Finally the inherited method addCaption() is used to add the label for the field and addField() to add the associated scroll pane.
The second argument of each of these identifies the index of the caption/field pair in the vertical column of the grid for this property panel. The third argument identifies the column index. The final argument is a vertical weighting to expand the field if there is room in the property tab. This is usually set to the same non-zero value for all fields and corresponding captions that can have multiple entries, so they expand equally. If none of the fields should expand, the caption only of the last field in each column should be given a non-zero value.
Adding Property Tab Tool-bar Buttons
These are added by creating new instances of PropPanelButton (you don't need to assign them to anything - just creating will do). This has six arguments.
- The container, i.e this property panel (usually just use this).
The panel for the buttons. Use buttonPanel which is inherited from PropPanel.
The icon. Lots of these are already defined in PropPanel.
The advisory text for the button. Use localize(string) to ensure international portability.
- The name of the method to invoke when this button is used. Some of the standard ones (e.g for navigation) are provided, but you will need to write any specials.
- The name of the method (if any) to invoke to see if this button should be enabled. Use null if the button should always be enabled.
In our example, the extend property panel has a “add extension point” button, with a method newExtensionPoint that we provide to create a new use case.
Support for stereotypes
The PropPanel should override the following (note the spelling of the method name).
protected boolean isAcceptibleBaseMetaClass(String baseClass)
Returns true if the given base class is a class of the target in the ?PropPanel. This is used to determine what stereotypes may be shown for this property panel.
Other sorts of fields
Another sort of field that may be useful is the ComboBox. This is useful to allow users to select from a pre-defined list of alongside a navigation arrow to go to the selected entry.
For example this is used to provide drop-down lists for the base and extension use cases of an Extend relationship in PropPanelExtend.
The model behind the drop down is created by using UMLComboBoxModel: UMLComboBoxModel(container, predicate, event, getter, setter, allowVoid, baseClass, useModel).
The container is the PropPanel where we are setting up this ?ComboBox, the predicate is the name of a public method in that PropPanel that, given a model element, determines if it should be in the drop down, the event is the Model subsystem event name we are looking for (see earlier for the list), getter is the name of a public method in the PropPanel that yields the current entry in the combo Box (of type baseClass), setter (with a single argument of type baseClass) sets that entry, allowVoid if true will allow an empty entry for the box, baseClass is the UML metaclass from which all entries must descend, useModel is true to consider all the elements in the standard profile model for inclusion (so the Java types, standard stereotypes etc.).
For our ?PropPanelExtend, we provide a predicate routine the call for the “base” field is:
UMLComboBoxModel(this, "isAcceptableUseCase", "base", "getBase", "setBase", true, MUseCase.class, true);
and we define the methods isAcceptableUseCase, getBase and setBase in PropPanelExtend.
How UMLTextField works
This information is provided by Jaap Branderhorst (September 2002).
UMLTextField implements several kinds of event listeners:
Furthermore it is a UMLUserInterfaceComponent.
Since it is an UMLUserInterfaceComponent it must implement targetChanged and targetReasserted. TargetChanged is called every time the UMLTextField is selected. targetReasserted is of no interest for UMLTextField. It plays a role in keeping history but since history is not really implemented at the moment in ArgoUML it is of no interest. targetChanged does two things:
It calls the targetChanged method of the UMLTextProperty this UMLTextfield is showing.
- It calls the update method. The update method is described further on.
Besides UMLUserInterfaceComponent there are several other interfaces of interest. One of them is MMElementListener.
Every time a MModelElement is changed this will fire an MEvent to UMLChangeDispatch. UMLChangeDispatch will dispatch these events to all containers implementing UMLUserInterfaceComponents interested in this event, including UMLTextField. It will also dispatch the event to all children of an interested container implementing UMLUserInterfaceComponent. By this it is only necessary to register a PropPanel which holds an UMLTextField at UMLChangeDispatch to dispatch the event to the UMLTextField too. MMelementListener knows several methods of which only one is of interest to UMLTextFields:
Called every time a property in a MModelElement is set. This method calls update too if the UMLTextProperty really is affected.
Furthermore UMLTextField implements DocumentListener. This is very typical for UMLTextField. At the moment it is not possible to change the style of the text in the UMLTextField. Therefore the method changedUpdate does not have a body. This method is only called when a DocumentEvent occurs that changes the style/layout of the text. The methods insertUpdate and removeUpdate are respectively called when a character is added to the document UMLTextField contains or removed. Since both methods are called when there is true user input and when the contents of the document are changed programmatically, the methods distinguish between them. insertUpdate and removeUpdate are both handled via the protected method handleEvent. handleEvent updates the property in UMLTextProperty if it is really changed. If the update comes via user input, it is checked if it is valid input. If it is not, a JOptionPane is shown with a warning and the change is not committed into the model. If it is not via user input, the input is not checked and the property is set. If the property is set, the update method is called.
The implementation of FocusListener makes sure that the checking of user input only happens when focus is lost. Otherwise, it would not be possible to enter 'intermediate' values that are not legal. For instance, say the value class is not legal. Without the implementation of FocusListener, it would not be possible to enter class diagram since handleEvent would pop-up a warning message box.
The method update updates both the actual JTextfield as the diagram as soon as some property is set. The updating of the diagram is done by calling the damage method of the figs that represent the property on the diagram.