Saturday 8 May 2010

Scala Swing ComboBox part 2

In the previous entry here I showed how to create an application using Scala Swing that contained a ComboBox and Button.

In this post I want to expand that example by showing you how to select a ComboBox item programmatically and also listen to changes to the ComboBox.

The code below shows how to change the ComboBox selection programmatically.

import swing.Swing._
import swing._
import event.{ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo2 extends Frame {
title = "Scala Swing Demo 2"
case class OS(name:String, version:String)
val snowLeopard = OS("Snow Leopard", "10.6.3")
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), snowLeopard, OS("Windows", "7"))) {
renderer = Renderer(_.name)
}
val button = new Button {
text = "Select Snow Leopard OS"
}
contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}
listenTo(button)
reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => comboBox.selection.item = snowLeopard
}
pack
centerOnScreen
visible = true
}
object ScalaSwingDemo2Runner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo2)
}
}
This code is very similar to before so I won't bother explaining it again. The difference is what happens when the button is clicked. The code called is:

comboBox.selection.item = snowLeopard

This sets the selected item of the ComboBox to the specified OS class, in this case Snow Leopard. Notice that it is expecting a class of type OS without casting. This is statically checked and I can prove this by changing the line to:

comboBox.selection.item = "Snow Leopard"

If you try and compile this you will get a compile time error.

This is very simple but is just another example of how concise Scala can be.

The next bit of code shows how to listen to selection changes on the ComboBox.

import swing.Swing._
import swing._
import event.{SelectionChanged, ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo3 extends Frame {
title = "Scala Swing Demo 3"
case class OS(name:String, version:String)
val snowLeopard = OS("Snow Leopard", "10.6.3")
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), snowLeopard, OS("Windows", "7"))) {
renderer = Renderer(_.name)
}
val button = new Button {
text = "Select Snow Leopard OS"
}
contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}
listenTo(button)
listenTo(comboBox.selection)
reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => comboBox.selection.item = snowLeopard
case SelectionChanged(`comboBox`) => {
val selectedOS = comboBox.selection.item
println("Selected OS Name: " + selectedOS.name + " Version: " + selectedOS.version)
}
}
pack
centerOnScreen
visible = true
}
object ScalaSwingDemo3Runner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo3)
}
}
We've added another reaction to our Frame and told our Frame to listen to another publisher. We want to listen to the selection of the ComboBox so achieve this with this line:

listenTo(comboBox.selection)

When a user selects a different item in the ComboBox, the block of code after the case SelectionChanged(`comboBox`) => is run. In this case the block of code just prints out information about the selected item.

The button functionality is still available. If you click the button (when Snow Leopard is not selected), you will see that Snow Leopard is selected, and the SelectionChanged block is run. This is what you would expect. However, when selecting something programmatically, sometimes you would not want that block of code to be run. Scala Swing provides an easy way to deal with this, which the following code demonstrates.

import swing.Swing._
import swing._
import event.{SelectionChanged, ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo4 extends Frame {
title = "Scala Swing Demo 4"
case class OS(name:String, version:String)
val snowLeopard = OS("Snow Leopard", "10.6.3")
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), snowLeopard, OS("Windows", "7"))) {
renderer = Renderer(_.name)
}
val button = new Button {
text = "Select Snow Leopard OS"
}
contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}
listenTo(button)
listenTo(comboBox.selection)
reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => {
deafTo(comboBox.selection)
comboBox.selection.item = snowLeopard
listenTo(comboBox.selection)
}
case SelectionChanged(`comboBox`) => {
val selectedOS = comboBox.selection.item
println("Selected OS Name: " + selectedOS.name + " Version: " + selectedOS.version)
}
}
pack
centerOnScreen
visible = true
}
object ScalaSwingDemo4Runner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo4)
}
}
Here, when the button is clicked, before we select the snowLeopard item, we tell the frame to be deaf to the ComboBox selection with this line of code:

deafTo(comboBox.selection)

Once the item has been selected we want to listen to the selection again so call:

listenTo(comboBox.selection)

That's it for this post. I hope Java Swing developers recognise what I'm doing here and how much easier it is when using Scala Swing. Remember as well, I'm only using the standard Scala Swing library.

Monday 26 April 2010

Scala Swing ComboBox

Swing has got a bad reputation. If it's not people saying it's too slow, it's people saying it's dead.

I've never had a problem with Swing. I've always been able to make it do what I want, and always managed to make it look good in the process.

I've been working completely in Scala now for at least the last year and recently I had to knock up a GUI. I could have done this using Swing from Scala as Scala is nice and flexible like that. However, I got to like the "Scala way of doing things" so much, I thought I would have a look at the scala.swing library.

I'm glad I did. It makes Swing a joy to use as I'll show you over the next couple of posts.

One of the problems people have with Swing is that it doesn't support generics in components such as JComboBox and JList. Scala Swing does allow this. In fact, they pick up the type automatically.

import swing.Swing._
import swing._
import event.{ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo extends Frame {
title = "Scala Swing Demo"

case class OS(name:String, version:String)
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), OS("Snow Leopard", "10.6.3"), OS("Windows", "7"))) {
renderer = Renderer(_.name) // The renderer is just the name field of the OS class.
}

val button = new Button {
text = "Output Selected OS"
}

contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}

listenTo(button) // Means this frame will listen to this button.

reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => { // This is what happens when the button is clicked.
val selectedOS = comboBox.selection.item // This returns the type OS.
println("Selected OS Name: " + selectedOS.name + " Version: " + selectedOS.version)
}
}

pack
centerOnScreen
visible = true
}

object ScalaSwingDemoRunner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo)
}
}
The main method is specified in the object ScalaSwingDemoRunner. I call onEDT which is a method on the scala.swing.Scala object. This method takes a function. In this case all I want to do is create my main frame.

The whole body of the ScalaSwingDemo is the constructor in Scala. I extend the scala Frame class. This allows me to set the title with an equals sign. Note that this is actually calling a method and not setting a field.

The next thing I do is define a class called OS. In Scala, case classes provide a nice way to specify a class without all the boilerplate associated with Java. I then create a ComboBox and pass through a List of these OS classes. It's nice to be able to specify a List with a "literal". I think this is coming in JDK7 as well.

Inside the ComboBox definition I specify the renderer. In this case I use a standard Renderer object that takes a function. I know that the OS class has got the "getter method" called name. I want the result of the name method call to be displayed in my ComboBox, so I specify that there. I say "whatever gets passed into this renderer, call the name method and display the result". This is all statically typed. The ComboBox knows that it is displaying OS objects so the renderer knows that it will get something with a name method on it.

I then create a Button and specify the text of the Button in a similar way to the way I specified the title of the Frame.

Next I specify the contents of the Frame. All I'm using here is a FlowPanel, which is very similar to a JPanel with a FlowLayout set.

If I want to react to the Button I have got various options but in this case I just say the Frame should listen to it.

I then add reactions to the Frame to specify what to do when certain events I'm interested in happen. In this case it is exiting when the Frame is closing and printing out info about the selection in the ComboBox when the button is clicked.

This is the interesting bit. Where I call, comboBox.selection.item I actually get back an object of type OS. That's pretty sweet and allows me to call methods on it without casting.

Here is a screen shot of it in action:


This is a pretty simple example but I hope it shows quite a few interesting ways in which Scala Swing makes creating a GUI a joy.

Oh, and there are no performance problems!

Wednesday 2 December 2009

Radial Gradient JXLayer LockableUI

Every now and again I climb out of system programming and delve into a bit of GUI work. No matter how good the system is, if the GUI looks bad, users will think that the system is bad. I know a lot of firms don't see this as a priority but I really do. Even internal systems should look good and if you know what you are doing, Swing can be your friend.

JXLayer is a library that allows you to do various things, such as blocking input to the user interface as well drawing over components. It's a bit like the built in glass pane but a lot more powerful.

When something bad happens in my system, I have a component that I want to block all user input to as well as making it obvious to the user that something has gone wrong. JXLayer is a good choice here for blocking user input but choosing what to display was a bit more difficult. There are a few built in or easy to generate LockableUI's (see the "Getting started" section on the home page) but I wanted something different.

I wanted something that was semi transparent in the centre of the screen, fading towards the edges. It was pretty easy to accomplish this using RadialGradientPaint.

This is how a demo app (can you tell it doesn't really do anything?) looks before it is blocked:


and when I click the button to simulate that there is a problem I get this:


Here is the code that accomplishes this:

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.ext.LockableUI;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;

public class MainFrame extends JFrame {
public MainFrame() {
super("JXLayer Demo");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(1000, 800));
JPanel mainPanel = new JPanel(new FlowLayout());
for (int i = 0; i < 45; i++) {
mainPanel.add(new JTextField(20));
mainPanel.add(new JButton("Press ME"));
mainPanel.add(new JLabel("This is a label"));
}
final RadialLockableUI lockableUI = new RadialLockableUI();
final JXLayer layer = new JXLayer(mainPanel, lockableUI);
add(layer);
JButton button = new JButton("Lock / Unlock");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
lockableUI.setLocked(!lockableUI.isLocked());
}
});
add(button, BorderLayout.SOUTH);
pack();
setLocationRelativeTo(null);
setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new MainFrame();
}
});
}

private static class RadialLockableUI extends LockableUI {
@Override
protected void paintLayer(Graphics2D g2, JXLayer layer) {
super.paintLayer(g2, layer);
if (isLocked()) {
int width = layer.getWidth();
int height = layer.getHeight();
Paint origPaint = g2.getPaint();
g2.setPaint(new RadialGradientPaint(new Point2D.Double(width / 2, width / 2),
width * 0.75f,
new Point2D.Double(width / 2.0, width / 2.0),
new float[] {0.0f, 0.7f, 1.0f},
new Color[] {new Color(255,0,0,128), new Color(255,0,0,64), new Color(255,0,0,0)},
MultipleGradientPaint.CycleMethod.NO_CYCLE,
MultipleGradientPaint.ColorSpaceType.SRGB,
AffineTransform.getScaleInstance(1.0, (double)height / (double)width)));
g2.fillRect(0, 0, width, height);
g2.setPaint(origPaint);
}
}
}
}
Now say that you wanted to have some text across the middle of the screen so it looks a bit like this:


You would do it with the following code:

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.ext.LockableUI;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;

public class MainFrame2 extends JFrame {
public MainFrame2() {
super("JXLayer Demo");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(1000, 800));
JPanel mainPanel = new JPanel(new FlowLayout());
for (int i = 0; i < 45; i++) {
mainPanel.add(new JTextField(20));
mainPanel.add(new JButton("Press ME"));
mainPanel.add(new JLabel("This is a label"));
}
final RadialLockableUI lockableUI = new RadialLockableUI();
final JXLayer layer = new JXLayer(mainPanel, lockableUI);
add(layer);
JButton button = new JButton("Lock / Unlock");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
lockableUI.setLocked(!lockableUI.isLocked());
}
});
add(button, BorderLayout.SOUTH);
pack();
setLocationRelativeTo(null);
setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new MainFrame2();
}
});
}

private static class RadialLockableUI extends LockableUI {
@Override
protected void paintLayer(Graphics2D g2, JXLayer layer) {
super.paintLayer(g2, layer);
if (isLocked()) {
int width = layer.getWidth();
int height = layer.getHeight();
Paint origPaint = g2.getPaint();
g2.setPaint(new RadialGradientPaint(new Point2D.Double(width / 2, width / 2),
width * 0.75f,
new Point2D.Double(width / 2.0, width / 2.0),
new float[] {0.0f, 0.7f, 1.0f},
new Color[] {new Color(255,0,0,128), new Color(255,0,0,64), new Color(255,0,0,0)},
MultipleGradientPaint.CycleMethod.NO_CYCLE,
MultipleGradientPaint.ColorSpaceType.SRGB,
AffineTransform.getScaleInstance(1.0, (double)height / (double)width)));
g2.fillRect(0, 0, width, height);
g2.setPaint(origPaint);

g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font origFont = g2.getFont();
float textSize = 1.0f;
Font lastFont = origFont.deriveFont(textSize);
Font fontToUse = lastFont;
int textWidth = 0;
String text = "CODE DISCONNECTED";
while (textWidth < width) {
textSize += 1.0f;
fontToUse = lastFont;
lastFont = lastFont.deriveFont(textSize);
textWidth = g2.getFontMetrics(lastFont).stringWidth(text);
}

g2.setFont(fontToUse);
FontMetrics metrics = g2.getFontMetrics();
textWidth = metrics.stringWidth(text);
int x = (width - textWidth) / 2;
int y = (metrics.getAscent() + (height - (metrics.getAscent() + metrics.getDescent())) / 3);
g2.drawString(text, x, y);
}
}
}
}
Most of the additional code here is just figuring out what size the text should be. If you want the text to change over time, just extract the text component out into a field, set it and then call repaint on the JXLayer (layer in the example above). One thing that is vital to do is to call setDirty(true) (this is a protected method so has to be done in the subclass of the layer (RadialLockableUI in the example above)) before calling repaint otherwise nothing will be updated.

Although the examples above don't exactly look great, it is possible to apply this effect and get a good looking result:


JXLayer is going to be part of JDK 7 so it's good to learn how to use it now!

Monday 30 November 2009

Calling Scala Object Fields from Java

It's pretty easy to call anything in Java from Scala but sometimes it can be hard to go the other way round.

I work on a large codebase that contains both Java and Scala. All new development is done in Scala but sometimes we need to update a Java class by calling some Scala code. In most cases this is easy but sometimes I run into problems.

Say you have a Scala object like so:

object JavaCallingScalaTest {
val Constant = "Constant"
}
and you want to use the 'Constant' field in a Java file. You would do this like so:

public class JavaCallingScalaTestJavaFile {
private static final String CONSTANT = JavaCallingScalaTest$.MODULE$.Constant();
}
Now lets say the Scala field is a map like so:

object JavaCallingScalaTest {
val IntToString = Map(1 -> "one", 2 -> "two")
}
and you want to extract some information out of the map in Java, you would do something like this:

public class JavaCallingScalaTestJavaFile {
private static final String TEXT_FOR_TWO = JavaCallingScalaTest$.MODULE$.IntToString().apply(2);
}
Enjoy.

Monday 16 November 2009

Scala Map Java Map and vice versa

I use a lot of Scala at work. It's a great language but one thing I don't like about the library in the current version (2.7.*) is the way it deals with (or doesn't for that matter) converting between Scala and Java collection classes (and vice versa). This is a big problem if you use Scala in anger as you will almost certainly have to interact with Java libraries. This is really disappointing as one of Scala's strengths is the way it can easily integrate with Java in most situations.

Luckily, I don't have to bother displaying all the extra code I've had to write to get this working in 2.7.* as this issue has been addressed in the upcoming 2.8 version.

The following code shows conversions both ways between Scala and Java maps:

object CollectionTest {
import scala.collection.JavaConversions._
import scala.collection.mutable.{Map => MMap}
import java.util.{Map => JMap}

def main(args: Array[String]) {
val scalaMap = MMap(1 -> "one", 2 -> "two", 3 -> "three")
println(scalaMap)

val convertedJavaMap: JMap[Int, String] = scalaMap
println(convertedJavaMap)

val convertedScalaMap: MMap[Int, String] = convertedJavaMap
println(convertedScalaMap)
}
}
I initially define a mutable Scala map and then assign it to a new val of type java.util.Map. I then assign this new Java map to a val of type scala.collection.mutable.Map. You can see from the output that the maps have been converted.

Easy!