f@0: /*
f@0: Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.
f@0:
f@0: Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
f@0:
f@0: This program is free software: you can redistribute it and/or modify
f@0: it under the terms of the GNU General Public License as published by
f@0: the Free Software Foundation, either version 3 of the License, or
f@0: (at your option) any later version.
f@0:
f@0: This program is distributed in the hope that it will be useful,
f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0: GNU General Public License for more details.
f@0:
f@0: You should have received a copy of the GNU General Public License
f@0: along with this program. If not, see .
f@0: */
f@0: package uk.ac.qmul.eecs.depic.daw.gui;
f@0:
f@0: import java.awt.BorderLayout;
f@0: import java.awt.Color;
f@0: import java.awt.Component;
f@0: import java.awt.Dimension;
f@0: import java.awt.event.KeyEvent;
f@0: import java.awt.event.MouseAdapter;
f@0: import java.awt.event.MouseEvent;
f@0: import java.beans.PropertyChangeEvent;
f@0: import java.beans.PropertyChangeListener;
f@0: import java.util.ArrayList;
f@0: import java.util.List;
f@0:
f@0: import javax.swing.BorderFactory;
f@0: import javax.swing.Box;
f@0: import javax.swing.BoxLayout;
f@0: import javax.swing.JComponent;
f@0: import javax.swing.JPanel;
f@0: import javax.swing.JScrollPane;
f@0: import javax.swing.KeyStroke;
f@0: import javax.swing.border.Border;
f@0:
f@0: import uk.ac.qmul.eecs.depic.daw.Selection;
f@0: import uk.ac.qmul.eecs.depic.daw.SoundWave;
f@0: import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent;
f@0: import uk.ac.qmul.eecs.depic.daw.SoundWaveListener;
f@0:
f@0: /**
f@0: *
f@0: * The panel containing the audio tracks.
f@0: *
f@0: * Tracks are arranged in a scrollable BoxLayout.
f@0: *
f@0: */
f@0: public class ArrangeWindow extends JPanel implements PropertyChangeListener, SoundWaveListener {
f@0: private static final long serialVersionUID = 1L;
f@0: public static final Color BACKGROUND_COLOR = new Color(226,226,226);
f@0: public static final int SPACE_BETWEEN_TRACKS = 5;
f@0: public static final Border BORDER_UNSELECTED = BorderFactory.createCompoundBorder(
f@0: BorderFactory.createEmptyBorder(1, 0, 1, 0), BorderFactory.createMatteBorder(1, 0, 1, 0, Color.GRAY));
f@0: public static final Border BORDER_SELECTED = BorderFactory.createCompoundBorder(
f@0: BorderFactory.createMatteBorder(1, 0, 1, 0, new Color(214, 193, 99)), BorderFactory.createMatteBorder(1, 0, 1, 0, Color.GRAY));
f@0:
f@0:
f@0: /* tracks panel contains the rule and audio tracks */
f@0: private JScrollPane tracksPanelScroll;
f@0: /* view of the trackPanelScroll and panel where all the audio tracks are places */
f@0: private JPanel tracksPanel;
f@0: /* left panel with AudioTrackParameters */
f@0: private JPanel parametersPanel;
f@0: private Rule rule;
f@0: private int currentTrackIndex;
f@0: private List tracks;
f@0: private List audioTrackParameters;
f@0: private List spacesBetweenTracks;
f@0: private List spacesBetweenTrackParameters;
f@0: private MouseInteraction mouseInteraction;
f@0:
f@0:
f@0: public ArrangeWindow(){
f@0: tracks = new ArrayList<>();
f@0: audioTrackParameters = new ArrayList<>();
f@0: spacesBetweenTracks = new ArrayList<>();
f@0: spacesBetweenTrackParameters = new ArrayList<>();
f@0: mouseInteraction = new MouseInteraction();
f@0:
f@0: setLayout(new BorderLayout());
f@0: setBackground(BACKGROUND_COLOR);
f@0:
f@0: /* left panel: header (of the same height as rule) and parameters */
f@0: parametersPanel = new JPanel();
f@0: parametersPanel.setLayout(new BoxLayout(parametersPanel,BoxLayout.Y_AXIS));
f@0: parametersPanel.add(Box.createRigidArea(new Dimension(247,Rule.HEIGHT+3)));
f@0: parametersPanel.setBackground(BACKGROUND_COLOR);
f@0:
f@0: add(parametersPanel,BorderLayout.WEST);
f@0:
f@0: /* right panel: scrollable panel with rule and tracks as header*/
f@0: tracksPanel = new JPanel();
f@0: tracksPanel.setLayout(new BoxLayout(tracksPanel,BoxLayout.Y_AXIS));
f@0: tracksPanel.setBackground(BACKGROUND_COLOR);
f@0: rule = new Rule();
f@0: rule.setBackground(BACKGROUND_COLOR);
f@0:
f@0:
f@0: tracksPanelScroll = new JScrollPane(tracksPanel,
f@0: JScrollPane.VERTICAL_SCROLLBAR_NEVER,
f@0: JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
f@0:
f@0: /* disable the scrolling via the left-right keys to let the cursor scrub handler
f@0: * take over when the track is focused */
f@0: tracksPanelScroll.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "none");
f@0: tracksPanelScroll.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "none");
f@0: /* setPreferredSize important, otherwise the view port gets resized to the track size *
f@0: * as soon as the window is resized and therefore the scroll bar disappear */
f@0: tracksPanelScroll.setPreferredSize(new Dimension(500,500));
f@0: /* rule is set as the header of the scroll pane with audio tracks */
f@0: tracksPanelScroll.setColumnHeaderView(rule);
f@0:
f@0: add(tracksPanelScroll, BorderLayout.CENTER);
f@0:
f@0: currentTrackIndex = -1;
f@0: }
f@0:
f@0: /**
f@0: * Adds a track to this component. The added track is automatically set as the current track.
f@0: *
f@0: * Adding a track that's already in the arrange window will have no effect.
f@0: *
f@0: * @param track the new track to add. It must be a track that is not currently
f@0: * contained in this arrange window
f@0: */
f@0: public void addTrack(AudioTrack track){
f@0: if(tracks.contains(track))
f@0: return;
f@0:
f@0: AudioTrackParameters trackParameters = new AudioTrackParameters(track,1);
f@0:
f@0: /* install the mouse listener */
f@0: track.addMouseListener(mouseInteraction);
f@0: trackParameters.addMouseListener(mouseInteraction); // FIXME verify
f@0:
f@0:
f@0: /* add track and parameters in the respective lists */
f@0: tracks.add(track);
f@0: audioTrackParameters.add(trackParameters);
f@0: Component box1 = Box.createVerticalStrut(SPACE_BETWEEN_TRACKS);
f@0: Component box2 = Box.createVerticalStrut(SPACE_BETWEEN_TRACKS);
f@0: spacesBetweenTracks.add(box1);
f@0: spacesBetweenTrackParameters.add(box2);
f@0:
f@0: /* add track and parameters in the respective panels */
f@0: parametersPanel.add(box1);
f@0: parametersPanel.add(trackParameters);
f@0: tracksPanel.add(box2);
f@0: tracksPanel.add(track);
f@0: /* set the new track as the current */
f@0: setCurrentTrack(getTracksCount()-1);
f@0:
f@0: /* set the label. Users will count starting from 1 and not from 0 */
f@0: trackParameters.setLabel("new Track "+(getCurrentTrackIndex()+1));
f@0: repaint();
f@0: }
f@0:
f@0: public AudioTrack getTrackAt(int index){
f@0: return tracks.get(index);
f@0: }
f@0:
f@0: public AudioTrackParameters getTrackParametersAt(int index){
f@0: return audioTrackParameters.get(index);
f@0: }
f@0:
f@0: /**
f@0: *
f@0: * @return the current track or {@code null} if there are no tracks
f@0: * in this arrange window
f@0: */
f@0: public AudioTrack getCurrentTrack(){
f@0: if(currentTrackIndex == -1)
f@0: return null;
f@0: return tracks.get(currentTrackIndex);
f@0: }
f@0:
f@0: /**
f@0: * Sets the track at the specified index as current.
f@0: *
f@0: * @param index the index of the new current track
f@0: * @throws ArrayIndexOutOfBoundsException if {@code index} is lower than 0 or greater or equal to {@code getTracksCount()}
f@0: *
f@0: *
f@0: */
f@0: public void setCurrentTrack(int index){
f@0: /* removes this from the listeners of the previous current track */
f@0: if(currentTrackIndex != -1){
f@0: AudioTrack previousTrack = getTrackAt(currentTrackIndex);
f@0: previousTrack.removePropertyChangeListener(this);
f@0: previousTrack.getSoundWave().removeSoundWaveListener(this);
f@0: previousTrack.setBorder(BORDER_UNSELECTED);
f@0: audioTrackParameters.get(currentTrackIndex).setBorder(BORDER_UNSELECTED);
f@0: }
f@0: currentTrackIndex = index;
f@0:
f@0: AudioTrack currentTrack = getTrackAt(currentTrackIndex);
f@0: /* install listeners in the new audio track */
f@0: currentTrack.addPropertyChangeListener(this);
f@0: currentTrack.getSoundWave().addSoundWaveListener(this);
f@0: /* set the borders */
f@0: currentTrack.setBorder(BORDER_SELECTED);
f@0: audioTrackParameters.get(currentTrackIndex).setBorder(BORDER_SELECTED);
f@0:
f@0: rule.setAudioTrack(getTrackAt(currentTrackIndex));
f@0: }
f@0:
f@0: /**
f@0: *
f@0: * @return the index of the currently selected track or {@code -1} if no track
f@0: * is open in the arrange window
f@0: */
f@0: public int getCurrentTrackIndex(){
f@0: return currentTrackIndex;
f@0: }
f@0:
f@0: /**
f@0: * Returns the number of tracks in this arrange window.
f@0: *
f@0: * @return the number of tracks in this arrange window
f@0: */
f@0: public int getTracksCount(){
f@0: return tracks.size();
f@0: }
f@0:
f@0: public Rule getRule(){
f@0: return rule;
f@0: }
f@0:
f@0: /**
f@0: *
f@0: * On {@code SELECTION_CHANGED} event from the current audio track, updates the other
f@0: * tracks accordingly.
f@0: *
f@0: * This enforces a unique selection and cursor position over the whole arrange window
f@0: *
f@0: * @param evt the sound wave event
f@0: *
f@0: */
f@0: @Override
f@0: public void update(SoundWaveEvent evt) {
f@0:
f@0: if(SoundWaveEvent.SCAN.equals(evt.getType())){
f@0: for(int i=0; i< getTracksCount(); i++){
f@0: SoundWave wave = getTrackAt(i).getSoundWave();
f@0: /* only change selection if it's not the one that triggered the event */
f@0: if(!wave.equals(evt.getSource())){
f@0: wave.scan((Integer)evt.getArgs());
f@0: }
f@0: }
f@0: }
f@0:
f@0: if(SoundWaveEvent.POSITION_CHANGED.equals(evt.getType()) ){
f@0: for(AudioTrack track : tracks){
f@0: if(!track.getSoundWave().equals(evt.getSource())){
f@0: track.getSoundWave().setPosition((Integer)evt.getArgs());
f@0: }
f@0: }
f@0:
f@0: /* this is called from the current audio track sound wave. The *
f@0: * selection is changed in the other audio tracks accordingly */
f@0: //SoundWave currentWave = evt.getSource();
f@0:
f@0: // for(int i=0; i< getTracksCount(); i++){
f@0: // SoundWave wave = getTrackAt(i).getSoundWave();
f@0: // /* only change selection if it's not the one that triggered the event */
f@0: // if(!wave.equals(currentWave)){
f@0: // wave.setSelection((Selection)evt.getArgs());
f@0: // }
f@0: // }
f@0: }
f@0: }
f@0:
f@0: private class MouseInteraction extends MouseAdapter {
f@0: @Override
f@0: public void mousePressed(MouseEvent e){
f@0: Component c = e.getComponent();
f@0: int index = tracks.lastIndexOf(c);
f@0:
f@0: if(index == getCurrentTrackIndex()){
f@0: return;
f@0: }else if(index != -1){
f@0: setCurrentTrack(index);
f@0: return;
f@0: }
f@0:
f@0: index = audioTrackParameters.lastIndexOf(c);
f@0: if(index == getCurrentTrackIndex()){
f@0: return;
f@0: }else if(index != -1){
f@0: setCurrentTrack(index);
f@0: return;
f@0: }
f@0:
f@0: }
f@0: }
f@0:
f@0: @Override
f@0: public void propertyChange(PropertyChangeEvent evt) {
f@0: switch(evt.getPropertyName()){
f@0: case "mouseDragSelection" : {
f@0: Selection selection = (Selection)evt.getNewValue();
f@0: for(AudioTrack track : tracks){
f@0: if(!track.equals(evt.getSource())){
f@0: track.trackInteraction.setMouseSelection(
f@0: selection.getStart(), selection.getEnd());
f@0: }
f@0: }
f@0: } break;
f@0: }
f@0: }
f@0:
f@0:
f@0:
f@0: }