JInternalFrame with scala.swing - 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.

Related

Scala SimpleSwingApplication not repainting

I have a simple swing application in Scala. Work is done by a separate object but progress must be periodically reported to the GUI which launched it. The problem is that the updates are only visible once the Thread has completed its task. I've tried adding various calls to repaint() top.peer.repaint() and Thread.sleep(0) all to no avail and all the while feeling that the fact that I'm resorting to adding these is a sign I'm doing something wrong.
I remember struggling and overcoming this issue with this back when I used to develop in Java and have tried to structure my solution based on what I recall being the right approach but I must be missing something.
Here is a simple example which reproduces the problem:
import scala.swing._
import scala.swing.event.ButtonClicked
import BorderPanel.Position._
import java.awt.EventQueue
class HeavyLifter extends Runnable {
override def run = {
UpdateInterface.say("Performing Useful Work")
for (i <- 0 until Int.MaxValue) {
UpdateInterface.say(i.toString)
}
}
}
object UpdateInterface extends SimpleSwingApplication {
private val txtLog = new TextArea(32,64) {
editable = false
}
private val scrollPane = new ScrollPane(txtLog)
private val btnGo = new Button("Go")
def say(strWhat : String) = {
txtLog.append(strWhat + "\n")
}
def top = new MainFrame {
contents = new BorderPanel {
listenTo(btnGo)
reactions += {
case ButtonClicked(`btnGo`) => EventQueue.invokeLater(new HeavyLifter)
}
layout(scrollPane) = Center
layout(btnGo) = South
}
}
}
Update I've accepted the answer which drew attention to the cause of the problem. I stupidly thought that since the worker was a Runnable that EventQueue.invokeLater would spawn a separate thread for it. I have been pointed in the direction of Swing Worker which is probably the right way to go.
The EventQueue.invokeLater cause the loop to run in the dispatch thread (i.e the UI thread), which basically means that the UI thread is occupied to do the for loop and once that is done then it will update the UI and perform other tasks, after all a thread can do a single thing at a time. This is the reason your UI gets updated after the loop is completed.
What you can do is, run the worker runnable in a new thread and from that worker code dispatch a new runnable, for each update, (whose run method updated the text area value) using EventQueue.invokeLater.

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) }
}
}
}

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

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.

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)

Scala applets - SimpleApplet demo

The ScalaDoc for the applet class is pretty thin on details on how you actually override the ui piece and add components. It says "Clients should implement the ui field. See the SimpleApplet demo for an example."
Where is this SimpleApplet demo?
Barring that, does anyone have some simple source code of using the Scala Applet class, rather than the JApplet class directly?
Thanks
The more recent ScalaDoc may be slightly more helpful (in particular, the new version of ScalaDoc allows you to show/hide concrete members so you can focus on what you must implement).
It should be noted that you don't have to define an object named ui that extends UI. What the ScalaDoc says is both more accurate and more flexible -- "implement the ui field". Because of the Uniform Access Principle, you're free to implement the ui field as a val or an object (similarly, you can use a val or var to implement a def). The only constraints (as reflected in the ScalaDoc as val ui : UI) are that
the ui has to be a UI, and
the reference to the ui has to be immutable
For example:
class MainApplet extends Applet {
val ui = new MainUI(Color.WHITE)
class MainUI(backgroundColor: Color) extends UI {
val mainPanel = new BoxPanel(Orientation.Vertical) {
// different sort of swing components
contents.append(new Button("HI"))
}
mainPanel.background = backgroundColor // no need for ugly _=
contents = mainPanel
def init(): Unit = {}
}
}
Finally found some source that shows what you need to do:
http://scala-forum.org/read.php?4,701,701
import swing._
import java.awt.Color
class MainApplet extends Applet {
object ui extends UI {
val mainPanel = new BoxPanel(Orientation.Vertical) {
// different sort of swing components
contents.append(new Button("HI"))
}
mainPanel.background = Color.WHITE
contents = mainPanel
def init():Unit = {}
}
}
In other words you define an object named ui that extends UI. I never would have thought of that. That ScalaDoc needs some serious work.