001/* An actor whose output is controlled by a slider in the run window.
002
003 @Copyright (c) 2001-2014 The Regents of the University of California.
004 All rights reserved.
005
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the
009 above copyright notice and the following two paragraphs appear in all
010 copies of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION 2
026 COPYRIGHTENDKEY
027 */
028package ptolemy.actor.lib.gui;
029
030import java.awt.Color;
031import java.awt.Container;
032
033import javax.swing.BorderFactory;
034import javax.swing.JFrame;
035import javax.swing.JPanel;
036import javax.swing.JSlider;
037import javax.swing.SwingConstants;
038import javax.swing.SwingUtilities;
039import javax.swing.border.EmptyBorder;
040import javax.swing.border.LineBorder;
041import javax.swing.event.ChangeEvent;
042import javax.swing.event.ChangeListener;
043
044import ptolemy.actor.gui.Placeable;
045import ptolemy.actor.lib.Source;
046import ptolemy.data.IntToken;
047import ptolemy.data.expr.Parameter;
048import ptolemy.data.type.BaseType;
049import ptolemy.kernel.CompositeEntity;
050import ptolemy.kernel.util.Attribute;
051import ptolemy.kernel.util.IllegalActionException;
052import ptolemy.kernel.util.NameDuplicationException;
053import ptolemy.kernel.util.StringAttribute;
054import ptolemy.kernel.util.Workspace;
055
056///////////////////////////////////////////////////////////////////
057//// SliderSource
058
059/**
060 The output of this actor is controlled by a slider in the run window.
061 The range of the output is specified by two parameters, <i>minimum</i> and
062 <i>maximum</i>. The type of these parameters and the output is integer.
063
064 @author Xiaojun Liu, Gang Zhou
065 @version $Id$
066 @since Ptolemy II 2.0
067 @Pt.ProposedRating Red (liuxj)
068 @Pt.AcceptedRating Red (liuxj)
069 */
070public class SliderSource extends Source implements ChangeListener, Placeable {
071    /** Construct an actor with an input multiport of type GENERAL.
072     *  @param container The container.
073     *  @param name The name of this actor.
074     *  @exception IllegalActionException If the actor cannot be contained
075     *   by the proposed container.
076     *  @exception NameDuplicationException If the container already has an
077     *   entity with this name.
078     */
079    public SliderSource(CompositeEntity container, String name)
080            throws IllegalActionException, NameDuplicationException {
081        super(container, name);
082
083        // Set the type of the output port.
084        output.setTypeEquals(BaseType.INT);
085
086        minimum = new Parameter(this, "minimum", new IntToken(-10));
087        minimum.setTypeEquals(BaseType.INT);
088        maximum = new Parameter(this, "maximum", new IntToken(10));
089        maximum.setTypeEquals(BaseType.INT);
090        majorTickSpacing = new Parameter(this, "majorTickSpacing",
091                new IntToken(10));
092        majorTickSpacing.setTypeEquals(BaseType.INT);
093        minorTickSpacing = new Parameter(this, "minorTickSpacing",
094                new IntToken(1));
095        minorTickSpacing.setTypeEquals(BaseType.INT);
096
097        title = new StringAttribute(this, "title");
098        title.setExpression("");
099    }
100
101    ///////////////////////////////////////////////////////////////////
102    ////        public variables and parameters                    ////
103
104    /** The slider that controls the output of this actor. */
105    public JSlider slider;
106
107    /** The minimum value of the slider. The value must be an integer.
108     *  The default value is -10.
109     */
110    public Parameter minimum;
111
112    /** The maximum value of the slider. The value must be an integer.
113     *  The default value is 10.
114     */
115    public Parameter maximum;
116
117    /** The major tick spacing of the slider. The value must be an integer.
118     *  The default value is 10.
119     */
120    public Parameter majorTickSpacing;
121
122    /** The minor tick spacing of the slider. The value must be an integer.
123     *  The default value is 1.
124     */
125    public Parameter minorTickSpacing;
126
127    /** The title to put on top. */
128    public StringAttribute title;
129
130    ///////////////////////////////////////////////////////////////////
131    ////                         public methods                    ////
132
133    /** If the specified attribute is <i>minimum</i> or <i>maximum</i>,
134     *  then set the range of the slider.
135     *  @param attribute The attribute that has changed.
136     *  @exception IllegalActionException If the specified range for the
137     *   slider is invalid.
138     */
139    @Override
140    public void attributeChanged(Attribute attribute)
141            throws IllegalActionException {
142        if (attribute == minimum || attribute == maximum
143                || attribute == majorTickSpacing
144                || attribute == minorTickSpacing) {
145            int min = ((IntToken) minimum.getToken()).intValue();
146            int max = ((IntToken) maximum.getToken()).intValue();
147            int major = ((IntToken) majorTickSpacing.getToken()).intValue();
148            int minor = ((IntToken) minorTickSpacing.getToken()).intValue();
149
150            if (min > max) {
151                throw new IllegalActionException(this, "The minimum value "
152                        + "of the slider cannot be larger than the maximum "
153                        + "value.");
154            }
155
156            if (slider != null) {
157                slider.setMaximum(max);
158                slider.setMinimum(min);
159                slider.setMajorTickSpacing(major);
160                slider.setMinorTickSpacing(minor);
161            }
162        } else {
163            super.attributeChanged(attribute);
164        }
165    }
166
167    /** Clone the actor into the specified workspace. This calls the
168     *  base class and then sets the slider public variable to null.
169     *  @param workspace The workspace for the new object.
170     *  @return A new actor.
171     *  @exception CloneNotSupportedException If a derived class contains
172     *   an attribute that cannot be cloned.
173     */
174    @Override
175    public Object clone(Workspace workspace) throws CloneNotSupportedException {
176        SliderSource newObject = (SliderSource) super.clone(workspace);
177        newObject.slider = null;
178        newObject._frame = null;
179        return newObject;
180    }
181
182    /** Output the value of the slider recorded when prefire() is last called.
183     */
184    @Override
185    public void fire() throws IllegalActionException {
186        super.fire();
187        output.send(0, _outputVal);
188    }
189
190    /** Return the background.
191     *  @return The background color.
192     *  @see #setBackground(Color)
193     */
194    public Color getBackground() {
195        return _panel.getBackground();
196    }
197
198    /** Create a slider on the screen, if necessary. If a graphical container
199     *  has not been specified, place the slider into its own frame.
200     *  Otherwise, place it in the specified container.
201     *  @exception IllegalActionException If the parent class throws it.
202     */
203    @Override
204    public void initialize() throws IllegalActionException {
205        super.initialize();
206
207        if (slider == null) {
208            int min = ((IntToken) minimum.getToken()).intValue();
209            int max = ((IntToken) maximum.getToken()).intValue();
210            int major = ((IntToken) majorTickSpacing.getToken()).intValue();
211            int minor = ((IntToken) minorTickSpacing.getToken()).intValue();
212            String titleSpec = title.getExpression();
213
214            // place the slider in its own frame.
215            // FIXME: This probably needs to be a PtolemyFrame, when one
216            // exists, so that the close button is dealt with, etc.
217            _frame = new SliderFrame(min, max, major, minor, titleSpec);
218            _panel = (JPanel) _frame.getContentPane().getComponent(0);
219            slider = (JSlider) _panel.getComponent(0);
220            slider.addChangeListener(this);
221        }
222
223        if (_frame != null) {
224            // Do not use show() as it overrides manual placement.
225            // FIXME: So does setVisible()... But with neither one used,
226            // then if the user dismisses the window, it does not reappear
227            // on re-running!
228            _frame.pack();
229            _frame.setVisible(true);
230            // _frame.toFront();
231        }
232    }
233
234    /** Specify the container in which the slider should be displayed.
235     *  An instance of JSlider will be added to that container.
236     *  This method needs to be called before the first call to initialize().
237     *  Otherwise, an instance of JSlider will be placed in its own frame.
238     *  The slider is also placed in its own frame if this method
239     *  is called with a null argument.
240     *  The background of the slider is set equal to that of the container
241     *  (unless it is null).
242     *  @param container The container into which to place the slider.
243     */
244    @Override
245    public void place(Container container) {
246
247        _container = container;
248
249        if (_container == null) {
250            if (_frame != null) {
251                _frame.dispose();
252            }
253
254            _frame = null;
255            _panel = null;
256            slider = null;
257            return;
258        }
259
260        int min = -10;
261        int max = 10;
262        int major = 10;
263        int minor = 1;
264        String titleSpec = title.getExpression();
265
266        try {
267            min = ((IntToken) minimum.getToken()).intValue();
268            max = ((IntToken) maximum.getToken()).intValue();
269            major = ((IntToken) majorTickSpacing.getToken()).intValue();
270            minor = ((IntToken) minorTickSpacing.getToken()).intValue();
271
272        } catch (IllegalActionException ex) {
273            // ignore
274        }
275
276        _panel = SliderFrame.createSliderPanel(min, max, major, minor,
277                titleSpec);
278        _container.add(_panel);
279
280        // java.awt.Component.setBackground(color) says that
281        // if the color "parameter is null then this component
282        // will inherit the  background color of its parent."
283        // plot.setBackground(_container.getBackground());
284        // _scrollPane.setBackground(_container.getBackground());
285        _panel.setBackground(null);
286        _panel.setBorder(new EmptyBorder(10, 10, 10, 10));
287        _panel.setBorder(new LineBorder(Color.black));
288        slider = (JSlider) _panel.getComponent(0);
289        slider.addChangeListener(this);
290    }
291
292    /** Record the current value of the slider. This value is output in the
293     *  subsequent firings of this actor.
294     */
295    @Override
296    public boolean prefire() throws IllegalActionException {
297        _outputVal = new IntToken(slider.getValue());
298        return super.prefire();
299    }
300
301    /** Set the background color of the panel that contains the slider.
302     *  @param background The background color.
303     *  @see #getBackground()
304     */
305    public void setBackground(Color background) {
306        _panel.setBackground(background);
307    }
308
309    /** Override the base class to remove the display from its graphical
310     *  container if the argument is null.
311     *  @param container The proposed container.
312     *  @exception IllegalActionException If the base class throws it.
313     *  @exception NameDuplicationException If the base class throws it.
314     */
315    @Override
316    public void setContainer(CompositeEntity container)
317            throws IllegalActionException, NameDuplicationException {
318        super.setContainer(container);
319
320        if (container == null) {
321            _remove();
322        }
323    }
324
325    /** The value of the slider changed, record the new value. */
326    @Override
327    public void stateChanged(ChangeEvent e) {
328        slider.getValue();
329    }
330
331    /** The frame for the slider. */
332    @SuppressWarnings("serial")
333    public static class SliderFrame extends JFrame {
334
335        /**  Create a frame for the slider.
336         * @param minimum the minimum value.
337         * @param maximum the maximum value.
338         * @param majorTickSpacing the space between major ticks.
339         * @param minorTickSpacing the space between minor ticks.
340         * @param title the title.
341         */
342        public SliderFrame(int minimum, int maximum, int majorTickSpacing,
343                int minorTickSpacing, String title) {
344
345            JPanel panel = createSliderPanel(minimum, maximum, majorTickSpacing,
346                    minorTickSpacing, title);
347            _slider = (JSlider) panel.getComponent(0);
348            getContentPane().add(panel);
349            pack();
350            setVisible(true);
351        }
352
353        /** Create a slider panel.
354         * @param minimum the minimum value.
355         * @param maximum the maximum value.
356         * @param majorTickSpacing the space between major ticks.
357         * @param minorTickSpacing the space between minor ticks.
358         * @param title the title.
359         * @return The slider panel.
360         */
361        public static JPanel createSliderPanel(int minimum, int maximum,
362                int majorTickSpacing, int minorTickSpacing, String title) {
363
364            JSlider slider = new JSlider(SwingConstants.HORIZONTAL, minimum,
365                    maximum, (maximum + minimum) / 2);
366            slider.setBackground(null);
367            slider.setMajorTickSpacing(majorTickSpacing);
368            slider.setMinorTickSpacing(minorTickSpacing);
369            slider.setPaintTicks(true);
370            slider.setPaintLabels(true);
371
372            //slider.addChangeListener(this);
373            JPanel panel = new JPanel();
374            panel.add(slider);
375            if (!title.trim().equals("")) {
376                panel.setBorder(BorderFactory.createTitledBorder(title));
377            }
378
379            return panel;
380        }
381
382        /** Get the value of the slider.
383         *  @return the slider value.
384         */
385        public int getValue() {
386            return _slider.getValue();
387        }
388
389        private JSlider _slider;
390    }
391
392    ///////////////////////////////////////////////////////////////////
393    ////                         private methods                   ////
394
395    /** Remove the display from the current container, if there is one.
396     */
397    private void _remove() {
398        SwingUtilities.invokeLater(new Runnable() {
399            @Override
400            public void run() {
401                if (slider != null) {
402                    if (_container != null) {
403                        _container.remove(_panel);
404                        _container.invalidate();
405                        _container.repaint();
406                    } else if (_frame != null) {
407                        _frame.dispose();
408                    }
409                }
410            }
411        });
412    }
413
414    ///////////////////////////////////////////////////////////////////
415    ////                         private members                   ////
416
417    /** The JPanel that contains the slider. */
418    private JPanel _panel;
419
420    private Container _container;
421
422    private IntToken _outputVal;
423
424    // The frame into which to put the text widget, if any.
425    private JFrame _frame;
426}