Month: February 2014

Creating a Custom Java AWT Component – Part 2: Resizing and Segment Control

The whole point of the control is to display a number, so inputting a number is just a case of setting a display value:

private int displayNumber = 8;

public int getDisplayNumber() {
    return displayNumber;
}

public void setDisplayNumber(int displayNumber) {
    this.displayNumber = displayNumber % 10;
    repaint();
}

A simple enough bean compatible getter and setter, but notice that in the setter, I’ve modulo 10, to restrict the input to single digits. I’ve also called the repaint method to re-display the component.

Now looking at the paint method, I only display a segment if it’s used for a particular number. To determine this, I use the Arrays.binarySearch method on an array of the relevant numbers. For example, on the bottom left segment, which is only used by 2, 6, 8 and zero:

//bottom left
if(Arrays.binarySearch(new int[] {0,2,6,8}, displayNumber) > -1) {
    polygon = createSegment(createSegmentPoints(0, segmentHeight * 2 + segmentWidth, segmentHeight, segmentWidth));
    g2.fill(polygon);
}

Perhaps this is a bit over the top and there’s a better way of doing it, but it saves all those OR clauses. This shows the segments and numbers when they are active:

Segment Number
Top 0,2,3,5,6,7,8,9
Top Left 0,4,5,6,8,9
Top Right 0,1,2,3,4,7,8,9
Middle 2,3,4,5,6,8,9
Bottom Left 0,2,6,8
Bottom Right 0,1,3,4,5,6,7,8,9
Bottom 0,2,3,5,6,8

All well and good so far. The only bit left to do is calculating the size of the segments in relation to the size of the panel. If you look at the previous diagram of the component:

SegmentArrangements

you can see that the height is three segment widths and two segment lengths. The width is two segment widths and one length. Using this as a basis, you can than say:

Ws = 2.Wp – Hp

Where Ws is the width of the segment, Wp is the width of the panel and Hp is the height of the panel.  Correspondingly:

Ls = 2.Hp – 3.Wp

Where Ls is the length of the segment. You can see that for certain height and width ratios, the width and length of the segments are negative. Therefore:

2.Wp > Hp

and

3.Wp < 2.Hp.

Also:

Ws < Ls

Doing all the maths, we get that 50% < Wp < 60% of Hp. 55% sounds about right. Plugging this into the paint function:

int width = this.getHeight();
width *= 0.55;
int height = this.getHeight();
//set the width as a proportion of the height
this.setSize(width, height);
        
int segmentWidth = 2 * width - height;
int segmentLength = 2 * height - 3 * width;

Testing all this is simple enough. Put it all in a Frame, with a little timer to tick through the numbers:

private LEDPanel led;

public TestFrame(String title){
    //call the superclass constructor with the specified title
    super(title);

    //add the LED panel for testing
    led = new LEDPanel();
    add(led);
    led.setBackground(Color.black);
    led.setForeground(Color.red);
    led.setDisplayNumber(5);

    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        public void run() {
            led.setDisplayNumber(led.getDisplayNumber() + 1);
        }
    }, 2000, 1000);

    //add the exit listener and set the size
    addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
            System.exit(0);
        }
    });

    setSize(200, 300);
    //show the frame
    setVisible(true);
}

Now it’s all working tickety-boo, I thought I’d go over the refectoring of the code in the next part.

Creating a Custom Java AWT Component – Part 1: Getting Started

As part of the course I’m on, I’m creating a custom AWT component. It’s a derivative of the Panel component and has a number of shapes drawn on its surface using the Graphics2D package.

Figure8

To draw the graphic, I created two methods, createSegment and createSegmentPoints. First createSegment:

private GeneralPath createSegment(int[][] points){
    GeneralPath polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, points.length);
    polygon.moveTo(points[0][0], points[0][1]);

    for(int index = 1; index < points.length; index++) {
        polygon.lineTo(points[index][0], points[index][1]);
    }
    polygon.closePath();
    return polygon;
}

This method creates and returns a GeneralPath object, a polygon drawn (using the method lineTo) by a set of points, starting  then finally completed with a call to the method closePath.

The createSegmentPoints method creates an array of six points:

private int[][] createSegmentPoints(int originX, int originY, int width, int height) {
    int[][] points = new int[6][2];
    if(width >= height) {
        points[0] = new int[]{originX + height/2, originY};
        points[1] = new int[]{originX + width - height/2, originY};
        points[2] = new int[]{originX + width, originY + height/2};
        points[3] = new int[]{originX + width - height/2, originY + height};
        points[4] = new int[]{originX + height/2, originY + height};
        points[5] = new int[]{originX, originY + height/2};
    } else {
        points[0] = new int[]{originX + width/2, originY};
        points[1] = new int[]{originX + width, originY + width/2};
        points[2] = new int[]{originX + width, originY + height - width/2};
        points[3] = new int[]{originX + width/2, originY + height};
        points[4] = new int[]{originX, originY + height - width/2};
        points[5] = new int[]{originX, originY + width/2};
    }
    return points;
}

It uses the ration of height and width to determine if the segment is horizontal or vertical and then figures out the points from there.

Segment

SegmentEnd

Each segment is then rendered to the surface of the Panel in the paint method:

public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;

    int height = 20;
    int width = 100;
    GeneralPath polygon;
    //top
    polygon = createSegment(createSegmentPoints(height, 0, width, height));
    g2.setPaint(Color.red);
    g2.fill(polygon);
    ...
}

SegmentArrangements

There is a problem with flickering on resize as it repaints and this could be solved using double-buffering, which I’ll write about later.