001package jmri.jmrit.vsdecoder.swing;
002
003import java.awt.Dimension;
004import java.awt.GraphicsEnvironment;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.beans.PropertyChangeEvent;
008
009import javax.swing.AbstractButton;
010import javax.swing.ButtonModel;
011import javax.swing.BorderFactory;
012import javax.swing.JSlider;
013import javax.swing.JSpinner;
014import javax.swing.JToggleButton;
015import javax.swing.SpinnerNumberModel;
016import javax.swing.event.ChangeEvent;
017import javax.swing.event.ChangeListener;
018import javax.swing.Timer;
019
020import jmri.jmrit.vsdecoder.EnginePane;
021import jmri.util.swing.JmriJOptionPane;
022
023/**
024 * Sound control buttons for the new GUI.
025 *
026 * <hr>
027 * This file is part of JMRI.
028 * <p>
029 * JMRI is free software; you can redistribute it and/or modify it under
030 * the terms of version 2 of the GNU General Public License as published
031 * by the Free Software Foundation. See the "COPYING" file for a copy
032 * of this license.
033 * <p>
034 * JMRI is distributed in the hope that it will be useful, but WITHOUT
035 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
036 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
037 * for more details.
038 *
039 * @author Mark Underwood Copyright (C) 2011
040 * @author Klaus Killinger Copyright (C) 2018-2021
041 */
042public class DieselPane extends EnginePane {
043
044    static final int THROTTLE_MIN = 1;
045    static final int THROTTLE_MAX = 18;
046    static final int THROTTLE_INIT = 1;
047
048    public static final String THROTTLE = "VSDDP:Throttle"; // NOI18N
049    public static final String START = "VSDDP:Start"; // NOI18N
050    public static final String VOLUME = "VSDDP:Volume"; // NOI18N
051
052    JSpinner throttle_spinner;
053    public JSlider volume_slider;
054    JToggleButton start_button;
055
056    int throttle_setting;
057    private boolean engine_is_started;
058
059    private Timer timer;
060    int dtime = 1;
061    float lastSpeed = 0.0f;
062
063    /**
064     * Constructor
065     *
066     * @param n pane title
067     */
068    public DieselPane(String n) {
069        super(n);
070        initComponents();
071        throttle_setting = THROTTLE_INIT;
072        engine_is_started = start_button.isSelected();
073    }
074
075    /**
076     * Null constructor
077     */
078    public DieselPane() {
079        this(null);
080    }
081
082    /**
083     * Init Context.
084     * @param context unused.
085     */
086    @Override
087    public void initContext(Object context) {
088        initComponents();
089    }
090
091    protected Timer newTimer(int time, boolean repeat, ActionListener al) {
092        timer = new Timer(time, al);
093        timer.setRepeats(repeat);
094        return timer;
095    }
096
097    // Lock the start/stop-button until the start/stop-sound has finished
098    // Skip the timer if there is no start/stop-sound
099    void startDelayTimer() {
100        if (dtime > 1) {
101            start_button.setEnabled(false);
102            timer = newTimer(dtime, false, new ActionListener() {
103                @Override
104                public void actionPerformed(ActionEvent e) {
105                    start_button.setEnabled(true);
106                }
107            });
108            timer.start();
109        }
110    }
111
112    @Override
113    public void setButtonDelay(long t) {
114        // Timer only takes an int ... cap the length at MAXINT
115        // Note: this only works for positive lengths ...
116        if (t > Integer.MAX_VALUE) {
117            t = Integer.MAX_VALUE; // small enough to safely cast
118        }
119        dtime = (int) t; // time in ms
120    }
121
122    /**
123     * Build the GUI components
124     */
125    @Override
126    public void initComponents() {
127        //Set up the throttle spinner
128        throttle_spinner = new JSpinner(new SpinnerNumberModel(THROTTLE_INIT, THROTTLE_MIN, THROTTLE_MAX, 1));
129        throttle_spinner.setPreferredSize(new Dimension(40, 30));
130        throttle_spinner.setToolTipText(Bundle.getMessage("ToolTipDP_ThrottleSpinner"));
131        throttle_spinner.setEnabled(false);
132
133        this.add(throttle_spinner);
134
135        // Setup the start button
136        start_button = new JToggleButton();
137        start_button.setText(Bundle.getMessage("ButtonEngineStart"));
138        start_button.setPreferredSize(new Dimension(150, 30));
139        start_button.setToolTipText(Bundle.getMessage("ToolTipDP_StartButton"));
140        start_button.addActionListener(new ActionListener() {
141            @Override
142            public void actionPerformed(ActionEvent e) {
143                startButtonChange(e);
144            }
145        });
146
147        start_button.addChangeListener(new ChangeListener() {
148            @Override
149            public void stateChanged(ChangeEvent ev) {
150                startButtonStateChange(ev);
151            }
152        });
153
154        this.add(start_button);
155
156        //Set up the volume slider
157        volume_slider = new JSlider(0, 100);
158        volume_slider.setMinorTickSpacing(10);
159        volume_slider.setPaintTicks(true);
160        volume_slider.setPreferredSize(new Dimension(160, 30));
161        volume_slider.setToolTipText(Bundle.getMessage("DecoderVolumeToolTip"));
162        volume_slider.addChangeListener(new ChangeListener() {
163            @Override
164            public void stateChanged(ChangeEvent e) {
165                volumeChange(e); // slider in real time
166            }
167        });
168        this.add(volume_slider);
169
170        this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
171        this.setVisible(true);
172    }
173
174    // Respond to a start button press with stateChanged
175    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
176        justification="I18N of Headless Info Message")
177    public void startButtonStateChange(ChangeEvent ev) {
178        AbstractButton abstractButton = (AbstractButton) ev.getSource();
179        ButtonModel buttonModel = abstractButton.getModel();
180        boolean armed = buttonModel.isArmed();
181        boolean pressed = buttonModel.isPressed();
182        boolean selected = buttonModel.isSelected();
183        if (armed && pressed && selected && (lastSpeed > 0.0f) && (!engine_is_started)) {
184            buttonModel.setArmed(false);
185            buttonModel.setPressed(false);
186            buttonModel.setSelected(false);
187            if (GraphicsEnvironment.isHeadless()) {
188                log.info(Bundle.getMessage("EngineStartSpeedMessage"));
189            } else {
190                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("EngineStartSpeedMessage"));
191            }
192        }
193
194        if (armed && pressed && selected && (lastSpeed > 0.0f) && (engine_is_started) && (getStopOption())) {
195            buttonModel.setArmed(false);
196            buttonModel.setPressed(false);
197            if (GraphicsEnvironment.isHeadless()) {
198                log.info(Bundle.getMessage("EngineStopSpeedMessage"));
199            } else {
200                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("EngineStopSpeedMessage"));
201            }
202        }
203    }
204
205    /**
206     * Respond to a throttle change.
207     * Basically, doesn't do anything.
208     * @param e unused.
209     */
210    public void throttleChange(ChangeEvent e) {
211        firePropertyChangeEvent(new PropertyChangeEvent(this, THROTTLE, throttle_setting, throttle_spinner.getModel().getValue()));
212        throttle_setting = throttleNotch();
213    }
214
215    /**
216     * Respond to a start button press.
217     * @param e unused.
218     */
219    public void startButtonChange(ActionEvent e) {
220        firePropertyChangeEvent(new PropertyChangeEvent(this, START, engine_is_started, start_button.isSelected()));
221        engine_is_started = start_button.isSelected();
222        if (engine_is_started) { // switch button name to make the panel more responsive
223            start_button.setText(Bundle.getMessage("ButtonEngineStop"));
224        } else {
225            start_button.setText(Bundle.getMessage("ButtonEngineStart"));
226        }
227        startDelayTimer();
228    }
229
230    protected void volumeChange(ChangeEvent e) {
231        JSlider v = (JSlider) e.getSource();
232        log.debug("Decoder Volume slider set to value: {}", v.getValue());
233        firePropertyChangeEvent(new PropertyChangeEvent(this, VOLUME, v.getValue(), null));
234    }
235
236    @Override
237    public void startButtonClick() {
238        start_button.doClick(); // Animate button and process ChangeEvent
239    }
240
241    /**
242     * Get if Engine is On.
243     * @return true if the start button is "on".
244     */
245    public boolean engineIsOn() {
246        return start_button.isSelected();
247    }
248
249    /**
250     * Get Throttle notch.
251     * @return current notch setting of the throttle slider.
252     */
253    public int throttleNotch() {
254        return (Integer) throttle_spinner.getModel().getValue();
255    }
256
257    /**
258     * Set the Throttle spinner value.
259     * @param t new value.
260     */
261    @Override
262    public void setThrottle(int t) {
263        throttle_spinner.setValue(t);
264    }
265
266    @Override
267    public void setSpeed(float s) {
268        lastSpeed = s;
269    }
270
271    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DieselPane.class);
272
273}