Cartesian Plane Lesson 16 Page 13: Developing the Graph Properties Panel GUI

GraphPropertiesPanel, GUI Testing, Serialization

Here we are going to begin development of the Graph Properties Panel, class GraphPropertiesPanel. The facility is going to look a lot like the LinePropertiesPanel, including a class to encapsulate a set of graph properties (class GraphPropertySet), and to coordinate their management with the PropertyManager. The GUI itself will be similar to the LinePropertiesPanel, with radio buttons encapsulating GraphPropertSet subclasses on the left, and components for setting property values on the right. You can see the panel, and experiment with its behavior by running application ShowGraphPropertiesPanel in the project sandbox.

Because our two panels are going to be so similar, we will concentrate on describing the GraphPropertiesPanel construction and behavior in general, without necessarily describing its code in detail. In fact it would be an excellent exercise for you, the student, to take a look at the GUI, then, using the LinePropertiesPanel facility as a guide, attempt to develop the facility yourself. We’ll start by comparing the two panels at a high level.

GitHub repository: Cartesian Plane Part 16

Previous lesson: Cartesian Plane Lesson 16 Page 12: Testing the Line Properties Panel GUI, Visual Testing

LinePropertiesPanel and GraphPropertiesPanel, a Comparison

The following table provides a side-by-side comparison of the components of the LinePropertiesPanel facilities, and the corresponding facilities for the GraphPropertiesPanel.

LinePropertiesPanel GraphPropertiesPanel
class LinePropertySet class GraphPropertySet
Categories:
  • Axes
  • Major Tics
  • Minor Tics
  • Grid Lines
Categories:
  • Main Window
  • Top Margin
  • Right Margin
  • Bottom Margin
  • Left Margin
Implemented using superclass LinePropertySet plus one subclass per category. Implemented using superclass GraphPropertySet plus one subclass per category.
Methods apply() and reset() coordinate property values with PropertyManager. Methods apply() and reset() coordinate property values with PropertyManager.
Properties:
  • draw
  • stroke
  • length
  • spacing
  • color
Properties are optional per category; e.g. Axes don’t have a length.
Properties:
  • draw
  • font-name
  • font-style
  • font-size
  • font-color
  • background color
Properties are never optional; simplifies property management.
GUI Layout GUI Layout
Options panel on left; vertical arrangement of PRadioButton<LinePropertySet> Options panel on left; vertical arrangement of PRadioButton<GraphPropertySet>
Component panel on right; vertical arrangement of Feedback components, ColorEditor and JToggleButton. Component panel on right; vertical arrangement of FontEditor, JToggleButton, JTextField and ColorEditor.
Control panel on bottom, contains Apply, Reset and Close buttons. Control panel on bottom, contains Apply, Reset and Close buttons.
Event Processing Event Processing
  • Initialization: load all property values from the PropertyManager (this happens automatically for each category as soon as we instantiate the category’s LinePropertySet subclass). Perform a synch-right for the selected radio button; this is taken care of when we select the first radio button (see Radio Button Selection, below).
  • Radio Button Deselection: Perform a synch-left for the button being deselected.
  • Radio Button Selection: Perform a synch-right for the button being selected.
  • Apply Button Selection: Perform a synch-left for the currently selected radio button; execute the apply method for each radio button’s encapsulated LinePropertySet object. Note that the synch-left operation is initiated by generating a deselection event for the currently selected radio button.
  • Reset Button Selection: Execute the reset method for each radio button’s encapsulated LinePropertySet object; perform a synch-right for the currently selected radio button. Note that the synch-right operation is initiated by generating a selection event for the currently selected radio button.
  • Close Button Selection: Look for the button’s JDialog ancestor. If found, close the dialog, otherwise ignore the operation.
  • Initialization: load all property values from the PropertyManager (this happens automatically for each category as soon as we instantiate the category’s GraphPropertySet subclass). Perform a synch-right for the selected radio button; this is taken care of when we select the first radio button (see Radio Button Selection, below).
  • Radio Button Deselection: Perform a synch-left for the button being deselected.
  • Radio Button Selection: Perform a synch-right for the button being selected.
  • Apply Button Selection: Perform a synch-left for the currently selected radio button; execute the apply method for each radio button’s encapsulated GraphPropertySet object. Note that the synch-left operation is initiated by generating a deselection event for the currently selected radio button.
  • Reset Button Selection: Execute the reset method for each radio button’s encapsulated GraphPropertySet object; perform a synch-right for the currently selected radio button. Note that the synch-right operation is initiated by generating a selection event for the currently selected radio button.
  • Close Button Selection: Look for the button’s JDialog ancestor. If found, close the dialog, otherwise ignore the operation.
Testing Facilities Testing Facilities
  • LinePropertySetInitializer: to start all applications and tests with predictable and unique property values.
  • LinePropertySetTest: JUnit test for the LinePropertySet class.
  • LPPTestDialog: dialog to manage GUI interaction during testing and test data generation.
  • LinePropertiesPanelFuncTest: JUnit test for functional testing of properties panel.
  • LPP_TADetail: for organizing test data.
  • LPP_TA: test assistant for generating visual test data.
  • LPP_TAVisualizer: test assistant for examining visual test data.
  • LinePropertiesPanelVisualTest: JUnit test for validating properties panel appearance.
  • GPPTestDataInitializer: to start all applications and tests with predictable and unique property values.
  • GraphPropertySetTest: JUnit test for the GraphPropertySet class.
  • GPPTestDialog: dialog to manage GUI interaction during testing and test data generation.
  • GraphPropertiesPanelFuncTest: JUnit test for functional testing of properties panel.
  • GPP_TADetail: for organizing test data.
  • GPP_TA: test assistant for generating visual test data.
  • GPP_TAVisualizer: test assistant for examining visual test data.
  • GraphPropertiesPanelVisualTest: JUnit test for validating properties panel appearance.

As you can see, all the work we did on the LinePropertiesPanel is applicable to the GraphPropertiesPanel; not the code itself, perhaps, but the overall design and implementation strategy. Let’s look at an overview of the GraphPropertySet object.

class GraphPropertySet
An object of this class is going to have a field, a getter and a setter for each of the properties it manages. It’s a little less complicated than LinePropertySet because we don’t have to worry about whether a graph category supports a particular property; all categories support all properties. The code for the class looks, in part, like the following; as usual, you can find the complete code in the GitHub repository.

public abstract class GraphPropertySet
{
    // ...
    private float   width;
    private Color   bgColor;
    private Color   fgColor;
    private String  fontName;
    private float   fontSize;
    private String  fontStyle;
    private boolean fontDraw;
    // ...
    public float getWidth()
    {
        return width;
    }

    public void setWidth(float width)
    {
        this.width = width;
    }
    // ...
}

The class has an instance field to hold the name of each property for a given graph category; the property names are established via the constructor, for example:

public abstract class GraphPropertySet
{
    private final String        widthProperty;
    private final String        bgColorProperty;
    private final String        fgColorProperty;
    private final String        fontNameProperty;
    private final String        fontSizeProperty;
    private final String        fontStyleProperty;
    private final String        fontDrawProperty;
    // ...
    public GraphPropertySet(
        String widthProperty, 
        String bgColorProperty,
        // ...
    )
    {
        super();
        this.widthProperty = widthProperty;
        this.bgColorProperty = bgColorProperty;
        // ...
        reset();
    }
    // ...
}

For each graph category there is a GraphPropertySet subclass: GraphPropertySetMW, GraphPropertySetTM, GraphPropertySetRM, GraphPropertySetBM, and GraphPropertySetLM. Each concrete subclass provides the superclass with the names of the properties for that category. Here is the complete class declaration for GraphPropertySetMW:

public class GraphPropertySetMW extends GraphPropertySet
{    
    public GraphPropertySetMW()
    {
        super(
            CPConstants.MW_WIDTH_PN,
            CPConstants.MW_BG_COLOR_PN,
            CPConstants.MW_FONT_COLOR_PN,
            CPConstants.MW_FONT_NAME_PN,
            CPConstants.MW_FONT_SIZE_PN,
            CPConstants.MW_FONT_STYLE_PN,
            CPConstants.MW_FONT_DRAW_PN
        );
    }
}

The class has an apply method which updates the PropertyManager, and a reset method which initializes its properties with values obtained from the PropertyManager:

public abstract class GraphPropertySet
{
    private final PropertyManager   pMgr    = PropertyManager.INSTANCE;
    // ...
    public void apply()
    {
        pMgr.setProperty( widthProperty, width );
        pMgr.setProperty( bgColorProperty, bgColor );
        pMgr.setProperty( fgColorProperty, fgColor );
        // ...
    }
    public void reset()
    {
        width = pMgr.asFloat( widthProperty );
        bgColor = pMgr.asColor( bgColorProperty );
        fgColor = pMgr.asColor( fgColorProperty );
        // ...
    }
    // ...
}

The way we handle the isBold and isItalic properties deserve special mention. These are two components of the font style, which in the PropertyManager is a single property stored as a string. The getters and setters for these properties, in conjunction with the helper method setStyle( boolean isItalic, boolean isBold ), deal with this issue as shown below. (See also Page 1, “PropertyManager” in the Refactoring section.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private String  fontStyle;
// ...
public boolean isBold()
{
    if ( fontStyle == null )
        fontStyle = "PLAIN";
    String  ucStyle = fontStyle.toUpperCase();
    return ucStyle.contains( "BOLD" );
}
public void setBold(boolean isBold)
{
    setStyle( isItalic(), isBold );
}
private void setStyle( boolean isItalic, boolean isBold )
{
    StringBuilder   bldr    = new StringBuilder();
    if ( !isItalic && !isBold )
        bldr.append( "PLAIN" );
    else
    {
        if ( isItalic )
        {
            bldr.append( "ITALIC" );
            if ( isBold )
                bldr.append( " " );
        }
        if ( isBold )
            bldr.append( "BOLD" );
    }
    fontStyle = bldr.toString();
}

⏹ GUI Layout

  • Main Panel
    The main panel for this class is a JPanel with a BorderLayout and a TitledBorder. Its direct children consist of three additional panels.
    • Left Panel
      This panel contains the one radio button for each category, type PRadionButton<GraphPropertySet>, arranged vertically. It is added to the main panel’s West region.
    • Control Panel
      This panel has a FlowLayout and contains the JButtons that control the Apply, Reset and Close operations. It is added to the main panel’s South region.
    • Right Panel
      This is the panel that contains the components to configure property values. It is added to the main panel’s Center region, and contains three JPanel children.
      • Font Panel
        The font panel has a TitledBorder; it contains all the controls related to configuring the font. It has two JPanel children arranged vertically:
      • Width Panel
        This is a JPanel with one child, a JSpinner that controls the category’s width property. It has a TitledBorder.
      • Background Color Panel
        This JPanel comes courtesy of the ColorEditor (see Cartesian Plane Lesson 15 Page 5: The ColorEditor Class). It has a TitledBorder.

⏹ Event Processing
Event processing for the GraphPropertiesPanel is nearly identical to that of the LinePropertiesPanel. Every radio button is associated with a GraphPropertSet subclass. When a radio button is selected, we do a sych-right, copying the button’s GraphPropertSet to the components in the right panel; when it’s deselected we do a synch-left, copying the values from the components on the right to the radio button’s GraphPropertSet. For an apply operation, we make sure that the property values encapsulated by the currently selected radio button match the values of the components on the right of the panel (synch-left), then execute the apply method for every radio button’s GraphPropertSet. For a reset, we call the reset method for every radio button’s GraphPropertSet, then do a synch-right for the currently selected radio button. All of this requires putting ItemListeners on on all the radio buttons, and ActionListeners on the push buttons.

Miscellaneous events, such as displaying the ColorSelector when a Color button is pushed, are handled by the editor and feedback components comprising the right panel of the GUI.

⏹ Testing Facilities
Testing the GraphPropertiesPanel is very much like testing the LinePropertiesPanel. The facilities consist of:

Property value initialization: A class to perform property initialization, so that tests and related facilities can begin with consistent and mostly unique property values*. The strategy used for testing the GraphPropertyPanel is a bit different from that used to test the LinePropertiesPanel; we initialize an instance of each of the GraphPropertySet subclasses, then call their apply methods.

*About mostly unique property values: in each of the five categories of GraphPropertySets we have properties with Boolean values, and their are only two Boolean values, so not all the Boolean properties can have unique values.

GraphPropertySetTest (JUnitTest): we make sure to test all the GraphPropertySet classes thoroughly prior to attempting to test the GUI. The test consists of one abstract test class, GraphPropertySetTest, and a concrete test subclass for each of the GraphPropertySet subclasses. The abstract superclass does most of the work; the concrete subclasses just provide configuration data.

Test Dialog: for testing the GUI, we have a utility class that manages the GUI under test, GPPTestDialog. This reduces the need for the associated JUnit test to have to go looking for components, and having special logic for making sure GUI operations are executed on the EDT.

Functional vs Visual Testing: our JUnit tests are separated into a functional test and a visual test. Functional testing makes sure that the various components in the GraphPropertiesPanel have the correct values after certain operations are performed. Visual testing is making sure that the panel looks right. Visual testing requires separate applications for generating and examining test images. The GPP_TA application performs the image generation, and the GPP_TAVisualizer application provides the monitoring task. To organize the images in the test files we use a special class that can be serialized and deserialized, the GPP_TADetail class.

Note: While testing GPP_TAVisualizer I came up with a minor issue. It’s not a blocking problem, it only occurs during testing and there’s a workaround for it. I documented the issue in the GitHub project issue tracker.

GPP_TAVisualizer Fails to Validate Actual Image Due to Font
Issue #1

If font size changes while switching between saved images; or if font size is too large; GPP_TAVisualizer fails to validate expected image (from data file) against actual image (from test dialog). Sometimes, when switching to a different image then switching back to the original, the image is successfully validated.

Upon examination by a human, no problem with any of the images could be discerned.

Work-Around
Added to GPPTestDataInitializer a facility to fix the font size to a constant for all test images (see GPPTestDataInitializer.setFixedFontSize(Double)), so that all images have the same font size. Fixed the font size to 6 in initialization code for:

  • GPP_TA
  • GPP_TAVisualier
  • GraphPropertiesPanelVisualTest

The complete code for testing the GraphPropertiesPanel can be found in the GitHub repository.

Summary

On this page we had a general discussion of how to design, code and test the GraphPropertiesPanel. The details of the implementation are left to the student (or, if you’re in a hurry, you can just look in the GitHub repository).

In anticipation of our final task, assembling a menu bar for our application, we will develop a facility for displaying messages that can contain HTML, CSS and hyper links.

Next: Class MessagePane