source: trunk/src_apollo/org/apollo/gui/ExpandedTrackManager.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.9 KB
Line 
1package org.apollo.gui;
2
3import java.awt.Dimension;
4import java.awt.GridBagConstraints;
5import java.awt.GridBagLayout;
6import java.awt.Rectangle;
7import java.awt.event.ActionEvent;
8import java.awt.event.ActionListener;
9import java.awt.event.ComponentEvent;
10import java.awt.event.ComponentListener;
11import java.util.LinkedList;
12
13import javax.swing.JButton;
14import javax.swing.JLabel;
15import javax.swing.SwingUtilities;
16
17import org.apollo.audio.SampledTrackModel;
18import org.apollo.audio.util.TrackMixSubject;
19import org.apollo.io.IconRepository;
20import org.apollo.mvc.AbstractSubject;
21import org.apollo.mvc.SubjectChangedEvent;
22import org.apollo.util.TrackModelHandler;
23import org.apollo.util.TrackModelLoadManager;
24import org.expeditee.gui.Browser;
25import org.expeditee.gui.FrameGraphics;
26import org.expeditee.gui.MessageBay;
27import org.expeditee.gui.Popup;
28import org.expeditee.gui.PopupManager;
29
30/**
31 * All expanded tracks are contained here. Fires SubjectChangedEvent.EMPTY events whenever
32 * expansion selection changes.
33 *
34 * @author Brook Novak
35 *
36 */
37public class ExpandedTrackManager extends AbstractSubject implements TrackModelHandler {
38
39 private static final int MAX_EXPANDED_TRACK_SIZE = 4;
40
41 /** All epxanded tracks ... either in all in view or all selected ... to be viewed */
42 private LinkedList<ExpandedTrackPopup> expandedTracks = new LinkedList<ExpandedTrackPopup> (); // SHARED Model data
43
44 private MultiTrackExpansionPopup trackSelectionPopup = new MultiTrackExpansionPopup();
45
46 private static ExpandedTrackManager instance = new ExpandedTrackManager();
47 private ExpandedTrackManager() {
48
49 // Keep view centered to browser size
50 SwingUtilities.invokeLater(new Runnable() {
51 public void run() {
52
53 // Keep expanded view centered to the browser window
54 if (Browser._theBrowser != null) {
55 Browser._theBrowser.addComponentListener(new ComponentListener() {
56
57 public void componentHidden(ComponentEvent e) {
58 }
59
60 public void componentMoved(ComponentEvent e) {
61 }
62
63 public void componentResized(ComponentEvent e) {
64
65 if (PopupManager.getInstance().isShowing(trackSelectionPopup)) {
66 // User currently selecting..
67 trackSelectionPopup.setLocation(
68 (Browser._theBrowser.getWidth() / 2) - (trackSelectionPopup.getWidth() / 2), 0);
69
70 } else { // expanded views might be showing
71
72 // Fails with Maximizing of window on some systems... swing
73 // bug!! really anoying
74 updateLayout();
75
76 }
77
78 }
79
80 public void componentShown(ComponentEvent e) {
81 }
82
83 });
84 }
85
86 }
87 });
88
89 // Registewr for (indirectly) handling track models
90 TrackModelLoadManager.getInstance().addTrackModelHandler(this);
91
92 }
93
94 /**
95 *
96 * @return The singleton instance.
97 */
98 public static ExpandedTrackManager getInstance() {
99 return instance;
100 }
101
102 private void fireTrackSelectionChanged() {
103 trackSelectionPopup.onTrackSelectionChanged();
104 fireSubjectChanged(SubjectChangedEvent.EMPTY);
105 }
106
107
108 /**
109 * Giver/receiver design pattern for only allowing this singleton
110 * to creat expanded views.
111 *
112 * @param requested
113 */
114 void receiveExpandedTrackPopup(ExpandedTrackPopup requested) {
115 synchronized(expandedTracks) {
116 assert (expandedTracks.size() < MAX_EXPANDED_TRACK_SIZE);
117 expandedTracks.add(requested);
118 }
119 // Notify observers
120 fireTrackSelectionChanged();
121 }
122
123 /**
124 * Whenever a ExpandedTrackPopup hides, it invokes this.
125 * @param xtp
126 */
127 void expandedTrackPopupHidden(ExpandedTrackPopup xtp) {
128 assert(xtp != null);
129 synchronized(expandedTracks) {
130 expandedTracks.remove(xtp);
131 }
132
133 // User might have closed one but kept more visible
134 updateLayout();
135
136 // Notify observers
137 fireTrackSelectionChanged();
138 }
139
140 /**
141 * Adds a SampledTrackModel ready for expanding.
142 *
143 * @param track
144 * The track to expand. Must not be null.
145 *
146 * @param trackSourceFrameName
147 * Where the expanded tracks track source was expanded from.
148 * Must not be null
149 *
150 * @param animationSource
151 * If null no animation will be used.
152 *
153 * @param mix
154 * The mix that the expanded view should use / manipulate. Must not be null.
155 *
156 * @return
157 * True if SampledTrackModel was added.
158 * False if not - because the selection amount is maxed out or
159 * the track is already selected.
160 *
161 * @throws NullPointerException
162 * If trackModel or trackMix or trackSourceFrameName or localFilename is null.
163 */
164 public boolean addTrackToSelection(
165 SampledTrackModel track,
166 String trackSourceFrameName,
167 Rectangle animationSource,
168 TrackMixSubject mix) {
169
170 assert(track != null);
171 assert(Browser._theBrowser != null);
172 assert(mix != null);
173
174 synchronized(expandedTracks) {
175
176 if (expandedTracks.size() < MAX_EXPANDED_TRACK_SIZE
177 && !isTrackInExpansionSelection(track)) {
178
179 ExpandedTrackPopup.giveToExpandedTrackManager(
180 track,
181 mix,
182 trackSourceFrameName,
183 0,
184 track.getFrameCount()); /** @see #receiveExpandedTrackPopup */
185
186 Rectangle trackSelectionPopupBounds = null;
187
188 // Ensure that the trackSelectionPopup is not already showing / animated to show
189 if (!PopupManager.getInstance().isShowing(trackSelectionPopup)) {
190
191 trackSelectionPopupBounds = new Rectangle(
192 (Browser._theBrowser.getWidth() / 2 ) - (trackSelectionPopup.getWidth() / 2),
193 0,
194 trackSelectionPopup.getWidth(),
195 trackSelectionPopup.getHeight());
196
197 // Creep from above
198 PopupManager.getInstance().showPopup(
199 trackSelectionPopup,
200 trackSelectionPopupBounds.getLocation(),
201 null,
202 PopupManager.getInstance().new ExpandShrinkAnimator(
203 trackSelectionPopupBounds,
204 null));
205
206 } else {
207 trackSelectionPopup.repaint();
208 }
209
210 // Animate a fake popup expanding into the trackSelectionPopup from the given source if not null
211 if (animationSource != null) {
212
213 PopupManager.getInstance().doPureAnimation(PopupManager.getInstance().new ExpandShrinkAnimator(
214 animationSource, null),
215 (trackSelectionPopupBounds == null) ?
216 trackSelectionPopup.getBounds() : trackSelectionPopupBounds);
217 }
218
219 return true;
220 }
221 }
222
223 return false;
224 }
225
226 /**
227 * Expands a given SampledTrackModel into view. Hides any other
228 * ExpandedTrackPopup currently in view.
229 *
230 * If the given track is arleady in view. It will be hidden and reshown.
231 *
232 * @param track
233 * Must not be null.
234 *
235 * @param mix
236 * The mix that the expanded view should use / manipulate. Must not be null.
237 *
238 * @param animationSource
239 * Must not be null.
240 *
241 * @param trackSourceFrameName
242 * Where the expanded tracks track source was expanded from.
243 * Must not be null
244 *
245 * @param frameStart
246 * the initial zoom - clamped
247 *
248 * @param frameLength
249 * the initial zoom - clamped
250 *
251 * @throws NullPointerException
252 * If trackModel or trackMix or trackSourceFrameName or localFilename is null.
253 *
254 */
255 public void expandSingleTrack(
256 SampledTrackModel track,
257 Rectangle animationSource,
258 TrackMixSubject mix,
259 String trackSourceFrameName,
260 int frameStart,
261 int frameLength) {
262 assert(animationSource != null);
263 assert(track != null);
264
265 synchronized(expandedTracks) {
266 // Hide all expandedTracks that are currently showing
267 while (!expandedTracks.isEmpty())
268 PopupManager.getInstance().hidePopup(expandedTracks.getFirst());
269 }
270
271 // Create a expanded tracks view
272 ExpandedTrackPopup.giveToExpandedTrackManager(
273 track,
274 mix,
275 trackSourceFrameName,
276 frameStart,
277 frameLength); /** @see #receiveExpandedTrackPopup */
278
279 // Show the expanded track from - expanding from the given animation source
280 expandTracks(animationSource);
281 }
282
283 /**
284 * Removes a SampledTrackModel from being expanded...
285 * @param track
286 */
287 public void removeTrackFromSelection(SampledTrackModel track) {
288
289 assert(track != null);
290
291 synchronized(expandedTracks) {
292
293 ExpandedTrackPopup expandedView = getExpandedTrackPopup(track);
294
295 // Does exist?
296 if (expandedView == null) return;
297
298 // Is already showing?
299 else if (PopupManager.getInstance().isShowing(expandedView)) return;
300
301 // Remove from selection
302 expandedTracks.remove(expandedView);
303
304 // Explicitly release memory since not called due to it never being in the PopupManager
305 expandedView.releaseMemory();
306
307 // Notify observers
308 fireTrackSelectionChanged();
309
310 // Hide the popup selection if nothing selected anymore
311 if (expandedTracks.isEmpty()) {
312 hideTrackSelectionPopup();
313 }
314 }
315 }
316
317 private void cancelSelection() {
318
319 hideTrackSelectionPopup();
320
321 // Remove from selection
322 LinkedList<? extends ExpandedTrackPopup> old = (LinkedList<? extends ExpandedTrackPopup>)expandedTracks.clone();
323
324 synchronized(expandedTracks) {
325 expandedTracks.clear();
326 }
327
328 // Explicitly release memory since may have not of called due to it never being in the PopupManager
329 for (ExpandedTrackPopup xtp : old)
330 xtp.releaseMemory();
331
332 // Notify observers
333 fireTrackSelectionChanged();
334 }
335
336 /**
337 * @param track
338 * The track to search for.
339 *
340 * @return
341 * A tracks ExpandedTrackPopup if one exists.
342 */
343 private ExpandedTrackPopup getExpandedTrackPopup(SampledTrackModel track) {
344 assert(track != null);
345
346 synchronized(expandedTracks) {
347 for (ExpandedTrackPopup xtp : expandedTracks) {
348 if (xtp.getObservedTrackModel() == track) return xtp;
349 }
350 }
351
352 return null;
353 }
354
355
356 /**
357 *
358 * @param track
359 * Must not be null.
360 *
361 * @return
362 * True if the track is expanded - or is selected to be expanded..
363 */
364 public boolean isTrackInExpansionSelection(SampledTrackModel track) {
365 assert(track != null);
366 return getExpandedTrackPopup(track) != null;
367 }
368
369 /**
370 * @return
371 * True if at least 1 expanded track is showing - or is selected to show.
372 */
373 public boolean doesExpandedTrackExist() {
374 synchronized(expandedTracks) {
375 return expandedTracks.size() > 0;
376 }
377 }
378
379 public boolean isAnyExpandedTrackVisible() {
380 for (ExpandedTrackPopup xtp : expandedTracks) {
381 if (PopupManager.getInstance().isShowing(xtp)) return true;
382 }
383
384 return false;
385 }
386
387 /**
388 * Shows all popups (if not already visible) to view and lays them out.
389 *
390 */
391 public void showSelection() {
392 expandTracks(null);
393 }
394
395 /**
396 * Returns immediatly if there are no tracks selected. Only expands if the popup is not already expanded.
397 *
398 * @param directAnimSource
399 * Set to null to animate expanded popups from the trackSelectionPopup.
400 * Otherwise specify where to animate from. (Intention for latter case: a single expanded view from
401 * a widget).
402 */
403 private void expandTracks(Rectangle directAnimSource) {
404
405 synchronized(expandedTracks) {
406
407 if (expandedTracks.isEmpty()) return;
408
409 // Animate the popups from the trackSelectionPopup if there is not directAnimSource
410 if (directAnimSource == null) {
411 directAnimSource = trackSelectionPopup.getBounds();
412 }
413
414 // Update the expanded view layouts
415 updateLayout();
416
417 // Animate the popup(s)
418 for (ExpandedTrackPopup xtp : expandedTracks) {
419
420 // Note: returns immediatly if already showing
421 PopupManager.getInstance().showPopup(
422 xtp,
423 xtp.getLocation(),
424 null,
425 PopupManager.getInstance().new ExpandShrinkAnimator(
426 directAnimSource,
427 null));
428
429 }
430 }
431
432 // Hide the trackSelectionPopup
433 hideTrackSelectionPopup();
434
435 }
436
437 private void hideTrackSelectionPopup() {
438 PopupManager.getInstance().hidePopup(
439 trackSelectionPopup, PopupManager.getInstance().new ExpandShrinkAnimator(
440 new Rectangle((Browser._theBrowser.getWidth() / 2 ) - (trackSelectionPopup.getWidth() / 2),
441 0, trackSelectionPopup.getWidth(), trackSelectionPopup.getHeight()), null));
442 }
443
444 /**
445 * Sets the bounds for all ExpandedTrackPopups, whether they are showing or not, to the
446 * current state of the window.
447 */
448 private void updateLayout() {
449
450 synchronized(expandedTracks) {
451
452
453 if (expandedTracks.isEmpty()) return;
454
455 assert (expandedTracks.size() > 0);
456 assert(Browser._theBrowser != null);
457
458 int cpWidth = Browser._theBrowser.getContentPane().getWidth();
459 int cpHeight = Browser._theBrowser.getContentPane().getHeight();
460 if (!FrameGraphics.isAudienceMode()) { // if message bay is showing - the remove the height
461
462 cpHeight -= MessageBay.MESSAGE_BUFFER_HEIGHT;
463
464 }
465
466 if (cpHeight < 0) cpHeight = 0;
467
468 // Calc widths
469 final int EXPANDED_HORO_MARGINS = 100;
470 int widths = cpWidth - (2 * EXPANDED_HORO_MARGINS);
471 if (widths < 150) widths = 150;
472
473 // Calc heights (per track view)
474 final int DESIRED_EXPANDED_HEIGHT = 340;
475 final int EXPANDED_VERT_MARGINS = 20;
476 int heights;
477 if ((expandedTracks.size() * DESIRED_EXPANDED_HEIGHT) <= (cpHeight - (2 * EXPANDED_VERT_MARGINS))) {
478 heights = DESIRED_EXPANDED_HEIGHT;
479 } else {
480 heights = (cpHeight - (2 * EXPANDED_VERT_MARGINS)) / expandedTracks.size();
481 if (heights < 100) heights = 100;
482 }
483
484 // Center the popup(s)
485 int y = ((cpHeight - (1 * EXPANDED_VERT_MARGINS)) - (expandedTracks.size() * heights)) / 2;
486 if (y < 0 || y > cpHeight) y = EXPANDED_VERT_MARGINS;
487
488 int x = (cpWidth - widths) / 2;
489 if (x < 0) x = 0;
490
491 // Update bounds
492 for (ExpandedTrackPopup xtp : expandedTracks) {
493
494 xtp.setBounds(x, y, widths, heights);
495
496 y += heights;
497
498 }
499 }
500
501 }
502
503 /**
504 * Global Track model re-use .... can be called from any thread
505 */
506 public SampledTrackModel getSharedSampledTrackModel(String localfilename) {
507 synchronized(expandedTracks) {
508 for (ExpandedTrackPopup xtp : expandedTracks) {
509 if (xtp.getObservedTrackModel().getLocalFilename().equals(localfilename))
510 return xtp.getObservedTrackModel();
511 }
512 }
513
514 return null;
515 }
516
517
518 /**
519 * A little popup that shows whenever there are tracks selected for expanding
520 * @author bjn8
521 *
522 */
523 private class MultiTrackExpansionPopup extends Popup implements ActionListener {
524
525 private JButton expandButton;
526 private JButton cancelButton;
527 private JLabel selected1;
528 private JLabel selected2;
529 private JLabel selectedMore;
530
531 private static final long serialVersionUID = 1L;
532 //private final Color BACK_COLOR = SampledTrackGraphViewPort.ZOOM_BACKING_COLOR_NORMAL;
533
534 MultiTrackExpansionPopup() {
535 super(new GridBagLayout());
536 setSize(260, 80);
537 super.setAudoHide(false);
538
539 expandButton = new JButton(IconRepository.getIcon("expand.png"));
540 expandButton.addActionListener(this);
541 expandButton.setPreferredSize(new Dimension(40, 40));
542 expandButton.setToolTipText("Expand selection");
543
544 cancelButton = new JButton(IconRepository.getIcon("close.png"));
545 cancelButton.addActionListener(this);
546 cancelButton.setPreferredSize(new Dimension(40, 40));
547 cancelButton.setToolTipText("Cancel selection");
548
549 selected1 = new JLabel();
550 selected1.setHorizontalTextPosition(JLabel.LEFT);
551 selected1.setVerticalTextPosition(JLabel.CENTER);
552
553 selected2 = new JLabel();
554 selected2.setHorizontalTextPosition(JLabel.LEFT);
555 selected2.setVerticalTextPosition(JLabel.CENTER);
556
557 selectedMore = new JLabel();
558 selectedMore.setHorizontalTextPosition(JLabel.CENTER);
559 selectedMore.setVerticalTextPosition(JLabel.CENTER);
560
561 GridBagConstraints c = new GridBagConstraints();
562 c.gridx = 0;
563 c.gridy = 0;
564 c.fill = GridBagConstraints.CENTER;
565 add(expandButton, c);
566
567 c = new GridBagConstraints();
568 c.gridx = 1;
569 c.gridy = 0;
570 c.fill = GridBagConstraints.CENTER;
571 add(cancelButton, c);
572
573 c = new GridBagConstraints();
574 c.gridx = 0;
575 c.gridy = 1;
576 c.gridwidth = 2;
577 c.fill = GridBagConstraints.CENTER;
578 add(selected1, c);
579
580 c = new GridBagConstraints();
581 c.gridx = 0;
582 c.gridy = 2;
583 c.gridwidth = 2;
584 c.fill = GridBagConstraints.CENTER;
585 add(selected2, c);
586
587 c = new GridBagConstraints();
588 c.gridx = 0;
589 c.gridy = 1;
590 c.gridwidth = 2;
591 c.gridheight = 2;
592 c.fill = GridBagConstraints.CENTER;
593 add(selectedMore, c);
594
595 }
596
597 public void onTrackSelectionChanged() {
598
599 synchronized(expandedTracks) {
600
601 if (expandedTracks.isEmpty()) return;
602
603 selected1.setVisible(false);
604 selected2.setVisible(false);
605 selectedMore.setVisible(false);
606
607 if (expandedTracks.size() <= 2) {
608 for (int i = 0; i < expandedTracks.size(); i++) {
609
610 ExpandedTrackPopup expp = expandedTracks.get(i);
611 JLabel label = (i == 0) ? selected1 : selected2;
612
613 String trackName = expp.getObservedTrackModel().getName();
614
615 if (trackName == null || trackName.length() == 0) trackName = "Unnamed";
616 else if (trackName.length() > 16) trackName = trackName.substring(0, 14) + "...";
617
618 label.setText(trackName);
619 label.setVisible(true);
620
621 }
622
623 } else {
624 selectedMore.setText(expandedTracks.size() + " tracks selected");
625 selectedMore.setVisible(true);
626 }
627 }
628 }
629
630 public void actionPerformed(ActionEvent e) {
631 if (e.getSource() == expandButton) {
632 showSelection();
633 } else if (e.getSource() == cancelButton) {
634 cancelSelection();
635 }
636 }
637
638
639
640 }
641
642
643}
Note: See TracBrowser for help on using the repository browser.