Compare commits

...

3 Commits

Author SHA1 Message Date
baa4b9bd1a
Fix lazy loading of detail tabs
All checks were successful
Pull Request / build (pull_request) Successful in 3m59s
2024-11-03 13:34:09 +01:00
64ecdd84aa
Add "Search on Web" context menu for entries and sentences 2024-11-03 11:16:20 +01:00
b8194f3ca6
Add "Copy sentence" context menu for example sentences 2024-11-03 11:01:55 +01:00
12 changed files with 131 additions and 25 deletions

View File

@ -34,6 +34,11 @@ class Model(
private val internalIsFindingCharacters: BooleanProperty = SimpleBooleanProperty(false)
private val internalIsFindingSentences: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingWordsBeginning: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingWordsContaining: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingCharacters: BooleanProperty = SimpleBooleanProperty(false)
private val internalFinishedFindingSentences: BooleanProperty = SimpleBooleanProperty(false)
val selectedEntry: ReadOnlyObjectProperty<DictionaryEntryFx> = internalSelectedEntry
val searchResults: ObservableList<DictionaryEntryFx> =
@ -53,6 +58,11 @@ class Model(
val isFindingCharacters: ReadOnlyBooleanProperty = internalIsFindingCharacters
val isFindingSentences: ReadOnlyBooleanProperty = internalIsFindingSentences
val finishedFindingWordsBeginning: ReadOnlyBooleanProperty = internalFinishedFindingWordsBeginning
val finishedFindingWordsContaining: ReadOnlyBooleanProperty = internalFinishedFindingWordsContaining
val finishedFindingCharacters: ReadOnlyBooleanProperty = internalFinishedFindingCharacters
val finishedFindingSentences: ReadOnlyBooleanProperty = internalFinishedFindingSentences
private val coroutineScope = MainScope()
fun search(query: String, searchMode: SearchMode) {
@ -72,6 +82,7 @@ class Model(
.map { it.toFx() }
)
internalIsFindingWordsBeginning.value = false
internalFinishedFindingWordsBeginning.value = true
}
}
@ -84,6 +95,7 @@ class Model(
.map { it.toFx() }
)
internalIsFindingWordsContaining.value = false
internalFinishedFindingWordsContaining.value = true
}
}
@ -96,6 +108,7 @@ class Model(
.map { it.toFx() }
)
internalIsFindingCharacters.value = false
internalFinishedFindingCharacters.value = true
}
}
@ -108,6 +121,7 @@ class Model(
.map { it.toFx() }
)
internalIsFindingSentences.value = false
internalFinishedFindingSentences.value = true
}
}
@ -116,6 +130,12 @@ class Model(
internalWordsContaining.setAll(emptyList())
internalCharacters.setAll(emptyList())
internalSentences.setAll(emptyList())
internalFinishedFindingCharacters.value = false
internalFinishedFindingWordsBeginning.value = false
internalFinishedFindingWordsContaining.value = false
internalFinishedFindingSentences.value = false
internalSelectedEntry.value = entry
}

View File

@ -8,6 +8,7 @@ import com.marvinelsen.willow.ui.controllers.MenuController
import com.marvinelsen.willow.ui.controllers.SearchController
import com.marvinelsen.willow.ui.controllers.SearchResultsController
import javafx.application.Application
import javafx.application.HostServices
import javafx.fxml.FXMLLoader
import javafx.scene.Scene
import javafx.scene.image.Image
@ -46,15 +47,17 @@ class WillowApplication : Application() {
val config = Config()
config.load()
val hostServices: HostServices = hostServices
val fxmlLoader = FXMLLoader()
fxmlLoader.resources = ResourceBundle.getBundle("i18n/willow", config.locale.value)
fxmlLoader.controllerFactory = Callback { type ->
when (type) {
MainController::class.java -> MainController(model)
MenuController::class.java -> MenuController(model, config)
DetailsController::class.java -> DetailsController(model, config)
DetailsController::class.java -> DetailsController(model, config, hostServices)
SearchController::class.java -> SearchController(model)
SearchResultsController::class.java -> SearchResultsController(model, config)
SearchResultsController::class.java -> SearchResultsController(model, config, hostServices)
else -> error("Trying to instantiate unknown controller type $type")
}
}

View File

@ -5,6 +5,7 @@ import com.marvinelsen.willow.config.Pronunciation
import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.util.createContextMenuForEntry
import javafx.application.HostServices
import javafx.beans.binding.Bindings
import javafx.geometry.VPos
import javafx.scene.control.Label
@ -15,10 +16,14 @@ import javafx.scene.layout.VBox
import javafx.util.Callback
import java.util.ResourceBundle
class DictionaryEntryCellFactory(private val resources: ResourceBundle, private val config: Config) :
Callback<ListView<DictionaryEntryFx?>, ListCell<DictionaryEntryFx?>> {
class DictionaryEntryCellFactory(
private val resources: ResourceBundle,
private val config: Config,
private val hostServices: HostServices,
) : Callback<ListView<DictionaryEntryFx?>, ListCell<DictionaryEntryFx?>> {
override fun call(listView: ListView<DictionaryEntryFx?>): ListCell<DictionaryEntryFx?> {
val entryCell = EntryCell(resources, config)
val entryCell = EntryCell(resources, config, hostServices)
entryCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING))
return entryCell
}
@ -28,8 +33,12 @@ class DictionaryEntryCellFactory(private val resources: ResourceBundle, private
}
}
private class EntryCell(private val resources: ResourceBundle, private val config: Config) :
ListCell<DictionaryEntryFx?>() {
private class EntryCell(
private val resources: ResourceBundle,
private val config: Config,
private val hostServices: HostServices,
) : ListCell<DictionaryEntryFx?>() {
private val labelHeadword = Label().apply {
styleClass.add("headword")
styleProperty().bind(
@ -118,14 +127,16 @@ private class EntryCell(private val resources: ResourceBundle, private val confi
entry.crossStraitsDefinitions.isNotEmpty() -> entry.crossStraitsDefinitions.joinToString(
separator = " / "
) { it.definition }
entry.moedictDefinitions.isNotEmpty() -> entry.moedictDefinitions.joinToString(
separator = " / "
) { it.definition }
else -> error("No definition for entry")
}
labelDefinition.text = definition
contextMenu = createContextMenuForEntry(entry, resources)
contextMenu = createContextMenuForEntry(entry, resources, hostServices)
graphic = root
}
}

View File

@ -3,17 +3,24 @@ package com.marvinelsen.willow.ui.cells
import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.ui.SentenceFx
import com.marvinelsen.willow.ui.util.createContextMenuForSentence
import javafx.application.HostServices
import javafx.beans.binding.Bindings
import javafx.scene.control.Label
import javafx.scene.control.ListCell
import javafx.scene.control.ListView
import javafx.scene.layout.VBox
import javafx.util.Callback
import java.util.ResourceBundle
class SentenceCellFactory(private val config: Config) : Callback<ListView<SentenceFx?>, ListCell<SentenceFx?>> {
class SentenceCellFactory(
private val resources: ResourceBundle,
private val config: Config,
private val hostServices: HostServices,
) : Callback<ListView<SentenceFx?>, ListCell<SentenceFx?>> {
override fun call(listView: ListView<SentenceFx?>): ListCell<SentenceFx?> {
val sentenceCell = SentenceCell(config)
val sentenceCell = SentenceCell(resources, config, hostServices)
sentenceCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING))
return sentenceCell
}
@ -23,7 +30,12 @@ class SentenceCellFactory(private val config: Config) : Callback<ListView<Senten
}
}
private class SentenceCell(private val config: Config) : ListCell<SentenceFx?>() {
private class SentenceCell(
private val resources: ResourceBundle,
private val config: Config,
private val hostServices: HostServices,
) : ListCell<SentenceFx?>() {
private val labelSentence = Label().apply {
styleClass.add("sentence")
isWrapText = true
@ -51,6 +63,7 @@ private class SentenceCell(private val config: Config) : ListCell<SentenceFx?>()
)
)
contextMenu = createContextMenuForSentence(sentence, resources, hostServices)
graphic = root
}
}

View File

@ -9,6 +9,7 @@ import com.marvinelsen.willow.ui.SentenceFx
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
import com.marvinelsen.willow.ui.cells.SentenceCellFactory
import com.marvinelsen.willow.ui.util.createContextMenuForEntry
import javafx.application.HostServices
import javafx.beans.binding.Bindings
import javafx.fxml.FXML
import javafx.scene.control.Label
@ -31,7 +32,12 @@ import kotlinx.html.stream.createHTML
import java.util.ResourceBundle
@Suppress("UnusedPrivateMember", "TooManyFunctions")
class DetailsController(private val model: Model, private val config: Config) {
class DetailsController(
private val model: Model,
private val config: Config,
private val hostServices: HostServices,
) {
@FXML
private lateinit var resources: ResourceBundle
@ -192,7 +198,7 @@ class DetailsController(private val model: Model, private val config: Config) {
private fun initializeListViewSentences() {
listViewSentences.apply {
cellFactory = SentenceCellFactory(config)
cellFactory = SentenceCellFactory(resources, config, hostServices)
items = model.sentences
disableProperty().bind(Bindings.or(model.isFindingSentences, Bindings.isEmpty(model.sentences)))
@ -205,7 +211,7 @@ class DetailsController(private val model: Model, private val config: Config) {
private fun initializeListViewWordsContaining() {
listViewWordsContaining.apply {
cellFactory = DictionaryEntryCellFactory(resources, config)
cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.wordsContaining
disableProperty().bind(Bindings.or(model.isFindingWordsContaining, Bindings.isEmpty(model.wordsContaining)))
@ -218,7 +224,7 @@ class DetailsController(private val model: Model, private val config: Config) {
private fun initializeListViewWordsBeginning() {
listViewWordsBeginning.apply {
cellFactory = DictionaryEntryCellFactory(resources, config)
cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.wordsBeginning
disableProperty().bind(Bindings.or(model.isFindingWordsBeginning, Bindings.isEmpty(model.wordsBeginning)))
@ -231,7 +237,7 @@ class DetailsController(private val model: Model, private val config: Config) {
private fun initializeListViewCharacters() {
listViewCharacters.apply {
cellFactory = DictionaryEntryCellFactory(resources, config)
cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.characters
disableProperty().bind(Bindings.or(model.isFindingCharacters, Bindings.isEmpty(model.characters)))
@ -258,7 +264,7 @@ class DetailsController(private val model: Model, private val config: Config) {
private fun headerOnContextMenuRequested(contextMenuEvent: ContextMenuEvent) {
if (model.selectedEntry.value == null) return
createContextMenuForEntry(model.selectedEntry.value, resources).show(
createContextMenuForEntry(model.selectedEntry.value, resources, hostServices).show(
flowPaneHeader.scene.window,
contextMenuEvent.screenX,
contextMenuEvent.screenY
@ -269,22 +275,22 @@ class DetailsController(private val model: Model, private val config: Config) {
private fun lazyUpdateTabContent(selectedTabId: String?) {
when (selectedTabId) {
"tabWords" -> {
if (model.wordsContaining.isEmpty()) {
if (!model.finishedFindingWordsContaining.value) {
model.findWordsContaining()
}
if (model.wordsBeginning.isEmpty()) {
if (!model.finishedFindingWordsBeginning.value) {
model.findWordsBeginning()
}
}
"tabCharacters" -> {
if (model.characters.isNotEmpty()) return
if (model.finishedFindingCharacters.value) return
model.findCharacters()
}
"tabSentences" -> {
if (model.sentences.isNotEmpty()) return
if (model.finishedFindingSentences.value) return
model.findSentences()
}

View File

@ -4,6 +4,7 @@ import com.marvinelsen.willow.Model
import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
import javafx.application.HostServices
import javafx.beans.binding.Bindings
import javafx.fxml.FXML
import javafx.scene.control.Label
@ -11,7 +12,12 @@ import javafx.scene.control.ListView
import javafx.scene.control.ProgressIndicator
import java.util.ResourceBundle
class SearchResultsController(private val model: Model, private val config: Config) {
class SearchResultsController(
private val model: Model,
private val config: Config,
private val hostServices: HostServices,
) {
@FXML
private lateinit var resources: ResourceBundle
@ -28,7 +34,7 @@ class SearchResultsController(private val model: Model, private val config: Conf
@FXML
@Suppress("UnusedPrivateMember")
private fun initialize() {
listViewSearchResults.cellFactory = DictionaryEntryCellFactory(resources, config)
listViewSearchResults.cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
listViewSearchResults.items = model.searchResults
listViewSearchResults
.disableProperty()

View File

@ -1,12 +1,20 @@
package com.marvinelsen.willow.ui.util
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.SentenceFx
import javafx.application.HostServices
import javafx.event.EventHandler
import javafx.scene.control.ContextMenu
import javafx.scene.control.MenuItem
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.util.ResourceBundle
fun createContextMenuForEntry(entry: DictionaryEntryFx, resourceBundle: ResourceBundle) = ContextMenu().apply {
fun createContextMenuForEntry(
entry: DictionaryEntryFx,
resourceBundle: ResourceBundle,
hostServices: HostServices,
) = ContextMenu().apply {
val menuItemCopyHeadword =
MenuItem(resourceBundle.getString("menubar.edit.copy.headword")).apply {
onAction = EventHandler { ClipboardHelper.copyString(entry.traditionalProperty.value) }
@ -17,5 +25,34 @@ fun createContextMenuForEntry(entry: DictionaryEntryFx, resourceBundle: Resource
onAction = EventHandler { ClipboardHelper.copyString(entry.pinyinWithToneMarksProperty.value) }
}
items.addAll(menuItemCopyHeadword, menuItemCopyPronunciation)
val menuItemSearchOnWeb =
MenuItem(resourceBundle.getString("menubar.edit.search-web")).apply {
onAction = EventHandler {
val query = URLEncoder.encode(entry.traditionalProperty.value, StandardCharsets.UTF_8)
hostServices.showDocument("https://duckduckgo.com/?q=$query")
}
}
items.addAll(menuItemCopyHeadword, menuItemCopyPronunciation, menuItemSearchOnWeb)
}
fun createContextMenuForSentence(
sentence: SentenceFx,
resourceBundle: ResourceBundle,
hostServices: HostServices,
) = ContextMenu().apply {
val menuItemCopySentence =
MenuItem(resourceBundle.getString("menubar.edit.copy.sentence")).apply {
onAction = EventHandler { ClipboardHelper.copyString(sentence.traditionalProperty.value) }
}
val menuItemSearchOnWeb =
MenuItem(resourceBundle.getString("menubar.edit.search-web")).apply {
onAction = EventHandler {
val query = URLEncoder.encode(sentence.traditionalProperty.value, StandardCharsets.UTF_8)
hostServices.showDocument("https://duckduckgo.com/?q=$query")
}
}
items.addAll(menuItemCopySentence, menuItemSearchOnWeb)
}

View File

@ -25,6 +25,8 @@ menubar.file.preferences=_Preferences…
menubar.edit=_Edit
menubar.edit.copy.headword=Copy Headword
menubar.edit.copy.pronunciation=Copy Pronunciation
menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_Help
menubar.help.about=_About…

View File

@ -25,6 +25,8 @@ menubar.file.preferences=_Einstellungen…
menubar.edit=_Bearbeiten
menubar.edit.copy.headword=Kopiere Wort
menubar.edit.copy.pronunciation=Kopiere Aussprache
menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_Hilfe
menubar.help.about=_Über…

View File

@ -25,6 +25,8 @@ menubar.file.preferences=_Preferences…
menubar.edit=_Edit
menubar.edit.copy.headword=Copy Headword
menubar.edit.copy.pronunciation=Copy Pronunciation
menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_Help
menubar.help.about=_About…

View File

@ -25,6 +25,8 @@ menubar.file.preferences=_設定…
menubar.edit=_編輯
menubar.edit.copy.headword=複製 Wort
menubar.edit.copy.pronunciation=複製 Aussprache
menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_說明
menubar.help.about=_關於 Willow…

View File

@ -25,6 +25,8 @@ menubar.file.preferences=_設定…
menubar.edit=_編輯
menubar.edit.copy.headword=複製 Wort
menubar.edit.copy.pronunciation=複製 Aussprache
menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_說明
menubar.help.about=_關於 Willow…