How to embed a (working) Button in a Swing Table in Scala? - swing

I'm trying to use Scala/Swing to create a Table, one of whose columns is populated by Buttons.
My starting point is the SCells spreadsheet example from Odersky et al's book, and in particular the use of rendererComponent to control the Component appearing in each cell.
Unfortunately, while this creates a button successfully, the button is not clickable. Here's a reasonably minimal and self-contained example:
import swing._
import swing.event._
class TableButtons extends ScrollPane {
viewportView = new Table(2,2) {
rowHeight = 25
override def rendererComponent(isSelected: Boolean, hasFocus: Boolean,
row: Int, column: Int): Component =
if (column == 0) {
new Label("Hello")
} else {
val b = new Button { text = "Click" }
listenTo(b)
reactions += {
case ButtonClicked(`b`) => println("Clicked")
}
b
}
}
}
object Main extends SimpleSwingApplication {
def top = new MainFrame {
title = "Table button test"
contents = new TableButtons
}
}
When I run this, I get a table with two columns; the first contains labels, the second contains buttons, but the buttons aren't clickable.
Possibly related issue: the cells (including the ones containing buttons) are editable. What's the best way to disable editing?
I've seen this question (and this one) and have tried following the approach there (using Table.AbstractRenderer) but that's also not working - and it's not at all obvious to me where to put reactions to button clicks in that version. (Is that approach outdated? Or is the approach from the Scala book too simplisitic?)
Thanks for any advice!

You can make a column ineditable by providing a custom table model. However, your cell must be editable, because that is the only way the editing component becomes 'live' (repaints state changes, receives mouse events).
In the normal rendering (using renderComponent), the component is only used to 'stamp' it, i.e. the table just calls paint on the component. Thus, performance-wise, you should re-use one instance of each rendering component, instead of creating a new Label / Button in every call.
So, you need to override the editor method. Unfortunately it returns a plain javax.swing.table.TableCellEditor, and thus you must step down to the plain javax.swing stuff and loose all the Scala goodness...
The following almost works. Strangely, the button disappears when clicking on it -- have no idea why :-(
import scala.swing._
import scala.swing.event._
import javax.swing.{AbstractCellEditor, JTable}
import javax.swing.table.TableCellEditor
import java.awt.{Component => AWTComponent}
 
class TableButtons extends ScrollPane {
private val lb = new Label("")
private val b = new Button
private val buttonEditor = new AbstractCellEditor with TableCellEditor {
listenTo(b)
reactions += {
case ButtonClicked(`b`) =>
println("Clicked")
fireEditingStopped()
}
def getCellEditorValue: AnyRef = "what value?"
// ouch, we get JTable not scala.swing.Table ...
def getTableCellEditorComponent(tab: JTable, value: AnyRef, isSelected: Boolean,
row: Int, col: Int): AWTComponent = {
b.text = "Click!"
b.peer // ouch... gotta go back to AWT
}
}
viewportView = new Table(2, 2) {
rowHeight = 25
override def rendererComponent(isSelected: Boolean, hasFocus: Boolean,
row: Int, column: Int): Component =
if (column == 0) {
lb.text = "Hello"
lb
} else {
b.text = "Click?"
b
}
override def editor(row: Int, col: Int): TableCellEditor =
if (col == 1) buttonEditor else super.editor(row, col)
}
}
 
val top = new Frame {
title = "Table button test"
contents = new TableButtons
pack()
visible = true
}
In any case, check the Oracle JTable tutorial for the intricate details of renderers and editors.

Related

Using Scala's Swing wrapper, how can I update a panel's contents via foreach with a collection?

I have a panel and a list full of filenames that reference .png files.
I can turn the string in any singe index of that list in to an image using:
val label = new Label {
icon = new ImageIcon(myList(0))
}
Then I can add that label to the panel:
object myGUI extends SimpleGUIApplication {
def top = new MainFrame {
title = "My simple GUI"
contents += label
}
}
but what if I want to construct a label for each entry in the list, and add each of those labels to myGUI?
I'd like to do so using foreach or some other idiomatic scala concept. If a different collection type makes more sense, I don't need to use a List. As I understand it, however, the collection processing functions are the same for an Array or Map as they are for a List so the collection type shouldn't matter (in the scope of this particular GUI related problem, anyway)
I'm not sure I understand correctly your question, but does this not work?:
object myGUI extends SimpleGUIApplication {
def top = new MainFrame {
title = "My simple GUI"
val list = ... //your list of filepaths
list foreach { filepath =>
contents += new Label { icon = new ImageIcon(filepath) }
}
}
}

Navigating between panels in scala

I want to know how to navigate between panels in scala swinng. the current code I have is:
val top = new MainFrame {
title = "Predator and Prey Agent simulation"
val buttonExit = new Button {
text = "Exit"
//foo
}
val buttonStart = new Button {
top.visible = false
text = "Play"
}
I want the buttonStart button to take me to another frame that I define in another class. How exactly do I implement that in scala. I get a recursive value error from what I have above.
Do you want to start a new window, or just switch the contents of the current window? If it's the latter, CardLayout is what you're looking for.
Which line in your example causes the error? I suspect it's the top.visible = false. This would be because the compiler needs to know the type of top but can't infer it because you have a reference to it in its definition. Adding a type annotation should fix this error:
val top: MainFrame = new MainFrame {

Scala swing newbie questions

This is my first experiment using Swing with Scala, and have a few questions about my code below. All it does is to produce a window with a coloured rectangle that changes colour. Please feel free to answer one or any of the questions.
1) I used a Java ActionListener below because I couldn't work out how to get javax.swing.Timer to work as a Publisher. Is there a way to use the Scala model, i.e. listenTo() - or is this the way to do it?
2) My overridden preferredSize value in the Panel doesn't seem to work: the window comes up minimized. In my Java version I override the getPreferredSize method, but there is no such method in Panel, so I assumed this is the way to do it, but why doesn't it work?
3) paintComponent isn't documented at all in the Scala API documentation. I assume this is because it is protected access in Java, but it seems like an oversight. Am I correct to override paintComponent or is it hidden because I'm supposed to use the documented paint method instead?
4) Scala doesn't seem to have getWidth() and getHeight() methods on components - is it standard to use size.width and size.height?
import swing._
import java.awt.{Graphics, Color}
import java.awt.event.{ActionEvent, ActionListener}
import javax.swing.Timer
object ColorPanel extends SimpleSwingApplication {
private var c: Color = new Color(0)
def top = new MainFrame {
title = "Flash!"
contents = p
}
val p = new Panel with ActionListener {
override val preferredSize = new Dimension(200, 200)
override def paintComponent(g: Graphics2D) {
g.setColor(c)
g.fillRect(0, 0, size.width, size.height)
}
def actionPerformed(e: ActionEvent) {
c = new Color((c.getRGB() + 1000) % 16777216)
repaint
}
}
val timer = new Timer(100, p)
timer.start()
}
No immediate answer. But your approach is certainly ok. I don't see though why your observer should be the panel. I would create an anonymous ActionListener directly with the timer, and instead add a specific method to that panel, like def animateColor() { ... }
You can use preferredSize = new Dimension(200, 200)
According to this quasi official document, yes : http://www.scala-lang.org/sid/8 (section 6 Custom Painting)
A bit stupid indeed to instantiate a new Dimension all the time. But if you look exactly at the example the SID, it does the same, uses size.height. In super high performance code, you may want to call directly into the underlying peer (peer.getWidth)

How to control scala swing listeners

This is a continuation to my last question. I used the MVC pattern with swing components and code goes like this
import scala.swing
import scala.swing.event._
case object MyBusinessEvent extends Event
class MyController extends Publisher {
val form = new MyForm
listenTo(form)
reactions += {
case MyBusinessEvent => //handle event code here
}
}
class MyForm extends Publisher {
val ui = new GridBagPanel {
val c = new Constraints
.... more code here
}
val button1 = new Button("Button 1")
//add button to panel
listenTo(button1)
reactions += {
case ButtonClicked(_) => publish(MyBusinessEvent)
}
}
However with multiple buttons the program hangs up and seems to stop publishing events. Is there any way to fix this? Thanks
Although from the truncated example it's not clear what the problem is, my guess is you are creating a cycle which unfortunately can happen quite easy with swing and MVC. that is, one model gets updated as part of listening to another model, and that again triggers an update in the other model. In java swing you have two choices,
a) temporarily remove the listener that is updating the model (e.g. do a removeActionListener before calling setSelectedItem on a JComboBox, and then afterwards re-register with addActionListener)
b) check for the event source (getSource on an java.util.EventObject) and ignore events in the model that originated from that very same model.
Now scala swing is more simplified, so you don't have event object and event sources. You can add though a source in your custom event case class. And it might be feasible to remove the reaction and re-add it after setting a model's state.
To avoid the cycles mentioned in 0__'s answer I have a trait like this:
trait Editable extends Publisher {
private var _editing = false
def editing = _editing
def editing_=(b: Boolean) = _editing = b
override def publish(e: Event) {
if(!editing) super.publish(e)
}
}
which I mix-in in the components I want to have more control about the events fired. So, when I want to change something without firing any event, I simply put the code between editing = true and editing = false.

JInternalFrame with scala.swing

scala swing looks interesting, but somehow it is incomplete, and sometimes I still need to use the old java classes, but I have no clue how I have to wrap them correctly.
So how do I wrap javax.swing.JInternalFrame correctly so that I can use it as Component in my MainFrame?
I try to get this example to work with scala and the scala swing library, and I finally managed to get an Internal Frame, but my MainFrame distorts all internal Frames and stretches them until they have exactly the same width and height as the space inside the MainFrame.
This is my current implementation:
import swing._
import event._
object InternalFrameDemo extends SimpleSwingApplication{
val top = new MainFrame{
title = "InternalFrameDemo"
preferredSize = new Dimension(640,480)
val menuNew = new MenuItem("New"){
mnemonic = Key.N
action = new Action("new"){
def apply(){
createFrame
}
}
}
val menuQuit = new MenuItem("Quit"){
mnemonic = Key.Q
action = new Action("quit"){
def apply(){
quit()
}
}
}
menuBar = new MenuBar{
contents += new Menu("Document"){
mnemonic = Key.D
contents ++= Seq(menuNew,menuQuit)
}
}
def createFrame{
val newFrame = MyInternalFrame()
newFrame.visible = true
contents = newFrame
}
}
}
object MyInternalFrame{
var openFrameCount = 0;
val xOffset, yOffset = 30;
def apply() = {
openFrameCount += 1
val jframe = new javax.swing.JInternalFrame("Document #" + openFrameCount,true,true,true,true)
jframe.setSize(300,300)
jframe.setLocation(xOffset*openFrameCount,yOffset*openFrameCount)
Component.wrap(jframe)
}
}
The following is defined on the companion of Component:
def wrap (c: JComponent): Component
So you write Component.wrap(new JInternalFrame).
I guess you do it like this:
scala> import swing.Component
import swing.Component
scala> import javax.swing.JInternalFrame
import javax.swing.JInternalFrame
scala> class InternalFrame extends Component {
| override lazy val peer = new JInternalFrame
| }
defined class InternalFrame
the problem is that you can't put an internal frame as the entire contents of MainFrame
so you need to set a JDesktopPane as the contents of MainFrame, then use the add method
to add internal frame to the JDesktopPane
add these lines in your MainFrame
val desktop = Component.wrap(new javax.swing.JDesktopPane())
contents = desktop
amend your last line of method createFrame to this:
desktop.peer.add(newFrame.peer)
this is an ugly solution. what need to be done is write a simple wrapper of JDesktopPane and JInternalFrame
or, a better solution at InteractiveMesh.org, check their ImScalaSwing API, which contain the JInternalFrame wrapper. Be sure to read some of their example code before you use it.