Compare commits
No commits in common. "cfff342a3afa429b1dd6194f4a5dd864df58474d" and "1b5c32c3207f68372f817aa26a3a664a43b4bcd4" have entirely different histories.
cfff342a3a
...
1b5c32c320
@ -24,8 +24,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.kotlinx.html.jvm)
|
implementation(libs.kotlinx.html.jvm)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.kotlinx.coroutines.javafx)
|
|
||||||
|
|
||||||
implementation(libs.segment)
|
implementation(libs.segment)
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ sqlite-jdbc = "3.46.0.1"
|
|||||||
|
|
||||||
kotlinx-serialization-json = "1.7.1"
|
kotlinx-serialization-json = "1.7.1"
|
||||||
kotlinx-html-jvm = "0.11.0"
|
kotlinx-html-jvm = "0.11.0"
|
||||||
kotlinx-coroutines = "1.9.0"
|
|
||||||
|
|
||||||
segment = "0.3.1"
|
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-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-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" }
|
segment = { module = "com.github.houbb:segment", version.ref = "segment" }
|
||||||
slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "slf4j" }
|
slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "slf4j" }
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
package com.marvinelsen.willow
|
package com.marvinelsen.willow
|
||||||
|
|
||||||
import com.marvinelsen.willow.domain.SearchMode
|
import com.marvinelsen.willow.domain.SearchMode
|
||||||
import com.marvinelsen.willow.domain.SqliteDictionary
|
|
||||||
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
||||||
import com.marvinelsen.willow.ui.SentenceFx
|
import com.marvinelsen.willow.ui.SentenceFx
|
||||||
import com.marvinelsen.willow.ui.toDomain
|
import com.marvinelsen.willow.ui.services.FindCharacterService
|
||||||
import com.marvinelsen.willow.ui.toFx
|
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 com.marvinelsen.willow.ui.util.ClipboardHelper
|
||||||
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.ReadOnlyObjectProperty
|
||||||
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 javafx.event.EventHandler
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class Model(
|
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 internalSelectedEntry: ObjectProperty<DictionaryEntryFx> = SimpleObjectProperty()
|
||||||
private val internalSearchResults: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
|
private val internalSearchResults: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
|
||||||
@ -28,12 +31,6 @@ class Model(
|
|||||||
private val internalCharacters: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
|
private val internalCharacters: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
|
||||||
private val internalSentences: ObservableList<SentenceFx> = 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 selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry
|
||||||
|
|
||||||
val searchResults: ObservableList<DictionaryEntryFx> =
|
val searchResults: ObservableList<DictionaryEntryFx> =
|
||||||
@ -47,68 +44,54 @@ class Model(
|
|||||||
val sentences: ObservableList<SentenceFx> =
|
val sentences: ObservableList<SentenceFx> =
|
||||||
FXCollections.unmodifiableObservableList(internalSentences)
|
FXCollections.unmodifiableObservableList(internalSentences)
|
||||||
|
|
||||||
val isSearching: ReadOnlyBooleanProperty = internalIsSearching
|
val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty()
|
||||||
val isFindingWordsBeginning: ReadOnlyBooleanProperty = internalIsFindingWordsBeginning
|
val isFindingWordsBeginning: ReadOnlyBooleanProperty = findWordsBeginningService.runningProperty()
|
||||||
val isFindingWordsContaining: ReadOnlyBooleanProperty = internalIsFindingWordsContaining
|
val isFindingWordsContaining: ReadOnlyBooleanProperty = findWordsContainingService.runningProperty()
|
||||||
val isFindingCharacters: ReadOnlyBooleanProperty = internalIsFindingCharacters
|
val isFindingCharacters: ReadOnlyBooleanProperty = findCharacterService.runningProperty()
|
||||||
val isFindingSentences: ReadOnlyBooleanProperty = internalIsFindingSentences
|
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) {
|
fun search(query: String, searchMode: SearchMode) {
|
||||||
coroutineScope.launch {
|
searchService.searchQuery = query
|
||||||
internalIsSearching.value = true
|
searchService.searchMode = searchMode
|
||||||
internalSearchResults.setAll(dictionary.search(query, searchMode).map { it.toFx() })
|
searchService.restart()
|
||||||
internalIsSearching.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findWordsBeginning() {
|
fun findWordsBeginning() {
|
||||||
coroutineScope.launch {
|
findWordsBeginningService.entry = internalSelectedEntry.value
|
||||||
internalIsFindingWordsBeginning.value = true
|
findWordsBeginningService.restart()
|
||||||
internalWordsBeginning.setAll(
|
|
||||||
dictionary
|
|
||||||
.findWordsBeginningWith(internalSelectedEntry.value.toDomain())
|
|
||||||
.map { it.toFx() }
|
|
||||||
)
|
|
||||||
internalIsFindingWordsBeginning.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findWordsContaining() {
|
fun findWordsContaining() {
|
||||||
coroutineScope.launch {
|
findWordsContainingService.entry = internalSelectedEntry.value
|
||||||
internalIsFindingWordsContaining.value = true
|
findWordsContainingService.restart()
|
||||||
internalWordsContaining.setAll(
|
|
||||||
dictionary
|
|
||||||
.findWordsContaining(internalSelectedEntry.value.toDomain())
|
|
||||||
.map { it.toFx() }
|
|
||||||
)
|
|
||||||
internalIsFindingWordsContaining.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findCharacters() {
|
fun findCharacters() {
|
||||||
coroutineScope.launch {
|
findCharacterService.entry = internalSelectedEntry.value
|
||||||
internalIsFindingCharacters.value = true
|
findCharacterService.restart()
|
||||||
internalCharacters.setAll(
|
|
||||||
dictionary
|
|
||||||
.findCharactersOf(internalSelectedEntry.value.toDomain())
|
|
||||||
.map { it.toFx() }
|
|
||||||
)
|
|
||||||
internalIsFindingCharacters.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findSentences() {
|
fun findSentences() {
|
||||||
coroutineScope.launch {
|
findSentencesService.entry = internalSelectedEntry.value
|
||||||
internalIsFindingSentences.value = true
|
findSentencesService.restart()
|
||||||
internalSentences.setAll(
|
|
||||||
dictionary
|
|
||||||
.findExampleSentencesFor(internalSelectedEntry.value.toDomain())
|
|
||||||
.map { it.toFx() }
|
|
||||||
)
|
|
||||||
internalIsFindingSentences.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectEntry(entry: DictionaryEntryFx) {
|
fun selectEntry(entry: DictionaryEntryFx) {
|
||||||
|
@ -7,6 +7,11 @@ import com.marvinelsen.willow.ui.controllers.MainController
|
|||||||
import com.marvinelsen.willow.ui.controllers.MenuController
|
import com.marvinelsen.willow.ui.controllers.MenuController
|
||||||
import com.marvinelsen.willow.ui.controllers.SearchController
|
import com.marvinelsen.willow.ui.controllers.SearchController
|
||||||
import com.marvinelsen.willow.ui.controllers.SearchResultsController
|
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.application.Application
|
||||||
import javafx.fxml.FXMLLoader
|
import javafx.fxml.FXMLLoader
|
||||||
import javafx.scene.Scene
|
import javafx.scene.Scene
|
||||||
@ -40,8 +45,17 @@ class WillowApplication : Application() {
|
|||||||
autoCommit = false
|
autoCommit = false
|
||||||
}
|
}
|
||||||
val dictionary = SqliteDictionary(connection)
|
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(
|
val model = Model(
|
||||||
dictionary,
|
searchService,
|
||||||
|
findWordsBeginningService,
|
||||||
|
findWordsContainingService,
|
||||||
|
findCharacterService,
|
||||||
|
findSentenceService
|
||||||
)
|
)
|
||||||
val config = Config()
|
val config = Config()
|
||||||
config.load()
|
config.load()
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package com.marvinelsen.willow.domain
|
package com.marvinelsen.willow.domain
|
||||||
|
|
||||||
interface Dictionary {
|
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>
|
fun findWordsBeginning(entry: DictionaryEntry): List<DictionaryEntry>
|
||||||
suspend fun findWordsContaining(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>
|
||||||
}
|
}
|
||||||
|
@ -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.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 kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
import java.sql.PreparedStatement
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
|
|
||||||
class SqliteDictionary(private val connection: Connection) : Dictionary {
|
class SqliteDictionary(private val connection: Connection) : Dictionary {
|
||||||
private val whitespaceRegex = """\s+""".toRegex()
|
private val whitespaceRegex = """\s+""".toRegex()
|
||||||
|
|
||||||
private val dispatcher = Dispatchers.IO
|
|
||||||
.limitedParallelism(1)
|
|
||||||
|
|
||||||
private val segmentBs = SegmentBs.newInstance()
|
private val segmentBs = SegmentBs.newInstance()
|
||||||
.segment(Segments.defaults())
|
.segment(Segments.defaults())
|
||||||
.segmentData(SegmentPhraseDatas.define())
|
.segmentData(SegmentPhraseDatas.define())
|
||||||
@ -28,29 +24,41 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
|||||||
.posTagging(SegmentPosTaggings.simple())
|
.posTagging(SegmentPosTaggings.simple())
|
||||||
.posData(SegmentPosDatas.define())
|
.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
|
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
|
||||||
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 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
|
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
|
||||||
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 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
|
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
|
||||||
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 ?
|
||||||
ORDER BY character_count ASC
|
ORDER BY character_count ASC
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val searchSegmentsSql = """
|
private val searchSegments = """
|
||||||
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
|
||||||
FROM entry INNER JOIN cte
|
FROM entry INNER JOIN cte
|
||||||
@ -58,113 +66,113 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
|||||||
ORDER BY cte.id
|
ORDER BY cte.id
|
||||||
""".trimIndent()
|
""".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
|
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
|
||||||
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 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
|
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
|
||||||
FROM entry
|
FROM entry
|
||||||
WHERE traditional LIKE ?
|
WHERE traditional LIKE ?
|
||||||
ORDER BY character_count ASC
|
ORDER BY character_count ASC
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val findCharactersSql = """
|
private val findCharacters = """
|
||||||
WITH cte(id, character, pinyin) AS (VALUES ?)
|
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
|
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, cedict_definitions, cross_straits_definitions, moe_definitions
|
||||||
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.syllable = entry.pinyin_with_tone_numbers
|
||||||
ORDER BY cte.id
|
ORDER BY cte.id
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
private val findExampleSentenceSql = """
|
private val findSentences: PreparedStatement by lazy {
|
||||||
|
connection.prepareStatement(
|
||||||
|
"""
|
||||||
SELECT traditional, simplified
|
SELECT traditional, simplified
|
||||||
FROM sentence
|
FROM sentence
|
||||||
WHERE traditional LIKE ?
|
WHERE traditional LIKE ?
|
||||||
ORDER BY character_count ASC
|
ORDER BY character_count ASC
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
)
|
||||||
override suspend fun search(query: String, searchMode: SearchMode) = withContext(dispatcher) {
|
|
||||||
when (searchMode) {
|
|
||||||
SearchMode.SIMPLIFIED -> searchSimplified(query)
|
|
||||||
SearchMode.TRADITIONAL -> searchTraditional(query)
|
|
||||||
SearchMode.PINYIN -> searchPinyin(query)
|
|
||||||
SearchMode.SEGMENTS -> searchSegmentsOf(query)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchSimplified(simplified: String) =
|
override fun search(query: String, searchMode: SearchMode) = when (searchMode) {
|
||||||
connection.prepareStatement(searchSimplifiedSql).use { preparedStatement ->
|
SearchMode.SIMPLIFIED -> searchSimplified(query)
|
||||||
preparedStatement.setString(1, "$simplified*")
|
SearchMode.TRADITIONAL -> searchTraditional(query)
|
||||||
|
SearchMode.PINYIN -> searchPinyin(query)
|
||||||
preparedStatement.executeQuery().use {
|
SearchMode.SEGMENTS -> searchSegments(query)
|
||||||
it.toListOfDictionaryEntries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchTraditional(traditional: String) =
|
|
||||||
connection.prepareStatement(searchTraditionalSql).use { preparedStatement ->
|
|
||||||
preparedStatement.setString(1, "$traditional*")
|
|
||||||
|
|
||||||
preparedStatement.executeQuery().use {
|
|
||||||
it.toListOfDictionaryEntries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchPinyin(pinyin: String): List<DictionaryEntry> {
|
|
||||||
val sanitizedPinyin = pinyin.lowercase().replace(whitespaceRegex, "")
|
|
||||||
|
|
||||||
return connection.prepareStatement(searchPinyinSql).use { preparedStatement ->
|
|
||||||
preparedStatement.setString(1, "$sanitizedPinyin*")
|
|
||||||
preparedStatement.setString(2, "$sanitizedPinyin*")
|
|
||||||
|
|
||||||
preparedStatement.executeQuery().use { resultSet ->
|
|
||||||
resultSet.toListOfDictionaryEntries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchSegmentsOf(phrase: String): List<DictionaryEntry> {
|
private fun searchSimplified(query: String): List<DictionaryEntry> {
|
||||||
|
searchSimplifiedPreparedStatement.setString(1, "$query*")
|
||||||
|
|
||||||
|
val resultSet: ResultSet = searchSimplifiedPreparedStatement.executeQuery()
|
||||||
|
|
||||||
|
return resultSet.toListOfDictionaryEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchTraditional(query: String): List<DictionaryEntry> {
|
||||||
|
searchTraditionalPreparedStatement.setString(1, "$query*")
|
||||||
|
|
||||||
|
val resultSet: ResultSet = searchTraditionalPreparedStatement.executeQuery()
|
||||||
|
|
||||||
|
return resultSet.toListOfDictionaryEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchPinyin(query: String): List<DictionaryEntry> {
|
||||||
|
val sanitizedQuery = query.lowercase().replace(whitespaceRegex, "")
|
||||||
|
|
||||||
|
searchPinyinPreparedStatement.setString(1, "$sanitizedQuery*")
|
||||||
|
searchPinyinPreparedStatement.setString(2, "$sanitizedQuery*")
|
||||||
|
|
||||||
|
val resultSet: ResultSet = searchPinyinPreparedStatement.executeQuery()
|
||||||
|
|
||||||
|
return resultSet.toListOfDictionaryEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchSegments(phrase: String): List<DictionaryEntry> {
|
||||||
val segments = segmentBs.segment(phrase, SegmentResultHandlers.word())
|
val segments = segmentBs.segment(phrase, SegmentResultHandlers.word())
|
||||||
|
|
||||||
val segmentsListString = segments
|
val segmentsListString = segments
|
||||||
.mapIndexed { index, segment -> "($index, '$segment')" }
|
.mapIndexed { index, s -> "($index, '$s')" }
|
||||||
.joinToString(",")
|
.joinToString(",")
|
||||||
|
|
||||||
val query = searchSegmentsSql.replace("?", segmentsListString)
|
val query = searchSegments.replace("?", segmentsListString)
|
||||||
|
|
||||||
return connection.createStatement().use { statement ->
|
val resultSet: ResultSet = connection.createStatement().executeQuery(query)
|
||||||
statement.executeQuery(query).use { resultSet ->
|
|
||||||
resultSet.toListOfDictionaryEntries()
|
return resultSet.toListOfDictionaryEntries()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findWordsContaining(entry: DictionaryEntry) = withContext(dispatcher) {
|
override fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry> {
|
||||||
connection.prepareStatement(findWordsContainingSql).use { preparedStatement ->
|
findWordsContaining.setString(1, "_%${entry.traditional}%")
|
||||||
preparedStatement.setString(1, "_%${entry.traditional}%")
|
|
||||||
|
|
||||||
preparedStatement.executeQuery().use { resultSet ->
|
val resultSet: ResultSet = findWordsContaining.executeQuery()
|
||||||
resultSet.toListOfDictionaryEntries()
|
|
||||||
}
|
return resultSet.toListOfDictionaryEntries()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findWordsBeginningWith(entry: DictionaryEntry) = withContext(dispatcher) {
|
override fun findWordsBeginning(entry: DictionaryEntry): List<DictionaryEntry> {
|
||||||
connection.prepareStatement(findWordsBeginningSql).use { preparedStatement ->
|
findWordsBeginning.setString(1, "${entry.traditional}?*")
|
||||||
preparedStatement.setString(1, "${entry.traditional}?*")
|
|
||||||
|
|
||||||
preparedStatement.executeQuery().use { resultSet ->
|
val resultSet: ResultSet = findWordsBeginning.executeQuery()
|
||||||
resultSet.toListOfDictionaryEntries()
|
|
||||||
}
|
return resultSet.toListOfDictionaryEntries()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findCharactersOf(entry: DictionaryEntry) = withContext(dispatcher) {
|
override fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry> {
|
||||||
val pinyinSyllablesWithToneNumbers = entry.pinyinWithToneNumbers
|
val pinyinSyllablesWithToneNumbers = entry.pinyinWithToneNumbers
|
||||||
.lowercase()
|
.lowercase()
|
||||||
.split(" ")
|
.split(" ")
|
||||||
@ -182,23 +190,19 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
|||||||
.mapIndexed { index, s -> "($index, '${s.first}', '${s.second}')" }
|
.mapIndexed { index, s -> "($index, '${s.first}', '${s.second}')" }
|
||||||
.joinToString(",")
|
.joinToString(",")
|
||||||
|
|
||||||
val query = findCharactersSql.replace("?", queryInput)
|
val query = findCharacters.replace("?", queryInput)
|
||||||
|
|
||||||
connection.createStatement().use { statement ->
|
val resultSet: ResultSet = connection.createStatement().executeQuery(query)
|
||||||
statement.executeQuery(query).use { resultSet ->
|
|
||||||
resultSet.toListOfDictionaryEntries()
|
return resultSet.toListOfDictionaryEntries()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findExampleSentencesFor(entry: DictionaryEntry) = withContext(dispatcher) {
|
override fun findSentencesContaining(entry: DictionaryEntry): List<Sentence> {
|
||||||
connection.prepareStatement(findExampleSentenceSql).use { preparedStatement ->
|
findSentences.setString(1, "_%${entry.traditional}%")
|
||||||
preparedStatement.setString(1, "_%${entry.traditional}%")
|
|
||||||
|
|
||||||
preparedStatement.executeQuery().use { resultSet ->
|
val resultSet: ResultSet = findSentences.executeQuery()
|
||||||
resultSet.toListOfSentences()
|
|
||||||
}
|
return resultSet.toListOfSentences()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,8 +219,10 @@ private fun ResultSet.toDictionaryEntry() = DictionaryEntry(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun ResultSet.toListOfDictionaryEntries() = buildList {
|
private fun ResultSet.toListOfDictionaryEntries() = buildList {
|
||||||
while (this@toListOfDictionaryEntries.next()) {
|
this@toListOfDictionaryEntries.use {
|
||||||
add(this@toListOfDictionaryEntries.toDictionaryEntry())
|
while (it.next()) {
|
||||||
|
add(it.toDictionaryEntry())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +232,9 @@ private fun ResultSet.toSentence() = Sentence(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun ResultSet.toListOfSentences() = buildList {
|
private fun ResultSet.toListOfSentences() = buildList {
|
||||||
while (this@toListOfSentences.next()) {
|
this@toListOfSentences.use {
|
||||||
add(this@toListOfSentences.toSentence())
|
while (it.next()) {
|
||||||
|
add(it.toSentence())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,18 +16,22 @@ class SearchController(private val model: Model) {
|
|||||||
@FXML
|
@FXML
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
textFieldSearch.textProperty().addListener { _, _, _ -> search() }
|
textFieldSearch.textProperty().addListener { _, _, newValue ->
|
||||||
searchModeToggleGroup.selectedToggleProperty().addListener { _, _, _ -> search() }
|
if (newValue.isNullOrBlank()) {
|
||||||
}
|
return@addListener
|
||||||
|
}
|
||||||
|
|
||||||
private fun search() {
|
val searchMode = searchModeToggleGroup.selectedToggle.userData as SearchMode
|
||||||
val searchQuery = textFieldSearch.text
|
model.search(newValue, searchMode)
|
||||||
val searchMode = searchModeToggleGroup.selectedToggle.userData as SearchMode
|
|
||||||
|
|
||||||
if (searchQuery.isNullOrBlank()) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() })
|
||||||
|
}
|
||||||
|
}
|
@ -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() })
|
||||||
|
}
|
||||||
|
}
|
@ -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() })
|
||||||
|
}
|
||||||
|
}
|
@ -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() })
|
||||||
|
}
|
||||||
|
}
|
@ -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() })
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user