James Williams
LinkedInMastodonGithub

Porting Simple Calculator from Ruby Shoes to Griffon

Tags: Griffon Ruby

In this post, we're going to continue our series of ports and create a simple calculator. With the exception of a gradient painter to duplicate the effect in the Shoes example, we will again use stock Swing. Here is the requisite screenshot.

View the Ruby code here

Our model for this example is relatively simple with variables for the current number displayed on the calculator, the previous number, and the operation.

SimpleCalculatorModel.groovy

import groovy.beans.Bindable

class SimpleCalcModel {
   @Bindable number = 0 
   @Bindable previous = null
   @Bindable op = null

}

You may have noticed the @Bindable annotation in previous examples. For this port, we're going to break out the training wheels and take it for a spin. @Bindable automagically wraps the variable with a PropertyChangeLister which fires an event whenever that variable is changed. We can bind that variable to a component in our user interface to make sure it is constantly updated. That is what we are doing in our view.

SimpleCalculatorView.groovy

import java.awt.Font
import java.awt.Color
import java.awt.GradientPaint
import org.jdesktop.swingx.painter.*

application(title:'SimpleCalc',
  //size:[320,480],
  pack:true,
  //location:[50,50],
  locationByPlatform:true,
  iconImage: imageIcon('/griffon-icon-48x48.png').image,
  iconImages: [imageIcon('/griffon-icon-48x48.png').image,
               imageIcon('/griffon-icon-32x32.png').image,
               imageIcon('/griffon-icon-16x16.png').image]
) {
    jxpanel(backgroundPainter:new MattePainter(
           new GradientPaint(
               200,0,
               new Color(238,238,204), 
               200,200,
               new Color(153,153,102)))){
        vbox {
        hbox {
            label(font:new Font("Arial", Font.BOLD, 24), text:bind{model.number})
        }
        hbox {
            button("7", actionPerformed:{controller.pressNumber(7)})
            button("8", actionPerformed:{controller.pressNumber(8)})
            button("9", actionPerformed:{controller.pressNumber(9)})
            button("/", actionPerformed:{controller.doOperation("/")})
        }
        hbox {
            button("4", actionPerformed:{controller.pressNumber(4)})
            button("5", actionPerformed:{controller.pressNumber(5)})
            button("6", actionPerformed:{controller.pressNumber(6)})
            button("*", actionPerformed:{controller.doOperation("*")})
        }
        hbox {
            button("1", actionPerformed:{controller.pressNumber(1)})
            button("2", actionPerformed:{controller.pressNumber(2)})
            button("3", actionPerformed:{controller.pressNumber(3)})
            button("-", actionPerformed:{controller.doOperation("-")})
        }
        hbox {
            button("0", actionPerformed:{controller.pressNumber(0)})
            button("Clr", actionPerformed:{controller.clear()})
            button("=", actionPerformed:{controller.pressEquals()})
            button("+", actionPerformed:{controller.doOperation("+")})
        }
      }
    }
}

As we'll see in the controller (below), all our operations act on our model variables and the text label is bound to the value stored in model.number. Paints are merely a short hand way to draw a matte color or paint, pinstripes, checkerboards, alpha values, or images. The class we are using to create our GradientPaint is a part of core Java but SwingX makes it a bit easier to use it. SwingX components are outside core Java and as such are a bit more robust and next-gen. They include various painters and shaped backgrounds, glass panes, login frames, authentication, and busy labels. To install the SwingXBuilder plugin, type griffon install-plugin swingx-builder at a command prompt (or select it from the menu if using NB). That command will import the SwingX components into a "jx" namespace so that they will not conflict with core Swing components and can be used along side them in the same user interface. The first, second, fourth, and fifth values correspond to the x1, y1 and x2, y2 positions in user space for the gradient and determine the direction and intensity.

SimpleCalculatorController.groovy

class SimpleCalcController {
    // these will be injected by Griffon
    def model
    def view

    void mvcGroupInit(Map args) {
        // this method is called after model and view are injected
    }

    def pressNumber = { num ->
        model.number = (model.number * 10) + num
    }

    def clear = {
        model.number = 0
    }

    def pressEquals = {
        switch (model.op) {
            case '*':
                model.number = (model.previous).multiply(model.number)
                break
            case '/':
                model.number = (model.previous).div(model.number)
                break
            case '+':
                model.number = (model.previous).plus(model.number)
                break
            case '-':
                model.number = (model.previous).minus(model.number)
                break
        }
        model.previous = null
        model.op = null
    }

    def doOperation = { op ->
        if (model.op) {
            pressEquals()
        } else {
            model.op = op
            model.previous = model.number 
            model.number = 0
        }
    }

}

Download the source code here.