New Features #1

Open
marvinelsen wants to merge 76 commits from develop into main
14 changed files with 182 additions and 5 deletions
Showing only changes of commit 168d1634a2 - Show all commits

View File

@ -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<DictionaryEntryFx> = SimpleObjectProperty()
private val internalSearchResults: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
private val internalWordsContaining: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
private val internalCharacters: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
private val internalSentences: ObservableList<SentenceFx> = FXCollections.observableArrayList()
val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry
@ -32,10 +36,13 @@ class Model(
FXCollections.unmodifiableObservableList(internalWordsContaining)
val characters: ObservableList<DictionaryEntryFx> =
FXCollections.unmodifiableObservableList(internalCharacters)
val sentences: ObservableList<SentenceFx> =
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
}

View File

@ -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()

View File

@ -3,7 +3,7 @@ package com.marvinelsen.willow.domain
interface Dictionary {
fun search(query: String, searchMode: SearchMode): List<DictionaryEntry>
fun findWordsContaining(entry: DictionaryEntry): List<DictionaryEntry>
fun findSentencesContaining(entry: DictionaryEntry): List<DictionaryEntry>
fun findSentencesContaining(entry: DictionaryEntry): List<Sentence>
fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry>
fun searchSegments(phrase: String): List<DictionaryEntry>
}

View File

@ -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<DictionaryEntry> {
return emptyList()
override fun findSentencesContaining(entry: DictionaryEntry): List<Sentence> {
findSentences.setString(1, "_%${entry.traditional}%")
val resultSet: ResultSet = findSentences.executeQuery()
return resultSet.toListOfSentences()
}
override fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry> {
@ -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())
}
}
}

View File

@ -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,
)

View File

@ -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<ListView<SentenceFx?>, ListCell<SentenceFx?>> {
override fun call(listView: ListView<SentenceFx?>): ListCell<SentenceFx?> {
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<SentenceFx?>() {
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
}
}
}

View File

@ -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<SentenceFx>
@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 -> {}
}
}

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

@ -21,7 +21,16 @@
<WebView fx:id="webViewDefinition" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0"/>
</Tab>
<Tab id="tabSentences" closable="false" text="%tab.sentences">
<StackPane>
<ProgressIndicator fx:id="progressIndicatorSentences" visible="false"/>
<Label fx:id="labelNoSentencesFound" text="%list.no_sentences_found" textAlignment="CENTER" visible="false"
wrapText="true">
<padding>
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0"/>
</padding>
</Label>
<ListView fx:id="listViewSentences"/>
</StackPane>
</Tab>
<Tab id="tabWords" closable="false" text="%tab.words">
<StackPane>

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.