fix #2008: Replace JFXTreeTableView with TableView

close #2068
This commit is contained in:
Glavo
2023-02-05 17:09:35 +08:00
committed by Haowei Wen
parent d54d9ae5cb
commit 1a6dffab74
5 changed files with 37 additions and 197 deletions

View File

@@ -1,135 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXCheckBox;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeTableCell;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class JFXCheckBoxTreeTableCell<S, T> extends TreeTableCell<S, T> {
private final CheckBox checkBox;
private boolean showLabel;
private ObservableValue<Boolean> booleanProperty;
public JFXCheckBoxTreeTableCell() {
this(null, null);
}
public JFXCheckBoxTreeTableCell(
final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) {
this(getSelectedProperty, null);
}
public JFXCheckBoxTreeTableCell(
final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty,
final StringConverter<T> converter) {
this.getStyleClass().add("check-box-tree-table-cell");
this.checkBox = new JFXCheckBox();
setGraphic(null);
setSelectedStateCallback(getSelectedProperty);
setConverter(converter);
}
private ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<StringConverter<T>>(this, "converter") {
protected void invalidated() {
updateShowLabel();
}
};
public final ObjectProperty<StringConverter<T>> converterProperty() {
return converter;
}
public final void setConverter(StringConverter<T> value) {
converterProperty().set(value);
}
public final StringConverter<T> getConverter() {
return converterProperty().get();
}
private ObjectProperty<Callback<Integer, ObservableValue<Boolean>>>
selectedStateCallback =
new SimpleObjectProperty<Callback<Integer, ObservableValue<Boolean>>>(
this, "selectedStateCallback");
public final ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> selectedStateCallbackProperty() {
return selectedStateCallback;
}
public final void setSelectedStateCallback(Callback<Integer, ObservableValue<Boolean>> value) {
selectedStateCallbackProperty().set(value);
}
public final Callback<Integer, ObservableValue<Boolean>> getSelectedStateCallback() {
return selectedStateCallbackProperty().get();
}
@SuppressWarnings("unchecked")
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
StringConverter<T> c = getConverter();
if (showLabel) {
setText(c.toString(item));
}
setGraphic(checkBox);
if (booleanProperty instanceof BooleanProperty) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty) booleanProperty);
}
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
booleanProperty = (ObservableValue<Boolean>) obsValue;
checkBox.selectedProperty().bindBidirectional((BooleanProperty) booleanProperty);
}
checkBox.disableProperty().bind(Bindings.not(
getTreeTableView().editableProperty().and(
getTableColumn().editableProperty()).and(
editableProperty())
));
}
}
private void updateShowLabel() {
this.showLabel = converter != null;
this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER);
}
private ObservableValue<?> getSelectedProperty() {
return getSelectedStateCallback() != null ?
getSelectedStateCallback().call(getIndex()) :
getTableColumn().getCellObservableValue(getIndex());
}
}

View File

@@ -179,7 +179,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
} }
public void checkUpdates() { public void checkUpdates() {
Controllers.taskDialog(Task Runnable action = () -> Controllers.taskDialog(Task
.composeAsync(() -> { .composeAsync(() -> {
Optional<String> gameVersion = profile.getRepository().getGameVersion(versionId); Optional<String> gameVersion = profile.getRepository().getGameVersion(versionId);
if (gameVersion.isPresent()) { if (gameVersion.isPresent()) {
@@ -193,11 +193,20 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
} else if (result.isEmpty()) { } else if (result.isEmpty()) {
Controllers.dialog(i18n("mods.check_updates.empty")); Controllers.dialog(i18n("mods.check_updates.empty"));
} else { } else {
Controllers.navigate(new ModUpdatesPage(modManager, result, profile.getRepository().isModpack(versionId))); Controllers.navigate(new ModUpdatesPage(modManager, result));
} }
}) })
.withStagesHint(Collections.singletonList("mods.check_updates")), .withStagesHint(Collections.singletonList("mods.check_updates")),
i18n("update.checking"), TaskCancellationAction.NORMAL); i18n("update.checking"), TaskCancellationAction.NORMAL);
if (profile.getRepository().isModpack(versionId)) {
Controllers.confirm(
i18n("mods.update_modpack_mod.warning"), null,
MessageDialogPane.MessageType.WARNING,
action, null);
} else {
action.run();
}
} }
public void download() { public void download() {

View File

@@ -17,20 +17,19 @@
*/ */
package org.jackhuang.hmcl.ui.versions; package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.*; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; import com.jfoenix.controls.JFXListView;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.*; import javafx.beans.property.*;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Label; import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.mutable.MutableObject;
import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.mod.ModManager;
@@ -41,8 +40,10 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.MDListCell;
import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.TaskCancellationAction;
@@ -67,38 +68,36 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
private final ObservableList<ModUpdateObject> objects; private final ObservableList<ModUpdateObject> objects;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ModUpdatesPage(ModManager modManager, List<LocalModFile.ModUpdate> updates, Boolean isModpack) { public ModUpdatesPage(ModManager modManager, List<LocalModFile.ModUpdate> updates) {
this.modManager = modManager; this.modManager = modManager;
getStyleClass().add("gray-background"); getStyleClass().add("gray-background");
JFXTreeTableColumn<ModUpdateObject, Boolean> enabledColumn = new JFXTreeTableColumn<>(); TableColumn<ModUpdateObject, Boolean> enabledColumn = new TableColumn<>();
enabledColumn.setCellFactory(column -> new JFXCheckBoxTreeTableCell<>()); enabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(enabledColumn));
setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty); setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty);
enabledColumn.setEditable(true); enabledColumn.setEditable(true);
enabledColumn.setMaxWidth(40); enabledColumn.setMaxWidth(40);
enabledColumn.setMinWidth(40); enabledColumn.setMinWidth(40);
JFXTreeTableColumn<ModUpdateObject, String> fileNameColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.file")); TableColumn<ModUpdateObject, String> fileNameColumn = new TableColumn<>(i18n("mods.check_updates.file"));
fileNameColumn.setPrefWidth(200);
setupCellValueFactory(fileNameColumn, ModUpdateObject::fileNameProperty); setupCellValueFactory(fileNameColumn, ModUpdateObject::fileNameProperty);
JFXTreeTableColumn<ModUpdateObject, String> currentVersionColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.current_version")); TableColumn<ModUpdateObject, String> currentVersionColumn = new TableColumn<>(i18n("mods.check_updates.current_version"));
currentVersionColumn.setPrefWidth(200);
setupCellValueFactory(currentVersionColumn, ModUpdateObject::currentVersionProperty); setupCellValueFactory(currentVersionColumn, ModUpdateObject::currentVersionProperty);
JFXTreeTableColumn<ModUpdateObject, String> targetVersionColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.target_version")); TableColumn<ModUpdateObject, String> targetVersionColumn = new TableColumn<>(i18n("mods.check_updates.target_version"));
targetVersionColumn.setPrefWidth(200);
setupCellValueFactory(targetVersionColumn, ModUpdateObject::targetVersionProperty); setupCellValueFactory(targetVersionColumn, ModUpdateObject::targetVersionProperty);
JFXTreeTableColumn<ModUpdateObject, String> sourceColumn = new JFXTreeTableColumn<>(i18n("mods.check_updates.source")); TableColumn<ModUpdateObject, String> sourceColumn = new TableColumn<>(i18n("mods.check_updates.source"));
setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty); setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty);
objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList())); objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList()));
RecursiveTreeItem<ModUpdateObject> root = new RecursiveTreeItem<>( TableView<ModUpdateObject> table = new TableView<>(objects);
objects,
RecursiveTreeObject::getChildren);
JFXTreeTableView<ModUpdateObject> table = new JFXTreeTableView<>(root);
table.setShowRoot(false);
table.setEditable(true); table.setEditable(true);
table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, sourceColumn); table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, sourceColumn);
@@ -111,9 +110,6 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
JFXButton nextButton = new JFXButton(i18n("mods.check_updates.update")); JFXButton nextButton = new JFXButton(i18n("mods.check_updates.update"));
nextButton.getStyleClass().add("jfx-button-raised"); nextButton.getStyleClass().add("jfx-button-raised");
nextButton.setButtonType(JFXButton.ButtonType.RAISED); nextButton.setButtonType(JFXButton.ButtonType.RAISED);
nextButton.setOnAction(e -> {
if (isModpack) updateModpackModWarningDialog();
});
JFXButton cancelButton = new JFXButton(i18n("button.cancel")); JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
cancelButton.getStyleClass().add("jfx-button-raised"); cancelButton.getStyleClass().add("jfx-button-raised");
@@ -125,38 +121,8 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
setBottom(actions); setBottom(actions);
} }
private <T> void setupCellValueFactory(JFXTreeTableColumn<ModUpdateObject, T> column, Function<ModUpdateObject, ObservableValue<T>> mapper) { private <T> void setupCellValueFactory(TableColumn<ModUpdateObject, T> column, Function<ModUpdateObject, ObservableValue<T>> mapper) {
column.setCellValueFactory(param -> { column.setCellValueFactory(param -> mapper.apply(param.getValue()));
if (column.validateValue(param))
return mapper.apply(param.getValue().getValue());
else
return column.getComputedValue(param);
});
}
private void updateModpackModWarningDialog() {
JFXDialogLayout warningPane = new JFXDialogLayout();
warningPane.setHeading(new Label(i18n("message.warning"), SVG.alert(new ObjectBinding<Paint>() {
@Override
protected Paint computeValue() {
return Color.ORANGERED;
}
}, -1, -1)));
warningPane.setBody(new Label(i18n("mods.update_modpack_mod.warning")));
JFXButton yesButton = new JFXButton(i18n("button.yes"));
yesButton.getStyleClass().add("dialog-accept");
yesButton.setOnAction(event -> {
warningPane.fireEvent(new DialogCloseEvent());
updateMods();
});
JFXButton noButton = new JFXButton(i18n("button.cancel"));
noButton.getStyleClass().add("dialog-cancel");
noButton.setOnAction(event -> {
// Do nothing.
warningPane.fireEvent(new DialogCloseEvent());
});
warningPane.setActions(yesButton, noButton);
Controllers.dialog(warningPane);
} }
private void updateMods() { private void updateMods() {
@@ -189,7 +155,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
return state; return state;
} }
public static class ModUpdateCell extends MDListCell<LocalModFile.ModUpdate> { public static final class ModUpdateCell extends MDListCell<LocalModFile.ModUpdate> {
TwoLineListItem content = new TwoLineListItem(); TwoLineListItem content = new TwoLineListItem();
public ModUpdateCell(JFXListView<LocalModFile.ModUpdate> listView, MutableObject<Object> lastCell) { public ModUpdateCell(JFXListView<LocalModFile.ModUpdate> listView, MutableObject<Object> lastCell) {
@@ -214,7 +180,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
} }
} }
private static class ModUpdateObject extends RecursiveTreeObject<ModUpdateObject> { private static final class ModUpdateObject {
final LocalModFile.ModUpdate data; final LocalModFile.ModUpdate data;
final BooleanProperty enabled = new SimpleBooleanProperty(); final BooleanProperty enabled = new SimpleBooleanProperty();
final StringProperty fileName = new SimpleStringProperty(); final StringProperty fileName = new SimpleStringProperty();

View File

@@ -705,7 +705,7 @@ mods.name=名稱
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。 mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
mods.restore=回退 mods.restore=回退
mods.url=官方頁面 mods.url=官方頁面
mods.update_modpack_mod.warning=更新整合包中的Mod可能導致合包損壞,使整合包無法正常啟動。該操作不可逆,確定要更新嗎? mods.update_modpack_mod.warning=更新模組包中的 Mod 可能導致合包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎?
multiplayer=多人聯機 multiplayer=多人聯機
multiplayer.agreement.prompt=多人聯機功能由 速聚 提供。使用前,你需要先同意多人聯機服務提供方 速聚 的用戶協議與免責聲明。\n你需要了解HMCL 僅為 速聚 提供多人聯機服務入口,使用中遇到的任何問題由 速聚 負責處理。您在使用多人聯機服務過程中所遇到的任何問題與糾紛(包括其付費業務)均與 HMCL 無關,應與 速聚 協商解決。 multiplayer.agreement.prompt=多人聯機功能由 速聚 提供。使用前,你需要先同意多人聯機服務提供方 速聚 的用戶協議與免責聲明。\n你需要了解HMCL 僅為 速聚 提供多人聯機服務入口,使用中遇到的任何問題由 速聚 負責處理。您在使用多人聯機服務過程中所遇到的任何問題與糾紛(包括其付費業務)均與 HMCL 無關,應與 速聚 協商解決。