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.Color;
|
f@0
|
22 import java.awt.Cursor;
|
f@0
|
23 import java.awt.Dimension;
|
f@0
|
24 import java.awt.Graphics;
|
f@0
|
25 import java.awt.Graphics2D;
|
f@0
|
26 import java.awt.Polygon;
|
f@0
|
27 import java.awt.Rectangle;
|
f@0
|
28 import java.awt.event.MouseEvent;
|
f@0
|
29 import java.awt.geom.Line2D;
|
f@0
|
30 import java.beans.PropertyChangeEvent;
|
f@0
|
31 import java.beans.PropertyChangeListener;
|
f@0
|
32 import java.text.DecimalFormat;
|
f@0
|
33
|
f@0
|
34 import javax.accessibility.Accessible;
|
f@0
|
35 import javax.accessibility.AccessibleContext;
|
f@0
|
36 import javax.accessibility.AccessibleRole;
|
f@0
|
37 import javax.accessibility.AccessibleState;
|
f@0
|
38 import javax.accessibility.AccessibleStateSet;
|
f@0
|
39 import javax.swing.BorderFactory;
|
f@0
|
40 import javax.swing.JComponent;
|
f@0
|
41 import javax.swing.event.MouseInputAdapter;
|
f@0
|
42
|
f@0
|
43 import uk.ac.qmul.eecs.depic.daw.Selection;
|
f@0
|
44 import uk.ac.qmul.eecs.depic.daw.SoundWave;
|
f@0
|
45
|
f@2
|
46 /**
|
f@2
|
47 *
|
f@2
|
48 * The rule on top of the arrange window.
|
f@2
|
49 *
|
f@2
|
50 */
|
f@0
|
51 public class Rule extends JComponent implements PropertyChangeListener , Accessible {
|
f@0
|
52 private static final long serialVersionUID = 1L;
|
f@0
|
53 public static final int DEFAULT_TICK_INTERVAL = 50;
|
f@0
|
54 public static final int TICK_HEIGHT = 10;
|
f@0
|
55 public static final int HEIGHT = 40;
|
f@0
|
56 public static final int CURSOR_HEIGHT = 10;
|
f@0
|
57 public static final int CURSOR_WIDTH = 20;
|
f@0
|
58 public static final Color LOOP_MARKERS_COLOR = new Color(10,100,30);
|
f@0
|
59 public static final Color CURSOR_COLOR = AudioTrack.WAVE_COLOR;
|
f@0
|
60 private static final DecimalFormat TICK_FORMAT = new DecimalFormat("###.#");
|
f@0
|
61
|
f@0
|
62 private AudioTrack track;
|
f@0
|
63 private int scaleFactor;
|
f@0
|
64 private Polygon cursor;
|
f@0
|
65 private Polygon loopStartMark;
|
f@0
|
66 private Polygon loopEndMark;
|
f@0
|
67
|
f@0
|
68
|
f@0
|
69 public Rule(){
|
f@0
|
70 Dimension size = new Dimension(AudioTrack.MAX_TRACK_WIDTH,HEIGHT);
|
f@0
|
71 setMaximumSize(size);
|
f@0
|
72
|
f@0
|
73 setPreferredSize(new Dimension(0,HEIGHT));
|
f@0
|
74 setBorder(BorderFactory.createMatteBorder(0,0,1,0,Color.black));
|
f@0
|
75
|
f@0
|
76 /* a triangle initially centred on 0*/
|
f@0
|
77 cursor = new Polygon(
|
f@0
|
78 new int[]{0,-CURSOR_WIDTH/2,CURSOR_WIDTH/2},
|
f@0
|
79 new int[] {HEIGHT,HEIGHT-CURSOR_HEIGHT,HEIGHT-CURSOR_HEIGHT},
|
f@0
|
80 3);
|
f@0
|
81 loopStartMark = new Polygon();
|
f@0
|
82 loopEndMark = new Polygon();
|
f@0
|
83
|
f@0
|
84 MouseInteraction mouseInteraction = new MouseInteraction();
|
f@0
|
85 addMouseListener(mouseInteraction);
|
f@0
|
86 addMouseMotionListener(mouseInteraction);
|
f@0
|
87 }
|
f@0
|
88
|
f@0
|
89 @Override
|
f@0
|
90 public void paintComponent(Graphics g){
|
f@0
|
91 Graphics2D g2 = (Graphics2D)g;
|
f@0
|
92 Color oldColor = g2.getColor();
|
f@0
|
93
|
f@0
|
94 g2.setColor(getBackground());
|
f@0
|
95 g2.fillRect(0, 0, getWidth(), getHeight());
|
f@0
|
96 g2.setColor(getForeground());
|
f@0
|
97
|
f@0
|
98 Line2D line = new Line2D.Float();
|
f@0
|
99 int height = getHeight();
|
f@0
|
100 /* draw rule's ticks */
|
f@0
|
101
|
f@0
|
102 if(track.getSecondsPerPixel() == 0){
|
f@0
|
103 for(int i=DEFAULT_TICK_INTERVAL; i< getSize().width; i += DEFAULT_TICK_INTERVAL ){
|
f@0
|
104 line.setLine(i, height, i, height-TICK_HEIGHT);
|
f@0
|
105 g2.draw(line);
|
f@0
|
106 }
|
f@0
|
107 }else{
|
f@0
|
108 int tickInterval = (int) ( (1/track.getSecondsPerPixel() / 2 ));
|
f@0
|
109 for(int i=tickInterval, j=1; i< getSize().width; i += tickInterval, j++ ){
|
f@0
|
110 if(j % 2 == 1){
|
f@0
|
111 line.setLine(i, height, i, height-TICK_HEIGHT/3);
|
f@0
|
112 g2.draw(line);
|
f@0
|
113 }else{
|
f@0
|
114 line.setLine(i, height, i, height-TICK_HEIGHT);
|
f@0
|
115 g2.draw(line);
|
f@0
|
116 String tick = TICK_FORMAT.format(j/2);//track.getSecondsPerPixel()*i);
|
f@0
|
117 int stringLen = (int)g2.getFontMetrics().getStringBounds(tick, g2).getWidth();
|
f@0
|
118 g2.drawString(tick, i-(stringLen/2), height-TICK_HEIGHT-3);
|
f@0
|
119 }
|
f@0
|
120 }
|
f@0
|
121 }
|
f@0
|
122
|
f@0
|
123
|
f@0
|
124 /* draw the cursor in WAVE_COLOR */
|
f@0
|
125 g2.setColor(CURSOR_COLOR);
|
f@0
|
126 g2.fill(cursor);
|
f@0
|
127
|
f@0
|
128 /* draw the loop markers, if any */
|
f@0
|
129 g2.setColor(LOOP_MARKERS_COLOR);
|
f@0
|
130 g2.fill(loopStartMark);
|
f@0
|
131 g2.fill(loopEndMark);
|
f@0
|
132 if(loopEndMark.npoints != 0){
|
f@0
|
133 g2.drawLine(loopStartMark.xpoints[0],
|
f@0
|
134 HEIGHT - (loopStartMark.getBounds().height/2),
|
f@0
|
135 loopEndMark.xpoints[0]-1,// with -1 it's drawn it better
|
f@0
|
136 HEIGHT - (loopEndMark.getBounds().height/2)
|
f@0
|
137 );
|
f@0
|
138 }
|
f@0
|
139
|
f@0
|
140 g2.setColor(oldColor);
|
f@0
|
141 }
|
f@0
|
142
|
f@0
|
143 public void setAudioTrack(AudioTrack t){
|
f@0
|
144 /* removes itself as a listener to the previous track, if any */
|
f@0
|
145 if(track != null){
|
f@0
|
146 track.removePropertyChangeListener(this);
|
f@0
|
147 }
|
f@0
|
148 /* adds itself as a listener to the new track */
|
f@0
|
149 if(t != null){
|
f@0
|
150 t.addPropertyChangeListener(this);
|
f@0
|
151 scaleFactor = t.getScaleFactor();
|
f@0
|
152 }else {
|
f@0
|
153 scaleFactor = 1;
|
f@0
|
154 }
|
f@0
|
155 track = t;
|
f@0
|
156 }
|
f@0
|
157
|
f@0
|
158 public int getCursorPos() {
|
f@0
|
159 Rectangle bounds = cursor.getBounds();
|
f@0
|
160 return bounds.x + bounds.width/2;
|
f@0
|
161 }
|
f@0
|
162
|
f@0
|
163 public void setCursorPos(int position) {
|
f@0
|
164 Rectangle bounds = cursor.getBounds();
|
f@0
|
165 int currentCenterX = bounds.x + bounds.width/2;
|
f@0
|
166 cursor.translate(position-currentCenterX, 0);
|
f@0
|
167 }
|
f@0
|
168
|
f@0
|
169 @Override
|
f@0
|
170 public void propertyChange(PropertyChangeEvent evt) {
|
f@0
|
171 switch(evt.getPropertyName()){
|
f@0
|
172 case "cursorPos" : {
|
f@0
|
173 setCursorPos((Integer)evt.getNewValue());
|
f@0
|
174 repaint();
|
f@0
|
175 break;
|
f@0
|
176 }
|
f@0
|
177 case "scaleFactor" : {
|
f@0
|
178 scaleFactor = (Integer)evt.getNewValue();
|
f@0
|
179 repaint();
|
f@0
|
180 break;
|
f@0
|
181 }
|
f@0
|
182 case "mouseDragSelection" : {
|
f@0
|
183 Selection selection = (Selection)evt.getNewValue();
|
f@0
|
184 if(selection.isOpen() ){ // no loop
|
f@0
|
185 loopStartMark.reset();
|
f@0
|
186 loopEndMark.reset();
|
f@0
|
187 }else{
|
f@0
|
188 loopStartMark.reset();
|
f@0
|
189 /* first point is the actual marker */
|
f@0
|
190 loopStartMark.addPoint(selection.getStart(), HEIGHT-(CURSOR_HEIGHT));
|
f@0
|
191 loopStartMark.addPoint(selection.getStart()+(CURSOR_WIDTH/2), HEIGHT-(CURSOR_HEIGHT*2));
|
f@0
|
192 loopStartMark.addPoint(selection.getStart()+(CURSOR_WIDTH/2),HEIGHT);
|
f@0
|
193
|
f@0
|
194 loopEndMark.reset();
|
f@0
|
195 /* first point is the actual marker */
|
f@0
|
196 loopEndMark.addPoint(selection.getEnd(),HEIGHT-(CURSOR_HEIGHT));
|
f@0
|
197 loopEndMark.addPoint(selection.getEnd()-(CURSOR_WIDTH/2),HEIGHT);
|
f@0
|
198 loopEndMark.addPoint(selection.getEnd()-(CURSOR_WIDTH/2),HEIGHT-(CURSOR_HEIGHT*2));
|
f@0
|
199 }
|
f@0
|
200 repaint();
|
f@0
|
201 break;
|
f@0
|
202 }
|
f@0
|
203 case "preferredSize" : {
|
f@0
|
204 /* resize the width acording to the new value of the listened component */
|
f@0
|
205 Dimension d = (Dimension)evt.getNewValue();
|
f@0
|
206 setPreferredSize(new Dimension(d.width,HEIGHT));
|
f@0
|
207 break;
|
f@0
|
208 }
|
f@0
|
209 }
|
f@0
|
210 }
|
f@0
|
211
|
f@0
|
212 @Override
|
f@0
|
213 public AccessibleContext getAccessibleContext(){
|
f@0
|
214 if(accessibleContext == null){
|
f@0
|
215 accessibleContext = new AccessibleRule();
|
f@0
|
216 }
|
f@0
|
217 return accessibleContext;
|
f@0
|
218 }
|
f@0
|
219
|
f@0
|
220 private class MouseInteraction extends MouseInputAdapter {
|
f@0
|
221 private Polygon clickedMark; // either loopStartMark or loopEndMark
|
f@0
|
222 int clickOffset;
|
f@0
|
223
|
f@0
|
224 @Override
|
f@0
|
225 public void mousePressed(MouseEvent evt){
|
f@0
|
226 if(loopStartMark.contains(evt.getX(),evt.getY())){ // click on the loop start mark
|
f@0
|
227 clickedMark = loopStartMark;
|
f@0
|
228 clickOffset = evt.getX() - loopStartMark.xpoints[0];
|
f@0
|
229 }else if(loopEndMark.contains(evt.getX(),evt.getY())){ // click on the loop end mark
|
f@0
|
230 clickedMark = loopEndMark;
|
f@0
|
231 clickOffset = evt.getX() - loopEndMark.xpoints[0];
|
f@0
|
232 }else if(cursor.contains(evt.getX(),evt.getY())){
|
f@0
|
233 clickedMark = cursor;
|
f@0
|
234 clickOffset = evt.getX() - cursor.xpoints[0];
|
f@0
|
235 }
|
f@0
|
236 }
|
f@0
|
237
|
f@0
|
238 @Override
|
f@0
|
239 public void mouseDragged(MouseEvent evt){
|
f@0
|
240 /* check again that clickedMark is != null and not reset *
|
f@0
|
241 * things might change in the middle of the mouse interaction *
|
f@0
|
242 * if the property change listener is called from another source */
|
f@0
|
243 if(clickedMark == null)
|
f@0
|
244 return;
|
f@0
|
245
|
f@0
|
246 if(clickedMark != cursor && clickedMark.npoints > 0 && track != null){ // loop marks
|
f@0
|
247 int mouseSelectionX = 0;
|
f@0
|
248 int mouseSelectionY = 0;
|
f@0
|
249 if(clickedMark == loopStartMark){ // dragging the start mark
|
f@0
|
250 if(evt.getX() > loopEndMark.xpoints[0] - 1 || (evt.getX()-clickOffset < 0) )
|
f@0
|
251 return; // can't drag start mark too right, past the end mark or too left past the screen
|
f@0
|
252 mouseSelectionX = evt.getX()-clickOffset;
|
f@0
|
253 mouseSelectionY = loopEndMark.xpoints[0];
|
f@0
|
254 }else{ // dragging the end mark
|
f@0
|
255 if(evt.getX() < loopStartMark.xpoints[0] + 1)
|
f@0
|
256 return; // can't drag end mark too left, past the end mark
|
f@0
|
257 mouseSelectionX = loopStartMark.xpoints[0];
|
f@0
|
258 mouseSelectionY = evt.getX()-clickOffset;
|
f@0
|
259 }
|
f@0
|
260 scrollRectToVisible(new Rectangle(evt.getX(), evt.getY(), 1, 1));
|
f@0
|
261 track.trackInteraction.setMouseSelection(mouseSelectionX, mouseSelectionY);
|
f@0
|
262 } else if (clickedMark == cursor){ // cursor mark
|
f@0
|
263 track.getSoundWave().scan(evt.getX());
|
f@0
|
264 }
|
f@0
|
265 }
|
f@0
|
266
|
f@0
|
267 @Override
|
f@0
|
268 public void mouseReleased(MouseEvent evt){
|
f@0
|
269 if(clickedMark != null){
|
f@0
|
270 if(clickedMark == cursor){
|
f@0
|
271 track.getSoundWave().scan(SoundWave.STOP_SCANNING); // stop scrubbing
|
f@0
|
272 }else
|
f@0
|
273 track.getSoundWave().setSelection(track.trackInteraction.getMouseSelection());
|
f@0
|
274 }
|
f@0
|
275 clickedMark = null;
|
f@0
|
276 clickOffset = 0;
|
f@0
|
277 }
|
f@0
|
278
|
f@0
|
279 @Override
|
f@0
|
280 public void mouseMoved(MouseEvent evt){
|
f@0
|
281 if(cursor.contains(evt.getPoint())){
|
f@0
|
282 setCursor(new Cursor(Cursor.E_RESIZE_CURSOR));
|
f@0
|
283 }else if(loopStartMark.contains(evt.getPoint())){
|
f@0
|
284 setCursor(new Cursor(Cursor.E_RESIZE_CURSOR));
|
f@0
|
285 }else if(loopEndMark.contains(evt.getPoint())){
|
f@0
|
286 setCursor(new Cursor(Cursor.W_RESIZE_CURSOR));
|
f@0
|
287 }else{
|
f@0
|
288 setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
f@0
|
289 }
|
f@0
|
290 }
|
f@0
|
291 }
|
f@0
|
292
|
f@0
|
293 protected class AccessibleRule extends AccessibleJComponent {
|
f@0
|
294 private static final long serialVersionUID = 1L;
|
f@0
|
295
|
f@0
|
296 protected AccessibleRule(){
|
f@0
|
297 this.setAccessibleName("Time Bar");
|
f@0
|
298 this.setAccessibleDescription( "Measures he time of tracks");
|
f@0
|
299 }
|
f@0
|
300
|
f@0
|
301 @Override
|
f@0
|
302 public AccessibleRole getAccessibleRole(){
|
f@0
|
303 return AccessibleRole.RULER;
|
f@0
|
304 }
|
f@0
|
305
|
f@0
|
306 @Override
|
f@0
|
307 public AccessibleStateSet getAccessibleStateSet(){
|
f@0
|
308 AccessibleStateSet states = super.getAccessibleStateSet();
|
f@0
|
309
|
f@0
|
310 states.add(AccessibleState.HORIZONTAL);
|
f@0
|
311 states.add(AccessibleState.VISIBLE);
|
f@0
|
312
|
f@0
|
313 return states;
|
f@0
|
314 }
|
f@0
|
315 }
|
f@0
|
316
|
f@0
|
317 }
|