James Williams
LinkedInMastodonGithub

Porting Simple Timer from Shoes to Griffon

Tags: Griffon

Shoes is a GUI toolkit for making cross-platform desktop applications. While the number of widgets somewhat limited, the simplicity makes it easier to dive into. Partnered with Ruby's terseness, it makes for very concise and readable code. It was created by _why the lucky stiff whose recent disappearance caused much Sturm und Drang in the Ruby community. Luckily, it was one of the projects successfully archived in the whymirror on Github. In this post, I'm going to port one of the examples to Griffon. Simple Timer is one of the first examples from the material creating a colored window, two buttons and a text label that responds to events from those buttons.

Here is what the Ruby code looks like:

Shoes.app :height => 150, :width => 250 do
  background rgb(240, 250, 208)
  stack :margin => 10 do
    button "Start" do
      @time = Time.now
      @label.replace "Stop watch started at #@time"
    end
    button "Stop" do
      @label.replace "Stopped, ", strong("#{Time.now - @time}"), " seconds elapsed."
    end
    @label = para "Press ", strong("start"), " to begin timing."
  end
end

For the Griffon version, we'll need to break that code into a couple parts. That's not to say that we couldn't put everything in one file, it's just that doing so would violate Griffon's MVC design. There is only one variable in the Ruby file that is not backed by a UI element, time. In our Griffon version, that variable will live in SimpleTimerModel.

SimpleTimerModel.groovy

import groovy.beans.Bindable

class SimpleTimerModel {
   @Bindable time
}

Shoes' stack keyword most closely correlates to a vertical box in Griffon. On the Griffon side, Shoes do...end blocks become actions, in this case actionPerfomed.

SimpleTimerView.groovy

import java.awt.Color

application(title:'SimpleTimer',
  size:[250,150],
  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]
) { //Start of code we added
    panel(background: new Color(240, 250, 208)) {
        vbox {
            button(text:'Start', actionPerformed: {
                model.time = new java.util.Date()
                controller.wrapLabelText(view.status, "Stop watch started at ${model.time}")
            })
            button(text:'Stop', actionPerformed:{
                def elapsed = (new java.util.Date().getTime() - model.time.getTime())/1000.0f
                view.status.text = "<html>Stopped, <strong>${elapsed}</strong> seconds elapsed."

            })
            label(id:'status', text:'<html>Press <strong>start</strong> to begin timing</html>')
        }
    } // End of code we added 
}

Now for the slightly difficult part. Shoes autowraps text fields based on the parent container whereas Java Swing doesn't. After a little Google-ing, I found a sample that I had to tweak a little to get working in Griffon. To honor MVC and keep our view clean, that code lives in the controller.

SimpleTimerController.groovy

import java.awt.Container;
import java.awt.FontMetrics;
import java.text.BreakIterator;
import javax.swing.SwingUtilities;

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

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

    // tweaked from sample on http://www.geekyramblings.org/2005/06/30/wrap-jlabel-text/
    private void wrapLabelText(label, text) {
        FontMetrics fm = label.getFontMetrics(label.getFont())
        Container container = label.getParent()
        int containerWidth = container.getWidth()

        BreakIterator boundary = BreakIterator.getWordInstance()
        boundary.setText(text)

        StringBuffer trial = new StringBuffer()
        StringBuffer real = new StringBuffer("")

        def words = text.split(" ")

        for (word in words) {
            trial.append(" "+word)
            int trialWidth = SwingUtilities.computeStringWidth(fm,
                trial.toString());
            if (trialWidth > containerWidth) {
                trial = new StringBuffer(word)
                real.append("<br>")
            }
            real.append(" "+word)
        }

        label.setText(real.toString())
    }
}

Besides the code enabling line wrapping, our Griffon version is very similiar to the Shoes version both in lines of code and composition. You can download the code here on Github

For those that don't already have Griffon installed, visit here.