A Java Drawing Editor, Part 1: Creation Aspects
Welcome to this series of three articles about developing a real-world, professional drawing editor with Java. We will discuss design choices and the implementation of the many possible features. I will discuss the most important issues, keeping the number of classes, and the amount of code to the minimum possible, in order to focus effectively on design concepts.
Whoever says that Java is too clumsy for the desktop should try out this simple piece of software. The first objective of the series is to show better design techniques; as a bonus, though, you will get a full working drawing editor that features, among other things:
- Ease of expandability with maximum flexibility:
- A simple mechanism for adding new symbols to the palette, even at runtime (plus several other features like serialization of the symbol palette)
- Each symbol has its own properties and can support its own actions
- Undo support
- Load and save functions
- Print function
- Common functionalities such as cut, paste, rotate, etc.
- Coherent enabling/disabling of menu items and toolbar buttons, depending on the user's input
- An expandable architecture that easily allows sophisticated features like zooming
In Figure 1, we have a screenshot from the drawing editor.
Symbol Creation Some Theory
We start our series with the symbol hierarchy design; in the second part, we will see the overall software architecture and discuss its most compelling aspects.
The Abstract Factory Design Pattern
There are many cases where the simple creation of objects represents a design problem itself. The classic Factory Method design pattern needs an abstract creator class so that its concrete subclasses can instantiate the needed Product subclasses. (See Figure 2.)
In some situations, the classic diagram below can be simplified. When the information about which product to create is given from outside to the Factory class, the subclasses of Creator are not needed. In our case, the information about what symbol (product) to create is given by the user, who selects it from a palette that stores all the known symbols.
We can translate our diagram to the following one (shown in Figure 3), tailored to our drawing editor:
class represents a placeholder for all the concrete subclasses of an
, which we will examine below.
Note that we don't need any special code for the initialization of the concrete subclasses of
, because we have to create an object with internal default values and place it on the screen where the cursor is. This means that our
class doesn't need to be subclassed for every
's concrete subclass; we have just one concrete
class for all our symbols. We accomplish this by creating symbols taken from the palette and adding them to the canvas as needed.
First of all, we have to define a hierarchy of symbols useful for our editor. Indeed, we may have several hierarchies, depending on the kind of domain our symbols belong to (geometrical shapes rather than technical symbols, etc.), the implementation chosen (splines, etc.) efficiency and other constraints. Of course any symbol hierarchy (also known as an ontology) is partial and arbitrary, in that its main required feature is to serve our purposes (see Figure 5). All the instances of these classes represent the objects put by the user on the drawing canvas. These objects can be dragged with the mouse, opened by the user to be modified, saved in a stream to disk, and so on.
One key guideline here is to keep all the behavior of one object inside itself (nothing new from an OO viewpoint). This means that when a symbol is edited, only its class is responsible for its behavior. A line will show the control points so they can be modified by the user (see Figure 4.). A text object will prompt the user with a caret to modify the displayed text via keyboard. The container and the other classes don't know anything about what is actually going on inside the symbol. This provides many useful properties, such as the ability to modify the behavior of single objects, or to add new symbols to the palette. Whenever an object needs to communicate with the outside framework, an event mechanism will do the job without sacrificing the decoupling we obtained with the architecture.