~josejuan-sanchez/+junk/original-jhv-experimental-version

« back to all changes in this revision

Viewing changes to src/jhv/src/org/helioviewer/jhv/gui/components/MoviePanel.java

  • Committer: José Juan Sánchez Hernández
  • Date: 2013-02-05 13:32:08 UTC
  • Revision ID: josejuan.sanchez@gmail.com-20130205133208-dfz1sh1uge5pjkny
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package org.helioviewer.jhv.gui.components;
 
2
 
 
3
import java.awt.BorderLayout;
 
4
import java.awt.Color;
 
5
import java.awt.Dimension;
 
6
import java.awt.FlowLayout;
 
7
import java.awt.Graphics;
 
8
import java.awt.event.ActionEvent;
 
9
import java.awt.event.ActionListener;
 
10
import java.awt.event.KeyEvent;
 
11
import java.awt.event.MouseEvent;
 
12
import java.awt.event.MouseListener;
 
13
import java.awt.event.MouseWheelEvent;
 
14
import java.awt.event.MouseWheelListener;
 
15
import java.text.ParseException;
 
16
import java.util.LinkedList;
 
17
 
 
18
import javax.swing.AbstractAction;
 
19
import javax.swing.BorderFactory;
 
20
import javax.swing.BoxLayout;
 
21
import javax.swing.Icon;
 
22
import javax.swing.JButton;
 
23
import javax.swing.JComboBox;
 
24
import javax.swing.JLabel;
 
25
import javax.swing.JPanel;
 
26
import javax.swing.JSeparator;
 
27
import javax.swing.JSlider;
 
28
import javax.swing.JSpinner;
 
29
import javax.swing.KeyStroke;
 
30
import javax.swing.SpinnerNumberModel;
 
31
import javax.swing.SwingConstants;
 
32
import javax.swing.event.ChangeListener;
 
33
import javax.swing.plaf.basic.BasicSliderUI;
 
34
 
 
35
import org.helioviewer.jhv.gui.ButtonCreator;
 
36
import org.helioviewer.jhv.gui.IconBank;
 
37
import org.helioviewer.jhv.gui.ImageViewerGui;
 
38
import org.helioviewer.jhv.gui.ViewListenerDistributor;
 
39
import org.helioviewer.jhv.gui.IconBank.JHVIcon;
 
40
import org.helioviewer.jhv.layers.LayersListener;
 
41
import org.helioviewer.jhv.layers.LayersModel;
 
42
import org.helioviewer.viewmodel.changeevent.CacheStatusChangedReason;
 
43
import org.helioviewer.viewmodel.changeevent.ChangeEvent;
 
44
import org.helioviewer.viewmodel.changeevent.LayerChangedReason;
 
45
import org.helioviewer.viewmodel.changeevent.PlayStateChangedReason;
 
46
import org.helioviewer.viewmodel.changeevent.SubImageDataChangedReason;
 
47
import org.helioviewer.viewmodel.changeevent.LayerChangedReason.LayerChangeType;
 
48
import org.helioviewer.viewmodel.metadata.ObserverMetaData;
 
49
import org.helioviewer.viewmodel.view.CachedMovieView;
 
50
import org.helioviewer.viewmodel.view.MetaDataView;
 
51
import org.helioviewer.viewmodel.view.MovieView;
 
52
import org.helioviewer.viewmodel.view.TimedMovieView;
 
53
import org.helioviewer.viewmodel.view.View;
 
54
import org.helioviewer.viewmodel.view.ViewListener;
 
55
import org.helioviewer.viewmodel.view.MovieView.AnimationMode;
 
56
import org.helioviewer.viewmodel.view.jp2view.datetime.ImmutableDateTime;
 
57
 
 
58
/**
 
59
 * Panel containing the movie controls.
 
60
 * 
 
61
 * <p>
 
62
 * This panel provides the capability to start and stop an movie, step to
 
63
 * certain frames and switch the movie speed as well as the movie mode.
 
64
 * 
 
65
 * <p>
 
66
 * Apart from that, this component is responsible for playing multiple movie
 
67
 * simultaneous. This is done by actual playing only one movie, the one with the
 
68
 * most frames per time. All other image series just jump to the frame being
 
69
 * closest to the current frame of the series currently playing. That way, it is
 
70
 * impossible that different series get asynchronous.
 
71
 * 
 
72
 * <p>
 
73
 * For further information about image series, see
 
74
 * {@link org.helioviewer.viewmodel.view.MovieView} and
 
75
 * {@link org.helioviewer.viewmodel.view.TimedMovieView}.
 
76
 * 
 
77
 * @author Markus Langenberg
 
78
 * @author Malte Nuhn
 
79
 * 
 
80
 */
 
81
public class MoviePanel extends JPanel implements ActionListener, ChangeListener, MouseListener, MouseWheelListener, ViewListener {
 
82
 
 
83
    private static final long serialVersionUID = 1L;
 
84
 
 
85
    // different animation speeds
 
86
    private enum SpeedUnit {
 
87
        FRAMESPERSECOND {
 
88
            public String toString() {
 
89
                return "Frames/sec";
 
90
            }
 
91
 
 
92
            public int getSecondsPerSecond() {
 
93
                return 0;
 
94
            }
 
95
        },
 
96
        MINUTESPERSECOND {
 
97
            public String toString() {
 
98
                return "Solar minutes/sec";
 
99
            }
 
100
 
 
101
            public int getSecondsPerSecond() {
 
102
                return 60;
 
103
            }
 
104
        },
 
105
        HOURSPERSECOND {
 
106
            public String toString() {
 
107
                return "Solar hours/sec";
 
108
            }
 
109
 
 
110
            public int getSecondsPerSecond() {
 
111
                return 3600;
 
112
            }
 
113
        },
 
114
        DAYSPERSECOND {
 
115
            public String toString() {
 
116
                return "Solar days/sec";
 
117
            }
 
118
 
 
119
            public int getSecondsPerSecond() {
 
120
                return 86400;
 
121
            }
 
122
        };
 
123
 
 
124
        public abstract int getSecondsPerSecond();
 
125
    }
 
126
 
 
127
    // Linking movies to play simultaneously
 
128
    private static LinkedMovieManager linkedMovieManager = new LinkedMovieManager();
 
129
    private static LinkedList<MoviePanel> panelList = new LinkedList<MoviePanel>();
 
130
 
 
131
    // Status
 
132
    private static boolean isAdvanced = false;
 
133
    private boolean isPlaying = false;
 
134
    private boolean isDragging = false;
 
135
 
 
136
    // Gui elements
 
137
    private TimeSlider timeSlider;
 
138
    private JLabel frameNumberLabel;
 
139
    private JButton previousFrameButton;
 
140
    private JButton playPauseButton;
 
141
    private JButton nextFrameButton;
 
142
    private JButton advancedButton;
 
143
 
 
144
    /****/
 
145
    // TEST
 
146
    //private JSpinner speedSpinner;
 
147
    public static JSpinner speedSpinner;
 
148
    /****/ 
 
149
    
 
150
    private JComboBox speedUnitComboBox;
 
151
    private JComboBox animationModeComboBox;
 
152
 
 
153
    private JPanel modePanel;
 
154
    private JPanel speedPanel;
 
155
 
 
156
    // References
 
157
    // TEST
 
158
    //private MovieView view;
 
159
    public static MovieView view;
 
160
    
 
161
    private TimedMovieView timedView = null;
 
162
 
 
163
    // Icons
 
164
    private static final Icon playIcon = IconBank.getIcon(JHVIcon.PLAY);
 
165
    private static final Icon pauseIcon = IconBank.getIcon(JHVIcon.PAUSE);
 
166
    private static final Icon openIcon = IconBank.getIcon(JHVIcon.SHOW_MORE);
 
167
    private static final Icon closeIcon = IconBank.getIcon(JHVIcon.SHOW_LESS);
 
168
 
 
169
    /**
 
170
     * Default constructor.
 
171
     * 
 
172
     * @param movieView
 
173
     *            Associated movie view
 
174
     */
 
175
    public MoviePanel(MovieView movieView) {
 
176
        this();
 
177
 
 
178
        if (movieView == null) {
 
179
            return;
 
180
        }
 
181
 
 
182
        view = movieView;
 
183
        if (view instanceof TimedMovieView) {
 
184
            timedView = (TimedMovieView) movieView;
 
185
        }
 
186
 
 
187
        timeSlider.setMaximum(movieView.getMaximumFrameNumber());
 
188
        timeSlider.setValue(movieView.getCurrentFrameNumber());
 
189
 
 
190
        SpeedUnit[] units;
 
191
        if (view.getAdapter(MetaDataView.class) != null && view.getAdapter(MetaDataView.class).getMetaData() instanceof ObserverMetaData) {
 
192
            SpeedUnit[] newunits = { SpeedUnit.MINUTESPERSECOND, SpeedUnit.HOURSPERSECOND, SpeedUnit.DAYSPERSECOND, SpeedUnit.FRAMESPERSECOND };
 
193
            units = newunits;
 
194
 
 
195
        } else {
 
196
            SpeedUnit[] newunits = { SpeedUnit.FRAMESPERSECOND };
 
197
            units = newunits;
 
198
        }
 
199
 
 
200
        speedUnitComboBox.removeActionListener(this);
 
201
        speedUnitComboBox.removeAllItems();
 
202
 
 
203
        for (SpeedUnit unit : units) {
 
204
            speedUnitComboBox.addItem(unit);
 
205
        }
 
206
 
 
207
        speedUnitComboBox.setSelectedItem(SpeedUnit.FRAMESPERSECOND);
 
208
 
 
209
        speedUnitComboBox.addActionListener(this);
 
210
 
 
211
        if (view instanceof CachedMovieView) {
 
212
            timeSlider.setPartialCachedUntil(Math.min(((CachedMovieView) view).getImageCacheStatus().getImageCachedPartiallyUntil(), ((CachedMovieView) view).getDateTimeCache().getMetaStatus()));
 
213
            timeSlider.setCompleteCachedUntil(Math.min(((CachedMovieView) view).getImageCacheStatus().getImageCachedCompletelyUntil(), ((CachedMovieView) view).getDateTimeCache().getMetaStatus()));
 
214
        }
 
215
 
 
216
        ViewListenerDistributor.getSingletonInstance().addViewListener(this);
 
217
        this.setEnabled(true);
 
218
    }
 
219
 
 
220
    public MoviePanel() {
 
221
        super(new BorderLayout());
 
222
 
 
223
        JPanel mainPanel = new JPanel();
 
224
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
 
225
        mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 
226
        add(mainPanel, BorderLayout.NORTH);
 
227
 
 
228
        // Time line
 
229
        timeSlider = new TimeSlider(TimeSlider.HORIZONTAL, 0, 0, 0);
 
230
        timeSlider.setBorder(BorderFactory.createEmptyBorder());
 
231
        timeSlider.setSnapToTicks(true);
 
232
        timeSlider.addChangeListener(this);
 
233
        timeSlider.addMouseListener(this);
 
234
        addMouseWheelListener(this);
 
235
 
 
236
        mainPanel.add(timeSlider);
 
237
 
 
238
        JPanel secondLine = new JPanel(new BorderLayout());
 
239
 
 
240
        // Control buttons
 
241
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 1, 1));
 
242
        buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
 
243
 
 
244
        previousFrameButton = ButtonCreator.createButton(IconBank.getIcon(JHVIcon.BACK), "Step to Previous Frame", this);
 
245
        buttonPanel.add(previousFrameButton);
 
246
 
 
247
        playPauseButton = ButtonCreator.createButton(playIcon, "Play movie", this);
 
248
        buttonPanel.add(playPauseButton);
 
249
 
 
250
        nextFrameButton = ButtonCreator.createButton(IconBank.getIcon(JHVIcon.FORWARD), "Step to Next Frame", this);
 
251
        buttonPanel.add(nextFrameButton);
 
252
        secondLine.add(buttonPanel, BorderLayout.WEST);
 
253
 
 
254
        buttonPanel.add(new JSeparator(SwingConstants.VERTICAL));
 
255
 
 
256
        advancedButton = ButtonCreator.createTextButton(IconBank.getIcon(JHVIcon.SHOW_MORE), "More Options", "More Options to Control Playback", this);
 
257
        buttonPanel.add(advancedButton);
 
258
 
 
259
        // Current frame number
 
260
        frameNumberLabel = new JLabel((timeSlider.getValue() + 1) + "/" + (timeSlider.getMaximum() + 1));
 
261
        frameNumberLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
 
262
        frameNumberLabel.setHorizontalAlignment(JLabel.RIGHT);
 
263
        frameNumberLabel.setPreferredSize(new Dimension(75, 20));
 
264
        secondLine.add(frameNumberLabel, BorderLayout.EAST);
 
265
 
 
266
        mainPanel.add(secondLine);
 
267
 
 
268
        // The speed panel has some distinction from above as it is one of the
 
269
        // advanced options
 
270
        // It is not included in the main Panel to save space if it is not shown
 
271
 
 
272
        // Speed
 
273
        speedPanel = new JPanel(new BorderLayout());
 
274
        speedPanel.add(new JSeparator(SwingConstants.VERTICAL), BorderLayout.PAGE_START);
 
275
        speedPanel.add(new JLabel("Speed:     "), BorderLayout.WEST);
 
276
 
 
277
        // PRUEBA
 
278
        //speedSpinner = new JSpinner(new SpinnerNumberModel(20, 1, 99, 1));
 
279
        speedSpinner = new JSpinner(new SpinnerNumberModel(5, 1, 99, 1));
 
280
        
 
281
        speedSpinner.addChangeListener(this);
 
282
        ((JSpinner.DefaultEditor) speedSpinner.getEditor()).getTextField().addActionListener(this);
 
283
        speedSpinner.setMaximumSize(speedSpinner.getPreferredSize());
 
284
        speedPanel.add(speedSpinner, BorderLayout.CENTER);
 
285
 
 
286
        SpeedUnit[] units = { SpeedUnit.FRAMESPERSECOND };
 
287
        speedUnitComboBox = new JComboBox(units);
 
288
 
 
289
        speedUnitComboBox.addActionListener(this);
 
290
        speedPanel.add(speedUnitComboBox, BorderLayout.EAST);
 
291
 
 
292
        mainPanel.add(speedPanel);
 
293
 
 
294
        // Animation mode
 
295
        modePanel = new JPanel(new BorderLayout());
 
296
        modePanel.add(new JLabel("Animation mode:"), BorderLayout.WEST);
 
297
 
 
298
        AnimationMode[] modi = { AnimationMode.LOOP, AnimationMode.STOP, AnimationMode.SWING };
 
299
        animationModeComboBox = new JComboBox(modi);
 
300
        animationModeComboBox.setPreferredSize(speedUnitComboBox.getPreferredSize());
 
301
        animationModeComboBox.addActionListener(this);
 
302
        modePanel.add(animationModeComboBox, BorderLayout.EAST);
 
303
 
 
304
        mainPanel.add(modePanel);
 
305
 
 
306
        synchronized (panelList) {
 
307
            panelList.add(this);
 
308
        }
 
309
        this.setEnabled(false);
 
310
        this.setAdvanced(MoviePanel.isAdvanced);
 
311
    }
 
312
 
 
313
    /**
 
314
     * Override the setEnabled method in order to keep the containing
 
315
     * components' enabledState synced with the enabledState of this component.
 
316
     */
 
317
    public void setEnabled(boolean enabled) {
 
318
        if (timedView == null)
 
319
            enabled = false;
 
320
 
 
321
        super.setEnabled(enabled);
 
322
        animationModeComboBox.setEnabled(enabled);
 
323
        timeSlider.setEnabled(enabled);
 
324
        playPauseButton.setEnabled(enabled);
 
325
        nextFrameButton.setEnabled(enabled);
 
326
        previousFrameButton.setEnabled(enabled);
 
327
        speedSpinner.setEnabled(enabled);
 
328
        speedUnitComboBox.setEnabled(enabled);
 
329
        advancedButton.setEnabled(enabled);
 
330
    }
 
331
 
 
332
    public void setAdvanced(boolean advanced) {
 
333
        MoviePanel.isAdvanced = advanced;
 
334
 
 
335
        advancedButton.setIcon(advanced ? closeIcon : openIcon);
 
336
        modePanel.setVisible(advanced);
 
337
        speedPanel.setVisible(advanced);
 
338
    }
 
339
 
 
340
    /**
 
341
     * Returns the movie panel for the given view
 
342
     * 
 
343
     * @return movie panel if available, else null
 
344
     */
 
345
    public static MoviePanel getMoviePanel(MovieView view) {
 
346
        if (view.getMaximumFrameNumber() <= 0) {
 
347
            return null;
 
348
        }
 
349
 
 
350
        synchronized (panelList) {
 
351
            for (MoviePanel moviePanel : panelList) {
 
352
                if (moviePanel.view != null && moviePanel.view.equals(view)) {
 
353
                    return moviePanel;
 
354
                }
 
355
            }
 
356
 
 
357
        }
 
358
        return null;
 
359
    }
 
360
 
 
361
    /**
 
362
     * Jumps to the specified frame
 
363
     * 
 
364
     * @param frame
 
365
     *            the number of the frame
 
366
     */
 
367
    public void jumpToFrameNumber(int frame) {
 
368
        if (view instanceof CachedMovieView) {
 
369
            frame = Math.min(frame, view.getMaximumAccessibleFrameNumber());
 
370
        }
 
371
        timeSlider.setValue(frame);
 
372
        view.setCurrentFrame(frame, new ChangeEvent());
 
373
    }
 
374
 
 
375
    /**
 
376
     * Returns the current frame number
 
377
     * 
 
378
     * @return the current frame number
 
379
     */
 
380
    public int getCurrentFrameNumber() {
 
381
        return view.getCurrentFrameNumber();
 
382
    }
 
383
 
 
384
    /**
 
385
     * Toggles between playing and not playing the animation.
 
386
     */
 
387
    public void togglePlayPause() {
 
388
        setPlaying(!isPlaying, false);
 
389
    }
 
390
 
 
391
    public void setPlaying(boolean playing, boolean onlyGUI) {
 
392
 
 
393
        isPlaying = playing;
 
394
 
 
395
        if (!isPlaying) {
 
396
            playPauseButton.setIcon(playIcon);
 
397
            playPauseButton.setToolTipText("Play movie");
 
398
            if (!onlyGUI) {
 
399
                view.pauseMovie();
 
400
            }
 
401
            timeSlider.setValue(view.getCurrentFrameNumber());
 
402
        } else {
 
403
            playPauseButton.setIcon(pauseIcon);
 
404
            playPauseButton.setToolTipText("Pause movie");
 
405
            if (!onlyGUI) {
 
406
                view.playMovie();
 
407
            }
 
408
        }
 
409
    }
 
410
 
 
411
    /**
 
412
     * Updates the speed of the animation. This function is called when changing
 
413
     * the speed of the animation or the its unit.
 
414
     */
 
415
    private void updateMovieSpeed() {
 
416
        if (speedUnitComboBox.getSelectedItem() == SpeedUnit.FRAMESPERSECOND) {
 
417
            view.setDesiredRelativeSpeed(((SpinnerNumberModel) speedSpinner.getModel()).getNumber().intValue());
 
418
 
 
419
        } else {
 
420
            timedView.setDesiredAbsoluteSpeed(((SpinnerNumberModel) speedSpinner.getModel()).getNumber().intValue() * ((SpeedUnit) speedUnitComboBox.getSelectedItem()).getSecondsPerSecond());
 
421
        }
 
422
    }
 
423
 
 
424
    /**
 
425
     * Locks or unlocks the movie, Should only be called by LayersModel
 
426
     * 
 
427
     * In future developments, the concept of linked movies might either be
 
428
     * dropped (when introducing a global timestamp/timeline) or be moved to
 
429
     * LayersModel
 
430
     * 
 
431
     * @param link
 
432
     *            true, if it should be locked, else false
 
433
     */
 
434
    public void setMovieLink(boolean link) {
 
435
        if (!link) {
 
436
            linkedMovieManager.unlinkMoviePanel(this);
 
437
        } else {
 
438
            linkedMovieManager.linkMoviePanel(this);
 
439
        }
 
440
    }
 
441
 
 
442
    /**
 
443
     * {@inheritDoc}
 
444
     */
 
445
    public void actionPerformed(ActionEvent e) {
 
446
 
 
447
        if (e.getSource() == advancedButton) {
 
448
            this.setAdvanced(!MoviePanel.isAdvanced);
 
449
            ImageViewerGui.getSingletonInstance().getMoviePanelContainer().updateActiveView();
 
450
 
 
451
            // Toggle play/pause
 
452
        } else if (e.getSource() == playPauseButton) {
 
453
 
 
454
            togglePlayPause();
 
455
 
 
456
            // Previous frame
 
457
        } else if (e.getSource() == previousFrameButton) {
 
458
            if (isPlaying) {
 
459
                togglePlayPause();
 
460
            }
 
461
 
 
462
            jumpToFrameNumber(getCurrentFrameNumber() - 1);
 
463
 
 
464
            // Next frame
 
465
        } else if (e.getSource() == nextFrameButton) {
 
466
            if (isPlaying) {
 
467
                togglePlayPause();
 
468
            }
 
469
 
 
470
            jumpToFrameNumber(getCurrentFrameNumber() + 1);
 
471
            // Change animation speed
 
472
        } else if (e.getSource() == ((JSpinner.DefaultEditor) speedSpinner.getEditor()).getTextField()) {
 
473
            try {
 
474
                speedSpinner.commitEdit();
 
475
            } catch (ParseException e1) {
 
476
                e1.printStackTrace();
 
477
            }
 
478
 
 
479
            linkedMovieManager.updateSpeedSpinnerLinkedMovies(this);
 
480
 
 
481
            updateMovieSpeed();
 
482
 
 
483
            // Change animation speed unit
 
484
        } else if (e.getSource() == speedUnitComboBox) {
 
485
 
 
486
            linkedMovieManager.updateSpeedUnitComboBoxLinkedMovies(this);
 
487
            updateMovieSpeed();
 
488
 
 
489
            // Change animation mode
 
490
        } else if (e.getSource() == animationModeComboBox) {
 
491
 
 
492
            linkedMovieManager.updateAnimationModeComboBoxLinkedMovies(this);
 
493
            view.setAnimationMode((AnimationMode) animationModeComboBox.getSelectedItem());
 
494
        }
 
495
 
 
496
    }
 
497
 
 
498
    // This is needed for the CardLayout
 
499
    @SuppressWarnings("deprecation")
 
500
    public void show(boolean visible) {
 
501
        super.show(visible);
 
502
        this.setAdvanced(MoviePanel.isAdvanced);
 
503
        // update
 
504
    }
 
505
 
 
506
    /**
 
507
     * {@inheritDoc}
 
508
     */
 
509
    public void stateChanged(javax.swing.event.ChangeEvent e) {
 
510
 
 
511
        // Jump to different frame
 
512
        if (e.getSource() == timeSlider) {
 
513
            jumpToFrameNumber(timeSlider.getValue());
 
514
            frameNumberLabel.setText((getCurrentFrameNumber() + 1) + "/" + (timeSlider.getMaximum() + 1));
 
515
 
 
516
            // Change animation speed
 
517
        } else if (e.getSource() == speedSpinner) {
 
518
            linkedMovieManager.updateSpeedSpinnerLinkedMovies(this);
 
519
            updateMovieSpeed();
 
520
        }
 
521
    }
 
522
 
 
523
    /**
 
524
     * {@inheritDoc}
 
525
     */
 
526
    public void mouseClicked(MouseEvent e) {
 
527
    }
 
528
 
 
529
    /**
 
530
     * {@inheritDoc}
 
531
     */
 
532
    public void mouseEntered(MouseEvent e) {
 
533
    }
 
534
 
 
535
    /**
 
536
     * {@inheritDoc}
 
537
     */
 
538
    public void mouseExited(MouseEvent e) {
 
539
    }
 
540
 
 
541
    /**
 
542
     * {@inheritDoc}
 
543
     */
 
544
    public void mousePressed(MouseEvent e) {
 
545
        linkedMovieManager.someoneIsDragging = true;
 
546
 
 
547
        if (isPlaying) {
 
548
            view.pauseMovie();
 
549
        }
 
550
    }
 
551
 
 
552
    /**
 
553
     * {@inheritDoc}
 
554
     */
 
555
    public void mouseWheelMoved(MouseWheelEvent e) {
 
556
        if (isEnabled()) {
 
557
            if (e.getWheelRotation() < 0) {
 
558
                jumpToFrameNumber(getCurrentFrameNumber() + 1);
 
559
            } else if (e.getWheelRotation() > 0) {
 
560
                jumpToFrameNumber(getCurrentFrameNumber() - 1);
 
561
            }
 
562
        }
 
563
    }
 
564
 
 
565
    /**
 
566
     * {@inheritDoc}
 
567
     */
 
568
    public void mouseReleased(MouseEvent e) {
 
569
        if (isPlaying) {
 
570
            view.playMovie();
 
571
        }
 
572
 
 
573
        linkedMovieManager.someoneIsDragging = false;
 
574
    }
 
575
 
 
576
    public void viewChanged(View sender, ChangeEvent aEvent) {
 
577
 
 
578
        // Stop movie, when the layer was removed.
 
579
        LayerChangedReason layerReason = aEvent.getLastChangedReasonByType(LayerChangedReason.class);
 
580
 
 
581
        if (layerReason != null && layerReason.getSubView().getAdapter(MovieView.class) == view && layerReason.getLayerChangeType() == LayerChangeType.LAYER_REMOVED) {
 
582
            linkedMovieManager.unlinkMoviePanel(this);
 
583
            synchronized (panelList) {
 
584
                panelList.remove(this);
 
585
            }
 
586
            return;
 
587
        }
 
588
 
 
589
        // Update time slider and linked frames
 
590
        SubImageDataChangedReason subImageDataChangedReason = aEvent.getLastChangedReasonByTypeAndView(SubImageDataChangedReason.class, view);
 
591
        if (subImageDataChangedReason != null) {
 
592
            if (!isDragging) {
 
593
                timeSlider.setValue(view.getCurrentFrameNumber());
 
594
            }
 
595
        }
 
596
 
 
597
        // Update start-stop-button. In animation mode "stop", it has to change
 
598
        // when the movie stops after reaching the last frame.
 
599
        if (aEvent.reasonOccurred(PlayStateChangedReason.class)) {
 
600
            PlayStateChangedReason pscr = aEvent.getLastChangedReasonByType(PlayStateChangedReason.class);
 
601
 
 
602
            // check if the event belongs to the same group of linked movies
 
603
            if (timedView.getLinkedMovieManager() == pscr.getLinkedMovieManager()) {
 
604
 
 
605
                if (pscr.isPlaying() != isPlaying) {
 
606
 
 
607
                    if (!isDragging && !(linkedMovieManager.someoneIsDragging)) {
 
608
 
 
609
                        // only update GUI
 
610
                        // Log.debug("Switching to " + pscr.isPlaying());
 
611
                        setPlaying(pscr.isPlaying(), true);
 
612
 
 
613
                    } else {
 
614
                        // Log.debug("Not switching because of dragging");
 
615
                    }
 
616
 
 
617
                } else {
 
618
                    // Log.debug("Playstate already ok");
 
619
                }
 
620
 
 
621
            }
 
622
        }
 
623
 
 
624
        CacheStatusChangedReason cacheReason = aEvent.getLastChangedReasonByType(CacheStatusChangedReason.class);
 
625
        if (cacheReason != null && cacheReason.getView() == view) {
 
626
            switch (cacheReason.getType()) {
 
627
            case PARTIAL:
 
628
                timeSlider.setPartialCachedUntil(cacheReason.getValue());
 
629
                break;
 
630
            case COMPLETE:
 
631
                timeSlider.setCompleteCachedUntil(cacheReason.getValue());
 
632
                break;
 
633
            }
 
634
            timeSlider.repaint();
 
635
        }
 
636
    }
 
637
 
 
638
    /**
 
639
     * Abstract base class for all static movie actions.
 
640
     * 
 
641
     * Static movie actions are supposed be integrated into {@link MenuBar},
 
642
     * also to provide shortcuts. They always refer to the active layer.
 
643
     */
 
644
    private static abstract class StaticMovieAction extends AbstractAction implements ActionListener, LayersListener {
 
645
        private static final long serialVersionUID = 1L;
 
646
        protected MoviePanel activePanel;
 
647
 
 
648
        /**
 
649
         * Default constructor.
 
650
         * 
 
651
         * @param name
 
652
         *            name of the action that shall be displayed on a button
 
653
         * @param icon
 
654
         *            icon of the action that shall be displayed on a button
 
655
         */
 
656
        public StaticMovieAction(String name, Icon icon) {
 
657
            super(name, icon);
 
658
 
 
659
            LayersModel.getSingletonInstance().addLayersListener(this);
 
660
        }
 
661
 
 
662
        /**
 
663
         * {@inheritDoc}
 
664
         */
 
665
        public void layerAdded(int idx) {
 
666
        }
 
667
 
 
668
        /**
 
669
         * {@inheritDoc}
 
670
         */
 
671
        public void layerRemoved(View oldView, int oldIdx) {
 
672
        }
 
673
 
 
674
        /**
 
675
         * {@inheritDoc}
 
676
         */
 
677
        public void layerChanged(int idx) {
 
678
        }
 
679
 
 
680
        /**
 
681
         * {@inheritDoc}
 
682
         */
 
683
        public void activeLayerChanged(int idx) {
 
684
            this.searchCorrespondingMoviePanel(LayersModel.getSingletonInstance().getLayer(idx));
 
685
        }
 
686
 
 
687
        /**
 
688
         * {@inheritDoc}
 
689
         */
 
690
        public void viewportGeometryChanged() {
 
691
        }
 
692
 
 
693
        /**
 
694
         * {@inheritDoc}
 
695
         */
 
696
        public void subImageDataChanged() {
 
697
        }
 
698
 
 
699
        /**
 
700
         * {@inheritDoc}
 
701
         */
 
702
        public void timestampChanged(int idx) {
 
703
        }
 
704
 
 
705
        /**
 
706
         * {@inheritDoc}
 
707
         */
 
708
        public void layerDownloaded(int idx) {
 
709
        }
 
710
 
 
711
        /**
 
712
         * Searches the movie panel corresponding to the given view.
 
713
         * 
 
714
         * All static movie actions are performed by accessing the movie panel
 
715
         * of the active and basically clicking on the corresponding button.
 
716
         * 
 
717
         * @param view
 
718
         *            View to search panel for
 
719
         */
 
720
        private void searchCorrespondingMoviePanel(View view) {
 
721
            if (view != null) {
 
722
                MovieView movieView = view.getAdapter(MovieView.class);
 
723
                if (movieView != null) {
 
724
                    setEnabled(true);
 
725
                    synchronized (panelList) {
 
726
                        for (MoviePanel panel : panelList) {
 
727
                            if (panel.view == movieView) {
 
728
                                activePanel = panel;
 
729
                                return;
 
730
                            }
 
731
                        }
 
732
                    }
 
733
                }
 
734
            }
 
735
            setEnabled(false);
 
736
        }
 
737
    }
 
738
 
 
739
    /**
 
740
     * Action to play or pause the active layer, if it is an image series.
 
741
     * 
 
742
     * Static movie actions are supposed be integrated into {@link MenuBar},
 
743
     * also to provide shortcuts. They always refer to the active layer.
 
744
     */
 
745
    public static class StaticPlayPauseAction extends StaticMovieAction {
 
746
        private static final long serialVersionUID = 1L;
 
747
 
 
748
        /**
 
749
         * Default constructor.
 
750
         */
 
751
        public StaticPlayPauseAction() {
 
752
            super("Play movie", playIcon);
 
753
            putValue(MNEMONIC_KEY, KeyEvent.VK_A);
 
754
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.ALT_MASK));
 
755
        }
 
756
 
 
757
        /**
 
758
         * {@inheritDoc}
 
759
         */
 
760
        public void actionPerformed(ActionEvent e) {
 
761
            if (activePanel != null) {
 
762
                activePanel.actionPerformed(new ActionEvent(activePanel.playPauseButton, 0, ""));
 
763
                putValue(NAME, activePanel.playPauseButton.getToolTipText());
 
764
                putValue(SMALL_ICON, activePanel.playPauseButton.getIcon());
 
765
            }
 
766
        }
 
767
 
 
768
        /**
 
769
         * {@inheritDoc}
 
770
         */
 
771
        public void activeLayerChanged(int idx) {
 
772
            super.activeLayerChanged(idx);
 
773
            if (activePanel != null && getValue(SMALL_ICON) != activePanel.playPauseButton.getIcon()) {
 
774
                putValue(NAME, activePanel.playPauseButton.getToolTipText());
 
775
                putValue(SMALL_ICON, activePanel.playPauseButton.getIcon());
 
776
            }
 
777
        }
 
778
    }
 
779
 
 
780
    /**
 
781
     * Action to step to the previous frame for the active layer, if it is an
 
782
     * image series.
 
783
     * 
 
784
     * Static movie actions are supposed be integrated into {@link MenuBar},
 
785
     * also to provide shortcuts. They always refer to the active layer.
 
786
     */
 
787
    public static class StaticPreviousFrameAction extends StaticMovieAction {
 
788
        private static final long serialVersionUID = 1L;
 
789
 
 
790
        /**
 
791
         * Default constructor.
 
792
         */
 
793
        public StaticPreviousFrameAction() {
 
794
            super("Step to Previous Frame", IconBank.getIcon(JHVIcon.BACK));
 
795
            putValue(MNEMONIC_KEY, KeyEvent.VK_P);
 
796
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.ALT_MASK));
 
797
        }
 
798
 
 
799
        /**
 
800
         * {@inheritDoc}
 
801
         */
 
802
        public void actionPerformed(ActionEvent e) {
 
803
            if (activePanel != null) {
 
804
                activePanel.actionPerformed(new ActionEvent(activePanel.previousFrameButton, 0, ""));
 
805
            }
 
806
        }
 
807
    }
 
808
 
 
809
    /**
 
810
     * Action to step to the next frame for the active layer, if it is an image
 
811
     * series.
 
812
     * 
 
813
     * Static movie actions are supposed be integrated into {@link MenuBar},
 
814
     * also to provide shortcuts. They always refer to the active layer.
 
815
     */
 
816
    public static class StaticNextFrameAction extends StaticMovieAction {
 
817
        private static final long serialVersionUID = 1L;
 
818
 
 
819
        /**
 
820
         * Default constructor.
 
821
         */
 
822
        public StaticNextFrameAction() {
 
823
            super("Step to Next Frame", IconBank.getIcon(JHVIcon.FORWARD));
 
824
            putValue(MNEMONIC_KEY, KeyEvent.VK_N);
 
825
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.ALT_MASK));
 
826
        }
 
827
 
 
828
        /**
 
829
         * {@inheritDoc}
 
830
         */
 
831
        public void actionPerformed(ActionEvent e) {
 
832
            if (activePanel != null) {
 
833
                activePanel.actionPerformed(new ActionEvent(activePanel.nextFrameButton, 0, ""));
 
834
            }
 
835
        }
 
836
    }
 
837
 
 
838
    /**
 
839
     * Class to synchronize linked image series.
 
840
     * 
 
841
     * Synchronize the GUI elements as well as the actual movie.
 
842
     */
 
843
    private static class LinkedMovieManager {
 
844
 
 
845
        private LinkedList<MoviePanel> linkedMovies = new LinkedList<MoviePanel>();
 
846
        public boolean someoneIsDragging = false;
 
847
 
 
848
        /**
 
849
         * Adds an image series to the set of series playing simultaneous.
 
850
         * 
 
851
         * <p>
 
852
         * The master movie panel may change.
 
853
         * 
 
854
         * @param newPanel
 
855
         *            Panel to add
 
856
         */
 
857
        public void linkMoviePanel(MoviePanel newPanel) {
 
858
 
 
859
            if (newPanel.timedView == null) {
 
860
                return;
 
861
            }
 
862
 
 
863
            newPanel.timedView.linkMovie();
 
864
 
 
865
            if (!linkedMovies.isEmpty()) {
 
866
                // Copy Settings
 
867
                MoviePanel copyFrom = linkedMovies.element();
 
868
                newPanel.isPlaying = copyFrom.isPlaying;
 
869
                newPanel.playPauseButton.setIcon(copyFrom.playPauseButton.getIcon());
 
870
                newPanel.speedSpinner.setValue(copyFrom.speedSpinner.getValue());
 
871
                newPanel.speedUnitComboBox.setSelectedItem(copyFrom.speedUnitComboBox.getSelectedItem());
 
872
                newPanel.animationModeComboBox.setSelectedItem(copyFrom.animationModeComboBox.getSelectedItem());
 
873
 
 
874
                // move frame
 
875
                ImmutableDateTime maxAvialableDateTime = newPanel.timedView.getFrameDateTime(newPanel.view.getMaximumAccessibleFrameNumber());
 
876
 
 
877
                if (maxAvialableDateTime.getMillis() >= copyFrom.timedView.getCurrentFrameDateTime().getMillis()) {
 
878
                    newPanel.timedView.setCurrentFrame(copyFrom.timedView.getCurrentFrameDateTime(), new ChangeEvent());
 
879
 
 
880
                } else {
 
881
                    newPanel.timedView.setCurrentFrame(newPanel.view.getMaximumAccessibleFrameNumber(), new ChangeEvent());
 
882
                }
 
883
            }
 
884
 
 
885
            linkedMovies.add(newPanel);
 
886
        }
 
887
 
 
888
        /**
 
889
         * Removes an image series from the set of series playing simultaneous.
 
890
         * 
 
891
         * <p>
 
892
         * The master movie panel may change.
 
893
         * 
 
894
         * @param panel
 
895
         *            Panel to remove
 
896
         */
 
897
        public void unlinkMoviePanel(MoviePanel panel) {
 
898
            panel.timedView.unlinkMovie();
 
899
            linkedMovies.remove(panel);
 
900
        }
 
901
 
 
902
        /**
 
903
         * Copies the value from the speed spinner of the given panel to all
 
904
         * other linked panels.
 
905
         * 
 
906
         * @param copyFrom
 
907
         *            Panel dominating the other ones right now
 
908
         */
 
909
        public void updateSpeedSpinnerLinkedMovies(MoviePanel copyFrom) {
 
910
            for (MoviePanel panel : linkedMovies) {
 
911
                panel.speedSpinner.setValue(copyFrom.speedSpinner.getValue());
 
912
            }
 
913
        }
 
914
 
 
915
        /**
 
916
         * Copies the value from the speed unit combobox of the given panel to
 
917
         * all other linked panels.
 
918
         * 
 
919
         * @param copyFrom
 
920
         *            Panel dominating the other ones right now
 
921
         */
 
922
        public void updateSpeedUnitComboBoxLinkedMovies(MoviePanel copyFrom) {
 
923
            for (MoviePanel panel : linkedMovies) {
 
924
                panel.speedUnitComboBox.setSelectedItem(copyFrom.speedUnitComboBox.getSelectedItem());
 
925
            }
 
926
        }
 
927
 
 
928
        /**
 
929
         * Copies the value from the animation mode combobox of the given panel
 
930
         * to all other linked panels.
 
931
         * 
 
932
         * @param copyFrom
 
933
         *            Panel dominating the other ones right now
 
934
         */
 
935
        public void updateAnimationModeComboBoxLinkedMovies(MoviePanel copyFrom) {
 
936
            for (MoviePanel panel : linkedMovies) {
 
937
                panel.animationModeComboBox.setSelectedItem(copyFrom.animationModeComboBox.getSelectedItem());
 
938
            }
 
939
        }
 
940
 
 
941
    }
 
942
 
 
943
    /**
 
944
     * Extension of JSlider displaying the caching status on the track.
 
945
     * 
 
946
     * This element provides its own look and feel. Therefore, it is independent
 
947
     * from the global look and feel.
 
948
     */
 
949
    private static class TimeSlider extends JSlider {
 
950
 
 
951
        private static final long serialVersionUID = 1L;
 
952
 
 
953
        private static final Color notCachedColor = Color.LIGHT_GRAY;
 
954
        private static final Color partialCachedColor = new Color(0x8080FF);
 
955
        private static final Color completeCachedColor = new Color(0x4040FF);
 
956
 
 
957
        private int partialCachedUntil = 0;
 
958
        private int completeCachedUntil = 0;
 
959
 
 
960
        /**
 
961
         * Default constructor
 
962
         * 
 
963
         * @param orientation
 
964
         *            specified orientation
 
965
         * @param min
 
966
         *            specified minimum
 
967
         * @param max
 
968
         *            specified maximum
 
969
         * @param value
 
970
         *            initial value
 
971
         */
 
972
        public TimeSlider(int orientation, int min, int max, int value) {
 
973
            super(orientation, min, max, value);
 
974
            setUI(new TimeSliderUI(this));
 
975
        }
 
976
 
 
977
        /**
 
978
         * Overrides updateUI, to keep own SliderUI.
 
979
         */
 
980
        public void updateUI() {
 
981
        }
 
982
 
 
983
        /**
 
984
         * Sets the frame number, to which partial information is loaded.
 
985
         * 
 
986
         * Partial information means, that the image already can be shown, but
 
987
         * not yet in full quality.
 
988
         * 
 
989
         * @param cachedUntil
 
990
         *            Frame number, to which partial information is loaded.
 
991
         */
 
992
        public void setPartialCachedUntil(int cachedUntil) {
 
993
            partialCachedUntil = cachedUntil;
 
994
            repaint();
 
995
        }
 
996
 
 
997
        /**
 
998
         * Sets the frame number, to which complete information is loaded.
 
999
         * 
 
1000
         * Complete information means, that the image can be shown in full
 
1001
         * quality.
 
1002
         * 
 
1003
         * @param cachedUntil
 
1004
         *            Frame number, to which complete information is loaded.
 
1005
         */
 
1006
        public void setCompleteCachedUntil(int cachedUntil) {
 
1007
            completeCachedUntil = cachedUntil;
 
1008
 
 
1009
            if (partialCachedUntil < cachedUntil)
 
1010
                partialCachedUntil = cachedUntil;
 
1011
 
 
1012
            repaint();
 
1013
        }
 
1014
 
 
1015
        /**
 
1016
         * Extension of BasicSliderUI overriding some drawing functions.
 
1017
         * 
 
1018
         * All functions for size calculations stay the same.
 
1019
         */
 
1020
        private class TimeSliderUI extends BasicSliderUI {
 
1021
 
 
1022
            /**
 
1023
             * Default constructor.
 
1024
             * 
 
1025
             * @param component
 
1026
             *            the component where this UI delegate is being
 
1027
             *            installed
 
1028
             */
 
1029
            public TimeSliderUI(JSlider component) {
 
1030
                super(component);
 
1031
            }
 
1032
 
 
1033
            /**
 
1034
             * {@inheritDoc}
 
1035
             */
 
1036
            protected TrackListener createTrackListener(JSlider slider) {
 
1037
                return new TimeTrackListener();
 
1038
            }
 
1039
 
 
1040
            /**
 
1041
             * {@inheritDoc}
 
1042
             */
 
1043
            protected void scrollDueToClickInTrack(int dir) {
 
1044
                setValue(this.valueForXPosition(((TimeTrackListener) trackListener).getCurrentX()));
 
1045
            }
 
1046
 
 
1047
            /**
 
1048
             * {@inheritDoc}
 
1049
             */
 
1050
            public void paintThumb(Graphics g) {
 
1051
                g.setColor(Color.BLACK);
 
1052
                g.drawRect(thumbRect.x, thumbRect.y, thumbRect.width - 1, thumbRect.height - 1);
 
1053
 
 
1054
                int x = thumbRect.x + (thumbRect.width - 1) / 2;
 
1055
                g.drawLine(x, thumbRect.y, x, thumbRect.y + thumbRect.height - 1);
 
1056
            }
 
1057
 
 
1058
            /**
 
1059
             * {@inheritDoc}
 
1060
             * 
 
1061
             * Draws the different region (no/partial/complete information
 
1062
             * loaded) in different colors.
 
1063
             */
 
1064
            public void paintTrack(Graphics g) {
 
1065
 
 
1066
                int height = getSize().height / 4;
 
1067
                int offset = (getSize().height - height) / 2;
 
1068
 
 
1069
                int partialCachedOffset = (int) ((float) (partialCachedUntil) / (getMaximum() - getMinimum()) * trackRect.width);
 
1070
 
 
1071
                int completeCachedOffset = (int) ((float) (completeCachedUntil) / (getMaximum() - getMinimum()) * trackRect.width);
 
1072
 
 
1073
                g.setColor(notCachedColor);
 
1074
                g.fillRect(trackRect.x + partialCachedOffset, offset, trackRect.width - partialCachedOffset, height);
 
1075
 
 
1076
                g.setColor(partialCachedColor);
 
1077
                g.fillRect(trackRect.x + completeCachedOffset, offset, partialCachedOffset - completeCachedOffset, height);
 
1078
 
 
1079
                g.setColor(completeCachedColor);
 
1080
                g.fillRect(trackRect.x, offset, completeCachedOffset, height);
 
1081
            }
 
1082
 
 
1083
            /**
 
1084
             * Overrides the track listener to access currentX
 
1085
             */
 
1086
            protected class TimeTrackListener extends TrackListener {
 
1087
                public int getCurrentX() {
 
1088
                    return currentMouseX;
 
1089
                }
 
1090
            }
 
1091
        }
 
1092
 
 
1093
    }
 
1094
 
 
1095
}