Compare commits
9 Commits
49fe0f65bd
...
bc9d09c39f
Author | SHA1 | Date | |
---|---|---|---|
bc9d09c39f | |||
6003bc678e | |||
fee7cbf260 | |||
02257b7e6b | |||
9ac6f51743 | |||
cc1da1f7b5 | |||
8350cb3b3d | |||
efde0dcdb4 | |||
9508412427 |
32
src/main/kotlin/com/marvinelsen/willow/Config.kt
Normal file
32
src/main/kotlin/com/marvinelsen/willow/Config.kt
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package com.marvinelsen.willow
|
||||||
|
|
||||||
|
import javafx.beans.property.IntegerProperty
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty
|
||||||
|
import java.util.prefs.Preferences
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
companion object {
|
||||||
|
private const val DETAIL_HEADWORD_FONT_SIZE_KEY = "detailHeadwordFontSize"
|
||||||
|
|
||||||
|
private const val DEFAULT_DETAIL_HEADWORD_FONT_SIZE = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
private val preferences = Preferences.userNodeForPackage(this::class.java)
|
||||||
|
|
||||||
|
val detailHeadwordFontSize: IntegerProperty = SimpleIntegerProperty(DEFAULT_DETAIL_HEADWORD_FONT_SIZE)
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
preferences.putInt(DETAIL_HEADWORD_FONT_SIZE_KEY, detailHeadwordFontSize.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load() {
|
||||||
|
detailHeadwordFontSize.value = preferences.getInt(
|
||||||
|
DETAIL_HEADWORD_FONT_SIZE_KEY,
|
||||||
|
DEFAULT_DETAIL_HEADWORD_FONT_SIZE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
detailHeadwordFontSize.value = DEFAULT_DETAIL_HEADWORD_FONT_SIZE
|
||||||
|
}
|
||||||
|
}
|
@ -53,10 +53,10 @@ class Model(private val searchService: SearchService, private val findWordsServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun copyHeadwordOfSelectedEntry() {
|
fun copyHeadwordOfSelectedEntry() {
|
||||||
ClipboardHelper.copyHeadword(internalSelectedEntry.get())
|
ClipboardHelper.copyString(internalSelectedEntry.value.traditionalProperty.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyPronunciationOfSelectedEntry() {
|
fun copyPronunciationOfSelectedEntry() {
|
||||||
ClipboardHelper.copyPronunciation(internalSelectedEntry.get())
|
ClipboardHelper.copyString(internalSelectedEntry.value.pinyinWithToneMarksProperty.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package com.marvinelsen.willow
|
|||||||
|
|
||||||
import com.marvinelsen.willow.domain.SqliteDictionary
|
import com.marvinelsen.willow.domain.SqliteDictionary
|
||||||
import com.marvinelsen.willow.ui.controllers.DetailsController
|
import com.marvinelsen.willow.ui.controllers.DetailsController
|
||||||
|
import com.marvinelsen.willow.ui.controllers.ListController
|
||||||
import com.marvinelsen.willow.ui.controllers.MainController
|
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
|
||||||
@ -29,7 +30,6 @@ 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:dictionary.db"
|
||||||
val resourceBundle: ResourceBundle = ResourceBundle.getBundle("i18n/willow", Locale.US)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
@ -44,15 +44,18 @@ class WillowApplication : Application() {
|
|||||||
val searchService = SearchService(dictionary)
|
val searchService = SearchService(dictionary)
|
||||||
val findWordsService = FindWordsService(dictionary)
|
val findWordsService = FindWordsService(dictionary)
|
||||||
val model = Model(searchService, findWordsService)
|
val model = Model(searchService, findWordsService)
|
||||||
|
val config = Config()
|
||||||
|
config.load()
|
||||||
|
|
||||||
val fxmlLoader = FXMLLoader()
|
val fxmlLoader = FXMLLoader()
|
||||||
fxmlLoader.resources = resourceBundle
|
fxmlLoader.resources = ResourceBundle.getBundle("i18n/willow", Locale.getDefault())
|
||||||
fxmlLoader.controllerFactory = Callback { type ->
|
fxmlLoader.controllerFactory = Callback { type ->
|
||||||
when (type) {
|
when (type) {
|
||||||
MainController::class.java -> MainController(model)
|
MainController::class.java -> MainController(model)
|
||||||
MenuController::class.java -> MenuController(model)
|
MenuController::class.java -> MenuController(model, config)
|
||||||
DetailsController::class.java -> DetailsController(model)
|
DetailsController::class.java -> DetailsController(model, config)
|
||||||
SearchController::class.java -> SearchController(model)
|
SearchController::class.java -> SearchController(model)
|
||||||
|
ListController::class.java -> ListController(model)
|
||||||
else -> error("Trying to instantiate unknown controller type $type")
|
else -> error("Trying to instantiate unknown controller type $type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
package com.marvinelsen.willow.ui
|
package com.marvinelsen.willow.ui
|
||||||
|
|
||||||
class Configuration
|
data class Configuration(
|
||||||
|
val preferredScript: Script = Script.SIMPLIFIED,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Script {
|
||||||
|
SIMPLIFIED, TRADITIONAL
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Pronunciation {
|
||||||
|
PINYIN_WITH_TONE_MARKS,
|
||||||
|
PINYIN_WITH_TONE_NUMBERS,
|
||||||
|
ZHUYIN
|
||||||
|
}
|
||||||
|
@ -9,10 +9,12 @@ import javafx.scene.control.ListView
|
|||||||
import javafx.scene.layout.FlowPane
|
import javafx.scene.layout.FlowPane
|
||||||
import javafx.scene.layout.VBox
|
import javafx.scene.layout.VBox
|
||||||
import javafx.util.Callback
|
import javafx.util.Callback
|
||||||
|
import java.util.ResourceBundle
|
||||||
|
|
||||||
class DictionaryEntryCellFactory : Callback<ListView<DictionaryEntryFx?>, ListCell<DictionaryEntryFx?>> {
|
class DictionaryEntryCellFactory(private val resources: ResourceBundle) :
|
||||||
|
Callback<ListView<DictionaryEntryFx?>, ListCell<DictionaryEntryFx?>> {
|
||||||
override fun call(listView: ListView<DictionaryEntryFx?>): ListCell<DictionaryEntryFx?> {
|
override fun call(listView: ListView<DictionaryEntryFx?>): ListCell<DictionaryEntryFx?> {
|
||||||
val entryCell = EntryCell()
|
val entryCell = EntryCell(resources)
|
||||||
entryCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING))
|
entryCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING))
|
||||||
return entryCell
|
return entryCell
|
||||||
}
|
}
|
||||||
@ -22,7 +24,7 @@ class DictionaryEntryCellFactory : Callback<ListView<DictionaryEntryFx?>, ListCe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class EntryCell : ListCell<DictionaryEntryFx?>() {
|
internal class EntryCell(private val resources: ResourceBundle) : ListCell<DictionaryEntryFx?>() {
|
||||||
private val labelHeadword = Label().apply {
|
private val labelHeadword = Label().apply {
|
||||||
styleClass.add("list-view-entry")
|
styleClass.add("list-view-entry")
|
||||||
}
|
}
|
||||||
@ -58,7 +60,7 @@ internal class EntryCell : ListCell<DictionaryEntryFx?>() {
|
|||||||
val definition = entry.definitions.joinToString(separator = " / ") { it.joinToString(separator = "; ") }
|
val definition = entry.definitions.joinToString(separator = " / ") { it.joinToString(separator = "; ") }
|
||||||
labelDefinition.text = definition
|
labelDefinition.text = definition
|
||||||
|
|
||||||
contextMenu = createContextMenuForEntry(entry)
|
contextMenu = createContextMenuForEntry(entry, resources)
|
||||||
graphic = root
|
graphic = root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package com.marvinelsen.willow.ui.controllers
|
package com.marvinelsen.willow.ui.controllers
|
||||||
|
|
||||||
|
import com.marvinelsen.willow.Config
|
||||||
import com.marvinelsen.willow.Model
|
import com.marvinelsen.willow.Model
|
||||||
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
||||||
|
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
|
||||||
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
|
||||||
@ -14,8 +16,12 @@ import kotlinx.html.html
|
|||||||
import kotlinx.html.li
|
import kotlinx.html.li
|
||||||
import kotlinx.html.ol
|
import kotlinx.html.ol
|
||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
|
import java.util.ResourceBundle
|
||||||
|
|
||||||
|
class DetailsController(private val model: Model, private val config: Config) {
|
||||||
|
@FXML
|
||||||
|
private lateinit var resources: ResourceBundle
|
||||||
|
|
||||||
class DetailsController(private val model: Model) {
|
|
||||||
@FXML
|
@FXML
|
||||||
private lateinit var tabPaneDetails: TabPane
|
private lateinit var tabPaneDetails: TabPane
|
||||||
|
|
||||||
@ -43,9 +49,19 @@ class DetailsController(private val model: Model) {
|
|||||||
Bindings.createStringBinding({ model.selectedEntry.value?.traditionalProperty?.value }, model.selectedEntry)
|
Bindings.createStringBinding({ model.selectedEntry.value?.traditionalProperty?.value }, model.selectedEntry)
|
||||||
|
|
||||||
labelHeadword.textProperty().bind(headwordObjectBinding)
|
labelHeadword.textProperty().bind(headwordObjectBinding)
|
||||||
|
labelHeadword
|
||||||
|
.styleProperty()
|
||||||
|
.bind(
|
||||||
|
Bindings.concat(
|
||||||
|
"-fx-font-size: ",
|
||||||
|
config.detailHeadwordFontSize.asString(),
|
||||||
|
"px;"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tabPaneDetails.disableProperty().bind(Bindings.isNull(model.selectedEntry))
|
tabPaneDetails.disableProperty().bind(Bindings.isNull(model.selectedEntry))
|
||||||
|
|
||||||
|
listViewWords.cellFactory = DictionaryEntryCellFactory(resources)
|
||||||
listViewWords.items = model.wordsContaining
|
listViewWords.items = model.wordsContaining
|
||||||
tabPaneDetails.selectionModel.selectedItemProperty().addListener { _, _, selectedTab ->
|
tabPaneDetails.selectionModel.selectedItemProperty().addListener { _, _, selectedTab ->
|
||||||
if (model.selectedEntry.value == null) return@addListener
|
if (model.selectedEntry.value == null) return@addListener
|
||||||
@ -69,6 +85,13 @@ class DetailsController(private val model: Model) {
|
|||||||
if (newValue == null) {
|
if (newValue == null) {
|
||||||
return@addListener
|
return@addListener
|
||||||
}
|
}
|
||||||
|
when (tabPaneDetails.selectionModel.selectedItem.id) {
|
||||||
|
"tabWords" -> {
|
||||||
|
model.findWords()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
webViewDefinition.engine.loadContent(
|
webViewDefinition.engine.loadContent(
|
||||||
createHTML().html {
|
createHTML().html {
|
||||||
body {
|
body {
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.marvinelsen.willow.ui.controllers
|
||||||
|
|
||||||
|
import com.marvinelsen.willow.Model
|
||||||
|
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
||||||
|
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
|
||||||
|
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 java.util.ResourceBundle
|
||||||
|
|
||||||
|
class ListController(private val model: Model) {
|
||||||
|
@FXML
|
||||||
|
private lateinit var resources: ResourceBundle
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private lateinit var progressIndicatorEntries: ProgressIndicator
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
@Suppress("UnusedPrivateProperty")
|
||||||
|
private lateinit var labelNoEntriesFound: Label
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private lateinit var listViewSearchResults: ListView<DictionaryEntryFx>
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
@Suppress("UnusedPrivateMember")
|
||||||
|
private fun initialize() {
|
||||||
|
listViewSearchResults.cellFactory = DictionaryEntryCellFactory(resources)
|
||||||
|
listViewSearchResults.items = model.searchResults
|
||||||
|
listViewSearchResults
|
||||||
|
.disableProperty()
|
||||||
|
.bind(Bindings.or(model.isSearching, Bindings.isEmpty(model.searchResults)))
|
||||||
|
|
||||||
|
progressIndicatorEntries.visibleProperty().bind(model.isSearching)
|
||||||
|
labelNoEntriesFound
|
||||||
|
.visibleProperty()
|
||||||
|
.bind(Bindings.and(Bindings.isEmpty(model.searchResults), Bindings.not(model.isSearching)))
|
||||||
|
|
||||||
|
listViewSearchResults.selectionModel.selectedItemProperty().addListener { _, _, newValue: DictionaryEntryFx? ->
|
||||||
|
if (newValue == null) {
|
||||||
|
return@addListener
|
||||||
|
}
|
||||||
|
model.selectEntry(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +1,12 @@
|
|||||||
package com.marvinelsen.willow.ui.controllers
|
package com.marvinelsen.willow.ui.controllers
|
||||||
|
|
||||||
import com.marvinelsen.willow.Model
|
import com.marvinelsen.willow.Model
|
||||||
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
|
||||||
import javafx.beans.binding.Bindings
|
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.control.Label
|
|
||||||
import javafx.scene.control.ListView
|
|
||||||
import javafx.scene.control.ProgressIndicator
|
|
||||||
|
|
||||||
|
@Suppress("UnusedPrivateProperty", "UnusedPrivateMember")
|
||||||
class MainController(private val model: Model) {
|
class MainController(private val model: Model) {
|
||||||
@FXML
|
@FXML
|
||||||
private lateinit var progressIndicatorEntries: ProgressIndicator
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
@Suppress("UnusedPrivateProperty")
|
|
||||||
private lateinit var labelNoEntriesFound: Label
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private lateinit var listViewSearchResults: ListView<DictionaryEntryFx>
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
@Suppress("UnusedPrivateMember")
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
listViewSearchResults.items = model.searchResults
|
// no-op
|
||||||
listViewSearchResults
|
|
||||||
.disableProperty()
|
|
||||||
.bind(Bindings.or(model.isSearching, Bindings.isEmpty(model.searchResults)))
|
|
||||||
|
|
||||||
progressIndicatorEntries.visibleProperty().bind(model.isSearching)
|
|
||||||
|
|
||||||
listViewSearchResults.selectionModel.selectedItemProperty().addListener { _, _, newValue: DictionaryEntryFx? ->
|
|
||||||
if (newValue == null) {
|
|
||||||
return@addListener
|
|
||||||
}
|
|
||||||
model.selectEntry(newValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package com.marvinelsen.willow.ui.controllers
|
package com.marvinelsen.willow.ui.controllers
|
||||||
|
|
||||||
|
import com.marvinelsen.willow.Config
|
||||||
import com.marvinelsen.willow.Model
|
import com.marvinelsen.willow.Model
|
||||||
|
import com.marvinelsen.willow.ui.dialogs.PreferencesDialog
|
||||||
import javafx.application.Platform
|
import javafx.application.Platform
|
||||||
import javafx.beans.binding.Bindings
|
import javafx.beans.binding.Bindings
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
|
import javafx.scene.control.MenuBar
|
||||||
import javafx.scene.control.MenuItem
|
import javafx.scene.control.MenuItem
|
||||||
|
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
class MenuController(private val model: Model) {
|
class MenuController(private val model: Model, private val config: Config) {
|
||||||
|
@FXML
|
||||||
|
private lateinit var menuBar: MenuBar
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private lateinit var menuItemCopyHeadword: MenuItem
|
private lateinit var menuItemCopyHeadword: MenuItem
|
||||||
|
|
||||||
@ -20,6 +26,16 @@ class MenuController(private val model: Model) {
|
|||||||
menuItemCopyHeadword.disableProperty().bind(Bindings.isNull(model.selectedEntry))
|
menuItemCopyHeadword.disableProperty().bind(Bindings.isNull(model.selectedEntry))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private fun onMenuItemPreferencesAction() {
|
||||||
|
PreferencesDialog(menuBar.scene.window, config).showAndWait().ifPresent { result ->
|
||||||
|
when (result) {
|
||||||
|
PreferencesDialog.Result.CHANGES -> config.save()
|
||||||
|
PreferencesDialog.Result.NO_CHANGES -> config.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private fun onMenuItemQuitAction() {
|
private fun onMenuItemQuitAction() {
|
||||||
Platform.exit()
|
Platform.exit()
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.marvinelsen.willow.ui.converters
|
||||||
|
|
||||||
|
import com.marvinelsen.willow.ui.Pronunciation
|
||||||
|
import javafx.util.StringConverter
|
||||||
|
|
||||||
|
class PronunciationStringConverter : StringConverter<Pronunciation>() {
|
||||||
|
override fun toString(pronunciation: Pronunciation) = when (pronunciation) {
|
||||||
|
Pronunciation.PINYIN_WITH_TONE_MARKS -> "Pinyin with tone marks"
|
||||||
|
Pronunciation.PINYIN_WITH_TONE_NUMBERS -> "Pinyin with tone numbers"
|
||||||
|
Pronunciation.ZHUYIN -> "Zhuyin"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromString(string: String) = null
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package com.marvinelsen.willow.ui.dialogs
|
||||||
|
|
||||||
|
import com.marvinelsen.willow.Config
|
||||||
|
import com.marvinelsen.willow.WillowApplication
|
||||||
|
import com.marvinelsen.willow.ui.Pronunciation
|
||||||
|
import com.marvinelsen.willow.ui.formatters.FontSizeTextFormatter
|
||||||
|
import javafx.fxml.FXMLLoader
|
||||||
|
import javafx.scene.control.ButtonType
|
||||||
|
import javafx.scene.control.CheckBox
|
||||||
|
import javafx.scene.control.ComboBox
|
||||||
|
import javafx.scene.control.Dialog
|
||||||
|
import javafx.scene.control.DialogPane
|
||||||
|
import javafx.scene.control.Spinner
|
||||||
|
import javafx.stage.Modality
|
||||||
|
import javafx.stage.Stage
|
||||||
|
import javafx.stage.Window
|
||||||
|
import javafx.util.Callback
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
class PreferencesDialog(owner: Window?, config: Config) : Dialog<PreferencesDialog.Result>() {
|
||||||
|
companion object {
|
||||||
|
private const val DIALOG_MIN_HEIGHT = 400.0
|
||||||
|
private const val DIALOG_MIN_WIDTH = 400.0
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Result {
|
||||||
|
CHANGES, NO_CHANGES
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var comboBoxEntryPronunciation: ComboBox<Pronunciation>
|
||||||
|
lateinit var comboBoxListPronunciation: ComboBox<Pronunciation>
|
||||||
|
|
||||||
|
lateinit var checkBoxListShowPronunciation: CheckBox
|
||||||
|
lateinit var checkBoxListShowDefinition: CheckBox
|
||||||
|
|
||||||
|
lateinit var spinnerEntryHeadwordFontSize: Spinner<Int>
|
||||||
|
lateinit var spinnerEntryPronunciationFontSize: Spinner<Int>
|
||||||
|
lateinit var spinnerListHeadwordFontSize: Spinner<Int>
|
||||||
|
lateinit var spinnerListPronunciationFontSize: Spinner<Int>
|
||||||
|
lateinit var spinnerListDefinitionFontSize: Spinner<Int>
|
||||||
|
|
||||||
|
private val entryHeadwordFontSizeObjectProperty = config.detailHeadwordFontSize.asObject()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val loader = FXMLLoader(WillowApplication::class.java.getResource("/fxml/preferences-dialog.fxml"))
|
||||||
|
loader.setController(this)
|
||||||
|
val root: DialogPane = loader.load()
|
||||||
|
|
||||||
|
dialogPane = root
|
||||||
|
title = "Settings"
|
||||||
|
isResizable = true
|
||||||
|
|
||||||
|
initOwner(owner)
|
||||||
|
initModality(Modality.APPLICATION_MODAL)
|
||||||
|
|
||||||
|
(dialogPane.scene.window as Stage).apply {
|
||||||
|
minWidth = DIALOG_MIN_WIDTH
|
||||||
|
minHeight = DIALOG_MIN_HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
with(spinnerEntryHeadwordFontSize) {
|
||||||
|
editor.textFormatter = FontSizeTextFormatter()
|
||||||
|
valueFactory.valueProperty().bindBidirectional(entryHeadwordFontSizeObjectProperty)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultConverter = Callback(::convertToResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertToResult(buttonType: ButtonType) =
|
||||||
|
when (buttonType) {
|
||||||
|
ButtonType.APPLY -> Result.CHANGES
|
||||||
|
else -> Result.NO_CHANGES
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.marvinelsen.willow.ui.formatters
|
||||||
|
|
||||||
|
import javafx.scene.control.TextFormatter
|
||||||
|
import javafx.util.converter.IntegerStringConverter
|
||||||
|
import java.util.function.UnaryOperator
|
||||||
|
|
||||||
|
private val digitRegex = """\d*""".toRegex()
|
||||||
|
|
||||||
|
class FontSizeTextFormatter : TextFormatter<Int>(
|
||||||
|
IntegerStringConverter(),
|
||||||
|
1,
|
||||||
|
UnaryOperator { if (digitRegex.matches(it.text)) it else null }
|
||||||
|
)
|
@ -1,21 +1,14 @@
|
|||||||
package com.marvinelsen.willow.ui.util
|
package com.marvinelsen.willow.ui.util
|
||||||
|
|
||||||
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
|
||||||
import javafx.scene.input.Clipboard
|
import javafx.scene.input.Clipboard
|
||||||
import javafx.scene.input.ClipboardContent
|
import javafx.scene.input.ClipboardContent
|
||||||
|
|
||||||
object ClipboardHelper {
|
object ClipboardHelper {
|
||||||
private val systemClipboard = Clipboard.getSystemClipboard()
|
private val systemClipboard = Clipboard.getSystemClipboard()
|
||||||
|
|
||||||
fun copyHeadword(entry: DictionaryEntryFx) {
|
fun copyString(string: String) {
|
||||||
val clipboardContent = ClipboardContent()
|
val clipboardContent = ClipboardContent()
|
||||||
clipboardContent.putString(entry.traditionalProperty.value)
|
clipboardContent.putString(string)
|
||||||
systemClipboard.setContent(clipboardContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copyPronunciation(entry: DictionaryEntryFx) {
|
|
||||||
val clipboardContent = ClipboardContent()
|
|
||||||
clipboardContent.putString(entry.pinyinWithToneMarksProperty.value)
|
|
||||||
systemClipboard.setContent(clipboardContent)
|
systemClipboard.setContent(clipboardContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package com.marvinelsen.willow.ui.util
|
package com.marvinelsen.willow.ui.util
|
||||||
|
|
||||||
import com.marvinelsen.willow.WillowApplication
|
|
||||||
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
||||||
import javafx.event.EventHandler
|
import javafx.event.EventHandler
|
||||||
import javafx.scene.control.ContextMenu
|
import javafx.scene.control.ContextMenu
|
||||||
import javafx.scene.control.MenuItem
|
import javafx.scene.control.MenuItem
|
||||||
|
import java.util.ResourceBundle
|
||||||
|
|
||||||
fun createContextMenuForEntry(entry: DictionaryEntryFx) = ContextMenu().apply {
|
fun createContextMenuForEntry(entry: DictionaryEntryFx, resourceBundle: ResourceBundle) = ContextMenu().apply {
|
||||||
val menuItemCopyHeadword =
|
val menuItemCopyHeadword =
|
||||||
MenuItem(WillowApplication.resourceBundle.getString("menubar.edit.copy.headword")).apply {
|
MenuItem(resourceBundle.getString("menubar.edit.copy.headword")).apply {
|
||||||
onAction = EventHandler { ClipboardHelper.copyHeadword(entry) }
|
onAction = EventHandler { ClipboardHelper.copyString(entry.traditionalProperty.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val menuItemCopyPronunciation =
|
val menuItemCopyPronunciation =
|
||||||
MenuItem(WillowApplication.resourceBundle.getString("menubar.edit.copy.pronunciation")).apply {
|
MenuItem(resourceBundle.getString("menubar.edit.copy.pronunciation")).apply {
|
||||||
onAction = EventHandler { ClipboardHelper.copyPronunciation(entry) }
|
onAction = EventHandler { ClipboardHelper.copyString(entry.pinyinWithToneMarksProperty.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
items.addAll(menuItemCopyHeadword, menuItemCopyPronunciation)
|
items.addAll(menuItemCopyHeadword, menuItemCopyPronunciation)
|
||||||
|
@ -37,6 +37,6 @@
|
|||||||
-fx-font: 16 "Noto Sans TC";
|
-fx-font: 16 "Noto Sans TC";
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-dialog .content {
|
.preferences-dialog .content {
|
||||||
-fx-padding: 0;
|
-fx-padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,7 @@
|
|||||||
<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" disable="false" text="%tab.words">
|
||||||
<ListView fx:id="listViewWords">
|
<ListView fx:id="listViewWords"/>
|
||||||
<cellFactory>
|
|
||||||
<DictionaryEntryCellFactory/>
|
|
||||||
</cellFactory>
|
|
||||||
</ListView>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab closable="false" disable="false" text="%tab.characters">
|
<Tab closable="false" disable="false" text="%tab.characters">
|
||||||
<ListView fx:id="listViewCharacters"/>
|
<ListView fx:id="listViewCharacters"/>
|
||||||
|
19
src/main/resources/fxml/list.fxml
Normal file
19
src/main/resources/fxml/list.fxml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
<?import javafx.scene.control.ProgressIndicator?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<StackPane xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="com.marvinelsen.willow.ui.controllers.ListController"
|
||||||
|
stylesheets="/css/main.css">
|
||||||
|
<ListView fx:id="listViewSearchResults" disable="true"/>
|
||||||
|
<Label fx:id="labelNoEntriesFound" text="%list.no_entries_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="progressIndicatorEntries" visible="false"/>
|
||||||
|
</StackPane>
|
@ -1,10 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory?>
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.control.ListView?>
|
|
||||||
<?import javafx.scene.control.ProgressIndicator?>
|
|
||||||
<?import javafx.scene.control.SplitPane?>
|
<?import javafx.scene.control.SplitPane?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<BorderPane xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
|
<BorderPane xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
@ -20,20 +16,7 @@
|
|||||||
</BorderPane.margin>
|
</BorderPane.margin>
|
||||||
<fx:include source="/fxml/search.fxml"/>
|
<fx:include source="/fxml/search.fxml"/>
|
||||||
<SplitPane dividerPositions="0.33" VBox.vgrow="ALWAYS">
|
<SplitPane dividerPositions="0.33" VBox.vgrow="ALWAYS">
|
||||||
<StackPane>
|
<fx:include source="/fxml/list.fxml"/>
|
||||||
<ListView fx:id="listViewSearchResults" disable="true">
|
|
||||||
<cellFactory>
|
|
||||||
<DictionaryEntryCellFactory/>
|
|
||||||
</cellFactory>
|
|
||||||
</ListView>
|
|
||||||
<Label fx:id="labelNoEntriesFound" text="%list.no_entries_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="progressIndicatorEntries" visible="false"/>
|
|
||||||
</StackPane>
|
|
||||||
<fx:include source="/fxml/details.fxml"/>
|
<fx:include source="/fxml/details.fxml"/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
@ -4,10 +4,11 @@
|
|||||||
<?import javafx.scene.input.KeyCodeCombination?>
|
<?import javafx.scene.input.KeyCodeCombination?>
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<MenuBar xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
|
<MenuBar xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:id="menuBar"
|
||||||
fx:controller="com.marvinelsen.willow.ui.controllers.MenuController" BorderPane.alignment="CENTER"
|
fx:controller="com.marvinelsen.willow.ui.controllers.MenuController" BorderPane.alignment="CENTER"
|
||||||
useSystemMenuBar="true">
|
useSystemMenuBar="true">
|
||||||
<Menu text="%menubar.file">
|
<Menu text="%menubar.file">
|
||||||
<MenuItem text="%menubar.file.settings">
|
<MenuItem text="%menubar.file.preferences" onAction="#onMenuItemPreferencesAction">
|
||||||
<accelerator>
|
<accelerator>
|
||||||
<KeyCodeCombination alt="UP" code="COMMA" control="UP" meta="UP" shift="UP"
|
<KeyCodeCombination alt="UP" code="COMMA" control="UP" meta="UP" shift="UP"
|
||||||
shortcut="DOWN"/>
|
shortcut="DOWN"/>
|
||||||
|
124
src/main/resources/fxml/preferences-dialog.fxml
Normal file
124
src/main/resources/fxml/preferences-dialog.fxml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import com.marvinelsen.willow.ui.converters.PronunciationStringConverter?>
|
||||||
|
<?import com.marvinelsen.willow.ui.*?>
|
||||||
|
<?import javafx.collections.FXCollections?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<DialogPane styleClass="preferences-dialog" xmlns="http://javafx.com/javafx/23" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
stylesheets="/css/main.css">
|
||||||
|
<fx:define>
|
||||||
|
<FXCollections fx:factory="observableArrayList" fx:id="phoneticAlphabets">
|
||||||
|
<Pronunciation fx:value="PINYIN_WITH_TONE_MARKS"/>
|
||||||
|
<Pronunciation fx:value="PINYIN_WITH_TONE_NUMBERS"/>
|
||||||
|
<Pronunciation fx:value="ZHUYIN"/>
|
||||||
|
</FXCollections>
|
||||||
|
</fx:define>
|
||||||
|
<content>
|
||||||
|
<TabPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||||
|
tabClosingPolicy="UNAVAILABLE">
|
||||||
|
<Tab closable="false" text="Entry">
|
||||||
|
<GridPane alignment="TOP_CENTER" hgap="8.0" vgap="8.0">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints halignment="RIGHT" hgrow="ALWAYS"/>
|
||||||
|
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS"/>
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
</rowConstraints>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="12.0" left="12.0" right="12.0" top="12.0"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="Pronunciation:"/>
|
||||||
|
<ComboBox fx:id="comboBoxEntryPronunciation" GridPane.columnIndex="1">
|
||||||
|
<value>
|
||||||
|
<Pronunciation fx:value="PINYIN_WITH_TONE_MARKS"/>
|
||||||
|
</value>
|
||||||
|
<converter>
|
||||||
|
<PronunciationStringConverter/>
|
||||||
|
</converter>
|
||||||
|
</ComboBox>
|
||||||
|
<Label text="Headword font size:" GridPane.rowIndex="2"/>
|
||||||
|
<Label text="Pronunciation font size:" GridPane.rowIndex="3"/>
|
||||||
|
<Spinner fx:id="spinnerEntryPronunciationFontSize" editable="true" GridPane.columnIndex="1"
|
||||||
|
GridPane.rowIndex="3">
|
||||||
|
<valueFactory>
|
||||||
|
<SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" max="300" min="1"/>
|
||||||
|
</valueFactory>
|
||||||
|
</Spinner>
|
||||||
|
<Spinner fx:id="spinnerEntryHeadwordFontSize" editable="true" GridPane.columnIndex="1"
|
||||||
|
GridPane.rowIndex="2">
|
||||||
|
<valueFactory>
|
||||||
|
<SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" max="300" min="1"/>
|
||||||
|
</valueFactory>
|
||||||
|
</Spinner>
|
||||||
|
<Separator prefWidth="200.0" GridPane.columnSpan="2147483647" GridPane.rowIndex="1"/>
|
||||||
|
</GridPane>
|
||||||
|
</Tab>
|
||||||
|
<Tab closable="false" text="List">
|
||||||
|
<GridPane alignment="TOP_CENTER" hgap="8.0" vgap="8.0">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints halignment="RIGHT" hgrow="ALWAYS"/>
|
||||||
|
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS"/>
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
<RowConstraints valignment="BASELINE" vgrow="NEVER"/>
|
||||||
|
</rowConstraints>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="12.0" left="12.0" right="12.0" top="12.0"/>
|
||||||
|
</padding>
|
||||||
|
<Label text="Pronunciation:"/>
|
||||||
|
<ComboBox fx:id="comboBoxListPronunciation" GridPane.columnIndex="1">
|
||||||
|
<value>
|
||||||
|
<Pronunciation fx:value="PINYIN_WITH_TONE_MARKS"/>
|
||||||
|
</value>
|
||||||
|
<converter>
|
||||||
|
<PronunciationStringConverter/>
|
||||||
|
</converter>
|
||||||
|
</ComboBox>
|
||||||
|
<Label text="Headword font size:" GridPane.rowIndex="5"/>
|
||||||
|
<Label text="Pronunciation font size:" GridPane.rowIndex="6"/>
|
||||||
|
<Spinner fx:id="spinnerListPronunciationFontSize" editable="true" GridPane.columnIndex="1"
|
||||||
|
GridPane.rowIndex="6">
|
||||||
|
<valueFactory>
|
||||||
|
<SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" max="300" min="1"/>
|
||||||
|
</valueFactory>
|
||||||
|
</Spinner>
|
||||||
|
<Spinner fx:id="spinnerListHeadwordFontSize" editable="true" GridPane.columnIndex="1"
|
||||||
|
GridPane.rowIndex="5">
|
||||||
|
<valueFactory>
|
||||||
|
<SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" max="300" min="1"/>
|
||||||
|
</valueFactory>
|
||||||
|
</Spinner>
|
||||||
|
<Spinner fx:id="spinnerListDefinitionFontSize" editable="true" GridPane.columnIndex="1"
|
||||||
|
GridPane.rowIndex="7">
|
||||||
|
<valueFactory>
|
||||||
|
<SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" max="300" min="1"/>
|
||||||
|
</valueFactory>
|
||||||
|
</Spinner>
|
||||||
|
<Separator prefWidth="200.0" GridPane.columnSpan="2147483647" GridPane.rowIndex="4"/>
|
||||||
|
<Label text="Definition font size:" GridPane.rowIndex="7"/>
|
||||||
|
<Label text="Display:" GridPane.rowIndex="2"/>
|
||||||
|
<CheckBox fx:id="checkBoxListShowPronunciation" mnemonicParsing="false" selected="true"
|
||||||
|
text="Show pronunciation" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||||
|
<CheckBox fx:id="checkBoxListShowDefinition" mnemonicParsing="false" selected="true"
|
||||||
|
text="Show definition" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||||
|
<Separator prefWidth="200.0" GridPane.columnSpan="2147483647" GridPane.rowIndex="1"/>
|
||||||
|
</GridPane>
|
||||||
|
</Tab>
|
||||||
|
</TabPane>
|
||||||
|
</content>
|
||||||
|
<ButtonType fx:id="buttonTypeApply" fx:constant="APPLY"/>
|
||||||
|
<ButtonType fx:constant="CANCEL"/>
|
||||||
|
</DialogPane>
|
@ -10,7 +10,7 @@ tab.words=Words
|
|||||||
tab.characters=Characters
|
tab.characters=Characters
|
||||||
menubar.file=_File
|
menubar.file=_File
|
||||||
menubar.file.quit=_Quit
|
menubar.file.quit=_Quit
|
||||||
menubar.file.settings=_Settings…
|
menubar.file.preferences=_Preferences…
|
||||||
menubar.edit=_Edit
|
menubar.edit=_Edit
|
||||||
menubar.edit.copy.headword=Copy Headword
|
menubar.edit.copy.headword=Copy Headword
|
||||||
menubar.edit.copy.pronunciation=Copy Pronunciation
|
menubar.edit.copy.pronunciation=Copy Pronunciation
|
||||||
|
@ -10,7 +10,7 @@ tab.words=Wörter
|
|||||||
tab.characters=Schriftzeichen
|
tab.characters=Schriftzeichen
|
||||||
menubar.file=_Datei
|
menubar.file=_Datei
|
||||||
menubar.file.quit=_Beenden
|
menubar.file.quit=_Beenden
|
||||||
menubar.file.settings=_Einstellungen…
|
menubar.file.preferences=_Einstellungen…
|
||||||
menubar.edit=_Bearbeiten
|
menubar.edit=_Bearbeiten
|
||||||
menubar.edit.copy.headword=Kopiere Wort
|
menubar.edit.copy.headword=Kopiere Wort
|
||||||
menubar.edit.copy.pronunciation=Kopiere Aussprache
|
menubar.edit.copy.pronunciation=Kopiere Aussprache
|
||||||
|
@ -10,7 +10,7 @@ tab.words=Words
|
|||||||
tab.characters=Characters
|
tab.characters=Characters
|
||||||
menubar.file=_File
|
menubar.file=_File
|
||||||
menubar.file.quit=_Quit
|
menubar.file.quit=_Quit
|
||||||
menubar.file.settings=_Settings…
|
menubar.file.preferences=_Preferences…
|
||||||
menubar.edit=_Edit
|
menubar.edit=_Edit
|
||||||
menubar.edit.copy.headword=Copy Headword
|
menubar.edit.copy.headword=Copy Headword
|
||||||
menubar.edit.copy.pronunciation=Copy Pronunciation
|
menubar.edit.copy.pronunciation=Copy Pronunciation
|
||||||
|
@ -10,7 +10,7 @@ tab.words=詞
|
|||||||
tab.characters=字
|
tab.characters=字
|
||||||
menubar.file=_檔案
|
menubar.file=_檔案
|
||||||
menubar.file.quit=_結束 Willow
|
menubar.file.quit=_結束 Willow
|
||||||
menubar.file.settings=_設定…
|
menubar.file.preferences=_設定…
|
||||||
menubar.edit=_編輯
|
menubar.edit=_編輯
|
||||||
menubar.edit.copy.headword=複製 Wort
|
menubar.edit.copy.headword=複製 Wort
|
||||||
menubar.edit.copy.pronunciation=複製 Aussprache
|
menubar.edit.copy.pronunciation=複製 Aussprache
|
||||||
|
@ -10,7 +10,7 @@ tab.words=詞
|
|||||||
tab.characters=字
|
tab.characters=字
|
||||||
menubar.file=_檔案
|
menubar.file=_檔案
|
||||||
menubar.file.quit=_結束 Willow
|
menubar.file.quit=_結束 Willow
|
||||||
menubar.file.settings=_設定…
|
menubar.file.preferences=_設定…
|
||||||
menubar.edit=_編輯
|
menubar.edit=_編輯
|
||||||
menubar.edit.copy.headword=複製 Wort
|
menubar.edit.copy.headword=複製 Wort
|
||||||
menubar.edit.copy.pronunciation=複製 Aussprache
|
menubar.edit.copy.pronunciation=複製 Aussprache
|
||||||
|
Loading…
Reference in New Issue
Block a user