August 22, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

A Java Drawing Editor, Part 1: Creation Aspects

  • January 3, 2001
  • By Mauro Marinilli
  • Send Email »
  • More Articles »

Parameterized Factory Method

The plot gets thicker when our drawing editor needs to deal with potentially hundreds of different types of symbols. Maintainability and efficiency issues must be taken into account. One common-sense guideline is to keep to the smallest number of classes possible. The internal code and the external behavior for, say, the pentagon and hexagon classes are very close, and they can safely be kept in one class. We just have to manage vertices, and let them be edited by the user, and so forth. On the other hand, we want to keep them different, because they have a conceptual meaning, and not just a different number of vertex in the implementation. To avoid letting the number of subclasses of

AbstractSymbol
explode, and in order not to have just a few large and unmanageable classes that draw everything, we can simply use a parameter to detail the (sub)type that the given class implements. So when the user selects a pentagon from the symbol palette, the software creates a polyhedron instance obtained with the "pentagon" parameter given to the constructor. A possible implementation could be using a hash table for mapping subtypes with actual classes, so that we can trace back symbols to the classes that actually implement them. To keep our implementation simple, we won't use this feature, although it is essential in large symbol ontologies.

We must design the
AbstractSymbol
class from two different perspectives: one internal (toward the canvas object and the other components of our software architecture), and the other external (toward the user).

Designing the AbstractSymbol Class

We want the user to be able to cut, copy, paste, scale, rotate and delete every symbol. In addition to having these functionalities, every symbol must fit within our software framework. It must be drawn onto the screen, so a symbol has to have attributes for the pixel location (the point that refers to the upper-left pixel of the symbol). Because we can generally modify the symbols (for instance, change the control points of a line, see Figure 4), a Boolean property that indicates the edit state of the symbol is needed. Of course, because every symbol can be edited to some extent, we only write the code once for this operation in the

AbstractSymbol
class, letting the subclasses specialize it for more complex behavior.

Implemented Classes

In this version the following graphical symbols are implemented (and given the prices paid for it, it's a good deal):

  • line
  • curve
  • ellipse
  • bitmap icon

Let's have a look at the

AbstractSymbol
class: First of all, let's start from the intended behaviors for our symbols:

  • We want to save and load our symbols (we can use standard Java serialization to accomplish this.)
  • We would like to clone symbols, for example, to keep track of the operations performed by the user, in order to provide undo support.
  • Symbol Factory: We need a way to build symbols when the user chooses them from the palette. Here we have many possible equally good design choices. For example, one can centralize the symbol creation in a
    SymbolFactory
    class. We could do it using an integer enumeration-like value, but it wouldn't scale nicely, and it's always tricky to fix things with dynamic features. Because today's Java lacks the feature of parameterized types (i.e., parameterized classes), we need a simple and effective ad hoc workaround. The needed behavior is to create the right
    AbstractSymbol
    subclass. A better and simpler way is just letting any symbol be responsible for creating new instances of itself. There are several points to notice here:
    • We are modeling the specialized behavior of creating a new symbol to put on the canvas. In some situations, that could be different from plain creation, by means of a standard constructor, for example. Distinguishing it from standard instantiation adds cleanness and clarity.
    • We lose centralization, and that could be an issue, especially for large ontologies and/or complex symbol environments (for example, if we were building a complex graph drawing toolkit, centralization could help both to tame complexity and to offer some extra features needed for complex symbol environments such as topology layout, etc.).
    • We gain in simplicity, but also in redundancy; have a look at the source code

      .

    • Finally, because it's an unusual choice. I think that seeing and discussing unusual design options can help sometimes to broaden the view, instead of always feeding developers with ready-made solutions.
    Unfortunately, we don't have the space to go into these aspects deeply here. My choice is toward cleanness and simplicity. Yours could be dynamic instantiation of classes from the palette or default constructors, cloning as in the prototype design pattern, etc. As you like. The interface
    SymbolCreable
    allows the creation of new symbols.
  • Every symbol could perform its own set of actions, callable from the right mouse button, for example. The simplest way to do that is implementing the
    ActionListener
    interface. In our simple editor, it's not implemented.
  • Symbols not only show the user commands but properties, as well. For example, a bitmap icon has an image as a property, and we would like the user to change it as needed. The interface
    HavingUserProperties
    will model this aspect. (Note that not only
    AbstractSymbol
    s will need that behavior.) The simplest way is to prompt the user with a dialog containing the symbol's user-sensitive properties. Again, every symbol is only responsible for its own properties management.
  • A lot of different behaviors could be added, depending on the complexity of our task. A common enhancement is a set of event listeners and event producers for every symbol, as in bean-like environments.

That said, we have the following class inheritance for our simple editor:

public abstract class AbstractSymbol implements java.io.Serializable, Cloneable, SymbolCreable, HavingUserProperties { ...

Then the methods:

  • public boolean contains(Point p, int tolerance) 

    Whether a screen point lies or not in the symbol.
  • public abstract void draw(java.awt.Graphics2D g) 

    Mimics the
    paint()
    method for rendering itself onto the canvas.
  • public abstract Rectangle2D.Float getRectBounds()

    To obtain the rectangle bounding the symbol; a rough design choice. A finer solution would be to obtain a shape rather than a rectangle.
  • public Object clone()

    Needed for cloning symbols.
  • public void processMouseEvent(MouseEvent me) 

    A very important method. Allows responsibility, encapsulation, and flexibility at the same time. Symbols receive mouse events from their container to be processed internally. For example, adding a control point to a curve is handled by this method.
  • public abstract AbstractSymbol createNew() 

    Implements
    SymbolCreable
    . Creates a new symbol.
  • public void initializeAt(Point location) 

    A method for setting up a symbol on the screen at a given location.
  • public abstract void editProperties()

    Implements
    HavingUserProperties
    . Forces the symbol to show a JDialog in order to modify its internal settings, as color, size, etc.
  • public void rotate()

    Rotates the symbol of a given, fixed amount (i.e., it draws the symbol rotating it accordingly).
  • public void rotateBack()

    Produces the opposite effect of the previous method.

And the properties:

  • boolean editMode

    Whether the symbol is open in edit mode (i.e., shows its control points, etc.) or not. Note that some symbols do not have an edit state, like bitmap icons, for instance.
  • float rotationAngle

    The current rotation angle, affected by the two methods,
    rotate()
    and
    rotateBack()
    .
  • boolean selected

    Whether the symbol is selected or not. Depending on this flag, the
    draw()
    method changes the appearance of the symbol to be drawn.
  • Point location

    The symbol location on the screen.
The Static Class Diagram

Finally, we can look at the classes we discussed in this first part, shown in Figure 6.

Figure 6. The classes related to our chosen symbol ontology.

In the next article, we will explore the rest of the software architecture, and in the third part we will put all the pieces together.

About the Author

Mauro Marinilli has been a Java developer since the language's early days. He's also active in academic research, mainly in information filtering, case-based reasoning, and adaptive hypermedia. He is currently working as chief engineer for the GUI team developing the new Italian Air Force Meteorological and Forecasting System.





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel