In my previous post, http://compulsiontocode.blogspot.com/2007/10/gui-building-with-scala.html, I introduced a library for building Swing interfaces in Scala that I've been creating. This post will show more features of the library and demonstrate them by building a rudimentary calculator.
Based on feedback received, features have been added since the previous post, so a new version of library is made available here
along with source for the calculator.
Ensure the jar above is on the CLASSPATH, compile and run the example program:
Here is a screenshot of the calculator:
Summary of the GuiBuilderCalc.scala source:
The first piece of noteworthy code is below:
frame.attributes_default = attributes('defaultcloseoperation->JFrame.EXIT_ON_CLOSE)
button.attributesDefault = attributes('foreground->Color.blue)
button.attributesByPattern += ("\\d" -> attributes('foreground->Color.red))The "attributesDefault" property of the component objects stores attribute mappings to be applied to any instance of the component thereafter. The first two lines above ensure that all frames created thereafter exit on close and all buttons created thereafter have blue text.
The "attributesByPattern" property stores a mapping of strings to attribute mappings that are applied to components whose id match the regular expression in the string key. It's kind of a "pseudo CSS". The last line above specifies that any button whose id is a single digit should have red text. The "attributesByPattern" are applied after the "attributesDefault" and therefore have precedence.
The next thing done in the code is create the textfield:
val calcField = textfield(
'text->"0"
,'horizontalalignment->SwingConstants.RIGHT
,'editable->false
,'background->Color.white
)
Based on feedback from the last post, I made it optional to explicitly specify "attributes" when the attributes are the last parameter. The code above creates a JTextField without a string id, with specified attributes, and assigns it to value "calcField".
Below the panel containing the calculator buttons is created:
val gridPanel = panel(
gridlayout('rows->5, 'columns->4, 'vgap->4, 'hgap->2)
,contents(
button("clear" ,'text->"C" , 'foreground->Color.black)
,button("clear_erase",'text->"CE", 'foreground->Color.black)
,new JLabel("")
,button("/","/")
,button("7","7")
,button("8","8")
,button("9","9")
,button("*","*")
,button("4","4")
,button("5","5")
,button("6","6")
,button("-","-")
,button("1","1")
,button("2","2")
,button("3","3")
,button("+","+")
,button("0","0")
,button("negate","+/-")
,button(".",".")
,button("=","=")
)
)
I've added factory methods for the button component to cover the more common of component construction scenarios. For example, button("+","+") creates a button with string id of "+" and "+" for the text. This can also be expressed as button("+", 'text->"+") or even button("+", attributes('text->"+")).
The new JLabel("") part shows that you can create Swing components using the normal constructors and include them. This allows custom components to be built into the interface. Components included this way can not have string ids nor do "attributesDefault" or "attributesByPattern" get applied to them.
The textfield and buttons are assembled together in a panel:
val frm = frame(
attributes(
'title->"GUIBuilder Calculator"
)
,borderlayout()
,contents(
panel(
borderlayout()
,contents(
calcField -> "North"
,gridPanel -> "Center"
)
) -> "Center"
)
)
Here components in the contents need constraints due to the use of borderlayout. In Scala, the expression "foo -> bar" is simply a tuple so (calcField, "North") and (gridPanel, "Center") could also have been used.
Finally, button handlers are added.
var stored = 0.0
var operation = ' '
var newInput = true
// Iterate and attach event handlers for the number buttons
for (val num<-Iterator.range(0, 10)) {
button.onAction("" + num,(ae: ActionEvent) => {
val prepend = if (newInput) "" else calcField.getText
calcField.setText(prepend + num)
newInput = false
})
}
// Attach an event handling to clear the sum text field
button.onAction("clear", (ae: ActionEvent) => {
calcField.setText("0")
newInput = true
})
button.onAction("clear_erase", (ae: ActionEvent) => {
calcField.setText("0")
stored = 0.0
newInput = true
})
button.onAction("negate", (ae: ActionEvent) => {
calcField.setText("" + (-calcField.getText().toDouble))
})
button.onAction(".", (ae: ActionEvent) => {
if (newInput) {
calcField.setText("0.")
newInput = false;
} else {
val text = calcField.getText()
if (!text.contains('.')) {
calcField.setText(text + ".")
}
}
})
def doOperation(oper:Char) {
operation = oper
stored = calcField.getText().toDouble
newInput = true
}
button.onAction("+", (ae: ActionEvent) => {
doOperation('+')
})
button.onAction("-", (ae: ActionEvent) => {
doOperation('-')
})
button.onAction("*", (ae: ActionEvent) => {
doOperation('*')
})
button.onAction("/", (ae: ActionEvent) => {
doOperation('/')
})
button.onAction("=", (ae: ActionEvent) => {
val current = calcField.getText().toDouble
calcField.setText("" + (operation match {
case '+' => (stored + current)
case '*' => (stored * current)
case '-' => (stored - current)
case '/' => (stored / current)
case _ => current
}))
newInput = true
})
The library continues to grow based on feedback from others and from my own ideas. There are still features already built in to be shown in future posts. At some point, I am hoping to release the source and create a project on SourceForge or Google Code. The name "Scala GUI Builder" is not very original. Shortening "SCala User Inteface Builder" makes "SCUIB" which isn't a word. I am liking "SQUIB" for "Scala Quick User Interface Builder".
4 comments:
So Scala isn't smart out java properties?
Why do you have to do:
calcField.setText("0.")
instead of:
calcField.Text = "0."
Like you can in C#, python, ruby, and countless other languages.
Why do you have to do:
calcField.setText("0.")
instead of:
calcField.Text = "0."
You can do this in Scala. You can define accessors and mutators for an object property that syntactically appear as public fields. However, in this case, the object being called was a Java JTextField that can't implement those. Suppose, some someone had implemented a Scala "UpperCaseTextField" that automatically upshifts the text. The code can be like:
class UpperCaseTextField(text:String) extends JTextComponent {
...
private var _text = txt.toUpperCase
def Text = _text
def Text_=(txt: String) = _text = txt.toUpperCase
...
}
On
http://scala.sygneca.com/code/scalagui
there is example code for the "ScalaGUI" Swing-wrapping framework predating your post.
While your library is very appealing, the post referenced above shows how the actions are kept within the object itself. To wit:
val say = new widget.Label {
text = "I'm here for your information"
toplevel eventloop {
case press.Click() =>
text = "Hey! Button was pressed ;-)"
}
subscribe(press)
}
What are your thoughts on this approach to event/action management (including event processing within object definition to "keep it all together")?
The "ScalaGUI" example does some interesting things. One thing, I didn't like about it is that the components basically delegate to an underlying Swing component and that made it seem a little heavyweight.
My Scala GUI Builder pretty much just builds the GUI and "gets out of the way". I was just trying to create something a little simpler. Hope this makes sense.
Post a Comment