source: trunk/src_apollo/org/apollo/gui/SampledTrackGraphViewPort.java@ 315

Last change on this file since 315 was 315, checked in by bjn8, 16 years ago

Apollo spin-off added

File size: 16.4 KB
Line 
1package org.apollo.gui;
2
3import java.awt.BasicStroke;
4import java.awt.Color;
5import java.awt.Event;
6import java.awt.Font;
7import java.awt.FontMetrics;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.Stroke;
11import java.awt.event.ComponentEvent;
12import java.awt.event.ComponentListener;
13import java.awt.event.KeyEvent;
14import java.awt.event.KeyListener;
15import java.awt.event.MouseEvent;
16import java.awt.event.MouseListener;
17import java.awt.event.MouseMotionListener;
18import java.awt.event.MouseWheelEvent;
19import java.awt.event.MouseWheelListener;
20import java.awt.geom.Rectangle2D;
21import java.awt.image.BufferedImage;
22import java.util.LinkedList;
23import java.util.List;
24
25import org.apollo.audio.ApolloSubjectChangedEvent;
26import org.apollo.audio.SampledTrackModel;
27import org.apollo.mvc.Subject;
28import org.apollo.mvc.SubjectChangedEvent;
29import org.apollo.util.AudioMath;
30import org.expeditee.gui.Browser;
31
32public class SampledTrackGraphViewPort extends SampledTrackGraphView
33 implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener {
34
35 private static final long serialVersionUID = 1L;
36
37 private static final int ZOOM_SELECTION_PIXEL_RANGE_THRESHOLD = 1;
38 private static final int COARSE_PAN_PIXEL_LENGTH = 14;
39 private static final int SCROLL_ZOOM_PIXEL_LENGTH = 10; // for scroll-wheel
40
41 private static final int ZOOM_ADJUSTMENT_MODE_NONE = 0;
42 private static final int ZOOM_ADJUSTMENT_MODE_REZOOM = 1;
43 private static final int ZOOM_ADJUSTMENT_MODE_PAN = 2;
44 private static final int ZOOM_ADJUSTMENT_MODE_MODIFYZOOM = 3;
45
46 public static final Color ZOOM_BACKING_COLOR_NORMAL = new Color(251, 255, 168);
47 private static final Color ZOOM_BACKING_COLOR_HIGHLIGHT = new Color(255, 255, 224);
48 private static final Color ZOOM_BACKING_BORDER_COLOR = new Color(215, 219, 144);
49 public static final Stroke ZOOM_BACKING_BORDER_STROKE = new BasicStroke(1.0f);
50
51 public static final Font TIME_HELPER_FONT = new Font("Arial", Font.PLAIN, 12);
52 public static final Color TIME_HELPER_COLOR = new Color(132, 175, 201);
53 public static final Color TIME_HELPER_BORDERCOLOR = new Color(111, 146, 168);
54
55 // displays the current zoom or the new zoom-selection
56 // (actual zoom is not set until mouse released
57 private ZoomBackSection zoomBackSection;
58
59 private int zoomAdjustmentMode = ZOOM_ADJUSTMENT_MODE_NONE;
60 private int adjustmentStartX = 0;
61 private int adjustmentEndX = 0;
62 private int panOffset = 0;
63
64 // The model data
65 private int zoomStartFrame = -1;
66 private int zoomEndFrame = -1;
67
68 private List<ZoomChangeListener> zoomChangeListeners = new LinkedList<ZoomChangeListener>();
69
70 public SampledTrackGraphViewPort() {
71 super();
72
73 setBackColor(new Color(250, 250, 250), new Color(230, 230, 230));
74 setAlwaysFullView(true);
75
76 // Create back sections
77 zoomBackSection = new ZoomBackSection();
78 addBackingSection(zoomBackSection);
79
80 addMouseListener(this);
81 addMouseMotionListener(this);
82 addMouseWheelListener(this);
83 addKeyListener(this);
84 this.addComponentListener(new ComponentListener() {
85
86 public void componentHidden(ComponentEvent e) {
87 }
88
89 public void componentMoved(ComponentEvent e) {
90 }
91
92 public void componentResized(ComponentEvent e) {
93 if (getSampledTrackModel() == null) return;
94
95 // Initialization
96 if (zoomStartFrame < 0 || zoomEndFrame < 0) {
97
98 fullyZoomOut();
99
100 } else {
101
102 // Update backing
103 zoomBackSection.updateBounds(false);
104 }
105 }
106
107 public void componentShown(ComponentEvent e) {
108 }
109
110 });
111 }
112
113 public void addZoomChangeListener(ZoomChangeListener zcl) {
114 if (!zoomChangeListeners.contains(zcl))
115 zoomChangeListeners.add(zcl);
116 }
117
118 @Override
119 public void setObservedSubject(Subject parent) {
120 super.setObservedSubject(parent);
121
122 if (parent != null && parent instanceof SampledTrackModel) {
123 fullyZoomOut();
124 }
125 }
126
127 public void fullyZoomOut()
128 {
129 if (getSampledTrackModel() == null) return;
130 setZoom(0, getSampledTrackModel().getFrameCount());
131 }
132
133 /**
134 * Sets the current zoom. In frames.
135 * Invalidates
136 *
137 * @param startFrame
138 *
139 * @param endFrame
140 *
141 */
142 public void setZoom(int startFrame, int endFrame) {
143 if (getSampledTrackModel() == null) return;
144
145 if (startFrame < 0)
146 startFrame = 0;
147 if (startFrame > getSampledTrackModel().getFrameCount())
148 startFrame = getSampledTrackModel().getFrameCount();
149
150 if (endFrame < 0)
151 endFrame = 0;
152 if (endFrame > getSampledTrackModel().getFrameCount())
153 endFrame = getSampledTrackModel().getFrameCount();
154
155 // Anything different?
156 if (startFrame == zoomStartFrame && endFrame == zoomEndFrame)
157 return;
158
159 zoomStartFrame = startFrame;
160 zoomEndFrame = endFrame;
161
162 // Update backing
163 zoomBackSection.updateBounds(false);
164
165 invalidateAll();
166
167 // Fire model changed event
168 Event e = new Event(this, 0, null);
169 for (ZoomChangeListener zcl : zoomChangeListeners)
170 zcl.zoomChanged(e);
171
172 }
173
174 public int getZoomStartFrame() {
175 return this.zoomStartFrame;
176 }
177
178 public int getZoomEndFrame() {
179 return this.zoomEndFrame;
180 }
181
182 public void keyPressed(KeyEvent e) {
183 if (getSampledTrackModel() == null) return;
184
185 // Fine pan the zoom
186 if (e.getKeyCode() == KeyEvent.VK_LEFT && Math.min(zoomStartFrame, zoomEndFrame) > 0) {
187
188 float ftmp = (float)COARSE_PAN_PIXEL_LENGTH / (float)getWidth();
189 int pan = (int)(Math.abs(zoomStartFrame - zoomEndFrame) * ftmp);
190 if (pan == 0) pan = 1;
191
192 setZoom(zoomStartFrame - pan, zoomEndFrame - pan);
193
194 } else if (e.getKeyCode() == KeyEvent.VK_RIGHT &&
195 Math.max(zoomStartFrame, zoomEndFrame) < getSampledTrackModel().getFrameCount()) {
196
197 float ftmp = (float)COARSE_PAN_PIXEL_LENGTH / (float)getWidth();
198 int pan = (int)(Math.abs(zoomStartFrame - zoomEndFrame) * ftmp);
199 if (pan == 0) pan = 1;
200
201 setZoom(zoomStartFrame + pan, zoomEndFrame + pan);
202 }
203
204 // Fine zoom the view
205 else if (e.getKeyCode() == KeyEvent.VK_UP) {
206 zoom(-1);
207 } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
208 zoom(1);
209 }
210
211 }
212
213 public void keyReleased(KeyEvent e) {
214 }
215
216 public void keyTyped(KeyEvent e) {
217 }
218
219 public void mouseClicked(MouseEvent e) {
220 if (e.getClickCount() >= 2) { // zoom out fully on double click
221 fullyZoomOut();
222 }
223 }
224
225 /**
226 * Zooms in/out of viewport...
227 *
228 * @param times
229 * How many zooms to do. Negative for zoom in. Positive for zoom out
230 */
231 private void zoom(int times) {
232
233 if (getSampledTrackModel() == null || times == 0) return;
234
235 float ftmp = (float)SCROLL_ZOOM_PIXEL_LENGTH / (float)getWidth();
236 int zoom = (int)(getTimeScaleLength() * ftmp * times);
237
238 if (Math.abs(zoom) > Math.abs(zoomStartFrame - zoomEndFrame)) {
239
240 if (times < 0) {
241 zoom = Math.abs(zoomStartFrame - zoomEndFrame) / 2;
242 zoom *= -1;
243 } else {
244 zoom = Math.abs(zoomStartFrame - zoomEndFrame) * 2;
245 }
246 }
247
248 if (zoom == 0) zoom = 1;
249
250 int start = 0, end = 0;
251
252 if (zoomStartFrame < zoomEndFrame) {
253
254 start = zoomStartFrame - (zoom / 2);
255 end = zoomEndFrame + (zoom / 2);
256
257 } else {
258
259 start = zoomStartFrame + (zoom / 2);
260 end = zoomEndFrame - (zoom / 2);
261
262 }
263
264 setZoom(start, end);
265
266 }
267
268 public void mouseWheelMoved(MouseWheelEvent e) {
269 zoom(e.getWheelRotation());
270 }
271
272 public void mouseEntered(MouseEvent e) {
273 }
274
275
276 public void mouseExited(MouseEvent e) {
277 }
278
279 public void mousePressed(MouseEvent e) {
280 if (getSampledTrackModel() == null) return;
281
282 // set selection mode
283 if (e.getButton() == MouseEvent.BUTTON1) { // re-zoom from scratch
284 setZoomAdjustmentMode(ZOOM_ADJUSTMENT_MODE_REZOOM);
285 adjustmentStartX = adjustmentEndX = e.getX();
286
287 } else if(e.getButton() == MouseEvent.BUTTON2) {
288
289 int adjustStart = XatFrame(zoomStartFrame);
290 int adjustEnd = XatFrame(zoomEndFrame);
291
292 // Only start panning if mouse over zoom area
293 if (e.getX() >= Math.min(adjustStart, adjustEnd)
294 && e.getX() <= Math.max(adjustStart, adjustEnd)) {
295
296 adjustmentStartX = adjustStart;
297 adjustmentEndX = adjustEnd;
298
299 setZoomAdjustmentMode(ZOOM_ADJUSTMENT_MODE_PAN);
300 panOffset = e.getX() - Math.min(adjustmentStartX, adjustmentEndX);
301 assert(panOffset >= 0);
302 }
303
304 } else if(e.getButton() == MouseEvent.BUTTON3) { // add/subtract to current zoom
305
306 setZoomAdjustmentMode(ZOOM_ADJUSTMENT_MODE_MODIFYZOOM);
307
308 adjustmentStartX = XatFrame(zoomStartFrame);
309 adjustmentEndX = XatFrame(zoomEndFrame);
310
311
312 if (e.isControlDown()) {
313 subtractCurrentZoomSelection(e.getX());
314 } else {
315 addCurrentZoomSelection(e.getX());
316 }
317
318 } else {
319 setZoomAdjustmentMode(ZOOM_ADJUSTMENT_MODE_NONE);
320 }
321
322 if (zoomAdjustmentMode != ZOOM_ADJUSTMENT_MODE_NONE) {
323 zoomBackSection.updateBounds(true);
324 invalidateAll();
325 }
326
327 }
328
329 public void mouseReleased(MouseEvent e) {
330 if (getSampledTrackModel() == null) return;
331
332 // Set the actual zoom only on mouse release events
333 if (zoomAdjustmentMode != ZOOM_ADJUSTMENT_MODE_NONE) {
334
335 setZoomAdjustmentMode(ZOOM_ADJUSTMENT_MODE_NONE);
336
337 if (Math.abs(adjustmentStartX - adjustmentEndX) >= ZOOM_SELECTION_PIXEL_RANGE_THRESHOLD)
338 setZoom(frameAtX(adjustmentStartX), frameAtX(adjustmentEndX));
339 else invalidateAll();
340
341 }
342
343 }
344
345
346 public void mouseDragged(MouseEvent e) {
347 if (getSampledTrackModel() == null) return;
348
349 switch (zoomAdjustmentMode) {
350 case ZOOM_ADJUSTMENT_MODE_REZOOM:
351 adjustmentEndX = e.getX();
352 break;
353
354 case ZOOM_ADJUSTMENT_MODE_PAN:
355
356 assert(panOffset >= 0);
357
358 int len = Math.abs(adjustmentEndX - adjustmentStartX);
359
360 if (adjustmentStartX < adjustmentEndX) {
361 adjustmentStartX = e.getX() - panOffset;
362 adjustmentEndX = adjustmentStartX + len;
363
364 // Clamp
365 if (adjustmentStartX < 0) {
366 adjustmentStartX = 0;
367 adjustmentEndX = len;
368 }
369
370 else if (adjustmentEndX > getWidth()) {
371 adjustmentEndX = getWidth();
372 adjustmentStartX = adjustmentEndX - len;
373 }
374
375 } else {
376 adjustmentEndX = e.getX() - panOffset;
377 adjustmentStartX = adjustmentEndX + len;
378
379 // Clamp
380 if (adjustmentEndX < 0) {
381 adjustmentEndX = 0;
382 adjustmentStartX = len;
383 }
384
385 else if (adjustmentStartX > getWidth()) {
386 adjustmentStartX = getWidth();
387 adjustmentEndX = adjustmentStartX - len;
388 }
389 }
390
391
392 break;
393
394 case ZOOM_ADJUSTMENT_MODE_MODIFYZOOM:
395 if (e.isControlDown()) {
396 subtractCurrentZoomSelection(e.getX());
397 } else {
398 addCurrentZoomSelection(e.getX());
399 }
400 break;
401 }
402
403 if (zoomAdjustmentMode != ZOOM_ADJUSTMENT_MODE_NONE) {
404 zoomBackSection.updateBounds(true);
405 invalidateAll();
406 }
407
408 }
409
410
411 public void mouseMoved(MouseEvent e) {
412
413 // If missed a mouse release event must reset the adjustment state...
414 if (e.getButton() == MouseEvent.NOBUTTON
415 && zoomAdjustmentMode != ZOOM_ADJUSTMENT_MODE_NONE) {
416 setZoomAdjustmentMode(ZOOM_ADJUSTMENT_MODE_NONE);
417 invalidateAll();
418 }
419
420 }
421
422 private void addCurrentZoomSelection(int x) {
423
424 if (adjustmentStartX < adjustmentEndX) {
425
426 if (x < adjustmentStartX) // expand to left
427 adjustmentStartX = x;
428
429 else if (x > adjustmentEndX) // expand to right
430 adjustmentEndX = x;
431
432 } else {
433
434 if (x < adjustmentEndX) // expand to left
435 adjustmentEndX = x;
436
437 else if (x > adjustmentStartX) // expand to right
438 adjustmentStartX = x;
439
440 }
441 }
442
443 private void subtractCurrentZoomSelection(int x) {
444
445 if (adjustmentStartX < adjustmentEndX) {
446
447 if (x > adjustmentStartX) // shrink to left
448 adjustmentStartX = x;
449
450 else if (x < adjustmentEndX) // shrink to right
451 adjustmentEndX = x;
452
453 } else {
454
455 if (x > adjustmentEndX) // shrink to right
456 adjustmentEndX = x;
457
458 else if (x < adjustmentStartX) // shrink to left
459 adjustmentStartX = x;
460 }
461
462 }
463
464
465 @Override
466 public void modelChanged(Subject source, SubjectChangedEvent event) {
467 super.modelChanged(source, event);
468
469 if (getSampledTrackModel() == null) return;
470
471
472 switch(event.getID()) {
473
474 case ApolloSubjectChangedEvent.AUDIO_INSERTED:
475 case ApolloSubjectChangedEvent.AUDIO_REMOVED:
476 zoomBackSection.updateBounds(false);
477 break;
478
479 }
480 }
481
482
483 /**
484 * Use this for setting the ajudstment mode.
485 * Updates GUI. But does not Invalidate.
486 *
487 * @param mode
488 */
489 private void setZoomAdjustmentMode(int mode) {
490
491 zoomAdjustmentMode = mode;
492
493 if (mode == ZOOM_ADJUSTMENT_MODE_NONE) {
494 zoomBackSection.updateBounds(false);
495 } else if (mode == ZOOM_ADJUSTMENT_MODE_REZOOM) {
496
497 } else if (mode == ZOOM_ADJUSTMENT_MODE_PAN) {
498
499 } else if (mode == ZOOM_ADJUSTMENT_MODE_MODIFYZOOM) {
500
501 } else {
502 assert(false);
503 }
504
505 }
506
507 @Override
508 public void paint(Graphics g) {
509 super.paint(g);
510 zoomBackSection.postPaint(g);
511 }
512
513
514 private class ZoomBackSection extends BackSection
515 {
516
517 private String startTimeHelpLabel = null;
518 private String endTimeHelpLabel = null;
519 private int labelStartX = 0;
520 private int labelEndX = 0;
521 private int labelStartY = 0;
522 private int labelEndY = 0;
523 private int labelWidth = 0;
524 private int labelHeight = 0;
525
526 public ZoomBackSection() {
527 super();
528 visible = true;
529 }
530
531 @Override
532 void paint(Graphics g) {
533
534 color = (zoomAdjustmentMode == ZOOM_ADJUSTMENT_MODE_NONE) ?
535 ZOOM_BACKING_COLOR_NORMAL : ZOOM_BACKING_COLOR_HIGHLIGHT;
536 super.paint(g);
537
538 g.setColor(ZOOM_BACKING_BORDER_COLOR);
539 ((Graphics2D)g).setStroke(ZOOM_BACKING_BORDER_STROKE);
540 g.drawLine(left, 0, left, getHeight());
541 g.drawLine(left + width, 0, left + width, getHeight());
542
543 }
544
545 void postPaint(Graphics g) {
546 // Paint little helper labels when ajusting view port
547 if (startTimeHelpLabel != null && endTimeHelpLabel != null &&
548 zoomAdjustmentMode != ZOOM_ADJUSTMENT_MODE_NONE) {
549
550 g.setColor(TIME_HELPER_COLOR);
551 g.fillRect(labelStartX - 2, labelStartY - labelHeight + 2, labelWidth + 3, labelHeight);
552 g.fillRect(labelEndX - 2, labelEndY - labelHeight + 2, labelWidth + 3, labelHeight);
553
554 g.setColor(TIME_HELPER_BORDERCOLOR);
555 ((Graphics2D)g).setStroke(ZOOM_BACKING_BORDER_STROKE);
556 g.drawRect(labelStartX - 2, labelStartY - labelHeight + 2, labelWidth + 3, labelHeight);
557 g.drawRect(labelEndX - 2, labelEndY - labelHeight + 2, labelWidth + 3, labelHeight);
558
559 g.setFont(TIME_HELPER_FONT);
560 g.setColor(Color.BLACK);
561 g.drawString(startTimeHelpLabel, labelStartX, labelStartY);
562 g.drawString(endTimeHelpLabel, labelEndX, labelEndY);
563
564 }
565 }
566
567 void updateBounds(boolean toAdjust) {
568 if (toAdjust) {
569 left = Math.min(adjustmentStartX, adjustmentEndX);
570
571 width = Math.abs(adjustmentStartX - adjustmentEndX);
572
573 updateHelperLabels();
574
575 } else {
576 left = XatFrame(Math.min(zoomStartFrame, zoomEndFrame));
577
578 width = Math.abs(XatFrame(zoomStartFrame) - XatFrame(zoomEndFrame));
579 }
580
581 if (width < 3) width = 3;
582 }
583
584 private void updateHelperLabels() {
585 if (getSampledTrackModel() == null) return;
586
587 Graphics g = null;
588 try {
589
590 // create time stings
591 long startms = AudioMath.framesToMilliseconds(
592 frameAtX(Math.min(adjustmentStartX, adjustmentEndX)),
593 getSampledTrackModel().getFormat());
594
595 long endms = AudioMath.framesToMilliseconds(
596 frameAtX(Math.max(adjustmentStartX, adjustmentEndX)),
597 getSampledTrackModel().getFormat());
598
599 startTimeHelpLabel = createHelperLabel(startms);
600 endTimeHelpLabel = createHelperLabel(endms);
601
602 assert(startTimeHelpLabel != null);
603 assert(endTimeHelpLabel != null);
604
605 // Create a temp graphics for centered label positioning
606 if (Browser._theBrowser != null && Browser._theBrowser.g != null) {
607 g = Browser._theBrowser.g.create();
608 } else {
609 g = new BufferedImage(1,1,BufferedImage.TYPE_BYTE_INDEXED).getGraphics();
610 }
611
612 // Position labels
613 FontMetrics fm = g.getFontMetrics(TIME_HELPER_FONT);
614 Rectangle2D rect = fm.getStringBounds(startTimeHelpLabel, g);
615
616 labelWidth = (int)rect.getWidth();
617 labelHeight = (int)rect.getHeight();
618
619 labelStartX = Math.min(adjustmentStartX, adjustmentEndX) - labelWidth - 5;
620 labelEndX = Math.max(adjustmentStartX, adjustmentEndX) + 5;
621
622 labelStartY = labelEndY = (getHeight() >> 1) + (labelHeight >> 1);
623
624 } finally {
625 if (g != null)
626 g.dispose();
627 }
628
629 }
630
631 /**
632 * @param time
633 * @return Never null
634 */
635 private String createHelperLabel(long time) {
636 assert(getSampledTrackModel() != null);
637
638 long totalMS = AudioMath.framesToMilliseconds(getSampledTrackModel().getFrameCount(),
639 getSampledTrackModel().getFormat());
640
641
642 Long mins = (totalMS < 60000) ? null : time / 60000;
643
644 Long secs = (totalMS < 1000) ? null : (time / 1000) % 60;
645
646 Long ms = time % 1000;
647
648 return TimeAxis.createTimeLabel(mins, secs, ms);
649 }
650 }
651}
Note: See TracBrowser for help on using the repository browser.