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

A Java Drawing Editor, Part 2: User Input and Symbol Management

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

Welcome back to our series on a designing a drawing editor in Java. Before reading the article, you may want to have a look at the real thing. Try it out by downloading this JAR file

. All the source code will be released in the next article. As promised, in this part, we will have a look at the class organization, focusing on the actions and the central Draw class.

The Overall Architecture

We can think of a drawing editor as a concrete instance of a class of software systems specialized in creating and manipulating a population of objects that obey certain abstract rules. The design techniques in this series can be used in the design of just such a large class of systems, and are even reusable in completely different contexts.

To build a simple, yet professional, drawing editor, we need:

  • A set of symbols that obey given rules, such as the ability to be cut and pasted, or the ability to fire events on their own. See part 1 of this series:

    A Java Drawing Editor, Part 1: Creation Aspects

  • A container for our symbols, which is able, among other things, to draw them and to let them work together. The rules that the symbols have to comply with are enforced partly by the symbols themselves, but mainly by their container.
  • The user, or a representative of the user. A source for all the commands that will affect the symbols. Our software will definitely be interactive.
  • Management — details will be presented later.

The user and the symbols continuously trigger actions and respond to actions; the container executes these actions and takes care of the correct overall functioning. Given the simplicity of the abstract rules in our editor, the implemented symbols will neither trigger events nor respond to them.

A system screenshot is shown in Figure 1.

Figure 1. A screenshot from the editor with actions' text labels.

The Central Piece: the Draw Class

Centralizing key aspects of the design into just one class helps the design of very simple cases, but makes the design of more complicated ones somewhat problematic. Nevertheless, our drawing editor has the canvas (symbol container), the reference for most user commands, and the interaction with auxiliary classes all combined in one class. Everything passes through the

Draw
class. (We don't have the space here to discuss it in detail, but you can look at the code

.) Here's a typical scenario: the user triggers an action, for example, clicks on the button for cutting an image from the drawing; the action is executed; and some of its processing involves the canvas, that centralizes all the operations on symbols, the access to the clipboard, currently selected symbols, etc. The canvas performs the requested operations, eventually involving other classes, like the stack used for undoing actions and the mediator (part of the auxiliary elements introduced at the beginning) for coordinating the state of other widgets. We will see in detail the mediator in the next (and last) part of this series.

The Draw class has several attributes. The most important ones include:

  • An array of symbols that records all the items added to the drawing. The order is meaningful, because it describes the painting order, so it affects the layering of one symbol on the other.
  • The clipboard, is implemented as an array of
    AbstractSymbols
    . Note that in the current implementation only a single selection is supported, so the clipboard is made up of only one symbol at a time.
  • The currently selected symbols, holding references to some objects in the symbols array. Again, only a single selection is currently implemented.
  • Some additional helper members discussed below.

To recap: the

Cut Action
asks the
Draw
class to cut the currently selected symbol. The
Draw
itself performs some internal processing, and then calls the mediator that recalculates the state of all the affected widgets (for example, the paste icon will get enabled). As you can see, the overall class interaction is clear and well-defined.

We have the symbols and we have their container, but we still need a user. Many choices are possible; here we'll use an action-centric design to represent user input. Let's take a look.

Actions and Undo Support

We saw that

Actions
are, in a way the user's extensions into our software. The user manipulates symbols by means of
Actions
. Clearly, our
Action
class extends the standard
AbstractAction
class. Because we designed our
Draw
class as the symbol manipulator class, every command dealing with symbols should invoke the
Draw
instance's methods accordingly.

Each

Action
provides two basic methods:
  • ActionPerformed()
    to execute that command
  • Undo()
    to neutralize itself, once executed

Let's see the structure of a typical

Action
suited for our class framework:
public final class Cut extends Action {

This action has one operand, the symbol to be cut:

    private AbstractSymbol symbol;

The constructor specifies the referring Draw instance:

    public Cut(Draw d) {      super(IconRepository.CUT_ICON,d);      mnemonic = 'x';      toolTipText = "cuts the selected symbol";    }

The command itself:

    public void actionPerformed(ActionEvent e) {      super.actionPerformed(e);//to register for undo      draw.cutSelectedSymbol();      symbol = draw.getClipboard();    }

Note the call to the

actionPerformed()
of its superclass

; without that line of code the action wouldn't get registered for undo.

Invoked when undoing this action:

    public void undo() {      symbol.setSelected(false);      draw.add(symbol);    }  }

Undo Support

We adopted a very simple form of undo support for two reasons: First, standard undo classes are implemented only for text-related components; second, we wanted to create a very simple and effective mechanism, not to reinvent a general purpose one.

Actions that support undo (not all the actions provide undo, for example the

Save
action) must save information about the current instances affected by their operation, i.e., their operands. In this way the
undo()
method can access the operands and restore the situation that existed before the command was executed. Using the stack, we can pop past actions as needed, calling their
undo()
method to restore the situation from just before their
actionPerformed()
was executed.

The

Undo
itself is a command just like any other. Note that with this design, the redo command is straightforward to implement, just expanding the class that stores the executed actions.

For the sake of convenience, all the actions are gathered together in a repository, from which they are taken as needed. This kind of organization works quite well for large numbers of similar classes that have short code implementations.

Finally, a side note on the function of our

Action
class. We stretched the standard implementation of
AbstractAction
, as provided by those nice guys at Sun, in two ways: First, we specialized it for fitting in our class framework; see, for instance, the
Draw
reference every Action has; second, we modified the meaning of an action slightly; keeping the operands self-contained makes it a recorded command, rather than an executable action. Leveraging existing class frameworks in this way eased the design process enormously. Dealing with class frameworks is always a two-faced coin: In order to make the most of them, developers need to understand their intended approach to problems, rather than just cutting and pasting code from somewhere else. That is more difficult at first, of course. But it pays off in the long run.

Unfortunately, the trend in modern programming languages is toward more complex, abstract, and powerful APIs, where classes are knitted together to create powerful class frameworks.

In the next article, we'll explore the chosen mediator implementation, the remaining classes, and some implementation issues.

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.






Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel