package com.narrowtux.fmm.gui; import com.narrowtux.fmm.model.Datastore; import com.narrowtux.fmm.model.Mod; import com.narrowtux.fmm.model.ModDependency; import com.narrowtux.fmm.model.ModReference; import com.narrowtux.fmm.model.Modpack; import com.narrowtux.fmm.model.Version; import com.narrowtux.fmm.util.Bindings; import com.narrowtux.fmm.util.ObservableMapValues; import javafx.application.Platform; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanExpression; import javafx.beans.binding.StringExpression; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ListChangeListener; import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; import javafx.collections.SetChangeListener; import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; import javafx.scene.control.Label; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.input.ContextMenuEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.paint.Paint; import javafx.scene.shape.Circle; import javafx.scene.text.Font; import javafx.scene.text.TextAlignment; import java.awt.Desktop; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.function.Consumer; import java.util.stream.Collectors; public class ModsTabController extends TabController { private final Datastore store; @FXML public AnchorPane root; Tab tab = new Tab("Mods"); @FXML TableView<Mod> mods; @FXML TableColumn<Mod, String> modsNameColumn; @FXML TableColumn<Mod, Version> modsVersionColumn; @FXML GridPane modProperties; @FXML ScrollPane scrollPane; @FXML StackPane stackPane; @FXML Label noSelectionLabel; @FXML Label multiSelectionLabel; StackPane graphic = new StackPane(); IntegerProperty unreadMods = new SimpleIntegerProperty(0); private static final int UNREAD_INDICATOR_WIDTH = 14; public ModsTabController() { store = Datastore.getInstance(); } @Override public Tab getTab() { return tab; } @Override public void init() { graphic.setAlignment(Pos.CENTER); //graphic.setMaxSize(UNREAD_INDICATOR_WIDTH, UNREAD_INDICATOR_WIDTH); Circle graphicCircle = new Circle(UNREAD_INDICATOR_WIDTH / 2, Paint.valueOf("red")); Label graphicLabel = new Label(); graphicLabel.setTextFill(Paint.valueOf("white")); graphicLabel.textProperty().bind(unreadMods.asString()); BooleanBinding unreadVisible = unreadMods.greaterThan(0); graphic.visibleProperty().bind(unreadVisible); graphicCircle.visibleProperty().bind(unreadVisible); graphicLabel.visibleProperty().bind(unreadVisible); // graphicLabel.setTextAlignment(TextAlignment.CENTER); // graphicLabel.setAlignment(Pos.CENTER); graphic.getChildren().addAll(graphicCircle, graphicLabel); tab.setGraphic(graphic); graphicLabel.setPrefWidth(Region.USE_COMPUTED_SIZE); //graphicCircle.setLayoutX(graphicLabel.getLayoutX() + graphicLabel.getWidth() / 2); //graphicCircle.setLayoutY(graphicLabel.getLayoutY() + graphicLabel.getHeight() / 2); tab.setOnSelectionChanged(event -> { if (!tab.isSelected()) { store.getMods().values().stream().filter(Mod::getUnread).forEach(mod -> mod.setUnread(false)); unreadMods.set(0); } }); Datastore.getInstance().getMods().addListener((MapChangeListener<Object, Mod>) change -> { int added = 0; unreadMods.set((int) store.getMods().values().stream().filter(Mod::getUnread).count()); }); tab.setContent(root); mods.setItems(new ObservableMapValues<Mod>(store.getMods())); modsNameColumn.setCellValueFactory(features -> { Mod mod = features.getValue(); return Bindings.decision( mod.titleProperty().isNotEmpty().and(mod.titleProperty().isNotNull()), mod.titleProperty(), mod.nameProperty()); }); modsNameColumn.setCellFactory(column -> { return new TableCell<Mod, String>() { @Override protected void updateItem(String s, boolean empty) { super.updateItem(s, empty); if (!empty && s != null ) { try { Mod mod = (Mod) getTableRow().getItem(); if (mod == null) { setGraphic(null); setText(s); return; } Circle circle = new Circle(5, Paint.valueOf("blue")); circle.setCenterY(circle.getRadius() / 2); circle.visibleProperty().bind(mod.unreadProperty()); setGraphic(circle); setText(s); } catch (Exception e) { e.printStackTrace(); } } else { setGraphic(null); setText(null); } if (s != null) { setText(s); } } }; }); modsVersionColumn.setCellValueFactory(features -> features.getValue().versionProperty()); mods.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); ContextMenu menu = new ContextMenu(); Menu add = new Menu("Add to modpack ..."); Consumer<Modpack> onModpackAdded = (pack -> { MenuItem packItem = new MenuItem(pack.getName()); packItem.setOnAction(actionEvent -> { mods.getSelectionModel().getSelectedItems().stream().filter(mod -> !pack.getMods().stream().filter(ref -> ref.getMod().equals(mod)).findAny().isPresent()).forEach(mod -> { pack.getMods().add(new ModReference(mod, pack, true)); }); pack.writeModList(true); }); Platform.runLater(() -> add.getItems().add(packItem)); }); store.getModpacks().forEach(onModpackAdded); store.getModpacks().addListener((SetChangeListener<Modpack>) change -> { if (change.wasAdded()) { onModpackAdded.accept(change.getElementAdded()); } if (change.wasRemoved()) { Modpack removed = change.getElementRemoved(); add.getItems().removeAll(add.getItems().stream().filter(item -> item.getText().equals(removed.getName())).collect(Collectors.toList())); } }); menu.getItems().add(add); MenuItem delete = new MenuItem("Delete"); menu.getItems().add(delete); mods.setContextMenu(menu); setupProperties(); } private void setupProperties() { ReadOnlyObjectProperty<Mod> selection = mods.getSelectionModel().selectedItemProperty(); BooleanBinding isNone = selection.isNull(); noSelectionLabel.visibleProperty().bind(isNone); BooleanBinding isMultiple = Bindings.collectionSize(mods.getSelectionModel().getSelectedCells()).greaterThan(1); multiSelectionLabel.visibleProperty().bind(isMultiple); modProperties.visibleProperty().bind(isNone.not().and(isMultiple.not())); modProperties.prefWidthProperty().bind(scrollPane.widthProperty().subtract(15)); Label titleLabel = new Label(); titleLabel.textProperty().bind(Bindings.selection(selection, mod -> Bindings.decision( mod.titleProperty().isNotEmpty().and(mod.titleProperty().isNotNull()), mod.titleProperty(), mod.nameProperty()))); titleLabel.setFont(new Font(titleLabel.getFont().getName(), 20)); modProperties.addRow(0, titleLabel); GridPane.setColumnSpan(titleLabel, 2); Hyperlink authorLink = new Hyperlink(); authorLink.setPadding(new Insets(0)); authorLink.textProperty().bind(Bindings.selection(selection, Mod::authorProperty)); authorLink.setOnAction(e -> { String contact = selection.get().getContact(); if (contact != null) { try { Desktop.getDesktop().mail(new URI("mailto:" + contact)); } catch (IOException e1) { e1.printStackTrace(); } catch (URISyntaxException e1) { e1.printStackTrace(); } } authorLink.setVisited(false); }); modProperties.addRow(1, new Label("Author"), authorLink); Label versionLabel = new Label(); versionLabel.textProperty().bind(Bindings.selection(selection, mod -> mod.versionProperty().asString())); modProperties.addRow(2, new Label("Version"), versionLabel); Hyperlink homepageLink = new Hyperlink("Homepage"); homepageLink.setPadding(new Insets(0)); homepageLink.setOnAction(e -> { String homepage = selection.get().getHomepage(); if (homepage != null) { try { Desktop.getDesktop().browse(new URI(homepage)); } catch (IOException e1) { e1.printStackTrace(); } catch (URISyntaxException e1) { e1.printStackTrace(); } } homepageLink.setVisited(false); }); modProperties.addRow(3, homepageLink); GridPane.setColumnSpan(homepageLink, 2); Label descriptionTextArea = new Label(); descriptionTextArea.textProperty().bind(Bindings.selection(selection, Mod::descriptionProperty)); descriptionTextArea.setWrapText(true); descriptionTextArea.maxWidthProperty().bind(modProperties.widthProperty().subtract(5)); descriptionTextArea.prefWidthProperty().bind(modProperties.widthProperty().subtract(5)); modProperties.addRow(4, descriptionTextArea); GridPane.setColumnSpan(descriptionTextArea, 2); Label dependenciesLabel = new Label("Dependencies"); modProperties.addRow(5, dependenciesLabel); GridPane.setColumnSpan(dependenciesLabel, 2); ListView<ModDependency> dependencyList = new ListView<>(); dependencyList.setPrefHeight(Region.USE_COMPUTED_SIZE); dependencyList.setMaxHeight(100); dependencyList.itemsProperty().bind(Bindings.selection(selection, mod -> new SimpleObjectProperty<ObservableList<ModDependency>>(mod.getDependencies()), new SimpleObjectProperty<ObservableList<ModDependency>>())); modProperties.addRow(6, dependencyList); GridPane.setColumnSpan(dependencyList, 2); } }