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

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

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:

    .) 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.

Latest Posts

Related Stories