001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.viewer;
020
021import java.awt.BorderLayout;
022import java.awt.Dimension;
023import java.awt.event.ActionEvent;
024import java.awt.event.ActionListener;
025import java.awt.event.ComponentAdapter;
026import java.awt.event.ComponentEvent;
027
028import javax.swing.BorderFactory;
029import javax.swing.Box;
030import javax.swing.BoxLayout;
031import javax.swing.JLabel;
032import javax.swing.JPanel;
033import javax.swing.JSlider;
034import javax.swing.JSpinner;
035import javax.swing.JTextField;
036import javax.swing.SpinnerNumberModel;
037import javax.swing.SwingConstants;
038import javax.swing.Timer;
039import javax.swing.event.ChangeEvent;
040import javax.swing.event.ChangeListener;
041
042import icy.gui.component.IcySlider;
043import icy.gui.component.button.IcyToggleButton;
044import icy.gui.util.ComponentUtil;
045import icy.gui.util.GuiUtil;
046import icy.resource.ResourceUtil;
047import icy.resource.icon.IcyIcon;
048import icy.system.thread.ThreadUtil;
049
050/**
051 * @author Stephane
052 */
053public class TNavigationPanel extends JPanel
054{
055    /**
056     * 
057     */
058    private static final long serialVersionUID = 9123780562399386045L;
059
060    private static final int DEFAULT_FRAME_RATE = 15;
061
062    final JSlider slider;
063    final JLabel leftLabel;
064    final JLabel rightLabel;
065
066    final IcyToggleButton play;
067    final IcyToggleButton loop;
068    final JSpinner frameRate;
069
070    final Timer timer;
071
072    public TNavigationPanel()
073    {
074        super(true);
075
076        slider = new IcySlider(SwingConstants.HORIZONTAL);
077        slider.setFocusable(false);
078        slider.setMaximum(0);
079        slider.setMinimum(0);
080        slider.setToolTipText("Move cursor to navigate in T dimension");
081        slider.addChangeListener(new ChangeListener()
082        {
083            @Override
084            public void stateChanged(ChangeEvent e)
085            {
086                rightLabel.setText(Integer.toString(slider.getMaximum()));
087                leftLabel.setText(Integer.toString(slider.getValue()));
088                validate();
089            }
090        });
091
092        ComponentUtil.setFixedHeight(slider, 22);
093
094        timer = new Timer(1000 / DEFAULT_FRAME_RATE, new ActionListener()
095        {
096            @Override
097            public void actionPerformed(ActionEvent e)
098            {
099                // only if slider is not adjusting T position
100                if (!slider.getValueIsAdjusting())
101                {
102                    final int oldT = getTPosition();
103
104                    incTPosition();
105
106                    // end reached ?
107                    if (oldT == getTPosition())
108                    {
109                        // loop mode --> reset
110                        if (isRepeat())
111                            setTPosition(0);
112                        else
113                        {
114                            // end play
115                            stopPlay();
116                            // and reset position
117                            setTPosition(0);
118                        }
119                    }
120                }
121            }
122        });
123
124        play = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_PLAY));
125        play.setFlat(true);
126        play.setToolTipText("play");
127        play.addActionListener(new ActionListener()
128        {
129            @Override
130            public void actionPerformed(ActionEvent e)
131            {
132                if (isPlaying())
133                    stopPlay();
134                else
135                    startPlay();
136            }
137        });
138
139        loop = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_ARROW_RIGHT, 16));
140        loop.setFlat(true);
141        loop.setToolTipText("Enable loop playback");
142        loop.addActionListener(new ActionListener()
143        {
144            @Override
145            public void actionPerformed(ActionEvent e)
146            {
147                setRepeat(!isRepeat());
148            }
149        });
150        // default
151        setRepeat(true);
152
153        frameRate = new JSpinner(new SpinnerNumberModel(DEFAULT_FRAME_RATE, 1, 60, 1));
154        frameRate.setFocusable(false);
155        // no manual edition and edition focus
156        final JTextField tf = ((JSpinner.DefaultEditor) frameRate.getEditor()).getTextField();
157        tf.setEditable(false);
158        tf.setFocusable(false);
159        frameRate.setToolTipText("Change playback frame rate");
160        frameRate.addChangeListener(new ChangeListener()
161        {
162            @Override
163            public void stateChanged(ChangeEvent e)
164            {
165                final int f = ((Integer) frameRate.getValue()).intValue();
166                // adjust timer delay
167                setTimerDelay(1000 / f);
168            }
169        });
170        ComponentUtil.setFixedSize(frameRate, new Dimension(50, 22));
171
172        leftLabel = new JLabel("0");
173        leftLabel.setToolTipText("T position");
174        rightLabel = new JLabel("0");
175        rightLabel.setToolTipText("T sequence size");
176
177        final JPanel leftPanel = GuiUtil.createLineBoxPanel(Box.createHorizontalStrut(8), GuiUtil.createBoldLabel("T"),
178                Box.createHorizontalStrut(10), leftLabel);
179
180        rightLabel.addComponentListener(new ComponentAdapter()
181        {
182            @Override
183            public void componentResized(ComponentEvent e)
184            {
185                // so left label is adjusted to right label size
186                ComponentUtil.setFixedWidth(leftLabel, rightLabel.getWidth());
187                leftPanel.revalidate();
188            }
189        });
190
191        final JPanel rightPanel = new JPanel();
192        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.LINE_AXIS));
193
194        final JLabel frameRateLabel = new JLabel("FPS");
195        frameRateLabel.setToolTipText("Frames Per Second");
196
197        rightPanel.add(rightLabel);
198        rightPanel.add(Box.createHorizontalStrut(12));
199        rightPanel.add(play);
200        rightPanel.add(loop);
201        rightPanel.add(Box.createHorizontalStrut(8));
202        rightPanel.add(frameRate);
203        rightPanel.add(Box.createHorizontalStrut(4));
204        rightPanel.add(frameRateLabel);
205        rightPanel.add(Box.createHorizontalStrut(4));
206
207        // setBorder(BorderFactory.createLineBorder(BorderFactory.createTitledBorder("").getTitleColor()));
208        setBorder(BorderFactory.createTitledBorder("").getBorder());
209        setLayout(new BorderLayout());
210
211        add(leftPanel, BorderLayout.WEST);
212        add(slider, BorderLayout.CENTER);
213        add(rightPanel, BorderLayout.EAST);
214
215        validate();
216    }
217
218    protected void incTPosition()
219    {
220        setTPosition(getTPosition() + 1);
221    }
222
223    protected void decTPosition()
224    {
225        setTPosition(Math.max(0, getTPosition() - 1));
226    }
227
228    protected void setTimerDelay(int delay)
229    {
230        timer.setDelay(delay);
231    }
232
233    protected int getTPosition()
234    {
235        return slider.getValue();
236    }
237
238    protected void setTPosition(final int t)
239    {
240        // we want to be sure the T position is changed in EDT
241        ThreadUtil.invokeNow(new Runnable()
242        {
243            @Override
244            public void run()
245            {
246                slider.setValue(t);
247            }
248        });
249    }
250
251    /**
252     * Returns the frame rate (given in frame per second) for play command.
253     */
254    public int getFrameRate()
255    {
256        return ((Integer) frameRate.getValue()).intValue();
257    }
258
259    /**
260     * Sets the frame rate (given in frame per second) for play command.
261     */
262    public void setFrameRate(int fps)
263    {
264        frameRate.setValue(Integer.valueOf(fps));
265    }
266
267    /**
268     * Returns true if <code>repeat</code> is enabled for play command.
269     */
270    public boolean isRepeat()
271    {
272        return loop.getToolTipText().startsWith("Disable");
273    }
274
275    /**
276     * Set <code>repeat</code> mode for play command.
277     */
278    public void setRepeat(boolean value)
279    {
280        if (value)
281        {
282            loop.setIcon(new IcyIcon(ResourceUtil.ICON_RELOAD, 16));
283            loop.setSelected(true);
284            loop.setToolTipText("Disable loop playback");
285        }
286        else
287        {
288            loop.setIcon(new IcyIcon(ResourceUtil.ICON_ARROW_RIGHT, 16));
289            loop.setSelected(false);
290            loop.setToolTipText("Enable loop playback");
291        }
292    }
293
294    /**
295     * Returns true if currently playing.
296     */
297    public boolean isPlaying()
298    {
299        return timer.isRunning();
300    }
301
302    /**
303     * Start sequence play.
304     * 
305     * @see #stopPlay()
306     * @see #setRepeat(boolean)
307     */
308    public void startPlay()
309    {
310        timer.start();
311        play.setIcon(new IcyIcon(ResourceUtil.ICON_PAUSE));
312        play.setSelected(true);
313        play.setToolTipText("pause");
314    }
315
316    /**
317     * Stop sequence play.
318     * 
319     * @see #startPlay()
320     */
321    public void stopPlay()
322    {
323        timer.stop();
324        play.setIcon(new IcyIcon(ResourceUtil.ICON_PLAY));
325        play.setSelected(false);
326        play.setToolTipText("play");
327    }
328
329    /**
330     * @see icy.gui.component.IcySlider#setPaintLabels(boolean)
331     */
332    public void setPaintLabels(boolean b)
333    {
334        slider.setPaintLabels(b);
335    }
336
337    /**
338     * @see icy.gui.component.IcySlider#setPaintTicks(boolean)
339     */
340    public void setPaintTicks(boolean b)
341    {
342        slider.setPaintTicks(b);
343    }
344
345    /**
346     * @see javax.swing.JSlider#addChangeListener(javax.swing.event.ChangeListener)
347     */
348    public void addChangeListener(ChangeListener l)
349    {
350        slider.addChangeListener(l);
351    }
352
353    /**
354     * @see javax.swing.JSlider#removeChangeListener(javax.swing.event.ChangeListener)
355     */
356    public void removeChangeListener(ChangeListener l)
357    {
358        slider.removeChangeListener(l);
359    }
360
361    /**
362     * Remove all change listener
363     */
364    public void removeAllChangeListener()
365    {
366        for (ChangeListener l : slider.getListeners(ChangeListener.class))
367            slider.removeChangeListener(l);
368    }
369
370    /**
371     * @see javax.swing.JSlider#getValue()
372     */
373    public int getValue()
374    {
375        return slider.getValue();
376    }
377
378    /**
379     * @param n
380     * @see javax.swing.JSlider#setValue(int)
381     */
382    public void setValue(int n)
383    {
384        slider.setValue(n);
385    }
386
387    /**
388     * @see javax.swing.JSlider#getMinimum()
389     */
390    public int getMinimum()
391    {
392        return slider.getMinimum();
393    }
394
395    /**
396     * @see javax.swing.JSlider#setMinimum(int)
397     */
398    public void setMinimum(int minimum)
399    {
400        slider.setMinimum(minimum);
401    }
402
403    /**
404     * @see javax.swing.JSlider#getMaximum()
405     */
406    public int getMaximum()
407    {
408        return slider.getMaximum();
409    }
410
411    /**
412     * @see javax.swing.JSlider#setMaximum(int)
413     */
414    public void setMaximum(int maximum)
415    {
416        slider.setMaximum(maximum);
417    }
418
419    /**
420     * @see javax.swing.JSlider#getPaintTicks()
421     */
422    public boolean getPaintTicks()
423    {
424        return slider.getPaintTicks();
425    }
426
427    /**
428     * @see javax.swing.JSlider#getPaintTrack()
429     */
430    public boolean getPaintTrack()
431    {
432        return slider.getPaintTrack();
433    }
434
435    /**
436     * @see javax.swing.JSlider#setPaintTrack(boolean)
437     */
438    public void setPaintTrack(boolean b)
439    {
440        slider.setPaintTrack(b);
441    }
442
443    /**
444     * @see javax.swing.JSlider#getPaintLabels()
445     */
446    public boolean getPaintLabels()
447    {
448        return slider.getPaintLabels();
449    }
450}