Compare commits

..

No commits in common. "46af4db9f19e3f5cc84de6f81a8c62a0c52b6aaf" and "7add73fe2880856443b5d95b0b445f51f432a1a1" have entirely different histories.

19 changed files with 100 additions and 304 deletions

View File

@ -30,8 +30,6 @@ dependencies {
implementation(libs.segment) implementation(libs.segment)
implementation(libs.ikonli.javafx)
testImplementation(libs.kotest.core) testImplementation(libs.kotest.core)
testImplementation(libs.kotest.assertions) testImplementation(libs.kotest.assertions)
} }

View File

@ -16,8 +16,6 @@ kotlinx-html-jvm = "0.11.0"
segment = "0.3.1" segment = "0.3.1"
ikonli-javafx = "12.3.1"
[libraries] [libraries]
chinese-transliteration = { module = "com.marvinelsen:chinese-transliteration", version.ref = "chinese-transliteration" } chinese-transliteration = { module = "com.marvinelsen:chinese-transliteration", version.ref = "chinese-transliteration" }
cedict-parser = { module = "com.marvinelsen:cedict-parser", version.ref = "cedict-parser" } cedict-parser = { module = "com.marvinelsen:cedict-parser", version.ref = "cedict-parser" }
@ -34,8 +32,6 @@ kotlinx-html-jvm = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version.
segment = { module = "com.github.houbb:segment", version.ref = "segment" } segment = { module = "com.github.houbb:segment", version.ref = "segment" }
ikonli-javafx = { module = "org.kordamp.ikonli:ikonli-javafx", version.ref = "ikonli-javafx" }
# Detekt # Detekt
# See: https://detekt.dev # See: https://detekt.dev
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }

View File

@ -3,7 +3,6 @@ package com.marvinelsen.willow
import com.marvinelsen.willow.domain.SearchMode import com.marvinelsen.willow.domain.SearchMode
import com.marvinelsen.willow.ui.DictionaryEntryFx import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.util.ClipboardHelper import com.marvinelsen.willow.ui.util.ClipboardHelper
import com.marvinelsen.willow.ui.util.FindCharacterService
import com.marvinelsen.willow.ui.util.FindWordsService import com.marvinelsen.willow.ui.util.FindWordsService
import com.marvinelsen.willow.ui.util.SearchService import com.marvinelsen.willow.ui.util.SearchService
import javafx.beans.property.ObjectProperty import javafx.beans.property.ObjectProperty
@ -14,15 +13,10 @@ import javafx.collections.FXCollections
import javafx.collections.ObservableList import javafx.collections.ObservableList
import javafx.event.EventHandler import javafx.event.EventHandler
class Model( class Model(private val searchService: SearchService, private val findWordsService: FindWordsService) {
private val searchService: SearchService,
private val findWordsService: FindWordsService,
private val findCharacterService: FindCharacterService,
) {
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()
private val internalWordsContaining: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList() private val internalWordsContaining: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
private val internalCharacters: ObservableList<DictionaryEntryFx> = FXCollections.observableArrayList()
val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry
@ -30,12 +24,9 @@ class Model(
FXCollections.unmodifiableObservableList(internalSearchResults) FXCollections.unmodifiableObservableList(internalSearchResults)
val wordsContaining: ObservableList<DictionaryEntryFx> = val wordsContaining: ObservableList<DictionaryEntryFx> =
FXCollections.unmodifiableObservableList(internalWordsContaining) FXCollections.unmodifiableObservableList(internalWordsContaining)
val characters: ObservableList<DictionaryEntryFx> =
FXCollections.unmodifiableObservableList(internalCharacters)
val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty() val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty()
val isFindingWords: ReadOnlyBooleanProperty = findWordsService.runningProperty() val isFindingWords: ReadOnlyBooleanProperty = findWordsService.runningProperty()
val isFindingCharacters: ReadOnlyBooleanProperty = findCharacterService.runningProperty()
init { init {
searchService.onSucceeded = EventHandler { searchService.onSucceeded = EventHandler {
@ -44,9 +35,6 @@ class Model(
findWordsService.onSucceeded = EventHandler { findWordsService.onSucceeded = EventHandler {
internalWordsContaining.setAll(findWordsService.value) internalWordsContaining.setAll(findWordsService.value)
} }
findCharacterService.onSucceeded = EventHandler {
internalCharacters.setAll(findCharacterService.value)
}
} }
fun search(query: String, searchMode: SearchMode) { fun search(query: String, searchMode: SearchMode) {
@ -60,14 +48,7 @@ class Model(
findWordsService.restart() findWordsService.restart()
} }
fun findCharacters() {
findCharacterService.entry = internalSelectedEntry.value
findCharacterService.restart()
}
fun selectEntry(entry: DictionaryEntryFx) { fun selectEntry(entry: DictionaryEntryFx) {
internalWordsContaining.setAll(emptyList())
internalCharacters.setAll(emptyList())
internalSelectedEntry.value = entry internalSelectedEntry.value = entry
} }

View File

@ -7,13 +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.util.FindCharacterService
import com.marvinelsen.willow.ui.util.FindWordsService import com.marvinelsen.willow.ui.util.FindWordsService
import com.marvinelsen.willow.ui.util.SearchService import com.marvinelsen.willow.ui.util.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
import javafx.scene.image.Image
import javafx.scene.layout.BorderPane import javafx.scene.layout.BorderPane
import javafx.scene.text.Font import javafx.scene.text.Font
import javafx.stage.Stage import javafx.stage.Stage
@ -31,7 +29,7 @@ class WillowApplication : Application() {
private const val FONT_SIZE = 12.0 private const val FONT_SIZE = 12.0
private const val JDBC_CONNECTION_STRING = "jdbc:sqlite::resource:data/dictionary.db" private const val JDBC_CONNECTION_STRING = "jdbc:sqlite:dictionary.db"
} }
override fun init() { override fun init() {
@ -45,8 +43,7 @@ class WillowApplication : Application() {
val dictionary = SqliteDictionary(connection) val dictionary = SqliteDictionary(connection)
val searchService = SearchService(dictionary) val searchService = SearchService(dictionary)
val findWordsService = FindWordsService(dictionary) val findWordsService = FindWordsService(dictionary)
val findCharacterService = FindCharacterService(dictionary) val model = Model(searchService, findWordsService)
val model = Model(searchService, findWordsService, findCharacterService)
val config = Config() val config = Config()
config.load() config.load()
@ -72,7 +69,6 @@ class WillowApplication : Application() {
minWidth = WINDOW_MIN_WIDTH minWidth = WINDOW_MIN_WIDTH
minHeight = WINDOW_MIN_HEIGHT minHeight = WINDOW_MIN_HEIGHT
scene = primaryScene scene = primaryScene
icons.add(Image(javaClass.getResourceAsStream("/img/icon.png")))
}.show() }.show()
} }

View File

@ -14,7 +14,11 @@ class Config {
private const val LOCALE_KEY = "locale" private const val LOCALE_KEY = "locale"
private const val THEME_KEY = "theme" private const val THEME_KEY = "theme"
private const val SCRIPT_KEY = "script" private const val SCRIPT_KEY = "script"
private const val DETAIL_HEADWORD_FONT_SIZE_KEY = "detailHeadwordFontSize"
private const val DETAIL_PRONUNCIATION_FONT_SIZE_KEY = "detailPronunciationFontSize"
private const val DEFAULT_DETAIL_HEADWORD_FONT_SIZE = 40
private const val DEFAULT_DETAIL_PRONUNCIATION_FONT_SIZE = 16
private val DEFAULT_THEME = Theme.SYSTEM private val DEFAULT_THEME = Theme.SYSTEM
private val DEFAULT_SCRIPT = Script.SIMPLIFIED private val DEFAULT_SCRIPT = Script.SIMPLIFIED
private val DEFAULT_LOCALE = Locale.ENGLISH private val DEFAULT_LOCALE = Locale.ENGLISH
@ -22,19 +26,21 @@ class Config {
private val preferences = Preferences.userNodeForPackage(this::class.java) private val preferences = Preferences.userNodeForPackage(this::class.java)
val searchResults = SearchResultsConfig(preferences) val searchResults = SearchResultsConfig(preferences)
val details = DetailsConfig(preferences)
val locale: ObjectProperty<Locale> = SimpleObjectProperty(DEFAULT_LOCALE) val locale: ObjectProperty<Locale> = SimpleObjectProperty(DEFAULT_LOCALE)
val theme: ObjectProperty<Theme> = SimpleObjectProperty(DEFAULT_THEME) val theme: ObjectProperty<Theme> = SimpleObjectProperty(DEFAULT_THEME)
val script: ObjectProperty<Script> = SimpleObjectProperty(DEFAULT_SCRIPT) val script: ObjectProperty<Script> = SimpleObjectProperty(DEFAULT_SCRIPT)
val detailHeadwordFontSize: IntegerProperty = SimpleIntegerProperty(DEFAULT_DETAIL_HEADWORD_FONT_SIZE)
val detailPronunciationFontSize: IntegerProperty = SimpleIntegerProperty(DEFAULT_DETAIL_PRONUNCIATION_FONT_SIZE)
fun save() { fun save() {
preferences.put(LOCALE_KEY, locale.value.toLanguageTag()) preferences.put(LOCALE_KEY, locale.value.toLanguageTag())
preferences.put(THEME_KEY, theme.value.name) preferences.put(THEME_KEY, theme.value.name)
preferences.put(SCRIPT_KEY, script.value.name) preferences.put(SCRIPT_KEY, script.value.name)
preferences.putInt(DETAIL_HEADWORD_FONT_SIZE_KEY, detailHeadwordFontSize.value)
preferences.putInt(DETAIL_PRONUNCIATION_FONT_SIZE_KEY, detailPronunciationFontSize.value)
searchResults.save() searchResults.save()
details.save()
preferences.flush() preferences.flush()
} }
@ -42,6 +48,16 @@ class Config {
fun load() { fun load() {
preferences.sync() preferences.sync()
detailHeadwordFontSize.value = preferences.getInt(
DETAIL_HEADWORD_FONT_SIZE_KEY,
DEFAULT_DETAIL_HEADWORD_FONT_SIZE
)
detailPronunciationFontSize.value = preferences.getInt(
DETAIL_PRONUNCIATION_FONT_SIZE_KEY,
DEFAULT_DETAIL_PRONUNCIATION_FONT_SIZE
)
theme.value = Theme.valueOf( theme.value = Theme.valueOf(
preferences.get( preferences.get(
THEME_KEY, THEME_KEY,
@ -59,7 +75,10 @@ class Config {
locale.value = Locale.forLanguageTag(preferences.get(LOCALE_KEY, DEFAULT_LOCALE.toLanguageTag())) locale.value = Locale.forLanguageTag(preferences.get(LOCALE_KEY, DEFAULT_LOCALE.toLanguageTag()))
searchResults.load() searchResults.load()
details.load() }
fun reset() {
detailHeadwordFontSize.value = DEFAULT_DETAIL_HEADWORD_FONT_SIZE
} }
} }
@ -125,42 +144,3 @@ class SearchResultsConfig(private val preferences: Preferences) {
) )
} }
} }
class DetailsConfig(private val preferences: Preferences) {
companion object {
private const val PRONUNCIATION_KEY = "detailsPronunciation"
private const val HEADWORD_FONT_SIZE_KEY = "detailsHeadwordFontSize"
private const val PRONUNCIATION_FONT_SIZE_KEY = "detailsPronunciationFontSize"
private val DEFAULT_PRONUNCIATION = Pronunciation.PINYIN_WITH_TONE_MARKS
private const val DEFAULT_HEADWORD_FONT_SIZE = 40
private const val DEFAULT_PRONUNCIATION_FONT_SIZE = 16
}
val pronunciation: ObjectProperty<Pronunciation> = SimpleObjectProperty(DEFAULT_PRONUNCIATION)
val headwordFontSize: IntegerProperty = SimpleIntegerProperty(DEFAULT_HEADWORD_FONT_SIZE)
val pronunciationFontSize: IntegerProperty = SimpleIntegerProperty(DEFAULT_PRONUNCIATION_FONT_SIZE)
fun save() {
preferences.put(PRONUNCIATION_KEY, pronunciation.value.name)
preferences.putInt(HEADWORD_FONT_SIZE_KEY, headwordFontSize.value)
preferences.putInt(PRONUNCIATION_FONT_SIZE_KEY, pronunciationFontSize.value)
}
fun load() {
headwordFontSize.value = preferences.getInt(
HEADWORD_FONT_SIZE_KEY,
DEFAULT_HEADWORD_FONT_SIZE
)
pronunciationFontSize.value = preferences.getInt(
PRONUNCIATION_FONT_SIZE_KEY,
DEFAULT_PRONUNCIATION_FONT_SIZE
)
pronunciation.value = Pronunciation.valueOf(
preferences.get(
PRONUNCIATION_KEY,
DEFAULT_PRONUNCIATION.name
)
)
}
}

View File

@ -78,11 +78,9 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
} }
private val findCharacters = """ private val findCharacters = """
WITH cte(id, character) AS (VALUES ?) SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions FROM cedict
FROM cedict INNER JOIN cte WHERE traditional IN (?)
ON cte.character = cedict.traditional OR cte.character = cedict.simplified
ORDER BY cte.id
""".trimIndent() """.trimIndent()
override fun search(query: String, searchMode: SearchMode) = when (searchMode) { override fun search(query: String, searchMode: SearchMode) = when (searchMode) {
@ -109,8 +107,7 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
val characterList = entry.traditional val characterList = entry.traditional
.split("") .split("")
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.mapIndexed { index, s -> "($index, '$s')" } .joinToString(",") { "'$it'" }
.joinToString(",")
val query = findCharacters.replace("?", characterList) val query = findCharacters.replace("?", characterList)

View File

@ -2,19 +2,14 @@ package com.marvinelsen.willow.ui.controllers
import com.marvinelsen.willow.Model import com.marvinelsen.willow.Model
import com.marvinelsen.willow.config.Config import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Pronunciation
import com.marvinelsen.willow.config.Script import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.ui.DictionaryEntryFx import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
import com.marvinelsen.willow.ui.util.createContextMenuForEntry
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ListView import javafx.scene.control.ListView
import javafx.scene.control.ProgressIndicator
import javafx.scene.control.TabPane import javafx.scene.control.TabPane
import javafx.scene.input.ContextMenuEvent
import javafx.scene.layout.FlowPane
import javafx.scene.web.WebView import javafx.scene.web.WebView
import kotlinx.html.body import kotlinx.html.body
import kotlinx.html.h1 import kotlinx.html.h1
@ -24,113 +19,62 @@ import kotlinx.html.ol
import kotlinx.html.stream.createHTML import kotlinx.html.stream.createHTML
import java.util.ResourceBundle import java.util.ResourceBundle
@Suppress("UnusedPrivateMember")
class DetailsController(private val model: Model, private val config: Config) { class DetailsController(private val model: Model, private val config: Config) {
@FXML @FXML
private lateinit var resources: ResourceBundle private lateinit var resources: ResourceBundle
@FXML
private lateinit var flowPaneHeader: FlowPane
@FXML
private lateinit var labelHeadword: Label
@FXML
private lateinit var labelPronunciation: Label
@FXML @FXML
private lateinit var tabPaneDetails: TabPane private lateinit var tabPaneDetails: TabPane
@FXML @FXML
private lateinit var webViewDefinition: WebView private lateinit var webViewDefinition: WebView
@FXML
@Suppress("UnusedPrivateProperty")
private lateinit var listviewSentences: ListView<DictionaryEntryFx>
@FXML @FXML
private lateinit var listViewWords: ListView<DictionaryEntryFx> private lateinit var listViewWords: ListView<DictionaryEntryFx>
@FXML @FXML
@Suppress("UnusedPrivateProperty")
private lateinit var listViewCharacters: ListView<DictionaryEntryFx> private lateinit var listViewCharacters: ListView<DictionaryEntryFx>
@FXML @FXML
private lateinit var progressIndicatorCharacters: ProgressIndicator private lateinit var labelHeadword: Label
@FXML
private lateinit var progressIndicatorWords: ProgressIndicator
@FXML
private lateinit var labelNoCharactersFound: Label
@FXML
private lateinit var labelNoWordsFound: Label
@FXML @FXML
@Suppress("UnusedPrivateMember")
private fun initialize() { private fun initialize() {
initializeLabelHeadword() initializeLabelHeadword()
initializeLabelPronunciation()
initializeTabPaneDetails() initializeTabPaneDetails()
initializeListViewWords() initializeListViewWords()
initializeListViewCharacters()
initializeWebViewDefinition() initializeWebViewDefinition()
}
private fun initializeLabelHeadword() { model.selectedEntry.addListener { _, _, newEntry ->
labelHeadword.apply { if (newEntry == null) return@addListener
textProperty().bind(
Bindings.createStringBinding( when (tabPaneDetails.selectionModel.selectedItem.id) {
{ "tabWords" -> {
val selectedEntry = model.selectedEntry.value model.findWords()
when (config.script.value!!) { }
Script.SIMPLIFIED -> selectedEntry?.simplifiedProperty?.value
Script.TRADITIONAL -> selectedEntry?.traditionalProperty?.value else -> {}
} }
}, webViewDefinition.engine.loadContent(newEntry.createCedictDefinitionHtml())
config.script,
model.selectedEntry
)
)
styleProperty().bind(
Bindings.concat(
"-fx-font-size: ",
config.details.headwordFontSize.asString(),
"px;"
)
)
} }
} }
private fun initializeLabelPronunciation() { private fun initializeWebViewDefinition() {
labelPronunciation.apply { webViewDefinition.apply {
textProperty().bind( engine.userStyleSheetLocation = this::class.java.getResource("/css/definitions.css")!!.toExternalForm()
Bindings.createStringBinding( }
{ }
val selectedEntry = model.selectedEntry.value
when (config.details.pronunciation.value!!) {
Pronunciation.PINYIN_WITH_TONE_MARKS ->
selectedEntry
?.pinyinWithToneMarksProperty
?.value
Pronunciation.PINYIN_WITH_TONE_NUMBERS -> private fun initializeListViewWords() {
selectedEntry listViewWords.apply {
?.pinyinWithToneNumbersProperty cellFactory = DictionaryEntryCellFactory(resources, config)
?.value items = model.wordsContaining
Pronunciation.ZHUYIN ->
selectedEntry
?.zhuyinProperty
?.value
}
},
config.details.pronunciation,
model.selectedEntry
)
)
styleProperty().bind(
Bindings.concat(
"-fx-font-size: ",
config.details.pronunciationFontSize.asString(),
"px;"
)
)
} }
} }
@ -141,52 +85,32 @@ class DetailsController(private val model: Model, private val config: Config) {
selectionModel.selectedItemProperty().addListener { _, _, selectedTab -> selectionModel.selectedItemProperty().addListener { _, _, selectedTab ->
if (model.selectedEntry.value == null) return@addListener if (model.selectedEntry.value == null) return@addListener
lazyUpdateTabContent(selectedTab.id) when (selectedTab.id) {
"tabWords" -> {
model.findWords()
}
else -> {}
}
} }
} }
model.selectedEntry.addListener { _, _, newEntry ->
if (newEntry == null) return@addListener
lazyUpdateTabContent(tabPaneDetails.selectionModel.selectedItem.id)
}
} }
private fun initializeListViewWords() { private fun initializeLabelHeadword() {
listViewWords.apply { labelHeadword.apply {
cellFactory = DictionaryEntryCellFactory(resources, config) textProperty().bind(
items = model.wordsContaining Bindings.createStringBinding(
{
disableProperty().bind(Bindings.or(model.isFindingWords, Bindings.isEmpty(model.wordsContaining))) when (config.script.value!!) {
} Script.SIMPLIFIED -> model.selectedEntry.value?.simplifiedProperty?.value
progressIndicatorWords.visibleProperty().bind(model.isFindingWords) Script.TRADITIONAL -> model.selectedEntry.value?.traditionalProperty?.value
labelNoWordsFound }
.visibleProperty() },
.bind(Bindings.and(Bindings.isEmpty(model.wordsContaining), Bindings.not(model.isFindingWords))) config.script,
} model.selectedEntry
)
private fun initializeListViewCharacters() { )
listViewCharacters.apply { styleProperty().bind(Bindings.concat("-fx-font-size: ", config.detailHeadwordFontSize.asString(), "px;"))
cellFactory = DictionaryEntryCellFactory(resources, config)
items = model.characters
disableProperty().bind(Bindings.or(model.isFindingCharacters, Bindings.isEmpty(model.characters)))
}
progressIndicatorCharacters.visibleProperty().bind(model.isFindingCharacters)
labelNoCharactersFound
.visibleProperty()
.bind(Bindings.and(Bindings.isEmpty(model.characters), Bindings.not(model.isFindingCharacters)))
}
private fun initializeWebViewDefinition() {
webViewDefinition.apply {
engine.userStyleSheetLocation = this::class.java.getResource("/css/definitions.css")!!.toExternalForm()
}
model.selectedEntry.addListener { _, _, newEntry ->
if (newEntry == null) return@addListener
webViewDefinition.engine.loadContent(newEntry.createCedictDefinitionHtml())
} }
} }
@ -204,33 +128,4 @@ class DetailsController(private val model: Model, private val config: Config) {
} }
} }
} }
@FXML
private fun headerOnContextMenuRequested(contextMenuEvent: ContextMenuEvent) {
if (model.selectedEntry.value == null) return
createContextMenuForEntry(model.selectedEntry.value, resources).show(
flowPaneHeader.scene.window,
contextMenuEvent.screenX,
contextMenuEvent.screenY
)
}
private fun lazyUpdateTabContent(selectedTabId: String?) {
when (selectedTabId) {
"tabWords" -> {
if (model.wordsContaining.isNotEmpty()) return
model.findWords()
}
"tabCharacters" -> {
if (model.characters.isNotEmpty()) return
model.findCharacters()
}
else -> {}
}
}
} }

View File

@ -42,9 +42,6 @@ class PreferencesDialog(owner: Window?, config: Config) : Dialog<PreferencesDial
@FXML @FXML
private lateinit var comboBoxPronunciationSearchResults: ComboBox<Pronunciation> private lateinit var comboBoxPronunciationSearchResults: ComboBox<Pronunciation>
@FXML
private lateinit var comboBoxPronunciationDetails: ComboBox<Pronunciation>
@FXML @FXML
private lateinit var checkBoxShowPronunciationSearchResults: CheckBox private lateinit var checkBoxShowPronunciationSearchResults: CheckBox
@ -66,8 +63,8 @@ class PreferencesDialog(owner: Window?, config: Config) : Dialog<PreferencesDial
@FXML @FXML
private lateinit var spinnerDefinitionFontSizeSearchResults: Spinner<Int> private lateinit var spinnerDefinitionFontSizeSearchResults: Spinner<Int>
private val entryHeadwordFontSizeObjectProperty = config.details.headwordFontSize.asObject() private val entryHeadwordFontSizeObjectProperty = config.detailHeadwordFontSize.asObject()
private val entryPronunciationFontSizeObjectProperty = config.details.pronunciationFontSize.asObject() private val entryPronunciationFontSizeObjectProperty = config.detailPronunciationFontSize.asObject()
private val searchResultHeadwordFontSizeObjectProperty = config.searchResults.headwordFontSize.asObject() private val searchResultHeadwordFontSizeObjectProperty = config.searchResults.headwordFontSize.asObject()
private val searchResultPronunciationFontSizeObjectProperty = config.searchResults.pronunciationFontSize.asObject() private val searchResultPronunciationFontSizeObjectProperty = config.searchResults.pronunciationFontSize.asObject()
@ -103,7 +100,6 @@ class PreferencesDialog(owner: Window?, config: Config) : Dialog<PreferencesDial
comboBoxScript.valueProperty().bindBidirectional(config.script) comboBoxScript.valueProperty().bindBidirectional(config.script)
comboBoxLocale.valueProperty().bindBidirectional(config.locale) comboBoxLocale.valueProperty().bindBidirectional(config.locale)
comboBoxPronunciationSearchResults.valueProperty().bindBidirectional(config.searchResults.pronunciation) comboBoxPronunciationSearchResults.valueProperty().bindBidirectional(config.searchResults.pronunciation)
comboBoxPronunciationDetails.valueProperty().bindBidirectional(config.details.pronunciation)
checkBoxShowDefinitionSearchResults checkBoxShowDefinitionSearchResults
.selectedProperty() .selectedProperty()

View File

@ -30,13 +30,3 @@ class FindWordsService(private val dictionary: Dictionary) : Service<ObservableL
FXCollections.observableList(dictionary.findWordsContaining(entry.toDomain()).map { it.toFx() }) FXCollections.observableList(dictionary.findWordsContaining(entry.toDomain()).map { it.toFx() })
} }
} }
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

@ -1,7 +1,4 @@
.headword { .headword {
-fx-font-family: TW-Kai; -fx-font-family: TW-Kai;
} -fx-font-size: 40;
.pronunciation {
-fx-font-family: "Noto Sans TC";
} }

View File

@ -1,3 +1,7 @@
.root { .root {
-fx-font-family: "Inter Variable"; -fx-font-family: "Inter Variable";
}
.details-pronunciation {
-fx-font: 16 "Noto Sans TC";
} }

View File

@ -1,55 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?> <?import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.web.WebView?> <?import javafx.scene.web.WebView?>
<?import javafx.geometry.Insets?>
<VBox xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1" <VBox xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.marvinelsen.willow.ui.controllers.DetailsController" fx:controller="com.marvinelsen.willow.ui.controllers.DetailsController"
stylesheets="/css/details.css"> stylesheets="/css/details.css">
<FlowPane fx:id="flowPaneHeader" hgap="8.0" onContextMenuRequested="#headerOnContextMenuRequested" <Label fx:id="labelHeadword" styleClass="headword" text="Label">
prefHeight="38.0" prefWidth="412.0" rowValignment="BASELINE" vgap="8.0" VBox.vgrow="NEVER">
<padding> <padding>
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0"/> <Insets left="8" right="8" top="8" bottom="8"/>
</padding> </padding>
<Label fx:id="labelHeadword" styleClass="headword" text="Label"> </Label>
<padding>
<Insets left="8" right="8" top="8" bottom="8"/>
</padding>
</Label>
<Label fx:id="labelPronunciation" styleClass="pronunciation">
</Label>
</FlowPane>
<TabPane fx:id="tabPaneDetails" tabClosingPolicy="UNAVAILABLE" disable="true" VBox.vgrow="ALWAYS"> <TabPane fx:id="tabPaneDetails" tabClosingPolicy="UNAVAILABLE" disable="true" VBox.vgrow="ALWAYS">
<Tab closable="false" text="%tab.definition"> <Tab closable="false" disable="false" text="%tab.definition">
<WebView fx:id="webViewDefinition" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0"/> <WebView fx:id="webViewDefinition" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0"
prefWidth="-1.0"/>
</Tab> </Tab>
<Tab id="tabSentences" closable="false" text="%tab.sentences"> <Tab id="tabSentences" closable="false" disable="false" text="%tab.sentences">
<ListView fx:id="listViewSentences"/> <ListView fx:id="listviewSentences"/>
</Tab> </Tab>
<Tab id="tabWords" closable="false" text="%tab.words"> <Tab id="tabWords" closable="false" disable="false" text="%tab.words">
<StackPane> <ListView fx:id="listViewWords"/>
<ListView fx:id="listViewWords"/>
<Label fx:id="labelNoWordsFound" text="%list.no_words_found" textAlignment="CENTER"
visible="false" wrapText="true">
<padding>
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0"/>
</padding>
</Label>
<ProgressIndicator fx:id="progressIndicatorWords" visible="false"/>
</StackPane>
</Tab> </Tab>
<Tab id="tabCharacters" closable="false" text="%tab.characters"> <Tab closable="false" disable="false" text="%tab.characters">
<StackPane> <ListView fx:id="listViewCharacters"/>
<ListView fx:id="listViewCharacters"/>
<Label fx:id="labelNoCharactersFound" text="%list.no_characters_found" textAlignment="CENTER"
visible="false" wrapText="true">
<padding>
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0"/>
</padding>
</Label>
<ProgressIndicator fx:id="progressIndicatorCharacters" visible="false"/>
</StackPane>
</Tab> </Tab>
</TabPane> </TabPane>
</VBox> </VBox>

View File

@ -18,5 +18,3 @@ menubar.help=_Help
menubar.help.about=_About… menubar.help.about=_About…
list.no_entries_found=No matching entries found list.no_entries_found=No matching entries found
search.mode.phrase=Phrase search.mode.phrase=Phrase
list.no_characters_found=No characters found
list.no_words_found=No words found

View File

@ -18,5 +18,3 @@ menubar.help=_Hilfe
menubar.help.about=_Über… menubar.help.about=_Über…
list.no_entries_found=No matching entries found list.no_entries_found=No matching entries found
search.mode.phrase=Phrase search.mode.phrase=Phrase
list.no_characters_found=No characters found
list.no_words_found=No words found

View File

@ -18,5 +18,3 @@ menubar.help=_Help
menubar.help.about=_About… menubar.help.about=_About…
list.no_entries_found=No matching entries found list.no_entries_found=No matching entries found
search.mode.phrase=Phrase search.mode.phrase=Phrase
list.no_characters_found=No characters found
list.no_words_found=No words found

View File

@ -18,5 +18,3 @@ menubar.help=_說明
menubar.help.about=_關於 Willow… menubar.help.about=_關於 Willow…
list.no_entries_found=No matching entries found list.no_entries_found=No matching entries found
search.mode.phrase=Phrase search.mode.phrase=Phrase
list.no_characters_found=No characters found
list.no_words_found=No words found

View File

@ -18,5 +18,3 @@ menubar.help=_說明
menubar.help.about=_關於 Willow… menubar.help.about=_關於 Willow…
list.no_entries_found=No matching entries found list.no_entries_found=No matching entries found
search.mode.phrase=Phrase search.mode.phrase=Phrase
list.no_characters_found=No characters found
list.no_words_found=No words found

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.