How can you get ScalaFX to play nice in the SBT console?
Asked Answered
C

1

7

I'm writing an image library for intro programming students to play with. (I stole the idea and the patterns from the image library for DrRacket.)

https://github.com/dupontmanualhs/dm-image

It's mostly written in Swing (that's the master branch), but I'm trying to convert it to ScalaFX (see the scalafx branch), and there are some issues. Ideally, a student should be able to do something like:

scala> import org.dupontmanual.image._
scala> TrainEngine.display()

and have a dialog box with a train engine show up. I've tried using the code at

https://github.com/scalafx/ScalaFX-Tutorials

in the stand-alone-dialog project, but if I include System.exit(0) after I dialog.showAndWait(), I get this error:

Not interrupting system thread Thread[process reaper,10,system]
Exception while removing reference: java.lang.InterruptedException
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at com.sun.glass.utils.Disposer.run(Disposer.java:69)
    at java.lang.Thread.run(Thread.java:744)
Not interrupting system thread Thread[Prism Font Disposer,10,system]
Exception in runnable
Exception in thread "JavaFX Application Thread"

(Note that I get the same error if I try to run the App from stand-alone-dialog in the console, so I'm guessing that calling System.exit(0) is not a great idea in the SBT console.)

If I leave the System.exit(0) line out, then things seem to work fine--mostly. After the first time I display the dialog, it doesn't bring the dialog into focus, so I have to click it to dismiss the dialog. But the real problem is that when I :q to exit the console, SBT hangs and I have to Ctrl-C to be able to type again. (And, yes, Ctrl-C exits SBT completely, not just the console.)

I think what I may need to do is create a thread specifically for ScalaFX stuff. For example, I have a method to stack one image on top of another, and I got an IllegalStateException when I tried to call that function, even though it doesn't actually display anything, just creates a new Group with the two previous Nodes stacked appropriately. Unfortunately, I'm not sure how to create a new thread and make sure that everything image-related runs through that.

I've already set fork := true in build.sbt, but that doesn't seem to make a difference with the console.

== Update ==

I found initialCommands and cleanupCommands in the SBT documentation and tried to clean up after everything when the console starts and ends. The values are:

initialCommands in console := """import org.dupontmanual.image._; org.dupontmanual.image.initialize()"""

cleanupCommands in console := """org.dupontmanual.image.cleanUp()"""

which are defined thusly:

package object image {
  var masterFrame: JFrame = _

  def initialize() {
    masterFrame = new JFrame()
    masterFrame.add(new JFXPanel())
    masterFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
  }

  def cleanUp() {
    println("exiting platform")
    Platform.exit()
    println("disposing of frames")
    Frame.getFrames().foreach {
      _.dispose()
    }
    println("frames all disposed")
    System.exit(0);
  }

Here's the result of running the console and then trying to quit:

> console
[info] Compiling 1 Scala source to /home/sysadmin/dm-workspace/dm-image/target/scala-2.10/classes...
[info] Starting scala interpreter...
[info] 
import org.dupontmanual.image._
Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> Hacker.display()

scala> :q
exiting platform
disposing of frames
frames all disposed
Not interrupting system thread Thread[XToolkt-Shutdown-Thread,5,system]
Not interrupting system thread Thread[AWT-XAWT,6,system]
Not interrupting system thread Thread[Prism Font Disposer,10,system]
Not interrupting system thread Thread[Java2D Disposer,10,system]
Exception while removing reference: java.lang.InterruptedException
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at com.sun.glass.utils.Disposer.run(Disposer.java:69)
    at java.lang.Thread.run(Thread.java:744)

Exception: sbt.TrapExitSecurityException thrown from the UncaughtExceptionHandler in thread "run-main-0"

and that doesn't even exit the console. You still have to use Ctrl-C, which exits out of SBT completely.

Something is still running, but I can't figure out what it is. Grrr.

Capillarity answered 10/3, 2014 at 16:43 Comment(15)
Have you tried calling scalafx.Platform.exit() instead of System.exit(0)? The former is the preferred method for terminating a ScalaFX application.Tillandsia
Whoops! That should be scalafx.application.Platform.exit()! Sorry for the confusion...Tillandsia
I still get the problem where SBT hangs after I do :q in the console.Belvia
Yeah - I just tried using it in place of System.exit(0) in the ScalaFX-Tutorials StandAloneFXDialog code and it just locks up the sbt console. According to the JavaFX version of this function (which ScalaFX calls under the hood), Note: if the application is embedded in a browser, then this method may have no effect. That could be a problem. I'll look into this some more...Tillandsia
I think the problem lies, not with Platform.exit(), but with the implementation of the FXUtils.runAndWait(...) function in the stand_alone_dialog package. In essence, it terminates the executing thread under a very limited set of conditions, so Platform.exit() ends up having no effect. I'm going to take a look at improving this. I'll place the revised code on GitHub...Tillandsia
Actually, upon further investigation, I think the problem is that this isn't really a ScalaFX/JavaFX application - it's a Swing application with some JavaFX stuff bolted into it. That probably why Platform.exit() isn't doing anything. If you look at the documentation for the JavaFX JFXPanel class - for embedding JavaFX/ScalaFX into Swing applications - there is a neat worked example to follow. I guess since it's a Swing app, you terminate the app by calling the dispose() method of the main frame. Hope this helps...Tillandsia
Any idea what you do if there is no main frame (e.g., you just want to display an image in a dialog box)? And you should turn your last two comments into an answer. If they turn out to be the way to go, they'd be check-worthy. (Unfortunately, I'm brain-dead at the moment, so I'll try it in the morning.)Belvia
Good question. I think JFXPanel creates a Swing execution thread, and I'm not sure whether there's also a separate ScalaFX/JavaFX thread as well. I guess you could try calling Platform.exit() then System.exit(0) and see if that does the trick. BTW, I'll happily turn the comments into an answer when I figure out how you should terminate your app - but thanks! It shouldn't be this hard... :-(Tillandsia
New idea... What would I have to do to create a new Scala/JavaFX Application and launch it from my Image class's display() method? I assume I'd have to run it in a new Thread or Process (because I tried just launching it without doing that and got an error).Belvia
OK. I've edited the question to reflect my latest attempts. Something seems to be starting a Thread that I can't access.Belvia
I guess you fixed it? Just checked out your project and ran the example, but no exception ;)Strawworm
Oops, ignore what I said, after typing exit I see the error now, although I see it everytime I exit the sbt console, even when not executing the TrainEngine.Strawworm
What is the reason you implemented it as a Swing app enclosing JavaFX, and not the other way around or JavaFX-only?Strawworm
One of the ways to initialize the JavaFX toolkit is to create a JFXPanel. Unfortunately, that puts you into Swing/JavaFX hybrid-ness.Belvia
@OJKrylow, the error is because the JavaFX Toolkit gets initialized when the console is open, whether or not you actually use it. A similar error does not happen if you don't initialize the toolkit.Belvia
C
0

I think the problem is that you would somehow need to fork the console, so perhaps it's this issue: https://github.com/sbt/sbt/issues/1918

The following idea seems to work: Instead of the sbt console you embed a REPL, such as Ammonite. Still sbt run doesn't work, even with fork in run := true. But packaging a fat jar and running that does seem to work:

build.sbt

name := "Foo"

version := "0.1.0"

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  "org.scala-lang.modules" %% "scala-swing"   % "1.0.2",
  "com.lihaoyi"            %  "ammonite-repl" % "0.5.1" cross CrossVersion.full
)

project/build.properties

sbt.version=0.13.9

project/plugins.sbt

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.1")

src/main/scala/foo/Main.scala

package foo

import scala.swing._

object Main extends App {
  def test(): Unit = Swing.onEDT {
    new MainFrame {
      contents = new Label("App exits if you close window")
    } .open()
  }

  ammonite.repl.Main.run("")
}

Then

$ sbt assembly
...
$ java -jar target/scala-2.11/Foo-assembly-0.1.0.jar
Loading...
Welcome to the Ammonite Repl 0.5.1
(Scala 2.11.7 Java 1.8.0_66)
@ foo.Main.test()

The only oddity is that after the application exists, the shell character echoing is broken, perhaps that's an issue of Ammonite, you might want to try with the default Scala REPL.

Cartridge answered 10/12, 2015 at 11:44 Comment(2)
Thanks for answering this. I'm hoping the SBT people will eventually get around to fixing the bug.Belvia
I've been able to get ammonite working with scalafx as an standalone project as well github.com/wsargent/scalafx-ammoniteEpitomize

© 2022 - 2024 — McMap. All rights reserved.