Compare commits
10 Commits
1ee597f934
...
cdcfd92f60
Author | SHA1 | Date | |
---|---|---|---|
cdcfd92f60 | |||
30f88c9e3e | |||
a88b4d409d | |||
cfaff6c6b9 | |||
ac182c10c0 | |||
47d53d4df8 | |||
573515217e | |||
239d0d7da2 | |||
a65c1db941 | |||
3a159fba76 |
@ -29,6 +29,7 @@ class WillowApplication : Application() {
|
||||
private const val FONT_SIZE = 12.0
|
||||
|
||||
private const val JDBC_CONNECTION_STRING = "jdbc:sqlite:dictionary.db"
|
||||
val resourceBundle: ResourceBundle = ResourceBundle.getBundle("i18n/willow", Locale.US)
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
@ -45,7 +46,7 @@ class WillowApplication : Application() {
|
||||
val model = Model(searchService, findWordsService)
|
||||
|
||||
val fxmlLoader = FXMLLoader()
|
||||
fxmlLoader.resources = ResourceBundle.getBundle("i18n/willow", Locale.US)
|
||||
fxmlLoader.resources = resourceBundle
|
||||
fxmlLoader.controllerFactory = Callback { type ->
|
||||
when (type) {
|
||||
MainController::class.java -> MainController(model)
|
||||
@ -71,7 +72,8 @@ class WillowApplication : Application() {
|
||||
private fun loadFonts() {
|
||||
Font.loadFont(javaClass.getResourceAsStream("/fonts/inter.ttf"), FONT_SIZE)
|
||||
Font.loadFont(javaClass.getResourceAsStream("/fonts/tw-kai.ttf"), FONT_SIZE)
|
||||
Font.loadFont(javaClass.getResourceAsStream("/fonts/noto-sans-tc.ttf"), FONT_SIZE)
|
||||
Font.loadFont(javaClass.getResourceAsStream("/fonts/noto-sans-tc-regular.ttf"), FONT_SIZE)
|
||||
Font.loadFont(javaClass.getResourceAsStream("/fonts/noto-sans-tc-bold.ttf"), FONT_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,8 @@ fun main() {
|
||||
pinyin_with_tone_marks TEXT NOT NULL,
|
||||
pinyin_with_tone_numbers TEXT NOT NULL,
|
||||
zhuyin TEXT NOT NULL,
|
||||
searchable_pinyin TEXT NOT NULL,
|
||||
searchable_pinyin_with_tone_numbers TEXT NOT NULL,
|
||||
definitions JSON NOT NULL,
|
||||
character_count INTEGER NOT NULL,
|
||||
CONSTRAINT character_count_gte CHECK(character_count > 0)
|
||||
@ -36,6 +38,10 @@ fun main() {
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_cedict_traditional ON cedict (traditional)")
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_cedict_simplified ON cedict (simplified)")
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_cedict_character_count ON cedict (character_count)")
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_cedict_searchable_pinyin ON cedict (searchable_pinyin)")
|
||||
statement.executeUpdate(
|
||||
"CREATE INDEX IF NOT EXISTS idx_cedict_searchable_pinyin_with_tone_numbers ON cedict (searchable_pinyin_with_tone_numbers)"
|
||||
)
|
||||
|
||||
val cedictParser = CedictParser.instance
|
||||
val cedictEntries =
|
||||
@ -45,7 +51,7 @@ fun main() {
|
||||
|
||||
val insertStatement =
|
||||
connection.prepareStatement(
|
||||
"INSERT OR IGNORE INTO cedict(traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions, character_count) VALUES(?,?,?,?,?,?,?)"
|
||||
"INSERT OR IGNORE INTO cedict(traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, searchable_pinyin, searchable_pinyin_with_tone_numbers, definitions, character_count) VALUES(?,?,?,?,?,?,?,?,?)"
|
||||
)
|
||||
for (entry in cedictEntries) {
|
||||
try {
|
||||
@ -71,12 +77,31 @@ fun main() {
|
||||
)
|
||||
insertStatement.setString(
|
||||
6,
|
||||
entry.pinyinSyllables.joinToString(
|
||||
separator = ""
|
||||
) {
|
||||
it
|
||||
.format(TransliterationSystem.PINYIN_WITH_TONE_NUMBERS)
|
||||
.lowercase()
|
||||
.replace("""\d""".toRegex(), "")
|
||||
}
|
||||
)
|
||||
insertStatement.setString(
|
||||
7,
|
||||
entry.pinyinSyllables.joinToString(
|
||||
separator = ""
|
||||
) {
|
||||
it.format(TransliterationSystem.PINYIN_WITH_TONE_NUMBERS).lowercase()
|
||||
}
|
||||
)
|
||||
insertStatement.setString(
|
||||
8,
|
||||
Json.encodeToString(
|
||||
ListSerializer(ListSerializer(String.serializer())),
|
||||
entry.definitions.map { it.glosses }
|
||||
)
|
||||
)
|
||||
insertStatement.setInt(7, entry.traditional.length)
|
||||
insertStatement.setInt(9, entry.traditional.length)
|
||||
} catch (_: Exception) {
|
||||
// no-op
|
||||
}
|
||||
|
@ -20,9 +20,15 @@ fun main() {
|
||||
val (shangWords, time3) = measureTimedValue {
|
||||
sqliteDictionary.findWordsContaining(shang)
|
||||
}
|
||||
val (characters, time4) = measureTimedValue {
|
||||
val searchi = sqliteDictionary.search("師範大學", SearchMode.TRADITIONAL).first()
|
||||
sqliteDictionary.findCharacters(searchi)
|
||||
}
|
||||
println(searchResults)
|
||||
println(shangWords)
|
||||
println(characters)
|
||||
println(time)
|
||||
println(time2)
|
||||
println(time3)
|
||||
println(time4)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
|
||||
class SqliteDictionary(private val connection: Connection) : Dictionary {
|
||||
private val whitespaceRegex = """\s+""".toRegex()
|
||||
|
||||
private val searchSimplifiedPreparedStatement: PreparedStatement by lazy {
|
||||
connection.prepareStatement(
|
||||
@ -29,6 +30,18 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
||||
)
|
||||
}
|
||||
|
||||
private val searchPinyinPreparedStatement: PreparedStatement by lazy {
|
||||
connection.prepareStatement(
|
||||
"""
|
||||
SELECT traditional, simplified, pinyin_with_tone_marks, pinyin_with_tone_numbers, zhuyin, definitions
|
||||
FROM cedict
|
||||
WHERE searchable_pinyin GLOB ?
|
||||
OR searchable_pinyin_with_tone_numbers GLOB ?
|
||||
ORDER BY character_count ASC
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
private val findWordsContaining: PreparedStatement by lazy {
|
||||
connection.prepareStatement(
|
||||
"""
|
||||
@ -40,8 +53,14 @@ 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 (?)
|
||||
""".trimIndent()
|
||||
|
||||
override fun search(query: String, searchMode: SearchMode) = when (searchMode) {
|
||||
SearchMode.PINYIN -> TODO()
|
||||
SearchMode.PINYIN -> searchPinyin(query)
|
||||
SearchMode.SIMPLIFIED -> searchSimplified(query)
|
||||
SearchMode.TRADITIONAL -> searchTraditional(query)
|
||||
SearchMode.ENGLISH -> TODO()
|
||||
@ -60,7 +79,16 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
||||
}
|
||||
|
||||
override fun findCharacters(entry: DictionaryEntry): List<DictionaryEntry> {
|
||||
return emptyList()
|
||||
val characterList = entry.traditional
|
||||
.split("")
|
||||
.filter { it.isNotBlank() }
|
||||
.joinToString(",") { "'$it'" }
|
||||
|
||||
val query = findCharacters.replace("?", characterList)
|
||||
|
||||
val resultSet: ResultSet = connection.createStatement().executeQuery(query)
|
||||
|
||||
return resultSet.toListOfDictionaryEntries()
|
||||
}
|
||||
|
||||
private fun searchSimplified(query: String): List<DictionaryEntry> {
|
||||
@ -71,6 +99,17 @@ class SqliteDictionary(private val connection: Connection) : Dictionary {
|
||||
return resultSet.toListOfDictionaryEntries()
|
||||
}
|
||||
|
||||
private fun searchPinyin(query: String): List<DictionaryEntry> {
|
||||
val sanitizedQuery = query.lowercase().replace(whitespaceRegex, "")
|
||||
|
||||
searchPinyinPreparedStatement.setString(1, "$sanitizedQuery*")
|
||||
searchPinyinPreparedStatement.setString(2, "$sanitizedQuery*")
|
||||
|
||||
val resultSet: ResultSet = searchPinyinPreparedStatement.executeQuery()
|
||||
|
||||
return resultSet.toListOfDictionaryEntries()
|
||||
}
|
||||
|
||||
private fun searchTraditional(query: String): List<DictionaryEntry> {
|
||||
searchTraditionalPreparedStatement.setString(1, "$query*")
|
||||
|
||||
|
@ -44,15 +44,13 @@ internal class EntryCell : ListCell<DictionaryEntryFx?>() {
|
||||
|
||||
init {
|
||||
text = null
|
||||
if (item != null) {
|
||||
contextMenu = createContextMenuForEntry(item!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateItem(entry: DictionaryEntryFx?, empty: Boolean) {
|
||||
super.updateItem(entry, empty)
|
||||
if (empty || entry == null) {
|
||||
graphic = null
|
||||
contextMenu = null
|
||||
} else {
|
||||
labelHeadword.text = entry.traditionalProperty.value
|
||||
labelPronunciation.text = entry.pinyinWithToneMarksProperty.value
|
||||
@ -60,6 +58,7 @@ internal class EntryCell : ListCell<DictionaryEntryFx?>() {
|
||||
val definition = entry.definitions.joinToString(separator = " / ") { it.joinToString(separator = "; ") }
|
||||
labelDefinition.text = definition
|
||||
|
||||
contextMenu = createContextMenuForEntry(entry)
|
||||
graphic = root
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,12 @@ import javafx.scene.control.Label
|
||||
import javafx.scene.control.ListView
|
||||
import javafx.scene.control.TabPane
|
||||
import javafx.scene.web.WebView
|
||||
import kotlinx.html.body
|
||||
import kotlinx.html.h1
|
||||
import kotlinx.html.html
|
||||
import kotlinx.html.li
|
||||
import kotlinx.html.ol
|
||||
import kotlinx.html.stream.createHTML
|
||||
|
||||
class DetailsController(private val model: Model) {
|
||||
@FXML
|
||||
@ -64,19 +70,19 @@ class DetailsController(private val model: Model) {
|
||||
return@addListener
|
||||
}
|
||||
webViewDefinition.engine.loadContent(
|
||||
buildString {
|
||||
append("<html>")
|
||||
append("<body>")
|
||||
append("<h1>CC-CEDICT</h1>")
|
||||
append("<ol>")
|
||||
for (definition in newValue.definitions) {
|
||||
append("<li>")
|
||||
append(definition.joinToString(separator = "; "))
|
||||
append("</li>")
|
||||
createHTML().html {
|
||||
body {
|
||||
h1 {
|
||||
+"CC-CEDICT"
|
||||
}
|
||||
ol {
|
||||
for (definition in newValue.definitions) {
|
||||
li {
|
||||
+definition.joinToString(separator = "; ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
append("</ol>")
|
||||
append("</body>")
|
||||
append("</html>")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -24,5 +24,14 @@ class SearchController(private val model: Model) {
|
||||
val searchMode = searchModeToggleGroup.selectedToggle.userData as SearchMode
|
||||
model.search(newValue, searchMode)
|
||||
}
|
||||
|
||||
searchModeToggleGroup.selectedToggleProperty().addListener { _, _, newValue ->
|
||||
if (textFieldSearch.text.isNullOrBlank()) {
|
||||
return@addListener
|
||||
}
|
||||
|
||||
val searchMode = newValue.userData as SearchMode
|
||||
model.search(textFieldSearch.text, searchMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
package com.marvinelsen.willow.ui.util
|
||||
|
||||
import com.marvinelsen.willow.WillowApplication
|
||||
import com.marvinelsen.willow.ui.DictionaryEntryFx
|
||||
import javafx.event.EventHandler
|
||||
import javafx.scene.control.ContextMenu
|
||||
import javafx.scene.control.MenuItem
|
||||
|
||||
fun createContextMenuForEntry(entry: DictionaryEntryFx) = ContextMenu().apply {
|
||||
val menuItemCopyHeadword = MenuItem("Copy Headword").apply {
|
||||
onAction = EventHandler { ClipboardHelper.copyHeadword(entry) }
|
||||
}
|
||||
val menuItemCopyHeadword =
|
||||
MenuItem(WillowApplication.resourceBundle.getString("menubar.edit.copy.headword")).apply {
|
||||
onAction = EventHandler { ClipboardHelper.copyHeadword(entry) }
|
||||
}
|
||||
|
||||
val menuItemCopyPronunciation = MenuItem("Copy Pronunciation").apply {
|
||||
onAction = EventHandler { ClipboardHelper.copyPronunciation(entry) }
|
||||
}
|
||||
val menuItemCopyPronunciation =
|
||||
MenuItem(WillowApplication.resourceBundle.getString("menubar.edit.copy.pronunciation")).apply {
|
||||
onAction = EventHandler { ClipboardHelper.copyPronunciation(entry) }
|
||||
}
|
||||
|
||||
items.addAll(menuItemCopyHeadword, menuItemCopyPronunciation)
|
||||
}
|
||||
|
@ -15,6 +15,11 @@ h1 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 0;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
ol li:only-child {
|
||||
list-style: none;
|
||||
}
|
||||
|
@ -8,11 +8,11 @@
|
||||
|
||||
.list-view {
|
||||
-fx-selection-bar: #B8EEFF;
|
||||
-fx-font-family: "Noto Sans CJK TC";
|
||||
/*-fx-selection-bar-non-focused: green;*/
|
||||
}
|
||||
|
||||
.list-view-entry {
|
||||
-fx-font-family: "Noto Sans TC";
|
||||
-fx-font-size: 20;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
@ -34,7 +34,7 @@
|
||||
}
|
||||
|
||||
.pronunciation {
|
||||
-fx-font: 16 "Noto Sans CJK TC";
|
||||
-fx-font: 16 "Noto Sans TC";
|
||||
}
|
||||
|
||||
.settings-dialog .content {
|
||||
|
BIN
src/main/resources/fonts/noto-sans-tc-bold.ttf
Normal file
BIN
src/main/resources/fonts/noto-sans-tc-bold.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/fonts/noto-sans-tc-regular.ttf
Normal file
BIN
src/main/resources/fonts/noto-sans-tc-regular.ttf
Normal file
Binary file not shown.
@ -1,5 +1,6 @@
|
||||
<?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?>
|
||||
@ -21,7 +22,11 @@
|
||||
<ListView fx:id="listviewSentences"/>
|
||||
</Tab>
|
||||
<Tab id="tabWords" closable="false" disable="false" text="%tab.words">
|
||||
<ListView fx:id="listViewWords"/>
|
||||
<ListView fx:id="listViewWords">
|
||||
<cellFactory>
|
||||
<DictionaryEntryCellFactory/>
|
||||
</cellFactory>
|
||||
</ListView>
|
||||
</Tab>
|
||||
<Tab closable="false" disable="false" text="%tab.characters">
|
||||
<ListView fx:id="listViewCharacters"/>
|
||||
|
@ -31,7 +31,8 @@
|
||||
</userData>
|
||||
</RadioButton>
|
||||
<RadioButton mnemonicParsing="false" text="%search.mode.english"
|
||||
toggleGroup="$searchModeToggleGroup">
|
||||
toggleGroup="$searchModeToggleGroup"
|
||||
disable="true">
|
||||
<userData>
|
||||
<SearchMode fx:value="ENGLISH"/>
|
||||
</userData>
|
||||
|
Loading…
x
Reference in New Issue
Block a user