Compare commits

..

No commits in common. "a2dfc858f46804b913db2e76d3cbd356b15f8951" and "52acfeb5da5f23271693fea89caeb12752839897" have entirely different histories.

19 changed files with 251 additions and 399 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ class UndoManager {
undoStack.push(command).execute()
canUndoProperty.value = !undoStack.isEmpty()
canUndoProperty.value = undoStack.size > 1
canRedoProperty.value = false
}
@ -24,7 +24,7 @@ class UndoManager {
redoStack.push(undoStack.pop()).undo()
canUndoProperty.value = !undoStack.isEmpty()
canUndoProperty.value = undoStack.size > 1
canRedoProperty.value = true
}
@ -36,11 +36,4 @@ class UndoManager {
canUndoProperty.value = true
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
import com.marvinelsen.willow.domain.objects.DictionaryEntry
import com.marvinelsen.willow.domain.objects.Sentence
import com.marvinelsen.willow.domain.entities.DictionaryEntry
import com.marvinelsen.willow.domain.entities.Sentence
import javafx.application.HostServices
import javafx.event.EventHandler
import javafx.scene.control.ContextMenu