Scala Swing Box Subscribes Multiple Events - swing

I'm writing a GUI in Scala, and I've run across a strange problem while trying to register Button Events in a foreach statement: It should be that for every element objecti in a list of objects (object0 ... objectn), a corresponding Button x = buttoni is retrieved and a given Box is subscribed to it with box.listenTo(x). When the Button is pushed, some action related to the objecti should be performed (in this case, println("Event triggered: " + event)):
import scala.swing.ComboBox
import scala.collection.mutable.Buffer
import scala.swing.Button
import scala.swing.event.ButtonClicked
import scala.swing.Action
import scala.swing.SimpleSwingApplication
import scala.swing.MainFrame
import scala.swing.GridPanel
import scala.swing.BorderPanel
object EventSet extends SimpleSwingApplication {
object PhoneKeyEvent extends Enumeration {
val Key1 = Value("1")
val Key2 = Value("2")
}
/* Constants */
private val DisplayHistory = Buffer[String]()
private val KeypadKeyEvents = List(
PhoneKeyEvent.Key1, PhoneKeyEvent.Key2)
private val PhoneKeyEventButtonNames = Map(
PhoneKeyEvent.Key1 -> "1",
PhoneKeyEvent.Key2 -> "2"
)
/* End constants */
private var PhoneKeyEventButtons = Map[PhoneKeyEvent.Value, Button]()
private def createDisplay() : ComboBox[String] = {
new ComboBox(DisplayHistory) {
// Listen to keypad keys
// Get the set of all keypad key events
val keypadEvents = List(PhoneKeyEvent.Key1, PhoneKeyEvent.Key2)
println("keypadEvents: " + keypadEvents)
keypadEvents.foreach({ event =>
println("event: " + event)
// Listen to each button representing a keypad key event
var keypadEventButton = PhoneKeyEventButtons(event)
println("keypadEventButton: " + keypadEventButton)
listenTo(keypadEventButton)
reactions += {
case ButtonClicked(keypadEventButton) => {
// TODO: fix strange bug here: adds all possible inputs
println("Event triggered: " + event)
// selection.item = selection.item + event
}
}
})
}
}
private def createPhoneControllerPanel() : BorderPanel = {
new BorderPanel() {
val keypadControlPanel = createPhoneKeyEventTypeControlPanel(KeypadKeyEvents)
add(keypadControlPanel, BorderPanel.Position.Center)
add(createDisplay(), BorderPanel.Position.North)
focusable = true
requestFocus
}
}
/**
* Creates a new {#link Button} for a given {#link PhoneKeyEvent} and adds
* the button to the global map of such buttons to their respective events;
* that means multiple buttons cannot be created for the same key event.
*/
private def createPhoneKeyEventButton(phoneKeyEvent: PhoneKeyEvent.Value) : Button = {
// Only one button can be created per key event
require(!PhoneKeyEventButtons.contains(phoneKeyEvent),
{System.err.println("A Button for the PhoneKeyEvent " + phoneKeyEvent + "has already been created.")})
val keyEventButtonName = PhoneKeyEventButtonNames(phoneKeyEvent)
val result = new Button(Action(keyEventButtonName) {
println("Key event button pressed: " + phoneKeyEvent)
})
// Add the button to the map of all created key event buttons
PhoneKeyEventButtons += phoneKeyEvent -> result
return result
}
private def createPhoneKeyEventTypeControlPanel(keyEvents : Iterable[PhoneKeyEvent.Value]) : GridPanel = {
new GridPanel(4, 3) {
// Get the intersection of all key events of the given type and the events with button names
keyEvents.foreach(phoneKeyEvent => contents += createPhoneKeyEventButton(phoneKeyEvent))
}
}
override def top = new MainFrame {
contents = createPhoneControllerPanel()
}
}
However, I get some very strange behaviour, where clicking any Button results in all such object actions are triggered -- See the program output:
keypadEvents: List(1, 2)
event: 1
keypadEventButton: scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=
javax.swing.plaf.BorderUIResource$CompoundBorderUIResource#7633f09,flags=296,maximumSize=,minimumSize=,preferredSize=,de
faultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=2,left=14,bottom=2,right=14]
,paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,te
xt=1,defaultCapable=true]
event: 2
keypadEventButton: scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=
javax.swing.plaf.BorderUIResource$CompoundBorderUIResource#7633f09,flags=296,maximumSize=,minimumSize=,preferredSize=,de
faultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=2,left=14,bottom=2,right=14]
,paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,te
xt=2,defaultCapable=true]
Key event button pressed: 1
Event triggered: 1
Event triggered: 2
Key event button pressed: 2
Event triggered: 1
Event triggered: 2
I'm completely at a loss as to why this is happening; I'm quite new at Scala anyway, so it's quite unfamiliar territory, but I've tried fiddling with a lot of stuff and snooped around in the Swing source code, and still clueless... how can every value of a reference inside of a loop be used in every iteration? or how can every event be triggered by Swing at once? or...?
Edit: Here are two minimised versions, both of which behave differently:
import scala.swing.SimpleSwingApplication
object ButtonEvents extends SimpleSwingApplication {
import scala.swing.Button
import scala.swing.event.ButtonClicked
import scala.swing.Action
import scala.swing.MainFrame
import scala.swing.FlowPanel
override def top = new MainFrame {
contents = new FlowPanel {
val button1 = new Button(Action("1") {
println("Button 1 pressed")
})
contents += button1
val button2 = new Button(Action("2") {
println("Button 2 pressed")
})
contents += button2
val buttons = List(button1, button2)
buttons.foreach({ button =>
listenTo(button)
reactions += {
case ButtonClicked(button) => {
println("Event triggered: " + button.text)
}
}
})
}
}
}
Prints:
Button 1 pressed
Event triggered: 1
Event triggered: 1
Button 2 pressed
Event triggered: 2
Event triggered: 2
And a version which seems to behave correctly (but I'm not sure why):
import scala.swing.SimpleSwingApplication
object ButtonEvents extends SimpleSwingApplication {
import scala.swing.Button
import scala.swing.event.ButtonClicked
import scala.swing.Action
import scala.swing.MainFrame
import scala.swing.FlowPanel
override def top = new MainFrame {
contents = new FlowPanel {
val button1 = new Button(Action("1") {
println("Button 1 pressed")
})
contents += button1
val button2 = new Button(Action("2") {
println("Button 2 pressed")
})
contents += button2
val buttons = Map("1" -> button1, "2" -> button2)
buttons.foreach({ eventButton =>
listenTo(eventButton._2)
reactions += {
case ButtonClicked(eventButton._2) => {
println("Event triggered: " + eventButton._1)
}
}
})
}
}
}
Prints (correct):
Button 1 pressed
Event triggered: 1
Button 2 pressed
Event triggered: 2

In the line
reactions += {
case ButtonClicked(keypadEventButton) => {
you are creating a new val keypadEventButtonand assigning it to whatever is inside ButtonClicked(). Changing the line to case ButtonClicked(abstractButton) will still work and display same problem.
I'm guessing you are expecting this to match the use of keypadEventButton on the preceding lines. You probably want to create one reaction and then use the abstractButton to tell what button has been pressed.

#andy is correct. A good IDE like IDEA will highlight "suspicious shadowing by a variable pattern", since you're binding a new variable in the pattern match. Scala allows you to shadow variables as much as you want, within nested code blocks, for instance:
scala> val a = 1; {val a = 2; println(a)}; println(a)
2
1
a: Int = 1
So what does the following return?
val a = 1
2 match {
case a => "it was 1"
case _ => "something else"
}
It returns "it was 1" because a is shadowed. Now try:
2 match {
case `a` => "it was 1"
case _ => "something else"
}
This returns "something else" because we used backticks to refer to the value of he previously defined variable. (Also try this where the variable begins with a capital letter...)
So you just need to add backticks, i.e.
case ButtonClicked(`button`) => {

Related

Right Click on a Button / Scala

I'm currently trying to code a minesweeper using scala, but I can't find the way to listen to a right click on a button.
I've already searched on the Internet the way to do it, but I definitely was not able to find it.
If anyone could help me out, I would be really grateful :)
Thanks,
Schnipp
(Note: Scala is a new language to me and I am not a Java user, so I am sorry if my questions sound dumb)
EDIT:
I am trying to find (or implement) a function 'ButtonClickedRight' that could listen to a right-click on a button.
like this
import scala.swing._
import scala._
import scala.swing.event._
object Right extends MainFrame with App {
title = ""
visible = true
val b = new button("")
listenTo(b)
reactions += {
case ButtonClicked(`b`) => *code*
case ButtonClickedRight(`b`) => *code*
}
}
EDIT 2 --
I would like to know if the user has clicked on the Button "1" or not. The problem I have is that this code prints "Mouse clicked at " + e.point+" type "+e.modifiers when I click on the label but not on the button.
object App extends SimpleSwingApplication {
lazy val ui = new GridPanel(2,1) {
contents += new Button("1")
contents += new Label("2")
listenTo(mouse.clicks)
reactions += {
case e: MouseClicked =>
println("Mouse clicked at " + e.point+" type "+e.modifiers)
}
}
def top = new MainFrame {
contents = ui
visible = true
preferredSize = new Dimension(500,500)
}
}
Button events are fired through a specific publisher .mouse.clicks.
import scala.swing._
import scala.swing.event._
object App extends SimpleSwingApplication {
lazy val ui = new GridPanel(2,1) {
val button = new Button("1")
contents += button
contents += new Label("2")
listenTo(button.mouse.clicks) // !
reactions += {
case evt # MouseClicked(`button`, pt, _, _, _) =>
val which = evt.peer.getButton
if (which > 1) {
println(s"Mouse clicked at (${pt.x}; ${pt.y}) - button: $which")
}
}
}
lazy val top = new MainFrame {
contents = ui
size = new Dimension(500,500)
}
}
Note that at least on Linux my right button has number 3 not 2. You could also use the triggersPopup flag, but then you must ensure to monitor both MousePressed and MouseReleased, as this flag is platform-dependent.
I think that you are on the right path, for my understanding of scala swings I think that the problem is that you are not attaching the listener correctly. For one I would assign the button to a value and call listenTo only on it:
val button = new Button("1")
listenTo(button)
Then, in the reactions, I would write the pattern checking in the event that it comes from the button (probably redundant if you only call listenTo passing the button) and that it has the correct button:
case ButtonClicked(b) if b == button && b.peer.getButton == MouseEvent.BUTTON_2 => ...
So the code you provided in your edit would become:
object App extends SimpleSwingApplication {
lazy val ui = new GridPanel(2,1) {
val button = new Button("1")
contents += button
contents += new Label("2")
listenTo(button)
reactions += {
case evt # MouseClicked(b, pt, _, _, _) if b == button && evt.peer.getButton == java.awt.event.MouseEvent.BUTTON2 =>
println(s"Mouse clicked at (${pt.x}; ${pt.y}) - button: ${evt.peer.getButton}")
}
}
def top = new MainFrame {
contents = ui
visible = true
preferredSize = new Dimension(500,500)
}
}
The following works for me:
new Button {
listenTo(mouse.clicks)
reactions += {
case MouseClicked(_, _, c, _, _) => handleClick(c == 0)
}
}
def handleClick(isLeftClick: Boolean): Unit = {
//
}

Listeners and reactions in Scala Swing

I've done a fair bit of searching and some trial and error in eclipse, but there seems to be a gap in my understanding of listeners and reactions when writing a GUI in Scala using Swing.
Does each listener get a reactions block, or do I register listeners on all components that might generate an event and react to each on in a large reactions block with case statements?
Where exactly do the listeners and reaction blocks belong.
Here's an abbreviated version of my GUI code:
import scala.swing._
import scala.swing.event.ButtonClicked
import scala.swing.event.KeyTyped
import scala.swing.event.KeyPressed
object HumanGUI extends SimpleGUIApplication {
val basicPane = new java.awt.Dimension(800, 200)
val botPane = new java.awt.Dimension(400, 200)
val felt = new java.awt.Color(35, 125, 35)
def top = new MainFrame {
title = "Blackjack GUI"
val ConnectionPanel = new BoxPanel(Orientation.Vertical) {
background = felt
preferredSize = new java.awt.Dimension(155, 90)
minimumSize = preferredSize
maximumSize = preferredSize
val ipAddressLabel = new Label("House IP:")
ipAddressLabel.foreground = java.awt.Color.WHITE
ipAddressLabel.horizontalTextPosition = scala.swing.Alignment.Left
val portLabel = new Label("House port:")
portLabel.foreground = java.awt.Color.WHITE
portLabel.horizontalTextPosition = scala.swing.Alignment.Left
val ipAddressTextField = new TextField
val portTextField = new TextField
contents += ipAddressLabel
contents += ipAddressTextField
contents += portLabel
contents += portTextField
}
val DetailPanel = new BoxPanel(Orientation.Vertical) {
background = felt
preferredSize = new java.awt.Dimension(100, 160)
minimumSize = preferredSize
maximumSize = preferredSize
val nameLabel = new Label("Your name:")
nameLabel.foreground = java.awt.Color.WHITE
nameLabel.horizontalTextPosition = scala.swing.Alignment.Left
val bankrollLabel = new Label("Bankroll:")
bankrollLabel.foreground = java.awt.Color.WHITE
bankrollLabel.horizontalTextPosition = scala.swing.Alignment.Left
val betLabel = new Label("Bet:")
betLabel.foreground = java.awt.Color.WHITE
betLabel.horizontalTextPosition = scala.swing.Alignment.Left
val nameTextField = new TextField
val bankrollTextField = new TextField
val betTextField = new TextField
val goButton = new Button("Go!")
contents += nameLabel
contents += nameTextField
contents += bankrollLabel
contents += bankrollTextField
contents += betLabel
contents += betTextField
contents += goButton
}
val PlayPanel = new BoxPanel(Orientation.Vertical) {
background = felt
val hitButton = new Button("Hit")
val stayButton = new Button("Stay")
val doubleButton = new Button("Double")
val quitButton = new Button("Quit")
contents += hitButton
contents += stayButton
contents += doubleButton
contents += quitButton
}
val playerPanel = new BoxPanel(Orientation.Horizontal) {
background = felt
border = new javax.swing.border.LineBorder(java.awt.Color.WHITE)
preferredSize = basicPane
minimumSize = basicPane
maximumSize = basicPane
opaque = true
contents += ConnectionPanel
contents += DetailPanel
contents += PlayPanel
}
contents = new BoxPanel(Orientation.Vertical) {
contents += playerPanel
}
}
}
So the question is where do I put my listeners and reaction blocks?
I want to react to the buttons in PlayPanel, and the text fields in both ConnectionPanel and DetailPanel.
Do I put the listeners and reaction blocks as close to the elements that I'm interested as possible, or do I put a big block of listeners and reactions at the end of the MainFrame section?
Does it even matter?
EDIT
I've made significant progress and have much of what I need working, along with a better understanding of the concepts I wasn't getting before.
This excerpt from Odersky's "Programming in Scala" was what helped me the most. Specifically, the example from this page:
http://www.artima.com/pins1ed/gui-programming.html
The code is from the first edition of the text, so I question whether or not there's a better way in Scala 2.9, but it was clear an concise and summed up what I was misunderstanding.
From the example, which is a simple fahrenheit to celsius converter, I came to understand that the listener and the reactions blocks belongs after the contents block for the MainFrame.
so I ended up with:
object HumanGUI extends SimpleSwingGUIApplication {
def top = new MainFrame {
title = "My Blackjack GUI"
//The fields I want to work with are instantiated as object
object ipAddressTextField extends TextField { columns = 15 }
object portNumberTextField extends TextField {columns = 5 }
//other panels, objects, etc would go here
val OtherPanel = new BoxPanel(Orientation.Horizontal) {
label = "Other Panel"
}
//and here we have the contents += block for the mainframe, other panels, etc from
//above would be added to the main frame here
contents = new BoxPanel(Orientation.Vertical) {
contents += ipAddressTextField
contents += portNumberTextField
}
//here's the listen to, listening on the object created above, and it's enclosed in
//in backticks, a good explanation of that is found in the link below
listenTo(`ipAddressTextField`)
reactions += {
case EditDone('ipAddressTextField`) =>
//do something!
}
}
Need clarification on Scala literal identifiers (backticks)
So it seems that the answer to my question is that the listenTo and reactions blocks belong in the MainFrame block, but should appear after it's contents += { //contents } block.
Additional trial and error in eclipse shows that while this solution works for me, there is clearly much more that I don't understand. For example, while I was unable to get listeners for KeyPress events to work if I tried to listen and react to them in within the
val OtherPanel = new BoxPanel(Orientation.Horizontal) { }
portion of the above code, I was able to get a button registered and working like this:
val OtherPanel = new BoxPanel(Orientation.Horizontal) {
val betLabel = new Label("Bet:")
val betTextField = new TextField
val goButton = new Button("Go!")
listenTo(goButton)
reactions += {
case ButtonClicked(b) =>
betTextField.text = "Go!"
}
contents += betLabel
contents += betTextField
contents += goButton
}
Why this worked but my attempts to do something along the lines of
val OtherPanel = new BoxPanel(Orientation.Horizontal) {
val betLabel = new Label("Bet:")
val betTextField = new TextField
val goButton = new Button("Go!")
listenTo(betTextField)
reactions += {
case KeyTyped(betTextField, Enter, _, _) => {
println("Caught enter")
}
contents += betLabel
contents += betTextField
contents += goButton
}
didn't work is still baffling me. I'm assuming that it should work and I'm just doing something wrong. Perhaps that melding that approach with a case EditDone instead of a case KeyTyped(,,,) would have worked but I'm a little too burnt out right now to follow up on that.
I haven't accepted an answer yet because I'm hoping that someone who sees this can clarify the points I still don't understand. Should that not happen and the question remain unanswered for a few days I will likely accept #som-snytt's answer as his code was very helpful.
Swing is educational, and Scala-Swing is educational. Especially if the course is "History of Swing: The Rise and Fall."
My first Scala program also used Swing. I've forgotten the details, but I'll share what I can see in the source.
Apparently, I had a main UI component called LightBox that handled some UI events, and also a mediator component LightBoxMediator that coordinated.
The interesting part would be, using cake pattern for composition, and moving business logic (or game logic) interaction into a component that "mediates" for the UI proper. The LightBox publishes events, too.
So the answer to your question would be: exploit the publisher framework, but distinguish UI events from application events. (This little game also had actor-based controllers.)
Maybe this suffices to illustrate the separation of concerns:
/**
* Draws the House of Mirrors.
* The LightBox is just a list of rays (line segments) and gates (various objects).
* The UI emits requests to move and rotate gates.
*/
class LightBox extends Panel {
this.peer.addComponentListener(
new ComponentAdapter {
override def componentResized(e: ComponentEvent) {
if (e.getID == ComponentEvent.COMPONENT_RESIZED && e.getComponent == LightBox.this.peer) {
calculateScale()
}
}
}
)
listenTo(mouse.clicks, mouse.moves, mouse.wheel, keys)
reactions += {
case KeyPressed(_, Key.N, _, _) => highlightNextMoveableGate()
case KeyPressed(_, Key.P, _, _) => highlightPreviousMoveableGate()
case e: MousePressed => startDrag(e)
case e: MouseDragged => doDrag(e)
case e: MouseReleased => endDrag(e)
case e: MouseWheelMoved => wheeling(e)
case _ => null // println ("Unreacted event")
}
and the mediator
trait ViewComponents {
this: ControllerComponents with ModelComponents =>
val lightBoxMediator: LightBoxMediator
val statusBarMediator: StatusBarMediator
val statusIconMediator: StatusIconMediator
val applicationMediator: ApplicationMediator
/**
* Handles update notifications from the application
* and user input from the LightBox.
*/
class LightBoxMediator(val ui: LightBox) extends Reactor with Observing {
/** Attempt to track our selection across updates: the point is where the gate should end up. */
private var selectionContinuity: (Option[Gate], Option[Point]) = (None, None)
listenTo(ui, ui.keys, ui.mouse.clicks)
reactions += {
case KeyPressed(_, Key.Q, _, _) => sys.exit()
case KeyPressed(_, Key.Space, _, _) => rotateSelectedGate()
case KeyPressed(_, Key.Enter, _, _) => rotateOtherwiseSelectedGate()
case KeyPressed(_, Key.Up, _, _) => moveUp()
case KeyPressed(_, Key.Down, _, _) => moveDown()
case KeyPressed(_, Key.Left, _, _) => moveLeft()
case KeyPressed(_, Key.Right, _, _) => moveRight()
case KeyPressed(_, Key.PageUp, _, _) => previousLevel()
case KeyPressed(_, Key.PageDown, _, _) => nextLevel()
case DragEvent(from, to) => handleDrag(from, to)
case ClickEvent(where, button) => handleClick(where, button)
//case x => println("Unreacted event " + x)
}
observe(controller.modelEvents) { e => e match {
case LevelLoaded(v) => onLevelLoaded(v)
case TraceResult(s) => onTrace(s)
case unknown => println("Lightbox mediator ignored: "+ unknown)
}
true
}
Just noticed the additional questions. By coincidence, I was cleaning up old code, actually a tiny app to grab images from sfgate.com (which stopped working when they changed the site, of course; but usually you can right-click-save now), and I happened to notice the following comment about resubscribing. I vaguely remember the bit about UIElement being a LazyPublisher, because I remember the head slap. But if I hadn't written the meager comment, that info would have been lost to ancient history.
I think somebody wants to support scala-swing and will probably take care of the head slaps.
package com.maqicode.sfg.jfc
import java.awt.Color
import java.awt.Color.{WHITE => White, RED => Red}
import java.net.{URI, URISyntaxException}
import javax.swing._
import swing.TextField
import swing.event.{EditDone, MouseEntered, ValueChanged}
import com.maqicode.sfg.BadGateURLException
import com.maqicode.sfg.GateUrlTranslator.translate
abstract class URIField extends TextField {
reactions += {
case e: EditDone => editDone(e)
case other: ValueChanged => editing(other)
case m: MouseEntered => onMouseEntered()
case _ => null
}
// necessary to resubscribe this so that onFirstSubscribe registers ActionListener
listenTo(this, mouse.moves)
def onMouseEntered() {
val t: Option[String] = ClipboardInput.contents
if (t.isDefined && t.get != this.text) {
this.text = t.get
submitURL(t.get)
}
}
def editing(e: ValueChanged) {
clearError()
}
def editDone(e: EditDone) {
submitURL(this.text)
}
def submitURL(s: String) {
val u = s.trim
if (!u.isEmpty)
try {
submitURI(translate(new URI(u)))
clearError()
} catch {
case t: BadGateURLException => flagError()
case t: URISyntaxException => flagError()
}
}
def flagError() {
colorCode(Red)
}
def clearError() {
colorCode(White)
}
private def colorCode(c: Color) {
if (this.background != c) this.background = c
}
def submitURI(uri: URI): Unit
}

Scala Swing ListView's Remove / Add Elements Events

With reference to this,
How do I change the contents of a ListView in Scala?
I could change ListView contents by changing listData. However, I couldn't get ListView to publish these events, ListElementsAdded, ListElementsAdded and ListChanged. From the looks of ListView source, it would only adds a listener to a read-only empty model. How do I go about this?
Thanks
Later on, I managed to figure out a way to have ListView published these events., please refer to the code.
Is the right to go about it? Is there a better way to do this? Please advise.
Thanks
** code borrowed and modified **
object ListViewTest extends SimpleSwingApplication
{
lazy val top = new MainFrame
{
title = "ListView Test"
contents = new BoxPanel(Orientation.Vertical)
{
border = Swing.EmptyBorder(2, 2, 2, 2)
val listModel = new DefaultListModel
List("First", "Second", "Third", "Fourth", "Fifth").map(listModel.addElement(_))
val myList = ListBuffer()
val listView = new ListView[String](myList)
{
selection.intervalMode = ListView.IntervalMode.Single
peer.setModel(listModel)
//listData = myList
}
listView.peer.getModel.addListDataListener(new ListDataListener {
def contentsChanged(e: ListDataEvent) { publish(ListChanged(listView)) }
def intervalRemoved(e: ListDataEvent) { publish(ListElementsRemoved(listView, e.getIndex0 to e.getIndex1)) }
def intervalAdded(e: ListDataEvent) { publish(ListElementsAdded(listView, e.getIndex0 to e.getIndex1)) }
})
contents += new ScrollPane(listView)
val label = new Label("No selection")
contents += label
val b = new Button("Remove")
contents += b
listenTo(listView.selection, listView, b)
reactions +=
{
case ListSelectionChanged(list, range, live) =>
label.text = "Selection: " + range
case e: ButtonClicked =>
if (listView.listData.isEmpty)
{
b.enabled = false
}
else
{
listView.peer.getModel.asInstanceOf[DefaultListModel].remove(listView.selection.anchorIndex)
}
case ListElementsRemoved(source, range) =>
println("Element at " + (range.start + 1) + " is removed.")
}
}
pack
}

scala swing: draggable / resizable component trait

I'm looking for a scala trait that I can mix in to a scala.swing.Component that will allow that component to be positioned and resized using mouse input.
Ideally it would add little boxes as "handles" to indicate to the user that the component can be resized:
I feel like this is a fairly common task, and there should be some traits out there that support it.
I'm using these in my current project. You probably need to replace the Vector library with your own and add implicit defs. Or use Point/Dimension from swing. The Components need to be in a panel which allows custom positions and sizes, like NullPanel from http://dgronau.wordpress.com/2010/08/28/eine-frage-des-layouts/
trait Movable extends Component{
var dragstart:Vec2i = null
listenTo(mouse.clicks, mouse.moves)
reactions += {
case e:MouseDragged =>
if( dragstart != null )
peer.setLocation(location - dragstart + e.point)
case e:MousePressed =>
this match {
case component:Resizable =>
if( component.resizestart == null )
dragstart = e.point
case _ =>
dragstart = e.point
}
case e:MouseReleased =>
dragstart = null
}
}
trait Resizable extends Component{
var resizestart:Vec2i = null
var oldsize = Vec2i(0)
def resized(delta:Vec2i) {}
listenTo(mouse.clicks, mouse.moves)
reactions += {
case e:MouseDragged =>
if( resizestart != null ){
val delta = e.point - resizestart
peer.setSize(max(oldsize + delta, minimumSize))
oldsize += delta
resizestart += delta
resized(delta)
revalidate
}
case e:MousePressed =>
if( size.width - e.point.x < 15
&& size.height - e.point.y < 15 ){
resizestart = e.point
oldsize = size
this match {
case component:Movable =>
component.dragstart = null
case _ =>
}
}
case e:MouseReleased =>
resizestart = null
}
}
The only Component I know of that is Dragable and resizeable is the InternalFrame on the JDesktop. here is an example:
import swing._
import event._
import javax.swing.{UIManager,JComponent}
import javax.swing.KeyStroke.getKeyStroke
import java.awt.{Graphics2D,Graphics}
object InternalFrameDemo extends SimpleSwingApplication{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName)
val top = new MainFrame{
title = "InternalFrameDemo"
preferredSize = new Dimension(640,480)
val desktop = new javax.swing.JDesktopPane
val jc = new JComponent{
override def paint(g:Graphics){
import g._
drawLine(0,0,20,20)
drawLine(0,20,20,0)
println("yay draw")
}
setVisible(true)
}
desktop add jc
contents = Component.wrap(desktop)
menuBar = new MenuBar{
contents += new Menu("Document"){
mnemonic = Key.D
contents += new MenuItem("New"){
mnemonic = Key.N
action = new Action("new"){
def apply = createFrame
accelerator = Some(getKeyStroke("alt N"))
}
}
contents += new MenuItem("Quit"){
mnemonic = Key.Q
action = new Action("quit"){
def apply(){
quit()
}
accelerator = Some(getKeyStroke("alt Q"))
}
}
}
}
def createFrame{
val newFrame = MyInternalFrame()
newFrame setVisible true
desktop add newFrame
newFrame setSelected true
}
}
}
import javax.swing.{JDesktopPane,JInternalFrame}
import collection.mutable.ArrayBuffer
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)
jframe //Component.wrap(jframe)
}
}
but both JInternalFrame and JDesktop are not integrated in the scala swing package and need to be wrapped manually.

scala event swing :: chicken or egg

how can I handle a situation, where a filed listens to a buttons that is not declared yet?
val detail = new BoxPanel(Orientation.Vertical){
listenTo(button)
}
val seznam = new BoxPanel(Orientation.Vertical){
val button = new Button("But"){
reactions += {
case ButtonClicked(_) =>
detail.contents.clear
detail.contents += new Label("Anystring")
}
}
I can't declare seznam first either, because it reference the field detail. So how can I write this?
listenTo is a public method. The easiest thing to do, therefore, is to create them as you've shown above, but add detail.listenTo(button) after you've created the button:
val detail = new BoxPanel(Orientation.Vertical){ }
val seznam:BoxPanel = new BoxPanel(Orientation.Vertical){
val button = new Button("But"){
reactions += {
case ButtonClicked(_) =>
detail.contents.clear
detail.contents += new Label("Anystring")
}
}
detail.listenTo(button)
}