Compare commits

..

No commits in common. "cfff342a3afa429b1dd6194f4a5dd864df58474d" and "1b5c32c3207f68372f817aa26a3a664a43b4bcd4" have entirely different histories.

12 changed files with 287 additions and 180 deletions

View File

@ -24,8 +24,6 @@ dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.html.jvm)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.javafx)
implementation(libs.segment)

View File

@ -10,7 +10,6 @@ sqlite-jdbc = "3.46.0.1"
kotlinx-serialization-json = "1.7.1"
kotlinx-html-jvm = "0.11.0"
kotlinx-coroutines = "1.9.0"
segment = "0.3.1"
@ -28,8 +27,6 @@ sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
kotlinx-html-jvm = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version.ref = "kotlinx-html-jvm" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-javafx = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-javafx", version.ref = "kotlinx-coroutines" }
segment = { module = "com.github.houbb:segment", version.ref = "segment" }
slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "slf4j" }

View File

@ -1,25 +1,28 @@
package com.marvinelsen.willow
import com.marvinelsen.willow.domain.SearchMode
import com.marvinelsen.willow.domain.SqliteDictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.SentenceFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.services.FindCharacterService
import com.marvinelsen.willow.ui.services.FindSentencesService
import com.marvinelsen.willow.ui.services.FindWordsBeginningService
import com.marvinelsen.willow.ui.services.FindWordsContainingService
import com.marvinelsen.willow.ui.services.SearchService
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
import javafx.event.EventHandler
class Model(
private val dictionary: SqliteDictionary,
private val searchService: SearchService,
private val findWordsBeginningService: FindWordsBeginningService,
private val findWordsContainingService: FindWordsContainingService,
private val findCharacterService: FindCharacterService,
private val findSentencesService: FindSentencesService,
) {
private val internalSelectedEntry: ObjectProperty<DictionaryEntryFx> = SimpleObjectProperty()
private val internalSearchResults: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
@ -28,12 +31,6 @@ class Model(
private val internalCharacters: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
private val internalSentences: ObservableList<SentenceFx> = FXCollections.observableArrayList()
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)
val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry
val searchResults: ObservableList<DictionaryEntryFx> =
@ -47,68 +44,54 @@ class Model(
val sentences: ObservableList<SentenceFx> =
FXCollections.unmodifiableObservableList(internalSentences)
val isSearching: ReadOnlyBooleanProperty = internalIsSearching
val isFindingWordsBeginning: ReadOnlyBooleanProperty = internalIsFindingWordsBeginning
val isFindingWordsContaining: ReadOnlyBooleanProperty = internalIsFindingWordsContaining
val isFindingCharacters: ReadOnlyBooleanProperty = internalIsFindingCharacters
val isFindingSentences: ReadOnlyBooleanProperty = internalIsFindingSentences
val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty()
val isFindingWordsBeginning: ReadOnlyBooleanProperty = findWordsBeginningService.runningProperty()
val isFindingWordsContaining: ReadOnlyBooleanProperty = findWordsContainingService.runningProperty()
val isFindingCharacters: ReadOnlyBooleanProperty = findCharacterService.runningProperty()
val isFindingSentences: ReadOnlyBooleanProperty = findSentencesService.runningProperty()
private val coroutineScope = MainScope()
init {
searchService.onSucceeded = EventHandler {
internalSearchResults.setAll(searchService.value)
}
findWordsBeginningService.onSucceeded = EventHandler {
internalWordsBeginning.setAll(findWordsBeginningService.value)
}
findWordsContainingService.onSucceeded = EventHandler {
internalWordsContaining.setAll(findWordsContainingService.value)
}
findCharacterService.onSucceeded = EventHandler {
internalCharacters.setAll(findCharacterService.value)
}
findSentencesService.onSucceeded = EventHandler {
internalSentences.setAll(findSentencesService.value)
}
}
fun search(query: String, searchMode: SearchMode) {
coroutineScope.launch {
internalIsSearching.value = true
internalSearchResults.setAll(dictionary.search(query, searchMode).map { it.toFx() })
internalIsSearching.value = false
}
searchService.searchQuery = query
searchService.searchMode = searchMode
searchService.restart()
}
fun findWordsBeginning() {
coroutineScope.launch {
internalIsFindingWordsBeginning.value = true
internalWordsBeginning.setAll(
dictionary
.findWordsBeginningWith(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingWordsBeginning.value = false
}
findWordsBeginningService.entry = internalSelectedEntry.value
findWordsBeginningService.restart()
}
fun findWordsContaining() {
coroutineScope.launch {
internalIsFindingWordsContaining.value = true
internalWordsContaining.setAll(
dictionary
.findWordsContaining(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingWordsContaining.value = false
}
findWordsContainingService.entry = internalSelectedEntry.value
findWordsContainingService.restart()
}
fun findCharacters() {
coroutineScope.launch {
internalIsFindingCharacters.value = true
internalCharacters.setAll(
dictionary
.findCharactersOf(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingCharacters.value = false
}
findCharacterService.entry = internalSelectedEntry.value
findCharacterService.restart()
}
fun findSentences() {
coroutineScope.launch {
internalIsFindingSentences.value = true
internalSentences.setAll(
dictionary
.findExampleSentencesFor(internalSelectedEntry.value.toDomain())
.map { it.toFx() }
)
internalIsFindingSentences.value = false
}
findSentencesService.entry = internalSelectedEntry.value
findSentencesService.restart()
}
fun selectEntry(entry: DictionaryEntryFx) {

View File

@ -7,6 +7,11 @@ import com.marvinelsen.willow.ui.controllers.MainController
import com.marvinelsen.willow.ui.controllers.MenuController
import com.marvinelsen.willow.ui.controllers.SearchController
import com.marvinelsen.willow.ui.controllers.SearchResultsController
import com.marvinelsen.willow.ui.services.FindCharacterService
import com.marvinelsen.willow.ui.services.FindSentencesService
import com.marvinelsen.willow.ui.services.FindWordsBeginningService
import com.marvinelsen.willow.ui.services.FindWordsContainingService
import com.marvinelsen.willow.ui.services.SearchService
import javafx.application.Application
import javafx.fxml.FXMLLoader
import javafx.scene.Scene
@ -40,8 +45,17 @@ class WillowApplication : Application() {
autoCommit = false
}
val dictionary = SqliteDictionary(connection)
val searchService = SearchService(dictionary)
val findWordsBeginningService = FindWordsBeginningService(dictionary)
val findWordsContainingService = FindWordsContainingService(dictionary)
val findCharacterService = FindCharacterService(dictionary)
val findSentenceService = FindSentencesService(dictionary)
val model = Model(
dictionary,
searchService,
findWordsBeginningService,
findWordsContainingService,
findCharacterService,
findSentenceService
)
val config = Config()
config.load()

View File

@ -1,12 +1,12 @@
package com.marvinelsen.willow.domain
interface Dictionary {
suspend fun search(query: String, searchMode: SearchMode): List<DictionaryEntry>
fun search(query: String, searchMode: SearchMode): List<DictionaryEntry>
suspend fun findWordsBeginningWith(entry: DictionaryEntry): List<DictionaryEntry>
suspend fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry>
fun findWordsBeginning(entry: DictionaryEntry): List<DictionaryEntry>
fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry>
suspend fun findCharactersOf(entry: DictionaryEntry): List<DictionaryEntry>
fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry>
suspend fun findExampleSentencesFor(entry: DictionaryEntry): List<Sentence>
fun findSentencesContaining(entry: DictionaryEntry): List<Sentence>
}

View File

@ -8,18 +8,14 @@ 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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet
class SqliteDictionary(private val connection: Connection) : Dictionary {
private val whitespaceRegex = """\s+""".toRegex()
private val dispatcher = Dispatchers.IO
.limitedParallelism(1)
private val segmentBs = SegmentBs.newInstance()
.segment(Segments.defaults())
.segmentData(SegmentPhraseDatas.define())
@ -28,29 +24,41 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
.posTagging(SegmentPosTaggings.simple())
.posData(SegmentPosDatas.define())
private val searchSimplifiedSql = """
private val searchSimplifiedPreparedStatement: PreparedStatement by lazy {
connection.prepareStatement(
"""
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 = """
private val searchTraditionalPreparedStatement: PreparedStatement by lazy {
connection.prepareStatement(
"""
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 = """
private val searchPinyinPreparedStatement: PreparedStatement by lazy {
connection.prepareStatement(
"""
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 ?
ORDER BY character_count ASC
""".trimIndent()
)
}
private val searchSegmentsSql = """
private val searchSegments = """
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
FROM entry INNER JOIN cte
@ -58,113 +66,113 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
ORDER BY cte.id
""".trimIndent()
private val findWordsBeginningSql = """
private val findWordsBeginning: PreparedStatement by lazy {
connection.prepareStatement(
"""
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 = """
private val findWordsContaining: PreparedStatement by lazy {
connection.prepareStatement(
"""
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
""".trimIndent()
)
}
private val findCharactersSql = """
WITH cte(id, character, pinyin) AS (VALUES ?)
private val findCharacters = """
WITH cte(id, character, syllable) AS (VALUES ?)
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
WHERE cte.syllable = entry.pinyin_with_tone_numbers
ORDER BY cte.id
""".trimIndent()
private val findExampleSentenceSql = """
private val findSentences: PreparedStatement by lazy {
connection.prepareStatement(
"""
SELECT traditional, simplified
FROM sentence
WHERE traditional LIKE ?
ORDER BY character_count ASC
""".trimIndent()
)
}
override suspend fun search(query: String, searchMode: SearchMode) = withContext(dispatcher) {
when (searchMode) {
override fun search(query: String, searchMode: SearchMode) = when (searchMode) {
SearchMode.SIMPLIFIED -> searchSimplified(query)
SearchMode.TRADITIONAL -> searchTraditional(query)
SearchMode.PINYIN -> searchPinyin(query)
SearchMode.SEGMENTS -> searchSegmentsOf(query)
}
SearchMode.SEGMENTS -> searchSegments(query)
}
private fun searchSimplified(simplified: String) =
connection.prepareStatement(searchSimplifiedSql).use { preparedStatement ->
preparedStatement.setString(1, "$simplified*")
private fun searchSimplified(query: String): List<DictionaryEntry> {
searchSimplifiedPreparedStatement.setString(1, "$query*")
preparedStatement.executeQuery().use {
it.toListOfDictionaryEntries()
}
val resultSet: ResultSet = searchSimplifiedPreparedStatement.executeQuery()
return resultSet.toListOfDictionaryEntries()
}
private fun searchTraditional(traditional: String) =
connection.prepareStatement(searchTraditionalSql).use { preparedStatement ->
preparedStatement.setString(1, "$traditional*")
private fun searchTraditional(query: String): List<DictionaryEntry> {
searchTraditionalPreparedStatement.setString(1, "$query*")
preparedStatement.executeQuery().use {
it.toListOfDictionaryEntries()
}
val resultSet: ResultSet = searchTraditionalPreparedStatement.executeQuery()
return resultSet.toListOfDictionaryEntries()
}
private fun searchPinyin(pinyin: String): List<DictionaryEntry> {
val sanitizedPinyin = pinyin.lowercase().replace(whitespaceRegex, "")
private fun searchPinyin(query: String): List<DictionaryEntry> {
val sanitizedQuery = query.lowercase().replace(whitespaceRegex, "")
return connection.prepareStatement(searchPinyinSql).use { preparedStatement ->
preparedStatement.setString(1, "$sanitizedPinyin*")
preparedStatement.setString(2, "$sanitizedPinyin*")
searchPinyinPreparedStatement.setString(1, "$sanitizedQuery*")
searchPinyinPreparedStatement.setString(2, "$sanitizedQuery*")
preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfDictionaryEntries()
}
}
val resultSet: ResultSet = searchPinyinPreparedStatement.executeQuery()
return resultSet.toListOfDictionaryEntries()
}
private fun searchSegmentsOf(phrase: String): List<DictionaryEntry> {
private fun searchSegments(phrase: String): List<DictionaryEntry> {
val segments = segmentBs.segment(phrase, SegmentResultHandlers.word())
val segmentsListString = segments
.mapIndexed { index, segment -> "($index, '$segment')" }
.mapIndexed { index, s -> "($index, '$s')" }
.joinToString(",")
val query = searchSegmentsSql.replace("?", segmentsListString)
val query = searchSegments.replace("?", segmentsListString)
return connection.createStatement().use { statement ->
statement.executeQuery(query).use { resultSet ->
resultSet.toListOfDictionaryEntries()
}
}
val resultSet: ResultSet = connection.createStatement().executeQuery(query)
return resultSet.toListOfDictionaryEntries()
}
override suspend fun findWordsContaining(entry: DictionaryEntry) = withContext(dispatcher) {
connection.prepareStatement(findWordsContainingSql).use { preparedStatement ->
preparedStatement.setString(1, "_%${entry.traditional}%")
override fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry> {
findWordsContaining.setString(1, "_%${entry.traditional}%")
preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfDictionaryEntries()
}
}
val resultSet: ResultSet = findWordsContaining.executeQuery()
return resultSet.toListOfDictionaryEntries()
}
override suspend fun findWordsBeginningWith(entry: DictionaryEntry) = withContext(dispatcher) {
connection.prepareStatement(findWordsBeginningSql).use { preparedStatement ->
preparedStatement.setString(1, "${entry.traditional}?*")
override fun findWordsBeginning(entry: DictionaryEntry): List<DictionaryEntry> {
findWordsBeginning.setString(1, "${entry.traditional}?*")
preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfDictionaryEntries()
}
}
val resultSet: ResultSet = findWordsBeginning.executeQuery()
return resultSet.toListOfDictionaryEntries()
}
override suspend fun findCharactersOf(entry: DictionaryEntry) = withContext(dispatcher) {
override fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry> {
val pinyinSyllablesWithToneNumbers = entry.pinyinWithToneNumbers
.lowercase()
.split(" ")
@ -182,23 +190,19 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
.mapIndexed { index, s -> "($index, '${s.first}', '${s.second}')" }
.joinToString(",")
val query = findCharactersSql.replace("?", queryInput)
val query = findCharacters.replace("?", queryInput)
connection.createStatement().use { statement ->
statement.executeQuery(query).use { resultSet ->
resultSet.toListOfDictionaryEntries()
}
}
val resultSet: ResultSet = connection.createStatement().executeQuery(query)
return resultSet.toListOfDictionaryEntries()
}
override suspend fun findExampleSentencesFor(entry: DictionaryEntry) = withContext(dispatcher) {
connection.prepareStatement(findExampleSentenceSql).use { preparedStatement ->
preparedStatement.setString(1, "_%${entry.traditional}%")
override fun findSentencesContaining(entry: DictionaryEntry): List<Sentence> {
findSentences.setString(1, "_%${entry.traditional}%")
preparedStatement.executeQuery().use { resultSet ->
resultSet.toListOfSentences()
}
}
val resultSet: ResultSet = findSentences.executeQuery()
return resultSet.toListOfSentences()
}
}
@ -215,8 +219,10 @@ private fun ResultSet.toDictionaryEntry() = DictionaryEntry(
)
private fun ResultSet.toListOfDictionaryEntries() = buildList {
while (this@toListOfDictionaryEntries.next()) {
add(this@toListOfDictionaryEntries.toDictionaryEntry())
this@toListOfDictionaryEntries.use {
while (it.next()) {
add(it.toDictionaryEntry())
}
}
}
@ -226,7 +232,9 @@ private fun ResultSet.toSentence() = Sentence(
)
private fun ResultSet.toListOfSentences() = buildList {
while (this@toListOfSentences.next()) {
add(this@toListOfSentences.toSentence())
this@toListOfSentences.use {
while (it.next()) {
add(it.toSentence())
}
}
}

View File

@ -16,18 +16,22 @@ class SearchController(private val model: Model) {
@FXML
@Suppress("UnusedPrivateMember")
private fun initialize() {
textFieldSearch.textProperty().addListener { _, _, _ -> search() }
searchModeToggleGroup.selectedToggleProperty().addListener { _, _, _ -> search() }
textFieldSearch.textProperty().addListener { _, _, newValue ->
if (newValue.isNullOrBlank()) {
return@addListener
}
private fun search() {
val searchQuery = textFieldSearch.text
val searchMode = searchModeToggleGroup.selectedToggle.userData as SearchMode
if (searchQuery.isNullOrBlank()) {
return
model.search(newValue, searchMode)
}
model.search(searchQuery, searchMode)
searchModeToggleGroup.selectedToggleProperty().addListener { _, _, newValue ->
if (textFieldSearch.text.isNullOrBlank()) {
return@addListener
}
val searchMode = newValue.userData as SearchMode
model.search(textFieldSearch.text, searchMode)
}
}
}

View File

@ -0,0 +1,20 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindCharacterService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findCharacters(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -0,0 +1,21 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.SentenceFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindSentencesService(private val dictionary: Dictionary) : Service<ObservableList<SentenceFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findSentencesContaining(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -0,0 +1,20 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindWordsBeginningService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findWordsBeginning(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -0,0 +1,20 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toDomain
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class FindWordsContainingService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var entry: DictionaryEntryFx
override fun createTask() = task {
if (!this::entry.isInitialized) error("Entry is not initialized")
FXCollections.observableList(dictionary.findWordsContaining(entry.toDomain()).map { it.toFx() })
}
}

View File

@ -0,0 +1,22 @@
package com.marvinelsen.willow.ui.services
import com.marvinelsen.willow.domain.Dictionary
import com.marvinelsen.willow.domain.SearchMode
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.toFx
import com.marvinelsen.willow.ui.util.task
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.concurrent.Service
class SearchService(private val dictionary: Dictionary) : Service<ObservableList<DictionaryEntryFx>>() {
lateinit var searchQuery: String
lateinit var searchMode: SearchMode
override fun createTask() = task {
if (!this::searchQuery.isInitialized) error("Search query is not initialized")
if (!this::searchMode.isInitialized) error("Search mode is not initialized")
FXCollections.observableList(dictionary.search(searchQuery, searchMode).map { it.toFx() })
}
}