Add "Search on Web" context menu for entries and sentences

This commit is contained in:
Marvin Elsen 2024-11-03 11:16:20 +01:00
parent b8194f3ca6
commit 64ecdd84aa
Signed by: marvinelsen
GPG Key ID: 820672408CC318C2
11 changed files with 71 additions and 18 deletions

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.SearchController
import com.marvinelsen.willow.ui.controllers.SearchResultsController import com.marvinelsen.willow.ui.controllers.SearchResultsController
import javafx.application.Application import javafx.application.Application
import javafx.application.HostServices
import javafx.fxml.FXMLLoader import javafx.fxml.FXMLLoader
import javafx.scene.Scene import javafx.scene.Scene
import javafx.scene.image.Image import javafx.scene.image.Image
@ -46,15 +47,17 @@ class WillowApplication : Application() {
val config = Config() val config = Config()
config.load() config.load()
val hostServices: HostServices = hostServices
val fxmlLoader = FXMLLoader() val fxmlLoader = FXMLLoader()
fxmlLoader.resources = ResourceBundle.getBundle("i18n/willow", config.locale.value) fxmlLoader.resources = ResourceBundle.getBundle("i18n/willow", config.locale.value)
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, config) MenuController::class.java -> MenuController(model, config)
DetailsController::class.java -> DetailsController(model, config) DetailsController::class.java -> DetailsController(model, config, hostServices)
SearchController::class.java -> SearchController(model) 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") 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.config.Script
import com.marvinelsen.willow.ui.DictionaryEntryFx import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.util.createContextMenuForEntry import com.marvinelsen.willow.ui.util.createContextMenuForEntry
import javafx.application.HostServices
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.geometry.VPos import javafx.geometry.VPos
import javafx.scene.control.Label import javafx.scene.control.Label
@ -18,10 +19,11 @@ import java.util.ResourceBundle
class DictionaryEntryCellFactory( class DictionaryEntryCellFactory(
private val resources: ResourceBundle, private val resources: ResourceBundle,
private val config: Config, private val config: Config,
private val hostServices: HostServices,
) : Callback<ListView<DictionaryEntryFx?>, ListCell<DictionaryEntryFx?>> { ) : Callback<ListView<DictionaryEntryFx?>, ListCell<DictionaryEntryFx?>> {
override fun call(listView: 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)) entryCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING))
return entryCell return entryCell
} }
@ -34,6 +36,7 @@ class DictionaryEntryCellFactory(
private class EntryCell( private class EntryCell(
private val resources: ResourceBundle, private val resources: ResourceBundle,
private val config: Config, private val config: Config,
private val hostServices: HostServices,
) : ListCell<DictionaryEntryFx?>() { ) : ListCell<DictionaryEntryFx?>() {
private val labelHeadword = Label().apply { private val labelHeadword = Label().apply {
@ -133,7 +136,7 @@ private class EntryCell(
} }
labelDefinition.text = definition labelDefinition.text = definition
contextMenu = createContextMenuForEntry(entry, resources) contextMenu = createContextMenuForEntry(entry, resources, hostServices)
graphic = root graphic = root
} }
} }

View File

@ -4,6 +4,7 @@ import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Script import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.ui.SentenceFx import com.marvinelsen.willow.ui.SentenceFx
import com.marvinelsen.willow.ui.util.createContextMenuForSentence import com.marvinelsen.willow.ui.util.createContextMenuForSentence
import javafx.application.HostServices
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ListCell import javafx.scene.control.ListCell
@ -15,10 +16,11 @@ import java.util.ResourceBundle
class SentenceCellFactory( class SentenceCellFactory(
private val resources: ResourceBundle, private val resources: ResourceBundle,
private val config: Config, private val config: Config,
private val hostServices: HostServices,
) : Callback<ListView<SentenceFx?>, ListCell<SentenceFx?>> { ) : Callback<ListView<SentenceFx?>, ListCell<SentenceFx?>> {
override fun call(listView: ListView<SentenceFx?>): ListCell<SentenceFx?> { override fun call(listView: ListView<SentenceFx?>): ListCell<SentenceFx?> {
val sentenceCell = SentenceCell(resources, config) val sentenceCell = SentenceCell(resources, config, hostServices)
sentenceCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING)) sentenceCell.prefWidthProperty().bind(listView.widthProperty().subtract(CELL_PADDING))
return sentenceCell return sentenceCell
} }
@ -31,6 +33,7 @@ class SentenceCellFactory(
private class SentenceCell( private class SentenceCell(
private val resources: ResourceBundle, private val resources: ResourceBundle,
private val config: Config, private val config: Config,
private val hostServices: HostServices,
) : ListCell<SentenceFx?>() { ) : ListCell<SentenceFx?>() {
private val labelSentence = Label().apply { private val labelSentence = Label().apply {
@ -60,7 +63,7 @@ private class SentenceCell(
) )
) )
contextMenu = createContextMenuForSentence(sentence, resources) contextMenu = createContextMenuForSentence(sentence, resources, hostServices)
graphic = root 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.DictionaryEntryCellFactory
import com.marvinelsen.willow.ui.cells.SentenceCellFactory import com.marvinelsen.willow.ui.cells.SentenceCellFactory
import com.marvinelsen.willow.ui.util.createContextMenuForEntry import com.marvinelsen.willow.ui.util.createContextMenuForEntry
import javafx.application.HostServices
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
@ -31,7 +32,12 @@ import kotlinx.html.stream.createHTML
import java.util.ResourceBundle import java.util.ResourceBundle
@Suppress("UnusedPrivateMember", "TooManyFunctions") @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 @FXML
private lateinit var resources: ResourceBundle private lateinit var resources: ResourceBundle
@ -192,7 +198,7 @@ class DetailsController(private val model: Model, private val config: Config) {
private fun initializeListViewSentences() { private fun initializeListViewSentences() {
listViewSentences.apply { listViewSentences.apply {
cellFactory = SentenceCellFactory(resources, config) cellFactory = SentenceCellFactory(resources, config, hostServices)
items = model.sentences items = model.sentences
disableProperty().bind(Bindings.or(model.isFindingSentences, Bindings.isEmpty(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() { private fun initializeListViewWordsContaining() {
listViewWordsContaining.apply { listViewWordsContaining.apply {
cellFactory = DictionaryEntryCellFactory(resources, config) cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.wordsContaining items = model.wordsContaining
disableProperty().bind(Bindings.or(model.isFindingWordsContaining, Bindings.isEmpty(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() { private fun initializeListViewWordsBeginning() {
listViewWordsBeginning.apply { listViewWordsBeginning.apply {
cellFactory = DictionaryEntryCellFactory(resources, config) cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.wordsBeginning items = model.wordsBeginning
disableProperty().bind(Bindings.or(model.isFindingWordsBeginning, Bindings.isEmpty(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() { private fun initializeListViewCharacters() {
listViewCharacters.apply { listViewCharacters.apply {
cellFactory = DictionaryEntryCellFactory(resources, config) cellFactory = DictionaryEntryCellFactory(resources, config, hostServices)
items = model.characters items = model.characters
disableProperty().bind(Bindings.or(model.isFindingCharacters, Bindings.isEmpty(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) { private fun headerOnContextMenuRequested(contextMenuEvent: ContextMenuEvent) {
if (model.selectedEntry.value == null) return if (model.selectedEntry.value == null) return
createContextMenuForEntry(model.selectedEntry.value, resources).show( createContextMenuForEntry(model.selectedEntry.value, resources, hostServices).show(
flowPaneHeader.scene.window, flowPaneHeader.scene.window,
contextMenuEvent.screenX, contextMenuEvent.screenX,
contextMenuEvent.screenY contextMenuEvent.screenY

View File

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

View File

@ -2,12 +2,19 @@ package com.marvinelsen.willow.ui.util
import com.marvinelsen.willow.ui.DictionaryEntryFx import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.SentenceFx import com.marvinelsen.willow.ui.SentenceFx
import javafx.application.HostServices
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.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.util.ResourceBundle import java.util.ResourceBundle
fun createContextMenuForEntry(entry: DictionaryEntryFx, resourceBundle: ResourceBundle) = ContextMenu().apply { fun createContextMenuForEntry(
entry: DictionaryEntryFx,
resourceBundle: ResourceBundle,
hostServices: HostServices,
) = ContextMenu().apply {
val menuItemCopyHeadword = val menuItemCopyHeadword =
MenuItem(resourceBundle.getString("menubar.edit.copy.headword")).apply { MenuItem(resourceBundle.getString("menubar.edit.copy.headword")).apply {
onAction = EventHandler { ClipboardHelper.copyString(entry.traditionalProperty.value) } onAction = EventHandler { ClipboardHelper.copyString(entry.traditionalProperty.value) }
@ -18,14 +25,34 @@ fun createContextMenuForEntry(entry: DictionaryEntryFx, resourceBundle: Resource
onAction = EventHandler { ClipboardHelper.copyString(entry.pinyinWithToneMarksProperty.value) } 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")
}
} }
fun createContextMenuForSentence(sentence: SentenceFx, resourceBundle: ResourceBundle) = ContextMenu().apply { items.addAll(menuItemCopyHeadword, menuItemCopyPronunciation, menuItemSearchOnWeb)
}
fun createContextMenuForSentence(
sentence: SentenceFx,
resourceBundle: ResourceBundle,
hostServices: HostServices,
) = ContextMenu().apply {
val menuItemCopySentence = val menuItemCopySentence =
MenuItem(resourceBundle.getString("menubar.edit.copy.sentence")).apply { MenuItem(resourceBundle.getString("menubar.edit.copy.sentence")).apply {
onAction = EventHandler { ClipboardHelper.copyString(sentence.traditionalProperty.value) } onAction = EventHandler { ClipboardHelper.copyString(sentence.traditionalProperty.value) }
} }
items.addAll(menuItemCopySentence) 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

@ -26,6 +26,7 @@ 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
menubar.edit.copy.sentence=Copy Sentence menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_Help menubar.help=_Help
menubar.help.about=_About… menubar.help.about=_About…

View File

@ -26,6 +26,7 @@ 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
menubar.edit.copy.sentence=Copy Sentence menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_Hilfe menubar.help=_Hilfe
menubar.help.about=_Über… menubar.help.about=_Über…

View File

@ -26,6 +26,7 @@ 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
menubar.edit.copy.sentence=Copy Sentence menubar.edit.copy.sentence=Copy Sentence
menubar.edit.search-web=Search on Web...
menubar.help=_Help menubar.help=_Help
menubar.help.about=_About… menubar.help.about=_About…

View File

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

View File

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