001package jmri.jmrix.loconet.locogen;
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.JTextField;
011import javax.swing.JToggleButton;
012import jmri.jmrix.loconet.LocoNetListener;
013import jmri.jmrix.loconet.LocoNetMessage;
014import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
015import jmri.util.StringUtil;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * User interface for sending LocoNet messages to exercise the system.
021 * <p>
022 * When sending a sequence of operations:
023 * <ul>
024 *   <li>Send the next message
025 *   <li>Wait until you hear the echo, then start a timer
026 *   <li>When the timer trips, repeat if buttons still down.
027 * </ul>
028 * @see jmri.jmrix.can.swing.send.CanSendPane
029 *
030 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2010
031 */
032public class LocoGenPanel extends jmri.jmrix.loconet.swing.LnPanel
033        implements LocoNetListener {
034
035    // member declarations
036    javax.swing.JLabel jLabel1 = new javax.swing.JLabel();
037    javax.swing.JButton sendButton = new javax.swing.JButton();
038    javax.swing.JTextField packetTextField = new javax.swing.JTextField(12);
039
040    public LocoGenPanel() {
041        super();
042    }
043
044    // internal members to hold sequence widgets
045    static final int MAXSEQUENCE = 4;
046    JTextField mPacketField[] = new JTextField[MAXSEQUENCE];
047    JCheckBox mUseField[] = new JCheckBox[MAXSEQUENCE];
048    JTextField mDelayField[] = new JTextField[MAXSEQUENCE];
049    JToggleButton mRunButton = new JToggleButton(Bundle.getMessage("ButtonRun"));
050
051    /**
052     * {@inheritDoc}
053     */
054    @Override
055    public String getHelpTarget() {
056        return "package.jmri.jmrix.loconet.locogen.LocoGenFrame"; // NOI18N
057    }
058
059    /**
060     * {@inheritDoc}
061     */
062    @Override
063    public String getTitle() {
064        return getTitle(Bundle.getMessage("MenuItemSendPacket"));
065    }
066
067    /**
068     * {@inheritDoc}
069     */
070    @Override
071    public void initComponents() {
072
073        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
074
075        // handle single-packet part
076        add(new JLabel(Bundle.getMessage("LabelSendOne")));
077        {
078            JPanel pane1 = new JPanel();
079            pane1.setLayout(new BoxLayout(pane1, BoxLayout.Y_AXIS));
080
081            jLabel1.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("PacketLabel")));
082            jLabel1.setVisible(true);
083
084            sendButton.setText(Bundle.getMessage("ButtonSend"));
085            sendButton.setVisible(true);
086            sendButton.setToolTipText(Bundle.getMessage("TooltipSendPacket"));
087
088            packetTextField.setToolTipText(Bundle.getMessage("EnterHexToolTip"));
089
090            pane1.add(jLabel1);
091            pane1.add(packetTextField);
092            pane1.add(sendButton);
093            pane1.add(Box.createVerticalGlue());
094
095            sendButton.addActionListener(this::sendButtonActionPerformed);
096
097            add(pane1);
098        }
099
100        add(new JSeparator());
101
102        // Configure the sequence
103        add(new JLabel(Bundle.getMessage("SendSeqTitle")));
104        JPanel pane2 = new JPanel();
105        pane2.setLayout(new GridLayout(MAXSEQUENCE + 2, 4));
106        pane2.add(new JLabel(""));
107        pane2.add(new JLabel(Bundle.getMessage("ButtonSend")));
108        pane2.add(new JLabel(Bundle.getMessage("PacketLabel")));
109        pane2.add(new JLabel(Bundle.getMessage("WaitLabel")));
110        for (int i = 0; i < MAXSEQUENCE; i++) {
111            pane2.add(new JLabel(Integer.toString(i + 1)));
112            mUseField[i] = new JCheckBox();
113            mPacketField[i] = new JTextField(10);
114            mDelayField[i] = new JTextField(10);
115            pane2.add(mUseField[i]);
116            pane2.add(mPacketField[i]);
117            pane2.add(mDelayField[i]);
118        }
119        pane2.add(mRunButton); // starts a new row in layout
120        add(pane2);
121
122        mRunButton.addActionListener(this::runButtonActionPerformed);
123    }
124
125    /**
126     * {@inheritDoc}
127     */
128    @Override
129    public void initComponents(LocoNetSystemConnectionMemo memo) {
130        super.initComponents(memo);
131
132        memo.getLnTrafficController().addLocoNetListener(~0, this);
133    }
134
135    public void sendButtonActionPerformed(java.awt.event.ActionEvent e) {
136        String input = packetTextField.getText();
137        // TODO check input + feedback on error. Too easy to cause NPE
138        memo.getLnTrafficController().sendLocoNetMessage(createPacket(input));
139    }
140
141    // control sequence operation
142    int mNextSequenceElement = 0;
143    LocoNetMessage mNextEcho = null;
144    javax.swing.Timer timer = null;
145
146    /**
147     * Internal routine to handle timer starts and restarts
148     *
149     * @param delay in mSec
150     */
151    protected void restartTimer(int delay) {
152        if (timer == null) {
153            timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() {
154                @Override
155                public void actionPerformed(java.awt.event.ActionEvent e) {
156                    sendNextItem();
157                }
158            });
159        }
160        timer.stop();
161        timer.setInitialDelay(delay);
162        timer.setRepeats(false);
163        timer.start();
164    }
165
166    /**
167     * Run button pressed down, start the sequence operation.
168     *
169     * @param e  a {@link java.awt.event.ActionEvent} to be triggered
170     */
171    public void runButtonActionPerformed(java.awt.event.ActionEvent e) {
172        if (!mRunButton.isSelected()) {
173            return;
174        }
175        // make sure at least one is checked
176        boolean ok = false;
177        for (int i = 0; i < MAXSEQUENCE; i++) {
178            if (mUseField[i].isSelected()) {
179                ok = true;
180            }
181        }
182        if (!ok) {
183            mRunButton.setSelected(false);
184            return;
185        }
186        // start the operation
187        mNextSequenceElement = 0;
188        sendNextItem();
189    }
190
191    /**
192     * {@inheritDoc}
193     */
194    @Override
195    public void message(LocoNetMessage m) {
196        log.debug("message"); // NOI18N
197        // are we running?
198        if (!mRunButton.isSelected()) {
199            return;
200        }
201        // yes, is this what we're looking for
202        if (!(mNextEcho.equals(m))) {
203            return;
204        }
205        // yes, we got it, do the next
206        startSequenceDelay();
207    }
208
209    /**
210     * Echo has been heard, start delay for next packet
211     */
212    void startSequenceDelay() {
213        log.debug("startSequenceDelay"); // NOI18N
214        // at the start, mNextSequenceElement contains index we're
215        // working on
216        int delay = Integer.parseInt(mDelayField[mNextSequenceElement].getText());
217        // increment to next line at completion
218        mNextSequenceElement++;
219        // start timer
220        restartTimer(delay);
221    }
222
223    /**
224     * Send next item; may be used for the first item or when a delay has
225     * elapsed.
226     */
227    void sendNextItem() {
228        log.debug("sendNextItem"); // NOI18N
229        // check if still running
230        if (!mRunButton.isSelected()) {
231            return;
232        }
233        // have we run off the end?
234        if (mNextSequenceElement >= MAXSEQUENCE) {
235            // past the end, go back
236            mNextSequenceElement = 0;
237        }
238        // is this one enabled?
239        if (mUseField[mNextSequenceElement].isSelected()) {
240            // make the packet
241            LocoNetMessage m = createPacket(mPacketField[mNextSequenceElement].getText());
242            // send it
243            mNextEcho = m;
244            memo.getLnTrafficController().sendLocoNetMessage(m);
245        } else {
246            // ask for the next one
247            mNextSequenceElement++;
248            sendNextItem();
249        }
250    }
251
252    /**
253     * Create a well-formed LocoNet packet from a String.
254     * <p>
255     * Well-formed generally means a space-separated string of hex values of
256     * two characters each, as defined in
257     * {@link jmri.util.StringUtil#bytesFromHexString(String s)} .
258     *
259     * @param s  a string containing raw hex data of good form
260     * @return The packet, with contents filled-in
261     */
262    LocoNetMessage createPacket(String s) {
263        // gather bytes in result
264        byte b[] = StringUtil.bytesFromHexString(s);
265        if (b.length == 0) {
266            return null;  // no such thing as a zero-length message
267        }
268        LocoNetMessage m = new LocoNetMessage(b.length);
269        for (int i = 0; i < b.length; i++) {
270            m.setElement(i, b[i]);
271        }
272        return m;
273    }
274
275    /**
276     * When the window closes, stop any sequences running
277     */
278    @Override
279    public void dispose() {
280        mRunButton.setSelected(false);
281        super.dispose();
282    }
283
284    // initialize logging
285    private final static Logger log = LoggerFactory.getLogger(LocoGenPanel.class);
286
287}