Compare commits
9 Commits
7add73fe28
...
46af4db9f1
Author | SHA1 | Date | |
---|---|---|---|
46af4db9f1 | |||
630d464916 | |||
f6f2cfac5c | |||
8ada0a5510 | |||
12eb98d5b1 | |||
d9b7b82c60 | |||
cb52240eea | |||
3939021285 | |||
1ab6ef453a |
@ -30,6 +30,8 @@ dependencies {
|
||||
|
||||
implementation(libs.segment)
|
||||
|
||||
implementation(libs.ikonli.javafx)
|
||||
|
||||
testImplementation(libs.kotest.core)
|
||||
testImplementation(libs.kotest.assertions)
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ kotlinx-html-jvm = "0.11.0"
|
||||
|
||||
segment = "0.3.1"
|
||||
|
||||
ikonli-javafx = "12.3.1"
|
||||
|
||||
[libraries]
|
||||
chinese-transliteration = { module = "com.marvinelsen:chinese-transliteration", version.ref = "chinese-transliteration" }
|
||||
cedict-parser = { module = "com.marvinelsen:cedict-parser", version.ref = "cedict-parser" }
|
||||
@ -32,6 +34,8 @@ kotlinx-html-jvm = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version.
|
||||
|
||||
segment = { module = "com.github.houbb:segment", version.ref = "segment" }
|
||||
|
||||
ikonli-javafx = { module = "org.kordamp.ikonli:ikonli-javafx", version.ref = "ikonli-javafx" }
|
||||
|
||||
# Detekt
|
||||
# See: https://detekt.dev
|
||||
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
|
||||
|
@ -3,6 +3,7 @@ package com.marvinelsen.willow
|
||||
import com.marvinelsen.willow.domain.SearchMode
|
||||
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
||||
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.SearchService
|
||||
import javafx.beans.property.ObjectProperty
|
||||
@ -13,10 +14,15 @@ import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.event.EventHandler
|
||||
|
||||
class Model(private val searchService: SearchService, private val findWordsService: FindWordsService) {
|
||||
class Model(
|
||||
private val searchService: SearchService,
|
||||
private val findWordsService: FindWordsService,
|
||||
private val findCharacterService: FindCharacterService,
|
||||
) {
|
||||
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()
|
||||
|
||||
val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry
|
||||
|
||||
@ -24,9 +30,12 @@ class Model(private val searchService: SearchService, private val findWordsServi
|
||||
FXCollections.unmodifiableObservableList(internalSearchResults)
|
||||
val wordsContaining: ObservableList<DictionaryEntryFx> =
|
||||
FXCollections.unmodifiableObservableList(internalWordsContaining)
|
||||
val characters: ObservableList<DictionaryEntryFx> =
|
||||
FXCollections.unmodifiableObservableList(internalCharacters)
|
||||
|
||||
val isSearching: ReadOnlyBooleanProperty = searchService.runningProperty()
|
||||
val isFindingWords: ReadOnlyBooleanProperty = findWordsService.runningProperty()
|
||||
val isFindingCharacters: ReadOnlyBooleanProperty = findCharacterService.runningProperty()
|
||||
|
||||
init {
|
||||
searchService.onSucceeded = EventHandler {
|
||||
@ -35,6 +44,9 @@ class Model(private val searchService: SearchService, private val findWordsServi
|
||||
findWordsService.onSucceeded = EventHandler {
|
||||
internalWordsContaining.setAll(findWordsService.value)
|
||||
}
|
||||
findCharacterService.onSucceeded = EventHandler {
|
||||
internalCharacters.setAll(findCharacterService.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun search(query: String, searchMode: SearchMode) {
|
||||
@ -48,7 +60,14 @@ class Model(private val searchService: SearchService, private val findWordsServi
|
||||
findWordsService.restart()
|
||||
}
|
||||
|
||||
fun findCharacters() {
|
||||
findCharacterService.entry = internalSelectedEntry.value
|
||||
findCharacterService.restart()
|
||||
}
|
||||
|
||||
fun selectEntry(entry: DictionaryEntryFx) {
|
||||
internalWordsContaining.setAll(emptyList())
|
||||
internalCharacters.setAll(emptyList())
|
||||
internalSelectedEntry.value = entry
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,13 @@ import com.marvinelsen.willow.ui.controllers.MainController
|
||||
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.util.FindCharacterService
|
||||
import com.marvinelsen.willow.ui.util.FindWordsService
|
||||
import com.marvinelsen.willow.ui.util.SearchService
|
||||
import javafx.application.Application
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.scene.text.Font
|
||||
import javafx.stage.Stage
|
||||
@ -29,7 +31,7 @@ class WillowApplication : Application() {
|
||||
|
||||
private const val FONT_SIZE = 12.0
|
||||
|
||||
private const val JDBC_CONNECTION_STRING = "jdbc:sqlite:dictionary.db"
|
||||
private const val JDBC_CONNECTION_STRING = "jdbc:sqlite::resource:data/dictionary.db"
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
@ -43,7 +45,8 @@ class WillowApplication : Application() {
|
||||
val dictionary = SqliteDictionary(connection)
|
||||
val searchService = SearchService(dictionary)
|
||||
val findWordsService = FindWordsService(dictionary)
|
||||
val model = Model(searchService, findWordsService)
|
||||
val findCharacterService = FindCharacterService(dictionary)
|
||||
val model = Model(searchService, findWordsService, findCharacterService)
|
||||
val config = Config()
|
||||
config.load()
|
||||
|
||||
@ -69,6 +72,7 @@ class WillowApplication : Application() {
|
||||
minWidth = WINDOW_MIN_WIDTH
|
||||
minHeight = WINDOW_MIN_HEIGHT
|
||||
scene = primaryScene
|
||||
icons.add(Image(javaClass.getResourceAsStream("/img/icon.png")))
|
||||
}.show()
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,7 @@ class Config {
|
||||
private const val LOCALE_KEY = "locale"
|
||||
private const val THEME_KEY = "theme"
|
||||
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_SCRIPT = Script.SIMPLIFIED
|
||||
private val DEFAULT_LOCALE = Locale.ENGLISH
|
||||
@ -26,21 +22,19 @@ class Config {
|
||||
|
||||
private val preferences = Preferences.userNodeForPackage(this::class.java)
|
||||
val searchResults = SearchResultsConfig(preferences)
|
||||
val details = DetailsConfig(preferences)
|
||||
|
||||
val locale: ObjectProperty<Locale> = SimpleObjectProperty(DEFAULT_LOCALE)
|
||||
val theme: ObjectProperty<Theme> = SimpleObjectProperty(DEFAULT_THEME)
|
||||
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() {
|
||||
preferences.put(LOCALE_KEY, locale.value.toLanguageTag())
|
||||
preferences.put(THEME_KEY, theme.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()
|
||||
details.save()
|
||||
|
||||
preferences.flush()
|
||||
}
|
||||
@ -48,16 +42,6 @@ class Config {
|
||||
fun load() {
|
||||
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(
|
||||
preferences.get(
|
||||
THEME_KEY,
|
||||
@ -75,10 +59,7 @@ class Config {
|
||||
locale.value = Locale.forLanguageTag(preferences.get(LOCALE_KEY, DEFAULT_LOCALE.toLanguageTag()))
|
||||
|
||||
searchResults.load()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
detailHeadwordFontSize.value = DEFAULT_DETAIL_HEADWORD_FONT_SIZE
|
||||
details.load()
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,3 +125,42 @@ 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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -78,9 +78,11 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
||||
}
|
||||
|
||||
private val findCharacters = """
|
||||
WITH cte(id, character) AS (VALUES ?)
|
||||
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions
|
||||
FROM cedict
|
||||
WHERE traditional IN (?)
|
||||
FROM cedict INNER JOIN cte
|
||||
ON cte.character = cedict.traditional OR cte.character = cedict.simplified
|
||||
ORDER BY cte.id
|
||||
""".trimIndent()
|
||||
|
||||
override fun search(query: String, searchMode: SearchMode) = when (searchMode) {
|
||||
@ -107,7 +109,8 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
||||
val characterList = entry.traditional
|
||||
.split("")
|
||||
.filter { it.isNotBlank() }
|
||||
.joinToString(",") { "'$it'" }
|
||||
.mapIndexed { index, s -> "($index, '$s')" }
|
||||
.joinToString(",")
|
||||
|
||||
val query = findCharacters.replace("?", characterList)
|
||||
|
||||
|
@ -2,14 +2,19 @@ package com.marvinelsen.willow.ui.controllers
|
||||
|
||||
import com.marvinelsen.willow.Model
|
||||
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.cells.DictionaryEntryCellFactory
|
||||
import com.marvinelsen.willow.ui.util.createContextMenuForEntry
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ListView
|
||||
import javafx.scene.control.ProgressIndicator
|
||||
import javafx.scene.control.TabPane
|
||||
import javafx.scene.input.ContextMenuEvent
|
||||
import javafx.scene.layout.FlowPane
|
||||
import javafx.scene.web.WebView
|
||||
import kotlinx.html.body
|
||||
import kotlinx.html.h1
|
||||
@ -19,62 +24,113 @@ import kotlinx.html.ol
|
||||
import kotlinx.html.stream.createHTML
|
||||
import java.util.ResourceBundle
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
class DetailsController(private val model: Model, private val config: Config) {
|
||||
@FXML
|
||||
private lateinit var resources: ResourceBundle
|
||||
|
||||
@FXML
|
||||
private lateinit var flowPaneHeader: FlowPane
|
||||
|
||||
@FXML
|
||||
private lateinit var labelHeadword: Label
|
||||
|
||||
@FXML
|
||||
private lateinit var labelPronunciation: Label
|
||||
|
||||
@FXML
|
||||
private lateinit var tabPaneDetails: TabPane
|
||||
|
||||
@FXML
|
||||
private lateinit var webViewDefinition: WebView
|
||||
|
||||
@FXML
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
private lateinit var listviewSentences: ListView<DictionaryEntryFx>
|
||||
|
||||
@FXML
|
||||
private lateinit var listViewWords: ListView<DictionaryEntryFx>
|
||||
|
||||
@FXML
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
private lateinit var listViewCharacters: ListView<DictionaryEntryFx>
|
||||
|
||||
@FXML
|
||||
private lateinit var labelHeadword: Label
|
||||
private lateinit var progressIndicatorCharacters: ProgressIndicator
|
||||
|
||||
@FXML
|
||||
private lateinit var progressIndicatorWords: ProgressIndicator
|
||||
|
||||
@FXML
|
||||
private lateinit var labelNoCharactersFound: Label
|
||||
|
||||
@FXML
|
||||
private lateinit var labelNoWordsFound: Label
|
||||
|
||||
@FXML
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun initialize() {
|
||||
initializeLabelHeadword()
|
||||
initializeLabelPronunciation()
|
||||
initializeTabPaneDetails()
|
||||
initializeListViewWords()
|
||||
initializeListViewCharacters()
|
||||
initializeWebViewDefinition()
|
||||
}
|
||||
|
||||
model.selectedEntry.addListener { _, _, newEntry ->
|
||||
if (newEntry == null) return@addListener
|
||||
|
||||
when (tabPaneDetails.selectionModel.selectedItem.id) {
|
||||
"tabWords" -> {
|
||||
model.findWords()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
webViewDefinition.engine.loadContent(newEntry.createCedictDefinitionHtml())
|
||||
private fun initializeLabelHeadword() {
|
||||
labelHeadword.apply {
|
||||
textProperty().bind(
|
||||
Bindings.createStringBinding(
|
||||
{
|
||||
val selectedEntry = model.selectedEntry.value
|
||||
when (config.script.value!!) {
|
||||
Script.SIMPLIFIED -> selectedEntry?.simplifiedProperty?.value
|
||||
Script.TRADITIONAL -> selectedEntry?.traditionalProperty?.value
|
||||
}
|
||||
},
|
||||
config.script,
|
||||
model.selectedEntry
|
||||
)
|
||||
)
|
||||
styleProperty().bind(
|
||||
Bindings.concat(
|
||||
"-fx-font-size: ",
|
||||
config.details.headwordFontSize.asString(),
|
||||
"px;"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeWebViewDefinition() {
|
||||
webViewDefinition.apply {
|
||||
engine.userStyleSheetLocation = this::class.java.getResource("/css/definitions.css")!!.toExternalForm()
|
||||
}
|
||||
}
|
||||
private fun initializeLabelPronunciation() {
|
||||
labelPronunciation.apply {
|
||||
textProperty().bind(
|
||||
Bindings.createStringBinding(
|
||||
{
|
||||
val selectedEntry = model.selectedEntry.value
|
||||
when (config.details.pronunciation.value!!) {
|
||||
Pronunciation.PINYIN_WITH_TONE_MARKS ->
|
||||
selectedEntry
|
||||
?.pinyinWithToneMarksProperty
|
||||
?.value
|
||||
|
||||
private fun initializeListViewWords() {
|
||||
listViewWords.apply {
|
||||
cellFactory = DictionaryEntryCellFactory(resources, config)
|
||||
items = model.wordsContaining
|
||||
Pronunciation.PINYIN_WITH_TONE_NUMBERS ->
|
||||
selectedEntry
|
||||
?.pinyinWithToneNumbersProperty
|
||||
?.value
|
||||
|
||||
Pronunciation.ZHUYIN ->
|
||||
selectedEntry
|
||||
?.zhuyinProperty
|
||||
?.value
|
||||
}
|
||||
},
|
||||
config.details.pronunciation,
|
||||
model.selectedEntry
|
||||
)
|
||||
)
|
||||
styleProperty().bind(
|
||||
Bindings.concat(
|
||||
"-fx-font-size: ",
|
||||
config.details.pronunciationFontSize.asString(),
|
||||
"px;"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,32 +141,52 @@ class DetailsController(private val model: Model, private val config: Config) {
|
||||
selectionModel.selectedItemProperty().addListener { _, _, selectedTab ->
|
||||
if (model.selectedEntry.value == null) return@addListener
|
||||
|
||||
when (selectedTab.id) {
|
||||
"tabWords" -> {
|
||||
model.findWords()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
lazyUpdateTabContent(selectedTab.id)
|
||||
}
|
||||
}
|
||||
|
||||
model.selectedEntry.addListener { _, _, newEntry ->
|
||||
if (newEntry == null) return@addListener
|
||||
|
||||
lazyUpdateTabContent(tabPaneDetails.selectionModel.selectedItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeLabelHeadword() {
|
||||
labelHeadword.apply {
|
||||
textProperty().bind(
|
||||
Bindings.createStringBinding(
|
||||
{
|
||||
when (config.script.value!!) {
|
||||
Script.SIMPLIFIED -> model.selectedEntry.value?.simplifiedProperty?.value
|
||||
Script.TRADITIONAL -> model.selectedEntry.value?.traditionalProperty?.value
|
||||
}
|
||||
},
|
||||
config.script,
|
||||
model.selectedEntry
|
||||
)
|
||||
)
|
||||
styleProperty().bind(Bindings.concat("-fx-font-size: ", config.detailHeadwordFontSize.asString(), "px;"))
|
||||
private fun initializeListViewWords() {
|
||||
listViewWords.apply {
|
||||
cellFactory = DictionaryEntryCellFactory(resources, config)
|
||||
items = model.wordsContaining
|
||||
|
||||
disableProperty().bind(Bindings.or(model.isFindingWords, Bindings.isEmpty(model.wordsContaining)))
|
||||
}
|
||||
progressIndicatorWords.visibleProperty().bind(model.isFindingWords)
|
||||
labelNoWordsFound
|
||||
.visibleProperty()
|
||||
.bind(Bindings.and(Bindings.isEmpty(model.wordsContaining), Bindings.not(model.isFindingWords)))
|
||||
}
|
||||
|
||||
private fun initializeListViewCharacters() {
|
||||
listViewCharacters.apply {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,4 +204,33 @@ 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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ class PreferencesDialog(owner: Window?, config: Config) : Dialog<PreferencesDial
|
||||
@FXML
|
||||
private lateinit var comboBoxPronunciationSearchResults: ComboBox<Pronunciation>
|
||||
|
||||
@FXML
|
||||
private lateinit var comboBoxPronunciationDetails: ComboBox<Pronunciation>
|
||||
|
||||
@FXML
|
||||
private lateinit var checkBoxShowPronunciationSearchResults: CheckBox
|
||||
|
||||
@ -63,8 +66,8 @@ class PreferencesDialog(owner: Window?, config: Config) : Dialog<PreferencesDial
|
||||
@FXML
|
||||
private lateinit var spinnerDefinitionFontSizeSearchResults: Spinner<Int>
|
||||
|
||||
private val entryHeadwordFontSizeObjectProperty = config.detailHeadwordFontSize.asObject()
|
||||
private val entryPronunciationFontSizeObjectProperty = config.detailPronunciationFontSize.asObject()
|
||||
private val entryHeadwordFontSizeObjectProperty = config.details.headwordFontSize.asObject()
|
||||
private val entryPronunciationFontSizeObjectProperty = config.details.pronunciationFontSize.asObject()
|
||||
|
||||
private val searchResultHeadwordFontSizeObjectProperty = config.searchResults.headwordFontSize.asObject()
|
||||
private val searchResultPronunciationFontSizeObjectProperty = config.searchResults.pronunciationFontSize.asObject()
|
||||
@ -100,6 +103,7 @@ class PreferencesDialog(owner: Window?, config: Config) : Dialog<PreferencesDial
|
||||
comboBoxScript.valueProperty().bindBidirectional(config.script)
|
||||
comboBoxLocale.valueProperty().bindBidirectional(config.locale)
|
||||
comboBoxPronunciationSearchResults.valueProperty().bindBidirectional(config.searchResults.pronunciation)
|
||||
comboBoxPronunciationDetails.valueProperty().bindBidirectional(config.details.pronunciation)
|
||||
|
||||
checkBoxShowDefinitionSearchResults
|
||||
.selectedProperty()
|
||||
|
@ -30,3 +30,13 @@ class FindWordsService(private val dictionary: Dictionary) : Service<ObservableL
|
||||
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() })
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
.headword {
|
||||
-fx-font-family: TW-Kai;
|
||||
-fx-font-size: 40;
|
||||
}
|
||||
|
||||
.pronunciation {
|
||||
-fx-font-family: "Noto Sans TC";
|
||||
}
|
||||
|
@ -1,7 +1,3 @@
|
||||
.root {
|
||||
-fx-font-family: "Inter Variable";
|
||||
}
|
||||
|
||||
.details-pronunciation {
|
||||
-fx-font: 16 "Noto Sans TC";
|
||||
}
|
@ -1,31 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.web.WebView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.web.WebView?>
|
||||
<VBox xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="com.marvinelsen.willow.ui.controllers.DetailsController"
|
||||
stylesheets="/css/details.css">
|
||||
<Label fx:id="labelHeadword" styleClass="headword" text="Label">
|
||||
<FlowPane fx:id="flowPaneHeader" hgap="8.0" onContextMenuRequested="#headerOnContextMenuRequested"
|
||||
prefHeight="38.0" prefWidth="412.0" rowValignment="BASELINE" vgap="8.0" VBox.vgrow="NEVER">
|
||||
<padding>
|
||||
<Insets left="8" right="8" top="8" bottom="8"/>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0"/>
|
||||
</padding>
|
||||
</Label>
|
||||
<Label fx:id="labelHeadword" styleClass="headword" text="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">
|
||||
<Tab closable="false" disable="false" text="%tab.definition">
|
||||
<WebView fx:id="webViewDefinition" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0"
|
||||
prefWidth="-1.0"/>
|
||||
<Tab closable="false" text="%tab.definition">
|
||||
<WebView fx:id="webViewDefinition" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0"/>
|
||||
</Tab>
|
||||
<Tab id="tabSentences" closable="false" disable="false" text="%tab.sentences">
|
||||
<ListView fx:id="listviewSentences"/>
|
||||
<Tab id="tabSentences" closable="false" text="%tab.sentences">
|
||||
<ListView fx:id="listViewSentences"/>
|
||||
</Tab>
|
||||
<Tab id="tabWords" closable="false" disable="false" text="%tab.words">
|
||||
<ListView fx:id="listViewWords"/>
|
||||
<Tab id="tabWords" closable="false" text="%tab.words">
|
||||
<StackPane>
|
||||
<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 closable="false" disable="false" text="%tab.characters">
|
||||
<ListView fx:id="listViewCharacters"/>
|
||||
<Tab id="tabCharacters" closable="false" text="%tab.characters">
|
||||
<StackPane>
|
||||
<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>
|
||||
</TabPane>
|
||||
</VBox>
|
||||
|
@ -18,3 +18,5 @@ menubar.help=_Help
|
||||
menubar.help.about=_About…
|
||||
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
|
||||
|
@ -18,3 +18,5 @@ menubar.help=_Hilfe
|
||||
menubar.help.about=_Über…
|
||||
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
|
||||
|
@ -18,3 +18,5 @@ menubar.help=_Help
|
||||
menubar.help.about=_About…
|
||||
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
|
||||
|
@ -18,3 +18,5 @@ menubar.help=_說明
|
||||
menubar.help.about=_關於 Willow…
|
||||
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
|
||||
|
@ -18,3 +18,5 @@ menubar.help=_說明
|
||||
menubar.help.about=_關於 Willow…
|
||||
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
|
||||
|
BIN
src/main/resources/img/icon.png
Normal file
BIN
src/main/resources/img/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
BIN
src/main/resources/img/logo.xcf
Normal file
BIN
src/main/resources/img/logo.xcf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user