Compare commits

...

6 Commits

19 changed files with 395 additions and 247 deletions

View File

@ -0,0 +1,127 @@
package com.marvinelsen.willow
import com.marvinelsen.willow.domain.SearchMode
import com.marvinelsen.willow.domain.SqliteDictionary
import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.ui.undo.Command
import com.marvinelsen.willow.ui.util.ClipboardHelper
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
@Suppress("TooManyFunctions")
class Interactor(
private val model: Model,
private val dictionary: SqliteDictionary,
) {
private val coroutineScope = MainScope()
fun copyHeadwordOfSelectedEntry() {
model.selectedEntry?.let { ClipboardHelper.copyString(it.traditional) }
}
fun copyPronunciationOfSelectedEntry() {
model.selectedEntry?.let { ClipboardHelper.copyString(it.pinyinWithToneMarks) }
}
fun undoSelection() {
model.undoManager.undo()
}
fun redoSelection() {
model.undoManager.redo()
}
fun search(query: String, searchMode: SearchMode) {
coroutineScope.launch {
model.isSearching = true
model.searchResults.setAll(dictionary.search(query, searchMode))
model.isSearching = false
}
}
fun findWordsBeginning() {
coroutineScope.launch {
model.isFindingWordsBeginning = true
model.wordsBeginning.setAll(
model.selectedEntry?.let {
dictionary
.findWordsBeginningWith(it)
}
)
model.isFindingWordsBeginning = false
model.finishedFindingWordsBeginning = true
}
}
fun findWordsContaining() {
coroutineScope.launch {
model.isFindingWordsContaining = true
model.wordsContaining.setAll(
model.selectedEntry?.let {
dictionary
.findWordsContaining(it)
}
)
model.isFindingWordsContaining = false
model.finishedFindingWordsContaining = true
}
}
fun findCharacters() {
coroutineScope.launch {
model.isFindingCharacters = true
model.characters.setAll(
model.selectedEntry?.let {
dictionary
.findCharactersOf(it)
}
)
model.isFindingCharacters = false
model.finishedFindingCharacters = true
}
}
fun findSentences() {
coroutineScope.launch {
model.isFindingSentences = true
model.sentences.setAll(
model.selectedEntry?.let {
dictionary
.findExampleSentencesFor(it)
}
)
model.isFindingSentences = false
model.finishedFindingSentences = true
}
}
fun deepDive(entry: DictionaryEntry) {
model.undoManager.execute(object : Command {
private val previouslySelectedEntry = model.selectedEntry
override fun execute() {
select(entry)
}
override fun undo() {
if (previouslySelectedEntry == null) return
select(previouslySelectedEntry)
}
})
}
fun normalSelect(entry: DictionaryEntry) {
model.undoManager.reset()
select(entry)
}
private fun select(entry: DictionaryEntry) {
model.finishedFindingCharacters = false
model.finishedFindingWordsBeginning = false
model.finishedFindingWordsContaining = false
model.finishedFindingSentences = false
model.selectedEntry = entry
}
}

View File

@ -1,169 +1,110 @@
package com.marvinelsen.willow package com.marvinelsen.willow
import com.marvinelsen.willow.domain.SearchMode import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.domain.SqliteDictionary import com.marvinelsen.willow.domain.objects.Sentence
import com.marvinelsen.willow.domain.entities.DictionaryEntry
import com.marvinelsen.willow.domain.entities.Sentence
import com.marvinelsen.willow.ui.undo.Command
import com.marvinelsen.willow.ui.undo.UndoManager import com.marvinelsen.willow.ui.undo.UndoManager
import com.marvinelsen.willow.ui.util.ClipboardHelper
import javafx.beans.property.BooleanProperty import javafx.beans.property.BooleanProperty
import javafx.beans.property.ObjectProperty import javafx.beans.property.ObjectProperty
import javafx.beans.property.ReadOnlyBooleanProperty import javafx.beans.property.ReadOnlyBooleanProperty
import javafx.beans.property.ReadOnlyObjectProperty
import javafx.beans.property.SimpleBooleanProperty import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.collections.ObservableList import javafx.collections.ObservableList
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class Model( class Model(
private val dictionary: SqliteDictionary, val undoManager: UndoManager,
private val undoManager: UndoManager,
) { ) {
private val internalSelectedEntry: ObjectProperty<DictionaryEntry> = SimpleObjectProperty() private val _selectedEntry: ObjectProperty<DictionaryEntry?> = SimpleObjectProperty<DictionaryEntry>()
private val internalSearchResults: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
private val internalWordsBeginning: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
private val internalWordsContaining: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
private val internalCharacters: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
private val internalSentences: ObservableList<Sentence> = FXCollections.observableArrayList()
private val internalIsSearching: BooleanProperty = SimpleBooleanProperty(false) private val _isSearching: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingWordsBeginning: BooleanProperty = SimpleBooleanProperty(false) private val _isFindingWordsBeginning: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingWordsContaining: BooleanProperty = SimpleBooleanProperty(false) private val _isFindingWordsContaining: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingCharacters: BooleanProperty = SimpleBooleanProperty(false) private val _isFindingCharacters: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingSentences: BooleanProperty = SimpleBooleanProperty(false) private val _isFindingSentences: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingWordsBeginning: BooleanProperty = SimpleBooleanProperty(false) private val _finishedFindingWordsBeginning: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingWordsContaining: BooleanProperty = SimpleBooleanProperty(false) private val _finishedFindingWordsContaining: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingCharacters: BooleanProperty = SimpleBooleanProperty(false) private val _finishedFindingCharacters: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingSentences: BooleanProperty = SimpleBooleanProperty(false) private val _finishedFindingSentences: BooleanProperty = SimpleBooleanProperty(false)
val searchResults: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
val wordsBeginning: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
val wordsContaining: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
val characters: ObservableList<DictionaryEntry> = FXCollections.observableArrayList()
val sentences: ObservableList<Sentence> = FXCollections.observableArrayList()
val canUndo: ReadOnlyBooleanProperty = undoManager.canUndoProperty val canUndo: ReadOnlyBooleanProperty = undoManager.canUndoProperty
val canRedo: ReadOnlyBooleanProperty = undoManager.canRedoProperty val canRedo: ReadOnlyBooleanProperty = undoManager.canRedoProperty
val selectedEntry: ReadOnlyObjectProperty<DictionaryEntry> = internalSelectedEntry var selectedEntry: DictionaryEntry?
get() = _selectedEntry.value
val searchResults: ObservableList<DictionaryEntry> = set(value) {
FXCollections.unmodifiableObservableList(internalSearchResults) _selectedEntry.value = value
val wordsBeginning: ObservableList<DictionaryEntry> =
FXCollections.unmodifiableObservableList(internalWordsBeginning)
val wordsContaining: ObservableList<DictionaryEntry> =
FXCollections.unmodifiableObservableList(internalWordsContaining)
val characters: ObservableList<DictionaryEntry> =
FXCollections.unmodifiableObservableList(internalCharacters)
val sentences: ObservableList<Sentence> =
FXCollections.unmodifiableObservableList(internalSentences)
val isSearching: ReadOnlyBooleanProperty = internalIsSearching
val isFindingWordsBeginning: ReadOnlyBooleanProperty = internalIsFindingWordsBeginning
val isFindingWordsContaining: ReadOnlyBooleanProperty = internalIsFindingWordsContaining
val isFindingCharacters: ReadOnlyBooleanProperty = internalIsFindingCharacters
val isFindingSentences: ReadOnlyBooleanProperty = internalIsFindingSentences
val finishedFindingWordsBeginning: ReadOnlyBooleanProperty = internalFinishedFindingWordsBeginning
val finishedFindingWordsContaining: ReadOnlyBooleanProperty = internalFinishedFindingWordsContaining
val finishedFindingCharacters: ReadOnlyBooleanProperty = internalFinishedFindingCharacters
val finishedFindingSentences: ReadOnlyBooleanProperty = internalFinishedFindingSentences
private val coroutineScope = MainScope()
fun copyHeadwordOfSelectedEntry() {
ClipboardHelper.copyString(internalSelectedEntry.value.traditional)
} }
fun copyPronunciationOfSelectedEntry() { var isSearching: Boolean
ClipboardHelper.copyString(internalSelectedEntry.value.pinyinWithToneMarks) get() = _isSearching.value
set(value) {
_isSearching.value = value
} }
fun undoSelection() { var isFindingWordsBeginning: Boolean
undoManager.undo() get() = _isFindingWordsBeginning.value
set(value) {
_isFindingWordsBeginning.value = value
} }
fun redoSelection() { var isFindingWordsContaining: Boolean
undoManager.redo() get() = _isFindingWordsContaining.value
set(value) {
_isFindingWordsContaining.value = value
} }
fun search(query: String, searchMode: SearchMode) { var isFindingCharacters: Boolean
coroutineScope.launch { get() = _isFindingCharacters.value
internalIsSearching.value = true set(value) {
internalSearchResults.setAll(dictionary.search(query, searchMode)) _isFindingCharacters.value = value
internalIsSearching.value = false
}
} }
fun findWordsBeginning() { var isFindingSentences: Boolean
coroutineScope.launch { get() = _isFindingSentences.value
internalIsFindingWordsBeginning.value = true set(value) {
internalWordsBeginning.setAll( _isFindingSentences.value = value
dictionary
.findWordsBeginningWith(internalSelectedEntry.value)
)
internalIsFindingWordsBeginning.value = false
internalFinishedFindingWordsBeginning.value = true
}
} }
fun findWordsContaining() { var finishedFindingWordsBeginning: Boolean
coroutineScope.launch { get() = _finishedFindingWordsBeginning.value
internalIsFindingWordsContaining.value = true set(value) {
internalWordsContaining.setAll( _finishedFindingWordsBeginning.value = value
dictionary
.findWordsContaining(internalSelectedEntry.value)
)
internalIsFindingWordsContaining.value = false
internalFinishedFindingWordsContaining.value = true
}
} }
fun findCharacters() { var finishedFindingWordsContaining: Boolean
coroutineScope.launch { get() = _finishedFindingWordsContaining.value
internalIsFindingCharacters.value = true set(value) {
internalCharacters.setAll( _finishedFindingWordsContaining.value = value
dictionary
.findCharactersOf(internalSelectedEntry.value)
)
internalIsFindingCharacters.value = false
internalFinishedFindingCharacters.value = true
}
} }
fun findSentences() { var finishedFindingCharacters: Boolean
coroutineScope.launch { get() = _finishedFindingCharacters.value
internalIsFindingSentences.value = true set(value) {
internalSentences.setAll( _finishedFindingCharacters.value = value
dictionary
.findExampleSentencesFor(internalSelectedEntry.value)
)
internalIsFindingSentences.value = false
internalFinishedFindingSentences.value = true
}
} }
fun selectEntry(entry: DictionaryEntry) { var finishedFindingSentences: Boolean
undoManager.execute(object : Command { get() = _finishedFindingSentences.value
private val previouslySelectedEntry = internalSelectedEntry.value set(value) {
_finishedFindingSentences.value = value
override fun execute() {
select(entry)
} }
override fun undo() { fun selectedEntryProperty(): ObjectProperty<DictionaryEntry?> = _selectedEntry
if (previouslySelectedEntry == null) return fun isSearchingProperty(): BooleanProperty = _isSearching
fun isFindingWordsBeginningProperty(): BooleanProperty = _isFindingWordsBeginning
select(previouslySelectedEntry) fun isFindingWordsContainingProperty(): BooleanProperty = _isFindingWordsContaining
} fun isFindingCharactersProperty(): BooleanProperty = _isFindingCharacters
}) fun isFindingSentencesProperty(): BooleanProperty = _isFindingSentences
} fun finishedFindingWordsBeginning(): BooleanProperty = _finishedFindingWordsBeginning
fun finishedFindingWordsContaining(): BooleanProperty = _finishedFindingWordsContaining
private fun select(entry: DictionaryEntry) { fun finishedFindingCharacters(): BooleanProperty = _finishedFindingCharacters
internalFinishedFindingCharacters.value = false fun finishedFindingSentences(): BooleanProperty = _finishedFindingSentences
internalFinishedFindingWordsBeginning.value = false
internalFinishedFindingWordsContaining.value = false
internalFinishedFindingSentences.value = false
internalSelectedEntry.value = entry
}
} }

View File

@ -43,10 +43,8 @@ class WillowApplication : Application() {
} }
val dictionary = SqliteDictionary(connection) val dictionary = SqliteDictionary(connection)
val undoManager = UndoManager() val undoManager = UndoManager()
val model = Model( val model = Model(undoManager)
dictionary, val interactor = Interactor(model, dictionary)
undoManager
)
val config = Config() val config = Config()
config.load() config.load()
@ -57,10 +55,10 @@ class WillowApplication : Application() {
fxmlLoader.controllerFactory = Callback { type -> fxmlLoader.controllerFactory = Callback { type ->
when (type) { when (type) {
MainController::class.java -> MainController(model) MainController::class.java -> MainController(model)
MenuController::class.java -> MenuController(model, config) MenuController::class.java -> MenuController(model, interactor, config)
DetailsController::class.java -> DetailsController(model, config, hostServices) DetailsController::class.java -> DetailsController(model, interactor, config, hostServices)
SearchController::class.java -> SearchController(model) SearchController::class.java -> SearchController(model, interactor)
SearchResultsController::class.java -> SearchResultsController(model, config, hostServices) SearchResultsController::class.java -> SearchResultsController(model, interactor, config, hostServices)
else -> error("Trying to instantiate unknown controller type $type") else -> error("Trying to instantiate unknown controller type $type")
} }
} }

View File

@ -1,7 +1,7 @@
package com.marvinelsen.willow.domain package com.marvinelsen.willow.domain
import com.marvinelsen.willow.domain.entities.DictionaryEntry import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.domain.entities.Sentence import com.marvinelsen.willow.domain.objects.Sentence
interface Dictionary { interface Dictionary {
suspend fun search(query: String, searchMode: SearchMode): List<DictionaryEntry> suspend fun search(query: String, searchMode: SearchMode): List<DictionaryEntry>

View File

@ -8,8 +8,8 @@ import com.github.houbb.segment.support.segment.impl.Segments
import com.github.houbb.segment.support.segment.mode.impl.SegmentModes import com.github.houbb.segment.support.segment.mode.impl.SegmentModes
import com.github.houbb.segment.support.segment.result.impl.SegmentResultHandlers import com.github.houbb.segment.support.segment.result.impl.SegmentResultHandlers
import com.github.houbb.segment.support.tagging.pos.tag.impl.SegmentPosTaggings import com.github.houbb.segment.support.tagging.pos.tag.impl.SegmentPosTaggings
import com.marvinelsen.willow.domain.entities.DictionaryEntry import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.domain.entities.Sentence import com.marvinelsen.willow.domain.objects.Sentence
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -31,21 +31,21 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
.posData(SegmentPosDatas.define()) .posData(SegmentPosDatas.define())
private val searchSimplifiedSql = """ private val searchSimplifiedSql = """
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions, tones
FROM entry FROM entry
WHERE simplified GLOB ? WHERE simplified GLOB ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
private val searchTraditionalSql = """ private val searchTraditionalSql = """
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions, tones
FROM entry FROM entry
WHERE traditional GLOB ? WHERE traditional GLOB ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
private val searchPinyinSql = """ private val searchPinyinSql = """
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions, tones
FROM entry FROM entry
WHERE searchable_pinyin GLOB ? WHERE searchable_pinyin GLOB ?
OR searchable_pinyin_with_tone_numbers GLOB ? OR searchable_pinyin_with_tone_numbers GLOB ?
@ -54,21 +54,21 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
private val searchSegmentsSql = """ private val searchSegmentsSql = """
WITH cte(id, segment) AS (VALUES ?) WITH cte(id, segment) AS (VALUES ?)
SELECT entry.traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT entry.traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions, tones
FROM entry INNER JOIN cte FROM entry INNER JOIN cte
ON cte.segment = entry.traditional OR cte.segment = entry.simplified ON cte.segment = entry.traditional OR cte.segment = entry.simplified
ORDER BY cte.id ORDER BY cte.id
""".trimIndent() """.trimIndent()
private val findWordsBeginningSql = """ private val findWordsBeginningSql = """
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions, tones
FROM entry FROM entry
WHERE traditional GLOB ? WHERE traditional GLOB ?
ORDER BY character_count ASC ORDER BY character_count ASC
""".trimIndent() """.trimIndent()
private val findWordsContainingSql = """ private val findWordsContainingSql = """
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions, tones
FROM entry FROM entry
WHERE traditional LIKE ? WHERE traditional LIKE ?
ORDER BY character_count ASC ORDER BY character_count ASC
@ -76,7 +76,7 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
private val findCharactersSql = """ private val findCharactersSql = """
WITH cte(id, character, pinyin) AS (VALUES ?) WITH cte(id, character, pinyin) AS (VALUES ?)
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions, tones
FROM entry INNER JOIN cte FROM entry INNER JOIN cte
ON cte.character = entry.traditional ON cte.character = entry.traditional
WHERE cte.pinyin = entry.pinyin_with_tone_numbers WHERE cte.pinyin = entry.pinyin_with_tone_numbers
@ -214,6 +214,7 @@ private fun ResultSet.toDictionaryEntry() = DictionaryEntry(
cedictDefinitions = Json.decodeFromString(this.getString(6)), cedictDefinitions = Json.decodeFromString(this.getString(6)),
crossStraitsDefinitions = Json.decodeFromString(this.getString(7)), crossStraitsDefinitions = Json.decodeFromString(this.getString(7)),
moedictDefinitions = Json.decodeFromString(this.getString(8)), moedictDefinitions = Json.decodeFromString(this.getString(8)),
tones = Json.decodeFromString(this.getString(9)),
) )
private fun ResultSet.toListOfDictionaryEntries() = buildList { private fun ResultSet.toListOfDictionaryEntries() = buildList {

View File

@ -1,7 +1,7 @@
package com.marvinelsen.willow.domain.entities package com.marvinelsen.willow.domain.objects
import com.marvinelsen.willow.domain.entities.definitions.CrossStraitsDefinition import com.marvinelsen.willow.domain.objects.definitions.CrossStraitsDefinition
import com.marvinelsen.willow.domain.entities.definitions.MoedictDefinition import com.marvinelsen.willow.domain.objects.definitions.MoedictDefinition
data class DictionaryEntry( data class DictionaryEntry(
val traditional: String, val traditional: String,
@ -12,4 +12,5 @@ data class DictionaryEntry(
val cedictDefinitions: List<List<String>>, val cedictDefinitions: List<List<String>>,
val crossStraitsDefinitions: List<CrossStraitsDefinition>, val crossStraitsDefinitions: List<CrossStraitsDefinition>,
val moedictDefinitions: List<MoedictDefinition>, val moedictDefinitions: List<MoedictDefinition>,
val tones: List<Int>,
) )

View File

@ -1,4 +1,4 @@
package com.marvinelsen.willow.domain.entities package com.marvinelsen.willow.domain.objects
data class Sentence( data class Sentence(
val traditional: String, val traditional: String,

View File

@ -1,4 +1,4 @@
package com.marvinelsen.willow.domain.entities.definitions package com.marvinelsen.willow.domain.objects.definitions
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package com.marvinelsen.willow.domain.entities.definitions package com.marvinelsen.willow.domain.objects.definitions
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -2,8 +2,8 @@ package com.marvinelsen.willow.ui.cells
import com.marvinelsen.willow.config.Config import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Pronunciation import com.marvinelsen.willow.config.Pronunciation
import com.marvinelsen.willow.config.Script import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.domain.entities.DictionaryEntry import com.marvinelsen.willow.ui.components.TextFlowWithToneColors
import com.marvinelsen.willow.ui.util.createContextMenuForEntry import com.marvinelsen.willow.ui.util.createContextMenuForEntry
import javafx.application.HostServices import javafx.application.HostServices
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
@ -39,8 +39,8 @@ private class EntryCell(
private val hostServices: HostServices, private val hostServices: HostServices,
) : ListCell<DictionaryEntry?>() { ) : ListCell<DictionaryEntry?>() {
private val labelHeadword by lazy { private val textFlowHeadword by lazy {
Label().apply { TextFlowWithToneColors(config).apply {
styleClass.add("headword") styleClass.add("headword")
styleProperty().bind( styleProperty().bind(
Bindings.concat( Bindings.concat(
@ -83,7 +83,7 @@ private class EntryCell(
} }
private val flowPane by lazy { private val flowPane by lazy {
FlowPane(labelHeadword, labelPronunciation).apply { FlowPane(textFlowHeadword, labelPronunciation).apply {
hgap = FLOW_PANE_HGAP hgap = FLOW_PANE_HGAP
rowValignment = VPos.BASELINE rowValignment = VPos.BASELINE
} }
@ -105,17 +105,8 @@ private class EntryCell(
graphic = null graphic = null
contextMenu = null contextMenu = null
} else { } else {
labelHeadword.textProperty().bind( textFlowHeadword.entry.value = entry
Bindings.createStringBinding(
{
when (config.script.value!!) {
Script.SIMPLIFIED -> entry.simplified
Script.TRADITIONAL -> entry.traditional
}
},
config.script
)
)
labelPronunciation.textProperty().bind( labelPronunciation.textProperty().bind(
Bindings.createStringBinding( Bindings.createStringBinding(
{ {
@ -144,6 +135,7 @@ private class EntryCell(
else -> error("No definition for entry") else -> error("No definition for entry")
} }
labelDefinition.text = definition labelDefinition.text = definition
contextMenu = createContextMenuForEntry(entry, resources, hostServices) contextMenu = createContextMenuForEntry(entry, resources, hostServices)

View File

@ -2,7 +2,7 @@ package com.marvinelsen.willow.ui.cells
import com.marvinelsen.willow.config.Config import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Script import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.domain.entities.Sentence import com.marvinelsen.willow.domain.objects.Sentence
import com.marvinelsen.willow.ui.util.createContextMenuForSentence import com.marvinelsen.willow.ui.util.createContextMenuForSentence
import javafx.application.HostServices import javafx.application.HostServices
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings

View File

@ -0,0 +1,55 @@
package com.marvinelsen.willow.ui.components
import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.domain.objects.DictionaryEntry
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.paint.Color
import javafx.scene.text.Text
import javafx.scene.text.TextFlow
class TextFlowWithToneColors(private val config: Config) : TextFlow() {
init {
config.script.addListener { _, _, newValue ->
updateTextFlow(newValue)
}
}
val entry = SimpleObjectProperty<DictionaryEntry?>().apply {
addListener { _, _, _ ->
updateTextFlow(config.script.value)
}
}
private val invalidCharacterRegex = """[a-zA-Z0-9·\[\]{}()+]""".toRegex()
@Suppress("MagicNumber")
private fun updateTextFlow(script: Script) {
children.clear()
val headword = when (script) {
Script.SIMPLIFIED -> entry.value?.simplified
Script.TRADITIONAL -> entry.value?.traditional
} ?: return
var index = 0
for (char in headword) {
val text = Text(char.toString())
val color = if (invalidCharacterRegex.matches(char.toString())) {
Color.BLACK
} else {
when (entry.value?.tones?.getOrNull(index++)) {
1 -> Color.RED
2 -> Color.GREEN
3 -> Color.BLUE
4 -> Color.PURPLE
5 -> Color.DARKGRAY
else -> Color.BLACK
}
}
text.fill = color
children.add(text)
}
}
}

View File

@ -1,11 +1,12 @@
package com.marvinelsen.willow.ui.controllers package com.marvinelsen.willow.ui.controllers
import com.marvinelsen.willow.Interactor
import com.marvinelsen.willow.Model import com.marvinelsen.willow.Model
import com.marvinelsen.willow.config.Config import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Pronunciation import com.marvinelsen.willow.config.Pronunciation
import com.marvinelsen.willow.config.Script import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.domain.entities.DictionaryEntry import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.domain.entities.Sentence import com.marvinelsen.willow.domain.objects.Sentence
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
import com.marvinelsen.willow.ui.cells.SentenceCellFactory import com.marvinelsen.willow.ui.cells.SentenceCellFactory
import com.marvinelsen.willow.ui.util.createContextMenuForEntry import com.marvinelsen.willow.ui.util.createContextMenuForEntry
@ -34,6 +35,7 @@ import java.util.ResourceBundle
@Suppress("UnusedPrivateMember", "TooManyFunctions") @Suppress("UnusedPrivateMember", "TooManyFunctions")
class DetailsController( class DetailsController(
private val model: Model, private val model: Model,
private val interactor: Interactor,
private val config: Config, private val config: Config,
private val hostServices: HostServices, private val hostServices: HostServices,
) { ) {
@ -112,14 +114,13 @@ class DetailsController(
textProperty().bind( textProperty().bind(
Bindings.createStringBinding( Bindings.createStringBinding(
{ {
val selectedEntry = model.selectedEntry.value
when (config.script.value!!) { when (config.script.value!!) {
Script.SIMPLIFIED -> selectedEntry?.simplified Script.SIMPLIFIED -> model.selectedEntry?.simplified
Script.TRADITIONAL -> selectedEntry?.traditional Script.TRADITIONAL -> model.selectedEntry?.traditional
} }
}, },
config.script, config.script,
model.selectedEntry model.selectedEntryProperty()
) )
) )
styleProperty().bind( styleProperty().bind(
@ -137,15 +138,14 @@ class DetailsController(
textProperty().bind( textProperty().bind(
Bindings.createStringBinding( Bindings.createStringBinding(
{ {
val selectedEntry = model.selectedEntry.value
when (config.details.pronunciation.value!!) { when (config.details.pronunciation.value!!) {
Pronunciation.PINYIN_WITH_TONE_MARKS -> selectedEntry?.pinyinWithToneMarks Pronunciation.PINYIN_WITH_TONE_MARKS -> model.selectedEntry?.pinyinWithToneMarks
Pronunciation.PINYIN_WITH_TONE_NUMBERS -> selectedEntry?.pinyinWithToneNumbers Pronunciation.PINYIN_WITH_TONE_NUMBERS -> model.selectedEntry?.pinyinWithToneNumbers
Pronunciation.ZHUYIN -> selectedEntry?.zhuyin Pronunciation.ZHUYIN -> model.selectedEntry?.zhuyin
} }
}, },
config.details.pronunciation, config.details.pronunciation,
model.selectedEntry model.selectedEntryProperty()
) )
) )
styleProperty().bind( styleProperty().bind(
@ -160,16 +160,16 @@ class DetailsController(
private fun initializeTabPaneDetails() { private fun initializeTabPaneDetails() {
tabPaneDetails.apply { tabPaneDetails.apply {
disableProperty().bind(Bindings.isNull(model.selectedEntry)) disableProperty().bind(Bindings.isNull(model.selectedEntryProperty()))
selectionModel.selectedItemProperty().addListener { _, _, selectedTab -> selectionModel.selectedItemProperty().addListener { _, _, selectedTab ->
if (model.selectedEntry.value == null) return@addListener if (model.selectedEntry == null) return@addListener
lazyUpdateTabContent(selectedTab.id) lazyUpdateTabContent(selectedTab.id)
} }
} }
model.selectedEntry.addListener { _, _, newEntry -> model.selectedEntryProperty().addListener { _, _, newEntry ->
if (newEntry == null) return@addListener if (newEntry == null) return@addListener
tabPaneDetails.selectionModel.select(0) tabPaneDetails.selectionModel.select(0)
@ -178,9 +178,9 @@ class DetailsController(
tabCharacters.disableProperty().bind( tabCharacters.disableProperty().bind(
Bindings.createBooleanBinding( Bindings.createBooleanBinding(
{ {
(model.selectedEntry.value?.traditional?.length ?: 0) < 2 (model.selectedEntry?.traditional?.length ?: 0) < 2
}, },
model.selectedEntry model.selectedEntryProperty()
) )
) )
} }
@ -190,12 +190,12 @@ class DetailsController(
cellFactory = SentenceCellFactory(resources, config, hostServices) cellFactory = SentenceCellFactory(resources, config, hostServices)
items = model.sentences items = model.sentences
disableProperty().bind(Bindings.or(model.isFindingSentences, Bindings.isEmpty(model.sentences))) disableProperty().bind(Bindings.or(model.isFindingSentencesProperty(), Bindings.isEmpty(model.sentences)))
} }
progressIndicatorSentences.visibleProperty().bind(model.isFindingSentences) progressIndicatorSentences.visibleProperty().bind(model.isFindingSentencesProperty())
labelNoSentencesFound labelNoSentencesFound
.visibleProperty() .visibleProperty()
.bind(Bindings.and(Bindings.isEmpty(model.sentences), Bindings.not(model.isFindingSentences))) .bind(Bindings.and(Bindings.isEmpty(model.sentences), Bindings.not(model.isFindingSentencesProperty())))
} }
private fun initializeListViewWordsContaining() { private fun initializeListViewWordsContaining() {
@ -203,17 +203,24 @@ class DetailsController(
cellFactory = DictionaryEntryCellFactory(resources, config, hostServices) cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.wordsContaining items = model.wordsContaining
disableProperty().bind(Bindings.or(model.isFindingWordsContaining, Bindings.isEmpty(model.wordsContaining))) disableProperty().bind(
Bindings.or(model.isFindingWordsContainingProperty(), Bindings.isEmpty(model.wordsContaining))
)
selectionModel.selectedItemProperty().addListener { _, _, newEntry -> selectionModel.selectedItemProperty().addListener { _, _, newEntry ->
if (newEntry == null) return@addListener if (newEntry == null) return@addListener
model.selectEntry(newEntry) interactor.deepDive(newEntry)
} }
} }
progressIndicatorWordsContaining.visibleProperty().bind(model.isFindingWordsContaining) progressIndicatorWordsContaining.visibleProperty().bind(model.isFindingWordsContainingProperty())
labelNoWordsContainingFound labelNoWordsContainingFound
.visibleProperty() .visibleProperty()
.bind(Bindings.and(Bindings.isEmpty(model.wordsContaining), Bindings.not(model.isFindingWordsContaining))) .bind(
Bindings.and(
Bindings.isEmpty(model.wordsContaining),
Bindings.not(model.isFindingWordsContainingProperty())
)
)
} }
private fun initializeListViewWordsBeginning() { private fun initializeListViewWordsBeginning() {
@ -221,17 +228,24 @@ class DetailsController(
cellFactory = DictionaryEntryCellFactory(resources, config, hostServices) cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.wordsBeginning items = model.wordsBeginning
disableProperty().bind(Bindings.or(model.isFindingWordsBeginning, Bindings.isEmpty(model.wordsBeginning))) disableProperty().bind(
Bindings.or(model.isFindingWordsBeginningProperty(), Bindings.isEmpty(model.wordsBeginning))
)
selectionModel.selectedItemProperty().addListener { _, _, newEntry -> selectionModel.selectedItemProperty().addListener { _, _, newEntry ->
if (newEntry == null) return@addListener if (newEntry == null) return@addListener
model.selectEntry(newEntry) interactor.deepDive(newEntry)
} }
} }
progressIndicatorWordsBeginning.visibleProperty().bind(model.isFindingWordsBeginning) progressIndicatorWordsBeginning.visibleProperty().bind(model.isFindingWordsBeginningProperty())
labelNoWordsBeginningFound labelNoWordsBeginningFound
.visibleProperty() .visibleProperty()
.bind(Bindings.and(Bindings.isEmpty(model.wordsBeginning), Bindings.not(model.isFindingWordsBeginning))) .bind(
Bindings.and(
Bindings.isEmpty(model.wordsBeginning),
Bindings.not(model.isFindingWordsBeginningProperty())
)
)
} }
private fun initializeListViewCharacters() { private fun initializeListViewCharacters() {
@ -239,17 +253,17 @@ class DetailsController(
cellFactory = DictionaryEntryCellFactory(resources, config, hostServices) cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.characters items = model.characters
disableProperty().bind(Bindings.or(model.isFindingCharacters, Bindings.isEmpty(model.characters))) disableProperty().bind(Bindings.or(model.isFindingCharactersProperty(), Bindings.isEmpty(model.characters)))
selectionModel.selectedItemProperty().addListener { _, _, newEntry -> selectionModel.selectedItemProperty().addListener { _, _, newEntry ->
if (newEntry == null) return@addListener if (newEntry == null) return@addListener
model.selectEntry(newEntry) interactor.deepDive(newEntry)
} }
} }
progressIndicatorCharacters.visibleProperty().bind(model.isFindingCharacters) progressIndicatorCharacters.visibleProperty().bind(model.isFindingCharactersProperty())
labelNoCharactersFound labelNoCharactersFound
.visibleProperty() .visibleProperty()
.bind(Bindings.and(Bindings.isEmpty(model.characters), Bindings.not(model.isFindingCharacters))) .bind(Bindings.and(Bindings.isEmpty(model.characters), Bindings.not(model.isFindingCharactersProperty())))
} }
private fun initializeWebViewDefinition() { private fun initializeWebViewDefinition() {
@ -257,7 +271,7 @@ class DetailsController(
engine.userStyleSheetLocation = this::class.java.getResource("/css/definitions.css")!!.toExternalForm() engine.userStyleSheetLocation = this::class.java.getResource("/css/definitions.css")!!.toExternalForm()
} }
model.selectedEntry.addListener { _, _, newEntry -> model.selectedEntryProperty().addListener { _, _, newEntry ->
if (newEntry == null) return@addListener if (newEntry == null) return@addListener
webViewDefinition.engine.loadContent(createDefinitionHtml(newEntry)) webViewDefinition.engine.loadContent(createDefinitionHtml(newEntry))
@ -266,9 +280,9 @@ class DetailsController(
@FXML @FXML
private fun headerOnContextMenuRequested(contextMenuEvent: ContextMenuEvent) { private fun headerOnContextMenuRequested(contextMenuEvent: ContextMenuEvent) {
if (model.selectedEntry.value == null) return if (model.selectedEntry == null) return
createContextMenuForEntry(model.selectedEntry.value, resources, hostServices).show( createContextMenuForEntry(model.selectedEntry!!, resources, hostServices).show(
flowPaneHeader.scene.window, flowPaneHeader.scene.window,
contextMenuEvent.screenX, contextMenuEvent.screenX,
contextMenuEvent.screenY contextMenuEvent.screenY
@ -279,24 +293,24 @@ class DetailsController(
private fun lazyUpdateTabContent(selectedTabId: String?) { private fun lazyUpdateTabContent(selectedTabId: String?) {
when (selectedTabId) { when (selectedTabId) {
"tabWords" -> { "tabWords" -> {
if (!model.finishedFindingWordsContaining.value) { if (!model.finishedFindingWordsContaining) {
model.findWordsContaining() interactor.findWordsContaining()
} }
if (!model.finishedFindingWordsBeginning.value) { if (!model.finishedFindingWordsBeginning) {
model.findWordsBeginning() interactor.findWordsBeginning()
} }
} }
"tabCharacters" -> { "tabCharacters" -> {
if (model.finishedFindingCharacters.value) return if (model.finishedFindingCharacters) return
model.findCharacters() interactor.findCharacters()
} }
"tabSentences" -> { "tabSentences" -> {
if (model.finishedFindingSentences.value) return if (model.finishedFindingSentences) return
model.findSentences() interactor.findSentences()
} }
else -> {} else -> {}

View File

@ -1,5 +1,6 @@
package com.marvinelsen.willow.ui.controllers package com.marvinelsen.willow.ui.controllers
import com.marvinelsen.willow.Interactor
import com.marvinelsen.willow.Model import com.marvinelsen.willow.Model
import com.marvinelsen.willow.config.Config import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.ui.dialogs.PreferencesDialog import com.marvinelsen.willow.ui.dialogs.PreferencesDialog
@ -11,7 +12,7 @@ import javafx.scene.control.MenuItem
import java.util.ResourceBundle import java.util.ResourceBundle
@Suppress("UnusedPrivateMember") @Suppress("UnusedPrivateMember")
class MenuController(private val model: Model, private val config: Config) { class MenuController(private val model: Model, private val interactor: Interactor, private val config: Config) {
@FXML @FXML
private lateinit var resources: ResourceBundle private lateinit var resources: ResourceBundle
@ -26,8 +27,8 @@ class MenuController(private val model: Model, private val config: Config) {
@FXML @FXML
private fun initialize() { private fun initialize() {
menuItemCopyPronunciation.disableProperty().bind(Bindings.isNull(model.selectedEntry)) menuItemCopyPronunciation.disableProperty().bind(Bindings.isNull(model.selectedEntryProperty()))
menuItemCopyHeadword.disableProperty().bind(Bindings.isNull(model.selectedEntry)) menuItemCopyHeadword.disableProperty().bind(Bindings.isNull(model.selectedEntryProperty()))
} }
@FXML @FXML
@ -47,11 +48,11 @@ class MenuController(private val model: Model, private val config: Config) {
@FXML @FXML
private fun onMenuItemCopyPronunciationAction() { private fun onMenuItemCopyPronunciationAction() {
model.copyPronunciationOfSelectedEntry() interactor.copyPronunciationOfSelectedEntry()
} }
@FXML @FXML
private fun onMenuItemCopyHeadwordAction() { private fun onMenuItemCopyHeadwordAction() {
model.copyHeadwordOfSelectedEntry() interactor.copyHeadwordOfSelectedEntry()
} }
} }

View File

@ -1,5 +1,6 @@
package com.marvinelsen.willow.ui.controllers package com.marvinelsen.willow.ui.controllers
import com.marvinelsen.willow.Interactor
import com.marvinelsen.willow.Model import com.marvinelsen.willow.Model
import com.marvinelsen.willow.domain.SearchMode import com.marvinelsen.willow.domain.SearchMode
import javafx.fxml.FXML import javafx.fxml.FXML
@ -7,7 +8,7 @@ import javafx.scene.control.Button
import javafx.scene.control.TextField import javafx.scene.control.TextField
import javafx.scene.control.ToggleGroup import javafx.scene.control.ToggleGroup
class SearchController(private val model: Model) { class SearchController(private val model: Model, private val interactor: Interactor) {
@FXML @FXML
private lateinit var buttonUndo: Button private lateinit var buttonUndo: Button
@ -38,14 +39,14 @@ class SearchController(private val model: Model) {
return return
} }
model.search(searchQuery, searchMode) interactor.search(searchQuery, searchMode)
} }
fun onButtonRedoAction() { fun onButtonRedoAction() {
model.redoSelection() interactor.redoSelection()
} }
fun onButtonUndoAction() { fun onButtonUndoAction() {
model.undoSelection() interactor.undoSelection()
} }
} }

View File

@ -1,8 +1,9 @@
package com.marvinelsen.willow.ui.controllers package com.marvinelsen.willow.ui.controllers
import com.marvinelsen.willow.Interactor
import com.marvinelsen.willow.Model import com.marvinelsen.willow.Model
import com.marvinelsen.willow.config.Config import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.domain.entities.DictionaryEntry import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
import javafx.application.HostServices import javafx.application.HostServices
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
@ -14,6 +15,7 @@ import java.util.ResourceBundle
class SearchResultsController( class SearchResultsController(
private val model: Model, private val model: Model,
private val interactor: Interactor,
private val config: Config, private val config: Config,
private val hostServices: HostServices, private val hostServices: HostServices,
) { ) {
@ -38,18 +40,18 @@ class SearchResultsController(
listViewSearchResults.items = model.searchResults listViewSearchResults.items = model.searchResults
listViewSearchResults listViewSearchResults
.disableProperty() .disableProperty()
.bind(Bindings.or(model.isSearching, Bindings.isEmpty(model.searchResults))) .bind(Bindings.or(model.isSearchingProperty(), Bindings.isEmpty(model.searchResults)))
progressIndicatorEntries.visibleProperty().bind(model.isSearching) progressIndicatorEntries.visibleProperty().bind(model.isSearchingProperty())
labelNoEntriesFound labelNoEntriesFound
.visibleProperty() .visibleProperty()
.bind(Bindings.and(Bindings.isEmpty(model.searchResults), Bindings.not(model.isSearching))) .bind(Bindings.and(Bindings.isEmpty(model.searchResults), Bindings.not(model.isSearchingProperty())))
listViewSearchResults.selectionModel.selectedItemProperty().addListener { _, _, newValue: DictionaryEntry? -> listViewSearchResults.selectionModel.selectedItemProperty().addListener { _, _, newValue: DictionaryEntry? ->
if (newValue == null) { if (newValue == null) {
return@addListener return@addListener
} }
model.selectEntry(newValue) interactor.normalSelect(newValue)
} }
} }
} }

View File

@ -49,13 +49,6 @@ class PreferencesDialogController(private val config: Config) {
@FXML @FXML
private lateinit var spinnerDefinitionFontSizeSearchResults: Spinner<Int> private lateinit var spinnerDefinitionFontSizeSearchResults: Spinner<Int>
private val entryHeadwordFontSizeObjectProperty = config.details.headwordFontSize.asObject()
private val entryPronunciationFontSizeObjectProperty = config.details.pronunciationFontSize.asObject()
private val searchResultHeadwordFontSizeObjectProperty = config.searchResults.headwordFontSize.asObject()
private val searchResultPronunciationFontSizeObjectProperty = config.searchResults.pronunciationFontSize.asObject()
private val searchResultDefinitionFontSizeObjectProperty = config.searchResults.definitionFontSize.asObject()
@FXML @FXML
private fun initialize() { private fun initialize() {
comboBoxLocale.items.addAll( comboBoxLocale.items.addAll(
@ -82,27 +75,42 @@ class PreferencesDialogController(private val config: Config) {
with(spinnerHeadwordFontSizeDetails) { with(spinnerHeadwordFontSizeDetails) {
editor.textFormatter = FontSizeTextFormatter() editor.textFormatter = FontSizeTextFormatter()
valueFactory.valueProperty().bindBidirectional(entryHeadwordFontSizeObjectProperty) valueFactory.value = config.details.headwordFontSize.value
valueProperty().addListener { _, _, newValue ->
config.details.headwordFontSize.value = newValue
}
} }
with(spinnerPronunciationFontSizeDetails) { with(spinnerPronunciationFontSizeDetails) {
editor.textFormatter = FontSizeTextFormatter() editor.textFormatter = FontSizeTextFormatter()
valueFactory.valueProperty().bindBidirectional(entryPronunciationFontSizeObjectProperty) valueFactory.value = config.details.pronunciationFontSize.value
valueProperty().addListener { _, _, newValue ->
config.details.pronunciationFontSize.value = newValue
}
} }
with(spinnerHeadwordFontSizeSearchResults) { with(spinnerHeadwordFontSizeSearchResults) {
editor.textFormatter = FontSizeTextFormatter() editor.textFormatter = FontSizeTextFormatter()
valueFactory.valueProperty().bindBidirectional(searchResultHeadwordFontSizeObjectProperty) valueFactory.value = config.searchResults.headwordFontSize.value
valueProperty().addListener { _, _, newValue ->
config.searchResults.headwordFontSize.value = newValue
}
} }
with(spinnerPronunciationFontSizeSearchResults) { with(spinnerPronunciationFontSizeSearchResults) {
editor.textFormatter = FontSizeTextFormatter() editor.textFormatter = FontSizeTextFormatter()
valueFactory.valueProperty().bindBidirectional(searchResultPronunciationFontSizeObjectProperty) valueFactory.value = config.searchResults.pronunciationFontSize.value
valueProperty().addListener { _, _, newValue ->
config.searchResults.pronunciationFontSize.value = newValue
}
} }
with(spinnerDefinitionFontSizeSearchResults) { with(spinnerDefinitionFontSizeSearchResults) {
editor.textFormatter = FontSizeTextFormatter() editor.textFormatter = FontSizeTextFormatter()
valueFactory.valueProperty().bindBidirectional(searchResultDefinitionFontSizeObjectProperty) valueFactory.value = config.searchResults.definitionFontSize.value
valueProperty().addListener { _, _, newValue ->
config.searchResults.definitionFontSize.value = newValue
}
} }
} }
} }

View File

@ -15,7 +15,7 @@ class UndoManager {
undoStack.push(command).execute() undoStack.push(command).execute()
canUndoProperty.value = undoStack.size > 1 canUndoProperty.value = !undoStack.isEmpty()
canRedoProperty.value = false canRedoProperty.value = false
} }
@ -24,7 +24,7 @@ class UndoManager {
redoStack.push(undoStack.pop()).undo() redoStack.push(undoStack.pop()).undo()
canUndoProperty.value = undoStack.size > 1 canUndoProperty.value = !undoStack.isEmpty()
canRedoProperty.value = true canRedoProperty.value = true
} }
@ -36,4 +36,11 @@ class UndoManager {
canUndoProperty.value = true canUndoProperty.value = true
canRedoProperty.value = !redoStack.isEmpty() canRedoProperty.value = !redoStack.isEmpty()
} }
fun reset() {
undoStack.clear()
redoStack.clear()
canUndoProperty.value = false
canRedoProperty.value = false
}
} }

View File

@ -1,7 +1,7 @@
package com.marvinelsen.willow.ui.util package com.marvinelsen.willow.ui.util
import com.marvinelsen.willow.domain.entities.DictionaryEntry import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.domain.entities.Sentence import com.marvinelsen.willow.domain.objects.Sentence
import javafx.application.HostServices import javafx.application.HostServices
import javafx.event.EventHandler import javafx.event.EventHandler
import javafx.scene.control.ContextMenu import javafx.scene.control.ContextMenu