package com.narrowtux.fmm.gui;
import com.narrowtux.fmm.io.FileDeleter;
import com.narrowtux.fmm.io.tasks.ModpackInstaller;
import com.narrowtux.fmm.io.tasks.ModsInstaller;
import com.narrowtux.fmm.io.tasks.TaskService;
import com.narrowtux.fmm.model.Datastore;
import com.narrowtux.fmm.model.Mod;
import com.narrowtux.fmm.model.ModReference;
import com.narrowtux.fmm.model.Modpack;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.SetChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.scene.control.cell.TextFieldTreeTableCell;
import javafx.scene.layout.AnchorPane;
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.converter.DefaultStringConverter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Controller for the modpacks tab
*/
public class ModpackTabController extends TabController {
@FXML
public AnchorPane root;
@FXML
TreeTableView<Object> modpacks;
@FXML
TreeTableColumn<Object, String> nameColumn;
@FXML
TreeTableColumn<Object, String> versionColumn;
@FXML
TreeTableColumn<Object, Boolean> enabledColumn;
@FXML
ProgressBar progress;
@FXML
Button playButton;
Tab tab = new Tab("Modpacks");
ContextMenu contextMenu = new ContextMenu();
private final Datastore store;
private final ConsoleWindow consoleWindow;
TreeItem<Object> treeRoot = new TreeItem<>("root");
public ModpackTabController(ConsoleWindow consoleWindow) {
this.consoleWindow = consoleWindow;
store = Datastore.getInstance();
}
@Override
public Tab getTab() {
return tab;
}
@Override
public void init() {
tab.setContent(root);
for (Modpack modpack : store.getModpacks()) {
treeRoot.getChildren().add(getModpackTreeItem(modpack));
}
store.getModpacks().addListener((SetChangeListener<Modpack>) change -> {
if (change.wasAdded()) {
Platform.runLater(() -> treeRoot.getChildren().add(getModpackTreeItem(change.getElementAdded())));
} else if (change.wasRemoved()) {
Platform.runLater(() ->
treeRoot.getChildren().stream()
.filter((TreeItem item2) -> item2.getValue() == change.getElementRemoved())
.findAny()
.ifPresent(item3 -> treeRoot.getChildren().remove(item3)));
}
});
progress.setVisible(false);
playButton.setDisable(true);
modpacks.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> {
if (nv == null) {
playButton.setDisable(true);
return;
}
playButton.setDisable(!(nv.getValue() instanceof Mod || nv.getValue() instanceof Modpack));
});
modpacks.setShowRoot(false);
modpacks.setRoot(treeRoot);
nameColumn.setCellValueFactory(features -> {
Object value = features.getValue().getValue();
if (value instanceof Modpack) {
return ((Modpack) value).nameProperty();
}
if (value instanceof ModReference) {
return ((ModReference) value).getMod().nameProperty();
}
return null;
});
nameColumn.setCellFactory(new Callback<TreeTableColumn<Object, String>, TreeTableCell<Object, String>>() {
@Override
public TreeTableCell<Object, String> call(TreeTableColumn<Object, String> objectStringTreeTableColumn) {
return new TextFieldTreeTableCell<Object, String>(new DefaultStringConverter()) {
@Override
public void startEdit() {
super.startEdit();
TreeItem item = modpacks.getTreeItem(getIndex());
if (item == null || !(item.getValue() instanceof Modpack)) {
cancelEdit();
}
}
};
}
});
nameColumn.setEditable(true);
versionColumn.setCellValueFactory(features -> {
Object value = features.getValue().getValue();
if (value instanceof ModReference) {
return ((ModReference) value).getMod().versionProperty().asString();
}
return null;
});
enabledColumn.setCellValueFactory(features -> {
Object value = features.getValue().getValue();
if (value instanceof ModReference) {
return ((ModReference) value).enabledProperty();
}
SimpleBooleanProperty simpleBooleanProperty = new SimpleBooleanProperty(false);
simpleBooleanProperty.addListener(((observableValue, ov, nv) -> {
if (nv) {
simpleBooleanProperty.set(false);
}
}));
return simpleBooleanProperty;
});
enabledColumn.setCellFactory(new Callback<TreeTableColumn<Object, Boolean>, TreeTableCell<Object, Boolean>>() {
@Override
public TreeTableCell<Object, Boolean> call(TreeTableColumn<Object, Boolean> objectBooleanTreeTableColumn) {
return new CheckBoxTreeTableCell<Object, Boolean>() {
@Override
public void updateItem(Boolean value, boolean empty) {
super.updateItem(value, empty);
if (empty) {
setText(null);
setGraphic(null);
}
TreeItem item = modpacks.getTreeItem(getIndex());
if (item == null || !(item.getValue() instanceof ModReference)) {
setGraphic(null);
}
}
};
}
});
MenuItem remove = new MenuItem("Remove");
remove.setOnAction(this::onRemoveAction);
contextMenu.getItems().add(remove);
modpacks.setContextMenu(contextMenu);
}
public void onRemoveAction(ActionEvent event) {
for (TreeItem selected : modpacks.getSelectionModel().getSelectedItems()) {
if (selected.getValue() instanceof Modpack) {
Modpack pack = (Modpack) selected.getValue();
store.getModpacks().remove(pack);
FileDeleter deleter = new FileDeleter();
try {
Files.walkFileTree(pack.getPath(), deleter);
} catch (IOException e) {
e.printStackTrace();
}
} else if (selected.getValue() instanceof ModReference) {
ModReference mod = (ModReference) selected.getValue();
mod.getModpack().getMods().remove(mod);
}
}
}
@FXML
public void onModpackAddAction(ActionEvent event) {
String name = "new-modpack";
Path path = store.getFMMDir().resolve(name);
try {
Files.createDirectory(path);
Modpack pack = new Modpack(name, path);
store.getModpacks().add(pack);
Timeline t = new Timeline(new KeyFrame(Duration.millis(100), ae -> {
for (TreeItem item : modpacks.getRoot().getChildren()) {
if (item.getValue() == pack) {
modpacks.getSelectionModel().select(item);
modpacks.edit(modpacks.getSelectionModel().getSelectedIndex(), nameColumn);
return;
}
}
}));
t.play();
// TODO figure out why committing the edit doesn't update the value
} catch (IOException e) {
e.printStackTrace();
}
}
@FXML
public void onPlayPressed(ActionEvent event) throws IOException {
int index = modpacks.getSelectionModel().getFocusedIndex();
if (index < 0) {
return;
}
TreeItem item = modpacks.getSelectionModel().getModelItem(index);
Object o = item.getValue();
Modpack pack = null;
if (o instanceof ModReference) {
pack = ((ModReference) o).getModpack();
} else if (o instanceof Modpack) {
pack = ((Modpack) o);
}
if (pack != null) {
List<Set<Mod>> solutions = pack.resolveDependencies();
Set<Mod> solution = null;
if (solutions.size() > 1) {
DependencyResolveDialog dialog = null;
try {
dialog = new DependencyResolveDialog(solutions);
} catch (Exception e) {
e.printStackTrace();
}
Optional<Set<Mod>> sol = dialog.showAndWait();
if (sol.isPresent()) {
solution = sol.get();
if (solution == DependencyResolveDialog.CANCELLED) {
return;
}
} else {
return;
}
} else if (!solutions.isEmpty()) {
solution = solutions.get(0);
} else {
Alert alert = new Alert(Alert.AlertType.ERROR,
"Could not launch modpack because one or more mods are missing.",
ButtonType.CLOSE);
alert.showAndWait();
return;
}
ModsInstaller installer = new ModpackInstaller(pack);
progress.setVisible(true);
playButton.setDisable(true);
progress.progressProperty().bind(installer.progressProperty());
installer.setOnDone(() -> {
progress.setVisible(false);
playButton.setDisable(false);
});
installer.setOnError((e) -> {
progress.setVisible(false);
playButton.setDisable(false);
});
installer.valueProperty().addListener((obj, ov, nv) -> {
if (nv != null) {
consoleWindow.getProcesses().add(nv);
}
});
TaskService.getInstance().submit(installer);
}
}
private TreeItem<Object> getModpackTreeItem(Modpack modpack) {
TreeItem<Object> item = new TreeItem<>(modpack);
for (ModReference mod : modpack.getMods()) {
item.getChildren().add(getModTreeItem(mod));
}
modpack.getMods().addListener((SetChangeListener<ModReference>) change -> {
if (change.wasAdded()) {
Platform.runLater(() -> item.getChildren().add(getModTreeItem(change.getElementAdded())));
} else if (change.wasRemoved()) {
Platform.runLater(() -> {
item.getChildren().stream().filter(item2 -> item2.getValue() == change.getElementRemoved()).findAny().ifPresent(item3 -> item.getChildren().remove(item3));
});
}
});
return item;
}
private TreeItem<Object> getModTreeItem(ModReference mod) {
TreeItem<Object> item = new TreeItem<>(mod);
return item;
}
}