001package jmri.jmrix.loconet.bdl16;
002
003import java.util.Collections;
004import java.awt.GridBagConstraints;
005import java.awt.GridBagLayout;
006import javax.swing.BoxLayout;
007import javax.swing.JComboBox;
008import javax.swing.JLabel;
009import javax.swing.JPanel;
010import javax.swing.BorderFactory;
011import javax.swing.SwingConstants;
012import javax.swing.border.Border;
013import javax.swing.border.TitledBorder;
014import jmri.jmrix.loconet.AbstractBoardProgPanel;
015import jmri.jmrix.loconet.LnConstants;
016import jmri.jmrix.loconet.LocoNetMessage;
017import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Panel displaying and programming a BDL16x configuration.
023 * <p>
024 * The read and write require a sequence of operations, which we handle with a
025 * state variable.
026 * <p>
027 * Programming of the BDL16x is done via configuration messages, so the BDL16x
028 * should not be put into programming mode via the built-in pushbutton while
029 * this tool is in use.
030 * <p>
031 * Some of the message formats used in this class are Copyright Digitrax, Inc.
032 * and used with permission as part of the JMRI project. That permission does
033 * not extend to uses in other software products. If you wish to use this code,
034 * algorithm or these message formats outside of JMRI, please contact Digitrax
035 * Inc for separate permission.
036 *
037 * @author Bob Jacobsen Copyright (C) 2002, 2004, 2007, 2010
038 */
039public class BDL16Panel extends AbstractBoardProgPanel {
040
041    /**
042     * BDL16x Configuration Tool.
043     * <p>
044     * Use this constructor when the Unit Address is unknown.
045     */
046    public BDL16Panel() {
047        this(1, false);
048    }
049
050    JComboBox<Integer> addressComboBox;
051    int[] boardNumbers;
052    int origAccessBoardNum = 0;
053    java.util.ArrayList<Integer> boardNumsEntryValue = new java.util.ArrayList<Integer>();
054
055    @SuppressWarnings("unchecked") // type erasure means can't ask for new JComboBox<String>[48]
056    JComboBox<String> comboBox[] = new JComboBox[48];
057
058    /**
059     * BDL16x Programming tool.
060     * <p>
061     * Use this constructor when the Unit Address is known.
062     *
063     * @param boardNum    integer for the initial Unit Address
064     * @param readOnInit  True to trigger automatic read of the board
065     */
066    public BDL16Panel(int boardNum, boolean readOnInit) {
067        super(boardNum, readOnInit,"BDL16");
068        setTypeWord(0x71);  // configure BDL16x message type
069        origAccessBoardNum = boardNum;
070        boardNumsEntryValue.add(boardNum);
071    }
072
073    /**
074     * Get the URL for the HTML help for this tool.
075     *
076     * @return URL
077     */
078    @Override
079    public String getHelpTarget() {
080        return "package.jmri.jmrix.loconet.bdl16.BDL16Frame"; // NOI18N
081    }
082
083    /**
084     * Get the name of the tool for use in the title of the window.
085     *
086     * @return String containing text for the title of the window
087     */
088    @Override
089    public String getTitle() {
090        return getTitle(Bundle.getMessage("MenuItemBDL16Programmer"));
091    }
092
093    /**
094     * Copy from the GUI to the OpSw array.
095     * <p>
096     * Used before write operations start.
097     */
098    @Override
099    protected void copyToOpsw() {
100        // copy over the display
101        opsw[1] = comboBox[1].getSelectedIndex()==1;
102        opsw[3] = comboBox[3].getSelectedIndex()==1;
103        opsw[5] = comboBox[5].getSelectedIndex()==1;
104        opsw[6] = comboBox[6].getSelectedIndex()==1;
105        opsw[7] = comboBox[7].getSelectedIndex()==1;
106        opsw[9] = comboBox[9].getSelectedIndex()==1;
107        opsw[10] = comboBox[10].getSelectedIndex()==1;
108        opsw[11] = comboBox[11].getSelectedIndex()==1;
109        opsw[12] = comboBox[12].getSelectedIndex()==1;
110        opsw[13] = comboBox[13].getSelectedIndex()==1;
111        opsw[19] = comboBox[19].getSelectedIndex()==1;
112        opsw[25] = comboBox[25].getSelectedIndex()==1;
113        opsw[26] = comboBox[26].getSelectedIndex()==1;
114        opsw[36] = comboBox[36].getSelectedIndex()==1;
115        opsw[39] = comboBox[39].getSelectedIndex()==1;
116        opsw[42] = comboBox[42].getSelectedIndex()==1;
117
118        int index = comboBox[37].getSelectedIndex();
119        opsw[37] = ((index==1) || (index==3))?true:false;
120        opsw[38] = (index >=2)?true:false;
121
122        index = comboBox[43].getSelectedIndex();
123        opsw[43] = ((index==1) || (index==3))?true:false;
124        opsw[44] = (index >=2)?true:false;
125        opsw[40] = comboBox[40].getSelectedIndex()==1;
126    }
127
128    /**
129     * Update the GUI elements.
130     */
131    @Override
132    protected void updateDisplay() {
133        comboBox[1].setSelectedIndex(opsw[1]?1:0);
134        comboBox[3].setSelectedIndex(opsw[3]?1:0);
135        comboBox[5].setSelectedIndex(opsw[5]?1:0);
136        comboBox[6].setSelectedIndex(opsw[6]?1:0);
137        comboBox[7].setSelectedIndex(opsw[7]?1:0);
138        comboBox[9].setSelectedIndex(opsw[9]?1:0);
139        comboBox[10].setSelectedIndex(opsw[10]?1:0);
140        comboBox[11].setSelectedIndex(opsw[11]?1:0);
141        comboBox[12].setSelectedIndex(opsw[12]?1:0);
142        comboBox[13].setSelectedIndex(opsw[13]?1:0);
143        comboBox[19].setSelectedIndex(opsw[19]?1:0);
144        comboBox[25].setSelectedIndex(opsw[25]?1:0);
145        comboBox[26].setSelectedIndex(opsw[26]?1:0);
146        comboBox[36].setSelectedIndex(opsw[36]?1:0);
147        comboBox[39].setSelectedIndex(opsw[39]?1:0);
148        comboBox[42].setSelectedIndex(opsw[42]?1:0);
149        comboBox[40].setSelectedIndex(opsw[40]?1:0);
150
151        int temp = opsw[37]?1:0;
152        temp += opsw[38]?2:0;
153        comboBox[37].setSelectedIndex(temp);
154
155        temp = opsw[43]?1:0;
156        temp += opsw[44]?2:0;
157        comboBox[43].setSelectedIndex(temp);
158    }
159
160    /**
161     * Determine the next OpSw to be accessed.
162     *
163     * @param state  most-recently accessed OpSw
164     * @return       next OpSw to be accessed
165     */
166    @Override
167    protected int nextState(int state) {
168        switch (state) {
169            case 1:
170                return 3;
171            case 3:
172                return 5;
173            case 5:
174                return 6;
175            case 6:
176                return 7;
177            case 7:
178                return 9;
179            case 9:
180                return 10;
181            case 10:
182                return 11;
183            case 11:
184                return 12;
185            case 12:
186                return 13;
187            case 13:
188                return 19;
189            case 19:
190                return 25;
191            case 25:
192                return 26;
193            case 26:
194                return 36;
195            case 36:
196                return 37;
197            case 37:
198                return 38;
199            case 38:
200                return 39;
201            case 39:
202                return 42;
203            case 42:
204                return 43;
205            case 43:
206                return 44;
207            case 44:
208                return 40;    // have to do 40 last
209            case 40:
210                return 0;    // done!
211            default:
212                log.error("unexpected state {}", state); // NOI18N
213                return 0;
214        }
215    }
216
217    /**
218     * Initialize LocoNet connection for use by the tool.
219     *
220     * @param memo  the LocoNet Connection
221     */
222    @Override
223    public void initComponents(LocoNetSystemConnectionMemo memo) {
224        super.initComponents(memo);
225        LocoNetMessage m = new LocoNetMessage(6);
226        m.setElement(0, LnConstants.OPC_MULTI_SENSE);
227        m.setElement(1, 0x62);
228        m.setElement(2, 0);
229        m.setElement(3, 0x70);
230        m.setElement(4, 0);
231        memo.getLnTrafficController().sendLocoNetMessage(m);
232    }
233
234    private GridBagConstraints gc = new GridBagConstraints();
235    private GridBagLayout gl = new GridBagLayout();
236
237    /**
238     * Initialize the GUI elements for use by the tool.
239     */
240    @Override
241    public void initComponents() {
242        JPanel addressingPanel = provideAddressing(" "); // create read/write buttons, address
243
244
245        int indexOfTargetBoardAddress = 0;
246
247        addressComboBox = new JComboBox<>();
248        for (Integer index = 0; index < boardNumsEntryValue.size(); ++index) {
249            if (boardNumsEntryValue.get(index) == origAccessBoardNum) {
250                origAccessBoardNum = -1;
251                indexOfTargetBoardAddress = index;
252            }
253            addressComboBox.addItem(boardNumsEntryValue.get(index));
254        }
255
256        addressComboBox.setSelectedIndex(indexOfTargetBoardAddress);
257        addressingPanel.add(addressComboBox, 2);
258        addressingPanel.getComponent(1).setVisible(false);
259        addressComboBox.setEditable(true);
260
261        addressingPanel.add(new JLabel(Bundle.getMessage("LabelBoardID")),1);
262        addressingPanel.getComponent(0).setVisible(false);
263
264        readAllButton.setPreferredSize(null);
265        readAllButton.setText(Bundle.getMessage("ButtonReadFullSheet"));
266        readAllButton.setToolTipText(Bundle.getMessage("ToolTipButtonReadFullSheet"));
267
268        writeAllButton.setPreferredSize(null);
269        writeAllButton.setText(Bundle.getMessage("ButtonWriteFullSheet"));
270        writeAllButton.setToolTipText(Bundle.getMessage("ToolTipButtonWriteFullSheet"));
271
272        // make both buttons a little bit bigger, with identical (preferred) sizes
273        // (width increased because some computers/displays trim the button text)
274        java.awt.Dimension d = writeAllButton.getPreferredSize();
275        int w = d.width;
276        d = readAllButton.getPreferredSize();
277        if (d.width > w) {
278            w = d.width;
279        }
280        writeAllButton.setPreferredSize(new java.awt.Dimension((int) (w * 1.1), d.height));
281        readAllButton.setPreferredSize(new java.awt.Dimension((int) (w * 1.1), d.height));
282
283        appendLine(addressingPanel);  // add read/write buttons, address
284
285        JPanel frame1 = new JPanel();
286        frame1.setLayout(new BoxLayout(frame1, BoxLayout.PAGE_AXIS));
287
288        TitledBorder allBoardsTitleBorder;
289        Border blackline;
290        blackline = BorderFactory.createLineBorder(java.awt.Color.black);
291        allBoardsTitleBorder = BorderFactory.createTitledBorder(blackline,
292                Bundle.getMessage("TitledBorderLabelAllBoards"));
293        frame1.setBorder(allBoardsTitleBorder);
294
295        JPanel allBoardsOptions = new JPanel();
296        allBoardsOptions.setLayout(gl);
297        // configure GridBagConstraints
298        gc.ipadx = 15;
299        gc.ipady = 20;
300        gc.gridy = 0;
301
302        allBoardsOptions.add(getLabel(1));
303        allBoardsOptions.add(getComboBox(1));
304        gc.gridy++;
305
306        allBoardsOptions.add(getLabel(9));
307        allBoardsOptions.add(getComboBox(9));
308        gc.gridy++;
309
310        allBoardsOptions.add(getLabel(10));
311        allBoardsOptions.add(getComboBox(10));
312        gc.gridy++;
313
314        allBoardsOptions.add(getLabel(11));
315        allBoardsOptions.add(getComboBox(11));
316        gc.gridy++;
317
318        allBoardsOptions.add(getLabel(12));
319        allBoardsOptions.add(getComboBox(12));
320        gc.gridy++;
321
322        allBoardsOptions.add(getLabel(13));
323        allBoardsOptions.add(getComboBox(13));
324        gc.gridy++;
325
326        allBoardsOptions.add(getLabel(19));
327        allBoardsOptions.add(getComboBox(19));
328        gc.gridy++;
329
330        allBoardsOptions.add(getLabel(25));
331        allBoardsOptions.add(getComboBox(25));
332        gc.gridy++;
333
334        allBoardsOptions.add(getLabel(26));
335        allBoardsOptions.add(getComboBox(26));
336        gc.gridy++;
337
338        allBoardsOptions.add(getLabel(40));
339        allBoardsOptions.add(getComboBox(40));
340
341        frame1.add(allBoardsOptions);
342
343        JPanel frame2 = new JPanel();
344        frame2.setLayout(new BoxLayout(frame2, BoxLayout.PAGE_AXIS));
345        TitledBorder bdl162Bdl168BoardsTitleBorder;
346        bdl162Bdl168BoardsTitleBorder = BorderFactory.createTitledBorder(blackline,
347                Bundle.getMessage("TitledBorderLabelBdl162Bdl168Boards"));
348        frame2.setBorder(bdl162Bdl168BoardsTitleBorder);
349
350        JPanel bdl162Bdl168BoardsOptions = new JPanel();
351        bdl162Bdl168BoardsOptions.setLayout(gl);
352        gc.gridy = 0;
353
354        bdl162Bdl168BoardsOptions.add(getLabel(3));
355        bdl162Bdl168BoardsOptions.add(getComboBox(3));
356        gc.gridy++;
357
358        bdl162Bdl168BoardsOptions.add(getLabel(5));
359        bdl162Bdl168BoardsOptions.add(getComboBox(5));
360        gc.gridy++;
361
362        bdl162Bdl168BoardsOptions.add(getLabel(6));
363        bdl162Bdl168BoardsOptions.add(getComboBox(6));
364        gc.gridy++;
365
366        bdl162Bdl168BoardsOptions.add(getLabel(7));
367        bdl162Bdl168BoardsOptions.add(getComboBox(7));
368        gc.gridy++;
369
370        bdl162Bdl168BoardsOptions.add(getLabel(36));
371        bdl162Bdl168BoardsOptions.add(getComboBox(36));
372
373        frame2.add(bdl162Bdl168BoardsOptions);
374        frame1.add(frame2);
375
376        JPanel frame3 = new JPanel();
377        frame3.setLayout(new BoxLayout(frame3, BoxLayout.PAGE_AXIS));
378
379        TitledBorder bdl168SpecificTitleBorder;
380        bdl168SpecificTitleBorder = BorderFactory.createTitledBorder(blackline,
381                Bundle.getMessage("TitledBorderLabelBdl168Only"));
382        frame3.setBorder(bdl168SpecificTitleBorder);
383
384        JPanel bdl168SpecificOptions = new JPanel();
385        bdl168SpecificOptions.setLayout(gl);
386        gc.gridy = 0;
387
388        bdl168SpecificOptions.add(getLabel(37, 38));
389        bdl168SpecificOptions.add(getComboBox(37, 38));
390        gc.gridy++;
391
392        bdl168SpecificOptions.add(getLabel(42));
393        bdl168SpecificOptions.add(getComboBox(42));
394        gc.gridy++;
395
396        bdl168SpecificOptions.add(getLabel(39));
397        bdl168SpecificOptions.add(getComboBox(39));
398        gc.gridy++;
399
400        bdl168SpecificOptions.add(getLabel(43, 44));
401        bdl168SpecificOptions.add(getComboBox(43, 44));
402
403        frame3.add(bdl168SpecificOptions);
404        frame2.add(frame3);
405
406        appendLine(frame1);
407        appendLine(provideStatusLine());
408        setStatus(Bundle.getMessage("STATUS_TEXT_BOARD_MODE"));
409
410        panelToScroll();
411
412    }
413
414    /**
415     * Create a JLabel for an OpSw.
416     *
417     * @param n   OpSw number
418     * @return the JPanel into which the Label is placed
419     */
420    private JLabel getLabel(int n) {
421        String number = Integer.toString(n);
422        if (number.length() == 1) {
423            number = "0" + number;
424        }
425        JLabel label = new JLabel(Bundle.getMessage("LabelX", number)); // NOI18N
426        label.setHorizontalAlignment(SwingConstants.RIGHT);
427        label.setPreferredSize(new JLabel("XXXXXXXXXXXXXXXXXX").getPreferredSize()); // NOI18N
428        // layout
429        gc.gridx = 0;
430        gc.weightx = 0.5;
431        gc.anchor = GridBagConstraints.EAST;
432        gl.setConstraints(label, gc);
433        return label;
434    }
435
436    /**
437     * Create a JLabel for an OpSw.
438     *
439     * @param n first OpSw number
440     * @param n2 second OpSw number
441     * @return the JPanel into which the Label is placed
442     */
443    private JLabel getLabel(int n, int n2) {
444        String number = Integer.toString(n);
445        if (number.length() == 1) {
446            number = "0" + number;
447        }
448        String number2 = Integer.toString(n2);
449        if (number2.length() == 1) {
450            number2 = "0" + number2;
451        }
452        JLabel label = new JLabel(Bundle.getMessage("LabelXY", number, number2)); // NOI18N
453        label.setHorizontalAlignment(SwingConstants.RIGHT);
454        label.setPreferredSize(new JLabel("XXXXXXXXXXXXXXXXXX").getPreferredSize()); // NOI18N
455        // layout
456        gc.gridx = 0;
457        gc.weightx = 0.5;
458        gc.anchor = GridBagConstraints.EAST;
459        gl.setConstraints(label, gc);
460        return label;
461    }
462
463    /**
464     * Create a JComboBox with two possible values.
465     * <p>
466     * For a given OpSw number, create a JComboBox containing the appropriate
467     * strings from the bundle.  Sets the initial value based on the OpSw's
468     * reported default value.
469     *
470     * @param n   OpSw number
471     * @return  the JPanel into which the JComboBox is placed
472     */
473    private JComboBox<String> getComboBox(int n) {
474        String number = Integer.toString(n);
475        if (number.length() == 1) {
476            number = "0" + number;
477        }
478        String[] s = new String[] {Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_THROWN"),
479                        Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_CLOSED")};
480        comboBox[n] = new JComboBox<>(s);
481        comboBox[n].setSelectedIndex(getIndexForDefault(n));
482        // size all combos to match the widest one (and a little bit more)
483        comboBox[n].setPreferredSize(new JLabel("XXXXXXX" + Bundle.getMessage("COMBOBOX_TEXT_OPSW36_THROWN")).getPreferredSize()); // NOI18N
484        // layout
485        gc.gridx = 1;
486        gc.weightx = 1.0;
487        gc.anchor = GridBagConstraints.WEST;
488        gl.setConstraints(comboBox[n], gc);
489        return comboBox[n];
490    }
491
492    /**
493     * Create a JComboBox with four possible values.
494     * <p>
495     * For two given OpSw numbers, create a JComboBox containing the appropriate
496     * strings from the bundle.  Sets the initial value based on the OpSws'
497     * reported default value.
498     *
499     * @param n first OpSw number
500     * @param n2 second OpSw number
501     * @return the JPanel into which the JComboBox is placed
502     */
503    private JComboBox<String> getComboBox(int n, int n2) {
504        String number = Integer.toString(n);
505        if (number.length() == 1) {
506            number = "0" + number; // NOI18N
507        }
508        String number2 = Integer.toString(n2);
509        if (number2.length() == 1) {
510            number2 = "0" + number2; // NOI18N
511        }
512
513        comboBox[n] = new JComboBox<>(
514                new String[] {Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_THROWN_OPSW" + number2 + "_THROWN"),
515                        Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_CLOSED_OPSW" + number2 + "_THROWN"),
516                        Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_THROWN_OPSW" + number2 + "_CLOSED"),
517                        Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_CLOSED_OPSW" + number2 + "_CLOSED")
518                });
519        // default choice = 0 so already set
520        // size all combos to match the widest one (and a little bit more)
521        comboBox[n].setPreferredSize(new JLabel("XXXXXXX" + Bundle.getMessage("COMBOBOX_TEXT_OPSW36_THROWN")).getPreferredSize()); // NOI18N
522        // layout
523        gc.gridx = 1;
524        gc.weightx = 1.0;
525        gc.anchor = GridBagConstraints.WEST;
526        gl.setConstraints(comboBox[n], gc);
527        return comboBox[n];
528    }
529
530    /**
531     * Determine the JComboBox index which corresponds to the default value
532     * for a given OpSw.
533     *
534     * @param  n OpSw number
535     * @return index of default choice
536     */
537    private int getIndexForDefault(int n) {
538        switch (n) {
539            case 5:
540            case 11:
541            case 12:
542                return 1;
543            default:
544                return 0;
545        }
546    }
547
548    /**
549     * Already know of this board (unit address)?
550     *
551     * @param id  Unit address to be checked against list
552     * @return    True if the unit address is already in the list
553     */
554    private boolean alreadyKnowThisBoardId(Integer id) {
555        return (boardNumsEntryValue.contains(id));
556    }
557
558    /**
559     * Add a board to the list of unit addresses if not already there.
560     *
561     * @param   id a unit address to be added
562     * @return  index into the boardNumsEntryValue list of entry for unit address "id"
563     */
564    private Integer addBoardIdToList(Integer id) {
565        boardNumsEntryValue.add(boardNumsEntryValue.size(), id);
566        addressComboBox.removeAllItems();
567        Collections.sort(boardNumsEntryValue);
568        Integer indexOfTargetBoardAddress = 0;
569        for (Integer index = 0; index < boardNumsEntryValue.size(); ++index) {
570            if (boardNumsEntryValue.get(index).equals(id)) {
571                indexOfTargetBoardAddress = index;
572            }
573            addressComboBox.addItem(boardNumsEntryValue.get(index));
574        }
575        return indexOfTargetBoardAddress;
576    }
577
578    /**
579     * Select a device based on an index into the list of unit addresses.
580     *
581     * @param index into the list of addresses
582     */
583    private void selectBoardIdByIndex(Integer index) {
584        addressComboBox.setSelectedIndex(index);
585    }
586
587    /**
588     * Read all OpSws, based on the selected unit address in the JComboBox.
589     */
590    @Override
591    public void readAll() {
592        addrField.setText(addressComboBox.getSelectedItem().toString());
593        Integer curAddr = Integer.parseInt(addrField.getText());
594
595        // If a new board address is specified, add it (and sort it) into the current list.
596        if (!alreadyKnowThisBoardId(curAddr)) {
597            Integer index = addBoardIdToList(curAddr);
598            selectBoardIdByIndex(index);
599        }
600        super.readAll();
601    }
602
603    /**
604     * Interpret incoming LocoNet messages.
605     *
606     * @param m LocoNet message to be interpreted
607     */
608    @Override
609    public void message(LocoNetMessage m) {
610        super.message(m);
611        if ((m.getOpCode() == LnConstants.OPC_MULTI_SENSE) && ((m.getElement(1) & 0x7E) == 0x62)) {
612            // device identity report
613            if (m.getElement(3) == 0x01) {
614                Integer extractedBoardId = 1 + ((m.getElement(1) & 0x1) << 7)
615                        + (m.getElement(2) & 0x7F);
616                if (!alreadyKnowThisBoardId(extractedBoardId)) {
617                    addBoardIdToList(extractedBoardId);
618                }
619            }
620        }
621    }
622
623    private final static Logger log = LoggerFactory.getLogger(BDL16Panel.class);
624
625}