Package net.angusi.sw.minidisc.gui

Source Code of net.angusi.sw.minidisc.gui.CueWindow

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package net.angusi.sw.minidisc.gui;

import javafx.animation.AnimationTimer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.web.HTMLEditor;
import javafx.stage.*;
import net.angusi.sw.minidisc.MiniDisc;
import net.angusi.sw.minidisc.audioobjects.Clip;
import org.controlsfx.dialog.Dialogs;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* This is the main cue player window.
* It uses Split Frames to divide the window up into regions, which can be resized to
* the user's preference, but defaults to side-bar style layout:
*  ===================
* |  |            |   |
* |--|            |   |
* |  |            |   |
* |--|            |   |
* |  |            |   |
* |--|            |   |
* |  |            |   |
*  ===================
*/

class CueWindow {
    //Window bits
    private static CueWindow classHandle;
    private final Stage stage;

    //Cue table
    private TableView<Clip> mainTable;

    //Audio Related Stuff
    private final ObservableList<Clip> audioClips;


    private CueWindow() {
        System.out.println("Setting up main frame");

        stage = new Stage();
        stage.setTitle(MiniDisc.getAppTitle()+" - Cue Player");
        stage.setMinHeight(480);
        stage.setMinWidth(640);
        stage.setMaximized(true);

        stage.setOnCloseRequest(event -> {
            ModePicker.getStage().show();
            stopAllTracks();
            stage.hide();
        });

        //Setup audio stuff:
        ArrayList<Clip> audioClipsArrayList = new ArrayList<>();
        audioClips = FXCollections.observableArrayList(audioClipsArrayList);

        //Setup everything else
        this.initSceneAndPanes();
        this.initKeyboardShortcuts();
    }

    /**
     * Initialises global keyboard shortcuts.
     * Useful for key triggering of stop and play.
     */
    private void initKeyboardShortcuts() {
        Map<KeyCombination, Runnable> keyEvents = new HashMap<>();
        keyEvents.put(new KeyCodeCombination(KeyCode.SPACE), this::playSelectedTrack);
        keyEvents.put(new KeyCodeCombination(KeyCode.ESCAPE), this::stopAllTracks);

        stage.getScene().setOnKeyPressed(event -> keyEvents.keySet().stream().filter(thisCombo -> thisCombo.match(event)).forEach(thisCombo -> {
            keyEvents.get(thisCombo).run();
            event.consume();
        }));
    }

    /**
     * Begins setting up all the split frames
     */
    private void initSceneAndPanes() {

        System.out.println("Initialising Split Panes");

        BorderPane container = new BorderPane();
        Scene scene = new Scene(container);
        stage.setScene(scene);

        VBox controlsContainer = new VBox();
        controlsContainer.getChildren().add(this.initMainMenu());
        controlsContainer.getChildren().add(this.initToolBar());
        container.setTop(controlsContainer);

        SplitPane splitPanes = new SplitPane();
        container.setCenter(splitPanes);

        SplitPane leftPane = initLeftPanes();
        Pane centerPane = initMainPane();
        SplitPane rightPane = initRightPanes();

        splitPanes.getItems().addAll(leftPane, centerPane, rightPane);
        splitPanes.setDividerPositions(0.1f, 0.9f);

    }

    /**
     * Sets up the left panes.
     * This is where (by default) the control buttons and clock live.
     */
    private SplitPane initLeftPanes() {
        System.out.println("Initialising Left Panes");

        SplitPane leftPanes = new SplitPane();
        leftPanes.setOrientation(Orientation.VERTICAL);
        leftPanes.setStyle("-fx-background-color:#000000");


        //Set up the GO button
        Text goButton = new Text("Go");
        goButton.setFill(Color.GREEN);
        BorderPane goPane = new BorderPane(goButton);
        goButton.setOnMouseClicked(event -> playSelectedTrack());
        leftPanes.getItems().add(goPane);


        //Set up the STOP button
        Text stopButton = new Text("Stop");
        stopButton.setFill(Color.RED);
        BorderPane stopPane = new BorderPane(stopButton);
        stopButton.setOnMouseClicked(event -> stopSelectedTrack());
        leftPanes.getItems().add(stopPane);

        //Set up the FADE button
        Text fadeButton = new Text("Fade");
        fadeButton.setFill(Color.YELLOW);
        BorderPane fadePane = new BorderPane(fadeButton);
        fadeButton.setOnMouseClicked(event -> fadeSelectedTrack());
        leftPanes.getItems().add(fadePane);

        //Set up the clock
        Text clock = new Text("00:00:00");
        clock.setFill(Color.WHITE);
        BorderPane clockPane = new BorderPane(clock);
        leftPanes.getItems().add(clockPane);
        //Make the clock tick:
        new AnimationTimer() {
            @Override
            public void handle(long now) {
                clock.setText(new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }.start();

        //Evaluating text width is easy - we just ask the Text object for its width boundary.
        // Similarly, the text height is done with the Text height boundary.
        // The maximum width is easily done, too, actually. Just get the width of the pane!
        // However, to calculate the height is a bit trickier...
        //  Typically, we'll need to find the position of the dividers ABOVE and BELOW the pane and subtract them.
        //  To get these positions, we can ask the dividers for their position - but that's actually a percentage of
        //  the entire SplitPane height, so we need to multiply the resulting subtraction by the SplitPane's height.
        //First, let's do the vertical movement of the first divider, which affects panes 0 (Go) and 1 (Stop)
        leftPanes.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> {
            //Note: Special case, as "ABOVE" is 0 (top of panes)
            double scale = resizeText(goButton.getBoundsInLocal().getWidth(), goButton.getBoundsInLocal().getHeight(), goPane.getWidth(), leftPanes.getHeight() * newValue.doubleValue());
            goButton.setScaleX(scale);
            goButton.setScaleY(scale);

            scale = resizeText(stopButton.getBoundsInLocal().getWidth(), stopButton.getBoundsInLocal().getHeight(), stopPane.getWidth(), leftPanes.getHeight() * (leftPanes.getDividers().get(1).getPosition() - newValue.doubleValue()));
            stopButton.setScaleX(scale);
            stopButton.setScaleY(scale);
        });
        //Then do the vertical movement of the second divider, which affects 1 (Stop) and 2 (Fade)
        leftPanes.getDividers().get(1).positionProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(stopButton.getBoundsInLocal().getWidth(), stopButton.getBoundsInLocal().getHeight(), stopPane.getWidth(), leftPanes.getHeight() * (newValue.doubleValue() - leftPanes.getDividers().get(0).getPosition()));
            stopButton.setScaleX(scale);
            stopButton.setScaleY(scale);

            scale = resizeText(fadeButton.getBoundsInLocal().getWidth(), fadeButton.getBoundsInLocal().getHeight(), fadePane.getWidth(), leftPanes.getHeight() * (leftPanes.getDividers().get(2).getPosition() - newValue.doubleValue()));
            fadeButton.setScaleX(scale);
            fadeButton.setScaleY(scale);
        });
        //Next, the third divider, affecting 2 (Fade) and 3 (Clock)
        leftPanes.getDividers().get(2).positionProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(fadeButton.getBoundsInLocal().getWidth(), fadeButton.getBoundsInLocal().getHeight(), fadePane.getWidth(), leftPanes.getHeight() * (newValue.doubleValue() - leftPanes.getDividers().get(1).getPosition()));
            fadeButton.setScaleX(scale);
            fadeButton.setScaleY(scale);

            //Note: Special case, as BELOW is leftPanes.getHeight (bottom of panes)
            scale = resizeText(clock.getBoundsInLocal().getWidth(), clock.getBoundsInLocal().getHeight(), clockPane.getWidth(), leftPanes.getHeight() - (leftPanes.getHeight() * newValue.doubleValue()));
            clock.setScaleX(scale);
            clock.setScaleY(scale);
        });

        //Lastly do the horizontal width of the first pane. In fact, if this pane's width changes, all the panes do.
        goPane.widthProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(goButton.getBoundsInLocal().getWidth(), goButton.getBoundsInLocal().getHeight(), newValue.doubleValue(), leftPanes.getHeight() * leftPanes.getDividers().get(0).getPosition());
            goButton.setScaleX(scale);
            goButton.setScaleY(scale);
        });
        stopPane.widthProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(stopButton.getBoundsInLocal().getWidth(), stopButton.getBoundsInLocal().getHeight(), newValue.doubleValue(), leftPanes.getHeight() * (leftPanes.getDividers().get(1).getPosition() - leftPanes.getDividers().get(0).getPosition()));
            stopButton.setScaleX(scale);
            stopButton.setScaleY(scale);
        });

        fadePane.widthProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(fadeButton.getBoundsInLocal().getWidth(), fadeButton.getBoundsInLocal().getHeight(), newValue.doubleValue(), leftPanes.getHeight() * (leftPanes.getDividers().get(2).getPosition() - leftPanes.getDividers().get(1).getPosition()));
            fadeButton.setScaleX(scale);
            fadeButton.setScaleY(scale);
        });

        clockPane.widthProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(clock.getBoundsInLocal().getWidth(), clock.getBoundsInLocal().getHeight(), newValue.doubleValue(), leftPanes.getHeight() - (leftPanes.getHeight() * leftPanes.getDividers().get(2).getPosition()));
            clock.setScaleX(scale);
            clock.setScaleY(scale);
        });

        return leftPanes;
    }

    private double resizeText(double textWidth, double textHeight, double maxWidth, double maxHeight) {
        double scaleX = maxWidth/textWidth;
        double scaleY = maxHeight/textHeight;
        return scaleX > scaleY ? scaleY : scaleX;
    }

    /**
     * Initialise the main pane.
     * This is where (by default) the cue list lives
     */

    private Pane initMainPane() {
        System.out.println("Initialising Main Pane");
        BorderPane mainFrame = new BorderPane();

        //Set up the table
        mainTable = new TableView<>();
        mainTable.setItems(audioClips);

        TableColumn<Clip, String> qTableColumn = new TableColumn<>("Q#");
        qTableColumn.setCellValueFactory(new PropertyValueFactory<>("cueNumber"));
        TableColumn<Clip, String> descTableColumn = new TableColumn<>("Description");
        descTableColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
        TableColumn<Clip, Double> durationTableColumn = new TableColumn<>("Duration");
        durationTableColumn.setCellFactory(param -> new TableCell<Clip, Double>() {
            @Override
            protected void updateItem(Double item, boolean empty) {
                if(item != null) {
                    long timeLeft = item.longValue();
                    int hoursLeft = (int) TimeUnit.MILLISECONDS.toHours(timeLeft);
                    int minsLeft = (int) (TimeUnit.MILLISECONDS.toMinutes(timeLeft) - TimeUnit.HOURS.toMinutes(hoursLeft));
                    int secondsLeft = (int) (TimeUnit.MILLISECONDS.toSeconds(timeLeft) - TimeUnit.MINUTES.toSeconds(minsLeft) - TimeUnit.HOURS.toSeconds(hoursLeft));
                    int millisecondsLeft = (int) (TimeUnit.MILLISECONDS.toMillis(timeLeft) - TimeUnit.SECONDS.toMillis(secondsLeft) - TimeUnit.MINUTES.toMillis(minsLeft) - TimeUnit.HOURS.toMillis(hoursLeft));
                    setText(String.format("%02d:%02d:%02d.%03d", hoursLeft, minsLeft, secondsLeft, millisecondsLeft));
                } else {
                    setText(null);
                }
            }
        });
        durationTableColumn.setCellValueFactory(new PropertyValueFactory<>("length"));
        TableColumn<Clip, Double> remainingTableColumn = new TableColumn<>("Remaining");
        remainingTableColumn.setCellValueFactory(new PropertyValueFactory<>("timeRemaining"));
        remainingTableColumn.setCellFactory(clip -> new TableCell<Clip, Double>() {
            @Override
            protected void updateItem(Double item, boolean empty) {
                if (item != null) {
                    long timeLeft = item.longValue();
                    int hoursLeft = (int) TimeUnit.MILLISECONDS.toHours(timeLeft);
                    int minsLeft = (int) (TimeUnit.MILLISECONDS.toMinutes(timeLeft) - TimeUnit.HOURS.toMinutes(hoursLeft));
                    int secondsLeft = (int) (TimeUnit.MILLISECONDS.toSeconds(timeLeft) - TimeUnit.MINUTES.toSeconds(minsLeft) - TimeUnit.HOURS.toSeconds(hoursLeft));
                    int millisecondsLeft = (int) (TimeUnit.MILLISECONDS.toMillis(timeLeft) - TimeUnit.SECONDS.toMillis(secondsLeft) - TimeUnit.MINUTES.toMillis(minsLeft) - TimeUnit.HOURS.toMillis(hoursLeft));
                    setText(String.format("%02d:%02d:%02d.%03d", hoursLeft, minsLeft, secondsLeft, millisecondsLeft));
                } else {
                    setText(null);
                }
            }
        });
        TableColumn<Clip, String> gainTableColumn = new TableColumn<>("Gain");
        gainTableColumn.setCellValueFactory(new PropertyValueFactory<>("gain"));
        TableColumn<Clip, String> loopTableColumn = new TableColumn<>("Loop");
        loopTableColumn.setCellValueFactory(new PropertyValueFactory<>("loop"));

        mainTable.getColumns().setAll(qTableColumn, descTableColumn, durationTableColumn, remainingTableColumn, gainTableColumn, loopTableColumn);
        mainTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);

        mainTable.setStyle("-fx-background-color: #000000; -fx-color:#FFFFFF");

        mainFrame.setCenter(mainTable);
        return mainFrame;
    }

    /**
     * Set up the right pane.
     * This is where (by default) the notes pane lives.
     */
    private SplitPane initRightPanes() {
        //TODO: Finish notes-per-cue
        System.out.println("Initialising Right Panes");
        SplitPane rightPanes = new SplitPane();
        rightPanes.setOrientation(Orientation.VERTICAL);
        rightPanes.setStyle("-fx-background-color:#000000");

        HTMLEditor clipNotes = new HTMLEditor();
        rightPanes.getItems().add(clipNotes);

        //Set up the E-STOP button
        Text stopButton = new Text("E-STOP");
        stopButton.setFill(Color.RED);
        BorderPane stopPane = new BorderPane(stopButton);
        stopButton.setOnMouseClicked(event -> stopAllTracks());
        rightPanes.getItems().add(stopPane);

        stopPane.widthProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(stopButton.getBoundsInLocal().getWidth(), stopButton.getBoundsInLocal().getHeight(), newValue.doubleValue(), rightPanes.getHeight() - (rightPanes.getHeight() * rightPanes.getDividers().get(0).getPosition()));
            stopButton.setScaleX(scale);
            stopButton.setScaleY(scale);
        });
        rightPanes.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> {
            double scale = resizeText(stopButton.getBoundsInLocal().getWidth(), stopButton.getBoundsInLocal().getHeight(), stopPane.getWidth(), rightPanes.getHeight() - (rightPanes.getHeight() * rightPanes.getDividers().get(0).getPosition()));
            stopButton.setScaleX(scale);
            stopButton.setScaleY(scale);
        });

        rightPanes.setDividerPositions(0.9f);

        return rightPanes;
    }

    /**
     * Set up the menus.
     * _File
     *   E_xit
     * _Help
     *   _Wiki
     *   _Project Home
     *   _About MiniDisc
     */
    private MenuBar initMainMenu() {
        System.out.println("Initialising Main Menu Bar");

        MenuBar mainMenuBar = new MenuBar();

        //First the _F_ile menu
        Menu fileMenu = new Menu("_File");
        fileMenu.setMnemonicParsing(true);
        mainMenuBar.getMenus().add(fileMenu);

        //And its items:
        MenuItem fileMenuExitMenuItem = new MenuItem("_Exit");
        fileMenuExitMenuItem.setMnemonicParsing(true);
        fileMenuExitMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN));
        fileMenuExitMenuItem.setOnAction(event -> MiniDisc.quitApplication());
        fileMenu.getItems().add(fileMenuExitMenuItem);


        //Then, the _H_elp menu
        Menu helpMenu = new Menu("_Help");
        helpMenu.setMnemonicParsing(true);
        mainMenuBar.getMenus().add(helpMenu);

        //And its items:
        MenuItem helpMenuWikiItem = new MenuItem("_Wiki");
        helpMenuWikiItem.setMnemonicParsing(true);
        helpMenuWikiItem.setOnAction(e -> MiniDisc.getHandle().getHostServices().showDocument("http://code.angusi.net/minidisc/wiki"));
        helpMenu.getItems().add(helpMenuWikiItem);

        MenuItem helpMenuProjectHomeItem = new MenuItem("_Project Home");
        helpMenuProjectHomeItem.setMnemonicParsing(true);
        helpMenuProjectHomeItem.setOnAction(e -> MiniDisc.getHandle().getHostServices().showDocument("http://sw.angusi.net/MiniDisc"));
        helpMenu.getItems().add(helpMenuProjectHomeItem);

        MenuItem helpMenuAboutItem = new MenuItem("_About MiniDisc");
        helpMenuAboutItem.setMnemonicParsing(true);
        helpMenuAboutItem.setOnAction(e -> {
            String aboutMessage = String.format("MiniDisc, a lightweight, cross-platform clone of Multiplay.%n" +
                    "MiniDisc was written by Angus Ireland - http://angusi.net%n%n"+
                    "For more information, see the project's home at%n" +
                    "  http://sw.angusi.net/minidisc%n%n" +
                    "Please see LICENSES.TXT (or the project Wiki) for applicable licenses.");

            Dialogs.create()
                    .owner(stage)
                    .title("About MiniDisc")
                    .masthead("This is " + MiniDisc.getAppTitle() + " version " + MiniDisc.getAppVersion())
                    .message(aboutMessage)
                    .showInformation();
        });
        helpMenu.getItems().add(helpMenuAboutItem);
        return mainMenuBar;
    }

    /**
     * Set up the toolbar
     * This toolbar has the add/remove clip buttons
     */
    private ToolBar initToolBar() {
        System.out.println("Initialising Toolbar");
        ToolBar mainToolBar = new ToolBar();

        Button addButton = new Button("Add", new ImageView(new Image(this.getClass().getResourceAsStream("/net/angusi/sw/minidisc/res/icons/plusicon.png"))));
        addButton.setOnAction(event -> addClipToCueList());
        mainToolBar.getItems().add(addButton);

        Button removeButton = new Button("Remove", new ImageView(new Image(this.getClass().getResourceAsStream("/net/angusi/sw/minidisc/res/icons/minusicon.png"))));
        removeButton.setOnAction(event -> removeClipFromCueList());
        mainToolBar.getItems().add(removeButton);

        Button propertiesButton = new Button("Properties", new ImageView(new Image(this.getClass().getResourceAsStream("/net/angusi/sw/minidisc/res/icons/propertiesicon.png"))));
        propertiesButton.setOnAction(event -> {
            //TODO: Clip Properties
        });
        mainToolBar.getItems().add(propertiesButton);
        propertiesButton.setDisable(true);

        return mainToolBar;
    }

    /**
     * Adds a clip to the cue list.
     * Creates a new Clip object, adding it to the Set of clips
     */
    private void addClipToCueList() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Add Clip");
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("All Audio Files", "*.MID", "*.MIDI", "*.M4A", "*.WAV", "*.SND", "*.AU", "*.AIF", "*.AIFF", "*.MP2", "*.MP3", "*.AAC", "*.WMA"),
                new FileChooser.ExtensionFilter("Midi Files", "*.MID", "*.MIDI"),
                new FileChooser.ExtensionFilter("M4A Files", "*.M4A"),
                new FileChooser.ExtensionFilter("Wave Files", "*.WAV"),
                new FileChooser.ExtensionFilter("Snd Files", "*.SND"),
                new FileChooser.ExtensionFilter("AU Files", "*.AU"),
                new FileChooser.ExtensionFilter("AIFF Files", "*.AIF", "*.AIFF"),
                new FileChooser.ExtensionFilter("MP2/MP3 Files", "*.MP2", "*.MP3"),
                new FileChooser.ExtensionFilter("AAC Files", "*.AAC"),
                new FileChooser.ExtensionFilter("WMA Files", "*.WMA"),
                new FileChooser.ExtensionFilter("All Files", "*.*")
        );

        File file = fileChooser.showOpenDialog(stage);
        if (file != null) {
            int insertAt = mainTable.getSelectionModel().getSelectedIndex() == -1 ? mainTable.getItems().size() : mainTable.getSelectionModel().getSelectedIndex();
            System.out.println("Adding " + file.getName() + " to cue list.");
            Clip newClip = new Clip(file);
            audioClips.add(insertAt, newClip);
        }
    }

    private void removeClipFromCueList() {
        audioClips.remove(mainTable.getSelectionModel().getSelectedItem());
    }

    private void playSelectedTrack() {
        Clip selectedClip = mainTable.getSelectionModel().getSelectedItem();
        System.out.println("Asking clip "+(selectedClip.getDescription())+" to play");
        new Thread(new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                selectedClip.playClip();
                return null;
            }
        }).start();

        mainTable.getSelectionModel().selectBelowCell();
    }

    private void stopSelectedTrack() {
        Clip selectedClip = mainTable.getSelectionModel().getSelectedItem();
        selectedClip.stopClip();
    }

    private void stopAllTracks() {
        System.out.println("Stopping all clips");
        for (Clip audioClip : audioClips) {
            audioClip.stopClip();
        }
    }

    private void fadeSelectedTrack() {
        Clip selectedClip = mainTable.getSelectionModel().getSelectedItem();
        selectedClip.fadeClip();
    }

    public static Stage getStage() {
        if(classHandle == null) {
            classHandle = new CueWindow();
        }
        return classHandle.stage;
    }
}
TOP

Related Classes of net.angusi.sw.minidisc.gui.CueWindow

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.