source: trunk/src/org/apollo/gui/SampledTrackGraphViewPort.java@ 1102

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

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