Java tutorial
/* * DoomManager * Copyright (C) 2014 Chris K * * 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 ca.wumbo.doommanager.client.controller.file; import javax.annotation.PostConstruct; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import ca.wumbo.doommanager.client.controller.CoreController; import ca.wumbo.doommanager.client.util.Controllable; import ca.wumbo.doommanager.client.util.EntryControllable; import ca.wumbo.doommanager.client.util.Resources; import ca.wumbo.doommanager.client.util.SelfInjectableController; import ca.wumbo.doommanager.file.entry.Entry; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.fxml.FXML; import javafx.scene.control.ContextMenu; import javafx.scene.control.SelectionMode; import javafx.scene.control.SplitPane; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableCell; import javafx.scene.control.TreeTableColumn; import javafx.scene.control.TreeTableRow; import javafx.scene.control.TreeTableView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.util.Callback; /** * The controller for the Doom file viewer window. */ public class DoomFileController extends SelfInjectableController implements Controllable { @Autowired private CoreController coreController; @Autowired private Resources resources; @Value("${doomfile.controller.fxmlpath}") private String fxmlPath; //========================================================================= @FXML private BorderPane rootBorderPane; @FXML private SplitPane splitPane; @FXML private BorderPane leftBorderPane; @FXML private TreeTableView<Entry> entryTreeTable; @FXML private TreeTableColumn<Entry, Image> statusColumn; @FXML private TreeTableColumn<Entry, Entry> nameColumn; @FXML private TreeTableColumn<Entry, String> sizeColumn; @FXML private TreeTableColumn<Entry, String> typeColumn; @FXML private BorderPane rightBorderPane; //========================================================================= /** * The logger for this class. */ private static final Logger log = LogManager.getLogger(DoomFileController.class); /** * Only to be instantiated by Spring. */ private DoomFileController() { } @FXML private void initialize() { // Keep the left window the same size when resizing/maximizing. SplitPane.setResizableWithParent(leftBorderPane, false); // Allow selection of multiple cells. entryTreeTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); // Make the cells update accordingly. nameColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().entryProperty()); sizeColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().dataLengthStringProperty()); typeColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().entryTypeProperty()); // Resize the splitter to a reasonable position. // Since we can't use Platform.runlater() to do this, we have to use a listener that removes itself. // Note: The old way it was done was to pass the tabPane's width to the function after this is initialized. InvalidationListener invalidationListener = new InvalidationListener() { @Override public void invalidated(Observable observable) { setSplitterPosition((int) splitPane.getWidth()); splitPane.widthProperty().removeListener(this); // Remove itself after the size is set. } }; splitPane.widthProperty().addListener(invalidationListener); // Handle item selection. This will clean up our GUI and add/remove panes on the right of the splitter. entryTreeTable.getSelectionModel().selectedItemProperty().addListener((obsValue, oldValue, newValue) -> { updateGUIFromEntrySelection(oldValue, newValue); }); // Support right clicking menus on the rows, source: https://gist.github.com/james-d/7758918 entryTreeTable.setRowFactory(new Callback<TreeTableView<Entry>, TreeTableRow<Entry>>() { @Override public TreeTableRow<Entry> call(TreeTableView<Entry> tableView) { // Create the row to return. TreeTableRow<Entry> row = new TreeTableRow<>(); ContextMenu contextMenu = new ContextMenu(); // Whenever this row gets a new item (or is updated), rebuild the right click menu. row.itemProperty().addListener((observableValue, oldValue, newValue) -> { // TODO - Dynamically generate a new context menu - contextMenu.getItems().add(new MenuItem()); }); // Set context menu on row, but use a binding to make it only show for non-empty rows: row.contextMenuProperty() .bind(Bindings.when(row.emptyProperty()).then((ContextMenu) null).otherwise(contextMenu)); return row; } }); // Instead of assigning graphics to each node, only do it for the cells. // This should help reduce object creation by having it only required for the visible rows. nameColumn.setCellFactory(new Callback<TreeTableColumn<Entry, Entry>, TreeTableCell<Entry, Entry>>() { @Override public TreeTableCell<Entry, Entry> call(TreeTableColumn<Entry, Entry> param) { return new TreeTableCell<Entry, Entry>() { @Override protected void updateItem(Entry item, boolean empty) { super.updateItem(item, empty); if (!empty && item != null) { setText(item.getName()); Image img = resources.getImage(item.getClass().getSimpleName().toLowerCase()); setGraphic(new ImageView(img)); } else { setText(null); setGraphic(null); } } }; } }); } /** * Loads the FXML data and injects it into this object. Should be called by * Spring right after the constructor and dependencies are linked. To * reduce code duplication, this functionality was moved to a containing * class. * * @throws NullPointerException * If the FXML path is null. * * @throws RuntimeException * If the FXML file is missing or corrupt. */ @PostConstruct public void loadFXML() { super.loadFXML(fxmlPath); } /** * This is to be called when the user clicks on a new row, which means the * GUI should be updated by disposing of whatever is on the right (if it's * possible), and adding in the new view. * * @param oldTreeItem * The old tree item (null is allowed). * * @param newTreeItem * The new tree item (null is allowed). */ private void updateGUIFromEntrySelection(TreeItem<Entry> oldTreeItem, TreeItem<Entry> newTreeItem) { // We only care if a click causes a change in entries. if (oldTreeItem != newTreeItem) { // Check if we haven't saved yet, and if not... prompt the user. // TODO // If we're changing to a new entry, time to load a new Pane. if (newTreeItem != null) { // Load the new Node based on what the entry is, and place it on the right. EntryControllable entryControllable = newTreeItem.getValue().getNewController(); entryControllable.setEntry(newTreeItem.getValue()); // Clean up the right pane to assist the garbage collector. // TODO // Insert the root pane at the center. rightBorderPane.setCenter(entryControllable.getRootPane()); } else { // If we're going to null, we should clear last element placed in the pane with a blank pane. rightBorderPane.setCenter(new BorderPane()); } } } /** * Setting the splitter position properly can be annoying due to the API * being unreliable with Platform.runLater(), therefore the only current * solution to make this work is to have this method called later on. * * @param widthContainer * The full width of the container (what should be the width of the * splitpane node). */ public void setSplitterPosition(int widthContainer) { log.trace("Setting splitter position relative to width {}.", widthContainer); double estimatedTableWidth = entryTreeTable.getColumns().size() * 2.0; // 1 pixel border on each side. estimatedTableWidth += 16.0; // Scrollpane scroller width is approximately 16 pixels wide. // Add up the individual column sizes. for (TreeTableColumn<Entry, ?> col : entryTreeTable.getColumns()) estimatedTableWidth += col.getWidth(); // Clamp between [0.0, 1.0], and set it. double splitIndex = Math.min(Math.max(0.0, estimatedTableWidth / widthContainer), 1.0); splitPane.setDividerPositions(splitIndex); } @Override public Pane getRootPane() { return rootBorderPane; } public TreeTableView<Entry> getEntryTreeTable() { return entryTreeTable; } }