From a9b044b4d29405573d2ef6c4d3a6d975629fd1a7 Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Fri, 11 Aug 2017 18:16:23 +0800 Subject: [PATCH] localization --- HMCL/build.gradle | 5 +- .../main/kotlin/org/jackhuang/hmcl/Main.kt | 16 + .../org/jackhuang/hmcl/setting/Config.kt | 6 + .../org/jackhuang/hmcl/setting/Profile.kt | 8 +- .../org/jackhuang/hmcl/setting/Settings.kt | 112 +++-- .../hmcl/setting/SettingsConstants.kt | 104 +++++ .../jackhuang/hmcl/setting/VersionSetting.kt | 9 + .../org/jackhuang/hmcl/ui/AccountsPage.kt | 14 +- .../org/jackhuang/hmcl/ui/AdvancedListBox.kt | 2 + .../org/jackhuang/hmcl/ui/Controllers.kt | 9 +- .../kotlin/org/jackhuang/hmcl/ui/Decorator.kt | 5 +- .../kotlin/org/jackhuang/hmcl/ui/FXUtils.kt | 42 +- .../jackhuang/hmcl/ui/LeftPaneController.kt | 10 +- .../kotlin/org/jackhuang/hmcl/ui/MainPage.kt | 3 +- .../org/jackhuang/hmcl/ui/SettingsPage.kt | 92 ++++ .../jackhuang/hmcl/ui/SidePaneController.kt | 12 +- .../org/jackhuang/hmcl/ui/UTF8Control.kt | 57 +++ .../org/jackhuang/hmcl/ui/VersionPage.kt | 3 +- .../hmcl/ui/VersionSettingsController.kt | 45 +- .../assets/css/jfoenix-main-demo.css | 4 + .../main/resources/assets/fxml/account.fxml | 84 ++-- .../main/resources/assets/fxml/decorator.fxml | 4 +- HMCL/src/main/resources/assets/fxml/main.fxml | 8 +- .../main/resources/assets/fxml/setting.fxml | 62 +++ .../assets/fxml/version-settings.fxml | 51 +- .../main/resources/assets/fxml/version.fxml | 13 +- .../resources/assets/lang/I18N.properties | 440 ++++++++++++++++++ .../resources/assets/lang/I18N_ru.properties | 440 ++++++++++++++++++ .../resources/assets/lang/I18N_vi.properties | 438 +++++++++++++++++ .../resources/assets/lang/I18N_zh.properties | 440 ++++++++++++++++++ .../assets/lang/I18N_zh_CN.properties | 440 ++++++++++++++++++ .../resources/assets/svg/folder-open.fxml | 2 + .../org/jackhuang/hmcl/auth/Accounts.kt | 27 -- .../hmcl/download/DefaultDependencyManager.kt | 3 + 34 files changed, 2820 insertions(+), 190 deletions(-) create mode 100644 HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt create mode 100644 HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt create mode 100644 HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/UTF8Control.kt create mode 100644 HMCL/src/main/resources/assets/fxml/setting.fxml create mode 100644 HMCL/src/main/resources/assets/lang/I18N.properties create mode 100644 HMCL/src/main/resources/assets/lang/I18N_ru.properties create mode 100644 HMCL/src/main/resources/assets/lang/I18N_vi.properties create mode 100644 HMCL/src/main/resources/assets/lang/I18N_zh.properties create mode 100644 HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties create mode 100644 HMCL/src/main/resources/assets/svg/folder-open.fxml delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/Accounts.kt diff --git a/HMCL/build.gradle b/HMCL/build.gradle index 8e4f7e7df..ed9917765 100644 --- a/HMCL/build.gradle +++ b/HMCL/build.gradle @@ -11,15 +11,16 @@ def buildnumber = System.getenv("TRAVIS_BUILD_NUMBER") if (buildnumber == null) buildnumber = System.getenv("BUILD_NUMBER") if (buildnumber == null) - buildnumber = "33" + buildnumber = "SNAPSHOT" def versionroot = System.getenv("VERSION_ROOT") if (versionroot == null) - versionroot = "2.7.8" + versionroot = "3.0" String mavenGroupId = 'HMCL' String mavenVersion = versionroot + '.' + buildnumber String bundleName = "Hello Minecraft! Launcher" +version = mavenVersion dependencies { compile project(":HMCLCore") diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt index 65e0d79ea..4625070e1 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt @@ -19,11 +19,25 @@ package org.jackhuang.hmcl import javafx.application.Application import javafx.stage.Stage +import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.ui.Controllers +import org.jackhuang.hmcl.ui.UTF8Control import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT +import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.OS import java.io.File +import java.util.* +import java.util.logging.Level + +fun i18n(key: String): String { + try { + return Main.RESOURCE_BUNDLE.getString(key) + } catch (e: Exception) { + LOG.log(Level.WARNING, "Cannot find key $key in resource bundle", e) + return key + } +} class Main : Application() { @@ -68,5 +82,7 @@ class Main : Application() { PRIMARY_STAGE.close() Scheduler.shutdown() } + + val RESOURCE_BUNDLE = Settings.LANG.resourceBundle } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Config.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Config.kt index fa4b19f88..3dab5d06d 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Config.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Config.kt @@ -42,6 +42,12 @@ class Config { field = value Settings.save() } + @SerializedName("proxyType") + var proxyType: Int = 0 + set(value) { + field = value + Settings.save() + } @SerializedName("proxyHost") var proxyHost: String? = null set(value) { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt index bd9b58b91..18bd61f60 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt @@ -40,11 +40,8 @@ class Profile(var name: String = "Default", initialGameDir: File = File(".minecr val gameDirProperty = ImmediateObjectProperty(this, "gameDir", initialGameDir) var gameDir: File by gameDirProperty - val noCommonProperty = ImmediateBooleanProperty(this, "noCommon", false) - var noCommon: Boolean by noCommonProperty - var repository = HMCLGameRepository(initialGameDir) - var dependency = DefaultDependencyManager(repository, BMCLAPIDownloadProvider) + val dependency: DefaultDependencyManager get() = DefaultDependencyManager(repository, Settings.DOWNLOAD_PROVIDER, Settings.PROXY) var modManager = ModManager(repository) init { @@ -93,7 +90,6 @@ class Profile(var name: String = "Default", initialGameDir: File = File(".minecr globalProperty.addListener(listener) selectedVersionProperty.addListener(listener) gameDirProperty.addListener(listener) - noCommonProperty.addListener(listener) } companion object Serializer: JsonSerializer, JsonDeserializer { @@ -104,7 +100,6 @@ class Profile(var name: String = "Default", initialGameDir: File = File(".minecr add("global", context.serialize(src.global)) addProperty("selectedVersion", src.selectedVersion) addProperty("gameDir", src.gameDir.path) - addProperty("noCommon", src.noCommon) } return jsonObject @@ -115,7 +110,6 @@ class Profile(var name: String = "Default", initialGameDir: File = File(".minecr return Profile(initialGameDir = File(json["gameDir"]?.asString ?: ""), initialSelectedVersion = json["selectedVersion"]?.asString ?: "").apply { global = context.deserialize(json["global"], VersionSetting::class.java) - noCommon = json["noCommon"]?.asBoolean ?: false } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt index 813b6779c..f804d4231 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt @@ -31,10 +31,11 @@ import org.jackhuang.hmcl.ProfileLoadingEvent import org.jackhuang.hmcl.ProfileChangedEvent import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.util.* -import org.jackhuang.hmcl.auth.OfflineAccount -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.event.EVENT_BUS import org.jackhuang.hmcl.util.property.ImmediateObjectProperty +import java.net.Authenticator +import java.net.InetSocketAddress +import java.net.PasswordAuthentication import java.net.Proxy import java.util.* @@ -58,13 +59,10 @@ object Settings { SETTINGS = initSettings(); loop@for ((name, settings) in SETTINGS.accounts) { - val factory = when(settings["type"]) { - "yggdrasil" -> YggdrasilAccount - "offline" -> OfflineAccount - else -> { - SETTINGS.accounts.remove(name) - continue@loop - } + val factory = Accounts.ACCOUNT_FACTORY[settings["type"] ?: ""] + if (factory == null) { + SETTINGS.accounts.remove(name) + continue@loop } val account = factory.fromStorage(settings) @@ -92,12 +90,6 @@ object Settings { } } - fun getDownloadProvider(): DownloadProvider = when (SETTINGS.downloadtype) { - 0 -> MojangDownloadProvider - 1 -> BMCLAPIDownloadProvider - else -> MojangDownloadProvider - } - private fun initSettings(): Config { var c = Config() if (SETTINGS_FILE.exists()) @@ -127,11 +119,7 @@ object Settings { SETTINGS.accounts.clear() for ((name, account) in ACCOUNTS) { val storage = account.toStorage() - storage["type"] = when(account) { - is OfflineAccount -> "offline" - is YggdrasilAccount -> "yggdrasil" - else -> "" - } + storage["type"] = Accounts.getAccountType(account) SETTINGS.accounts[name] = storage } @@ -141,13 +129,70 @@ object Settings { } } - val selectedProfile: Profile - get() { - if (!hasProfile(SETTINGS.selectedProfile)) - SETTINGS.selectedProfile = DEFAULT_PROFILE - return getProfile(SETTINGS.selectedProfile) + var LANG: Locales.SupportedLocale = Locales.getLocaleByName(SETTINGS.localization) + set(value) { + field = value + SETTINGS.localization = Locales.getNameByLocal(value) } + var PROXY: Proxy = Proxy.NO_PROXY + var PROXY_TYPE: Proxy.Type? = Proxies.getProxyType(SETTINGS.proxyType) + set(value) { + field = value + SETTINGS.proxyType = Proxies.PROXIES.indexOf(value) + loadProxy() + } + + var PROXY_HOST: String? get() = SETTINGS.proxyHost; set(value) { SETTINGS.proxyHost = value } + var PROXY_PORT: String? get() = SETTINGS.proxyPort; set(value) { SETTINGS.proxyPort = value } + var PROXY_USER: String? get() = SETTINGS.proxyUserName; set(value) { SETTINGS.proxyUserName = value } + var PROXY_PASS: String? get() = SETTINGS.proxyPassword; set(value) { SETTINGS.proxyPassword = value } + + private fun loadProxy() { + val host = PROXY_HOST + val port = PROXY_PORT?.toIntOrNull() + if (host == null || host.isBlank() || port == null) + PROXY = Proxy.NO_PROXY + else { + System.setProperty("http.proxyHost", PROXY_HOST) + System.setProperty("http.proxyPort", PROXY_PORT) + PROXY = Proxy(PROXY_TYPE, InetSocketAddress(host, port)) + + val user = PROXY_USER + val pass = PROXY_PASS + if (user != null && user.isNotBlank() && pass != null && pass.isNotBlank()) { + System.setProperty("http.proxyUser", user) + System.setProperty("http.proxyPassword", pass) + + Authenticator.setDefault(object : Authenticator() { + override fun getPasswordAuthentication(): PasswordAuthentication { + return PasswordAuthentication(user, pass.toCharArray()) + } + }) + } + } + } + + init { loadProxy() } + + var DOWNLOAD_PROVIDER: DownloadProvider + get() = when (SETTINGS.downloadtype) { + 0 -> MojangDownloadProvider + 1 -> BMCLAPIDownloadProvider + else -> MojangDownloadProvider + } + set(value) { + SETTINGS.downloadtype = when (value) { + MojangDownloadProvider -> 0 + BMCLAPIDownloadProvider -> 1 + else -> 0 + } + } + + /**************************************** + * ACCOUNTS * + ****************************************/ + val selectedAccountProperty = object : ImmediateObjectProperty(this, "selectedAccount", getAccount(SETTINGS.selectedAccount)) { override fun get(): Account? { val a = super.get() @@ -172,12 +217,6 @@ object Settings { } var selectedAccount: Account? by selectedAccountProperty - val PROXY: Proxy = Proxy.NO_PROXY - val PROXY_HOST: String? get() = SETTINGS.proxyHost - val PROXY_PORT: String? get() = SETTINGS.proxyPort - val PROXY_USER: String? get() = SETTINGS.proxyUserName - val PROXY_PASS: String? get() = SETTINGS.proxyPassword - fun addAccount(account: Account) { ACCOUNTS[account.username] = account } @@ -196,6 +235,17 @@ object Settings { selectedAccountProperty.get() } + /**************************************** + * PROFILES * + ****************************************/ + + val selectedProfile: Profile + get() { + if (!hasProfile(SETTINGS.selectedProfile)) + SETTINGS.selectedProfile = DEFAULT_PROFILE + return getProfile(SETTINGS.selectedProfile) + } + fun getProfile(name: String?): Profile { var p: Profile? = getProfileMap()[name ?: DEFAULT_PROFILE] if (p == null) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt new file mode 100644 index 000000000..93cd52ae6 --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt @@ -0,0 +1,104 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.setting + +import org.jackhuang.hmcl.auth.Account +import org.jackhuang.hmcl.auth.AccountFactory +import org.jackhuang.hmcl.auth.OfflineAccount +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider +import org.jackhuang.hmcl.download.MojangDownloadProvider +import org.jackhuang.hmcl.ui.UTF8Control +import java.net.Proxy +import java.util.* + +object Proxies { + val PROXIES = listOf(null, Proxy.Type.DIRECT, Proxy.Type.HTTP, Proxy.Type.SOCKS) + + fun getProxyType(index: Int): Proxy.Type? = PROXIES.getOrNull(index) +} + +object DownloadProviders { + val DOWNLOAD_PROVIDERS = listOf(MojangDownloadProvider, BMCLAPIDownloadProvider) + + fun getDownloadProvider(index: Int) = DOWNLOAD_PROVIDERS.getOrElse(index, { MojangDownloadProvider }) +} + +object Accounts { + val OFFLINE_ACCOUNT_KEY = "offline" + val YGGDRASIL_ACCOUNT_KEY = "yggdrasil" + + val ACCOUNTS = listOf(OfflineAccount, YggdrasilAccount) + val ACCOUNT_FACTORY = mapOf>( + OFFLINE_ACCOUNT_KEY to OfflineAccount, + YGGDRASIL_ACCOUNT_KEY to YggdrasilAccount + ) + + fun getAccountType(account: Account): String { + return when (account) { + is OfflineAccount -> OFFLINE_ACCOUNT_KEY + is YggdrasilAccount -> YGGDRASIL_ACCOUNT_KEY + else -> YGGDRASIL_ACCOUNT_KEY + } + } +} + +object Locales { + class SupportedLocale internal constructor( + val locale: Locale, + private val nameImpl: String? = null + ) { + val resourceBundle: ResourceBundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control) + + fun getName(nowResourceBundle: ResourceBundle): String { + if (nameImpl == null) + return resourceBundle.getString("lang") + else + return nowResourceBundle.getString(nameImpl) + } + } + + val DEFAULT = SupportedLocale(Locale.getDefault(), "lang.default") + val EN = SupportedLocale(Locale.ENGLISH) + val ZH = SupportedLocale(Locale.TRADITIONAL_CHINESE) + val ZH_CN = SupportedLocale(Locale.SIMPLIFIED_CHINESE) + val VI = SupportedLocale(Locale("vi")) + val RU = SupportedLocale(Locale("ru")) + + val LOCALES = listOf(DEFAULT, EN, ZH, ZH_CN, VI, RU) + + fun getLocale(index: Int) = LOCALES.getOrElse(index, { DEFAULT }) + fun getLocaleByName(name: String?) = when(name) { + "en" -> EN + "zh" -> ZH + "zh_CN" -> ZH_CN + "vi" -> VI + "ru" -> RU + else -> DEFAULT + } + + fun getNameByLocal(supportedLocale: SupportedLocale) = when(supportedLocale) { + EN -> "en" + ZH -> "zh" + ZH_CN -> "zh_CN" + VI -> "vi" + RU -> "ru" + DEFAULT -> "def" + else -> throw IllegalArgumentException("Unknown argument: " + supportedLocale) + } +} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt index 30e573054..f7b4b669c 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt @@ -108,6 +108,12 @@ class VersionSetting() { val notCheckGameProperty = ImmediateBooleanProperty(this, "notCheckGame", false) var notCheckGame: Boolean by notCheckGameProperty + /** + * True if HMCL does not find/download libraries in/to common path. + */ + val noCommonProperty = ImmediateBooleanProperty(this, "noCommon", false) + var noCommon: Boolean by noCommonProperty + // Minecraft settings. /** @@ -175,6 +181,7 @@ class VersionSetting() { minecraftArgsProperty.addListener(listener) noJVMArgsProperty.addListener(listener) notCheckGameProperty.addListener(listener) + noCommonProperty.addListener(listener) serverIpProperty.addListener(listener) fullscreenProperty.addListener(listener) widthProperty.addListener(listener) @@ -229,6 +236,7 @@ class VersionSetting() { addProperty("fullscreen", src.fullscreen) addProperty("noJVMArgs", src.noJVMArgs) addProperty("notCheckGame", src.notCheckGame) + addProperty("noCommon", src.noCommon) addProperty("launcherVisibility", src.launcherVisibility.ordinal) addProperty("gameDirType", src.gameDirType.ordinal) } @@ -258,6 +266,7 @@ class VersionSetting() { fullscreen = json["fullscreen"]?.asBoolean ?: false noJVMArgs = json["noJVMArgs"]?.asBoolean ?: false notCheckGame = json["notCheckGame"]?.asBoolean ?: false + noCommon = json["noCommon"]?.asBoolean ?: false launcherVisibility = LauncherVisibility.values()[json["launcherVisibility"]?.asInt ?: 1] gameDirType = EnumGameDirectory.values()[json["gameDirType"]?.asInt ?: 0] } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt index 5cde9d476..ed89c30a5 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt @@ -31,6 +31,7 @@ import javafx.scene.control.ToggleGroup import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Task @@ -45,9 +46,9 @@ class AccountsPage() : StackPane(), DecoratorPage { @FXML lateinit var dialog: JFXDialog @FXML lateinit var txtUsername: JFXTextField @FXML lateinit var txtPassword: JFXPasswordField - @FXML lateinit var lblPassword: Label @FXML lateinit var lblCreationWarning: Label @FXML lateinit var cboType: JFXComboBox + @FXML lateinit var progressBar: JFXProgressBar val listener = ChangeListener { _, _, newValue -> masonryPane.children.forEach { @@ -76,7 +77,6 @@ class AccountsPage() : StackPane(), DecoratorPage { cboType.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> val visible = newValue != 0 - lblPassword.isVisible = visible txtPassword.isVisible = visible } cboType.selectionModel.select(0) @@ -125,6 +125,8 @@ class AccountsPage() : StackPane(), DecoratorPage { } fun addNewAccount() { + txtUsername.text = "" + txtPassword.text = "" dialog.show() } @@ -132,6 +134,7 @@ class AccountsPage() : StackPane(), DecoratorPage { val type = cboType.selectionModel.selectedIndex val username = txtUsername.text val password = txtPassword.text + progressBar.isVisible = true val task = Task.of(Callable { try { val account = when (type) { @@ -155,6 +158,7 @@ class AccountsPage() : StackPane(), DecoratorPage { } else if (account is Exception) { lblCreationWarning.text = account.localizedMessage } + progressBar.isVisible = false } } @@ -165,7 +169,7 @@ class AccountsPage() : StackPane(), DecoratorPage { fun accountType(account: Account) = when(account) { - is OfflineAccount -> "Offline Account" - is YggdrasilAccount -> "Yggdrasil Account" - else -> throw Error("Unsupported account: $account") + is OfflineAccount -> i18n("login.methods.offline") + is YggdrasilAccount -> i18n("login.methods.yggdrasil") + else -> throw Error("${i18n("login.methods.no_method")}: $account") } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AdvancedListBox.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AdvancedListBox.kt index 40195d59d..158c4711d 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AdvancedListBox.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AdvancedListBox.kt @@ -40,11 +40,13 @@ class AdvancedListBox: ScrollPane() { fun add(child: Node): AdvancedListBox { if (child is Pane) { + child.maxWidthProperty().bind(this.widthProperty()) container.children += child } else { val pane = StackPane() pane.styleClass += "advanced-list-box-item" pane.children.setAll(child) + pane.maxWidthProperty().bind(this.widthProperty()) container.children += pane } return this diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt index 3e4f5c015..971a2c41e 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt @@ -22,13 +22,14 @@ import javafx.scene.Node import javafx.scene.Scene import javafx.scene.layout.StackPane import javafx.stage.Stage +import org.jackhuang.hmcl.Main object Controllers { lateinit var scene: Scene private set lateinit var stage: Stage private set val mainPane = MainPage() - + val settingsPane = SettingsPage() val versionPane = VersionPage() lateinit var leftPaneController: LeftPaneController @@ -39,10 +40,10 @@ object Controllers { fun initialize(stage: Stage) { this.stage = stage - decorator = Decorator(stage, mainPane, max = false) + decorator = Decorator(stage, mainPane, Main.TITLE, max = false) decorator.showPage(null) leftPaneController = LeftPaneController(decorator.leftPane) - sidePaneController = SidePaneController(decorator.sidePane) + sidePaneController = SidePaneController(decorator.sidePane, decorator.drawer) decorator.isCustomMaximize = false @@ -57,6 +58,4 @@ object Controllers { fun navigate(node: Node?) { decorator.showPage(node) } - - private fun loadPane(s: String): T = FXMLLoader(Controllers::class.java.getResource("/assets/fxml/$s.fxml")).load() } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt index e505ffbe2..d06e6e053 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt @@ -52,7 +52,7 @@ import org.jackhuang.hmcl.util.* import java.util.* import java.util.concurrent.ConcurrentLinkedQueue -class Decorator @JvmOverloads constructor(private val primaryStage: Stage, private val mainPage: Node, private val max: Boolean = true, min: Boolean = true) : StackPane(), AbstractWizardDisplayer { +class Decorator @JvmOverloads constructor(private val primaryStage: Stage, private val mainPage: Node, title: String, private val max: Boolean = true, min: Boolean = true) : StackPane(), AbstractWizardDisplayer { override val wizardController: WizardController = WizardController(this) private var xOffset: Double = 0.0 @@ -73,6 +73,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva @FXML lateinit var refreshMenuButton: JFXButton @FXML lateinit var addMenuButton: JFXButton @FXML lateinit var titleLabel: Label + @FXML lateinit var lblTitle: Label @FXML lateinit var leftPane: AdvancedListBox @FXML lateinit var drawer: JFXDrawer @FXML lateinit var sidePane: AdvancedListBox @@ -113,6 +114,8 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva btnMin.graphic = minus btnMax.graphic = resizeMax + lblTitle.text = title + buttonsContainer.background = Background(*arrayOf(BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))) titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED) { mouseEvent -> if (mouseEvent.clickCount == 2) { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt index d675542b3..a73239bdf 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt @@ -18,10 +18,15 @@ package org.jackhuang.hmcl.ui import com.jfoenix.concurrency.JFXUtilities +import com.jfoenix.controls.JFXCheckBox +import com.jfoenix.controls.JFXComboBox import com.jfoenix.controls.JFXScrollPane +import com.jfoenix.controls.JFXTextField import javafx.animation.Animation import javafx.animation.KeyFrame import javafx.animation.Timeline +import javafx.beans.property.Property +import javafx.beans.value.ChangeListener import javafx.event.ActionEvent import javafx.event.EventHandler import javafx.fxml.FXMLLoader @@ -37,9 +42,10 @@ import javafx.scene.input.ScrollEvent import javafx.scene.layout.Region import javafx.scene.shape.Rectangle import javafx.util.Duration +import org.jackhuang.hmcl.Main fun Node.loadFXML(absolutePath: String) { - val fxmlLoader = FXMLLoader(this.javaClass.getResource(absolutePath)) + val fxmlLoader = FXMLLoader(this.javaClass.getResource(absolutePath), Main.RESOURCE_BUNDLE) fxmlLoader.setRoot(this) fxmlLoader.setController(this) fxmlLoader.load() @@ -114,3 +120,37 @@ val stylesheets = arrayOf( Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(), Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(), Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm()) + + + +fun bindInt(textField: JFXTextField, property: Property<*>) { + textField.textProperty().unbind() + @Suppress("UNCHECKED_CAST") + textField.textProperty().bindBidirectional(property as Property, SafeIntStringConverter()) +} + +fun bindString(textField: JFXTextField, property: Property) { + textField.textProperty().unbind() + textField.textProperty().bindBidirectional(property) +} + +fun bindBoolean(checkBox: JFXCheckBox, property: Property) { + checkBox.selectedProperty().unbind() + checkBox.selectedProperty().bindBidirectional(property) +} + +fun bindEnum(comboBox: JFXComboBox<*>, property: Property>) { + unbindEnum(comboBox) + val listener = ChangeListener { _, _, newValue -> + property.value = property.value.javaClass.enumConstants[newValue.toInt()] + } + comboBox.selectionModel.select(property.value.ordinal) + comboBox.properties["listener"] = listener + comboBox.selectionModel.selectedIndexProperty().addListener(listener) +} + +fun unbindEnum(comboBox: JFXComboBox<*>) { + @Suppress("UNCHECKED_CAST") + val listener = comboBox.properties["listener"] as? ChangeListener ?: return + comboBox.selectionModel.selectedIndexProperty().removeListener(listener) +} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt index 8c4b622a7..f2bfacac2 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt @@ -28,13 +28,14 @@ import org.jackhuang.hmcl.event.EVENT_BUS import org.jackhuang.hmcl.event.RefreshedVersionsEvent import org.jackhuang.hmcl.game.LauncherHelper import org.jackhuang.hmcl.game.minecraftVersion +import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.ui.download.DownloadWizardProvider -class LeftPaneController(leftPane: AdvancedListBox) { +class LeftPaneController(private val leftPane: AdvancedListBox) { val versionsPane = VBox() val cboProfiles = JFXComboBox().apply { items.add("Default"); prefWidthProperty().bind(leftPane.widthProperty()) } - val accountItem = VersionListItem("mojang@mojang.com", "Yggdrasil") + val accountItem = VersionListItem("No account", "unknown") init { leftPane @@ -44,9 +45,9 @@ class LeftPaneController(leftPane: AdvancedListBox) { Controllers.navigate(AccountsPage()) } }) - .startCategory("PROFILES") + .startCategory(i18n("ui.label.profile")) .add(cboProfiles) - .startCategory("VERSIONS") + .startCategory(i18n("ui.label.version")) .add(versionsPane) EVENT_BUS.channel() += this::loadVersions @@ -109,6 +110,7 @@ class LeftPaneController(leftPane: AdvancedListBox) { profile.selectedVersion = version.id } ripplerContainer.properties["version"] = version.id to item + ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty()) versionsPane.children += ripplerContainer } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt index a91148f63..6df569b77 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt @@ -22,13 +22,14 @@ import javafx.beans.property.SimpleStringProperty import javafx.beans.property.StringProperty import javafx.fxml.FXML import javafx.scene.layout.StackPane +import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.ui.wizard.DecoratorPage /** * @see /assets/fxml/main.fxml */ class MainPage : StackPane(), DecoratorPage { - override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Main Page") + override val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.main")) @FXML lateinit var buttonLaunch: JFXButton diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt new file mode 100644 index 000000000..e80c1444d --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt @@ -0,0 +1,92 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui + +import com.jfoenix.controls.JFXComboBox +import com.jfoenix.controls.JFXTextField +import javafx.beans.binding.Bindings +import javafx.beans.property.SimpleStringProperty +import javafx.beans.property.StringProperty +import javafx.collections.FXCollections +import javafx.fxml.FXML +import javafx.scene.control.Label +import javafx.scene.layout.StackPane +import org.jackhuang.hmcl.i18n +import org.jackhuang.hmcl.setting.DownloadProviders +import org.jackhuang.hmcl.setting.Locales +import org.jackhuang.hmcl.setting.Proxies +import org.jackhuang.hmcl.setting.Settings +import org.jackhuang.hmcl.ui.wizard.DecoratorPage + +class SettingsPage : StackPane(), DecoratorPage { + override val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.launcher")) + + @FXML lateinit var txtProxyHost: JFXTextField + @FXML lateinit var txtProxyPort: JFXTextField + @FXML lateinit var txtProxyUsername: JFXTextField + @FXML lateinit var txtProxyPassword: JFXTextField + @FXML lateinit var cboProxyType: JFXComboBox<*> + @FXML lateinit var cboLanguage: JFXComboBox<*> + @FXML lateinit var cboDownloadSource: JFXComboBox<*> + + init { + loadFXML("/assets/fxml/setting.fxml") + + txtProxyHost.text = Settings.PROXY_HOST + txtProxyHost.textProperty().addListener { _, _, newValue -> + Settings.PROXY_HOST = newValue + } + + txtProxyPort.text = Settings.PROXY_PORT + txtProxyPort.textProperty().addListener { _, _, newValue -> + Settings.PROXY_PORT = newValue + } + + txtProxyUsername.text = Settings.PROXY_USER + txtProxyUsername.textProperty().addListener { _, _, newValue -> + Settings.PROXY_USER = newValue + } + + txtProxyPassword.text = Settings.PROXY_PASS + txtProxyPassword.textProperty().addListener { _, _, newValue -> + Settings.PROXY_PASS = newValue + } + + cboDownloadSource.selectionModel.select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.DOWNLOAD_PROVIDER)) + cboDownloadSource.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> + Settings.DOWNLOAD_PROVIDER = DownloadProviders.getDownloadProvider(newValue.toInt()) + } + + val list = FXCollections.observableArrayList