Compulsion to Code

In software development there are no silver bullets, but I am always looking out for the next bronze one.

Tuesday, June 9, 2009

A Refactoring in Scala

    One pet project I have going is a little Logo interpreter done using Scala parser combinators with a Swing GUI where code can be edited and executed with results being rendered on a canvas. Being a good little Agile developer, I wrote automated tests for it

    I've had experience on a previous Swing project using the Netbeans Jemmy library for driving the GUI tests in Java. I was curious of what Scala can wrap around such testing libraries like Jemmy. Initially, the test code was more Java style than Scala since I've only used Jemmy with Java previously. I found that interesting refactorings can be applied as the code takes on the more functional style of Scala.

    Experienced Scala developers are probably familiar with these concepts, so the intended audience is developers who are relatively new to Scala and insights into why Scala code is the way it is.

    As I was developing the tests, I needed to programatically find buttons on a toolbar that do such things as "New", "Save", "Load", and "Execute". These buttons don't have text, so I am using the tooltip to distinguish them. A Java-style Scala function to do this would look like this:



def findJButton(container:Container, toolTipText:String) = {
val button = JButtonOperator.findJButton(container,
new ComponentChooser() {
override def checkComponent(component:Component) =
if (component.getClass.isAssignableFrom(classOf[JButton])) {
component.asInstanceOf[JButton].getToolTipText == toolTipText
} else {
false
}
override def getDescription() = "JButton Finder using toolTipText"
}
)
if (button == null) {
fail("cannot find JButton with tooltip = '" + toolTipText + "'")
}
new JButtonOperator(button)
}

Client code:



val newButtonOper = findJButton(frmOper.getContentPane, "Start new Logo program" )
val runButtonOper = findJButton(frmOper.getContentPane, "Run Logo program")

    The "*Operator" classes in Jemmy wraps corresponding Swing components so that can be programatically manipulated in the same way an actual user would. The frmOper object is a Jemmy JFrameOperator that wraps the main frame of the application.

    Now in Java, one can refactor the inner class into a regular class and leverage generics so that it can match any JComponent subtype since that is where getToolTipText is first implemented. In Scala, we will do something like that and make it so that a higher order function can be passed in the do the comparison provided the component is the correct type:



class LoanChooser[A <: java.awt.Component](clazz: Class[A], filter: A => Boolean)
extends ComponentChooser
{
override def checkComponent(component:Component) =
if (component.getClass.isAssignableFrom(clazz)) {
filter(component.asInstanceOf[A])
} else {
false
}
override def getDescription() = "Generic Finder using Loan Pattern"
}

def findJButton(container:Container, filter: JButton => Boolean) = {
val button = JButtonOperator.findJButton(container,
new LoanChooser(classOf[JButton], filter))
if (button == null) {
fail("cannot find JButton with matching criteria")
}
new JButtonOperator(button)
}

Client code:



val newButtonOper = findJButton(frmOper.getContentPane, btn => btn.getToolTipText == "Start new Logo program" )
val runButtonOper = findJButton(frmOper.getContentPane, btn => btn.getToolTipText == "Run Logo program")

    The code above uses the "Loan Pattern" in Scala (http://scala.sygneca.com/patterns/loan). The client code is loaned the the JButton instance so it can be tested to see if it matches the criteria.

    Knowing the Scala implements functions as objects, I know that two identical anonymous function classes are going be created in the form "JButton => Boolean", so I create a common function that can be curried (or partially applied) with the toolTipText as the first parameter:


def toolTipTextEquals(toolTipText:String)(component:JComponent) = component.getToolTipText == toolTipText


val newButtonOper = findJButton(frmOper.getContentPane, toolTipTextEquals("Start new Logo program"))
val runButtonOper = findJButton(frmOper.getContentPane, toolTipTextEquals("Run Logo program"))

    Now is getting pretty obvious what the code is trying to accomplish. There are some repeated code patterns that can be refactored out. At this point, it probably more at matter of taste in that some will want to refactor more and others might see it an an exercise in of "Scala Golf" (http://codegolf.com/). First we can get rid of the repeated "frmOper.getContentPane" by the traditional means of temporary local variable:


val contentPane = frmOper.getContentPane
val newButtonOper = findJButton(contentPane, toolTipTextEquals("Start new Logo program"))
val runButtonOper = findJButton(contentPane, toolTipTextEquals("Run Logo program"))

Or use an implicit conversion:



implicit def jFrameOperator2Container(frmOper:JFrameOperator) = frmOper.getContentPane

val newButtonOper = findJButton(frmOper, toolTipTextEquals("Start new Logo program"))
val runButtonOper = findJButton(frmOper, toolTipTextEquals("Run Logo program"))

    The implicit conversion is invoked since the findJButton function it expecting a Container as the first param, but is provided a JFrameOperator. Since there is an implicit function in scope in the form "JFrameOperator => Container", it is used to perform the conversion implicitly.

    My Logo application currently has four buttons on a toolbar that will need to be tested and I'll need JButtonOperators for all of them:


val newButtonOper = findJButton(frmOper, toolTipTextEquals("Start new Logo program"))
val openButtonOper = findJButton(frmOper, toolTipTextEquals("Open existing .logo file"))
val saveButtonOper = findJButton(frmOper, toolTipTextEquals("Save current logo code to file"))
val runButtonOper = findJButton(frmOper, toolTipTextEquals("Run Logo program"))

Once can utilize Scala's ability to deconstruct on patterns during declarations and do this:


val List(newButtonOper, openButtonOper, saveButtonOper, runButtonOper) =
List("Start new Logo program",
"Open existing .logo file",
"Save current logo code to file",
"Run Logo program").map(toolTipText => findJButton(frmOper, toolTipTextEquals(toolTipText)))

    It is a matter of taste whether four buttons warrant such refactoring, but if the code started to look unwieldy and I didn't want to keep them in some kind of collection, I would definitely consider this approach.


    I felt this recent experience refactoring Scala code was interesting enough to blog about it and hope that readers gain some insight from this and want to use Scala more. I feel that Scala or at least a language with many of its features (like some kind of "Scala--") will play a big role on the JVM platform going forward.


Sidebar: Whither SQUIB

    I haven't done much with the SQUIB project recently. I am seeing too many drawbacks to its approach:


  • Property resolution done at runtime - If the property is misspelled or uses an invalid type the best thing to do is fail fast and throw an exception. It would be nice if such issues could be detected at compile time at least.

  • Lack of IDE support - It would be nice if a framework uses actual methods that an IDE could then provide auto-completion and the like.

  • It's just a little heavyweight: I've found that very useful things can be done in Scala with minimal code.

Tuesday, March 10, 2009

Bowling in Yet Another Language (F#)

Another day, another blog posting involving bowling and a functional programming language. This one is in F#, which is very interesting language for the .NET platform. Code:

#light

module Bowling

open System;;

let scoreToInt x =
match x with
Some y -> y
| None -> -1;;

let rec tallyScores accum x =
match x with
[] -> []
| x::rest -> let score = scoreToInt x in
if (score >= 0) then
let total = (accum + score) in total :: tallyScores total rest;
else
[];;

let rec take n l =
if (n > 0) then
match l with
x::rest -> let newn = n-1 in x :: take newn rest
| [] -> []
else
[];;

let rec reduce f ys =
if ys <> [] then
let (x, ys') = f ys in x::reduce f ys'
else [];;

let scoreFrame rolls =
match rolls with
x1::x2::x3::rest -> if (x1 = 10) then (Some (x1+x2+x3), x2::x3::rest)
else if (x1 + x2 = 10) then
(Some (x1+x2+x3), x3::rest)
else (Some(x1+x2), x3::rest)
| x1::x2::[] -> if (x1 + x2 < 10) then
(Some(x1 + x2), [])
else (None, []);
| _ -> (None, []);;

let scoreTenFrames rolls =
take 10 (reduce scoreFrame rolls);;

let tallyGame rolls =
tallyScores 0 (scoreTenFrames rolls);;

let printGame rolls =
List.iter (fun x -> Console.Write(int x); Console.Write " ") (tallyGame rolls);
Console.Write "\n";;

let assertTrue expression =
if (expression = false) then
failwith "Assertion Not True";;

assertTrue ([30;60;90;120;150;180;210;240;270;300] = tallyGame[10;10;10;10;10;10;10;10;10;10;10;10]);;
assertTrue ([30;60;90;120;150;180;210;240;270;299] = tallyGame[10;10;10;10;10;10;10;10;10;10;10;9]);;
assertTrue ([30;60;90;120;150;180;210;240;270;290] = tallyGame[10;10;10;10;10;10;10;10;10;10;10;0]);;
assertTrue ([20;50;80;110;140;170;200;230;260;290] = tallyGame[9;1;10;10;10;10;10;10;10;10;10;10;10]);;
assertTrue ([30;60;90;120;150;180;210;240;269;289] = tallyGame[10;10;10;10;10;10;10;10;10;10;9;1]);;
assertTrue ([30;60;90;120;150;180;210;240;269;288] = tallyGame[10;10;10;10;10;10;10;10;10;10;9;0]);;
assertTrue ([30;60;90;120;150;180;210;240;260;280] = tallyGame[10;10;10;10;10;10;10;10;10;10;0;10]);;
assertTrue ([20;40;70;100;130;160;190;220;250;280] = tallyGame[10;9;1;10;10;10;10;10;10;10;10;10;10]);;
assertTrue ([30;60;90;120;150;180;210;239;259;279] = tallyGame[10;10;10;10;10;10;10;10;10;9;1;10]);;
assertTrue ([30;60;90;120;150;180;210;240;260;270] = tallyGame[10;10;10;10;10;10;10;10;10;10;0;0]);;
assertTrue ([20;40;60;80;100;120;140;160;180;200] = tallyGame[10;9;1;10;9;1;10;9;1;10;9;1;10;9;1;10]);;
assertTrue ([19;38;57;76;95;114;133;152;171;190] = tallyGame[9;1;9;1;9;1;9;1;9;1;9;1;9;1;9;1;9;1;9;1;9]);;
assertTrue ([19;38;57;76;95;114;133;152;171;180] = tallyGame[9;1;9;1;9;1;9;1;9;1;9;1;9;1;9;1;9;1;9;0]);;
// Partial games
assertTrue ([] = tallyGame[]);;
assertTrue ([] = tallyGame[0]);;
assertTrue ([] = tallyGame[9]);;
assertTrue ([] = tallyGame[10]);;
assertTrue ([] = tallyGame[9;1]);;
assertTrue ([] = tallyGame[10; 9]);;
assertTrue ([] = tallyGame[10; 10]);;
assertTrue ([0] = tallyGame[0; 0]);;
assertTrue ([1] = tallyGame[0; 1]);;
assertTrue ([9] = tallyGame[8; 1]);;
assertTrue ([9] = tallyGame[9; 0]);;
assertTrue ([19] = tallyGame[9; 1; 9]);;
assertTrue ([19; 28] = tallyGame[10; 9; 0]);;
assertTrue ([19; 28] = tallyGame[9; 1; 9; 0]);;
assertTrue ([20] = tallyGame[10; 9; 1]);;
assertTrue ([30] = tallyGame[10; 10; 10]);;
assertTrue ([30; 59] = tallyGame[10; 10; 10; 9]);;
assertTrue ([30; 59; 78; 87] = tallyGame[10; 10; 10; 9; 0]);;

Note that I've included a quick and dirty assertTrue method to validate the functions.


The above example can be used via the interactive shell (fsi) like below:



C:\DotNET\src>fsi

Microsoft F# Interactive, (c) Microsoft Corporation, All Rights Reserved
F# Version 1.9.6.2, compiling for .NET Framework Version v2.0.50727

Please send bug reports to fsbugs@microsoft.com
For help type #help;;

> #load "bowling.fs";;
[Loading C:\DotNET\src\bowling.fs]

namespace FSI_0002
val scoreToInt : int option -> int
val tallyScores : int -> int option list -> int list
val take : int -> 'a list -> 'a list
val reduce : ('a list -> 'b * 'a list) -> 'a list -> 'b list
val scoreFrame : int list -> int option * int list
val scoreTenFrames : int list -> int option list
val tallyGame : int list -> int list
val printGame : int list -> unit
val assertTrue : bool -> unit

> Bowling.printGame [10; 10; 10; 10; 10; 10; 10; 10; 10; 10; 10; 10];;
30 60 90 120 150 180 210 240 270 300
val it : unit = ()
> Bowling.printGame [10; 9;1; 10; 9;0; 10; 8;2; 10; 10; 9;1; 9;1; 10];;
20 40 59 68 88 108 137 157 176 196
val it : unit = ()

Each element on the list represents a roll of the ball. The program will figure out which frames the rolls belong to. For example, "[9;1]" represents single frame consisting of a nine count followed by spare of the remaining pin. This implementation assumes valid input and is not designed to handle such things as more than 10 pins in a frame on a negative value for a roll.

Tuesday, February 10, 2009

Bowling with Clojure

Having an interest in function programming languages (notably Scala), I decided to take a look at Clojure. In a nutshell, Clojure is a LISP variant that runs on the Java Virtual Machine and hence enjoys access to all libraries therein.
One of my favorite simple code katas with functional languages is to tally up bowling scores. Previously, I had done this with Erlang and Scala. Here is a Clojure version of the same including some tests:
(defn score-frame [rolls]
(let [[x1 x2 x3] rolls]
(cond
(nil? x1) nil
(nil? x2) nil
(nil? x3) (let [sumx (+ x1 x2)]
(if (< sumx 10) (list sumx))) ; open at end of game
(= x1 10) (cons (+ x1 x2 x3) (rest rolls)) ; strike
(= (+ x1 x2) 10) (cons (+ x1 x2 x3) (nthrest rolls 2)) ; spare
(< (+ x1 x2) 10) (cons (+ x1 x2) (nthrest rolls 2)) ; open
(true) nil
)
)
)

(defn roll-reduce [remaining-rolls]
(if (not (= nil remaining-rolls))
(let [result (score-frame remaining-rolls)]
(if (not (= nil result))
(cons (first result) (roll-reduce (rest result))))))
)

(defn score-ten-frames [rolls]
(take 10 (roll-reduce rolls)))

(defn tally-game [accum scores]
(if (> (count scores) 0) (let [new_accum (+ accum (first scores))] (cons new_accum (tally-game new_accum (rest scores)))))
)

(defn score-game [rolls]
(tally-game 0 (score-ten-frames rolls))
)

(assert (= '(30 60 90 120 150 180 210 240 270 300) (score-game '(10 10 10 10 10 10 10 10 10 10 10 10))))
(assert (= '(30 60 90 120 150 180 210 240 270 299) (score-game '(10 10 10 10 10 10 10 10 10 10 10 9))))
(assert (= '(30 60 90 120 150 180 210 240 270 290) (score-game '(10 10 10 10 10 10 10 10 10 10 10 0))))
(assert (= '(20 50 80 110 140 170 200 230 260 290) (score-game '(9 1 10 10 10 10 10 10 10 10 10 10 10))))
(assert (= '(30 60 90 120 150 180 210 240 269 289) (score-game '(10 10 10 10 10 10 10 10 10 10 9 1))))
(assert (= '(30 60 90 120 150 180 210 240 269 288) (score-game '(10 10 10 10 10 10 10 10 10 10 9 0))))
(assert (= '(30 60 90 120 150 180 210 240 260 280) (score-game '(10 10 10 10 10 10 10 10 10 10 0 10))))
(assert (= '(20 40 70 100 130 160 190 220 250 280) (score-game '(10 9 1 10 10 10 10 10 10 10 10 10 10))))
(assert (= '(30 60 90 120 150 180 210 239 259 279) (score-game '(10 10 10 10 10 10 10 10 10 9 1 10))))
(assert (= '(30 60 90 120 150 180 210 240 260 270) (score-game '(10 10 10 10 10 10 10 10 10 10 0 0))))
(assert (= '(20 40 60 80 100 120 140 160 180 200) (score-game '(10 9 1 10 9 1 10 9 1 10 9 1 10 9 1 10))))
(assert (= '(19 38 57 76 95 114 133 152 171 190) (score-game '(9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9))))
(assert (= '(19 38 57 76 95 114 133 152 171 180) (score-game '(9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 0))))
(assert (= '(0 0 0 0 0 0 0 0 0 0) (score-ten-frames '(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))))

Note this code has been updated since initial posting to incorporate changes based on feedback in a comment from Mark Volkmann below. Mark has published an excellent overview of Clojure here, http://www.ociweb.com/jnb/jnbMar2009.html


Link to code


Example:
C:\lang\clojure>java -jar clojure.jar bowling.clj
Clojure
user=> (score-game '(10 10 10 10 10 10 10 10 10 10 10 10))
(30 60 90 120 150 180 210 240 270 300)
user=> (score-game '(10 10 10 10 10 10 10 10 10 10 10 9))
(30 60 90 120 150 180 210 240 270 299)
user=> (score-game '(10 10 10 10 10 10 10 10 10 10 9 1))
(30 60 90 120 150 180 210 240 269 289)
user=> (score-game '(10 10 10 10 10 10 10 10 10 9 1 10))
(30 60 90 120 150 180 210 239 259 279)
user=> (score-game '(10 10 10 10 10 10 10 10 10 9 0))
(30 60 90 120 150 180 210 239 258 267)
user=> (score-game '(9 0 10 10 10 10 10 10 10 10 10 10 10))
(9 39 69 99 129 159 189 219 249 279)

Monday, May 19, 2008

SQUIB Google Group

I should have done this a while ago. I've created a Google Group for discussion about the SQUIB project:

http://groups.google.com/group/scala-squib

Thursday, March 6, 2008

SQUIB demos now "Web Start-able"

    

I've packaged and signed the SQUIB libraries and the Scala 2.6.1 runtime to enable demos to executed via Java Web Start. My ISP doesn't send the appropriate MIME type to launch Web Start from JNLP links. To get around this, one can save the link to a local drive and (if they have the right file associations) launch the application. Another way, would be to use the command line using "javaws <JNLP URL>". Java version 1.5 or newer is required to launch these demos.


    

The first is the Scene Graph Intro program from a prior post, http://compulsiontocode.blogspot.com/2008/02/squib-kicks-part-1.html:


http://webpages.charter.net/daltontk/scala/scala_scenegraph_intro.jnlp


    


The second is a simple picture viewer inspired by the animated rotation done by Google Picasa. Image rotation is animated and done in 90 degree increments using translucent buttons in upper left corner of the image. Use the mouse wheel to zoom in and out.


http://webpages.charter.net/daltontk/scala/scala_squib_pictureviewer.jnlp


Link to source:

http://scala-squib.googlecode.com/svn/trunk/demo/src/tfd/scala/squib/demo/scenegraph/PictureViewer.scala


Screenshot:


Sunday, February 24, 2008

SQUIB Kicks (Part 1)

    

SQUIB (Scala's Quirky User Interface Builder) has been undergoing changes in the last month. New features include support for many event types beyond the simple ActionEvent. Instead of just showing cool things done using SQUIB (That will come later), I want to take a step back and a start a little tutorial on how to use SQUIB.


    


SQUIB gets its inspiration from JavaFX, but is not intended to be a clone of it. A design goal of SQUIB is to be lightweight, but enable developers to code GUIs in a more expressive manner with less lines of code.


    


For this tutorial, I've built a release and made it available at the project home on Google Code. Please download the main library jar, http://scala-squib.googlecode.com/files/scala_squibV0_4.jar.
SQUIB requires Scala 2.6 or later. I've only done basic validation the release candidates for Scala 2.7, but it seems to be working. The examples below will be Scala scripts instead of compiled applications to simplify execution by removing the compile step.


A "Hello World" frame

import tfd.scala.squib._

import javax.swing.WindowConstants

frame(
attributes( 'title -> "Hello",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE),
contents(
label( attributes('text->"Hello World") )
)
).pack

link
    

To execute the script, save the source code to a file (for example "helloWorld.scalash") or download from the link, Ensure the scala_squibV0_4.jar is in the CLASSPATH, and invoke the script using "scala <saved source file>". Screenshot for the first example script:



    

SQUIB uses a helper object, attributes, to convert variable number of arguments into a list passed into an apply method of an object that constructs the component. Objects, frame and label, are used to construct JFrames and JLabels respectively. Originally, the apply method was overloaded to handle various combination of options. Eventually this got burdensome, so I made the apply methods use pattern matching of variable arguments and pretty much made attributes optional. For example, the code below doesn't use attributes:



import tfd.scala.squib._

import javax.swing.WindowConstants

frame(
'title -> "Hello",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
contents(
label('text->"Hello World Again!"))
).pack

link
Screenshot:

    

Attributes names are not case sensitive. Setter methods corresponding to the attributes are invoked on the event queue via invokeAndWait if the current thread is not the event dispatch thread.



 

Buttons and Events
    


The next example is a button that simply changes its text when pressed:



import tfd.scala.squib._

import java.awt.event.ActionEvent
import javax.swing.{JButton, WindowConstants}

frame(
'title -> "Press Me",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
contents(
button(
'text->"Press Me",
events (
'actionPerformed -> { ae:ActionEvent =>
ae.getSource.asInstanceOf[JButton].setText("Pressed")
}
)
)
)
).pack
link
    
Screenshots before and after button pressed:





Event names are not case sensitive using this notation. Like attributes, events is also pretty much optional as well. However, there is one tiny risk. The pattern matcher could mistake an attribute for an event if by small chance, there is an attribute that is a Scala Function1[EventObject] object. This could not happen with an existing Swing component, but only with a custom component developed in Scala. The risk is very small and can be avoided by using events. Here is the equivalent code sans events:



import tfd.scala.squib._

import java.awt.event.ActionEvent
import javax.swing.{JButton, WindowConstants}

frame(
'title -> "Press Me",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
contents(
button(
'text->"Press Me",
'actionPerformed -> { ae:ActionEvent =>
ae.getSource.asInstanceOf[JButton].setText("Pressed")
}
)
)
).pack

link
    

Since there are a finite number of events within Swing, SQUIB has helper/factory objects for the most common events. Being the name of an object, the event specification does become case sensitive. Here an a example that uses such a helper for the actionPerformed event which is in the tfd.scala.squib.event package:



import tfd.scala.squib._
import tfd.scala.squib.event._

import java.awt.event.ActionEvent
import javax.swing.{JButton, WindowConstants}

frame(
'title -> "Press Me",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
contents(
button(
'text->"Press Me",
actionPerformed { ae:ActionEvent =>
ae.getSource.asInstanceOf[JButton].setText("Pressed")
}
)
)
).pack

link
    

The actionPerformed object makes the code a little tidier. Another benefit is that they can be passed a block without the EventObject parameter for those cases where the event handler is not "interested" in it. Example:


import tfd.scala.squib._
import tfd.scala.squib.event._

import javax.swing.{JButton, WindowConstants}

var clickCount = 0;

def clickCountText = "ClickCount: " + clickCount

frame(
'title -> "Press Me",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
'layout->gridlayout('rows->2, 'columns->1),
contents(
button('text->"Press Me",
actionPerformed {
clickCount += 1
label.id("clickCount").setText(clickCountText)
}
),
label("clickCount", 'text->clickCountText)
)
).pack

link
Screenshot:

    

Removing the unneeded "ActionEvent:ae => " cleans up the code even more. This example also introduces the concept of component ids. Every SQUIB component object (like "label", "button" or "frame") keeps a map of components indexed by a String. The "id" method takes a String and returns the last component of that type constructed with that id (if there is any). Each component type has a different map, so different component types can have the same id. In the previous example, looking up the id during every event is not optimal, since the event handlers are lexically scoped closures, that can access values or variables within the current scope requiring the lookup to be performed only once. Example:



import tfd.scala.squib._
import tfd.scala.squib.event._

import javax.swing.{JButton, WindowConstants}

var clickCount = 0;

def clickCountText = "ClickCount: " + clickCount

lazy val clickCountLabel = label.id("clickCount")

frame(
'title -> "Press Me",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
'layout->gridlayout('rows->2, 'columns->1),
contents(
button('text->"Press Me",
actionPerformed {
clickCount += 1
clickCountLabel.setText(clickCountText)
}
),
label("clickCount", 'text->clickCountText)
)
).pack

link
    

Since the label id is not set until after the block declaration, the val needs to be "lazy", so that it is not instantiated until the first time it is needed in the button event handler. Use of "lazy" is very helpful for SQUIB applications, because of situations like this.


    


If one doesn't want to use an id, the component itself can be referenced by a value and used:



import tfd.scala.squib._
import tfd.scala.squib.event._

import javax.swing.{JButton, WindowConstants}

var clickCount = 0;

def clickCountText = "ClickCount: " + clickCount

val clickCountLabel = label("clickCount", 'text->clickCountText)

frame(
'title -> "Press Me",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
'layout->gridlayout('rows->2, 'columns->1),
contents(
button('text->"Press Me",
actionPerformed {
clickCount += 1
clickCountLabel.setText(clickCountText)
}
),
clickCountLabel
)
).pack

link
    

Here is one last example of a button with mouse event handlers:



import tfd.scala.squib._
import tfd.scala.squib.event._

import javax.swing.{JButton, WindowConstants}

val defaultButtonText = "Please Press Me"

lazy val pressMeButton = button.id("pressMe")

frame(
'title -> "Press Me",
'visible -> true,
'defaultCloseOperation -> WindowConstants.DISPOSE_ON_CLOSE,
contents(
button("pressMe",
'text->defaultButtonText,
actionPerformed {
pressMeButton.setText("Ouch !!!!")
},
mouseEntered {
pressMeButton.setText("Don't Press Me")
},
mouseExited {
pressMeButton.setText(defaultButtonText)
}
)
)
).pack

link
Screenshots before and after the mouse entered the component:





    

Currently, not all events are supported by SQUIB. For example, HierarchyEvent is not supported. In the those cases, one would have to implement a HierarchyListener in the same manner as Java. Supported events are ActionEvent, ChangeEvent, ComponentEvent, FocusEvent, InternalFrameEvent, ItemEvent, KeyEvent, ListSelectionEvent, MouseEvent, MouseWheelEvent, and WindowEvent.


    

This should pretty good starting point for how to use the SQUIB library. There will be future posts that will demonstrate other aspects including layouts and the SceneGraph library.


"Cool Thing" for this post
    

I've created a version of a SceneGraph demo named "Intro". The Java version is demonstrated here, https://scenegraph-demos.dev.java.net/demos.html.
There is an also a link to the source on the demo page.
The Scala source can be viewed here, http://scala-squib.googlecode.com/svn/trunk/demo/src/tfd/scala/squib/demo/scenegraph/Intro.scala.
To execute the demo, one needs the scala_squibV0_4.jar linked above and the jar with SQUIB SceneGraph support downloaded from http://scala-squib.googlecode.com/files/scala_squib_scenegraphV0_4.jar. Also, required is the archive containing the SceneGraph library itself available here, http://download.java.net/javadesktop/scenario/releases/0.5/Scenario-0.5.jar , and a jar containing the compiled application and resources downloaded from
http://scala-squib.googlecode.com/files/scala_scenegraph_intro.jar. Execute via:

scala -cp scala_squibV0_4.jar;scala_squib_scenegraphV0_4.jar;Scenario-0.5.jar;scala_scenegraph_intro.jar tfd.scala.squib.demo.scenegraph.Intro

Screenshot of the SQUIB application which is pretty much identical to the Java version on the SceneGraph demos page:

    

I'll let readers draw their own conclusions comparing the Scala/SQUIB code with the Java code for the application.


    

My own experience with Scala was that at first, I found the syntax kind of "wonky", but once I got used to it, I found it to be well thought out and even intuitive. Features like type inference and first class function objects are sorely missed at times when developing in Java at work.

Wednesday, January 16, 2008

SQUIB on Google Code

    

I am happy to announce that Scala's Quirky User Interface Builder (SQUIB) is now a Google Code project. The project home page is http://code.google.com/p/scala-squib/.
    A Subversion client is required to get the latest source from the trunk. To check out a copy of the current source use:

svn checkout http://scala-squib.googlecode.com/svn/trunk/ scala-squib-read-only
If you don't have a Subversion client, a snapshot from 1/16/08 is made available here . Of course using a Subversion client allows one to stay in sync with updates to the project via "svn update".


    

Requirements for SQUIB is Scala version 2.6 or later and a JRE version 1.5 or later. I've only used 1.6 for SQUIB, but there shouldn't be any problem using 1.5. Please let me know if there are issues. To build the project, Ant 1.6 or later is required.


    

There is a README.txt file on the root directory of the project that gives a quick run down of ant tasks available and demo programs currently in the project.


    

Currently, the scaladoc API documentation is rather thin, so in the near term, I will focused on getting more API documentation in the source code, so re-run "ant doc" after any Subversion update for updated documentation.


Please feel free to provide me feedback about this library. This is my first open source project that I've owned. What I have so far is just a start and there is lot of functionality yet to be added. I will be looking for new members for the project in the near future.