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

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