Merge pull request #385 from yushijinhun/relative-path
添加设置项:若可能,游戏目录使用相对路径
This commit is contained in:
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.setting;
|
|||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.game.HMCLDependencyManager;
|
import org.jackhuang.hmcl.game.HMCLDependencyManager;
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
import org.jackhuang.hmcl.mod.ModManager;
|
import org.jackhuang.hmcl.mod.ModManager;
|
||||||
@@ -83,8 +86,18 @@ public final class Profile {
|
|||||||
nameProperty.set(name);
|
nameProperty.set(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Profile() {
|
private BooleanProperty useRelativePathProperty = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||||
this("Default");
|
|
||||||
|
public BooleanProperty useRelativePathProperty() {
|
||||||
|
return useRelativePathProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseRelativePath() {
|
||||||
|
return useRelativePathProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseRelativePath(boolean useRelativePath) {
|
||||||
|
useRelativePathProperty.set(useRelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Profile(String name) {
|
public Profile(String name) {
|
||||||
@@ -153,6 +166,7 @@ public final class Profile {
|
|||||||
return new ToStringBuilder(this)
|
return new ToStringBuilder(this)
|
||||||
.append("gameDir", getGameDir())
|
.append("gameDir", getGameDir())
|
||||||
.append("name", getName())
|
.append("name", getName())
|
||||||
|
.append("useRelativePath", isUseRelativePath())
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +174,7 @@ public final class Profile {
|
|||||||
nameProperty.addListener(listener);
|
nameProperty.addListener(listener);
|
||||||
globalProperty.addListener(listener);
|
globalProperty.addListener(listener);
|
||||||
gameDirProperty.addListener(listener);
|
gameDirProperty.addListener(listener);
|
||||||
|
useRelativePathProperty.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||||
@@ -176,6 +191,7 @@ public final class Profile {
|
|||||||
JsonObject jsonObject = new JsonObject();
|
JsonObject jsonObject = new JsonObject();
|
||||||
jsonObject.add("global", context.serialize(src.getGlobal()));
|
jsonObject.add("global", context.serialize(src.getGlobal()));
|
||||||
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
|
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
|
||||||
|
jsonObject.addProperty("useRelativePath", src.isUseRelativePath());
|
||||||
|
|
||||||
return jsonObject;
|
return jsonObject;
|
||||||
}
|
}
|
||||||
@@ -188,6 +204,8 @@ public final class Profile {
|
|||||||
|
|
||||||
Profile profile = new Profile("Default", new File(gameDir));
|
Profile profile = new Profile("Default", new File(gameDir));
|
||||||
profile.setGlobal(context.deserialize(obj.get("global"), VersionSetting.class));
|
profile.setGlobal(context.deserialize(obj.get("global"), VersionSetting.class));
|
||||||
|
|
||||||
|
profile.setUseRelativePath(Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -512,8 +512,12 @@ public class Settings {
|
|||||||
|
|
||||||
private void checkProfileMap() {
|
private void checkProfileMap() {
|
||||||
if (getProfileMap().isEmpty()) {
|
if (getProfileMap().isEmpty()) {
|
||||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, new Profile(Profiles.DEFAULT_PROFILE));
|
Profile current = new Profile(Profiles.DEFAULT_PROFILE);
|
||||||
getProfileMap().put(Profiles.HOME_PROFILE, new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
|
current.setUseRelativePath(true);
|
||||||
|
getProfileMap().put(Profiles.DEFAULT_PROFILE, current);
|
||||||
|
|
||||||
|
Profile home = new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY);
|
||||||
|
getProfileMap().put(Profiles.HOME_PROFILE, home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.ui;
|
package org.jackhuang.hmcl.ui;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXCheckBox;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
@@ -40,12 +41,11 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
|||||||
private final StringProperty location;
|
private final StringProperty location;
|
||||||
private final Profile profile;
|
private final Profile profile;
|
||||||
|
|
||||||
@FXML
|
@FXML private JFXTextField txtProfileName;
|
||||||
private JFXTextField txtProfileName;
|
@FXML private FileItem gameDir;
|
||||||
@FXML
|
|
||||||
private FileItem gameDir;
|
|
||||||
@FXML private JFXButton btnSave;
|
@FXML private JFXButton btnSave;
|
||||||
@FXML private JFXButton btnDelete;
|
@FXML private JFXButton btnDelete;
|
||||||
|
@FXML private JFXCheckBox toggleUseRelativePath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param profile null if creating a new profile.
|
* @param profile null if creating a new profile.
|
||||||
@@ -65,13 +65,16 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
|||||||
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
||||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||||
});
|
});
|
||||||
gameDir.setProperty(location);
|
gameDir.pathProperty().bindBidirectional(location);
|
||||||
FXUtils.onChangeAndOperate(location, it -> {
|
FXUtils.onChangeAndOperate(location, it -> {
|
||||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||||
});
|
});
|
||||||
|
gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty());
|
||||||
if (profile == null)
|
if (profile == null) {
|
||||||
btnDelete.setVisible(false);
|
btnDelete.setVisible(false);
|
||||||
|
} else {
|
||||||
|
toggleUseRelativePath.setSelected(profile.isUseRelativePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -86,13 +89,17 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
|||||||
private void onSave() {
|
private void onSave() {
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
profile.setName(txtProfileName.getText());
|
profile.setName(txtProfileName.getText());
|
||||||
if (StringUtils.isNotBlank(getLocation()))
|
profile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||||
|
if (StringUtils.isNotBlank(getLocation())) {
|
||||||
profile.setGameDir(new File(getLocation()));
|
profile.setGameDir(new File(getLocation()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (StringUtils.isBlank(getLocation())) {
|
if (StringUtils.isBlank(getLocation())) {
|
||||||
gameDir.onExplore();
|
gameDir.onExplore();
|
||||||
}
|
}
|
||||||
Settings.INSTANCE.putProfile(new Profile(txtProfileName.getText(), new File(getLocation())));
|
Profile newProfile = new Profile(txtProfileName.getText(), new File(getLocation()));
|
||||||
|
newProfile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||||
|
Settings.INSTANCE.putProfile(newProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings.INSTANCE.onProfileLoading();
|
Settings.INSTANCE.onProfileLoading();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
|
|||||||
import com.jfoenix.controls.*;
|
import com.jfoenix.controls.*;
|
||||||
import com.jfoenix.effects.JFXDepthManager;
|
import com.jfoenix.effects.JFXDepthManager;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
@@ -178,7 +179,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
|||||||
chkProxyAuthentication.selectedProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setHasProxyAuth(newValue));
|
chkProxyAuthentication.selectedProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setHasProxyAuth(newValue));
|
||||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||||
|
|
||||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
|
fileCommonLocation.pathProperty().bindBidirectional(Settings.INSTANCE.commonPathProperty());
|
||||||
|
|
||||||
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
|
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
|
||||||
checkUpdate();
|
checkUpdate();
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
package org.jackhuang.hmcl.ui.construct;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import javafx.beans.property.Property;
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@@ -31,24 +33,30 @@ import org.jackhuang.hmcl.setting.Theme;
|
|||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
public class FileItem extends BorderPane {
|
public class FileItem extends BorderPane {
|
||||||
private Property<String> property;
|
private final Label lblPath = new Label();
|
||||||
private final Label x = new Label();
|
|
||||||
|
|
||||||
private final SimpleStringProperty name = new SimpleStringProperty(this, "name");
|
private final SimpleStringProperty name = new SimpleStringProperty(this, "name");
|
||||||
private final SimpleStringProperty title = new SimpleStringProperty(this, "title");
|
private final SimpleStringProperty title = new SimpleStringProperty(this, "title");
|
||||||
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||||
|
private final SimpleStringProperty path = new SimpleStringProperty(this, "path");
|
||||||
|
private final SimpleBooleanProperty convertToRelativePath = new SimpleBooleanProperty(this, "convertToRelativePath");
|
||||||
|
|
||||||
public FileItem() {
|
public FileItem() {
|
||||||
VBox left = new VBox();
|
VBox left = new VBox();
|
||||||
Label name = new Label();
|
Label name = new Label();
|
||||||
name.textProperty().bind(nameProperty());
|
name.textProperty().bind(nameProperty());
|
||||||
x.getStyleClass().addAll("subtitle-label");
|
lblPath.getStyleClass().addAll("subtitle-label");
|
||||||
left.getChildren().addAll(name, x);
|
lblPath.textProperty().bind(path);
|
||||||
|
left.getChildren().addAll(name, lblPath);
|
||||||
setLeft(left);
|
setLeft(left);
|
||||||
|
|
||||||
JFXButton right = new JFXButton();
|
JFXButton right = new JFXButton();
|
||||||
@@ -61,12 +69,29 @@ public class FileItem extends BorderPane {
|
|||||||
Tooltip tip = new Tooltip();
|
Tooltip tip = new Tooltip();
|
||||||
tip.textProperty().bind(tooltipProperty());
|
tip.textProperty().bind(tooltipProperty());
|
||||||
Tooltip.install(this, tip);
|
Tooltip.install(this, tip);
|
||||||
|
|
||||||
|
convertToRelativePath.addListener(onInvalidating(() -> path.set(processPath(path.get()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given path to absolute/relative(if possible) path according to {@link #convertToRelativePathProperty()}.
|
||||||
|
*/
|
||||||
|
private String processPath(String path) {
|
||||||
|
Path given = Paths.get(path).toAbsolutePath();
|
||||||
|
if (isConvertToRelativePath()) {
|
||||||
|
try {
|
||||||
|
return Paths.get(".").toAbsolutePath().relativize(given).normalize().toString();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// the given path can't be relativized against current path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return given.normalize().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onExplore() {
|
public void onExplore() {
|
||||||
DirectoryChooser chooser = new DirectoryChooser();
|
DirectoryChooser chooser = new DirectoryChooser();
|
||||||
if (property.getValue() != null) {
|
if (path.get() != null) {
|
||||||
File file = new File(property.getValue());
|
File file = new File(path.get());
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
if (file.isFile())
|
if (file.isFile())
|
||||||
file = file.getAbsoluteFile().getParentFile();
|
file = file.getAbsoluteFile().getParentFile();
|
||||||
@@ -77,14 +102,10 @@ public class FileItem extends BorderPane {
|
|||||||
}
|
}
|
||||||
chooser.titleProperty().bind(titleProperty());
|
chooser.titleProperty().bind(titleProperty());
|
||||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||||
if (selectedDir != null)
|
if (selectedDir != null) {
|
||||||
property.setValue(selectedDir.getAbsolutePath());
|
path.set(processPath(selectedDir.toString()));
|
||||||
chooser.titleProperty().unbind();
|
|
||||||
}
|
}
|
||||||
|
chooser.titleProperty().unbind();
|
||||||
public void setProperty(Property<String> property) {
|
|
||||||
this.property = property;
|
|
||||||
x.textProperty().bind(property);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -122,4 +143,28 @@ public class FileItem extends BorderPane {
|
|||||||
public void setTooltip(String tooltip) {
|
public void setTooltip(String tooltip) {
|
||||||
this.tooltip.set(tooltip);
|
this.tooltip.set(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty pathProperty() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPath(String path) {
|
||||||
|
this.path.set(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConvertToRelativePath() {
|
||||||
|
return convertToRelativePath.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty convertToRelativePathProperty() {
|
||||||
|
return convertToRelativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConvertToRelativePath(boolean convertToRelativePath) {
|
||||||
|
this.convertToRelativePath.set(convertToRelativePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import com.jfoenix.controls.JFXButton?>
|
<?import com.jfoenix.controls.JFXButton?>
|
||||||
|
<?import com.jfoenix.controls.JFXCheckBox?>
|
||||||
<?import com.jfoenix.controls.JFXTextField?>
|
<?import com.jfoenix.controls.JFXTextField?>
|
||||||
<?import com.jfoenix.validation.RequiredFieldValidator?>
|
<?import com.jfoenix.validation.RequiredFieldValidator?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ScrollPane?>
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||||
<?import org.jackhuang.hmcl.ui.construct.FileItem?>
|
<?import org.jackhuang.hmcl.ui.construct.FileItem?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
@@ -32,6 +34,12 @@
|
|||||||
</BorderPane>
|
</BorderPane>
|
||||||
|
|
||||||
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%profile.instance_directory.choose"/>
|
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%profile.instance_directory.choose"/>
|
||||||
|
|
||||||
|
<JFXCheckBox fx:id="toggleUseRelativePath" text="%profile.use_relative_path" StackPane.alignment="CENTER_LEFT">
|
||||||
|
<StackPane.margin>
|
||||||
|
<Insets left="-10"/>
|
||||||
|
</StackPane.margin>
|
||||||
|
</JFXCheckBox>
|
||||||
</ComponentList>
|
</ComponentList>
|
||||||
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ profile.instance_directory.choose=Choose Game Directory
|
|||||||
profile.new=New Config
|
profile.new=New Config
|
||||||
profile.title=Game Directories
|
profile.title=Game Directories
|
||||||
profile.selected=Selected
|
profile.selected=Selected
|
||||||
|
profile.use_relative_path=Use relative path for game directory if possible
|
||||||
|
|
||||||
selector.choose=Choose
|
selector.choose=Choose
|
||||||
selector.choose_file=Select a file
|
selector.choose_file=Select a file
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ profile.instance_directory.choose=選擇遊戲路徑
|
|||||||
profile.new=新建配置
|
profile.new=新建配置
|
||||||
profile.title=遊戲目錄
|
profile.title=遊戲目錄
|
||||||
profile.selected=已選中
|
profile.selected=已選中
|
||||||
|
profile.use_relative_path=若可能,遊戲目錄使用相對路徑
|
||||||
|
|
||||||
selector.choose=選擇
|
selector.choose=選擇
|
||||||
selector.choose_file=選擇文件
|
selector.choose_file=選擇文件
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ profile.instance_directory.choose=选择游戏路径
|
|||||||
profile.new=新建配置
|
profile.new=新建配置
|
||||||
profile.title=游戏目录
|
profile.title=游戏目录
|
||||||
profile.selected=已选中
|
profile.selected=已选中
|
||||||
|
profile.use_relative_path=若可能,游戏目录使用相对路径
|
||||||
|
|
||||||
selector.choose=选择
|
selector.choose=选择
|
||||||
selector.choose_file=选择文件
|
selector.choose_file=选择文件
|
||||||
|
|||||||
Reference in New Issue
Block a user