Scala's swing API has undergone a few changes since Scala 2.7. Besides the obvious bugfixes, we added more wrappers, a more comprehensive component caching mechanism, and revised the window base hierarchy. I'll go into details of the most important changes in 2.8 below, assuming you are familiar with Java Swing and the basics of scala.swing.
Window hierarchyThis is how the Java AWT/Swing window hierarchy looks like:
Dialogshare the common base class Window, the Swing extensions have no subclass relationship or extend a common interface even though they share common functionality not present in AWT. In an attempt to straighten things up a little, we redesigned the window base hierarchy in scala.swing which now looks as follows:
I've put the corresponding peer type next to each class. Note the Mixin trait, which extracts common functionality of the Swing window classes. As of now, it contains the following methods:
def getJMenuBar: JMenuBar
def setJMenuBar(b: JMenuBar)
def setUndecorated(b: Boolean)
def setTitle(s: String)
def getTitle: String
def setResizable(b: Boolean)
def isResizable: Boolean
This design lets us factor common wrapper code into class
DialogsThe Dialog companion object in 2.8 wraps message utilities from
javax.swing.JOptionPane. Together with named and default arguments, dialog code becomes much more readable. Consider the following snippet from the Java Swing tutorials which opens a multiple choice dialog:
val options = Array[Object]("Yes, please", "No, thanks", "No eggs, no ham!")
"Would you like some green eggs to go with that ham?",
"A Silly Question",
It is a little tedious to remember the order and the meaning of those arguments. In particular, what is the title and what is the question message above? What is the parameter we set to
null? In scala.swing, you can now write
message = "Would you like some green eggs to go with that ham?",
title = "A Silly Question",
messageType = Dialog.Message.Question,
optionType = Dialog.Options.YesNoCancel,
entries = Seq("Yes, please", "No, thanks", "No eggs, no ham!"),
initial = 2)
Now it's clear what's the title and what's the message. The
nullargument (which specifies the icon BTW) does not appear since the
iconparameter is set to
nullby default. Note that named arguments are not only more descriptive but their order at call site is also arbitrary .
EventsScala swing components publish key events now. As usual, the event classes are a straightforward adaption of the Java classes and can all be found in package scala.swing.event. Another important feature we were missing in 2.7 was the possibility to consume input events. Fortunately, that has been fixed now. Moreover, ticket #1442 taught us that some Swing components behave differently depending on whether certain listeners are installed or not. Therefore, scala.swing publishers install their Swing/AWT listeners lazily now. This means we install Java listeners only when a scala.swing reaction is added to the corresponding publisher and uninstall listeners when the reaction is removed. Consequently, we are little more efficient but more importantly avoid to spuriously trigger side-effects that Swing relies on in the first place (this is one of the few places where we actually do save additional state - the reactions - and by not rigorously synchronizing the wrapper's with the peer's state we got what we paid for).
Wrapper cachesThe following is a change under the hood which most users won't notice but for those who are interested, here are a few details.
As I said earlier, we can get the peer of every wrapper. The other way works as well. Every peer is associated with a wrapper if scala.swing created one already and as long as it hasn't been reclaimed by the garbage collector. This backward association is established by a wrapper cache. So why do we cache wrappers in the first place? Note that duplicate wrappers for the same Java Swing component are not a problem for equality, since
scala.swing.UIElementsare instances of
scala.Proxyand hence redirect
hashCodeto their peer. The main reason we do cache components is that we don't have control over every creation site. Consider a scroll panel for an example. It internally creates a set of scroll bars that clients can obtain a handle to. In contrast to wrappers created by user code, we have no control over the creation of those components. Since we don't want to create a new wrapper every time the client obtains a handle, we store them in a cache of weak references (we might wanna use soft references in the future). Because of subtyping, there can be wrappers of different types for the same Java Swing component. Right now, we create wrappers based on the static type of a Java Swing component, which in some situations is not very useful. We might change that to a more dynamic approach in the future, that's why the caching API is still subject to change.