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

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

Apollo spin-off added

File size: 25.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.GradientPaint;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.Paint;
11import java.awt.Point;
12import java.awt.Rectangle;
13import java.awt.Stroke;
14import java.awt.event.ComponentEvent;
15import java.awt.event.ComponentListener;
16import java.awt.image.BufferedImage;
17import java.util.LinkedList;
18import java.util.List;
19
20import javax.swing.JComponent;
21import javax.swing.SwingUtilities;
22
23import org.apollo.ApolloSystem;
24import org.apollo.audio.ApolloSubjectChangedEvent;
25import org.apollo.audio.SampledTrackModel;
26import org.apollo.audio.TrackSequence;
27import org.apollo.audio.util.SoundDesk;
28import org.apollo.audio.util.PlaybackClock;
29import org.apollo.audio.util.TrackMixSubject;
30import org.apollo.audio.util.PlaybackClock.PlaybackClockListener;
31import org.apollo.mvc.Observer;
32import org.apollo.mvc.Subject;
33import org.apollo.mvc.SubjectChangedEvent;
34import org.expeditee.gui.Browser;
35import org.expeditee.gui.FrameGraphics;
36
37/**
38 * A graphical view for SampledTrackModel's.
39 *
40 * To use the view, just add a SampledTrackGraphView instance to a SampledTrackModel.
41 * Can re-use a SampledTrackGraphView instance.
42 *
43 * @author Brook Novak
44 */
45public class SampledTrackGraphView extends JComponent implements Observer, PlaybackClockListener {
46
47 private static final long serialVersionUID = 1L;
48
49 /** The observed subject. Can be null */
50 private SampledTrackModel trackModel = null;
51
52 /** The mix to use for the observed track. */
53 private TrackMixSubject trackMix = null;
54
55 /** The track sequence currently playing. Nullified when playback is stopped. */
56 private TrackSequence currentTrackSequence = null;
57
58 /** Negative = no playback */
59 private int currentPlaybackFramePosition = -1;
60
61 private WaveFormRenderProccessingUnit.WaveFormRenderTask renderTask = null;
62 private BufferedImage backBuffer = null; // Shared resource
63 private int bufferWidth = -1; // cached from backBuffer - so don't have to lock
64 private int bufferHeight = -1; // cached from backBuffer - so don't have to lock
65
66 // Model Data
67 private int timescaleFrameStart = 0; // in frames
68 private int timescaleFrameLength = 0; // in frames
69
70 private final Font RENDER_MESSAGE_FONT = new Font("Arial", Font.ITALIC | Font.BOLD, 14);
71
72 private boolean alwaysFullViewOn = false;
73
74 public static final Color DEFAULT_BACKGROUND_COLOR = new Color(200, 200, 200);
75 public static final Color DEFAULT_BACKGROUND_HIGHTLIGHTS_COLOR = new Color(240, 240, 240);
76
77 private Color backColor = DEFAULT_BACKGROUND_COLOR;
78 private Color backColorHighlights = DEFAULT_BACKGROUND_HIGHTLIGHTS_COLOR;
79
80 public static final Color PLAYBACK_BAR_COLOR = new Color(225, 187, 15);
81
82 /** The stroke used for drawing graph bars. E.G: The selection Start bar. */
83 public static final Stroke GRAPH_BAR_STROKE = new BasicStroke(1.0f);
84 public static final int GRAPH_BAR_NWIDTH = 1;
85
86 /** Used for safely invalidating playback positions at realtime. */
87 private Point expediteePosition;
88
89 private LinkedList<BackSection> backingSections = new LinkedList<BackSection>();
90 private EffecientInvalidator invalidator = null;
91 private List<TimelineChangeListener> timelineChangeListeners = new LinkedList<TimelineChangeListener>();
92
93 public SampledTrackGraphView() {
94
95 // Keep backings consistant with component resize
96 this.addComponentListener(new ComponentListener() {
97 public void componentHidden(ComponentEvent e) {
98 }
99
100 public void componentMoved(ComponentEvent e) {
101 //updateExpediteePoint(); // Doesn;t work here - swing has nasty bug
102 }
103
104 public void componentResized(ComponentEvent e) {
105
106 if (SampledTrackGraphView.this.trackModel != null) {
107
108 for (BackSection bs : backingSections) {
109 bs.updateBounds();
110 }
111 }
112 }
113
114 public void componentShown(ComponentEvent e) {
115 //updateExpediteePoint(); // Doesn;t work here - swing has nasty bug
116 }
117
118 });
119
120
121 }
122
123 /**
124 * {@inheritDoc}
125 */
126 @Override
127 public void setBounds(int x, int y, int width, int height) {
128 super.setBounds(x, y, width, height);
129 updateExpediteePoint(); // work around for Swing bug!
130 }
131
132
133 /**
134 * Saves this views position in expeditee space.
135 */
136 private void updateExpediteePoint() {
137 if (Browser._theBrowser == null) return;
138
139 expediteePosition = SwingUtilities.convertPoint(
140 this,
141 0, 0,
142 Browser._theBrowser.getContentPane());
143 }
144
145 /**
146 * Take care: can be null if not intialized.
147 * @return
148 * The point at which this view is in a expeditee frame.
149 */
150 protected Point getExpediteePoint() {
151 return expediteePosition;
152 }
153
154 public void addTimeScaleChangeListener(TimelineChangeListener tcl) {
155 if (!timelineChangeListeners.contains(tcl))
156 timelineChangeListeners.add(tcl);
157 }
158
159 protected void fireTimelineChanged() {
160 Event e = new Event(this, 0, null);
161 for (TimelineChangeListener listener : timelineChangeListeners)
162 listener.timelineChanged(e);
163 }
164
165 /**
166 * {@inheritDoc}
167 */
168 public Subject getObservedSubject() {
169 return trackModel;
170 }
171
172
173 /**
174 * @return
175 * The observed track model. Null if none is being viewed.
176 */
177 protected SampledTrackModel getSampledTrackModel() {
178 return trackModel;
179 }
180
181 /**
182 * {@inheritDoc}
183 */
184 public void setObservedSubject(Subject parent) {
185
186 // Note: parent can be an observer task. But that type of parent will never
187 // send null.
188 if (parent == null) { // Reset view to view nothing
189 trackModel = null;
190 if (trackMix != null) // avoid infinite recursion (deadlock until out of memory)
191 setMix(null); // stop observing the mix desk - otherwise this reference will never be garbage collected
192 releaseBuffer();
193
194 } else if (parent instanceof SampledTrackModel) { // Prepares the view
195
196 trackModel = (SampledTrackModel)parent;
197
198 if (alwaysFullViewOn) {
199 timescaleFrameStart = 0;
200 timescaleFrameLength = trackModel.getFrameCount();
201 fireTimelineChanged();
202 } else {
203 // Ensure that the timeline is valid - even though will probably change
204 clampCurrentTimescale();
205 }
206 }
207
208
209 }
210
211 /**
212 * {@inheritDoc}
213 */
214 public void modelChanged(Subject source, SubjectChangedEvent event) {
215 if (trackModel == null) return;
216
217
218 switch(event.getID()) {
219
220 // Invalidate dirty graph - according to render proccessor
221 case ApolloSubjectChangedEvent.RENDER_TASK_INVALIDATION_RECOMENDATION: // event from render proccessor
222 invalidateAll();
223 break;
224
225 case ApolloSubjectChangedEvent.AUDIO_REMOVED: // event from track model
226 case ApolloSubjectChangedEvent.AUDIO_INSERTED: // event from track model
227 if (alwaysFullViewOn) {
228 timescaleFrameStart = 0;
229 timescaleFrameLength = trackModel.getFrameCount();
230 fireTimelineChanged();
231 } else {
232 // Ensure that the timeline is valid - even though will probably change
233 clampCurrentTimescale();
234 }
235 redrawBuffer();
236 break;
237
238 case ApolloSubjectChangedEvent.TRACK_SEQUENCE_CREATED: // event from mix desk
239 assert(trackMix != null);
240 assert(event.getState() != null);
241
242 // OK a track sequence has just been created, but is it for this
243 // track mix?
244 if (event.getState().equals(trackMix.getChannelID())) {
245
246 // If so - then observe the created track sequence for playback messages
247 TrackSequence ts = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
248 assert(ts != null);
249 ts.addObserver(this); // NOTE: No need to remove self since these are garbage collected rather quickly
250 }
251
252 break;
253
254 case ApolloSubjectChangedEvent.PLAYBACK_STARTED: // event from track sequence
255
256 // Important to set here - since a track can be created but fail to initiate
257 // playback ... thus must set reference only when gauranteed that it can be unset.
258 currentTrackSequence = (TrackSequence)source;
259
260 PlaybackClock.getInstance().addPlaybackClockListener(this); // listen to ticks
261
262 onPlaybackStarted();
263 break;
264
265 case ApolloSubjectChangedEvent.PLAYBACK_STOPPED: // event from track sequence
266 PlaybackClock.getInstance().removePlaybackClockListener(this); // stop listening to ticks
267
268 int playbackFramePos = -1;
269
270 // The stop event might be due to a pause:
271 if (currentTrackSequence != null && trackMix != null &&
272 SoundDesk.getInstance().isPaused(trackMix.getChannelID())) {
273 playbackFramePos = currentTrackSequence.getSuspendedFrame();
274 }
275
276 // Has the user edited the audio such that the playback position is invalid?
277 if (playbackFramePos > trackModel.getFrameCount())
278 playbackFramePos = -1;
279
280 // Note: although these nullify the audio bytes when stopped, the state
281 // information carries a reference to the original track model at the moment
282 // which stores a lot of data and thus keeping the reference could
283 // hog the memory.
284 currentTrackSequence = null;
285
286 updatePlaybackPosition(playbackFramePos);
287
288 onPlaybackStopped();
289 break;
290
291
292 case ApolloSubjectChangedEvent.PAUSE_MARK_CHANGED:
293
294 if (trackMix != null && event.getState().equals(trackMix.getChannelID())) {
295
296 if (!SoundDesk.getInstance().isPaused(trackMix.getChannelID())) {
297 updatePlaybackPosition(-1);
298 }
299
300 }
301 break;
302
303 }
304 }
305
306 protected void onPlaybackStarted() {}
307 protected void onPlaybackStopped() {}
308
309 /**
310 * @return
311 * True if this view is viewing a track sequence that is playing
312 */
313 public final boolean isPlaying() {
314 return (currentTrackSequence != null && currentTrackSequence.isPlaying());
315 }
316
317 private int getPlaybackXPos(int playbackFrame) {
318
319 if (playbackFrame >= timescaleFrameStart &&
320 playbackFrame <= (timescaleFrameStart + timescaleFrameLength)) {
321
322 return XatFrame(playbackFrame);
323 }
324
325 return -1;
326 }
327
328 private void updatePlaybackPosition(int absFramePos) {
329
330 if (currentPlaybackFramePosition != absFramePos) {
331
332 int height = getHeight();
333 int viewWidth = getWidth();
334 int x;
335
336 // Invalidate old pos
337 if(expediteePosition != null) {
338
339 x = getPlaybackXPos(currentPlaybackFramePosition);
340
341 if (x >= 0 && x <= viewWidth) { // is the playback position in view?
342 FrameGraphics.invalidateArea(new Rectangle(
343 x + expediteePosition.x,
344 expediteePosition.y,
345 GRAPH_BAR_NWIDTH,
346 height));
347 }
348 }
349
350 // Set new pos
351 currentPlaybackFramePosition = absFramePos;
352
353 // Invalidate new pos
354 if(expediteePosition != null && currentPlaybackFramePosition >= 0) {
355
356 x = getPlaybackXPos(currentPlaybackFramePosition);
357
358 if (x >= 0 && x <= viewWidth) { // is the playback position in view?
359 FrameGraphics.invalidateArea(new Rectangle(
360 x + expediteePosition.x ,
361 expediteePosition.y,
362 GRAPH_BAR_NWIDTH,
363 height));
364
365 }
366 }
367
368 }
369
370
371 }
372
373 /**
374 * {@inheritDoc}
375 */
376 public void onTick(long framePosition, long msPosition) {
377
378 if (framePosition < 0) return; // Done
379
380 // The frame position / ms position is according to the software mixers timeline - not
381 // neccessarily starting off when the observed track sequence started. Thus must
382 // determine the actual frame position relative to the current rack sequence being played
383 // for this view:
384 int fpos = (int)(framePosition - currentTrackSequence.getInitiationFrame() + currentTrackSequence.getStartFrame());
385 if (fpos > currentTrackSequence.getEndFrame()) fpos = currentTrackSequence.getEndFrame();
386 updatePlaybackPosition(fpos);
387
388 // Note: the clock issues render requests
389 }
390
391 /**
392 * Sets the mix to use for playback / and to identify the channel for observering
393 * live playback...
394 *
395 * This resets to null whenever this view is set to no longer observe a track model.
396 *
397 * @param trackMix
398 * The new track mix to use. Null to use none.
399 *
400 */
401 public void setMix(TrackMixSubject trackMix) {
402
403 this.trackMix = trackMix;
404
405 if (trackMix != null) {
406 // Listen for track seq creation events ...
407 // thus if a track seq is created for this new ID then we can listen
408 // for playback events and render playback bars.
409 SoundDesk.getInstance().addObserver(this);
410
411 // If the mix is already playing - switch to a playing state.
412 if (SoundDesk.getInstance().isPlaying(trackMix.getChannelID())) {
413
414 currentTrackSequence = SoundDesk.getInstance().getTrackSequence(trackMix.getChannelID());
415
416 if (currentTrackSequence != null) {
417
418 // Note: that there are no race conditions because track sequence events
419 // are riased on this thread.
420 //currentPlaybackFramePosition = currentTrackSequence.getCurrentFrame(); // no invalidation
421 currentTrackSequence.addObserver(this);
422 PlaybackClock.getInstance().addPlaybackClockListener(this);
423 onPlaybackStarted();
424
425 }
426
427 } else if (SoundDesk.getInstance().isPaused(trackMix.getChannelID())){
428
429 int suspos = SoundDesk.getInstance().getLastPlayedFramePosition(trackMix.getChannelID());
430 updatePlaybackPosition(suspos);
431
432 }
433
434 } else {
435 SoundDesk.getInstance().removeObserver(this);
436 }
437
438 }
439
440 /**
441 * @return
442 * The mix set to be used for this view. Can be null.
443 */
444 public TrackMixSubject getMix() {
445 return trackMix;
446 }
447
448 public void setAlwaysFullView(boolean alwaysFullViewOn) {
449 this.alwaysFullViewOn = alwaysFullViewOn;
450 }
451
452 private void clampCurrentTimescale() {
453 int[] newTimeScale = getClampedTimeScale(timescaleFrameStart, timescaleFrameLength);
454
455 if (timescaleFrameStart != newTimeScale[0]
456 || timescaleFrameLength != newTimeScale[1]) {
457
458 timescaleFrameStart = newTimeScale[0];
459 timescaleFrameLength = newTimeScale[1];
460 fireTimelineChanged();
461 }
462 }
463
464 /**
465 * Calculates an appropriate timescale from the given start and length.
466 *
467 * trackModel Must not be null.
468 *
469 * @param frameStart
470 *
471 * @param frameLength
472 *
473 * @return
474 * Array of two ints. First element is clamped start frame,
475 * The second element is the clamped length.
476 */
477 private int[] getClampedTimeScale(int frameStart, int frameLength) {
478 assert(trackModel != null);
479
480 if (frameLength < 1) frameLength = 1;
481
482 if (frameLength > trackModel.getFrameCount()) {
483
484 frameLength = trackModel.getFrameCount();
485 frameStart = 0;
486
487 } else if (frameStart < 0) { // watch out for start
488
489 frameStart = 0;
490
491 } else if ((frameStart + frameLength) > trackModel.getFrameCount()) { // and end
492
493 frameStart = trackModel.getFrameCount() - frameLength;
494
495 }
496
497 assert (frameStart >= 0);
498 assert ((frameStart + frameLength) <= trackModel.getFrameCount());
499
500 return new int[] {frameStart, frameLength};
501 }
502
503 /**
504 * Sets the time scale for the graph viewport.
505 * As a result, the graph will be re-rendered asynchronously.
506 *
507 * @param frameStart
508 * @param frameLength
509 */
510 public void setTimeScale(int frameStart, int frameLength) {
511
512 int[] newScale = getClampedTimeScale(frameStart, frameLength);
513
514 if (newScale[0] == timescaleFrameStart && newScale[1] == timescaleFrameLength)
515 return;
516
517 // Set new model data
518 timescaleFrameStart = newScale[0];
519 timescaleFrameLength = newScale[1];
520
521 // Re-render graph with new time scale
522 redrawBuffer();
523
524 fireTimelineChanged();
525 }
526
527 /**
528 * @return
529 * The start <i>frame</i> of the current timescale.
530 * Always in valid position if subject is set.
531 *
532 */
533 public int getTimeScaleStart() {
534 return timescaleFrameStart;
535 }
536
537 /**
538 * @return
539 * The length of the timescale in <i>frames</i>.
540 * Always larger or equal to one if subject is set.
541 */
542 public int getTimeScaleLength() {
543 return timescaleFrameLength;
544 }
545
546 /**
547 * Re-renders the waveform buffers to match the graph size if it needs to.
548 * This will avoid pixelation artifacts.
549 */
550 public void updateBuffer() {
551 // Check for resize of view or if not initialized yet
552 if (bufferWidth != getWidth() || bufferHeight != getHeight() || backBuffer == null) {
553 redrawBuffer();
554 }
555 }
556
557 /**
558 * Releases the image buffer.
559 *
560 * <b>Important:</b> Must call on the swing thread.
561 */
562 public void releaseBuffer() {
563
564 if (renderTask != null && !renderTask.isStopped())
565 WaveFormRenderProccessingUnit.getInstance().cancelTask(renderTask);
566
567 renderTask = null; // must be on swing thread because could be painting
568 if (backBuffer != null) backBuffer.flush();
569 backBuffer = null; // must be on swing thread because could be painting
570 }
571
572 /**
573 * Initiates redrawing. If currently redrawing then it will be cancelled and will start from scratch.
574 * Re-creates the waveform buffer based on the panels current width and height.
575 *
576 * The graph panel will be fully invalidated
577 *
578 * If the trackModel is not set, then the buffer will not be redrawn.
579 *
580 */
581 protected void redrawBuffer() {
582
583 if (trackModel == null)
584 return;
585
586 int width = getWidth();
587 int height = getHeight();
588
589 if (width <= 0 || height <= 0) return;
590
591 // Remember width for re-rendering when graph size changes
592 bufferWidth = width;
593 bufferHeight = height;
594
595 // Cancel current rendering task
596 if (renderTask != null && !renderTask.isStopped())
597 WaveFormRenderProccessingUnit.getInstance().cancelTask(renderTask);
598
599 // Re-create the back buffer if need to
600 Object bufferLock = (backBuffer == null) ? new Object() : backBuffer;
601 synchronized(bufferLock) {
602
603 if (backBuffer == null ||
604 backBuffer.getWidth() != width
605 || backBuffer.getHeight() != height) {
606
607 // Create new sized buffer
608 backBuffer = new BufferedImage(
609 width,
610 height,
611 BufferedImage.TYPE_BYTE_INDEXED,
612 ApolloColorIndexedModels.graphIndexColorModel);
613 }
614 }
615
616
617 // Describe render task
618 renderTask = WaveFormRenderProccessingUnit.getInstance().new WaveFormRenderTask(
619 backBuffer,
620 trackModel.getAllAudioBytes(),
621 trackModel.getFormat(),
622 timescaleFrameStart,
623 timescaleFrameLength,
624 true
625 );
626
627 // Wait for invalidation recomendation messages
628 renderTask.addObserver(this);
629
630 // Begin rendering ASAP
631 WaveFormRenderProccessingUnit.getInstance().queueTask(renderTask);
632 }
633
634 public void invalidateAll() {
635 invalidate(this.getBounds());
636 }
637
638 /**
639 * @param dirty
640 * The diry area with respect to this views origin... may want to
641 * do an efficient invalidation.
642 * For example if the parent of this is an iwidgets top most container
643 * then may want to explicitly invalidate rather than having to
644 * create an AWT message...
645 */
646 protected void invalidate(Rectangle dirty) {
647 if (invalidator != null) invalidator.onGraphDirty(this, dirty);
648 else super.repaint(); // the slower way
649 }
650
651 public void setInvalidator(EffecientInvalidator invalidator) {
652 this.invalidator = invalidator;
653 }
654
655 /**
656 * Adds a backing section to the graph - will be painted once visible.
657 * Does not invalidate the graph.
658 *
659 * Intended use: One BackSection for the lifetime of the instance.
660 * Just keep updateing the backing section.
661 *
662 * @param bs
663 */
664 protected void addBackingSection(BackSection bs) {
665
666 if (!backingSections.contains(bs)) {
667 backingSections.add(bs);
668 }
669 }
670
671 protected void removeBackingSection(BackSection bs) {
672 backingSections.remove(bs);
673 }
674
675 @Override
676 public void paint(Graphics g) {
677
678 // only performs rendering if widths are different.
679 // Also note that this is not invoked when widget is resizing because
680 // the widget is painted another way
681 updateBuffer(); // First paint will probably initialize the back buffer
682
683 // Draw backing sections
684 paintBacking((Graphics2D)g);
685
686 // Draw the wave forms
687 if (backBuffer != null) { // zoomed scale view
688
689 if (renderTask != null &&
690 (renderTask.isRendering()
691 || !renderTask.hasStarted())) {
692
693 // If rendering or pending - lock the buffer
694 synchronized(backBuffer) {
695
696 g.drawImage(backBuffer,
697 0,
698 0,
699 getWidth(),
700 getHeight(),
701 null);
702
703 }
704
705 // .. and give feedback to user about rendering progress
706 paintRenderProgress(g);
707
708 } else { // Not rendering
709
710 // No need to synchronize waveformBuffer because there is no thread accessing it,
711 // and a new render thread is always created on this thread.
712 g.drawImage(backBuffer,
713 0,
714 0,
715 getWidth(),
716 getHeight(),
717 null);
718
719 }
720
721 }
722
723 // Get the playback bar position
724 //int playbackFramePos = currentPlaybackFramePosition;
725
726 // If not playing but is suspended....
727 /*if (playbackFramePos < 0 && trackMix != null
728 && MixDesk.getInstance().isPaused(trackMix.getChannelID())) {
729
730 // Get the last playback pos - can be negitive if not played yet
731 playbackFramePos = MixDesk.getInstance().getLastPlayedFramePosition(trackMix.getChannelID());
732
733 // Has the user edited the audio such that the playback position is invalid?
734 if (playbackFramePos > trackModel.getFrameCount())
735 playbackFramePos = -1;
736 }*/
737
738 // Paint the playback bar
739 if (currentPlaybackFramePosition >= 0) {
740
741 int x = getPlaybackXPos(currentPlaybackFramePosition);
742
743 if (x >= 0 && x <= getWidth()) {
744
745 ((Graphics2D)g).setStroke(GRAPH_BAR_STROKE);
746 g.setColor(PLAYBACK_BAR_COLOR);
747 g.drawLine(x, 0, x, getHeight());
748 }
749
750 }
751
752 if (getComponentCount() > 0) {
753 paintChildren(g);
754 }
755
756 }
757
758 private void paintBacking(Graphics2D g) {
759
760 // Clear background
761 Paint restore = g.getPaint();
762
763 if (ApolloSystem.useQualityGraphics) {
764
765 GradientPaint gp = new GradientPaint(
766 (getWidth() / 2), (int)(getHeight() * 0.8), backColor,
767 (getWidth() / 2), 0, backColorHighlights);
768 g.setPaint(gp);
769
770 } else {
771 g.setColor(backColor);
772 }
773
774 g.fillRect(0, 0, getWidth(), getHeight());
775
776 if (ApolloSystem.useQualityGraphics) {
777 g.setPaint(restore);
778 }
779
780 // Paint sections
781 for (BackSection bs : backingSections) {
782 bs.paint(g);
783 }
784
785 }
786
787 private void paintRenderProgress(Graphics g) {
788
789 g.setColor(Color.BLACK);
790
791 g.setFont(RENDER_MESSAGE_FONT);
792
793 String message = (renderTask.isRendering()) ? "Rendering..." : "Pending...";
794/*
795 Area clip = FrameGraphics.getCurrentClip();
796 Shape clipBackUp = g.getClip();
797 Area tmpClip = (clip != null) ? clip :
798 new Area(new Rectangle(0, 0,
799 Browser._theBrowser.getContentPane().getWidth(),
800 Browser._theBrowser.getContentPane().getHeight()));
801
802 Rectangle r = getBounds();
803 r.translate(expediteePosition.x, expediteePosition.y);
804 tmpClip.intersect(new Area(r));*/
805
806 // if (!tmpClip.isEmpty()) {
807
808 //g.setClip(tmpClip);
809 g.drawString(message, (getWidth() / 2) - 40,
810 (getHeight() / 2) + (RENDER_MESSAGE_FONT.getSize() / 2)); // center roughly
811 // g.setClip(clipBackUp);
812 //}
813
814 }
815
816
817
818 /**
819 * At a given x position in the graph, the matching frame number is returned.
820 * This considers the current timescale
821 *
822 * @param x
823 *
824 * @return Frame in track model
825 */
826 public int frameAtX(int x) {
827 return frameAtX(x, timescaleFrameStart, timescaleFrameLength);
828 }
829
830 private int frameAtX(int x, int frameStart, int frameLength) {
831
832 double viewPortPercent = x;
833 viewPortPercent /= getWidth();
834
835 // clamp
836 int frame = (int)(frameLength * viewPortPercent);
837 frame += frameStart;
838
839 if (frame >= (frameStart + frameLength))
840 frame = frameStart + frameLength;
841 else if(frame < frameStart)
842 frame = frameStart;
843
844 return frame;
845 }
846
847
848
849 /**
850 * At a given frame position in the track model, the matching x coordinate in the graph is returned.
851 * This considers the current timescale. Clamped to graph bounds.
852 *
853 * @param frame
854 *
855 * @return X pos in graph
856 */
857 public int XatFrame(long frame) {
858 return XatFrame(frame, timescaleFrameStart, timescaleFrameLength);
859 }
860
861 private int XatFrame(long frame, long frameStart, long frameLength) {
862
863 double framePercent = frame - frameStart;
864 framePercent /= frameLength;
865
866 int x = (int)(getWidth() * framePercent);
867
868 // clamp
869 if (x > getWidth()) x = getWidth();
870 else if (x < 0) x = 0;
871
872 return x;
873 }
874
875
876 /**
877 * A back section of a graph... intended for ranged highlighting.
878 *
879 * @author Brook Novak
880 */
881 protected class BackSection {
882
883 int left;
884
885 /** If less or equal to 1, then nothing is drawn. */
886 int width;
887
888 /** A flag for showing/hiding backing */
889 boolean visible;
890 Color color;
891
892 BackSection() {
893 left = 0;
894 width = 0;
895 visible = false;
896 color = Color.BLACK;
897 }
898
899 void paint(Graphics g) {
900 if (visible && width > 1) {
901 g.setColor(color);
902 g.fillRect(left, 0, width, getHeight());
903 }
904 }
905
906 /**
907 * Invoked when panel resized
908 */
909 void updateBounds() {
910 }
911
912 }
913
914 /**
915 * A SampledTrackGraphView can have a EffecientInvalidator where instead of
916 * invalidating via swing AWT event it invalidates via the implementor of
917 * a EffecientInvalidator if one is set.
918 *
919 * @author Brook Novak
920 *
921 */
922 public interface EffecientInvalidator {
923 public void onGraphDirty(SampledTrackGraphView graph, Rectangle dirty);
924 }
925
926 public Color getBackColor() {
927 return backColor;
928 }
929
930 /**
931 * Changes the background color and invalidates the graph.
932 * Doesnt invalidate if the color has not changed.
933 *
934 * @param backColor
935 * The new Background color. Must not be null.
936 *
937 */
938 public void setBackColor(Color backColor, Color highlights) {
939 setBackColor(backColor, highlights, true);
940 }
941
942 /**
943 * Changes the background color.
944 *
945 * @param backColor
946 * The new Background color. Must not be null.
947 *
948 * @param invalidate
949 * True to invalidate the graph. False to miss invalidation.
950 * Doesnt invalidate if the color has not changed.
951 */
952 public void setBackColor(Color backColor, Color highlights, boolean invalidate) {
953 if (backColor == null) throw new NullPointerException("backColor");
954 if (this.backColor != backColor || backColorHighlights != highlights) {
955 this.backColor = backColor;
956 backColorHighlights = highlights;
957 if (invalidate) invalidateAll();
958 }
959 }
960
961
962
963
964
965}
Note: See TracBrowser for help on using the repository browser.