Why doesn't scala swing app exit when main frame is closed? - swing

In the following minimal Scala Swing app, if I choose File->Exit (which calls sys.exit), or File->Quit (which calls SimpleSwingApplication.quit) then the application exits. If I choose File->Close (which calls Frame.close) then the window closes but the application doesn't exit (e.g. when run using java -jar myexample.jar it doesn't return to the prompt -- I have to ^C to kill it).
import scala.swing._
import swing.event.{Key,WindowClosed,WindowClosing}
object GUI extends SimpleSwingApplication {
def top = new Frame {
import javax.swing.WindowConstants.EXIT_ON_CLOSE
peer.setDefaultCloseOperation(EXIT_ON_CLOSE)
menuBar = new MenuBar {
contents += new Menu("File") {
mnemonic = Key.F
contents += new MenuItem(Action("Exit") { sys.exit(0) }) { mnemonic = Key.X }
contents += new MenuItem(Action("Close") { close() }) { mnemonic = Key.C }
contents += new MenuItem(Action("Quit") { quit }) { mnemonic = Key.Q }
}
}
}
}
I've tried a few different things, all with the same behavior:
use MainFrame instead of Frame
add a handler to reactions that matches WindowClosing and call sys.exit from there
added a reaction to both a Frame subclass (not shown here) and
to the SimpleSwingApplication
EXIT_ON_CLOSE seemed like it should do the job, but whether it's there or not doesn't matter
set default close operation to DISPOSE_ON_CLOSE
override Frame.closeOperation to call sys.exit, but this doesn't seem to get called at all (I added a print there and it doesn't result in any output)
Inspired by this answer, if I change the File->Close handler to call dispose instead of close, the app exits. The docs for MainFrame say:
Shuts down the framework and quits the application when closed.
Which made me think that close should work.
Why do I have to call dispose instead of close here? Why doesn't EXIT_ON_CLOSE or overriding the closeOperation work?

The method you're calling on swing.Window is:
def close() { peer setVisible false }
which obviously isn't what you might assume.

Alternatively, you can use GUI.quit().

Related

How do you share a variable between scripts using the MovieClip variable?

I'm currently trying to code an interactive timeline for my Uni project (keep in mind im a new coder) and we go over basic actionscript stuff. I was taught to communicate between scripts using a movieclip variable and declaring this.parent.
I have 3 scripts, one that controls the button that is used to move forward in the timeline, one is main, and the other controls the text box which displays the timeline. I placed a number variable in main, initialised at 0(timeCount). In the button script, i have it linked to main using refToMain, my movieclip variable. Within the button script, if the user clicks on the button, it rises the number variable from main using refToMain(refToMain.timeCount). It was my ambition to have the text box script track the number and each number has a different bit of the timeline on. However, when I trace timeCount in the button script, the number seems fine and raises accordingly, however it doesnt change the number in any other script. How can I fix this using basic as3 code?
In Main:
var timeCount:Number = 0;
In Button:
public function mDown (mDown:MouseEvent){
refToMain.timeCount += 1;
if(refToMain.timeCount >= 10){
refToMain.timeCount = 10;
}
trace(refToMain.timeCount);
In timeline:
if(refToMain.timeCount == 0){
timelineText.text = "welcome"
}
if(refToMain.timeCount == 1){
timelineText.text = "hello"
}
Are you expecting the code in your timeline to run continuously instead of just once? A frame script will only run once each time the timeline reaches that frame. And if you only have one frame, the timeline won't advance at all. If that's the case, a simple fix would be to add another frame to your timeline with F5, and then your timeline will alternate between your two frames forever so that your script on frame 1 will execute every other frame.
A better option would be to call the script that updates the timeline text directly every time the button is clicked. So you would move the code from your timeline script to your button script like this:
public function mDown (mDown:MouseEvent) {
refToMain.timeCount += 1;
if(refToMain.timeCount >= 10) {
refToMain.timeCount = 10;
}
trace(refToMain.timeCount);
if(refToMain.timeCount == 0) {
MovieClip(root).timelineText.text = "welcome";
}
if(refToMain.timeCount == 1) {
MovieClip(root).timelineText.text = "hello";
}
}
There are several ways and approaches to access objects and variables across your application.
1) Traversing. The (probably) older and the most straightforward one is fully understanding and controlling the display list tree. If you understand where your current script is and where your target script is, you just traverse this tree with root to go straight to the top, parent to go level up and getChildByName or [] or dot notation to go level down.
Pros: it's simple. Contras: The weak point of this approach is its inflexibility. Once you change the structure of display list tree, the access would presumably be broken. Also, this way you might not be able to access things that are not on the display list. Also, there are cases the dot notation would not work, and there are cases getChildByName would not work. Not that simple, after all.
2) Bubbling events. These are events that bubble from the depths of display list to the root. Mouse events are bubbling: you can catch it anywhere from the deepest object that had some mouse event then all its parents right up to the stage. You can read about them here. So, you can send bubbles from whatever depth you want then intercept them at the any parent of the event target:
// *** TextEvent.as class file *** //
package
{
import flash.events.Event;
public class TextEvent extends Event
{
static public const TEXT_EVENT:String = "text_event";
public var text:String;
// Although it is not a very good practice to leave the basic Event
// parameters out of it, but it will do for this example.
public function TextEvent(value:String)
{
// Set type = "text_event" and bubbles = true.
super(TEXT_EVENT, true);
text = value;
}
}
}
// *** Button script *** //
import TextEvent;
// Dispatch the event.
dispatchEvent(new TextEvent("welcome"));
// *** Main timeline *** //
import TextEvent;
// Subscribe to catch events.
addEventListener(TextEvent.TEXT_EVENT, onText);
function onText(e:TextEvent):void
{
// Extract the passed text value.
timelineText.text = e.text;
}
Pros: it is good in an app architecture terms. Contras: you cannot catch the bubbling event at the point that is not parent of event source.
3) Static class members. Or singleton pattern, its basically the same. You can devise a class that shares certain values and references over the whole application:
// *** SharedData.as class file *** //
package
{
import flash.display.MovieClip;
public class SharedData
{
static public var MainTimeline:MovieClip;
}
}
// *** Main timeline *** //
import SharedData;
// Make root accessible from anywhere.
SharedData.MainTimeline = this;
// *** Button script *** //
import SharedData;
// You can access main timeline via shared reference.
SharedData.MainTimeline.timelineText.text = "welcome";
Pros: you are not limited by display list structure any more, you can also share non-visual instances this way, anything. Contras: careful with timelines, they tend to destroy and create timeline instances as playhead moves, so it is not impossible to end up with a reference to a removed object while timeline holds a new instance that is no longer shared.

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.

Handle swing ScrollBar events in scala

I am trying to add a ScrollBar. The ScrollBar will iterate through the documents displayed. However, I am having trouble receiving an event when the scroll bar changes. I'm not sure what I need to listen to, and I'm not sure what event I should be responding to. I tried the following and I get some events, but I don't think these are the ideal events to handle.
listenTo(scrollBar)
listenTo(scrollBar.keys)
listenTo(scrollBar.mouse.moves)
listenTo(scrollBar.mouse.wheel)
listenTo(scrollBar.mouse.clicks)
For example, I only get MouseClicked, MousePressed, and MouseReleased when I click inside the scrollbar--not when I click on the arrows to actually change the value.
I found this discussion about scroll bars not receiving events properly, but it's two years old. As far as I can tell, the author did not follow up an file a ticket. Maybe he found a workaround.
Any ideas?
Good question. Clicking on arrows isn't handled by ScrollBar, it's handled by ScrollBarUI. I believe that default implementation(or at least base class for majority of ScrollBarUI implementations) is BasicScrollBarUI.
If you'll take a look into source of javax.swing.plaf.basic.BasicScrollBarUI it has incrButton and decrButton buttons and they are components you want listen to.
P.S. I had similar need for having custom (key) listener for my Slider and having custom ui which exposes needed components/model(as you could see almost all components are protected so you easy could access them in subclasses and expose via public getters) worked great for me. I did that in plain java though, maybe in scala you can listen to buttons just by specyfying property name.
One more piece of Scala Swing being broken. The Adjustable trait seems to be completely hollow, nothing is wired.
The following works:
class ScrollBarAlive extends swing.ScrollBar {
me =>
peer.addAdjustmentListener(new java.awt.event.AdjustmentListener {
def adjustmentValueChanged(e: java.awt.event.AdjustmentEvent) {
publish(new swing.event.ValueChanged(me))
}
})
}
Test:
import swing._
object ScrollBarTest extends SimpleSwingApplication {
lazy val top = new Frame {
val label = new Label { text = "0" }
val scroll = new ScrollBarAlive {
orientation = Orientation.Horizontal
listenTo(this)
reactions += {
case event.ValueChanged(_) =>
label.text = value.toString + (if (valueIsAjusting) " A" else "")
}
}
contents = new BorderPanel {
add(label, BorderPanel.Position.North)
add(scroll, BorderPanel.Position.South)
}
pack().centerOnScreen()
open()
}
}
A proper implementation would also introduce a subtype of AdjustingEvent.

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.

Responding to key events in scala

I'm experimenting with a bit of Scala gui programming (my first project in scala, so I thought I'd start with something simple). But I seem to have got stuck at something that seems like it should be relatively trivial. I have a class that extends scala.swing.MainFrame, and I'd like to detect when a user presses a key when that window has focus. Funny thing is I don't seem to be able to find any way to get that event to fire.
I found an example of how someone else had got around the problem here: http://houseofmirrors.googlecode.com/svn/trunk/src/src/main/scala/HouseGui.scala but they seem to have reverted to using the Java Swing API, which is a little disappointing. Does anyone know if there's a more idiomatic way of intercepting events?
This seems to work with Scala 2.9
package fi.harjum.swing
import scala.swing._
import scala.swing.event._
import java.awt.event._
object KeyEventTest extends SimpleSwingApplication {
def top = new MainFrame {
val label = new Label {
text = "No click yet"
}
contents = new BoxPanel(Orientation.Vertical) {
contents += label
border = Swing.EmptyBorder(30,30,10,10)
listenTo(keys)
reactions += {
case KeyPressed(_, Key.Space, _, _) =>
label.text = "Space is down"
case KeyReleased(_, Key.Space, _, _) =>
label.text = "Space is up"
}
focusable = true
requestFocus
}
}
}
In addition to listening to this.keys you should also call requestFocus on the component or set focusable=true, if it is Panel or derived class.
I expect you need to listen to this.keys (where this is the element of the GUI receiving the keyboard events). See the equivalent question about mouse event.
My solution for this required me to do the following:
class MyFrame extends MainFrame {
this.peer.addKeyListener(new KeyListener() {
def keyPressed(e:KeyEvent) {
println("key pressed")
}
def keyReleased(e:KeyEvent) {
println("key released")
}
def keyTyped(e:KeyEvent) {
println("key typed")
}
})
}
This only seemed to work though if there were no button objects attached to this component, or any of it's children.
Rather than falling back to java events all components have keys that publishes these events (so MainFrame does not). Not sure what the best solution is but it's always possible to wrap everything in the frame inside a Component and listen to its keys.