001package jmri.jmrit.sendpacket;
002
003import java.awt.GridLayout;
004import javax.swing.Box;
005import javax.swing.BoxLayout;
006import javax.swing.JCheckBox;
007import javax.swing.JLabel;
008import javax.swing.JPanel;
009import javax.swing.JSeparator;
010import javax.swing.JSpinner;
011import javax.swing.JTextField;
012import javax.swing.JToggleButton;
013import javax.swing.SpinnerNumberModel;
014import jmri.CommandStation;
015import jmri.InstanceManager;
016import jmri.util.StringUtil;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * User interface for sending DCC packets.
022 * <p>
023 * This was originally made from jmrix.loconet.logogen, but note that the logic
024 * is somewhat different here. The LocoNet version waited for the sent (LocoNet)
025 * packet to be echo'd, while this starts the timeout immediately.
026 *
027 * @author Bob Jacobsen Copyright (C) 2003
028 */
029public class SendPacketFrame extends jmri.util.JmriJFrame {
030
031    // member declarations
032    javax.swing.JLabel jLabel1 = new javax.swing.JLabel();
033    javax.swing.JButton sendButton = new javax.swing.JButton();
034    javax.swing.JTextField packetTextField = new javax.swing.JTextField(12);
035
036    public SendPacketFrame() {
037        super();
038    }
039
040    // internal members to hold sequence widgets
041    static final int MAXSEQUENCE = 4;
042    JTextField mPacketField[] = new JTextField[MAXSEQUENCE];
043    JCheckBox mUseField[] = new JCheckBox[MAXSEQUENCE];
044    JSpinner mDelaySpinner[] = new JSpinner[MAXSEQUENCE];
045    JToggleButton mRunButton = new JToggleButton(Bundle.getMessage("ButtonStart"));
046
047    @Override
048    public void initComponents() {
049
050        setTitle(Bundle.getMessage("SendPacketTitle"));
051        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
052
053        // handle single-packet part
054        getContentPane().add(new JLabel(Bundle.getMessage("SendOneLabel")));
055        {
056            JPanel pane1 = new JPanel();
057            pane1.setLayout(new BoxLayout(pane1, BoxLayout.Y_AXIS));
058
059            jLabel1.setText(Bundle.getMessage("PacketLabel"));
060            jLabel1.setVisible(true);
061
062            sendButton.setText(Bundle.getMessage("ButtonSend"));
063            sendButton.setVisible(true);
064            sendButton.setToolTipText(Bundle.getMessage("SendToolTip"));
065
066            packetTextField.setToolTipText(Bundle.getMessage("EnterHexBytesToolTip"));
067
068            pane1.add(jLabel1);
069            pane1.add(packetTextField);
070            pane1.add(sendButton);
071            pane1.add(Box.createVerticalGlue());
072
073            sendButton.addActionListener(new java.awt.event.ActionListener() {
074                @Override
075                public void actionPerformed(java.awt.event.ActionEvent e) {
076                    sendButtonActionPerformed(e);
077                }
078            });
079
080            getContentPane().add(pane1);
081        }
082
083        getContentPane().add(new JSeparator());
084
085        // Configure the sequence
086        getContentPane().add(new JLabel(Bundle.getMessage("SendSequenceLabel")));
087        JPanel pane2 = new JPanel();
088        pane2.setLayout(new GridLayout(MAXSEQUENCE + 2, 4));
089        pane2.add(new JLabel(""));
090        pane2.add(new JLabel(Bundle.getMessage("ButtonSend")));
091        pane2.add(new JLabel(Bundle.getMessage("Packet")));
092        pane2.add(new JLabel(Bundle.getMessage("WaitMsec")));
093        for (int i = 0; i < MAXSEQUENCE; i++) {
094            pane2.add(new JLabel(Integer.toString(i + 1)));
095            mUseField[i] = new JCheckBox();
096            mPacketField[i] = new JTextField(10);
097            mDelaySpinner[i] = new JSpinner(new SpinnerNumberModel(100, 0, 1000, 1));
098            pane2.add(mUseField[i]);
099            pane2.add(mPacketField[i]);
100            pane2.add(mDelaySpinner[i]);
101        }
102        pane2.add(mRunButton); // starts a new row in layout
103        getContentPane().add(pane2);
104
105        mRunButton.addActionListener(new java.awt.event.ActionListener() {
106            @Override
107            public void actionPerformed(java.awt.event.ActionEvent e) {
108                runButtonActionPerformed(e);
109            }
110        });
111
112        // get the CommandStation reference
113        cs = InstanceManager.getNullableDefault(CommandStation.class);
114        if (cs == null) {
115            log.error("No CommandStation object available");
116        }
117
118        // add help menu
119        addHelpMenu("package.jmri.jmrit.sendpacket.SendPacketFrame", true);
120
121        // pack to cause display
122        pack();
123    }
124
125    public void sendButtonActionPerformed(java.awt.event.ActionEvent e) {
126        cs.sendPacket(createPacket(packetTextField.getText()), 1);
127    }
128
129    // control sequence operation
130    int mNextSequenceElement = 0;
131    byte[] mNextEcho = null;
132    javax.swing.Timer timer = null;
133
134    /**
135     * Internal routine to handle timer starts and restarts.
136     * @param delay Milliseconds to wait
137     */
138    protected void restartTimer(int delay) {
139        if (timer == null) {
140            timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() {
141                @Override
142                public void actionPerformed(java.awt.event.ActionEvent e) {
143                    sendNextItem();
144                }
145            });
146        }
147        timer.stop();
148        timer.setInitialDelay(delay);
149        timer.setRepeats(false);
150        timer.start();
151    }
152
153    /**
154     * Run button pressed down, start the sequence operation.
155     * @param e unused.
156     */
157    public void runButtonActionPerformed(java.awt.event.ActionEvent e) {
158        if (!mRunButton.isSelected()) {
159            return;
160        }
161        // make sure at least one is checked
162        boolean ok = false;
163        for (int i = 0; i < MAXSEQUENCE; i++) {
164            if (mUseField[i].isSelected()) {
165                ok = true;
166            }
167        }
168        if (!ok) {
169            mRunButton.setSelected(false);
170            return;
171        }
172        // start the operation
173        mNextSequenceElement = 0;
174        sendNextItem();
175    }
176
177    /**
178     * Echo has been heard, start delay for next packet.
179     */
180    void startSequenceDelay() {
181        // at the start, mNextSequenceElement contains index we're
182        // working on
183        int delay = 500;   // default delay if non specified, or format bad
184        try {
185            delay = (Integer) mDelaySpinner[mNextSequenceElement].getValue();
186        } catch (NumberFormatException e) {
187        }
188
189        // increment to next line at completion
190        mNextSequenceElement++;
191        // start timer
192        restartTimer(delay);
193    }
194
195    /**
196     * Send next item; may be used for the first item or when a delay has
197     * elapsed.
198     */
199    void sendNextItem() {
200        // check if still running
201        if (!mRunButton.isSelected()) {
202            return;
203        }
204        // have we run off the end?
205        if (mNextSequenceElement >= MAXSEQUENCE) {
206            // past the end, go back
207            mNextSequenceElement = 0;
208        }
209        // is this one enabled?
210        if (mUseField[mNextSequenceElement].isSelected()) {
211            // make the packet
212            byte[] m = createPacket(mPacketField[mNextSequenceElement].getText());
213            // send it
214            mNextEcho = m;
215            if (m != null) {
216                cs.sendPacket(m, 1);
217            } else {
218                log.warn("Message invalid: {}", mPacketField[mNextSequenceElement].getText());
219            }
220            // and queue the rest of the sequence if we're continuing
221            if (mRunButton.isSelected()) {
222                startSequenceDelay();
223            }
224        } else {
225            // ask for the next one
226            mNextSequenceElement++;
227            sendNextItem();
228        }
229    }
230
231    /**
232     * Create a well-formed DCC packet from a String.
233     * @param s Content for packet
234     * @return the packet, with contents filled-in
235     */
236    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS",
237        justification = "API defined by CommmandStation interface")
238    byte[] createPacket(String s) {
239        // gather bytes in result
240        byte b[] = StringUtil.bytesFromHexString(s);
241        if (b.length == 0) {
242            return null;  // no such thing as a zero-length message
243        }
244        return b;
245    }
246
247    /**
248     * When the window closes, stop any sequences running.
249     */
250    @Override
251    public void dispose() {
252        mRunButton.setSelected(false);
253        super.dispose();
254    }
255
256    // private data
257    private CommandStation cs = null;
258
259    private final static Logger log = LoggerFactory.getLogger(SendPacketFrame.class);
260
261}