package nl.tudelft.lifetiles.core.controller;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.Stack;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.MenuBar;
import javafx.scene.input.MouseButton;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import nl.tudelft.lifetiles.core.util.FileUtils;
import nl.tudelft.lifetiles.core.util.Message;
import nl.tudelft.lifetiles.notification.controller.NotificationController;
import nl.tudelft.lifetiles.notification.model.AbstractNotification;
import nl.tudelft.lifetiles.notification.model.NotificationFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* The controller of the menu bar.
*
* @author Joren Hammudoglu
*
*/
public class MenuController extends AbstractController {
/**
* Message to display when file is not found.
*/
private static final String NOT_FOUND_MSG = " file could not be found or multiple files found ";
/**
* Constant known mutation extension, currently as defined by client:
* '.txt'.
*/
private static final String KNOWN_MUTATION_EXTENSION = ".txt";
/**
* Constant annotations extension, currently as defined by client:
* '.gff'.
*/
private static final String ANNOTATIONS_EXTENSION = ".gff";
/**
* Extension of the node file.
*/
private static final String NODE_EXTENSION = ".node.graph";
/**
* Extension of the edge file.
*/
private static final String EDGE_EXTENSION = ".edge.graph";
/**
* Extension of the tree file.
*/
private static final String TREE_EXTENSION = ".nwk";
/**
* Extension of the sequence meta data file.
*/
private static final String META_EXTENSION = ".meta";
/**
* The initial x-coordinate of the window.
*/
private double initialX;
/**
* The initial y-coordinate of the window.
*/
private double initialY;
/**
* The menu element.
*/
@FXML
private MenuBar menuBar;
/**
* Keep track of filter actions for the purpose of undoing them.
*/
private Stack<Set<?>> filterStack;
/**
* The notification factory.
*/
private NotificationFactory nf;
/**
* Handle action related to "Open" menu item.
*
* @param event
* Event on "Open" menu item.
*/
@FXML
// PMD/findbugs do not work well with javafx. The method IS used.
@SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
private void openAction(final ActionEvent event) {
try {
loadDataFiles();
} catch (IOException e) {
AbstractNotification notification = nf.getNotification(e);
shout(NotificationController.NOTIFY, "", notification);
}
}
/**
* Handle the click on the "Reset" item in the "Filter" menu.
*
* @param event
* Event on "Reset" menu item.
*/
@FXML
// PMD/findbugs do not work well with javafx. The method IS used.
@SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
private void resetAction(final ActionEvent event) {
shout(Message.RESET, "");
}
/**
* indicate the bookmark menu needs to be shown.
*/
@FXML
// PMD/findbugs do not work well with javafx. The method IS used.
@SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
private void bookmarksAction() {
shout(Message.BOOKMARKS, "");
}
/**
* Handle clicks on the Undo item in the Filter menu.
*
*/
@FXML
private void undoFilterAction() {
filterStack.pop();
if (filterStack.isEmpty()) {
shout(Message.RESET, "");
} else {
shout(Message.FILTERED, "", filterStack.peek());
}
}
/**
* Perform functionality associated with opening a file.
*
* @throws IOException
* throws <code>IOException</code> if any of the files were not
* found
*/
private void loadDataFiles() throws IOException {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Open folder containing data files");
Window window = menuBar.getScene().getWindow();
File directory = directoryChooser.showDialog(window);
// user aborted
if (directory == null) {
return;
}
loadGraph(directory);
loadTree(directory);
loadKnownMutations(directory);
loadAnnotations(directory);
loadMetaData(directory);
}
/**
* Loads the graph from files in the specified directory.
*
* @param directory
* The directory in which to locate the files.
* @throws IOException
* When the directory does not contain exactly one graph file
* and one node file.
*/
private void loadGraph(final File directory) throws IOException {
File nodeFile = FileUtils.getSingleFileByExtension(directory,
NODE_EXTENSION);
File edgeFile = FileUtils.getSingleFileByExtension(directory,
EDGE_EXTENSION);
shout(Message.OPENED, "graph", nodeFile, edgeFile);
}
/**
* Loads the sequence meta-data file in the specified directory.
*
* @param directory
* The directory in which to locate the files.
* @throws IOException
* When the directory does not contain exactly one graph file
* and one node file.
*/
private void loadMetaData(final File directory) throws IOException {
File metaDataFile = loadOrWarn(directory, META_EXTENSION);
if (metaDataFile != null) {
shout(Message.OPENED, "meta", metaDataFile);
}
}
/**
* Loads the known mutations from a file in the specified directory.
*
* @param directory
* The directory from which to load annotations.
*/
private void loadKnownMutations(final File directory) {
File annotationFile = loadOrWarn(directory, KNOWN_MUTATION_EXTENSION);
if (annotationFile != null) {
shout(Message.OPENED, "known mutations", annotationFile);
}
}
/**
* Loads the annotations from a file in the specified directory.
*
* @param directory
* The directory from which to load annotations.
*/
private void loadAnnotations(final File directory) {
File annotationFile = loadOrWarn(directory, ANNOTATIONS_EXTENSION);
if (annotationFile != null) {
shout(Message.OPENED, "annotations", annotationFile);
}
}
/**
* Loads the tree from a file in the specified directory.
*
* @param directory
* The directory in which to search for the tree file.
*/
private void loadTree(final File directory) {
File treeFile = loadOrWarn(directory, TREE_EXTENSION);
if (treeFile != null) {
shout(Message.OPENED, "tree", treeFile);
}
}
/**
* Loads a file from the specified directory, with the specified extension,
* and give a warning.
*
* @param directory
* The directory in which to search for the file.
* @param extension
* The extension to search for.
* @return The found file.
*/
private File loadOrWarn(final File directory, final String extension) {
try {
File file = FileUtils
.getSingleFileByExtension(directory, extension);
return file;
} catch (IOException e) {
shout(NotificationController.NOTIFY, "", nf.getNotification(
extension + NOT_FOUND_MSG, NotificationFactory.WARNING));
}
return null;
}
/**
* Handle action to "Insert Known Mutations" menu item.
*
* @param event
* Event on "Insert Known Mutations" item.
*/
@FXML
// PMD/findbugs do not work well with javafx. The method IS used.
@SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
private void insertKnownMutationAction(final ActionEvent event) {
try {
loadKnownMutationsFile();
} catch (IOException e) {
AbstractNotification notification = nf.getNotification(e);
shout(NotificationController.NOTIFY, "", notification);
}
}
/**
* Perform functionality associated with opening and inserting a known
* mutations file.
*
* @throws IOException
* throws <code>IOException</code> if any of the files were not
* found
*/
private void loadKnownMutationsFile() throws IOException {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open file containing known mutations");
Window window = menuBar.getScene().getWindow();
File file = fileChooser.showOpenDialog(window);
// user aborted
if (file == null) {
return;
}
shout(Message.OPENED, "known mutations", file);
}
/**
* Handle action to "Insert Annotations" menu item.
*
* @param event
* Event on "Insert Annotations" item.
*/
@FXML
// PMD/findbugs do not work well with javafx. The method IS used.
@SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
private void insertAnnotationsAction(final ActionEvent event) {
try {
loadAnnotationsFile();
} catch (IOException e) {
AbstractNotification notification = nf.getNotification(e);
shout(NotificationController.NOTIFY, "", notification);
}
}
/**
* Perform functionality associated with opening and inserting a known
* mutations file.
*
* @throws IOException
* throws <code>IOException</code> if any of the files were not
* found
*/
private void loadAnnotationsFile() throws IOException {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open file containing annotations");
Window window = menuBar.getScene().getWindow();
File file = fileChooser.showOpenDialog(window);
// user aborted
if (file == null) {
return;
}
shout(Message.OPENED, "annotations", file);
}
/**
* Make a node draggable so that when draggin that node, the window moves.
* Code from <a
* href="http://stackoverflow.com/a/12961943/1627479">StackOverflow</a>.
*
* @param node
* the node
*/
private void addDraggableNode(final Node node) {
node.setOnMousePressed((mouseEvent) -> {
if (mouseEvent.getButton() != MouseButton.MIDDLE) {
initialX = mouseEvent.getSceneX();
initialY = mouseEvent.getSceneY();
}
});
node.setOnMouseDragged((mouseEvent) -> {
if (mouseEvent.getButton() != MouseButton.MIDDLE) {
double x = mouseEvent.getScreenX() - initialX;
double y = mouseEvent.getScreenY() - initialY;
node.getScene().getWindow().setX(x);
node.getScene().getWindow().setY(y);
}
});
}
@Override
// checkstyle bug causes false positives in our assert, so suppress.
@SuppressWarnings("checkstyle:genericwhitespace")
public void initialize(final URL location, final ResourceBundle resources) {
addDraggableNode(menuBar);
nf = new NotificationFactory();
filterStack = new Stack<>();
listen(Message.FILTERED, (sender, subject, args) -> {
if (sender.equals(this)) {
return;
}
assert args.length == 1;
assert args[0] instanceof Set<?>;
Set<?> newFilter = (Set<?>) args[0];
filterStack.push(new HashSet<>(newFilter));
});
}
}