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

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

Related

Enumerating class properties

I'm trying to iterate through the properties of a custom class, however, the methods provided by Adobe appear to not work. I'm not receiving compile or run-time errors.
Class
package {
public dynamic class enum {
public var foo:Number = 123;
public function enum() {
this.setPropertyIsEnumerable("foo", true);
if (this.propertyIsEnumerable("foo") == false) {
trace("foo:" + foo + " is not enumerable.")
}
}
}
}
// outputs "foo:bar is not enumerable."
Implementaiton
var test:enum = new enum();
for (var property:String in test) {
trace(property);
}
// outputs nothing
I try to keep my code fast and flexible, so it's really frustrating when you must change the class to Dynamic just to be able to use for ... in on the properties. Jackson Dunstan's testing confirms that this can be 400x slower than static class properties, but those have to be explicitly referenced (impractical for property agnostic methods), or use reflection of the class (computationally expensive) to be accessible.
The only way I've found to sidestep the whole issue is to use dynamically declared variables... which is pointless since at that point using setPropertyIsEnumerable(prop, true) is superfluous; all dynamically created properties already are enumerable. Additionally, dynamic variables cannot be strongly datatyped, and performance goes out the window.
For example...
Class
package {
public dynamic class enum {
public var foo:String = "apple";
public function enum(){
this.dynamicVar = "orange";
this.dynamicProp = "banana";
this.setPropertyIsEnumerable("foo", true);
this.setPropertyIsEnumerable("dynamicProp", false);
}
}
}
Implementation
var test:enum = new enum();
for (var key:String in test) {
trace(key + ": " + test[key]); // dynamicVar: 1
}
// outputs "dynamicVar: orange"
Now that the class is dynamic, we see that only one of our 3 test properties are being iterated. There should be 2.
It almost feels like Adobe wants us to adopt bad programming habits. Words fail me...
Non-dynamic classes do not provide enumerable properties or methods.
As stated in the description of the link you provided.
Sets the availability of a dynamic property for loop operations.
I think you might want to refactor your code on this approach.
I have never had to loop over a classes properties like you are doing here.
If you want to track items dynamically you should use an associative array and track them that way not at the class level like you are doing.
And if you want strong data typing then use a vector.

Adding an object reference to a component from the properties window

Is there a way to pass an object reference to a component directly from the property/component parameter window? Using the [Inspectible] tag only allows me to input strings and not actual object references.
For example, I have a custom component called "knob" which should hold a reference to a door on the stage which it opens. I know this can be easily done in code with "knob.door = someDoor;" but since there are many objects in the scene I would prefer if I could do it visually trough the properties window.
I don't think you can do this. Your best bet is to pass in a string identifier (perhaps a whole dot-separated path if your clips are deeply nested), and then implement code inside your custom component to find that item by name.
I've got a custom component which lays itself out relative to horizontal and vertical predecessor components, so I do this:
protected var horizontalPredecessor:String = "";
[Inspectable(name = "HorizontalPredecessor", type = String, defaultValue="")]
public function set HorizontalPredecessor($value:String):void
{
horizontalPredecessor = $value;
drawNow();
}
override protected function draw():void
{
if (parent)
{
if (horizontalPredecessor != "")
{
var hp:DisplayObject = parent.getChildByName(horizontalPredecessor);
if (hp)
{
x = hp.y + hp.height + horizontalSpacing;
}
}
}
}
... which is made easy because all these components share the same parent.
Alternatively, if there's only one door, you could make it a singleton, and give it a static reference, like this:
public class Door
{
private static var _singleton:Door;
public static function get Singleton():Door
{
if(!_door) _door = new Door();
return _door;
}
}
Then your handle can just refer to Door.Singleton and you don't have to worry about passing anything in. Alternatively, you could have a static array of Doors in the Door class, and give your handle an index number to link it to a specific Door.

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.

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 {

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.