f@0
|
1 /*
|
f@0
|
2 Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation.
|
f@0
|
3
|
f@0
|
4 Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)
|
f@0
|
5
|
f@0
|
6 This program is free software: you can redistribute it and/or modify
|
f@0
|
7 it under the terms of the GNU General Public License as published by
|
f@0
|
8 the Free Software Foundation, either version 3 of the License, or
|
f@0
|
9 (at your option) any later version.
|
f@0
|
10
|
f@0
|
11 This program is distributed in the hope that it will be useful,
|
f@0
|
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
f@0
|
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
f@0
|
14 GNU General Public License for more details.
|
f@0
|
15
|
f@0
|
16 You should have received a copy of the GNU General Public License
|
f@0
|
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
f@0
|
18 */
|
f@0
|
19 package uk.ac.qmul.eecs.depic.daw.gui;
|
f@0
|
20
|
f@0
|
21 import java.awt.BorderLayout;
|
f@0
|
22 import java.awt.Color;
|
f@0
|
23 import java.awt.Component;
|
f@0
|
24 import java.awt.Dimension;
|
f@0
|
25 import java.awt.event.KeyEvent;
|
f@0
|
26 import java.awt.event.MouseAdapter;
|
f@0
|
27 import java.awt.event.MouseEvent;
|
f@0
|
28 import java.beans.PropertyChangeEvent;
|
f@0
|
29 import java.beans.PropertyChangeListener;
|
f@0
|
30 import java.util.ArrayList;
|
f@0
|
31 import java.util.List;
|
f@0
|
32
|
f@0
|
33 import javax.swing.BorderFactory;
|
f@0
|
34 import javax.swing.Box;
|
f@0
|
35 import javax.swing.BoxLayout;
|
f@0
|
36 import javax.swing.JComponent;
|
f@0
|
37 import javax.swing.JPanel;
|
f@0
|
38 import javax.swing.JScrollPane;
|
f@0
|
39 import javax.swing.KeyStroke;
|
f@0
|
40 import javax.swing.border.Border;
|
f@0
|
41
|
f@0
|
42 import uk.ac.qmul.eecs.depic.daw.Selection;
|
f@0
|
43 import uk.ac.qmul.eecs.depic.daw.SoundWave;
|
f@0
|
44 import uk.ac.qmul.eecs.depic.daw.SoundWaveEvent;
|
f@0
|
45 import uk.ac.qmul.eecs.depic.daw.SoundWaveListener;
|
f@0
|
46
|
f@0
|
47 /**
|
f@0
|
48 *
|
f@0
|
49 * The panel containing the audio tracks.
|
f@0
|
50 *
|
f@0
|
51 * Tracks are arranged in a scrollable BoxLayout.
|
f@0
|
52 *
|
f@0
|
53 */
|
f@0
|
54 public class ArrangeWindow extends JPanel implements PropertyChangeListener, SoundWaveListener {
|
f@0
|
55 private static final long serialVersionUID = 1L;
|
f@0
|
56 public static final Color BACKGROUND_COLOR = new Color(226,226,226);
|
f@0
|
57 public static final int SPACE_BETWEEN_TRACKS = 5;
|
f@0
|
58 public static final Border BORDER_UNSELECTED = BorderFactory.createCompoundBorder(
|
f@0
|
59 BorderFactory.createEmptyBorder(1, 0, 1, 0), BorderFactory.createMatteBorder(1, 0, 1, 0, Color.GRAY));
|
f@0
|
60 public static final Border BORDER_SELECTED = BorderFactory.createCompoundBorder(
|
f@0
|
61 BorderFactory.createMatteBorder(1, 0, 1, 0, new Color(214, 193, 99)), BorderFactory.createMatteBorder(1, 0, 1, 0, Color.GRAY));
|
f@0
|
62
|
f@0
|
63
|
f@0
|
64 /* tracks panel contains the rule and audio tracks */
|
f@0
|
65 private JScrollPane tracksPanelScroll;
|
f@0
|
66 /* view of the trackPanelScroll and panel where all the audio tracks are places */
|
f@0
|
67 private JPanel tracksPanel;
|
f@0
|
68 /* left panel with AudioTrackParameters */
|
f@0
|
69 private JPanel parametersPanel;
|
f@0
|
70 private Rule rule;
|
f@0
|
71 private int currentTrackIndex;
|
f@0
|
72 private List<AudioTrack> tracks;
|
f@0
|
73 private List<AudioTrackParameters> audioTrackParameters;
|
f@0
|
74 private List<Component> spacesBetweenTracks;
|
f@0
|
75 private List<Component> spacesBetweenTrackParameters;
|
f@0
|
76 private MouseInteraction mouseInteraction;
|
f@0
|
77
|
f@0
|
78
|
f@0
|
79 public ArrangeWindow(){
|
f@0
|
80 tracks = new ArrayList<>();
|
f@0
|
81 audioTrackParameters = new ArrayList<>();
|
f@0
|
82 spacesBetweenTracks = new ArrayList<>();
|
f@0
|
83 spacesBetweenTrackParameters = new ArrayList<>();
|
f@0
|
84 mouseInteraction = new MouseInteraction();
|
f@0
|
85
|
f@0
|
86 setLayout(new BorderLayout());
|
f@0
|
87 setBackground(BACKGROUND_COLOR);
|
f@0
|
88
|
f@0
|
89 /* left panel: header (of the same height as rule) and parameters */
|
f@0
|
90 parametersPanel = new JPanel();
|
f@0
|
91 parametersPanel.setLayout(new BoxLayout(parametersPanel,BoxLayout.Y_AXIS));
|
f@0
|
92 parametersPanel.add(Box.createRigidArea(new Dimension(247,Rule.HEIGHT+3)));
|
f@0
|
93 parametersPanel.setBackground(BACKGROUND_COLOR);
|
f@0
|
94
|
f@0
|
95 add(parametersPanel,BorderLayout.WEST);
|
f@0
|
96
|
f@0
|
97 /* right panel: scrollable panel with rule and tracks as header*/
|
f@0
|
98 tracksPanel = new JPanel();
|
f@0
|
99 tracksPanel.setLayout(new BoxLayout(tracksPanel,BoxLayout.Y_AXIS));
|
f@0
|
100 tracksPanel.setBackground(BACKGROUND_COLOR);
|
f@0
|
101 rule = new Rule();
|
f@0
|
102 rule.setBackground(BACKGROUND_COLOR);
|
f@0
|
103
|
f@0
|
104
|
f@0
|
105 tracksPanelScroll = new JScrollPane(tracksPanel,
|
f@0
|
106 JScrollPane.VERTICAL_SCROLLBAR_NEVER,
|
f@0
|
107 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
f@0
|
108
|
f@0
|
109 /* disable the scrolling via the left-right keys to let the cursor scrub handler
|
f@0
|
110 * take over when the track is focused */
|
f@0
|
111 tracksPanelScroll.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "none");
|
f@0
|
112 tracksPanelScroll.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "none");
|
f@0
|
113 /* setPreferredSize important, otherwise the view port gets resized to the track size *
|
f@0
|
114 * as soon as the window is resized and therefore the scroll bar disappear */
|
f@0
|
115 tracksPanelScroll.setPreferredSize(new Dimension(500,500));
|
f@0
|
116 /* rule is set as the header of the scroll pane with audio tracks */
|
f@0
|
117 tracksPanelScroll.setColumnHeaderView(rule);
|
f@0
|
118
|
f@0
|
119 add(tracksPanelScroll, BorderLayout.CENTER);
|
f@0
|
120
|
f@0
|
121 currentTrackIndex = -1;
|
f@0
|
122 }
|
f@0
|
123
|
f@0
|
124 /**
|
f@0
|
125 * Adds a track to this component. The added track is automatically set as the current track.
|
f@0
|
126 *
|
f@0
|
127 * Adding a track that's already in the arrange window will have no effect.
|
f@0
|
128 *
|
f@0
|
129 * @param track the new track to add. It must be a track that is not currently
|
f@0
|
130 * contained in this arrange window
|
f@0
|
131 */
|
f@0
|
132 public void addTrack(AudioTrack track){
|
f@0
|
133 if(tracks.contains(track))
|
f@0
|
134 return;
|
f@0
|
135
|
f@0
|
136 AudioTrackParameters trackParameters = new AudioTrackParameters(track,1);
|
f@0
|
137
|
f@0
|
138 /* install the mouse listener */
|
f@0
|
139 track.addMouseListener(mouseInteraction);
|
f@0
|
140 trackParameters.addMouseListener(mouseInteraction); // FIXME verify
|
f@0
|
141
|
f@0
|
142
|
f@0
|
143 /* add track and parameters in the respective lists */
|
f@0
|
144 tracks.add(track);
|
f@0
|
145 audioTrackParameters.add(trackParameters);
|
f@0
|
146 Component box1 = Box.createVerticalStrut(SPACE_BETWEEN_TRACKS);
|
f@0
|
147 Component box2 = Box.createVerticalStrut(SPACE_BETWEEN_TRACKS);
|
f@0
|
148 spacesBetweenTracks.add(box1);
|
f@0
|
149 spacesBetweenTrackParameters.add(box2);
|
f@0
|
150
|
f@0
|
151 /* add track and parameters in the respective panels */
|
f@0
|
152 parametersPanel.add(box1);
|
f@0
|
153 parametersPanel.add(trackParameters);
|
f@0
|
154 tracksPanel.add(box2);
|
f@0
|
155 tracksPanel.add(track);
|
f@0
|
156 /* set the new track as the current */
|
f@0
|
157 setCurrentTrack(getTracksCount()-1);
|
f@0
|
158
|
f@0
|
159 /* set the label. Users will count starting from 1 and not from 0 */
|
f@0
|
160 trackParameters.setLabel("new Track "+(getCurrentTrackIndex()+1));
|
f@0
|
161 repaint();
|
f@0
|
162 }
|
f@0
|
163
|
f@0
|
164 public AudioTrack getTrackAt(int index){
|
f@0
|
165 return tracks.get(index);
|
f@0
|
166 }
|
f@0
|
167
|
f@0
|
168 public AudioTrackParameters getTrackParametersAt(int index){
|
f@0
|
169 return audioTrackParameters.get(index);
|
f@0
|
170 }
|
f@0
|
171
|
f@0
|
172 /**
|
f@0
|
173 *
|
f@0
|
174 * @return the current track or {@code null} if there are no tracks
|
f@0
|
175 * in this arrange window
|
f@0
|
176 */
|
f@0
|
177 public AudioTrack getCurrentTrack(){
|
f@0
|
178 if(currentTrackIndex == -1)
|
f@0
|
179 return null;
|
f@0
|
180 return tracks.get(currentTrackIndex);
|
f@0
|
181 }
|
f@0
|
182
|
f@0
|
183 /**
|
f@0
|
184 * Sets the track at the specified index as current.
|
f@0
|
185 *
|
f@0
|
186 * @param index the index of the new current track
|
f@0
|
187 * @throws ArrayIndexOutOfBoundsException if {@code index} is lower than 0 or greater or equal to {@code getTracksCount()}
|
f@0
|
188 *
|
f@0
|
189 *
|
f@0
|
190 */
|
f@0
|
191 public void setCurrentTrack(int index){
|
f@0
|
192 /* removes this from the listeners of the previous current track */
|
f@0
|
193 if(currentTrackIndex != -1){
|
f@0
|
194 AudioTrack previousTrack = getTrackAt(currentTrackIndex);
|
f@0
|
195 previousTrack.removePropertyChangeListener(this);
|
f@0
|
196 previousTrack.getSoundWave().removeSoundWaveListener(this);
|
f@0
|
197 previousTrack.setBorder(BORDER_UNSELECTED);
|
f@0
|
198 audioTrackParameters.get(currentTrackIndex).setBorder(BORDER_UNSELECTED);
|
f@0
|
199 }
|
f@0
|
200 currentTrackIndex = index;
|
f@0
|
201
|
f@0
|
202 AudioTrack currentTrack = getTrackAt(currentTrackIndex);
|
f@0
|
203 /* install listeners in the new audio track */
|
f@0
|
204 currentTrack.addPropertyChangeListener(this);
|
f@0
|
205 currentTrack.getSoundWave().addSoundWaveListener(this);
|
f@0
|
206 /* set the borders */
|
f@0
|
207 currentTrack.setBorder(BORDER_SELECTED);
|
f@0
|
208 audioTrackParameters.get(currentTrackIndex).setBorder(BORDER_SELECTED);
|
f@0
|
209
|
f@0
|
210 rule.setAudioTrack(getTrackAt(currentTrackIndex));
|
f@0
|
211 }
|
f@0
|
212
|
f@0
|
213 /**
|
f@0
|
214 *
|
f@0
|
215 * @return the index of the currently selected track or {@code -1} if no track
|
f@0
|
216 * is open in the arrange window
|
f@0
|
217 */
|
f@0
|
218 public int getCurrentTrackIndex(){
|
f@0
|
219 return currentTrackIndex;
|
f@0
|
220 }
|
f@0
|
221
|
f@0
|
222 /**
|
f@0
|
223 * Returns the number of tracks in this arrange window.
|
f@0
|
224 *
|
f@0
|
225 * @return the number of tracks in this arrange window
|
f@0
|
226 */
|
f@0
|
227 public int getTracksCount(){
|
f@0
|
228 return tracks.size();
|
f@0
|
229 }
|
f@0
|
230
|
f@0
|
231 public Rule getRule(){
|
f@0
|
232 return rule;
|
f@0
|
233 }
|
f@0
|
234
|
f@0
|
235 /**
|
f@0
|
236 *
|
f@0
|
237 * On {@code SELECTION_CHANGED} event from the current audio track, updates the other
|
f@0
|
238 * tracks accordingly.
|
f@0
|
239 *
|
f@0
|
240 * This enforces a unique selection and cursor position over the whole arrange window
|
f@0
|
241 *
|
f@0
|
242 * @param evt the sound wave event
|
f@0
|
243 *
|
f@0
|
244 */
|
f@0
|
245 @Override
|
f@0
|
246 public void update(SoundWaveEvent evt) {
|
f@0
|
247
|
f@0
|
248 if(SoundWaveEvent.SCAN.equals(evt.getType())){
|
f@0
|
249 for(int i=0; i< getTracksCount(); i++){
|
f@0
|
250 SoundWave wave = getTrackAt(i).getSoundWave();
|
f@0
|
251 /* only change selection if it's not the one that triggered the event */
|
f@0
|
252 if(!wave.equals(evt.getSource())){
|
f@0
|
253 wave.scan((Integer)evt.getArgs());
|
f@0
|
254 }
|
f@0
|
255 }
|
f@0
|
256 }
|
f@0
|
257
|
f@0
|
258 if(SoundWaveEvent.POSITION_CHANGED.equals(evt.getType()) ){
|
f@0
|
259 for(AudioTrack track : tracks){
|
f@0
|
260 if(!track.getSoundWave().equals(evt.getSource())){
|
f@0
|
261 track.getSoundWave().setPosition((Integer)evt.getArgs());
|
f@0
|
262 }
|
f@0
|
263 }
|
f@0
|
264
|
f@0
|
265 /* this is called from the current audio track sound wave. The *
|
f@0
|
266 * selection is changed in the other audio tracks accordingly */
|
f@0
|
267 //SoundWave currentWave = evt.getSource();
|
f@0
|
268
|
f@0
|
269 // for(int i=0; i< getTracksCount(); i++){
|
f@0
|
270 // SoundWave wave = getTrackAt(i).getSoundWave();
|
f@0
|
271 // /* only change selection if it's not the one that triggered the event */
|
f@0
|
272 // if(!wave.equals(currentWave)){
|
f@0
|
273 // wave.setSelection((Selection)evt.getArgs());
|
f@0
|
274 // }
|
f@0
|
275 // }
|
f@0
|
276 }
|
f@0
|
277 }
|
f@0
|
278
|
f@0
|
279 private class MouseInteraction extends MouseAdapter {
|
f@0
|
280 @Override
|
f@0
|
281 public void mousePressed(MouseEvent e){
|
f@0
|
282 Component c = e.getComponent();
|
f@0
|
283 int index = tracks.lastIndexOf(c);
|
f@0
|
284
|
f@0
|
285 if(index == getCurrentTrackIndex()){
|
f@0
|
286 return;
|
f@0
|
287 }else if(index != -1){
|
f@0
|
288 setCurrentTrack(index);
|
f@0
|
289 return;
|
f@0
|
290 }
|
f@0
|
291
|
f@0
|
292 index = audioTrackParameters.lastIndexOf(c);
|
f@0
|
293 if(index == getCurrentTrackIndex()){
|
f@0
|
294 return;
|
f@0
|
295 }else if(index != -1){
|
f@0
|
296 setCurrentTrack(index);
|
f@0
|
297 return;
|
f@0
|
298 }
|
f@0
|
299
|
f@0
|
300 }
|
f@0
|
301 }
|
f@0
|
302
|
f@0
|
303 @Override
|
f@0
|
304 public void propertyChange(PropertyChangeEvent evt) {
|
f@0
|
305 switch(evt.getPropertyName()){
|
f@0
|
306 case "mouseDragSelection" : {
|
f@0
|
307 Selection selection = (Selection)evt.getNewValue();
|
f@0
|
308 for(AudioTrack track : tracks){
|
f@0
|
309 if(!track.equals(evt.getSource())){
|
f@0
|
310 track.trackInteraction.setMouseSelection(
|
f@0
|
311 selection.getStart(), selection.getEnd());
|
f@0
|
312 }
|
f@0
|
313 }
|
f@0
|
314 } break;
|
f@0
|
315 }
|
f@0
|
316 }
|
f@0
|
317
|
f@0
|
318
|
f@0
|
319
|
f@0
|
320 }
|