Skip to content

Commit c5f53b9

Browse files
committed
Ensure that the extension can be loaded headlessly
1 parent 7470591 commit c5f53b9

12 files changed

+88
-73
lines changed

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ netLogoTestExtras += (baseDirectory.value / "test")
1515

1616
Compile / scalaSource := baseDirectory.value / "src" / "main"
1717
Test / scalaSource := baseDirectory.value / "src" / "test"
18-
scalacOptions ++= Seq("-deprecation", "-unchecked", "-Xfatal-warnings", "-encoding", "us-ascii", "-feature", "-release", "11")
18+
scalacOptions ++= Seq("-deprecation", "-unchecked", "-Xfatal-warnings", "-encoding", "us-ascii", "-feature", "-release", "11", "-Wunused:linted")
1919

2020
libraryDependencies ++= Seq(
2121
"com.google.guava" % "guava" % "18.0",

src/main/BackingModelManager.scala

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
package org.nlogo.ls
22

3-
import org.nlogo.ls.gui.{ModelCodeTab, LevelSpaceMenu, ModelManager}
4-
5-
import java.util.{ Map => JMap }
3+
import org.nlogo.ls.gui.{ GUILevelSpaceMenu, LevelSpaceMenu, ModelCodeTab, ModelManager }
64

75
import org.nlogo.app.App
86
import org.nlogo.app.codetab.CodeTab
9-
import org.nlogo.theme.ThemeSync
107
import org.nlogo.workspace.AbstractWorkspaceScala
118

129
import scala.collection.Map
1310
import scala.collection.parallel.mutable.ParHashMap
1411

15-
trait LSModelManager extends ModelManager with ThemeSync {
12+
trait LSModelManager extends ModelManager {
1613
def updateChildModels(map: Map[Integer, ChildModel]): Unit = {}
1714
def guiComponent: LevelSpaceMenu = null
1815
}
1916

2017
class BackingModelManager extends LSModelManager {
21-
override val guiComponent = new LevelSpaceMenu(App.app.tabManager, this)
18+
override val guiComponent: GUILevelSpaceMenu = new GUILevelSpaceMenu(App.app.tabManager, this)
2219
private val backingModels = ParHashMap.empty[String, (ChildModel, ModelCodeTab)]
2320
private var openModels = Map.empty[String, ChildModel]
2421
private var models = Seq[ChildModel]()
@@ -78,7 +75,15 @@ class BackingModelManager extends LSModelManager {
7875

7976
def syncTheme(): Unit = {
8077
guiComponent.syncTheme()
81-
models.foreach(_.syncTheme())
78+
79+
models.foreach(_ match {
80+
case model: GUIChildModel =>
81+
model.syncTheme()
82+
83+
case model: HeadlessChildModel =>
84+
model.syncTheme()
85+
})
86+
8287
backingModels.values.foreach(_._2.syncTheme())
8388
}
8489
}
@@ -89,5 +94,4 @@ class HeadlessBackingModelManager extends LSModelManager {
8994
def existingTab(filePath: String): Option[ModelCodeTab] = None
9095
def registerTab(filePath: String)
9196
(f: AbstractWorkspaceScala => ModelCodeTab): Option[ModelCodeTab] = None
92-
def syncTheme(): Unit = {}
9397
}

src/main/ChildModel.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package org.nlogo.ls
22

3-
import java.awt.GraphicsEnvironment
3+
import javax.swing.{ JFrame, SwingUtilities }
44

5-
import javax.swing.{JFrame, SwingUtilities}
6-
import org.nlogo.api.{CommandRunnable, ExtensionException, Version, Workspace}
7-
import org.nlogo.theme.ThemeSync
5+
import org.nlogo.api.{ CommandRunnable, ExtensionException, Version, Workspace }
86
import org.nlogo.workspace.AbstractWorkspaceScala
97

108
import scala.concurrent.ExecutionContext.Implicits.global
119
import scala.concurrent.Future
1210
import scala.jdk.CollectionConverters.IterableHasAsScala
1311

14-
abstract class ChildModel(val parentWorkspace: Workspace, val modelID: Int) extends ThemeSync {
12+
abstract class ChildModel(val parentWorkspace: Workspace, val modelID: Int) {
1513
lazy val evaluator = new Evaluator(modelID, name, workspace, parentWorkspace.asInstanceOf[AbstractWorkspaceScala])
1614

1715
private var _name: Option[String] = None

src/main/Evaluator.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package org.nlogo.ls
22

3-
import com.google.common.cache.{CacheBuilder, CacheLoader}
4-
import org.nlogo.api.{Context, MersenneTwisterFast, SimpleJobOwner, Workspace}
5-
import org.nlogo.core.{AgentKind, LogoList, Nobody}
6-
import org.nlogo.nvm.{ExclusiveJob, Procedure, Reporter}
7-
import org.nlogo.prim.{_constboolean, _constdouble, _constlist, _conststring, _nobody}
8-
import org.nlogo.workspace.{AbstractWorkspaceScala, Plotting}
9-
10-
import scala.collection.mutable
3+
import com.google.common.cache.{ CacheBuilder, CacheLoader }
4+
5+
import org.nlogo.api.{ Context, MersenneTwisterFast, SimpleJobOwner, Workspace }
6+
import org.nlogo.core.{ AgentKind, LogoList, Nobody }
7+
import org.nlogo.nvm.{ ExclusiveJob, Procedure, Reporter }
8+
import org.nlogo.prim.{ _constboolean, _constdouble, _constlist, _conststring, _nobody }
9+
import org.nlogo.workspace.{ AbstractWorkspaceScala, Plotting }
1110

1211
object RNG {
1312
def apply(ctx: Context): RNG = ctx.workspace match {

src/main/GUIChildModel.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package org.nlogo.ls
22

33
import java.awt._
4-
import java.awt.event.{WindowAdapter, WindowEvent}
5-
import javax.swing.{JFrame, JMenuBar, WindowConstants}
4+
import java.awt.event.{ WindowAdapter, WindowEvent }
5+
import javax.swing.{ JFrame, JMenuBar, WindowConstants }
66
import java.io.IOException
77

88
import org.nlogo.api._
99
import org.nlogo.app.{ App, ZoomMenu }
10-
import org.nlogo.ls.gui.{GUIPanel, InterfaceComponent, ZoomableInterfaceComponent}
10+
import org.nlogo.ls.gui.{ GUIPanel, InterfaceComponent, ZoomableInterfaceComponent }
1111
import org.nlogo.nvm.HaltException
12-
import org.nlogo.swing.{ Menu, ModalProgress, NetLogoIcon, Utils }
13-
import org.nlogo.theme.{ InterfaceColors, ThemeSync }
12+
import org.nlogo.swing.{ ModalProgress, NetLogoIcon, Utils }
13+
import org.nlogo.theme.InterfaceColors
1414
import org.nlogo.window.GUIWorkspace
1515

16-
import scala.util.{Failure, Try}
16+
import scala.util.{ Failure, Try }
1717

1818
class GUIChildModel @throws(classOf[InterruptedException]) @throws(classOf[ExtensionException]) @throws(classOf[HaltException]) @throws(classOf[IOException])
1919
(ls: LevelSpace, parentWorkspace: Workspace, path: String, modelID: Int)
@@ -91,7 +91,7 @@ class GUIChildModel @throws(classOf[InterruptedException]) @throws(classOf[Exten
9191
}
9292
}
9393

94-
class SyncedMenuBar extends JMenuBar with ThemeSync {
94+
class SyncedMenuBar extends JMenuBar {
9595
// val zoomMenuClass = Class.forName("org.nlogo.app.ZoomMenu")
9696
// add(zoomMenuClass.getDeclaredConstructor().newInstance().asInstanceOf[Menu])
9797

src/main/GUIPanel.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import javax.swing.event.ChangeEvent
66

77
import org.nlogo.app.interfacetab.CommandCenter
88
import org.nlogo.swing.{ ScrollPane, SplitPane, Transparent }
9-
import org.nlogo.theme.{ InterfaceColors, ThemeSync }
9+
import org.nlogo.theme.InterfaceColors
1010
import org.nlogo.window.{ Events, GUIWorkspace, TickCounterLabel }
1111
import org.nlogo.workspace.AbstractWorkspaceScala
1212

1313
abstract class ModelPanel(ws: AbstractWorkspaceScala, panel: JPanel, verticalScroll: Int, resizeWeight: Int)
14-
extends JPanel with Events.OutputEvent.Handler with ThemeSync {
14+
extends JPanel with Events.OutputEvent.Handler {
1515
setLayout(new BorderLayout)
1616

1717
val controlStrip = new JPanel
@@ -63,7 +63,9 @@ extends JPanel with Events.OutputEvent.Handler with ThemeSync {
6363
cc.syncTheme()
6464

6565
panel match {
66-
case ts: ThemeSync => ts.syncTheme()
66+
case component: InterfaceComponent =>
67+
component.syncTheme()
68+
6769
case _ =>
6870
}
6971
}

src/main/HeadlessChildModel.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package org.nlogo.ls
22

3-
import java.io.IOException
43
import javax.swing.Timer
54

6-
import org.nlogo.agent.{CompilationManagement, OutputObject, World, World2D, World3D}
5+
import org.nlogo.agent.{ CompilationManagement, OutputObject, World, World2D, World3D }
76
import org.nlogo.api._
8-
import org.nlogo.nvm.{Context, HaltException, Instruction}
7+
import org.nlogo.nvm.{ Context, Instruction }
98
import org.nlogo.headless.HeadlessWorkspace
109
import org.nlogo.ls.gui.ViewFrame
11-
import org.nlogo.workspace.{AbstractWorkspace, AbstractWorkspaceScala}
10+
import org.nlogo.workspace.AbstractWorkspace
1211

1312
class HeadlessChildModel (parentWorkspace: AbstractWorkspace, path: String, modelID: Int)
1413
extends ChildModel(parentWorkspace, modelID) {

src/main/InterfaceComponent.scala

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,32 @@ import java.awt.image.BufferedImage
55
import java.nio.file.Paths
66
import javax.swing.{ JFrame, JPanel }
77

8-
import org.nlogo.agent.{Agent, CompilationManagement, World, World2D, World3D}
8+
import org.nlogo.agent.{ Agent, CompilationManagement, World, World2D, World3D }
99
import org.nlogo.api.{ Agent => APIAgent, ControlSet, LabProtocol, ModelType, NetLogoLegacyDialect,
1010
NetLogoThreeDDialect, Version }
1111
import org.nlogo.app.codetab.ExternalFileManager
1212
import org.nlogo.app.tools.AgentMonitorManager
1313
import org.nlogo.awt.EventQueue
1414
import org.nlogo.compile.Compiler
15-
import org.nlogo.core.{AgentKind, Model}
15+
import org.nlogo.core.{ AgentKind, Model }
1616
import org.nlogo.gl.view.ViewManager
1717
import org.nlogo.lite.ProceduresLite
1818
import org.nlogo.sdm.AggregateManagerLite
19-
import org.nlogo.theme.{ InterfaceColors, ThemeSync }
20-
import org.nlogo.window.Events.{CompiledEvent, LoadModelEvent}
21-
import org.nlogo.window.{CompilerManager, DefaultEditorFactory, ErrorDialogManager, Event, FileController, GUIWorkspace, InterfacePanelLite, LinkRoot, NetLogoListenerManager, OutputWidget, ReconfigureWorkspaceUI, UpdateManager}
19+
import org.nlogo.theme.InterfaceColors
20+
import org.nlogo.window.Events.{ CompiledEvent, LoadModelEvent }
21+
import org.nlogo.window.{ CompilerManager, DefaultEditorFactory, ErrorDialogManager, Event, FileController,
22+
GUIWorkspace, InterfacePanelLite, LinkRoot, NetLogoListenerManager, OutputWidget,
23+
ReconfigureWorkspaceUI, UpdateManager }
2224
import org.nlogo.workspace.OpenModelFromURI
2325
import org.nlogo.fileformat.FileFormat
2426

25-
import scala.concurrent.{Future, Promise}
27+
import scala.concurrent.{ Future, Promise }
2628
import scala.util.Try
2729

2830
abstract class InterfaceComponent(frame: JFrame) extends JPanel
2931
with Event.LinkParent
3032
with LinkRoot
31-
with ControlSet
32-
with ThemeSync {
33+
with ControlSet {
3334
val listenerManager = new NetLogoListenerManager
3435
val world: World = if(Version.is3D) new World3D() else new World2D()
3536

src/main/LevelSpace.scala

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import org.nlogo.app.{ App, ToolsMenu }
1414
import org.nlogo.awt.EventQueue
1515
import org.nlogo.core.LogoList
1616
import org.nlogo.nvm.HaltException
17-
import org.nlogo.theme.ThemeSync
1817
import org.nlogo.workspace.{ AbstractWorkspace, ExtensionManager => WorkspaceExtensionManager }
1918

2019
import scala.collection.immutable.ArraySeq
@@ -55,9 +54,15 @@ object LevelSpace {
5554
}
5655
}
5756

58-
class LevelSpace extends DefaultClassManager with ThemeSync { // This can be accessed by both the JobThread and EDT (when halting)
57+
class LevelSpace extends DefaultClassManager { // This can be accessed by both the JobThread and EDT (when halting)
5958
LevelSpace.checkSupportNetLogoVersion()
6059

60+
private var functionID: Option[Long] = None
61+
62+
private def addSyncFunction(): Unit = {
63+
functionID = Some(App.app.addSyncFunction(() => syncTheme()))
64+
}
65+
6166
final private val models = new ConcurrentHashMap[Integer, ChildModel].asScala
6267
// counter for keeping track of new models
6368
private var modelCounter = 0
@@ -96,9 +101,6 @@ class LevelSpace extends DefaultClassManager with ThemeSync { // This can be acc
96101
primitiveManager.addPrimitive("uses-level-space?", new UsesLS(this))
97102
primitiveManager.addPrimitive("random-seed", new RandomSeed(this))
98103
primitiveManager.addPrimitive("assign", new Assign(this))
99-
// We need to actually listen for halt actions because gui child models can be running independently on their own
100-
// job threads if the user is interacting with them.
101-
haltButton.foreach(_.addActionListener(haltListener))
102104
}
103105

104106
def isMainModel(myEM: ExtensionManager): Boolean = myEM eq App.app.workspace.getExtensionManager
@@ -115,11 +117,14 @@ class LevelSpace extends DefaultClassManager with ThemeSync { // This can be acc
115117

116118
@throws[ExtensionException]
117119
override def unload(em: ExtensionManager): Unit = {
118-
if (!LevelSpace.isHeadless && isMainModel(em)) {
119-
App.app.frame.getJMenuBar.remove(modelManager.guiComponent)
120-
App.app.removeSyncComponent(this)
120+
if (!LevelSpace.isHeadless) {
121+
if (isMainModel(em)) {
122+
App.app.frame.getJMenuBar.remove(modelManager.guiComponent)
123+
functionID.foreach(App.app.removeSyncFunction)
124+
}
125+
126+
haltButton.foreach(_.removeActionListener(haltListener))
121127
}
122-
haltButton.foreach(_.removeActionListener(haltListener))
123128
try reset()
124129
catch {
125130
case _: HaltException =>
@@ -211,20 +216,30 @@ class LevelSpace extends DefaultClassManager with ThemeSync { // This can be acc
211216
em.asInstanceOf[WorkspaceExtensionManager].workspace.isInstanceOf[AbstractWorkspace] &&
212217
em.asInstanceOf[WorkspaceExtensionManager].workspace.asInstanceOf[AbstractWorkspace].isHeadless
213218

214-
if (!LevelSpace.isHeadless) { modelManager = new BackingModelManager }
219+
if (!LevelSpace.isHeadless) {
220+
modelManager = new BackingModelManager
221+
// We need to actually listen for halt actions because gui child models can be running independently on their own
222+
// job threads if the user is interacting with them.
223+
haltButton.foreach(_.addActionListener(haltListener))
224+
}
215225
modelManager.updateChildModels(models)
216226
if (!LevelSpace.isHeadless && isMainModel(em)) {
217227
val menuBar = App.app.frame.getJMenuBar
218228
if (menuBar.getComponentIndex(modelManager.guiComponent) == -1) {
219229
menuBar.add(modelManager.guiComponent)
220230
}
221-
App.app.addSyncComponent(this)
231+
addSyncFunction()
222232
}
223233
}
224234

225235
private def haltChildModels(): Unit = models.values.foreach(_.halt())
226236

227237
def syncTheme(): Unit = {
228-
modelManager.syncTheme()
238+
modelManager match {
239+
case guiManager: BackingModelManager =>
240+
guiManager.syncTheme()
241+
242+
case _ =>
243+
}
229244
}
230245
}

src/main/LevelSpaceMenu.scala

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
package org.nlogo.ls.gui
22

3-
import java.awt.FileDialog.{LOAD => LOADFILE, SAVE => SAVEFILE}
3+
import java.awt.FileDialog.{ LOAD => LOADFILE, SAVE => SAVEFILE }
44
import java.awt.event.ActionEvent
55
import java.nio.file.{ Paths, Files }
6-
import java.io.{File, FileWriter, IOException}
6+
import java.io.IOException
77
import javax.swing._
88

9-
import org.nlogo.api.ModelSections.ModelSaveable
10-
import org.nlogo.api.{ExtensionException, ModelSections, Version, Exceptions}
11-
import org.nlogo.core.{CompilerException, Shape, ShapeParser}
12-
import org.nlogo.app.{ModelSaver, App, TabManager}
9+
import org.nlogo.api.{ Exceptions, ExtensionException, Version }
10+
import org.nlogo.core.CompilerException
11+
import org.nlogo.app.{ App, TabManager }
1312
import org.nlogo.app.codetab.CodeTab
1413
import org.nlogo.awt.UserCancelException
1514
import org.nlogo.fileformat.FileFormat
1615
import org.nlogo.swing.{ FileDialog, Menu, MenuItem }
17-
import org.nlogo.workspace.{AbstractWorkspaceScala, ModelsLibrary, ModelTracker, SaveModel}
18-
19-
import scala.jdk.CollectionConverters.SeqHasAsJava
16+
import org.nlogo.workspace.{ AbstractWorkspaceScala, ModelsLibrary, ModelTracker, SaveModel }
2017

2118
trait ModelManager {
2219
def removeTab(tab: ModelCodeTab): Unit
@@ -25,8 +22,10 @@ trait ModelManager {
2522
(f: AbstractWorkspaceScala => ModelCodeTab): Option[ModelCodeTab]
2623
}
2724

28-
class LevelSpaceMenu(tabManager: TabManager, val backingModelManager: ModelManager)
29-
extends Menu("LevelSpace") {
25+
trait LevelSpaceMenu extends JMenu
26+
27+
class GUILevelSpaceMenu(tabManager: TabManager, val backingModelManager: ModelManager)
28+
extends Menu("LevelSpace") with LevelSpaceMenu {
3029

3130
import LevelSpaceMenu._
3231

0 commit comments

Comments
 (0)