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: }