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.segment)
|
||||||
|
|
||||||
|
implementation(libs.ikonli.javafx)
|
||||||
|
|
||||||
testImplementation(libs.kotest.core)
|
testImplementation(libs.kotest.core)
|
||||||
testImplementation(libs.kotest.assertions)
|
testImplementation(libs.kotest.assertions)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ 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" }
|
||||||
@ -32,6 +34,8 @@ 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" }
|
||||||
|
@ -3,6 +3,7 @@ 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
|
||||||
@ -13,10 +14,15 @@ import javafx.collections.FXCollections
|
|||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import javafx.event.EventHandler
|
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 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
|
||||||
|
|
||||||
@ -24,9 +30,12 @@ class Model(private val searchService: SearchService, private val findWordsServi
|
|||||||
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 {
|
||||||
@ -35,6 +44,9 @@ class Model(private val searchService: SearchService, private val findWordsServi
|
|||||||
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) {
|
||||||
@ -48,7 +60,14 @@ class Model(private val searchService: SearchService, private val findWordsServi
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,13 @@ 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
|
||||||
@ -29,7 +31,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:dictionary.db"
|
private const val JDBC_CONNECTION_STRING = "jdbc:sqlite::resource:data/dictionary.db"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
@ -43,7 +45,8 @@ 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 model = Model(searchService, findWordsService)
|
val findCharacterService = FindCharacterService(dictionary)
|
||||||
|
val model = Model(searchService, findWordsService, findCharacterService)
|
||||||
val config = Config()
|
val config = Config()
|
||||||
config.load()
|
config.load()
|
||||||
|
|
||||||
@ -69,6 +72,7 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,7 @@ 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
|
||||||
@ -26,21 +22,19 @@ 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()
|
||||||
}
|
}
|
||||||
@ -48,16 +42,6 @@ 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,
|
||||||
@ -75,10 +59,7 @@ 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 = """
|
private val findCharacters = """
|
||||||
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions
|
WITH cte(id, character) AS (VALUES ?)
|
||||||
FROM cedict
|
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions
|
||||||
WHERE traditional IN (?)
|
FROM cedict INNER JOIN cte
|
||||||
|
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) {
|
||||||
@ -107,7 +109,8 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
|||||||
val characterList = entry.traditional
|
val characterList = entry.traditional
|
||||||
.split("")
|
.split("")
|
||||||
.filter { it.isNotBlank() }
|
.filter { it.isNotBlank() }
|
||||||
.joinToString(",") { "'$it'" }
|
.mapIndexed { index, s -> "($index, '$s')" }
|
||||||
|
.joinToString(",")
|
||||||
|
|
||||||
val query = findCharacters.replace("?", characterList)
|
val query = findCharacters.replace("?", characterList)
|
||||||
|
|
||||||
|
@ -2,14 +2,19 @@ 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
|
||||||
@ -19,62 +24,113 @@ 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 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
|
@FXML
|
||||||
@Suppress("UnusedPrivateMember")
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
initializeLabelHeadword()
|
initializeLabelHeadword()
|
||||||
|
initializeLabelPronunciation()
|
||||||
initializeTabPaneDetails()
|
initializeTabPaneDetails()
|
||||||
initializeListViewWords()
|
initializeListViewWords()
|
||||||
|
initializeListViewCharacters()
|
||||||
initializeWebViewDefinition()
|
initializeWebViewDefinition()
|
||||||
|
}
|
||||||
|
|
||||||
model.selectedEntry.addListener { _, _, newEntry ->
|
private fun initializeLabelHeadword() {
|
||||||
if (newEntry == null) return@addListener
|
labelHeadword.apply {
|
||||||
|
textProperty().bind(
|
||||||
when (tabPaneDetails.selectionModel.selectedItem.id) {
|
Bindings.createStringBinding(
|
||||||
"tabWords" -> {
|
{
|
||||||
model.findWords()
|
val selectedEntry = model.selectedEntry.value
|
||||||
}
|
when (config.script.value!!) {
|
||||||
|
Script.SIMPLIFIED -> selectedEntry?.simplifiedProperty?.value
|
||||||
else -> {}
|
Script.TRADITIONAL -> selectedEntry?.traditionalProperty?.value
|
||||||
}
|
}
|
||||||
webViewDefinition.engine.loadContent(newEntry.createCedictDefinitionHtml())
|
},
|
||||||
|
config.script,
|
||||||
|
model.selectedEntry
|
||||||
|
)
|
||||||
|
)
|
||||||
|
styleProperty().bind(
|
||||||
|
Bindings.concat(
|
||||||
|
"-fx-font-size: ",
|
||||||
|
config.details.headwordFontSize.asString(),
|
||||||
|
"px;"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeWebViewDefinition() {
|
private fun initializeLabelPronunciation() {
|
||||||
webViewDefinition.apply {
|
labelPronunciation.apply {
|
||||||
engine.userStyleSheetLocation = this::class.java.getResource("/css/definitions.css")!!.toExternalForm()
|
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() {
|
Pronunciation.PINYIN_WITH_TONE_NUMBERS ->
|
||||||
listViewWords.apply {
|
selectedEntry
|
||||||
cellFactory = DictionaryEntryCellFactory(resources, config)
|
?.pinyinWithToneNumbersProperty
|
||||||
items = model.wordsContaining
|
?.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 ->
|
selectionModel.selectedItemProperty().addListener { _, _, selectedTab ->
|
||||||
if (model.selectedEntry.value == null) return@addListener
|
if (model.selectedEntry.value == null) return@addListener
|
||||||
|
|
||||||
when (selectedTab.id) {
|
lazyUpdateTabContent(selectedTab.id)
|
||||||
"tabWords" -> {
|
|
||||||
model.findWords()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.selectedEntry.addListener { _, _, newEntry ->
|
||||||
|
if (newEntry == null) return@addListener
|
||||||
|
|
||||||
|
lazyUpdateTabContent(tabPaneDetails.selectionModel.selectedItem.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeLabelHeadword() {
|
private fun initializeListViewWords() {
|
||||||
labelHeadword.apply {
|
listViewWords.apply {
|
||||||
textProperty().bind(
|
cellFactory = DictionaryEntryCellFactory(resources, config)
|
||||||
Bindings.createStringBinding(
|
items = model.wordsContaining
|
||||||
{
|
|
||||||
when (config.script.value!!) {
|
disableProperty().bind(Bindings.or(model.isFindingWords, Bindings.isEmpty(model.wordsContaining)))
|
||||||
Script.SIMPLIFIED -> model.selectedEntry.value?.simplifiedProperty?.value
|
}
|
||||||
Script.TRADITIONAL -> model.selectedEntry.value?.traditionalProperty?.value
|
progressIndicatorWords.visibleProperty().bind(model.isFindingWords)
|
||||||
}
|
labelNoWordsFound
|
||||||
},
|
.visibleProperty()
|
||||||
config.script,
|
.bind(Bindings.and(Bindings.isEmpty(model.wordsContaining), Bindings.not(model.isFindingWords)))
|
||||||
model.selectedEntry
|
}
|
||||||
)
|
|
||||||
)
|
private fun initializeListViewCharacters() {
|
||||||
styleProperty().bind(Bindings.concat("-fx-font-size: ", config.detailHeadwordFontSize.asString(), "px;"))
|
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
|
@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
|
||||||
|
|
||||||
@ -63,8 +66,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.detailHeadwordFontSize.asObject()
|
private val entryHeadwordFontSizeObjectProperty = config.details.headwordFontSize.asObject()
|
||||||
private val entryPronunciationFontSizeObjectProperty = config.detailPronunciationFontSize.asObject()
|
private val entryPronunciationFontSizeObjectProperty = config.details.pronunciationFontSize.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()
|
||||||
@ -100,6 +103,7 @@ 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()
|
||||||
|
@ -30,3 +30,13 @@ 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() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
.headword {
|
.headword {
|
||||||
-fx-font-family: TW-Kai;
|
-fx-font-family: TW-Kai;
|
||||||
-fx-font-size: 40;
|
}
|
||||||
|
|
||||||
|
.pronunciation {
|
||||||
|
-fx-font-family: "Noto Sans TC";
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
.root {
|
.root {
|
||||||
-fx-font-family: "Inter Variable";
|
-fx-font-family: "Inter Variable";
|
||||||
}
|
|
||||||
|
|
||||||
.details-pronunciation {
|
|
||||||
-fx-font: 16 "Noto Sans TC";
|
|
||||||
}
|
}
|
@ -1,31 +1,55 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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.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"
|
<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">
|
||||||
<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>
|
<padding>
|
||||||
<Insets left="8" right="8" top="8" bottom="8"/>
|
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0"/>
|
||||||
</padding>
|
</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">
|
<TabPane fx:id="tabPaneDetails" tabClosingPolicy="UNAVAILABLE" disable="true" VBox.vgrow="ALWAYS">
|
||||||
<Tab closable="false" disable="false" text="%tab.definition">
|
<Tab closable="false" text="%tab.definition">
|
||||||
<WebView fx:id="webViewDefinition" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0"
|
<WebView fx:id="webViewDefinition" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0"/>
|
||||||
prefWidth="-1.0"/>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="tabSentences" closable="false" disable="false" text="%tab.sentences">
|
<Tab id="tabSentences" closable="false" text="%tab.sentences">
|
||||||
<ListView fx:id="listviewSentences"/>
|
<ListView fx:id="listViewSentences"/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="tabWords" closable="false" disable="false" text="%tab.words">
|
<Tab id="tabWords" closable="false" text="%tab.words">
|
||||||
<ListView fx:id="listViewWords"/>
|
<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>
|
||||||
<Tab closable="false" disable="false" text="%tab.characters">
|
<Tab id="tabCharacters" closable="false" text="%tab.characters">
|
||||||
<ListView fx:id="listViewCharacters"/>
|
<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>
|
</Tab>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
@ -18,3 +18,5 @@ 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
|
||||||
|
@ -18,3 +18,5 @@ 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
|
||||||
|
@ -18,3 +18,5 @@ 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
|
||||||
|
@ -18,3 +18,5 @@ 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
|
||||||
|
@ -18,3 +18,5 @@ 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
|
||||||
|
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