Compare commits

..

9 Commits

Author SHA1 Message Date
46af4db9f1
Fix detekt issues
All checks were successful
Pull Request / build (pull_request) Successful in 3m26s
2024-10-03 13:40:58 +02:00
630d464916
Polish words and character list 2024-10-03 13:38:40 +02:00
f6f2cfac5c
Refactor details controller 2024-10-03 13:16:51 +02:00
8ada0a5510
Add context menu for details view 2024-10-03 12:50:30 +02:00
12eb98d5b1
Display pronunciation in details view 2024-10-03 12:41:12 +02:00
d9b7b82c60
Implement find characters 2024-10-03 12:11:50 +02:00
cb52240eea
Add ikonli dependency 2024-10-01 20:17:39 +02:00
3939021285
Add application icon 2024-10-01 20:08:08 +02:00
1ab6ef453a
Load dictionary database from resources 2024-09-29 23:29:42 +02:00
19 changed files with 305 additions and 101 deletions

View File

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

View File

@ -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" }

View File

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

View File

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

View File

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

View File

@ -78,9 +78,11 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
}
private val findCharacters = """
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions
FROM cedict
WHERE traditional IN (?)
WITH cte(id, character) AS (VALUES ?)
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.