Java Selection

This commit is contained in:
huangyuhui 2017-08-18 13:58:49 +08:00
parent 99f60ea6e5
commit dc468f1a76
24 changed files with 465 additions and 134 deletions

View File

@ -47,8 +47,8 @@ class VersionSetting() {
/** /**
* Java version or null if user customizes java directory. * Java version or null if user customizes java directory.
*/ */
val javaProperty = ImmediateNullableStringProperty(this, "java", null) val javaProperty = ImmediateStringProperty(this, "java", "")
var java: String? by javaProperty var java: String by javaProperty
/** /**
* User customized java directory or null if user uses system Java. * User customized java directory or null if user uses system Java.
@ -169,6 +169,27 @@ class VersionSetting() {
val launcherVisibilityProperty = ImmediateObjectProperty<LauncherVisibility>(this, "launcherVisibility", LauncherVisibility.HIDE) val launcherVisibilityProperty = ImmediateObjectProperty<LauncherVisibility>(this, "launcherVisibility", LauncherVisibility.HIDE)
var launcherVisibility: LauncherVisibility by launcherVisibilityProperty var launcherVisibility: LauncherVisibility by launcherVisibilityProperty
val javaVersion: JavaVersion? get() {
// TODO: lazy initialization may result in UI suspension.
if (java.isBlank())
java = if (javaDir.isBlank()) "Default" else "Custom"
if (java == "Default") return JavaVersion.fromCurrentEnvironment()
else if (java == "Custom") {
try {
return JavaVersion.fromExecutable(File(javaDir))
} catch (e: IOException) {
return null // Custom Java Directory not found,
}
} else if (java.isNotBlank()) {
val c = JavaVersion.JAVAS[java]
if (c == null) {
java = "Default"
return JavaVersion.fromCurrentEnvironment()
} else
return c
} else throw Error()
}
fun addPropertyChangedListener(listener: InvalidationListener) { fun addPropertyChangedListener(listener: InvalidationListener) {
usesGlobalProperty.addListener(listener) usesGlobalProperty.addListener(listener)
javaProperty.addListener(listener) javaProperty.addListener(listener)
@ -194,8 +215,7 @@ class VersionSetting() {
fun toLaunchOptions(gameDir: File): LaunchOptions { fun toLaunchOptions(gameDir: File): LaunchOptions {
return LaunchOptions( return LaunchOptions(
gameDir = gameDir, gameDir = gameDir,
java = if (java == null) JavaVersion.fromCurrentEnvironment() java = javaVersion ?: JavaVersion.fromCurrentEnvironment(),
else JavaVersion.fromExecutable(File(java)),
versionName = Main.TITLE, versionName = Main.TITLE,
profileName = Main.TITLE, profileName = Main.TITLE,
minecraftArgs = minecraftArgs, minecraftArgs = minecraftArgs,
@ -261,7 +281,7 @@ class VersionSetting() {
javaDir = json["javaDir"]?.asString ?: "" javaDir = json["javaDir"]?.asString ?: ""
precalledCommand = json["precalledCommand"]?.asString ?: "" precalledCommand = json["precalledCommand"]?.asString ?: ""
serverIp = json["serverIp"]?.asString ?: "" serverIp = json["serverIp"]?.asString ?: ""
java = json["java"]?.asString java = json["java"]?.asString ?: ""
wrapper = json["wrapper"]?.asString ?: "" wrapper = json["wrapper"]?.asString ?: ""
fullscreen = json["fullscreen"]?.asBoolean ?: false fullscreen = json["fullscreen"]?.asBoolean ?: false
noJVMArgs = json["noJVMArgs"]?.asBoolean ?: false noJVMArgs = json["noJVMArgs"]?.asBoolean ?: false

View File

@ -34,10 +34,8 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.taskResult
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.ui.wizard.DecoratorPage import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import java.util.concurrent.Callable
class AccountsPage() : StackPane(), DecoratorPage { class AccountsPage() : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts") override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
@ -136,7 +134,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
val username = txtUsername.text val username = txtUsername.text
val password = txtPassword.text val password = txtPassword.text
progressBar.isVisible = true progressBar.isVisible = true
val task = task(Callable { taskResult("create_account") {
try { try {
val account = when (type) { val account = when (type) {
0 -> OfflineAccount.fromUsername(username) 0 -> OfflineAccount.fromUsername(username)
@ -149,9 +147,8 @@ class AccountsPage() : StackPane(), DecoratorPage {
} catch (e: Exception) { } catch (e: Exception) {
e e
} }
}) }.subscribe(Scheduler.JAVAFX) {
task.subscribe(Scheduler.JAVAFX) { val account: Any = it["create_account"]
val account = task.result
if (account is Account) { if (account is Account) {
Settings.addAccount(account) Settings.addAccount(account)
dialog.close() dialog.close()

View File

@ -19,14 +19,11 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.effects.JFXDepthManager import com.jfoenix.effects.JFXDepthManager
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.Node
import javafx.scene.control.ScrollPane import javafx.scene.control.ScrollPane
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
import org.jackhuang.hmcl.mod.ModManager import org.jackhuang.hmcl.mod.ModManager
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.task.task import org.jackhuang.hmcl.task.task
import java.util.concurrent.Callable
class ModController { class ModController {
@FXML lateinit var scrollPane: ScrollPane @FXML lateinit var scrollPane: ScrollPane

View File

@ -19,23 +19,27 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.controls.* import com.jfoenix.controls.*
import javafx.beans.InvalidationListener import javafx.beans.InvalidationListener
import javafx.beans.binding.Bindings
import javafx.beans.value.ChangeListener
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.geometry.Pos
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ScrollPane import javafx.scene.control.ScrollPane
import javafx.scene.layout.GridPane import javafx.scene.control.Toggle
import javafx.scene.layout.VBox import javafx.scene.control.ToggleGroup
import javafx.scene.layout.*
import javafx.stage.DirectoryChooser import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.VersionSetting import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.ui.construct.ComponentList import org.jackhuang.hmcl.ui.construct.ComponentList
import org.jackhuang.hmcl.ui.construct.NumberValidator import org.jackhuang.hmcl.ui.construct.NumberValidator
import org.jackhuang.hmcl.util.JavaVersion
import org.jackhuang.hmcl.util.OS import org.jackhuang.hmcl.util.OS
class VersionSettingsController { class VersionSettingsController {
var lastVersionSetting: VersionSetting? = null var lastVersionSetting: VersionSetting? = null
@FXML lateinit var rootPane: VBox @FXML lateinit var rootPane: VBox
@FXML lateinit var scroll: ScrollPane @FXML lateinit var scroll: ScrollPane
@FXML lateinit var settingsPane: GridPane
@FXML lateinit var txtWidth: JFXTextField @FXML lateinit var txtWidth: JFXTextField
@FXML lateinit var txtHeight: JFXTextField @FXML lateinit var txtHeight: JFXTextField
@FXML lateinit var txtMaxMemory: JFXTextField @FXML lateinit var txtMaxMemory: JFXTextField
@ -45,7 +49,7 @@ class VersionSettingsController {
@FXML lateinit var txtWrapper: JFXTextField @FXML lateinit var txtWrapper: JFXTextField
@FXML lateinit var txtPrecallingCommand: JFXTextField @FXML lateinit var txtPrecallingCommand: JFXTextField
@FXML lateinit var txtServerIP: JFXTextField @FXML lateinit var txtServerIP: JFXTextField
@FXML lateinit var txtGameDir: JFXTextField @FXML lateinit var txtJavaDir: JFXTextField
@FXML lateinit var advancedSettingsPane: ComponentList @FXML lateinit var advancedSettingsPane: ComponentList
@FXML lateinit var cboLauncherVisibility: JFXComboBox<*> @FXML lateinit var cboLauncherVisibility: JFXComboBox<*>
@FXML lateinit var cboRunDirectory: JFXComboBox<*> @FXML lateinit var cboRunDirectory: JFXComboBox<*>
@ -54,6 +58,13 @@ class VersionSettingsController {
@FXML lateinit var chkNoJVMArgs: JFXToggleButton @FXML lateinit var chkNoJVMArgs: JFXToggleButton
@FXML lateinit var chkNoCommon: JFXToggleButton @FXML lateinit var chkNoCommon: JFXToggleButton
@FXML lateinit var chkNoGameCheck: JFXToggleButton @FXML lateinit var chkNoGameCheck: JFXToggleButton
@FXML lateinit var componentJava: ComponentList
@FXML lateinit var javaPane: VBox
@FXML lateinit var javaPaneCustom: HBox
@FXML lateinit var radioCustom: JFXRadioButton
@FXML lateinit var btnJavaSelect: JFXButton
val javaGroup = ToggleGroup()
fun initialize() { fun initialize() {
lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB" lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB"
@ -61,7 +72,7 @@ class VersionSettingsController {
scroll.smoothScrolling() scroll.smoothScrolling()
val limit = 300.0 val limit = 300.0
txtGameDir.limitWidth(limit) //txtJavaDir.limitWidth(limit)
txtMaxMemory.limitWidth(limit) txtMaxMemory.limitWidth(limit)
cboLauncherVisibility.limitWidth(limit) cboLauncherVisibility.limitWidth(limit)
cboRunDirectory.limitWidth(limit) cboRunDirectory.limitWidth(limit)
@ -82,6 +93,32 @@ class VersionSettingsController {
txtMaxMemory.textProperty().addListener(validation(txtMaxMemory)) txtMaxMemory.textProperty().addListener(validation(txtMaxMemory))
txtMetaspace.setValidators(validator(true)) txtMetaspace.setValidators(validator(true))
txtMetaspace.textProperty().addListener(validation(txtMetaspace)) txtMetaspace.textProperty().addListener(validation(txtMetaspace))
javaPane.children.clear()
javaPane.children += createJavaPane(JavaVersion.fromCurrentEnvironment(), javaGroup)
JavaVersion.JAVAS.values.forEach { javaVersion ->
javaPane.children += createJavaPane(javaVersion, javaGroup)
}
javaPane.children += javaPaneCustom
radioCustom.toggleGroup = javaGroup
txtJavaDir.disableProperty().bind(radioCustom.selectedProperty().not())
btnJavaSelect.disableProperty().bind(radioCustom.selectedProperty().not())
}
private fun createJavaPane(java: JavaVersion, group: ToggleGroup): Pane {
return HBox().apply {
style = "-fx-padding: 3;"
spacing = 8.0
alignment = Pos.CENTER_LEFT
children += JFXRadioButton(java.longVersion).apply {
toggleGroup = group
userData = java
}
children += Label(java.binary.absolutePath).apply {
styleClass += "subtitle-label"
style += "-fx-font-size: 10;"
}
}
} }
fun loadVersionSetting(version: VersionSetting) { fun loadVersionSetting(version: VersionSetting) {
@ -100,6 +137,7 @@ class VersionSettingsController {
fullscreenProperty.unbind() fullscreenProperty.unbind()
notCheckGameProperty.unbind() notCheckGameProperty.unbind()
noCommonProperty.unbind() noCommonProperty.unbind()
javaDirProperty.unbind()
unbindEnum(cboLauncherVisibility) unbindEnum(cboLauncherVisibility)
unbindEnum(cboRunDirectory) unbindEnum(cboRunDirectory)
} }
@ -107,6 +145,7 @@ class VersionSettingsController {
bindInt(txtWidth, version.widthProperty) bindInt(txtWidth, version.widthProperty)
bindInt(txtHeight, version.heightProperty) bindInt(txtHeight, version.heightProperty)
bindInt(txtMaxMemory, version.maxMemoryProperty) bindInt(txtMaxMemory, version.maxMemoryProperty)
bindString(txtJavaDir, version.javaDirProperty)
bindString(txtJVMArgs, version.javaArgsProperty) bindString(txtJVMArgs, version.javaArgsProperty)
bindString(txtGameArgs, version.minecraftArgsProperty) bindString(txtGameArgs, version.minecraftArgsProperty)
bindString(txtMetaspace, version.permSizeProperty) bindString(txtMetaspace, version.permSizeProperty)
@ -119,9 +158,47 @@ class VersionSettingsController {
bindBoolean(chkNoGameCheck, version.notCheckGameProperty) bindBoolean(chkNoGameCheck, version.notCheckGameProperty)
bindBoolean(chkNoCommon, version.noCommonProperty) bindBoolean(chkNoCommon, version.noCommonProperty)
val javaGroupKey = "java_group.listener"
@Suppress("UNCHECKED_CAST")
if (javaGroup.properties.containsKey(javaGroupKey))
javaGroup.selectedToggleProperty().removeListener(javaGroup.properties[javaGroupKey] as ChangeListener<in Toggle>)
var flag = false
var defaultToggle: JFXRadioButton? = null
for (toggle in javaGroup.toggles)
if (toggle is JFXRadioButton)
if (toggle.userData == version.javaVersion) {
toggle.isSelected = true
flag = true
} else if (toggle.userData == JavaVersion.fromCurrentEnvironment()) {
defaultToggle = toggle
}
val listener = ChangeListener<Toggle> { _, _, newValue ->
if (newValue == radioCustom) { // Custom
version.java = "Custom"
} else {
version.java = ((newValue as JFXRadioButton).userData as JavaVersion).longVersion
}
}
javaGroup.properties[javaGroupKey] = listener
javaGroup.selectedToggleProperty().addListener(listener)
if (!flag) {
defaultToggle?.isSelected = true
}
version.javaDirProperty.setChangedListener { initJavaSubtitle(version) }
version.javaProperty.setChangedListener { initJavaSubtitle(version)}
initJavaSubtitle(version)
lastVersionSetting = version lastVersionSetting = version
} }
private fun initJavaSubtitle(version: VersionSetting) {
componentJava.subtitle = version.javaVersion?.binary?.absolutePath ?: "Invalid Java Directory"
}
fun onShowAdvanced() { fun onShowAdvanced() {
if (!rootPane.children.contains(advancedSettingsPane)) if (!rootPane.children.contains(advancedSettingsPane))
rootPane.children += advancedSettingsPane rootPane.children += advancedSettingsPane
@ -134,6 +211,6 @@ class VersionSettingsController {
chooser.title = i18n("settings.choose_javapath") chooser.title = i18n("settings.choose_javapath")
val selectedDir = chooser.showDialog(Controllers.stage) val selectedDir = chooser.showDialog(Controllers.stage)
if (selectedDir != null) if (selectedDir != null)
txtGameDir.text = selectedDir.absolutePath txtJavaDir.text = selectedDir.absolutePath
} }
} }

View File

@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.construct
import javafx.beans.DefaultProperty import javafx.beans.DefaultProperty
import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.collections.ListChangeListener import javafx.collections.ListChangeListener
import javafx.collections.ObservableList import javafx.collections.ObservableList
@ -52,10 +53,12 @@ class ComponentList: StackPane() {
} }
fun addChildren(node: Node) { fun addChildren(node: Node) {
if (node is ComponentList) if (node is ComponentList) {
node.properties["title"] = node.title node.properties["title"] = node.title
node.properties["subtitle"] = node.subtitle
}
vbox.children += StackPane().apply { vbox.children += StackPane().apply {
children += ComponentListCell(this@ComponentList, node) children += ComponentListCell(node)
if (vbox.children.isEmpty()) if (vbox.children.isEmpty())
styleClass += "options-list-item-ahead" styleClass += "options-list-item-ahead"
else { else {
@ -64,9 +67,14 @@ class ComponentList: StackPane() {
} }
} }
val titleProperty = SimpleObjectProperty(this, "title", "Group") val titleProperty = SimpleStringProperty(this, "title", "Group")
var title: String by titleProperty var title: String by titleProperty
val subtitleProperty = SimpleStringProperty(this, "subtitle", "")
var subtitle: String by subtitleProperty
var hasSubtitle: Boolean = false
val depthProperty = SimpleIntegerProperty(this, "depth", 0) val depthProperty = SimpleIntegerProperty(this, "depth", 0)
var depth: Int by depthProperty var depth: Int by depthProperty
} }

View File

@ -35,7 +35,7 @@ import org.jackhuang.hmcl.ui.SVG
import org.jackhuang.hmcl.ui.limitHeight import org.jackhuang.hmcl.ui.limitHeight
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
class ComponentListCell(private val superList: ComponentList, private val content: Node) : StackPane() { class ComponentListCell(private val content: Node) : StackPane() {
var expandAnimation: Animation? = null var expandAnimation: Animation? = null
private var clipRect: Rectangle? = null private var clipRect: Rectangle? = null
@ -67,9 +67,7 @@ class ComponentListCell(private val superList: ComponentList, private val conten
} }
private fun updateLayout() { private fun updateLayout() {
val isSubList = content is ComponentList if (content is ComponentList) {
if (isSubList) {
content.styleClass -= "options-list" content.styleClass -= "options-list"
content.styleClass += "options-sublist" content.styleClass += "options-sublist"
@ -82,12 +80,33 @@ class ComponentListCell(private val superList: ComponentList, private val conten
expandButton.styleClass += "options-list-item-expand-button" expandButton.styleClass += "options-list-item-expand-button"
StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT) StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT)
val labelVBox = VBox()
Label().apply {
textProperty().bind(content.titleProperty)
isMouseTransparent = true
labelVBox.children += this
}
if (content.hasSubtitle)
Label().apply {
textProperty().bind(content.subtitleProperty)
isMouseTransparent = true
styleClass += "subtitle-label"
labelVBox.children += this
}
StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT)
groupNode.children.setAll( groupNode.children.setAll(
Label(content.properties["title"]?.toString() ?: "Group").apply { isMouseTransparent = true; StackPane.setAlignment(this, Pos.CENTER_LEFT) }, labelVBox,
expandButton) expandButton)
val container = VBox().apply { val container = VBox().apply {
style += "-fx-padding: 8 0 0 0;"
limitHeight(0.0) limitHeight(0.0)
val clipRect = Rectangle()
clipRect.widthProperty().bind(widthProperty())
clipRect.heightProperty().bind(heightProperty())
clip = clipRect
children.setAll(content) children.setAll(content)
} }

View File

@ -45,7 +45,7 @@ class FileItem : BorderPane() {
init { init {
left = VBox().apply { left = VBox().apply {
children += Label().apply { textProperty().bind(nameProperty) } children += Label().apply { textProperty().bind(nameProperty) }
children += x.apply { style += "-fx-text-fill: gray;" } children += x.apply { styleClass += "subtitle-label" }
} }
right = JFXButton().apply { right = JFXButton().apply {

View File

@ -18,9 +18,7 @@
package org.jackhuang.hmcl.ui.download package org.jackhuang.hmcl.ui.download
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.layout.Pane
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.mod.CurseForgeModpackCompletionTask
import org.jackhuang.hmcl.mod.CurseForgeModpackInstallTask import org.jackhuang.hmcl.mod.CurseForgeModpackInstallTask
import org.jackhuang.hmcl.mod.CurseForgeModpackManifest import org.jackhuang.hmcl.mod.CurseForgeModpackManifest
import org.jackhuang.hmcl.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.EnumGameDirectory

View File

@ -23,6 +23,10 @@
-fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
} }
.subtitle-label {
-fx-text-fill: gray;
}
.radio-button-title-label { .radio-button-title-label {
-fx-font-size: 16.0px; -fx-font-size: 16.0px;
-fx-padding: 14.0 0.0 -20.0 0.0; -fx-padding: 14.0 0.0 -20.0 0.0;
@ -579,7 +583,11 @@
.options-list { .options-list {
-fx-background-color: transparent; -fx-background-color: transparent;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2); -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
}
.card {
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
} }
.options-sublist { .options-sublist {
@ -602,7 +610,7 @@
} }
.options-list-item-expand-button { .options-list-item-expand-button {
-fx-toggle-icon4-size: 15px; -fx-toggle-icon4-size: 20px;
-fx-pref-width: -fx-toggle-icon4-size; -fx-pref-width: -fx-toggle-icon4-size;
-fx-max-width: -fx-toggle-icon4-size; -fx-max-width: -fx-toggle-icon4-size;
-fx-min-width: -fx-toggle-icon4-size; -fx-min-width: -fx-toggle-icon4-size;

View File

@ -1,8 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
@ -12,9 +9,9 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import com.jfoenix.controls.JFXComboBox?> <?import com.jfoenix.controls.JFXComboBox?>
<?import javafx.collections.FXCollections?> <?import javafx.collections.FXCollections?>
<?import com.jfoenix.controls.JFXListView?>
<?import org.jackhuang.hmcl.ui.construct.ComponentList?> <?import org.jackhuang.hmcl.ui.construct.ComponentList?>
<?import com.jfoenix.controls.JFXToggleButton?> <?import com.jfoenix.controls.JFXToggleButton?>
<?import com.jfoenix.controls.JFXRadioButton?>
<StackPane xmlns="http://javafx.com/javafx" <StackPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jackhuang.hmcl.ui.VersionSettingsController"> fx:controller="org.jackhuang.hmcl.ui.VersionSettingsController">
@ -24,19 +21,22 @@
<VBox fx:id="rootPane" style="-fx-padding: 20;"> <VBox fx:id="rootPane" style="-fx-padding: 20;">
<ComponentList depth="1"> <ComponentList depth="1">
<ComponentList title="%settings.java_dir"> <!-- Java Directory --> <ComponentList fx:id="componentJava" title="%settings.java_dir" hasSubtitle="true"> <!-- Java Directory -->
<VBox> <VBox fx:id="javaPane" spacing="8">
<JFXTextField fx:id="txtGameDir" BorderPane.alignment="CENTER_RIGHT" /> <HBox fx:id="javaPaneCustom" style="-fx-padding: 3;" spacing="3" alignment="CENTER_LEFT">
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" onMouseClicked="#onExploreJavaDir"> <JFXRadioButton fx:id="radioCustom" text="%settings.custom" />
<graphic> <JFXTextField fx:id="txtJavaDir" BorderPane.alignment="CENTER_RIGHT" />
<fx:include source="/assets/svg/folder-open.fxml" /> <JFXButton fx:id="btnJavaSelect" onMouseClicked="#onExploreJavaDir">
</graphic> <graphic>
</JFXButton> <fx:include source="/assets/svg/folder-open.fxml" />
</graphic>
</JFXButton>
</HBox>
</VBox> </VBox>
</ComponentList> </ComponentList>
<BorderPane> <!-- Max Memory --> <BorderPane> <!-- Max Memory -->
<left><VBox><Label text="%settings.max_memory" BorderPane.alignment="CENTER_LEFT" /><Label fx:id="lblPhysicalMemory" style="-fx-text-fill: gray;" /></VBox></left> <left><VBox><Label text="%settings.max_memory" BorderPane.alignment="CENTER_LEFT" /><Label fx:id="lblPhysicalMemory" styleClass="subtitle-label" /></VBox></left>
<right><JFXTextField fx:id="txtMaxMemory" BorderPane.alignment="CENTER_RIGHT" /></right> <right><JFXTextField fx:id="txtMaxMemory" BorderPane.alignment="CENTER_RIGHT" /></right>
</BorderPane> </BorderPane>
@ -73,16 +73,16 @@
<BorderPane> <!-- Dimension --> <BorderPane> <!-- Dimension -->
<left><Label text="%settings.dimension" BorderPane.alignment="CENTER_LEFT" /></left> <left><Label text="%settings.dimension" BorderPane.alignment="CENTER_LEFT" /></left>
<right> <right>
<BorderPane BorderPane.alignment="CENTER_RIGHT"> <BorderPane>
<left> <left>
<HBox prefWidth="210"> <HBox prefWidth="210" spacing="3" alignment="CENTER" BorderPane.alignment="CENTER">
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" /> <JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" />
<Label>x</Label> <Label>x</Label>
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" /> <JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" />
</HBox> </HBox>
</left> </left>
<right> <right>
<JFXCheckBox fx:id="chkFullscreen" text="%settings.fullscreen" alignment="CENTER"> <JFXCheckBox fx:id="chkFullscreen" text="%settings.fullscreen" alignment="CENTER" BorderPane.alignment="CENTER">
<BorderPane.margin> <BorderPane.margin>
<Insets right="7"/> <Insets right="7"/>
</BorderPane.margin> </BorderPane.margin>
@ -101,11 +101,14 @@
<Tooltip text="%advancedsettings.java_args_default" /> <Tooltip text="%advancedsettings.java_args_default" />
</tooltip> </tooltip>
</JFXTextField> </JFXTextField>
<JFXTextField labelFloat="true" promptText="%advancedsettings.Minecraft_arguments" styleClass="fit-width" fx:id="txtGameArgs" /> <fx:define>
<JFXTextField labelFloat="true" promptText="%advancedsettings.java_permanent_generation_space" styleClass="fit-width" fx:id="txtMetaspace" /> <Insets fx:id="insets" bottom="8" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.wrapper_launcher" styleClass="fit-width" fx:id="txtWrapper" /> </fx:define>
<JFXTextField labelFloat="true" promptText="%advancedsettings.precall_command" styleClass="fit-width" fx:id="txtPrecallingCommand" /> <JFXTextField labelFloat="true" promptText="%advancedsettings.Minecraft_arguments" styleClass="fit-width" fx:id="txtGameArgs" StackPane.margin="$insets" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.server_ip" styleClass="fit-width" fx:id="txtServerIP" /> <JFXTextField labelFloat="true" promptText="%advancedsettings.java_permanent_generation_space" styleClass="fit-width" fx:id="txtMetaspace" StackPane.margin="$insets" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.wrapper_launcher" styleClass="fit-width" fx:id="txtWrapper" StackPane.margin="$insets" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.precall_command" styleClass="fit-width" fx:id="txtPrecallingCommand" StackPane.margin="$insets" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.server_ip" styleClass="fit-width" fx:id="txtServerIP" StackPane.margin="$insets" />
<BorderPane><left><Label text="%advancedsettings.no_jvm_args" /></left><right><JFXToggleButton fx:id="chkNoJVMArgs" size="7" /></right></BorderPane> <BorderPane><left><Label text="%advancedsettings.no_jvm_args" /></left><right><JFXToggleButton fx:id="chkNoJVMArgs" size="7" /></right></BorderPane>
<BorderPane><left><Label text="%advancedsettings.no_common" /></left><right><JFXToggleButton fx:id="chkNoCommon" size="7" /></right></BorderPane> <BorderPane><left><Label text="%advancedsettings.no_common" /></left><right><JFXToggleButton fx:id="chkNoCommon" size="7" /></right></BorderPane>
<BorderPane><left><Label text="%advancedsettings.dont_check_game_completeness" /></left><right><JFXToggleButton fx:id="chkNoGameCheck" size="7" /></right></BorderPane> <BorderPane><left><Label text="%advancedsettings.dont_check_game_completeness" /></left><right><JFXToggleButton fx:id="chkNoGameCheck" size="7" /></right></BorderPane>

View File

@ -36,8 +36,9 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
private val forgeVersionList = dependencyManager.getVersionList("forge") private val forgeVersionList = dependencyManager.getVersionList("forge")
private val installer: File = File("forge-installer.jar").absoluteFile private val installer: File = File("forge-installer.jar").absoluteFile
lateinit var remote: RemoteVersion<*> lateinit var remote: RemoteVersion<*>
override val dependents: MutableCollection<Task> = mutableListOf() override val dependents = mutableListOf<Task>()
override val dependencies: MutableCollection<Task> = mutableListOf() override val dependencies = mutableListOf<Task>()
override val id = ID
init { init {
if (!forgeVersionList.loaded) if (!forgeVersionList.loaded)
@ -76,5 +77,7 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
check(installer.delete(), { "Unable to delete installer file $installer" }) check(installer.delete(), { "Unable to delete installer file $installer" })
} }
companion object {
const val ID = "forge_install_task"
}
} }

View File

@ -37,7 +37,7 @@ import java.util.logging.Level
* @param resolvedVersion the <b>resolved</b> version * @param resolvedVersion the <b>resolved</b> version
*/ */
class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager, private val resolvedVersion: Version): Task() { class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager, private val resolvedVersion: Version): Task() {
override val dependencies: MutableCollection<Task> = LinkedList() override val dependencies = LinkedList<Task>()
override fun execute() { override fun execute() {
for (library in resolvedVersion.libraries) for (library in resolvedVersion.libraries)
if (library.appliesToCurrentEnvironment) { if (library.appliesToCurrentEnvironment) {
@ -50,7 +50,7 @@ class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager,
} }
class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
override val dependencies: MutableCollection<Task> = LinkedList() override val dependencies = LinkedList<Task>()
override fun execute() { override fun execute() {
val logging = version.logging?.get(DownloadType.CLIENT) ?: return val logging = version.logging?.get(DownloadType.CLIENT) ?: return
val file = dependencyManager.repository.getLoggingObject(version.id, version.actualAssetIndex.id, logging) val file = dependencyManager.repository.getLoggingObject(version.id, version.actualAssetIndex.id, logging)
@ -60,7 +60,7 @@ class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyMa
} }
class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
override val dependencies: MutableCollection<Task> = LinkedList() override val dependencies = LinkedList<Task>()
override fun execute() { override fun execute() {
val assetIndexInfo = version.actualAssetIndex val assetIndexInfo = version.actualAssetIndex
val assetDir = dependencyManager.repository.getAssetDirectory(version.id, assetIndexInfo.id) val assetDir = dependencyManager.repository.getAssetDirectory(version.id, assetIndexInfo.id)
@ -76,7 +76,8 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version) private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version)
private val assetIndexInfo = version.actualAssetIndex private val assetIndexInfo = version.actualAssetIndex
private val assetIndexFile = dependencyManager.repository.getIndexFile(version.id, assetIndexInfo.id) private val assetIndexFile = dependencyManager.repository.getIndexFile(version.id, assetIndexInfo.id)
override val dependents: MutableCollection<Task> = LinkedList() override val dependents = LinkedList<Task>()
override val id = ID
init { init {
if (!assetIndexFile.exists()) if (!assetIndexFile.exists())
@ -93,12 +94,16 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
} }
result = res result = res
} }
companion object {
val ID = "game_asset_refresh_task"
}
} }
class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
private val refreshTask = GameAssetRefreshTask(dependencyManager, version) private val refreshTask = GameAssetRefreshTask(dependencyManager, version)
override val dependents: Collection<Task> = listOf(refreshTask) override val dependents = listOf(refreshTask)
override val dependencies: MutableCollection<Task> = LinkedList() override val dependencies = LinkedList<Task>()
override fun execute() { override fun execute() {
val size = refreshTask.result?.size ?: 0 val size = refreshTask.result?.size ?: 0
refreshTask.result?.forEach single@{ (file, assetObject) -> refreshTask.result?.forEach single@{ (file, assetObject) ->

View File

@ -35,8 +35,9 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
private val remoteVersion: String): TaskResult<Version>() { private val remoteVersion: String): TaskResult<Version>() {
private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag> lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
override val dependents: MutableCollection<Task> = mutableListOf() override val dependents = mutableListOf<Task>()
override val dependencies: MutableCollection<Task> = mutableListOf() override val dependencies = mutableListOf<Task>()
override val id = ID
init { init {
if (!liteLoaderVersionList.loaded) if (!liteLoaderVersionList.loaded)
@ -70,4 +71,7 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
dependencies += GameLibrariesTask(dependencyManager, tempVersion) dependencies += GameLibrariesTask(dependencyManager, tempVersion)
} }
companion object {
const val ID = "lite_loader_install_task"
}
} }

View File

@ -32,8 +32,9 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
private val remoteVersion: String): TaskResult<Version>() { private val remoteVersion: String): TaskResult<Version>() {
private val optiFineVersionList = dependencyManager.getVersionList("optifine") private val optiFineVersionList = dependencyManager.getVersionList("optifine")
lateinit var remote: RemoteVersion<*> lateinit var remote: RemoteVersion<*>
override val dependents: MutableCollection<Task> = mutableListOf() override val dependents = mutableListOf<Task>()
override val dependencies: MutableCollection<Task> = mutableListOf() override val dependencies = mutableListOf<Task>()
override val id = ID
init { init {
if (!optiFineVersionList.loaded) if (!optiFineVersionList.loaded)
@ -45,6 +46,7 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found") remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found")
} }
} }
override fun execute() { override fun execute() {
val library = Library( val library = Library(
groupId = "net.optifine", groupId = "net.optifine",
@ -73,4 +75,8 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
result = version.copy(libraries = merge(version.libraries, libraries), mainClass = mainClass, minecraftArguments = arg) result = version.copy(libraries = merge(version.libraries, libraries), mainClass = mainClass, minecraftArguments = arg)
dependencies += GameLibrariesTask(dependencyManager, version.copy(libraries = libraries)) dependencies += GameLibrariesTask(dependencyManager, version.copy(libraries = libraries))
} }
companion object {
const val ID = "optifine_install_task"
}
} }

View File

@ -68,7 +68,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
if (OS.CURRENT_OS == OS.OSX) { if (OS.CURRENT_OS == OS.OSX) {
res.add("-Xdock:name=Minecraft ${version.id}") res.add("-Xdock:name=Minecraft ${version.id}")
res.add("-Xdock:icon=" + repository.getAssetObject(version.id, version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath); res.add("-Xdock:icon=" + repository.getAssetObject(version.id, version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath)
} }
val logging = version.logging val logging = version.logging
@ -107,8 +107,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
if (options.minMemory != null && options.minMemory > 0) if (options.minMemory != null && options.minMemory > 0)
res.add("-Xms${options.minMemory}m") res.add("-Xms${options.minMemory}m")
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true"); res.add("-Dfml.ignoreInvalidMinecraftCertificates=true")
res.add("-Dfml.ignorePatchDiscrepancies=true"); res.add("-Dfml.ignorePatchDiscrepancies=true")
} }
// Classpath // Classpath
@ -161,9 +161,9 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
// Optional Minecraft arguments // Optional Minecraft arguments
if (options.height != null && options.width != null) { if (options.height != null && options.width != null) {
res.add("--height"); res.add("--height")
res.add(options.height.toString()) res.add(options.height.toString())
res.add("--width"); res.add("--width")
res.add(options.width.toString()) res.add(options.width.toString())
} }
@ -180,16 +180,16 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
if (options.proxyHost != null && options.proxyHost.isNotBlank() && if (options.proxyHost != null && options.proxyHost.isNotBlank() &&
options.proxyPort != null && options.proxyPort.isNotBlank()) { options.proxyPort != null && options.proxyPort.isNotBlank()) {
res.add("--proxyHost"); res.add("--proxyHost")
res.add(options.proxyHost) res.add(options.proxyHost)
res.add("--proxyPort"); res.add("--proxyPort")
res.add(options.proxyPort) res.add(options.proxyPort)
if (options.proxyUser != null && options.proxyUser.isNotBlank() && if (options.proxyUser != null && options.proxyUser.isNotBlank() &&
options.proxyPass != null && options.proxyPass.isNotBlank()) { options.proxyPass != null && options.proxyPass.isNotBlank()) {
res.add("--proxyUser"); res.add("--proxyUser")
res.add(options.proxyUser); res.add(options.proxyUser)
res.add("--proxyPass"); res.add("--proxyPass")
res.add(options.proxyPass); res.add(options.proxyPass)
} }
} }
@ -246,6 +246,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
fun launchAsync(): TaskResult<JavaProcess> { fun launchAsync(): TaskResult<JavaProcess> {
return object : TaskResult<JavaProcess>() { return object : TaskResult<JavaProcess>() {
override val id = LAUNCH_ASYNC_ID
override fun execute() { override fun execute() {
result = launch() result = launch()
} }
@ -259,11 +260,11 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
throw IOException("Script file: $scriptFile cannot be created.") throw IOException("Script file: $scriptFile cannot be created.")
scriptFile.bufferedWriter().use { writer -> scriptFile.bufferedWriter().use { writer ->
if (isWindows) { if (isWindows) {
writer.write("@echo off"); writer.write("@echo off")
writer.newLine() writer.newLine()
writer.write("set APPDATA=" + options.gameDir.parent); writer.write("set APPDATA=" + options.gameDir.parent)
writer.newLine() writer.newLine()
writer.write("cd /D %APPDATA%"); writer.write("cd /D %APPDATA%")
writer.newLine() writer.newLine()
} }
if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) { if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) {
@ -287,4 +288,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, processListener::onErrorLog)::run) thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, processListener::onErrorLog)::run)
thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess.process, processListener::onExit)::run) thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess.process, processListener::onExit)::run)
} }
companion object {
const val LAUNCH_ASYNC_ID = "process"
}
} }

View File

@ -27,6 +27,7 @@ import java.nio.charset.Charset
class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY): TaskResult<String>() { class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY): TaskResult<String>() {
override val scheduler: Scheduler = Scheduler.IO_THREAD override val scheduler: Scheduler = Scheduler.IO_THREAD
override val id = ID
override fun execute() { override fun execute() {
var exception: IOException? = null var exception: IOException? = null
@ -63,4 +64,8 @@ class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Ch
if (exception != null) if (exception != null)
throw exception throw exception
} }
companion object {
const val ID = "http_get"
}
} }

View File

@ -17,8 +17,10 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
internal class SimpleTask @JvmOverloads constructor(private val runnable: () -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() { import org.jackhuang.hmcl.util.AutoTypingMap
internal class SimpleTask @JvmOverloads constructor(private val runnable: (AutoTypingMap<String>) -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() {
override fun execute() { override fun execute() {
runnable() runnable(variables!!)
} }
} }

View File

@ -49,6 +49,8 @@ abstract class Task {
var title: String = this.javaClass.toString() var title: String = this.javaClass.toString()
var variables: AutoTypingMap<String>? = null
/** /**
* @see Thread.isInterrupted * @see Thread.isInterrupted
* @throws InterruptedException if current thread is interrupted * @throws InterruptedException if current thread is interrupted
@ -119,12 +121,13 @@ abstract class Task {
submit(subscriber).start() submit(subscriber).start()
} }
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(task(scheduler, closure)) fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit) = subscribe(task(scheduler, closure))
override fun toString(): String { override fun toString(): String {
return title return title
} }
} }
fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit): Task = SimpleTask(closure, scheduler) fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit): Task = SimpleTask(closure, scheduler)
fun <V> task(callable: Callable<V>): TaskResult<V> = TaskCallable(callable) fun <V> taskResult(id: String, callable: Callable<V>): TaskResult<V> = TaskCallable(id, callable)
fun <V> taskResult(id: String, callable: (AutoTypingMap<String>) -> V): TaskResult<V> = TaskCallable2(id, callable)

View File

@ -17,10 +17,17 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
import org.jackhuang.hmcl.util.AutoTypingMap
import java.util.concurrent.Callable import java.util.concurrent.Callable
internal class TaskCallable<V>(private val callable: Callable<V>) : TaskResult<V>() { internal class TaskCallable<V>(override val id: String, private val callable: Callable<V>) : TaskResult<V>() {
override fun execute() { override fun execute() {
result = callable.call() result = callable.call()
} }
}
internal class TaskCallable2<V>(override val id: String, private val callable: (AutoTypingMap<String>) -> V) : TaskResult<V>() {
override fun execute() {
result = callable(variables!!)
}
} }

View File

@ -17,6 +17,7 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
import org.jackhuang.hmcl.util.AutoTypingMap
import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.LOG
import java.util.concurrent.* import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -30,6 +31,7 @@ class TaskExecutor() {
var canceled = false var canceled = false
private set private set
val totTask = AtomicInteger(0) val totTask = AtomicInteger(0)
val variables = AutoTypingMap<String>(mutableMapOf())
private val taskQueue = ConcurrentLinkedQueue<Task>() private val taskQueue = ConcurrentLinkedQueue<Task>()
private val workerQueue = ConcurrentLinkedQueue<Future<*>>() private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
@ -119,7 +121,10 @@ class TaskExecutor() {
if (!doDependentsSucceeded && t.reliant || canceled) if (!doDependentsSucceeded && t.reliant || canceled)
throw SilentException() throw SilentException()
t.variables = variables
t.execute() t.execute()
if (t is TaskResult<*>)
variables[t.id] = t.result
flag = true flag = true
if (!t.hidden) if (!t.hidden)
LOG.finer("Task finished: ${t.title}") LOG.finer("Task finished: ${t.title}")
@ -142,6 +147,8 @@ class TaskExecutor() {
t.onDone(TaskEvent(source = this, task = t, failed = true)) t.onDone(TaskEvent(source = this, task = t, failed = true))
taskListener?.onFailed(t) taskListener?.onFailed(t)
} }
} finally {
t.variables = null
} }
return flag return flag
} }

View File

@ -19,4 +19,9 @@ package org.jackhuang.hmcl.task
abstract class TaskResult<V> : Task() { abstract class TaskResult<V> : Task() {
open var result: V? = null open var result: V? = null
/**
* The task id, will be stored as key of [variables]
*/
abstract val id: String
} }

View File

@ -0,0 +1,35 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* 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.util
class AutoTypingMap<K>(private val impl: MutableMap<K, Any>) {
fun clear() = impl.clear()
@Suppress("UNCHECKED_CAST")
operator fun <V> get(key: K): V = impl[key] as V
operator fun set(key: K, value: Any?) {
if (value != null)
impl.set(key, value)
}
val values get() = impl.values
val keys get() = impl.keys
fun containsKey(key: K) = impl.containsKey(key)
fun remove(key: K) = impl.remove(key)
}

View File

@ -21,17 +21,22 @@ import com.google.gson.annotations.SerializedName
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable
import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
data class JavaVersion internal constructor( data class JavaVersion internal constructor(
@SerializedName("location") @SerializedName("location")
val binary: File, val binary: File,
val version: Int, val longVersion: String,
val platform: Platform) : Serializable val platform: Platform) : Serializable
{ {
val version = parseVersion(longVersion)
companion object { companion object {
private val regex = Pattern.compile("java version \"(?<version>[1-9]*\\.[1-9]*\\.[0-9]*(.*?))\"") private val regex = Pattern.compile("java version \"(?<version>[1-9]*\\.[1-9]*\\.[0-9]*(.*?))\"")
val JAVAS: Map<String, JavaVersion>
val UNKNOWN: Int = -1 val UNKNOWN: Int = -1
val JAVA_5: Int = 50 val JAVA_5: Int = 50
val JAVA_6: Int = 60 val JAVA_6: Int = 60
@ -54,12 +59,15 @@ data class JavaVersion internal constructor(
@Throws(IOException::class) @Throws(IOException::class)
fun fromExecutable(file: File): JavaVersion { fun fromExecutable(file: File): JavaVersion {
var actualFile = file
var platform = Platform.BIT_32 var platform = Platform.BIT_32
var version: String? = null var version: String? = null
if (actualFile.nameWithoutExtension == "javaw") // javaw will not output version information
actualFile = actualFile.absoluteFile.parentFile.resolve("java")
try { try {
val process = ProcessBuilder(file.absolutePath, "-version").start() val process = ProcessBuilder(actualFile.absolutePath, "-version").start()
process.waitFor() process.waitFor()
process.inputStream.bufferedReader().forEachLine { line -> process.errorStream.bufferedReader().forEachLine { line ->
val m = regex.matcher(line) val m = regex.matcher(line)
if (m.find()) if (m.find())
version = m.group("version") version = m.group("version")
@ -74,10 +82,26 @@ data class JavaVersion internal constructor(
val parsedVersion = parseVersion(thisVersion) val parsedVersion = parseVersion(thisVersion)
if (parsedVersion == UNKNOWN) if (parsedVersion == UNKNOWN)
throw IOException("Java version '$thisVersion' can not be recognized") throw IOException("Java version '$thisVersion' can not be recognized")
return JavaVersion(file.parentFile, parsedVersion, platform) return JavaVersion(file.parentFile, thisVersion, platform)
} }
fun getJavaFile(home: File): File { private fun fromExecutable(file: File, version: String) =
JavaVersion (
binary = file,
longVersion = version,
platform = Platform.UNKNOWN
)
@Throws(IOException::class)
fun fromJavaHome(home: File): JavaVersion {
return fromExecutable(getJavaFile(home))
}
private fun fromJavaHome(home: File, version: String): JavaVersion {
return fromExecutable(getJavaFile(home), version)
}
private fun getJavaFile(home: File): File {
val path = home.resolve("bin") val path = home.resolve("bin")
val javaw = path.resolve("javaw.exe") val javaw = path.resolve("javaw.exe")
if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile) if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile)
@ -86,11 +110,80 @@ data class JavaVersion internal constructor(
return path.resolve("java") return path.resolve("java")
} }
fun fromCurrentEnvironment(): JavaVersion { private val currentJava: JavaVersion = JavaVersion(
return JavaVersion( binary = getJavaFile(File(System.getProperty("java.home"))),
binary = getJavaFile(File(System.getProperty("java.home"))), longVersion = System.getProperty("java.version"),
version = parseVersion(System.getProperty("java.version")), platform = Platform.PLATFORM)
platform = Platform.PLATFORM) fun fromCurrentEnvironment() = currentJava
init {
val temp = mutableMapOf<String, JavaVersion>()
(when (OS.CURRENT_OS) {
OS.WINDOWS -> queryWindows()
OS.OSX -> queryMacintosh()
else -> emptyList<JavaVersion>() /* Cannot detect Java in linux. */
}).forEach { javaVersion ->
temp.put(javaVersion.longVersion, javaVersion)
}
JAVAS = temp
}
private fun queryMacintosh() = LinkedList<JavaVersion>().apply {
val currentJRE = File("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home")
if (currentJRE.exists())
this += fromJavaHome(currentJRE)
File("/Library/Java/JavaVirtualMachines/").listFiles()?.forEach { file ->
this += fromJavaHome(file.resolve("Contents/Home"))
}
}
private fun queryWindows() = LinkedList<JavaVersion>().apply {
ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\") }
ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\") }
}
private fun queryRegisterKey(location: String) = LinkedList<JavaVersion>().apply {
querySubFolders(location).forEach { java ->
val s = java.count { it == '.' }
if (s > 1) {
val home = queryRegisterValue(java, "JavaHome")
if (home != null)
this += fromJavaHome(File(home))
}
}
}
private fun querySubFolders(location: String) = LinkedList<String>().apply {
val cmd = arrayOf("cmd", "/c", "reg", "query", location)
val process = Runtime.getRuntime().exec(cmd)
process.waitFor()
process.inputStream.bufferedReader().readLines().forEach { s ->
if (s.startsWith(location) && s != location)
this += s
}
}
private fun queryRegisterValue(location: String, name: String): String? {
val cmd = arrayOf("cmd", "/c", "reg", "query", location, "/v", name)
var last = false
val process = Runtime.getRuntime().exec(cmd)
process.waitFor()
process.inputStream.bufferedReader().readLines().forEach { s ->
if (s.isNotBlank()) {
if (last && s.trim().startsWith(name)) {
var begins = s.indexOf(name)
if (begins > 0) {
val s2 = s.substring(begins + name.length)
begins = s2.indexOf("REG_SZ")
if (begins > 0)
return s2.substring(begins + "REG_SZ".length).trim()
}
}
if (s.trim() == location)
last = true
}
}
return null
} }
} }
} }

View File

@ -18,6 +18,7 @@
package org.jackhuang.hmcl.util.property package org.jackhuang.hmcl.util.property
import javafx.beans.property.* import javafx.beans.property.*
import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue import javafx.beans.value.ObservableValue
open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) { open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) {
@ -27,10 +28,6 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String
super.set(newValue) super.set(newValue)
} }
protected fun superSet(newValue: String) {
super.set(newValue)
}
override fun bind(newObservable: ObservableValue<out String>) { override fun bind(newObservable: ObservableValue<out String>) {
super.get() super.get()
super.bind(newObservable) super.bind(newObservable)
@ -41,30 +38,17 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String
super.unbind() super.unbind()
} }
public override fun fireValueChangedEvent() { private var myListener: (String) -> Unit = {}
super.fireValueChangedEvent() private val changeListener = ChangeListener<String> { _, _, newValue ->
} myListener(newValue)
}
open class ImmediateNullableStringProperty(bean: Any, name: String, initialValue: String?): SimpleStringProperty(bean, name, initialValue) {
override fun set(newValue: String?) {
super.get()
super.set(newValue)
} }
protected fun superSet(newValue: String?) { fun setChangedListener(listener: (String) -> Unit) {
super.set(newValue) myListener = listener
} }
override fun bind(newObservable: ObservableValue<out String?>) { init {
super.get() addListener(changeListener)
super.bind(newObservable)
}
override fun unbind() {
super.get()
super.unbind()
} }
public override fun fireValueChangedEvent() { public override fun fireValueChangedEvent() {
@ -79,10 +63,6 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole
super.set(newValue) super.set(newValue)
} }
protected fun superSet(newValue: Boolean) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Boolean>?) { override fun bind(rawObservable: ObservableValue<out Boolean>?) {
super.get() super.get()
super.bind(rawObservable) super.bind(rawObservable)
@ -93,6 +73,19 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole
super.unbind() super.unbind()
} }
private var myListener: (Boolean) -> Unit = {}
private val changeListener = ChangeListener<Boolean> { _, _, newValue ->
myListener(newValue)
}
fun setChangedListener(listener: (Boolean) -> Unit) {
myListener = listener
}
init {
addListener(changeListener)
}
public override fun fireValueChangedEvent() { public override fun fireValueChangedEvent() {
super.fireValueChangedEvent() super.fireValueChangedEvent()
} }
@ -105,10 +98,6 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int):
super.set(newValue) super.set(newValue)
} }
protected fun superSet(newValue: Int) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Number>) { override fun bind(rawObservable: ObservableValue<out Number>) {
super.get() super.get()
super.bind(rawObservable) super.bind(rawObservable)
@ -119,6 +108,19 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int):
super.unbind() super.unbind()
} }
private var myListener: (Int) -> Unit = {}
private val changeListener = ChangeListener<Number> { _, _, newValue ->
myListener(newValue.toInt())
}
fun setChangedListener(listener: (Int) -> Unit) {
myListener = listener
}
init {
addListener(changeListener)
}
public override fun fireValueChangedEvent() { public override fun fireValueChangedEvent() {
super.fireValueChangedEvent() super.fireValueChangedEvent()
} }
@ -131,10 +133,6 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double
super.set(newValue) super.set(newValue)
} }
protected fun superSet(newValue: Double) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Number>) { override fun bind(rawObservable: ObservableValue<out Number>) {
super.get() super.get()
super.bind(rawObservable) super.bind(rawObservable)
@ -145,6 +143,19 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double
super.unbind() super.unbind()
} }
private var myListener: (Double) -> Unit = {}
private val changeListener = ChangeListener<Number> { _, _, newValue ->
myListener(newValue.toDouble())
}
fun setChangedListener(listener: (Double) -> Unit) {
myListener = listener
}
init {
addListener(changeListener)
}
public override fun fireValueChangedEvent() { public override fun fireValueChangedEvent() {
super.fireValueChangedEvent() super.fireValueChangedEvent()
} }
@ -167,6 +178,19 @@ open class ImmediateObjectProperty<T>(bean: Any, name: String, initialValue: T):
super.unbind() super.unbind()
} }
private var myListener: (T) -> Unit = {}
private val changeListener = ChangeListener<T> { _, _, newValue ->
myListener(newValue)
}
fun setChangedListener(listener: (T) -> Unit) {
myListener = listener
}
init {
addListener(changeListener)
}
public override fun fireValueChangedEvent() { public override fun fireValueChangedEvent() {
super.fireValueChangedEvent() super.fireValueChangedEvent()
} }