[Editor] Enable code editor shutdown when code editor tab is closed as well as application is being shutdown

This commit is contained in:
2021-02-24 21:20:06 +01:00
parent 6f89c5f6fb
commit 569daa7ebd
6 changed files with 64 additions and 20 deletions

View File

@@ -1,6 +1,8 @@
package com.bartlomiejpluta.base.editor package com.bartlomiejpluta.base.editor
import com.bartlomiejpluta.base.editor.main.controller.MainController
import com.bartlomiejpluta.base.editor.main.view.MainView import com.bartlomiejpluta.base.editor.main.view.MainView
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.SpringApplication import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.ConfigurableApplicationContext import org.springframework.context.ConfigurableApplicationContext
@@ -14,6 +16,9 @@ import kotlin.reflect.KClass
open class EditorApp : App(MainView::class) { open class EditorApp : App(MainView::class) {
private lateinit var context: ConfigurableApplicationContext private lateinit var context: ConfigurableApplicationContext
@Autowired
private lateinit var mainController: MainController
override fun init() { override fun init() {
this.context = SpringApplication.run(this.javaClass) this.context = SpringApplication.run(this.javaClass)
context.autowireCapableBeanFactory.autowireBean(this) context.autowireCapableBeanFactory.autowireBean(this)
@@ -27,6 +32,7 @@ open class EditorApp : App(MainView::class) {
override fun stop() { override fun stop() {
super.stop() super.stop()
context.close() context.close()
mainController.clearResources()
} }
} }

View File

@@ -20,7 +20,18 @@ class CodeEditor(private val highlighter: ObservableValue<out SyntaxHighlighter>
StackPane() { StackPane() {
private val editor = CodeArea() private val editor = CodeArea()
private val executor = Executors.newSingleThreadExecutor() private val executor = Executors.newSingleThreadExecutor()
private val cleanupWhenDone = editor.multiPlainChanges()
private val highlightingSubscription = editor.multiPlainChanges()
.successionEnds(Duration.ofMillis(500))
.supplyTask(this::computeHighlightingAsync)
.awaitLatest(editor.multiPlainChanges())
.filterMap {
when {
it.isSuccess -> Optional.of(it.get())
else -> Optional.empty()
}
}
.subscribe(this::applyHighlighting)
init { init {
editor.replaceText(0, 0, codeProperty.value) editor.replaceText(0, 0, codeProperty.value)
@@ -28,20 +39,6 @@ class CodeEditor(private val highlighter: ObservableValue<out SyntaxHighlighter>
editor.paragraphGraphicFactory = LineNumberFactory.get(editor) editor.paragraphGraphicFactory = LineNumberFactory.get(editor)
applyHighlighting(highlighter.value.highlight(editor.text)) applyHighlighting(highlighter.value.highlight(editor.text))
cleanupWhenDone
.successionEnds(Duration.ofMillis(500))
.supplyTask(this::computeHighlightingAsync)
.awaitLatest(editor.multiPlainChanges())
.filterMap {
when {
it.isSuccess -> Optional.of(it.get())
else -> Optional.empty()
}
}
.subscribe(this::applyHighlighting)
initAutoIndents() initAutoIndents()
children += VirtualizedScrollPane(editor) children += VirtualizedScrollPane(editor)
@@ -55,6 +52,11 @@ class CodeEditor(private val highlighter: ObservableValue<out SyntaxHighlighter>
editor.redo() editor.redo()
} }
fun shutdownHighlighterThread() {
highlightingSubscription.unsubscribe()
executor.shutdownNow()
}
private fun initAutoIndents() { private fun initAutoIndents() {
editor.addEventHandler(KeyEvent.KEY_PRESSED) { event -> editor.addEventHandler(KeyEvent.KEY_PRESSED) { event ->
if (event.code === KeyCode.ENTER) { if (event.code === KeyCode.ENTER) {

View File

@@ -5,5 +5,9 @@ import tornadofx.Fragment
class CodeEditorFragment : Fragment() { class CodeEditorFragment : Fragment() {
private val editorView = find<CodeEditorView>() private val editorView = find<CodeEditorView>()
fun shutdown() {
editorView.shutdown()
}
override val root = editorView.root override val root = editorView.root
} }

View File

@@ -27,6 +27,10 @@ class CodeEditorView : View() {
private val editor = CodeEditor(highlighter, codeVM.codeProperty) private val editor = CodeEditor(highlighter, codeVM.codeProperty)
fun shutdown() {
editor.shutdownHighlighterThread()
}
override val root = borderpane { override val root = borderpane {
top = toolbar { top = toolbar {
button(graphic = FontIcon("fa-floppy-o")) { button(graphic = FontIcon("fa-floppy-o")) {

View File

@@ -69,7 +69,7 @@ class MainController : Controller() {
title = "Load Project", title = "Load Project",
filters = arrayOf(FileChooser.ExtensionFilter("BASE Editor Project (*.bep)", "*.bep")), filters = arrayOf(FileChooser.ExtensionFilter("BASE Editor Project (*.bep)", "*.bep")),
).getOrNull(0)?.let { ).getOrNull(0)?.let {
openItems.clear() clearResources()
projectContext.open(it) projectContext.open(it)
} }
} }
@@ -143,4 +143,8 @@ class MainController : Controller() {
} }
} }
} }
fun clearResources() {
openItems.clear()
}
} }

View File

@@ -11,6 +11,7 @@ import com.bartlomiejpluta.base.editor.map.view.editor.MapFragment
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import com.bartlomiejpluta.base.editor.project.context.ProjectContext import com.bartlomiejpluta.base.editor.project.context.ProjectContext
import javafx.collections.MapChangeListener import javafx.collections.MapChangeListener
import javafx.event.Event
import javafx.scene.control.Tab import javafx.scene.control.Tab
import org.kordamp.ikonli.javafx.FontIcon import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.* import tornadofx.*
@@ -50,8 +51,26 @@ class MainView : View("BASE Game Editor") {
it.wasRemoved() -> { it.wasRemoved() -> {
val tab = openTabs[it.key] val tab = openTabs[it.key]
tabs -= tab
openTabs.remove(it.key) openTabs.remove(it.key)
// FIXME
// This ugly hack prevents from double-firing the CLOSED EVENT
// when user tries to close tab from the UI (cross sign).
// When user clicks the close button, the event is fired automatically,
// which removes the item from mainController.openItems.
// Right here, this listener would fire the event once again,
// so essentially we need to check if the tab exists in TabPane.tabs
// (which means the tab wasn't be closed from UI). If so, we need to fire
// the event manually since it won't be fired by TabPane.
// Otherwise, if the tab does not exist in tabs anymore, the tab
// was closed from UI and likely the event has been fired automatically.
// The same goes for the TAB_CLOSE_REQUEST event.
if (tab in tabs) {
Event.fireEvent(tab, Event(Tab.CLOSED_EVENT))
Event.fireEvent(tab, Event(Tab.TAB_CLOSE_REQUEST_EVENT))
}
tabs -= tab
} }
} }
}) })
@@ -82,10 +101,15 @@ class MainView : View("BASE Game Editor") {
is Code -> Tab().apply { is Code -> Tab().apply {
val vm = CodeVM(item) val vm = CodeVM(item)
setInScope(vm, scope) setInScope(vm, scope)
content = find<CodeEditorFragment>(scope).root val editor = find<CodeEditorFragment>(scope)
textProperty().bindBidirectional(item.fileProperty.select { it.name.toProperty() }) content = editor.root
graphic = FontIcon("fa-code") graphic = FontIcon("fa-code")
setOnClosed { mainController.openItems.remove(scope) } textProperty().bindBidirectional(item.fileProperty.select { it.name.toProperty() })
setOnClosed {
editor.shutdown()
mainController.openItems.remove(scope)
}
} }
else -> throw IllegalStateException("Unsupported tab item") else -> throw IllegalStateException("Unsupported tab item")