package nl.utwente.viskell.ui; import com.google.common.base.Charsets; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.scene.control.MenuItem; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.stage.Window; import nl.utwente.viskell.ui.serialize.Exporter; import nl.utwente.viskell.ui.serialize.Importer; import nl.utwente.viskell.ui.serialize.ViskellFormat; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.*; /** * menu actions */ public class MenuActions { /** * The main overlay of which this menu is a part */ protected MainOverlay overlay; private final ToplevelPane toplevelPane; /** * The File we're currently working on, if any. */ private Optional<File> currentFile; /** The current preferences window, or null if not yet opened. */ private PreferencesWindow preferences; /** The current inspector window, or null if not yet opened. */ private InspectorWindow inspector; public MenuActions(final MainOverlay ol, final ToplevelPane tl) { overlay = ol; toplevelPane = tl; newFile(); } /** * Set the state to reflect that we are working on a new file */ private void newFile() { this.currentFile = Optional.empty(); Main.getStage().setTitle("New file"); } /** * Set the current file we are working on, including putting it's name in the window title * * @param currentFile we are now using */ private void setFile(final File currentFile) { this.currentFile = Optional.of(currentFile); Main.getStage().setTitle(currentFile.getName()); } protected List<MenuItem> fileMenuItems() { List<MenuItem> list = new ArrayList<>(); MenuItem menuNew = new MenuItem("New"); menuNew.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN)); menuNew.setOnAction(this::onNew); list.add(menuNew); MenuItem menuOpen = new MenuItem("Open..."); menuOpen.setAccelerator(new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN)); menuOpen.setOnAction(this::onOpen); list.add(menuOpen); MenuItem menuSave = new MenuItem("Save"); menuSave.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN)); menuSave.setOnAction(this::onSave); list.add(menuSave); MenuItem menuSaveAs = new MenuItem("Save as..."); menuSaveAs.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.SHIFT_DOWN, KeyCombination.SHORTCUT_DOWN)); menuSaveAs.setOnAction(this::onSaveAs); list.add(menuSaveAs); return list; } @SuppressWarnings("UnusedParameters") protected void showPreferences(ActionEvent actionEvent) { if (preferences == null) { preferences = new PreferencesWindow(overlay); } preferences.show(); } @SuppressWarnings("UnusedParameters") protected void showInspector(ActionEvent actionEvent) { if (inspector == null) { inspector = new InspectorWindow(overlay); } inspector.show(); } protected void onNew(ActionEvent actionEvent) { toplevelPane.clearChildren(); newFile(); } /** * onOpen() acts more like a "load from file" than open that file. It reads all the objects from the specified file * and adds them to the UI, but doesn't assume that filename. The user can use this to read in objects to a file * they are already working on, to build-on the objects in the file - and then save to a different file later. * * They will have to choose the destination file when saving. They can choose to save to the same file they read * from if they want by selecting it in the dialog. * * @param actionEvent correspondign to the open request */ protected void onOpen(ActionEvent actionEvent) { Window window = overlay.getScene().getWindow(); File file = new FileChooser().showOpenDialog(window); if (file != null) { addChildrenFrom(file, toplevelPane); } } protected void onSave(ActionEvent actionEvent) { if (currentFile.isPresent()) { saveTo(currentFile.get()); } else { onSaveAs(actionEvent); } } protected void onSaveAs(ActionEvent actionEvent) { Window window = overlay.getScene().getWindow(); File file = new FileChooser().showSaveDialog(window); if (file != null) { saveTo(file); setFile(file); } } protected void addChildrenFrom(File file, ToplevelPane toplevelPane) { try (FileInputStream fis = new FileInputStream(file)) { Map<String, Object> layers = Importer.readLayers(fis); fis.close(); // check we can read this version of the serialized file format Integer fileFormatversion = ((Double)layers.get(ViskellFormat.VERSION_NUMBER_LABEL)).intValue(); if (ViskellFormat.SUPPORTED_IMPORT_VERSIONS.contains(fileFormatversion)) { toplevelPane.fromBundle(layers); } else { // TODO show a dialog telling the user this version of the file cannot be read, list the versions // that it can read, and pointing them to where they can download a newer version of the app to read the version System.err.println("This version of the app cannot read files saved in format version: " + fileFormatversion); } } catch (IOException e) { // TODO do something sensible here - like show a dialog e.printStackTrace(); } } protected void saveTo(File file) { try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(Exporter.export(toplevelPane).getBytes(Charsets.UTF_8)); fos.close(); } catch (IOException e) { // TODO do something sensible here e.printStackTrace(); } } @SuppressWarnings("UnusedParameters") protected void toggleFullScreen(ActionEvent actionEvent) { Stage stage = Main.getStage(); if (stage.isFullScreen()) { stage.setFullScreen(false); } else { stage.setMaximized(true); stage.setFullScreen(true); } } @SuppressWarnings("UnusedParameters") protected void onQuit(ActionEvent actionEvent) { Platform.exit(); } @SuppressWarnings("UnusedParameters") protected void zoomIn(ActionEvent actionEvent) { toplevelPane.zoom(1.1); } @SuppressWarnings("UnusedParameters") protected void zoomOut(ActionEvent actionEvent) { toplevelPane.zoom(1 / 1.1); } }