From c427af15b656f2286720f3358a189ebad94e2fe8 Mon Sep 17 00:00:00 2001 From: Marvin Elsen Date: Mon, 14 Oct 2024 22:44:48 +0200 Subject: [PATCH] Implement words beginning and refactor words containing --- .../kotlin/com/marvinelsen/willow/Model.kt | 31 ++++-- .../marvinelsen/willow/WillowApplication.kt | 14 ++- .../marvinelsen/willow/domain/Dictionary.kt | 7 +- .../willow/domain/SqliteDictionary.kt | 103 +++++++++++------- .../ui/controllers/DetailsController.kt | 52 ++++++--- .../ui/services/FindWordsBeginningService.kt | 20 ++++ ...rvice.kt => FindWordsContainingService.kt} | 2 +- src/main/resources/fxml/details.fxml | 60 +++++++--- src/main/resources/i18n/willow.properties | 2 + src/main/resources/i18n/willow_de.properties | 2 + src/main/resources/i18n/willow_en.properties | 2 + .../resources/i18n/willow_zh_CN.properties | 2 + .../resources/i18n/willow_zh_TW.properties | 2 + 13 files changed, 212 insertions(+), 87 deletions(-) create mode 100644 src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsBeginningService.kt rename src/main/kotlin/com/marvinelsen/willow/ui/services/{FindWordsService.kt => FindWordsContainingService.kt} (84%) diff --git a/src/main/kotlin/com/marvinelsen/willow/Model.kt b/src/main/kotlin/com/marvinelsen/willow/Model.kt index cce6ee3..a8e4fa2 100644 --- a/src/main/kotlin/com/marvinelsen/willow/Model.kt +++ b/src/main/kotlin/com/marvinelsen/willow/Model.kt @@ -5,7 +5,8 @@ import com.marvinelsen.willow.ui.DictionaryEntryFx import com.marvinelsen.willow.ui.SentenceFx import com.marvinelsen.willow.ui.services.FindCharacterService import com.marvinelsen.willow.ui.services.FindSentencesService -import com.marvinelsen.willow.ui.services.FindWordsService +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.ObjectProperty @@ -18,12 +19,14 @@ import javafx.event.EventHandler class Model( private val searchService: SearchService, - private val findWordsService: FindWordsService, + private val findWordsBeginningService: FindWordsBeginningService, + private val findWordsContainingService: FindWordsContainingService, private val findCharacterService: FindCharacterService, private val findSentencesService: FindSentencesService, ) { private val internalSelectedEntry: ObjectProperty = SimpleObjectProperty() private val internalSearchResults: ObservableList = FXCollections.observableArrayList() + private val internalWordsBeginning: ObservableList = FXCollections.observableArrayList() private val internalWordsContaining: ObservableList = FXCollections.observableArrayList() private val internalCharacters: ObservableList = FXCollections.observableArrayList() private val internalSentences: ObservableList = FXCollections.observableArrayList() @@ -32,6 +35,8 @@ class Model( val searchResults: ObservableList = FXCollections.unmodifiableObservableList(internalSearchResults) + val wordsBeginning: ObservableList = + FXCollections.unmodifiableObservableList(internalWordsBeginning) val wordsContaining: ObservableList = FXCollections.unmodifiableObservableList(internalWordsContaining) val characters: ObservableList = @@ -40,7 +45,8 @@ class Model( FXCollections.unmodifiableObservableList(internalSentences) val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty() - val isFindingWords: ReadOnlyBooleanProperty = findWordsService.runningProperty() + val isFindingWordsBeginning: ReadOnlyBooleanProperty = findWordsBeginningService.runningProperty() + val isFindingWordsContaining: ReadOnlyBooleanProperty = findWordsContainingService.runningProperty() val isFindingCharacters: ReadOnlyBooleanProperty = findCharacterService.runningProperty() val isFindingSentences: ReadOnlyBooleanProperty = findSentencesService.runningProperty() @@ -48,8 +54,11 @@ class Model( searchService.onSucceeded = EventHandler { internalSearchResults.setAll(searchService.value) } - findWordsService.onSucceeded = EventHandler { - internalWordsContaining.setAll(findWordsService.value) + findWordsBeginningService.onSucceeded = EventHandler { + internalWordsBeginning.setAll(findWordsBeginningService.value) + } + findWordsContainingService.onSucceeded = EventHandler { + internalWordsContaining.setAll(findWordsContainingService.value) } findCharacterService.onSucceeded = EventHandler { internalCharacters.setAll(findCharacterService.value) @@ -65,9 +74,14 @@ class Model( searchService.restart() } - fun findWords() { - findWordsService.entry = internalSelectedEntry.value - findWordsService.restart() + fun findWordsBeginning() { + findWordsBeginningService.entry = internalSelectedEntry.value + findWordsBeginningService.restart() + } + + fun findWordsContaining() { + findWordsContainingService.entry = internalSelectedEntry.value + findWordsContainingService.restart() } fun findCharacters() { @@ -81,6 +95,7 @@ class Model( } fun selectEntry(entry: DictionaryEntryFx) { + internalWordsBeginning.setAll(emptyList()) internalWordsContaining.setAll(emptyList()) internalCharacters.setAll(emptyList()) internalSentences.setAll(emptyList()) diff --git a/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt b/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt index 03e8170..a9a5c18 100644 --- a/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt +++ b/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt @@ -9,7 +9,8 @@ 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.FindWordsService +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 @@ -45,10 +46,17 @@ class WillowApplication : Application() { } val dictionary = SqliteDictionary(connection) val searchService = SearchService(dictionary) - val findWordsService = FindWordsService(dictionary) + val findWordsBeginningService = FindWordsBeginningService(dictionary) + val findWordsContainingService = FindWordsContainingService(dictionary) val findCharacterService = FindCharacterService(dictionary) val findSentenceService = FindSentencesService(dictionary) - val model = Model(searchService, findWordsService, findCharacterService, findSentenceService) + val model = Model( + searchService, + findWordsBeginningService, + findWordsContainingService, + findCharacterService, + findSentenceService + ) val config = Config() config.load() diff --git a/src/main/kotlin/com/marvinelsen/willow/domain/Dictionary.kt b/src/main/kotlin/com/marvinelsen/willow/domain/Dictionary.kt index 94074c3..57d0a57 100644 --- a/src/main/kotlin/com/marvinelsen/willow/domain/Dictionary.kt +++ b/src/main/kotlin/com/marvinelsen/willow/domain/Dictionary.kt @@ -2,8 +2,11 @@ package com.marvinelsen.willow.domain interface Dictionary { fun search(query: String, searchMode: SearchMode): List + + fun findWordsBeginning(entry: DictionaryEntry): List fun findWordsContaining(entry: DictionaryEntry): List - fun findSentencesContaining(entry: DictionaryEntry): List + fun findCharacters(entry: DictionaryEntry): List - fun searchSegments(phrase: String): List + + fun findSentencesContaining(entry: DictionaryEntry): List } diff --git a/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt b/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt index c53a66b..e6241f3 100644 --- a/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt +++ b/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt @@ -66,6 +66,17 @@ class SqliteDictionary(private val connection: Connection) : Dictionary { ORDER BY cte.id """.trimIndent() + 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 findWordsContaining: PreparedStatement by lazy { connection.prepareStatement( """ @@ -98,12 +109,53 @@ class SqliteDictionary(private val connection: Connection) : Dictionary { } override fun search(query: String, searchMode: SearchMode) = when (searchMode) { - SearchMode.PINYIN -> searchPinyin(query) SearchMode.SIMPLIFIED -> searchSimplified(query) SearchMode.TRADITIONAL -> searchTraditional(query) + SearchMode.PINYIN -> searchPinyin(query) SearchMode.SEGMENTS -> searchSegments(query) } + private fun searchSimplified(query: String): List { + searchSimplifiedPreparedStatement.setString(1, "$query*") + + val resultSet: ResultSet = searchSimplifiedPreparedStatement.executeQuery() + + return resultSet.toListOfDictionaryEntries() + } + + private fun searchTraditional(query: String): List { + searchTraditionalPreparedStatement.setString(1, "$query*") + + val resultSet: ResultSet = searchTraditionalPreparedStatement.executeQuery() + + return resultSet.toListOfDictionaryEntries() + } + + private fun searchPinyin(query: String): List { + 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 { + val segments = segmentBs.segment(phrase, SegmentResultHandlers.word()) + + val segmentsListString = segments + .mapIndexed { index, s -> "($index, '$s')" } + .joinToString(",") + + val query = searchSegments.replace("?", segmentsListString) + + val resultSet: ResultSet = connection.createStatement().executeQuery(query) + + return resultSet.toListOfDictionaryEntries() + } + override fun findWordsContaining(entry: DictionaryEntry): List { findWordsContaining.setString(1, "_%${entry.traditional}%") @@ -112,12 +164,12 @@ class SqliteDictionary(private val connection: Connection) : Dictionary { return resultSet.toListOfDictionaryEntries() } - override fun findSentencesContaining(entry: DictionaryEntry): List { - findSentences.setString(1, "_%${entry.traditional}%") + override fun findWordsBeginning(entry: DictionaryEntry): List { + findWordsBeginning.setString(1, "${entry.traditional}?*") - val resultSet: ResultSet = findSentences.executeQuery() + val resultSet: ResultSet = findWordsBeginning.executeQuery() - return resultSet.toListOfSentences() + return resultSet.toListOfDictionaryEntries() } override fun findCharacters(entry: DictionaryEntry): List { @@ -145,45 +197,12 @@ class SqliteDictionary(private val connection: Connection) : Dictionary { return resultSet.toListOfDictionaryEntries() } - override fun searchSegments(phrase: String): List { - val segments = segmentBs.segment(phrase, SegmentResultHandlers.word()) + override fun findSentencesContaining(entry: DictionaryEntry): List { + findSentences.setString(1, "_%${entry.traditional}%") - val segmentsListString = segments - .mapIndexed { index, s -> "($index, '$s')" } - .joinToString(",") + val resultSet: ResultSet = findSentences.executeQuery() - val query = searchSegments.replace("?", segmentsListString) - - val resultSet: ResultSet = connection.createStatement().executeQuery(query) - - return resultSet.toListOfDictionaryEntries() - } - - private fun searchSimplified(query: String): List { - searchSimplifiedPreparedStatement.setString(1, "$query*") - - val resultSet: ResultSet = searchSimplifiedPreparedStatement.executeQuery() - - return resultSet.toListOfDictionaryEntries() - } - - private fun searchPinyin(query: String): List { - val sanitizedQuery = query.lowercase().replace(whitespaceRegex, "") - - searchPinyinPreparedStatement.setString(1, "$sanitizedQuery*") - searchPinyinPreparedStatement.setString(2, "$sanitizedQuery*") - - val resultSet: ResultSet = searchPinyinPreparedStatement.executeQuery() - - return resultSet.toListOfDictionaryEntries() - } - - private fun searchTraditional(query: String): List { - searchTraditionalPreparedStatement.setString(1, "$query*") - - val resultSet: ResultSet = searchTraditionalPreparedStatement.executeQuery() - - return resultSet.toListOfDictionaryEntries() + return resultSet.toListOfSentences() } } diff --git a/src/main/kotlin/com/marvinelsen/willow/ui/controllers/DetailsController.kt b/src/main/kotlin/com/marvinelsen/willow/ui/controllers/DetailsController.kt index da8a223..32619ce 100644 --- a/src/main/kotlin/com/marvinelsen/willow/ui/controllers/DetailsController.kt +++ b/src/main/kotlin/com/marvinelsen/willow/ui/controllers/DetailsController.kt @@ -54,7 +54,10 @@ class DetailsController(private val model: Model, private val config: Config) { private lateinit var webViewDefinition: WebView @FXML - private lateinit var listViewWords: ListView + private lateinit var listViewWordsContaining: ListView + + @FXML + private lateinit var listViewWordsBeginning: ListView @FXML private lateinit var listViewCharacters: ListView @@ -63,13 +66,19 @@ class DetailsController(private val model: Model, private val config: Config) { private lateinit var progressIndicatorCharacters: ProgressIndicator @FXML - private lateinit var progressIndicatorWords: ProgressIndicator + private lateinit var progressIndicatorWordsContaining: ProgressIndicator + + @FXML + private lateinit var progressIndicatorWordsBeginning: ProgressIndicator @FXML private lateinit var labelNoCharactersFound: Label @FXML - private lateinit var labelNoWordsFound: Label + private lateinit var labelNoWordsContainingFound: Label + + @FXML + private lateinit var labelNoWordsBeginningFound: Label @FXML private lateinit var listViewSentences: ListView @@ -85,7 +94,8 @@ class DetailsController(private val model: Model, private val config: Config) { initializeLabelHeadword() initializeLabelPronunciation() initializeTabPaneDetails() - initializeListViewWords() + initializeListViewWordsContaining() + initializeListViewWordsBeginning() initializeListViewCharacters() initializeListViewSentences() initializeWebViewDefinition() @@ -193,17 +203,30 @@ class DetailsController(private val model: Model, private val config: Config) { .bind(Bindings.and(Bindings.isEmpty(model.sentences), Bindings.not(model.isFindingSentences))) } - private fun initializeListViewWords() { - listViewWords.apply { + private fun initializeListViewWordsContaining() { + listViewWordsContaining.apply { cellFactory = DictionaryEntryCellFactory(resources, config) items = model.wordsContaining - disableProperty().bind(Bindings.or(model.isFindingWords, Bindings.isEmpty(model.wordsContaining))) + disableProperty().bind(Bindings.or(model.isFindingWordsContaining, Bindings.isEmpty(model.wordsContaining))) } - progressIndicatorWords.visibleProperty().bind(model.isFindingWords) - labelNoWordsFound + progressIndicatorWordsContaining.visibleProperty().bind(model.isFindingWordsContaining) + labelNoWordsContainingFound .visibleProperty() - .bind(Bindings.and(Bindings.isEmpty(model.wordsContaining), Bindings.not(model.isFindingWords))) + .bind(Bindings.and(Bindings.isEmpty(model.wordsContaining), Bindings.not(model.isFindingWordsContaining))) + } + + private fun initializeListViewWordsBeginning() { + listViewWordsBeginning.apply { + cellFactory = DictionaryEntryCellFactory(resources, config) + items = model.wordsBeginning + + disableProperty().bind(Bindings.or(model.isFindingWordsBeginning, Bindings.isEmpty(model.wordsBeginning))) + } + progressIndicatorWordsBeginning.visibleProperty().bind(model.isFindingWordsBeginning) + labelNoWordsBeginningFound + .visibleProperty() + .bind(Bindings.and(Bindings.isEmpty(model.wordsBeginning), Bindings.not(model.isFindingWordsBeginning))) } private fun initializeListViewCharacters() { @@ -246,9 +269,12 @@ class DetailsController(private val model: Model, private val config: Config) { private fun lazyUpdateTabContent(selectedTabId: String?) { when (selectedTabId) { "tabWords" -> { - if (model.wordsContaining.isNotEmpty()) return - - model.findWords() + if (model.wordsContaining.isEmpty()) { + model.findWordsContaining() + } + if (model.wordsBeginning.isEmpty()) { + model.findWordsBeginning() + } } "tabCharacters" -> { diff --git a/src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsBeginningService.kt b/src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsBeginningService.kt new file mode 100644 index 0000000..d30da0b --- /dev/null +++ b/src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsBeginningService.kt @@ -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>() { + 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() }) + } +} diff --git a/src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsService.kt b/src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsContainingService.kt similarity index 84% rename from src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsService.kt rename to src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsContainingService.kt index 7ea6a08..a80e8fe 100644 --- a/src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsService.kt +++ b/src/main/kotlin/com/marvinelsen/willow/ui/services/FindWordsContainingService.kt @@ -9,7 +9,7 @@ import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.concurrent.Service -class FindWordsService(private val dictionary: Dictionary) : Service>() { +class FindWordsContainingService(private val dictionary: Dictionary) : Service>() { lateinit var entry: DictionaryEntryFx override fun createTask() = task { diff --git a/src/main/resources/fxml/details.fxml b/src/main/resources/fxml/details.fxml index 3ba967d..605ccaa 100644 --- a/src/main/resources/fxml/details.fxml +++ b/src/main/resources/fxml/details.fxml @@ -1,14 +1,13 @@ - + - - + + @@ -16,14 +15,15 @@ - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/i18n/willow.properties b/src/main/resources/i18n/willow.properties index 071e002..5bdeede 100644 --- a/src/main/resources/i18n/willow.properties +++ b/src/main/resources/i18n/willow.properties @@ -21,3 +21,5 @@ search.mode.phrase=Phrase list.no_characters_found=No characters found list.no_words_found=No words found list.no_sentences_found=No sentences found. +list.words_beginning=Words beginning +list.words_containing=Words containing diff --git a/src/main/resources/i18n/willow_de.properties b/src/main/resources/i18n/willow_de.properties index 1947912..dbfd94d 100644 --- a/src/main/resources/i18n/willow_de.properties +++ b/src/main/resources/i18n/willow_de.properties @@ -21,3 +21,5 @@ search.mode.phrase=Phrase list.no_characters_found=No characters found list.no_words_found=No words found list.no_sentences_found=No sentences found. +list.words_beginning=Words beginning +list.words_containing=Words containing diff --git a/src/main/resources/i18n/willow_en.properties b/src/main/resources/i18n/willow_en.properties index 071e002..5bdeede 100644 --- a/src/main/resources/i18n/willow_en.properties +++ b/src/main/resources/i18n/willow_en.properties @@ -21,3 +21,5 @@ search.mode.phrase=Phrase list.no_characters_found=No characters found list.no_words_found=No words found list.no_sentences_found=No sentences found. +list.words_beginning=Words beginning +list.words_containing=Words containing diff --git a/src/main/resources/i18n/willow_zh_CN.properties b/src/main/resources/i18n/willow_zh_CN.properties index 03f5324..5257ace 100644 --- a/src/main/resources/i18n/willow_zh_CN.properties +++ b/src/main/resources/i18n/willow_zh_CN.properties @@ -21,3 +21,5 @@ search.mode.phrase=Phrase list.no_characters_found=No characters found list.no_words_found=No words found list.no_sentences_found=No sentences found. +list.words_beginning=Words beginning +list.words_containing=Words containing diff --git a/src/main/resources/i18n/willow_zh_TW.properties b/src/main/resources/i18n/willow_zh_TW.properties index 03f5324..5257ace 100644 --- a/src/main/resources/i18n/willow_zh_TW.properties +++ b/src/main/resources/i18n/willow_zh_TW.properties @@ -21,3 +21,5 @@ search.mode.phrase=Phrase list.no_characters_found=No characters found list.no_words_found=No words found list.no_sentences_found=No sentences found. +list.words_beginning=Words beginning +list.words_containing=Words containing