From 168d1634a2d08b39a5b60448b1508aa462675836 Mon Sep 17 00:00:00 2001 From: Marvin Elsen Date: Mon, 14 Oct 2024 21:46:09 +0200 Subject: [PATCH] Implement find sentence function --- .../kotlin/com/marvinelsen/willow/Model.kt | 16 +++++++ .../marvinelsen/willow/WillowApplication.kt | 4 +- .../marvinelsen/willow/domain/Dictionary.kt | 2 +- .../willow/domain/SqliteDictionary.kt | 32 +++++++++++++- .../com/marvinelsen/willow/ui/SentenceFx.kt | 20 +++++++++ .../willow/ui/cells/SentenceCellFactory.kt | 44 +++++++++++++++++++ .../ui/controllers/DetailsController.kt | 32 ++++++++++++++ .../ui/services/FindSentencesService.kt | 21 +++++++++ src/main/resources/fxml/details.fxml | 11 ++++- src/main/resources/i18n/willow.properties | 1 + src/main/resources/i18n/willow_de.properties | 1 + src/main/resources/i18n/willow_en.properties | 1 + .../resources/i18n/willow_zh_CN.properties | 1 + .../resources/i18n/willow_zh_TW.properties | 1 + 14 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/marvinelsen/willow/ui/SentenceFx.kt create mode 100644 src/main/kotlin/com/marvinelsen/willow/ui/cells/SentenceCellFactory.kt create mode 100644 src/main/kotlin/com/marvinelsen/willow/ui/services/FindSentencesService.kt diff --git a/src/main/kotlin/com/marvinelsen/willow/Model.kt b/src/main/kotlin/com/marvinelsen/willow/Model.kt index bcc195f..cce6ee3 100644 --- a/src/main/kotlin/com/marvinelsen/willow/Model.kt +++ b/src/main/kotlin/com/marvinelsen/willow/Model.kt @@ -2,7 +2,9 @@ package com.marvinelsen.willow import com.marvinelsen.willow.domain.SearchMode 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.SearchService import com.marvinelsen.willow.ui.util.ClipboardHelper @@ -18,11 +20,13 @@ class Model( private val searchService: SearchService, private val findWordsService: FindWordsService, private val findCharacterService: FindCharacterService, + private val findSentencesService: FindSentencesService, ) { private val internalSelectedEntry: ObjectProperty = SimpleObjectProperty() private val internalSearchResults: ObservableList = FXCollections.observableArrayList() private val internalWordsContaining: ObservableList = FXCollections.observableArrayList() private val internalCharacters: ObservableList = FXCollections.observableArrayList() + private val internalSentences: ObservableList = FXCollections.observableArrayList() val selectedEntry: ReadOnlyObjectProperty = internalSelectedEntry @@ -32,10 +36,13 @@ class Model( FXCollections.unmodifiableObservableList(internalWordsContaining) val characters: ObservableList = FXCollections.unmodifiableObservableList(internalCharacters) + val sentences: ObservableList = + FXCollections.unmodifiableObservableList(internalSentences) val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty() val isFindingWords: ReadOnlyBooleanProperty = findWordsService.runningProperty() val isFindingCharacters: ReadOnlyBooleanProperty = findCharacterService.runningProperty() + val isFindingSentences: ReadOnlyBooleanProperty = findSentencesService.runningProperty() init { searchService.onSucceeded = EventHandler { @@ -47,6 +54,9 @@ class Model( findCharacterService.onSucceeded = EventHandler { internalCharacters.setAll(findCharacterService.value) } + findSentencesService.onSucceeded = EventHandler { + internalSentences.setAll(findSentencesService.value) + } } fun search(query: String, searchMode: SearchMode) { @@ -65,9 +75,15 @@ class Model( findCharacterService.restart() } + fun findSentences() { + findSentencesService.entry = internalSelectedEntry.value + findSentencesService.restart() + } + fun selectEntry(entry: DictionaryEntryFx) { internalWordsContaining.setAll(emptyList()) internalCharacters.setAll(emptyList()) + internalSentences.setAll(emptyList()) internalSelectedEntry.value = entry } diff --git a/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt b/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt index 7df322e..03e8170 100644 --- a/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt +++ b/src/main/kotlin/com/marvinelsen/willow/WillowApplication.kt @@ -8,6 +8,7 @@ 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.FindWordsService import com.marvinelsen.willow.ui.services.SearchService import javafx.application.Application @@ -46,7 +47,8 @@ class WillowApplication : Application() { val searchService = SearchService(dictionary) val findWordsService = FindWordsService(dictionary) val findCharacterService = FindCharacterService(dictionary) - val model = Model(searchService, findWordsService, findCharacterService) + val findSentenceService = FindSentencesService(dictionary) + val model = Model(searchService, findWordsService, 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 2812777..94074c3 100644 --- a/src/main/kotlin/com/marvinelsen/willow/domain/Dictionary.kt +++ b/src/main/kotlin/com/marvinelsen/willow/domain/Dictionary.kt @@ -3,7 +3,7 @@ package com.marvinelsen.willow.domain interface Dictionary { fun search(query: String, searchMode: SearchMode): List fun findWordsContaining(entry: DictionaryEntry): List - fun findSentencesContaining(entry: DictionaryEntry): List + fun findSentencesContaining(entry: DictionaryEntry): List fun findCharacters(entry: DictionaryEntry): List fun searchSegments(phrase: String): List } diff --git a/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt b/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt index 14ce395..c53a66b 100644 --- a/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt +++ b/src/main/kotlin/com/marvinelsen/willow/domain/SqliteDictionary.kt @@ -86,6 +86,17 @@ class SqliteDictionary(private val connection: Connection) : Dictionary { ORDER BY cte.id """.trimIndent() + private val findSentences: PreparedStatement by lazy { + connection.prepareStatement( + """ + SELECT traditional, simplified + FROM sentence + WHERE traditional LIKE ? + ORDER BY character_count ASC + """.trimIndent() + ) + } + override fun search(query: String, searchMode: SearchMode) = when (searchMode) { SearchMode.PINYIN -> searchPinyin(query) SearchMode.SIMPLIFIED -> searchSimplified(query) @@ -101,8 +112,12 @@ class SqliteDictionary(private val connection: Connection) : Dictionary { return resultSet.toListOfDictionaryEntries() } - override fun findSentencesContaining(entry: DictionaryEntry): List { - return emptyList() + override fun findSentencesContaining(entry: DictionaryEntry): List { + findSentences.setString(1, "_%${entry.traditional}%") + + val resultSet: ResultSet = findSentences.executeQuery() + + return resultSet.toListOfSentences() } override fun findCharacters(entry: DictionaryEntry): List { @@ -191,3 +206,16 @@ private fun ResultSet.toListOfDictionaryEntries() = buildList { } } } + +private fun ResultSet.toSentence() = Sentence( + traditional = this.getString(1), + simplified = this.getString(2), +) + +private fun ResultSet.toListOfSentences() = buildList { + this@toListOfSentences.use { + while (it.next()) { + add(it.toSentence()) + } + } +} diff --git a/src/main/kotlin/com/marvinelsen/willow/ui/SentenceFx.kt b/src/main/kotlin/com/marvinelsen/willow/ui/SentenceFx.kt new file mode 100644 index 0000000..6776e38 --- /dev/null +++ b/src/main/kotlin/com/marvinelsen/willow/ui/SentenceFx.kt @@ -0,0 +1,20 @@ +package com.marvinelsen.willow.ui + +import com.marvinelsen.willow.domain.Sentence +import javafx.beans.property.SimpleStringProperty +import javafx.beans.property.StringProperty + +data class SentenceFx( + val traditionalProperty: StringProperty, + val simplifiedProperty: StringProperty, +) + +fun Sentence.toFx() = SentenceFx( + traditionalProperty = SimpleStringProperty(this.traditional), + simplifiedProperty = SimpleStringProperty(this.simplified), +) + +fun SentenceFx.toDomain() = Sentence( + traditional = this.traditionalProperty.value, + simplified = this.simplifiedProperty.value, +) diff --git a/src/main/kotlin/com/marvinelsen/willow/ui/cells/SentenceCellFactory.kt b/src/main/kotlin/com/marvinelsen/willow/ui/cells/SentenceCellFactory.kt new file mode 100644 index 0000000..001eaf7 --- /dev/null +++ b/src/main/kotlin/com/marvinelsen/willow/ui/cells/SentenceCellFactory.kt @@ -0,0 +1,44 @@ +package com.marvinelsen.willow.ui.cells + +import com.marvinelsen.willow.ui.SentenceFx +import javafx.scene.control.Label +import javafx.scene.control.ListCell +import javafx.scene.control.ListView +import javafx.scene.layout.VBox +import javafx.util.Callback + +class SentenceCellFactory() : Callback, ListCell> { + + override fun call(listView: ListView): ListCell { + val sentenceCell = SentenceCell() + sentenceCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING)) + return sentenceCell + } + + companion object { + private const val CELL_PADDING = 16 + } +} + +internal class SentenceCell() : ListCell() { + private val labelTraditional = Label().apply { + styleClass.add("chinese") + styleClass.add("list-view-sentence-cell") + } + private val root = VBox(labelTraditional) + + init { + text = null + } + + override fun updateItem(sentence: SentenceFx?, empty: Boolean) { + super.updateItem(sentence, empty) + if (empty || sentence == null) { + graphic = null + } else { + labelTraditional.text = sentence.traditionalProperty.value + + graphic = root + } + } +} 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 9bc1518..cedc68a 100644 --- a/src/main/kotlin/com/marvinelsen/willow/ui/controllers/DetailsController.kt +++ b/src/main/kotlin/com/marvinelsen/willow/ui/controllers/DetailsController.kt @@ -5,7 +5,9 @@ import com.marvinelsen.willow.config.Config import com.marvinelsen.willow.config.Pronunciation import com.marvinelsen.willow.config.Script import com.marvinelsen.willow.ui.DictionaryEntryFx +import com.marvinelsen.willow.ui.SentenceFx import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory +import com.marvinelsen.willow.ui.cells.SentenceCellFactory import com.marvinelsen.willow.ui.util.createContextMenuForEntry import javafx.beans.binding.Bindings import javafx.fxml.FXML @@ -68,6 +70,16 @@ class DetailsController(private val model: Model, private val config: Config) { @FXML private lateinit var labelNoWordsFound: Label + @FXML + private lateinit var listViewSentences: ListView + + @FXML + private lateinit var progressIndicatorSentences: ProgressIndicator + + @FXML + private lateinit var labelNoSentencesFound: Label + + @FXML private fun initialize() { initializeLabelHeadword() @@ -75,6 +87,7 @@ class DetailsController(private val model: Model, private val config: Config) { initializeTabPaneDetails() initializeListViewWords() initializeListViewCharacters() + initializeListViewSentences() initializeWebViewDefinition() } @@ -167,6 +180,19 @@ class DetailsController(private val model: Model, private val config: Config) { ) } + private fun initializeListViewSentences() { + listViewSentences.apply { + cellFactory = SentenceCellFactory() + items = model.sentences + + disableProperty().bind(Bindings.or(model.isFindingSentences, Bindings.isEmpty(model.sentences))) + } + progressIndicatorSentences.visibleProperty().bind(model.isFindingSentences) + labelNoSentencesFound + .visibleProperty() + .bind(Bindings.and(Bindings.isEmpty(model.sentences), Bindings.not(model.isFindingSentences))) + } + private fun initializeListViewWords() { listViewWords.apply { cellFactory = DictionaryEntryCellFactory(resources, config) @@ -324,6 +350,12 @@ class DetailsController(private val model: Model, private val config: Config) { model.findCharacters() } + "tabSentences" -> { + if (model.sentences.isNotEmpty()) return + + model.findSentences() + } + else -> {} } } diff --git a/src/main/kotlin/com/marvinelsen/willow/ui/services/FindSentencesService.kt b/src/main/kotlin/com/marvinelsen/willow/ui/services/FindSentencesService.kt new file mode 100644 index 0000000..589819a --- /dev/null +++ b/src/main/kotlin/com/marvinelsen/willow/ui/services/FindSentencesService.kt @@ -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>() { + 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() }) + } +} diff --git a/src/main/resources/fxml/details.fxml b/src/main/resources/fxml/details.fxml index 3a00ff2..3ba967d 100644 --- a/src/main/resources/fxml/details.fxml +++ b/src/main/resources/fxml/details.fxml @@ -21,7 +21,16 @@ - + + + + + diff --git a/src/main/resources/i18n/willow.properties b/src/main/resources/i18n/willow.properties index a38a7a1..071e002 100644 --- a/src/main/resources/i18n/willow.properties +++ b/src/main/resources/i18n/willow.properties @@ -20,3 +20,4 @@ list.no_entries_found=No matching entries found 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. diff --git a/src/main/resources/i18n/willow_de.properties b/src/main/resources/i18n/willow_de.properties index 4d2d28e..1947912 100644 --- a/src/main/resources/i18n/willow_de.properties +++ b/src/main/resources/i18n/willow_de.properties @@ -20,3 +20,4 @@ list.no_entries_found=No matching entries found 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. diff --git a/src/main/resources/i18n/willow_en.properties b/src/main/resources/i18n/willow_en.properties index a38a7a1..071e002 100644 --- a/src/main/resources/i18n/willow_en.properties +++ b/src/main/resources/i18n/willow_en.properties @@ -20,3 +20,4 @@ list.no_entries_found=No matching entries found 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. diff --git a/src/main/resources/i18n/willow_zh_CN.properties b/src/main/resources/i18n/willow_zh_CN.properties index 7b84147..03f5324 100644 --- a/src/main/resources/i18n/willow_zh_CN.properties +++ b/src/main/resources/i18n/willow_zh_CN.properties @@ -20,3 +20,4 @@ list.no_entries_found=No matching entries found 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. diff --git a/src/main/resources/i18n/willow_zh_TW.properties b/src/main/resources/i18n/willow_zh_TW.properties index 7b84147..03f5324 100644 --- a/src/main/resources/i18n/willow_zh_TW.properties +++ b/src/main/resources/i18n/willow_zh_TW.properties @@ -20,3 +20,4 @@ list.no_entries_found=No matching entries found 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.