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.
*/
val javaProperty = ImmediateNullableStringProperty(this, "java", null)
var java: String? by javaProperty
val javaProperty = ImmediateStringProperty(this, "java", "")
var java: String by javaProperty
/**
* 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)
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) {
usesGlobalProperty.addListener(listener)
javaProperty.addListener(listener)
@ -194,8 +215,7 @@ class VersionSetting() {
fun toLaunchOptions(gameDir: File): LaunchOptions {
return LaunchOptions(
gameDir = gameDir,
java = if (java == null) JavaVersion.fromCurrentEnvironment()
else JavaVersion.fromExecutable(File(java)),
java = javaVersion ?: JavaVersion.fromCurrentEnvironment(),
versionName = Main.TITLE,
profileName = Main.TITLE,
minecraftArgs = minecraftArgs,
@ -261,7 +281,7 @@ class VersionSetting() {
javaDir = json["javaDir"]?.asString ?: ""
precalledCommand = json["precalledCommand"]?.asString ?: ""
serverIp = json["serverIp"]?.asString ?: ""
java = json["java"]?.asString
java = json["java"]?.asString ?: ""
wrapper = json["wrapper"]?.asString ?: ""
fullscreen = json["fullscreen"]?.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.setting.Settings
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.task.task
import org.jackhuang.hmcl.task.taskResult
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import java.util.concurrent.Callable
class AccountsPage() : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
@ -136,7 +134,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
val username = txtUsername.text
val password = txtPassword.text
progressBar.isVisible = true
val task = task(Callable {
taskResult("create_account") {
try {
val account = when (type) {
0 -> OfflineAccount.fromUsername(username)
@ -149,9 +147,8 @@ class AccountsPage() : StackPane(), DecoratorPage {
} catch (e: Exception) {
e
}
})
task.subscribe(Scheduler.JAVAFX) {
val account = task.result
}.subscribe(Scheduler.JAVAFX) {
val account: Any = it["create_account"]
if (account is Account) {
Settings.addAccount(account)
dialog.close()

View File

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

View File

@ -19,23 +19,27 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.controls.*
import javafx.beans.InvalidationListener
import javafx.beans.binding.Bindings
import javafx.beans.value.ChangeListener
import javafx.fxml.FXML
import javafx.geometry.Pos
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.layout.GridPane
import javafx.scene.layout.VBox
import javafx.scene.control.Toggle
import javafx.scene.control.ToggleGroup
import javafx.scene.layout.*
import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.ui.construct.ComponentList
import org.jackhuang.hmcl.ui.construct.NumberValidator
import org.jackhuang.hmcl.util.JavaVersion
import org.jackhuang.hmcl.util.OS
class VersionSettingsController {
var lastVersionSetting: VersionSetting? = null
@FXML lateinit var rootPane: VBox
@FXML lateinit var scroll: ScrollPane
@FXML lateinit var settingsPane: GridPane
@FXML lateinit var txtWidth: JFXTextField
@FXML lateinit var txtHeight: JFXTextField
@FXML lateinit var txtMaxMemory: JFXTextField
@ -45,7 +49,7 @@ class VersionSettingsController {
@FXML lateinit var txtWrapper: JFXTextField
@FXML lateinit var txtPrecallingCommand: JFXTextField
@FXML lateinit var txtServerIP: JFXTextField
@FXML lateinit var txtGameDir: JFXTextField
@FXML lateinit var txtJavaDir: JFXTextField
@FXML lateinit var advancedSettingsPane: ComponentList
@FXML lateinit var cboLauncherVisibility: JFXComboBox<*>
@FXML lateinit var cboRunDirectory: JFXComboBox<*>
@ -54,6 +58,13 @@ class VersionSettingsController {
@FXML lateinit var chkNoJVMArgs: JFXToggleButton
@FXML lateinit var chkNoCommon: 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() {
lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB"
@ -61,7 +72,7 @@ class VersionSettingsController {
scroll.smoothScrolling()
val limit = 300.0
txtGameDir.limitWidth(limit)
//txtJavaDir.limitWidth(limit)
txtMaxMemory.limitWidth(limit)
cboLauncherVisibility.limitWidth(limit)
cboRunDirectory.limitWidth(limit)
@ -82,6 +93,32 @@ class VersionSettingsController {
txtMaxMemory.textProperty().addListener(validation(txtMaxMemory))
txtMetaspace.setValidators(validator(true))
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) {
@ -100,6 +137,7 @@ class VersionSettingsController {
fullscreenProperty.unbind()
notCheckGameProperty.unbind()
noCommonProperty.unbind()
javaDirProperty.unbind()
unbindEnum(cboLauncherVisibility)
unbindEnum(cboRunDirectory)
}
@ -107,6 +145,7 @@ class VersionSettingsController {
bindInt(txtWidth, version.widthProperty)
bindInt(txtHeight, version.heightProperty)
bindInt(txtMaxMemory, version.maxMemoryProperty)
bindString(txtJavaDir, version.javaDirProperty)
bindString(txtJVMArgs, version.javaArgsProperty)
bindString(txtGameArgs, version.minecraftArgsProperty)
bindString(txtMetaspace, version.permSizeProperty)
@ -119,9 +158,47 @@ class VersionSettingsController {
bindBoolean(chkNoGameCheck, version.notCheckGameProperty)
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
}
private fun initJavaSubtitle(version: VersionSetting) {
componentJava.subtitle = version.javaVersion?.binary?.absolutePath ?: "Invalid Java Directory"
}
fun onShowAdvanced() {
if (!rootPane.children.contains(advancedSettingsPane))
rootPane.children += advancedSettingsPane
@ -134,6 +211,6 @@ class VersionSettingsController {
chooser.title = i18n("settings.choose_javapath")
val selectedDir = chooser.showDialog(Controllers.stage)
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.property.SimpleIntegerProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList
@ -52,10 +53,12 @@ class ComponentList: StackPane() {
}
fun addChildren(node: Node) {
if (node is ComponentList)
if (node is ComponentList) {
node.properties["title"] = node.title
node.properties["subtitle"] = node.subtitle
}
vbox.children += StackPane().apply {
children += ComponentListCell(this@ComponentList, node)
children += ComponentListCell(node)
if (vbox.children.isEmpty())
styleClass += "options-list-item-ahead"
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
val subtitleProperty = SimpleStringProperty(this, "subtitle", "")
var subtitle: String by subtitleProperty
var hasSubtitle: Boolean = false
val depthProperty = SimpleIntegerProperty(this, "depth", 0)
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.util.*
class ComponentListCell(private val superList: ComponentList, private val content: Node) : StackPane() {
class ComponentListCell(private val content: Node) : StackPane() {
var expandAnimation: Animation? = null
private var clipRect: Rectangle? = null
@ -67,9 +67,7 @@ class ComponentListCell(private val superList: ComponentList, private val conten
}
private fun updateLayout() {
val isSubList = content is ComponentList
if (isSubList) {
if (content is ComponentList) {
content.styleClass -= "options-list"
content.styleClass += "options-sublist"
@ -82,12 +80,33 @@ class ComponentListCell(private val superList: ComponentList, private val conten
expandButton.styleClass += "options-list-item-expand-button"
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(
Label(content.properties["title"]?.toString() ?: "Group").apply { isMouseTransparent = true; StackPane.setAlignment(this, Pos.CENTER_LEFT) },
labelVBox,
expandButton)
val container = VBox().apply {
style += "-fx-padding: 8 0 0 0;"
limitHeight(0.0)
val clipRect = Rectangle()
clipRect.widthProperty().bind(widthProperty())
clipRect.heightProperty().bind(heightProperty())
clip = clipRect
children.setAll(content)
}

View File

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

View File

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

View File

@ -23,6 +23,10 @@
-fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
}
.subtitle-label {
-fx-text-fill: gray;
}
.radio-button-title-label {
-fx-font-size: 16.0px;
-fx-padding: 14.0 0.0 -20.0 0.0;
@ -579,7 +583,11 @@
.options-list {
-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 {
@ -602,7 +610,7 @@
}
.options-list-item-expand-button {
-fx-toggle-icon4-size: 15px;
-fx-toggle-icon4-size: 20px;
-fx-pref-width: -fx-toggle-icon4-size;
-fx-max-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"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
@ -12,9 +9,9 @@
<?import javafx.geometry.Insets?>
<?import com.jfoenix.controls.JFXComboBox?>
<?import javafx.collections.FXCollections?>
<?import com.jfoenix.controls.JFXListView?>
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
<?import com.jfoenix.controls.JFXToggleButton?>
<?import com.jfoenix.controls.JFXRadioButton?>
<StackPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jackhuang.hmcl.ui.VersionSettingsController">
@ -24,19 +21,22 @@
<VBox fx:id="rootPane" style="-fx-padding: 20;">
<ComponentList depth="1">
<ComponentList title="%settings.java_dir"> <!-- Java Directory -->
<VBox>
<JFXTextField fx:id="txtGameDir" BorderPane.alignment="CENTER_RIGHT" />
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" onMouseClicked="#onExploreJavaDir">
<graphic>
<fx:include source="/assets/svg/folder-open.fxml" />
</graphic>
</JFXButton>
<ComponentList fx:id="componentJava" title="%settings.java_dir" hasSubtitle="true"> <!-- Java Directory -->
<VBox fx:id="javaPane" spacing="8">
<HBox fx:id="javaPaneCustom" style="-fx-padding: 3;" spacing="3" alignment="CENTER_LEFT">
<JFXRadioButton fx:id="radioCustom" text="%settings.custom" />
<JFXTextField fx:id="txtJavaDir" BorderPane.alignment="CENTER_RIGHT" />
<JFXButton fx:id="btnJavaSelect" onMouseClicked="#onExploreJavaDir">
<graphic>
<fx:include source="/assets/svg/folder-open.fxml" />
</graphic>
</JFXButton>
</HBox>
</VBox>
</ComponentList>
<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>
</BorderPane>
@ -73,16 +73,16 @@
<BorderPane> <!-- Dimension -->
<left><Label text="%settings.dimension" BorderPane.alignment="CENTER_LEFT" /></left>
<right>
<BorderPane BorderPane.alignment="CENTER_RIGHT">
<BorderPane>
<left>
<HBox prefWidth="210">
<HBox prefWidth="210" spacing="3" alignment="CENTER" BorderPane.alignment="CENTER">
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" />
<Label>x</Label>
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" />
</HBox>
</left>
<right>
<JFXCheckBox fx:id="chkFullscreen" text="%settings.fullscreen" alignment="CENTER">
<JFXCheckBox fx:id="chkFullscreen" text="%settings.fullscreen" alignment="CENTER" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets right="7"/>
</BorderPane.margin>
@ -101,11 +101,14 @@
<Tooltip text="%advancedsettings.java_args_default" />
</tooltip>
</JFXTextField>
<JFXTextField labelFloat="true" promptText="%advancedsettings.Minecraft_arguments" styleClass="fit-width" fx:id="txtGameArgs" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.java_permanent_generation_space" styleClass="fit-width" fx:id="txtMetaspace" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.wrapper_launcher" styleClass="fit-width" fx:id="txtWrapper" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.precall_command" styleClass="fit-width" fx:id="txtPrecallingCommand" />
<JFXTextField labelFloat="true" promptText="%advancedsettings.server_ip" styleClass="fit-width" fx:id="txtServerIP" />
<fx:define>
<Insets fx:id="insets" bottom="8" />
</fx:define>
<JFXTextField labelFloat="true" promptText="%advancedsettings.Minecraft_arguments" styleClass="fit-width" fx:id="txtGameArgs" StackPane.margin="$insets" />
<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_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>

View File

@ -36,8 +36,9 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
private val forgeVersionList = dependencyManager.getVersionList("forge")
private val installer: File = File("forge-installer.jar").absoluteFile
lateinit var remote: RemoteVersion<*>
override val dependents: MutableCollection<Task> = mutableListOf()
override val dependencies: MutableCollection<Task> = mutableListOf()
override val dependents = mutableListOf<Task>()
override val dependencies = mutableListOf<Task>()
override val id = ID
init {
if (!forgeVersionList.loaded)
@ -76,5 +77,7 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
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
*/
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() {
for (library in resolvedVersion.libraries)
if (library.appliesToCurrentEnvironment) {
@ -50,7 +50,7 @@ class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager,
}
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() {
val logging = version.logging?.get(DownloadType.CLIENT) ?: return
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() {
override val dependencies: MutableCollection<Task> = LinkedList()
override val dependencies = LinkedList<Task>()
override fun execute() {
val assetIndexInfo = version.actualAssetIndex
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 assetIndexInfo = version.actualAssetIndex
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 {
if (!assetIndexFile.exists())
@ -93,12 +94,16 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
}
result = res
}
companion object {
val ID = "game_asset_refresh_task"
}
}
class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
private val refreshTask = GameAssetRefreshTask(dependencyManager, version)
override val dependents: Collection<Task> = listOf(refreshTask)
override val dependencies: MutableCollection<Task> = LinkedList()
override val dependents = listOf(refreshTask)
override val dependencies = LinkedList<Task>()
override fun execute() {
val size = refreshTask.result?.size ?: 0
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 liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
override val dependents: MutableCollection<Task> = mutableListOf()
override val dependencies: MutableCollection<Task> = mutableListOf()
override val dependents = mutableListOf<Task>()
override val dependencies = mutableListOf<Task>()
override val id = ID
init {
if (!liteLoaderVersionList.loaded)
@ -70,4 +71,7 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
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 optiFineVersionList = dependencyManager.getVersionList("optifine")
lateinit var remote: RemoteVersion<*>
override val dependents: MutableCollection<Task> = mutableListOf()
override val dependencies: MutableCollection<Task> = mutableListOf()
override val dependents = mutableListOf<Task>()
override val dependencies = mutableListOf<Task>()
override val id = ID
init {
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")
}
}
override fun execute() {
val library = Library(
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)
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) {
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
@ -107,8 +107,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
if (options.minMemory != null && options.minMemory > 0)
res.add("-Xms${options.minMemory}m")
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
res.add("-Dfml.ignorePatchDiscrepancies=true");
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true")
res.add("-Dfml.ignorePatchDiscrepancies=true")
}
// Classpath
@ -161,9 +161,9 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
// Optional Minecraft arguments
if (options.height != null && options.width != null) {
res.add("--height");
res.add("--height")
res.add(options.height.toString())
res.add("--width");
res.add("--width")
res.add(options.width.toString())
}
@ -180,16 +180,16 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
if (options.proxyHost != null && options.proxyHost.isNotBlank() &&
options.proxyPort != null && options.proxyPort.isNotBlank()) {
res.add("--proxyHost");
res.add("--proxyHost")
res.add(options.proxyHost)
res.add("--proxyPort");
res.add("--proxyPort")
res.add(options.proxyPort)
if (options.proxyUser != null && options.proxyUser.isNotBlank() &&
options.proxyPass != null && options.proxyPass.isNotBlank()) {
res.add("--proxyUser");
res.add(options.proxyUser);
res.add("--proxyPass");
res.add(options.proxyPass);
res.add("--proxyUser")
res.add(options.proxyUser)
res.add("--proxyPass")
res.add(options.proxyPass)
}
}
@ -246,6 +246,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
fun launchAsync(): TaskResult<JavaProcess> {
return object : TaskResult<JavaProcess>() {
override val id = LAUNCH_ASYNC_ID
override fun execute() {
result = launch()
}
@ -259,11 +260,11 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
throw IOException("Script file: $scriptFile cannot be created.")
scriptFile.bufferedWriter().use { writer ->
if (isWindows) {
writer.write("@echo off");
writer.write("@echo off")
writer.newLine()
writer.write("set APPDATA=" + options.gameDir.parent);
writer.write("set APPDATA=" + options.gameDir.parent)
writer.newLine()
writer.write("cd /D %APPDATA%");
writer.write("cd /D %APPDATA%")
writer.newLine()
}
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 = "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>() {
override val scheduler: Scheduler = Scheduler.IO_THREAD
override val id = ID
override fun execute() {
var exception: IOException? = null
@ -63,4 +64,8 @@ class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Ch
if (exception != null)
throw exception
}
companion object {
const val ID = "http_get"
}
}

View File

@ -17,8 +17,10 @@
*/
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() {
runnable()
runnable(variables!!)
}
}

View File

@ -49,6 +49,8 @@ abstract class Task {
var title: String = this.javaClass.toString()
var variables: AutoTypingMap<String>? = null
/**
* @see Thread.isInterrupted
* @throws InterruptedException if current thread is interrupted
@ -119,12 +121,13 @@ abstract class Task {
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 {
return title
}
}
fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit): Task = SimpleTask(closure, scheduler)
fun <V> task(callable: Callable<V>): TaskResult<V> = TaskCallable(callable)
fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit): Task = SimpleTask(closure, scheduler)
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
import org.jackhuang.hmcl.util.AutoTypingMap
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() {
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
import org.jackhuang.hmcl.util.AutoTypingMap
import org.jackhuang.hmcl.util.LOG
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicBoolean
@ -30,6 +31,7 @@ class TaskExecutor() {
var canceled = false
private set
val totTask = AtomicInteger(0)
val variables = AutoTypingMap<String>(mutableMapOf())
private val taskQueue = ConcurrentLinkedQueue<Task>()
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
@ -119,7 +121,10 @@ class TaskExecutor() {
if (!doDependentsSucceeded && t.reliant || canceled)
throw SilentException()
t.variables = variables
t.execute()
if (t is TaskResult<*>)
variables[t.id] = t.result
flag = true
if (!t.hidden)
LOG.finer("Task finished: ${t.title}")
@ -142,6 +147,8 @@ class TaskExecutor() {
t.onDone(TaskEvent(source = this, task = t, failed = true))
taskListener?.onFailed(t)
}
} finally {
t.variables = null
}
return flag
}

View File

@ -19,4 +19,9 @@ package org.jackhuang.hmcl.task
abstract class TaskResult<V> : Task() {
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.IOException
import java.io.Serializable
import java.util.*
import java.util.regex.Pattern
data class JavaVersion internal constructor(
@SerializedName("location")
val binary: File,
val version: Int,
val longVersion: String,
val platform: Platform) : Serializable
{
val version = parseVersion(longVersion)
companion object {
private val regex = Pattern.compile("java version \"(?<version>[1-9]*\\.[1-9]*\\.[0-9]*(.*?))\"")
val JAVAS: Map<String, JavaVersion>
val UNKNOWN: Int = -1
val JAVA_5: Int = 50
val JAVA_6: Int = 60
@ -54,12 +59,15 @@ data class JavaVersion internal constructor(
@Throws(IOException::class)
fun fromExecutable(file: File): JavaVersion {
var actualFile = file
var platform = Platform.BIT_32
var version: String? = null
if (actualFile.nameWithoutExtension == "javaw") // javaw will not output version information
actualFile = actualFile.absoluteFile.parentFile.resolve("java")
try {
val process = ProcessBuilder(file.absolutePath, "-version").start()
val process = ProcessBuilder(actualFile.absolutePath, "-version").start()
process.waitFor()
process.inputStream.bufferedReader().forEachLine { line ->
process.errorStream.bufferedReader().forEachLine { line ->
val m = regex.matcher(line)
if (m.find())
version = m.group("version")
@ -74,10 +82,26 @@ data class JavaVersion internal constructor(
val parsedVersion = parseVersion(thisVersion)
if (parsedVersion == UNKNOWN)
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 javaw = path.resolve("javaw.exe")
if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile)
@ -86,11 +110,80 @@ data class JavaVersion internal constructor(
return path.resolve("java")
}
fun fromCurrentEnvironment(): JavaVersion {
return JavaVersion(
binary = getJavaFile(File(System.getProperty("java.home"))),
version = parseVersion(System.getProperty("java.version")),
platform = Platform.PLATFORM)
private val currentJava: JavaVersion = JavaVersion(
binary = getJavaFile(File(System.getProperty("java.home"))),
longVersion = System.getProperty("java.version"),
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
import javafx.beans.property.*
import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue
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)
}
protected fun superSet(newValue: String) {
super.set(newValue)
}
override fun bind(newObservable: ObservableValue<out String>) {
super.get()
super.bind(newObservable)
@ -41,30 +38,17 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String
super.unbind()
}
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
}
open class ImmediateNullableStringProperty(bean: Any, name: String, initialValue: String?): SimpleStringProperty(bean, name, initialValue) {
override fun set(newValue: String?) {
super.get()
super.set(newValue)
private var myListener: (String) -> Unit = {}
private val changeListener = ChangeListener<String> { _, _, newValue ->
myListener(newValue)
}
protected fun superSet(newValue: String?) {
super.set(newValue)
fun setChangedListener(listener: (String) -> Unit) {
myListener = listener
}
override fun bind(newObservable: ObservableValue<out String?>) {
super.get()
super.bind(newObservable)
}
override fun unbind() {
super.get()
super.unbind()
init {
addListener(changeListener)
}
public override fun fireValueChangedEvent() {
@ -79,10 +63,6 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole
super.set(newValue)
}
protected fun superSet(newValue: Boolean) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Boolean>?) {
super.get()
super.bind(rawObservable)
@ -93,6 +73,19 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole
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() {
super.fireValueChangedEvent()
}
@ -105,10 +98,6 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int):
super.set(newValue)
}
protected fun superSet(newValue: Int) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Number>) {
super.get()
super.bind(rawObservable)
@ -119,6 +108,19 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int):
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() {
super.fireValueChangedEvent()
}
@ -131,10 +133,6 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double
super.set(newValue)
}
protected fun superSet(newValue: Double) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Number>) {
super.get()
super.bind(rawObservable)
@ -145,6 +143,19 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double
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() {
super.fireValueChangedEvent()
}
@ -167,6 +178,19 @@ open class ImmediateObjectProperty<T>(bean: Any, name: String, initialValue: T):
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() {
super.fireValueChangedEvent()
}