1
package org.helioviewer.jhv.gui.components;
3
import java.awt.BorderLayout;
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;
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;
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;
59
* Panel containing the movie controls.
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.
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.
73
* For further information about image series, see
74
* {@link org.helioviewer.viewmodel.view.MovieView} and
75
* {@link org.helioviewer.viewmodel.view.TimedMovieView}.
77
* @author Markus Langenberg
81
public class MoviePanel extends JPanel implements ActionListener, ChangeListener, MouseListener, MouseWheelListener, ViewListener {
83
private static final long serialVersionUID = 1L;
85
// different animation speeds
86
private enum SpeedUnit {
88
public String toString() {
92
public int getSecondsPerSecond() {
97
public String toString() {
98
return "Solar minutes/sec";
101
public int getSecondsPerSecond() {
106
public String toString() {
107
return "Solar hours/sec";
110
public int getSecondsPerSecond() {
115
public String toString() {
116
return "Solar days/sec";
119
public int getSecondsPerSecond() {
124
public abstract int getSecondsPerSecond();
127
// Linking movies to play simultaneously
128
private static LinkedMovieManager linkedMovieManager = new LinkedMovieManager();
129
private static LinkedList<MoviePanel> panelList = new LinkedList<MoviePanel>();
132
private static boolean isAdvanced = false;
133
private boolean isPlaying = false;
134
private boolean isDragging = false;
137
private TimeSlider timeSlider;
138
private JLabel frameNumberLabel;
139
private JButton previousFrameButton;
140
private JButton playPauseButton;
141
private JButton nextFrameButton;
142
private JButton advancedButton;
146
//private JSpinner speedSpinner;
147
public static JSpinner speedSpinner;
150
private JComboBox speedUnitComboBox;
151
private JComboBox animationModeComboBox;
153
private JPanel modePanel;
154
private JPanel speedPanel;
158
//private MovieView view;
159
public static MovieView view;
161
private TimedMovieView timedView = null;
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);
170
* Default constructor.
173
* Associated movie view
175
public MoviePanel(MovieView movieView) {
178
if (movieView == null) {
183
if (view instanceof TimedMovieView) {
184
timedView = (TimedMovieView) movieView;
187
timeSlider.setMaximum(movieView.getMaximumFrameNumber());
188
timeSlider.setValue(movieView.getCurrentFrameNumber());
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 };
196
SpeedUnit[] newunits = { SpeedUnit.FRAMESPERSECOND };
200
speedUnitComboBox.removeActionListener(this);
201
speedUnitComboBox.removeAllItems();
203
for (SpeedUnit unit : units) {
204
speedUnitComboBox.addItem(unit);
207
speedUnitComboBox.setSelectedItem(SpeedUnit.FRAMESPERSECOND);
209
speedUnitComboBox.addActionListener(this);
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()));
216
ViewListenerDistributor.getSingletonInstance().addViewListener(this);
217
this.setEnabled(true);
220
public MoviePanel() {
221
super(new BorderLayout());
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);
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);
236
mainPanel.add(timeSlider);
238
JPanel secondLine = new JPanel(new BorderLayout());
241
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 1, 1));
242
buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
244
previousFrameButton = ButtonCreator.createButton(IconBank.getIcon(JHVIcon.BACK), "Step to Previous Frame", this);
245
buttonPanel.add(previousFrameButton);
247
playPauseButton = ButtonCreator.createButton(playIcon, "Play movie", this);
248
buttonPanel.add(playPauseButton);
250
nextFrameButton = ButtonCreator.createButton(IconBank.getIcon(JHVIcon.FORWARD), "Step to Next Frame", this);
251
buttonPanel.add(nextFrameButton);
252
secondLine.add(buttonPanel, BorderLayout.WEST);
254
buttonPanel.add(new JSeparator(SwingConstants.VERTICAL));
256
advancedButton = ButtonCreator.createTextButton(IconBank.getIcon(JHVIcon.SHOW_MORE), "More Options", "More Options to Control Playback", this);
257
buttonPanel.add(advancedButton);
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);
266
mainPanel.add(secondLine);
268
// The speed panel has some distinction from above as it is one of the
270
// It is not included in the main Panel to save space if it is not shown
273
speedPanel = new JPanel(new BorderLayout());
274
speedPanel.add(new JSeparator(SwingConstants.VERTICAL), BorderLayout.PAGE_START);
275
speedPanel.add(new JLabel("Speed: "), BorderLayout.WEST);
278
//speedSpinner = new JSpinner(new SpinnerNumberModel(20, 1, 99, 1));
279
speedSpinner = new JSpinner(new SpinnerNumberModel(5, 1, 99, 1));
281
speedSpinner.addChangeListener(this);
282
((JSpinner.DefaultEditor) speedSpinner.getEditor()).getTextField().addActionListener(this);
283
speedSpinner.setMaximumSize(speedSpinner.getPreferredSize());
284
speedPanel.add(speedSpinner, BorderLayout.CENTER);
286
SpeedUnit[] units = { SpeedUnit.FRAMESPERSECOND };
287
speedUnitComboBox = new JComboBox(units);
289
speedUnitComboBox.addActionListener(this);
290
speedPanel.add(speedUnitComboBox, BorderLayout.EAST);
292
mainPanel.add(speedPanel);
295
modePanel = new JPanel(new BorderLayout());
296
modePanel.add(new JLabel("Animation mode:"), BorderLayout.WEST);
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);
304
mainPanel.add(modePanel);
306
synchronized (panelList) {
309
this.setEnabled(false);
310
this.setAdvanced(MoviePanel.isAdvanced);
314
* Override the setEnabled method in order to keep the containing
315
* components' enabledState synced with the enabledState of this component.
317
public void setEnabled(boolean enabled) {
318
if (timedView == null)
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);
332
public void setAdvanced(boolean advanced) {
333
MoviePanel.isAdvanced = advanced;
335
advancedButton.setIcon(advanced ? closeIcon : openIcon);
336
modePanel.setVisible(advanced);
337
speedPanel.setVisible(advanced);
341
* Returns the movie panel for the given view
343
* @return movie panel if available, else null
345
public static MoviePanel getMoviePanel(MovieView view) {
346
if (view.getMaximumFrameNumber() <= 0) {
350
synchronized (panelList) {
351
for (MoviePanel moviePanel : panelList) {
352
if (moviePanel.view != null && moviePanel.view.equals(view)) {
362
* Jumps to the specified frame
365
* the number of the frame
367
public void jumpToFrameNumber(int frame) {
368
if (view instanceof CachedMovieView) {
369
frame = Math.min(frame, view.getMaximumAccessibleFrameNumber());
371
timeSlider.setValue(frame);
372
view.setCurrentFrame(frame, new ChangeEvent());
376
* Returns the current frame number
378
* @return the current frame number
380
public int getCurrentFrameNumber() {
381
return view.getCurrentFrameNumber();
385
* Toggles between playing and not playing the animation.
387
public void togglePlayPause() {
388
setPlaying(!isPlaying, false);
391
public void setPlaying(boolean playing, boolean onlyGUI) {
396
playPauseButton.setIcon(playIcon);
397
playPauseButton.setToolTipText("Play movie");
401
timeSlider.setValue(view.getCurrentFrameNumber());
403
playPauseButton.setIcon(pauseIcon);
404
playPauseButton.setToolTipText("Pause movie");
412
* Updates the speed of the animation. This function is called when changing
413
* the speed of the animation or the its unit.
415
private void updateMovieSpeed() {
416
if (speedUnitComboBox.getSelectedItem() == SpeedUnit.FRAMESPERSECOND) {
417
view.setDesiredRelativeSpeed(((SpinnerNumberModel) speedSpinner.getModel()).getNumber().intValue());
420
timedView.setDesiredAbsoluteSpeed(((SpinnerNumberModel) speedSpinner.getModel()).getNumber().intValue() * ((SpeedUnit) speedUnitComboBox.getSelectedItem()).getSecondsPerSecond());
425
* Locks or unlocks the movie, Should only be called by LayersModel
427
* In future developments, the concept of linked movies might either be
428
* dropped (when introducing a global timestamp/timeline) or be moved to
432
* true, if it should be locked, else false
434
public void setMovieLink(boolean link) {
436
linkedMovieManager.unlinkMoviePanel(this);
438
linkedMovieManager.linkMoviePanel(this);
445
public void actionPerformed(ActionEvent e) {
447
if (e.getSource() == advancedButton) {
448
this.setAdvanced(!MoviePanel.isAdvanced);
449
ImageViewerGui.getSingletonInstance().getMoviePanelContainer().updateActiveView();
452
} else if (e.getSource() == playPauseButton) {
457
} else if (e.getSource() == previousFrameButton) {
462
jumpToFrameNumber(getCurrentFrameNumber() - 1);
465
} else if (e.getSource() == nextFrameButton) {
470
jumpToFrameNumber(getCurrentFrameNumber() + 1);
471
// Change animation speed
472
} else if (e.getSource() == ((JSpinner.DefaultEditor) speedSpinner.getEditor()).getTextField()) {
474
speedSpinner.commitEdit();
475
} catch (ParseException e1) {
476
e1.printStackTrace();
479
linkedMovieManager.updateSpeedSpinnerLinkedMovies(this);
483
// Change animation speed unit
484
} else if (e.getSource() == speedUnitComboBox) {
486
linkedMovieManager.updateSpeedUnitComboBoxLinkedMovies(this);
489
// Change animation mode
490
} else if (e.getSource() == animationModeComboBox) {
492
linkedMovieManager.updateAnimationModeComboBoxLinkedMovies(this);
493
view.setAnimationMode((AnimationMode) animationModeComboBox.getSelectedItem());
498
// This is needed for the CardLayout
499
@SuppressWarnings("deprecation")
500
public void show(boolean visible) {
502
this.setAdvanced(MoviePanel.isAdvanced);
509
public void stateChanged(javax.swing.event.ChangeEvent e) {
511
// Jump to different frame
512
if (e.getSource() == timeSlider) {
513
jumpToFrameNumber(timeSlider.getValue());
514
frameNumberLabel.setText((getCurrentFrameNumber() + 1) + "/" + (timeSlider.getMaximum() + 1));
516
// Change animation speed
517
} else if (e.getSource() == speedSpinner) {
518
linkedMovieManager.updateSpeedSpinnerLinkedMovies(this);
526
public void mouseClicked(MouseEvent e) {
532
public void mouseEntered(MouseEvent e) {
538
public void mouseExited(MouseEvent e) {
544
public void mousePressed(MouseEvent e) {
545
linkedMovieManager.someoneIsDragging = true;
555
public void mouseWheelMoved(MouseWheelEvent e) {
557
if (e.getWheelRotation() < 0) {
558
jumpToFrameNumber(getCurrentFrameNumber() + 1);
559
} else if (e.getWheelRotation() > 0) {
560
jumpToFrameNumber(getCurrentFrameNumber() - 1);
568
public void mouseReleased(MouseEvent e) {
573
linkedMovieManager.someoneIsDragging = false;
576
public void viewChanged(View sender, ChangeEvent aEvent) {
578
// Stop movie, when the layer was removed.
579
LayerChangedReason layerReason = aEvent.getLastChangedReasonByType(LayerChangedReason.class);
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);
589
// Update time slider and linked frames
590
SubImageDataChangedReason subImageDataChangedReason = aEvent.getLastChangedReasonByTypeAndView(SubImageDataChangedReason.class, view);
591
if (subImageDataChangedReason != null) {
593
timeSlider.setValue(view.getCurrentFrameNumber());
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);
602
// check if the event belongs to the same group of linked movies
603
if (timedView.getLinkedMovieManager() == pscr.getLinkedMovieManager()) {
605
if (pscr.isPlaying() != isPlaying) {
607
if (!isDragging && !(linkedMovieManager.someoneIsDragging)) {
610
// Log.debug("Switching to " + pscr.isPlaying());
611
setPlaying(pscr.isPlaying(), true);
614
// Log.debug("Not switching because of dragging");
618
// Log.debug("Playstate already ok");
624
CacheStatusChangedReason cacheReason = aEvent.getLastChangedReasonByType(CacheStatusChangedReason.class);
625
if (cacheReason != null && cacheReason.getView() == view) {
626
switch (cacheReason.getType()) {
628
timeSlider.setPartialCachedUntil(cacheReason.getValue());
631
timeSlider.setCompleteCachedUntil(cacheReason.getValue());
634
timeSlider.repaint();
639
* Abstract base class for all static movie actions.
641
* Static movie actions are supposed be integrated into {@link MenuBar},
642
* also to provide shortcuts. They always refer to the active layer.
644
private static abstract class StaticMovieAction extends AbstractAction implements ActionListener, LayersListener {
645
private static final long serialVersionUID = 1L;
646
protected MoviePanel activePanel;
649
* Default constructor.
652
* name of the action that shall be displayed on a button
654
* icon of the action that shall be displayed on a button
656
public StaticMovieAction(String name, Icon icon) {
659
LayersModel.getSingletonInstance().addLayersListener(this);
665
public void layerAdded(int idx) {
671
public void layerRemoved(View oldView, int oldIdx) {
677
public void layerChanged(int idx) {
683
public void activeLayerChanged(int idx) {
684
this.searchCorrespondingMoviePanel(LayersModel.getSingletonInstance().getLayer(idx));
690
public void viewportGeometryChanged() {
696
public void subImageDataChanged() {
702
public void timestampChanged(int idx) {
708
public void layerDownloaded(int idx) {
712
* Searches the movie panel corresponding to the given view.
714
* All static movie actions are performed by accessing the movie panel
715
* of the active and basically clicking on the corresponding button.
718
* View to search panel for
720
private void searchCorrespondingMoviePanel(View view) {
722
MovieView movieView = view.getAdapter(MovieView.class);
723
if (movieView != null) {
725
synchronized (panelList) {
726
for (MoviePanel panel : panelList) {
727
if (panel.view == movieView) {
740
* Action to play or pause the active layer, if it is an image series.
742
* Static movie actions are supposed be integrated into {@link MenuBar},
743
* also to provide shortcuts. They always refer to the active layer.
745
public static class StaticPlayPauseAction extends StaticMovieAction {
746
private static final long serialVersionUID = 1L;
749
* Default constructor.
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));
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());
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());
781
* Action to step to the previous frame for the active layer, if it is an
784
* Static movie actions are supposed be integrated into {@link MenuBar},
785
* also to provide shortcuts. They always refer to the active layer.
787
public static class StaticPreviousFrameAction extends StaticMovieAction {
788
private static final long serialVersionUID = 1L;
791
* Default constructor.
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));
802
public void actionPerformed(ActionEvent e) {
803
if (activePanel != null) {
804
activePanel.actionPerformed(new ActionEvent(activePanel.previousFrameButton, 0, ""));
810
* Action to step to the next frame for the active layer, if it is an image
813
* Static movie actions are supposed be integrated into {@link MenuBar},
814
* also to provide shortcuts. They always refer to the active layer.
816
public static class StaticNextFrameAction extends StaticMovieAction {
817
private static final long serialVersionUID = 1L;
820
* Default constructor.
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));
831
public void actionPerformed(ActionEvent e) {
832
if (activePanel != null) {
833
activePanel.actionPerformed(new ActionEvent(activePanel.nextFrameButton, 0, ""));
839
* Class to synchronize linked image series.
841
* Synchronize the GUI elements as well as the actual movie.
843
private static class LinkedMovieManager {
845
private LinkedList<MoviePanel> linkedMovies = new LinkedList<MoviePanel>();
846
public boolean someoneIsDragging = false;
849
* Adds an image series to the set of series playing simultaneous.
852
* The master movie panel may change.
857
public void linkMoviePanel(MoviePanel newPanel) {
859
if (newPanel.timedView == null) {
863
newPanel.timedView.linkMovie();
865
if (!linkedMovies.isEmpty()) {
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());
875
ImmutableDateTime maxAvialableDateTime = newPanel.timedView.getFrameDateTime(newPanel.view.getMaximumAccessibleFrameNumber());
877
if (maxAvialableDateTime.getMillis() >= copyFrom.timedView.getCurrentFrameDateTime().getMillis()) {
878
newPanel.timedView.setCurrentFrame(copyFrom.timedView.getCurrentFrameDateTime(), new ChangeEvent());
881
newPanel.timedView.setCurrentFrame(newPanel.view.getMaximumAccessibleFrameNumber(), new ChangeEvent());
885
linkedMovies.add(newPanel);
889
* Removes an image series from the set of series playing simultaneous.
892
* The master movie panel may change.
897
public void unlinkMoviePanel(MoviePanel panel) {
898
panel.timedView.unlinkMovie();
899
linkedMovies.remove(panel);
903
* Copies the value from the speed spinner of the given panel to all
904
* other linked panels.
907
* Panel dominating the other ones right now
909
public void updateSpeedSpinnerLinkedMovies(MoviePanel copyFrom) {
910
for (MoviePanel panel : linkedMovies) {
911
panel.speedSpinner.setValue(copyFrom.speedSpinner.getValue());
916
* Copies the value from the speed unit combobox of the given panel to
917
* all other linked panels.
920
* Panel dominating the other ones right now
922
public void updateSpeedUnitComboBoxLinkedMovies(MoviePanel copyFrom) {
923
for (MoviePanel panel : linkedMovies) {
924
panel.speedUnitComboBox.setSelectedItem(copyFrom.speedUnitComboBox.getSelectedItem());
929
* Copies the value from the animation mode combobox of the given panel
930
* to all other linked panels.
933
* Panel dominating the other ones right now
935
public void updateAnimationModeComboBoxLinkedMovies(MoviePanel copyFrom) {
936
for (MoviePanel panel : linkedMovies) {
937
panel.animationModeComboBox.setSelectedItem(copyFrom.animationModeComboBox.getSelectedItem());
944
* Extension of JSlider displaying the caching status on the track.
946
* This element provides its own look and feel. Therefore, it is independent
947
* from the global look and feel.
949
private static class TimeSlider extends JSlider {
951
private static final long serialVersionUID = 1L;
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);
957
private int partialCachedUntil = 0;
958
private int completeCachedUntil = 0;
961
* Default constructor
964
* specified orientation
972
public TimeSlider(int orientation, int min, int max, int value) {
973
super(orientation, min, max, value);
974
setUI(new TimeSliderUI(this));
978
* Overrides updateUI, to keep own SliderUI.
980
public void updateUI() {
984
* Sets the frame number, to which partial information is loaded.
986
* Partial information means, that the image already can be shown, but
987
* not yet in full quality.
990
* Frame number, to which partial information is loaded.
992
public void setPartialCachedUntil(int cachedUntil) {
993
partialCachedUntil = cachedUntil;
998
* Sets the frame number, to which complete information is loaded.
1000
* Complete information means, that the image can be shown in full
1003
* @param cachedUntil
1004
* Frame number, to which complete information is loaded.
1006
public void setCompleteCachedUntil(int cachedUntil) {
1007
completeCachedUntil = cachedUntil;
1009
if (partialCachedUntil < cachedUntil)
1010
partialCachedUntil = cachedUntil;
1016
* Extension of BasicSliderUI overriding some drawing functions.
1018
* All functions for size calculations stay the same.
1020
private class TimeSliderUI extends BasicSliderUI {
1023
* Default constructor.
1026
* the component where this UI delegate is being
1029
public TimeSliderUI(JSlider component) {
1036
protected TrackListener createTrackListener(JSlider slider) {
1037
return new TimeTrackListener();
1043
protected void scrollDueToClickInTrack(int dir) {
1044
setValue(this.valueForXPosition(((TimeTrackListener) trackListener).getCurrentX()));
1050
public void paintThumb(Graphics g) {
1051
g.setColor(Color.BLACK);
1052
g.drawRect(thumbRect.x, thumbRect.y, thumbRect.width - 1, thumbRect.height - 1);
1054
int x = thumbRect.x + (thumbRect.width - 1) / 2;
1055
g.drawLine(x, thumbRect.y, x, thumbRect.y + thumbRect.height - 1);
1061
* Draws the different region (no/partial/complete information
1062
* loaded) in different colors.
1064
public void paintTrack(Graphics g) {
1066
int height = getSize().height / 4;
1067
int offset = (getSize().height - height) / 2;
1069
int partialCachedOffset = (int) ((float) (partialCachedUntil) / (getMaximum() - getMinimum()) * trackRect.width);
1071
int completeCachedOffset = (int) ((float) (completeCachedUntil) / (getMaximum() - getMinimum()) * trackRect.width);
1073
g.setColor(notCachedColor);
1074
g.fillRect(trackRect.x + partialCachedOffset, offset, trackRect.width - partialCachedOffset, height);
1076
g.setColor(partialCachedColor);
1077
g.fillRect(trackRect.x + completeCachedOffset, offset, partialCachedOffset - completeCachedOffset, height);
1079
g.setColor(completeCachedColor);
1080
g.fillRect(trackRect.x, offset, completeCachedOffset, height);
1084
* Overrides the track listener to access currentX
1086
protected class TimeTrackListener extends TrackListener {
1087
public int getCurrentX() {
1088
return currentMouseX;