001package jmri.jmrit.entryexit;
002
003import java.awt.Color;
004import java.awt.Container;
005import java.awt.GridLayout;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.beans.PropertyChangeEvent;
009import java.beans.PropertyChangeListener;
010import java.util.ArrayList;
011import java.util.List;
012import java.util.SortedSet;
013import java.util.TreeSet;
014
015import javax.swing.*;
016import javax.swing.table.TableCellEditor;
017import javax.swing.table.TableColumn;
018import javax.swing.table.TableRowSorter;
019
020import jmri.InstanceManager;
021import jmri.NamedBean;
022import jmri.jmrit.display.EditorManager;
023import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
024import jmri.jmrit.display.layoutEditor.LayoutEditor;
025import jmri.jmrit.display.layoutEditor.LayoutSlip;
026import jmri.jmrit.display.layoutEditor.LayoutTurnout;
027import jmri.jmrit.display.layoutEditor.LevelXing;
028import jmri.jmrit.display.layoutEditor.PositionablePoint;
029import jmri.swing.NamedBeanComboBox;
030import jmri.util.JmriJFrame;
031import jmri.util.swing.*;
032import jmri.util.table.ButtonEditor;
033import jmri.util.table.ButtonRenderer;
034
035/**
036 * JPanel to create a new EntryExitPair.
037 *
038 * @author Bob Jacobsen Copyright (C) 2009
039 */
040public class AddEntryExitPairPanel extends jmri.util.swing.JmriPanel {
041
042    JComboBox<String> selectPanel = new JComboBox<>();
043    JComboBox<String> fromPoint = new JComboBox<>();
044    JComboBox<String> toPoint = new JComboBox<>();
045
046    String[] interlockTypes = {Bundle.getMessage("SetTurnoutsOnly"), Bundle.getMessage("SetTurnoutsAndSignalMasts"), Bundle.getMessage("FullInterlock")};  // NOI18N
047    JComboBox<String> typeBox = new JComboBox<>(interlockTypes);
048
049    List<LayoutEditor> panels;
050
051    EntryExitPairs nxPairs = jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class);
052
053    // signalling.EntryExitBundle via Bundle method
054
055    public AddEntryExitPairPanel(LayoutEditor panel) {
056
057        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
058        // combo boxes selection panel
059        JPanel top = new JPanel();
060        top.setBorder(BorderFactory.createEtchedBorder());
061        top.setLayout(new GridLayout(4, 2));
062        top.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("SelectPanel")), SwingConstants.RIGHT));  // NOI18N
063        top.add(selectPanel);
064        selectPanel.removeAllItems();
065        panels = InstanceManager.getDefault(EditorManager.class).getList(LayoutEditor.class);
066        for (int i = 0; i < panels.size(); i++) {
067            selectPanel.addItem(panels.get(i).getLayoutName());
068        }
069        if (panel != null) {
070            selectPanel.setSelectedItem(panel.getLayoutName());
071        }
072
073        top.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("FromLocation")), SwingConstants.RIGHT));  // NOI18N
074        top.add(fromPoint);
075        ActionListener selectPanelListener = (ActionEvent e) -> {
076            selectPointsFromPanel();
077            nxModel.setPanel(panels.get(selectPanel.getSelectedIndex()));
078        };
079        selectPointsFromPanel();
080        selectPanel.addActionListener(selectPanelListener);
081
082        top.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ToLocation")), SwingConstants.RIGHT));  // NOI18N
083        top.add(toPoint);
084
085        JComboBoxUtil.setupComboBoxMaxRows(fromPoint);
086        JComboBoxUtil.setupComboBoxMaxRows(toPoint);
087
088        top.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("NXType")), SwingConstants.RIGHT));  // NOI18N
089        top.add(typeBox);
090        add(top);
091
092        // button panel
093        JPanel p = new JPanel();
094        JButton ok = new JButton(Bundle.getMessage("AddPair"));  // NOI18N
095        p.add(ok);
096        ok.addActionListener((ActionEvent e) -> {
097            addButton();
098        });
099
100        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
101        JButton auto;
102        p.add(auto = new JButton(Bundle.getMessage("AutoGenerate")));  // NOI18N
103        auto.addActionListener((ActionEvent e) -> {
104            autoDiscovery();
105        });
106        p.add(auto);
107        add(p);
108        nxModel = new TableModel(panel);
109        nxDataTable = new JTable(nxModel);
110        nxDataTable.setRowSorter(new TableRowSorter<>(nxModel));
111        nxDataScroll = new JScrollPane(nxDataTable);
112        nxModel.configureTable(nxDataTable);
113        java.awt.Dimension dataTableSize = nxDataTable.getPreferredSize();
114        // width is right, but if table is empty, it's not high
115        // enough to reserve much space.
116        dataTableSize.height = Math.max(dataTableSize.height, 400);
117        nxDataScroll.getViewport().setPreferredSize(dataTableSize);
118        add(nxDataScroll);
119    }
120
121    LayoutEditor panel;
122
123    private void addButton() {
124        ValidPoints from = getValidPointFromCombo(fromPoint);
125        ValidPoints to = getValidPointFromCombo(toPoint);
126        if (from == null || to == null) {
127            return;
128        }
129
130        nxPairs.addNXDestination(from.getPoint(), to.getPoint(), panel);
131        nxPairs.setEntryExitType(from.getPoint(), panel, to.getPoint(), typeBox.getSelectedIndex());
132
133        validateManualPair(from.getPoint(), to.getPoint());
134    }
135
136    /**
137     * Verify that a route can be set between the source and destination points.
138     * @parm fromPoint The source bean, normally a sensor.
139     * @parm toPoint The destination bean, normally a sensor.
140     */
141    private void validateManualPair(NamedBean fromPoint, NamedBean toPoint) {
142        var fromDetail = nxPairs.getPointDetails(fromPoint, panel);
143        var toDetail = nxPairs.getPointDetails(toPoint, panel);
144        if (fromDetail == null || toDetail == null) {
145            log.debug("validateManualPair: a point detail was null, skip checks");
146            return;
147        }
148
149        // Get the facing and protected blocks for each point.
150        var fromFace = fromDetail.getFacing();
151        var fromProtect = fromDetail.getProtecting();
152        var toFace = toDetail.getFacing();
153        var toProtect = toDetail.getProtecting();
154        if (fromFace == null || fromProtect.isEmpty() || toFace == null || toProtect.isEmpty()) {
155            log.debug("validateManualPair: a facing block or a protected block was not found, skip checks");
156            return;
157        }
158
159        log.debug("validateManualPair: from: {} :: {}", fromFace.getDisplayName(), fromProtect.get(0).getDisplayName());
160        log.debug("validateManualPair: to: {} :: {}", toFace.getDisplayName(), toProtect.get(0).getDisplayName());
161
162        var lbm = InstanceManager.getDefault(LayoutBlockManager.class);
163        var lbc = lbm.getLayoutBlockConnectivityTools();
164
165        try {
166            if (!lbc.checkValidDest(
167                    fromFace, fromProtect.get(0), toFace, toProtect,
168                    jmri.jmrit.display.layoutEditor.LayoutBlockConnectivityTools.Routing.SENSORTOSENSOR)) {
169                log.debug("validateManualPair: checkValidDest returned false");
170                validateDialog(fromPoint, toPoint);
171            }
172        } catch(jmri.JmriException ex) {
173            log.debug("validateManualPair: JmriException: {}", ex.getMessage());
174            validateDialog(fromPoint, toPoint);
175        }
176    }
177
178    /**
179     * Display the validation failed dialog.  Provide an option to delete the bad NX pair that was just added.
180     * @parm fromPoint The source bean, normally a sensor.
181     * @parm toPoint The destination bean, normally a sensor.
182     */
183    private void validateDialog(NamedBean fromPoint, NamedBean toPoint) {
184        int reply = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("ValidateWarning"), Bundle.getMessage("ValidateTitle"),  // NOI18N
185                JmriJOptionPane.YES_NO_OPTION,
186                JmriJOptionPane.WARNING_MESSAGE);
187        if (reply == JmriJOptionPane.YES_OPTION) {
188            nxPairs.deleteNxPair(fromPoint, toPoint, panel);
189        }
190    }
191
192    jmri.util.JmriJFrame entryExitFrame = null;
193    JLabel sourceLabel = new JLabel();
194
195    private void autoDiscovery() {
196        if (!InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
197            int response = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("EnableLayoutBlockRouting"), Bundle.getMessage("EnableLayoutBlockRouting"), JmriJOptionPane.YES_NO_OPTION);
198            if ( response == JmriJOptionPane.YES_OPTION ) {
199                InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).enableAdvancedRouting(true);
200                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("LayoutBlockRoutingEnabled"));  // NOI18N
201            }
202        }
203        entryExitFrame = new jmri.util.JmriJFrame(Bundle.getMessage("DiscoverEntryExitPairs"), false, false);   // NOI18N
204        entryExitFrame.setPreferredSize(null);
205        JPanel panel1 = new JPanel();
206        sourceLabel = new JLabel(Bundle.getMessage("DiscoveringEntryExitPairs"));  // NOI18N
207        /*ImageIcon i;
208         i = new ImageIcon(FileUtil.findURL("resources/icons/misc/gui3/process-working.gif"));
209         JLabel label = new JLabel();
210         label.setIcon(i);
211         panel1.add(label);*/
212        panel1.add(sourceLabel);
213
214        entryExitFrame.add(panel1);
215        entryExitFrame.pack();
216        entryExitFrame.setVisible(true);
217        int retval = JmriJOptionPane.showOptionDialog(this, Bundle.getMessage("AutoGenEntryExitMessage"), Bundle.getMessage("AutoGenEntryExitTitle"),  // NOI18N
218                JmriJOptionPane.YES_NO_OPTION,
219                JmriJOptionPane.QUESTION_MESSAGE, null, null, null);
220        if (retval == JmriJOptionPane.YES_OPTION ) {
221            final PropertyChangeListener propertyNXListener = new PropertyChangeListener() {
222                @Override
223                public void propertyChange(PropertyChangeEvent evt) {
224                    if (evt.getPropertyName().equals("autoGenerateComplete")) {  // NOI18N
225                        if (entryExitFrame != null) {
226                            entryExitFrame.setVisible(false);
227                            entryExitFrame.dispose();
228                        }
229                        nxPairs.removePropertyChangeListener(this);
230                        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("AutoGenComplete"));  // NOI18N
231                    }
232                }
233            };
234            try {
235                nxPairs.addPropertyChangeListener(propertyNXListener);
236                nxPairs.automaticallyDiscoverEntryExitPairs(panels.get(selectPanel.getSelectedIndex()), typeBox.getSelectedIndex());
237            } catch (jmri.JmriException e) {
238                nxPairs.removePropertyChangeListener(propertyNXListener);
239                JmriJOptionPane.showMessageDialog(this, e.toString());
240                entryExitFrame.setVisible(false);
241            }
242        } else {
243            entryExitFrame.setVisible(false);
244        }
245    }
246
247    ValidPoints getValidPointFromCombo(JComboBox<String> box) {
248        String item = (String) box.getSelectedItem();
249        for (int i = 0; i < validPoints.size(); i++) {
250            if (validPoints.get(i).getDescription().equals(item)) {
251                return validPoints.get(i);
252            }
253        }
254        return null;
255    }
256
257    List<ValidPoints> validPoints = new ArrayList<>();
258    boolean doFromCombo;
259    SortedSet<String> fromSet = new TreeSet<>();
260    SortedSet<String> toSet = new TreeSet<>();
261
262    private void selectPointsFromPanel() {
263        if (selectPanel.getSelectedIndex() == -1) {
264            return;
265        }
266        if (panel == panels.get(selectPanel.getSelectedIndex())) {
267            return;
268        }
269        panel = panels.get(selectPanel.getSelectedIndex());
270        fromSet.clear();
271        toSet.clear();
272        doFromCombo = true;
273        selectPoints(panel);
274
275        // Do other panels if any
276        doFromCombo = false;
277        panels = InstanceManager.getDefault(EditorManager.class).getList(LayoutEditor.class);
278        for (int i = 0; i < panels.size(); i++) {
279            if (panels.get(i) != panel) {
280                selectPoints(panels.get(i));
281            }
282        }
283
284        // Update the combo boxes
285        fromPoint.removeAllItems();
286        fromSet.forEach((ent) -> {
287            fromPoint.addItem(ent);
288        });
289        toPoint.removeAllItems();
290        toSet.forEach((ent) -> {
291            toPoint.addItem(ent);
292        });
293    }
294
295    private void selectPoints(LayoutEditor panel) {
296        for (PositionablePoint pp : panel.getPositionablePoints()) {
297            addPointToCombo(pp.getWestBoundSignalMastName(), pp.getWestBoundSensorName());
298            addPointToCombo(pp.getEastBoundSignalMastName(), pp.getEastBoundSensorName());
299        }
300
301        for (LayoutTurnout t : panel.getLayoutTurnouts()) {
302            addPointToCombo(t.getSignalAMastName(), t.getSensorAName());
303            addPointToCombo(t.getSignalBMastName(), t.getSensorBName());
304            addPointToCombo(t.getSignalCMastName(), t.getSensorCName());
305            addPointToCombo(t.getSignalDMastName(), t.getSensorDName());
306        }
307
308        for (LevelXing xing : panel.getLevelXings()) {
309            addPointToCombo(xing.getSignalAMastName(), xing.getSensorAName());
310            addPointToCombo(xing.getSignalBMastName(), xing.getSensorBName());
311            addPointToCombo(xing.getSignalCMastName(), xing.getSensorCName());
312            addPointToCombo(xing.getSignalDMastName(), xing.getSensorDName());
313        }
314
315        for (LayoutSlip slip : panel.getLayoutSlips()) {
316            addPointToCombo(slip.getSignalAMastName(), slip.getSensorAName());
317            addPointToCombo(slip.getSignalBMastName(), slip.getSensorBName());
318            addPointToCombo(slip.getSignalCMastName(), slip.getSensorCName());
319            addPointToCombo(slip.getSignalDMastName(), slip.getSensorDName());
320        }
321    }
322
323    void addPointToCombo(String signalMastName, String sensorName) {
324        if (sensorName != null && !sensorName.isEmpty()) {
325            String description = sensorName;
326            NamedBean source = InstanceManager.sensorManagerInstance().getSensor(sensorName);
327            if (signalMastName != null && !signalMastName.isEmpty()) {
328                description = sensorName + " (" + signalMastName + ")";
329            }
330            validPoints.add(new ValidPoints(source, description));
331            if (doFromCombo) {
332                fromSet.add(description);
333            }
334            toSet.add(description);
335        }
336    }
337
338    JTable nxDataTable;
339    JScrollPane nxDataScroll;
340
341    TableModel nxModel;
342
343    static final int FROMPOINTCOL = 0;
344    static final int TOPOINTCOL = 1;
345    static final int ACTIVECOL = 2;
346    static final int CLEARCOL = 3;
347    static final int BOTHWAYCOL = 4;
348    static final int DELETECOL = 5;
349    static final int TYPECOL = 6;
350    static final int ENABLEDCOL = 7;
351
352    static final int NUMCOL = ENABLEDCOL + 1;
353
354    class TableModel extends javax.swing.table.AbstractTableModel implements PropertyChangeListener {
355
356        //needs a method to for when panel changes
357        //need a method to delete an item
358        //Possibly also to set a route.
359        // Since 4.17.4 has added functionality a property change listener to catch when paths go active.
360        TableModel(LayoutEditor panel) {
361            setPanel(panel);
362            nxPairs.addPropertyChangeListener(this);
363            source = nxPairs.getNxSource(panel);
364            dest = nxPairs.getNxDestination();
365        }
366
367        void setPanel(LayoutEditor panel) {
368            if (this.panel == panel) {
369                return;
370            }
371            this.panel = panel;
372            rowCount = nxPairs.getNxPairNumbers(panel);
373            updateNameList();
374            fireTableDataChanged();
375        }
376
377        LayoutEditor panel;
378
379        List<Object> source = null;
380        List<Object> dest = null;
381
382        void updateNameList() {
383            source = nxPairs.getNxSource(panel);
384            dest = nxPairs.getNxDestination();
385        }
386
387        int rowCount = 0;
388
389        @Override
390        public int getRowCount() {
391            return rowCount;
392        }
393
394        public void configureTable(JTable table) {
395            // allow reordering of the columns
396            table.getTableHeader().setReorderingAllowed(true);
397
398            // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
399            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
400
401            // resize columns as requested
402            for (int i = 0; i < table.getColumnCount(); i++) {
403                int width = getPreferredWidth(i);
404                table.getColumnModel().getColumn(i).setPreferredWidth(width);
405            }
406            table.sizeColumnsToFit(-1);
407
408            configDeleteColumn(table);
409
410        }
411
412        @Override
413        public Object getValueAt(int row, int col) {
414            // get roster entry for row
415            if (panel == null) {
416                log.debug("no panel selected!");  // NOI18N
417                return Bundle.getMessage("ErrorTitle");  // NOI18N
418            }
419            switch (col) {
420                case FROMPOINTCOL:
421                    return nxPairs.getPointAsString((NamedBean) source.get(row), panel);
422                case TOPOINTCOL:
423                    return nxPairs.getPointAsString((NamedBean) dest.get(row), panel);
424                case ACTIVECOL:
425                    return isPairActive(row);
426                case BOTHWAYCOL:
427                    return !nxPairs.isUniDirection(source.get(row), panel, dest.get(row));
428                case ENABLEDCOL:
429                    return !nxPairs.isEnabled(source.get(row), panel, dest.get(row));
430                case CLEARCOL:
431                    return Bundle.getMessage("ButtonClear");  // NOI18N
432                case DELETECOL:
433                    return Bundle.getMessage("ButtonDelete");  // NOI18N
434                case TYPECOL:
435                    return NXTYPE_NAMES[nxPairs.getEntryExitType(source.get(row), panel, dest.get(row))];
436                default:
437                    return "";
438            }
439        }
440
441        @Override
442        public void setValueAt(Object value, int row, int col) {
443            if (col == DELETECOL) {
444                // button fired, delete Bean
445                deleteEntryExit(row, col);
446            }
447            if (col == CLEARCOL) {
448                nxPairs.cancelInterlock(source.get(row), panel, dest.get(row));
449            }
450            if (col == BOTHWAYCOL) {
451                boolean b = !((Boolean) value);
452                nxPairs.setUniDirection(source.get(row), panel, dest.get(row), b);
453            }
454            if (col == ENABLEDCOL) {
455                boolean b = !((Boolean) value);
456                nxPairs.setEnabled(source.get(row), panel, dest.get(row), b);
457            }
458            if (col == TYPECOL) {
459                String val = (String) value;
460                if (val.equals(Bundle.getMessage("SetTurnoutsOnly"))) { // I18N matching needs if-else  // NOI18N
461                    nxPairs.setEntryExitType(source.get(row), panel, dest.get(row), 0x00);
462                } else if (val.equals(Bundle.getMessage("SetTurnoutsAndSignalMasts"))) {  // NOI18N
463                    nxPairs.setEntryExitType(source.get(row), panel, dest.get(row), 0x01);
464                } else if (val.equals(Bundle.getMessage("FullInterlock"))) {
465                    nxPairs.setEntryExitType(source.get(row), panel, dest.get(row), 0x02);  // NOI18N
466                }
467            }
468        }
469
470        public int getPreferredWidth(int col) {
471            switch (col) {
472                case FROMPOINTCOL:
473                case TOPOINTCOL:
474                    return new JTextField(15).getPreferredSize().width;
475                case ACTIVECOL:
476                case BOTHWAYCOL:
477                case ENABLEDCOL:
478                    return new JTextField(5).getPreferredSize().width;
479                case CLEARCOL:
480                case DELETECOL:  //
481                    return new JTextField(22).getPreferredSize().width;
482                case TYPECOL:
483                    return new JTextField(10).getPreferredSize().width;
484                default:
485                    log.warn("Unexpected column in getPreferredWidth: {}", col);  // NOI18N
486                    return new JTextField(8).getPreferredSize().width;
487            }
488        }
489
490        protected void deleteEntryExit(int row, int col) {
491            NamedBean nbSource = ((NamedBean) source.get(row));
492            NamedBean nbDest = (NamedBean) dest.get(row);
493            nxPairs.deleteNxPair(nbSource, nbDest, panel);
494        }
495
496        String isPairActive(int row) {
497            if (nxPairs.isPathActive(source.get(row), dest.get(row), panel)) {
498                return (Bundle.getMessage("ButtonYes")); // "Yes"  // NOI18N
499            }
500            return ("");
501        }
502
503        @Override
504        public String getColumnName(int col) {
505            switch (col) {
506                case FROMPOINTCOL:
507                    return Bundle.getMessage("ColumnFrom");  // NOI18N
508                case TOPOINTCOL:
509                    return Bundle.getMessage("ColumnTo");  // NOI18N
510                case ACTIVECOL:
511                    return Bundle.getMessage("SensorStateActive"); // "Active"  // NOI18N
512                case DELETECOL:
513                    return "";
514                case CLEARCOL:
515                    return "";
516                case BOTHWAYCOL:
517                    return Bundle.getMessage("ColumnBoth");  // NOI18N
518                case TYPECOL:
519                    return Bundle.getMessage("NXType");  // NOI18N
520                case ENABLEDCOL:
521                    return Bundle.getMessage("Disabled");  // NOI18N
522                default:
523                    return "<UNKNOWN>";  // NOI18N
524            }
525        }
526
527        @Override
528        public Class<?> getColumnClass(int col) {
529            switch (col) {
530                case FROMPOINTCOL:
531                case TOPOINTCOL:
532                case ACTIVECOL:
533                    return String.class;
534                case DELETECOL:
535                case CLEARCOL:
536                    return JButton.class;
537                case BOTHWAYCOL:
538                case ENABLEDCOL:
539                    return Boolean.class;
540                case TYPECOL:
541                    return String.class;
542                default:
543                    return null;
544            }
545        }
546
547        @Override
548        public boolean isCellEditable(int row, int col) {
549            switch (col) {
550                case BOTHWAYCOL:
551                    Object obj = nxPairs.getEndPointLocation((NamedBean) dest.get(row), panel);
552                    if (obj instanceof PositionablePoint) {
553                        PositionablePoint point = (PositionablePoint) obj;
554                        if (point.getType() == PositionablePoint.PointType.END_BUMPER) {
555                            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("EndBumperPoint"));  // NOI18N
556                            return false;
557                        }
558                    }
559                    if (!nxPairs.canBeBiDirectional(source.get(row), panel, dest.get(row))) {
560                        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BothWayTurnoutOnly"));  // NOI18N
561                        return false;
562                    }
563                    /*if(nxPairs.getEntryExitType(source.get(row), panel, dest.get(row))!=0x00){
564                     JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BothWayTurnoutOnly"));
565                     return false;
566                     }*/
567                    return true;
568                case DELETECOL:
569                case CLEARCOL:
570                case ENABLEDCOL:
571                case TYPECOL:
572                    return true;
573                default:
574                    return false;
575            }
576        }
577
578        @Override
579        public int getColumnCount() {
580            return NUMCOL;
581        }
582
583        @Override
584        public void propertyChange(java.beans.PropertyChangeEvent e) {
585            if (e.getPropertyName().equals("length") || e.getPropertyName().equals("active")) {  // NOI18N
586                rowCount = nxPairs.getNxPairNumbers(panel);
587                updateNameList();
588                fireTableDataChanged();
589            }
590        }
591    }
592
593    String[] NXTYPE_NAMES = {Bundle.getMessage("SetTurnoutsOnly"), Bundle.getMessage("SetTurnoutsAndSignalMasts"), Bundle.getMessage("FullInterlock")};  // NOI18N
594    // Picked up in setValueAt() to read back from table
595
596    protected void configDeleteColumn(JTable table) {
597        // have the delete column hold a button
598        setColumnToHoldButton(table, DELETECOL,
599                new JButton(Bundle.getMessage("ButtonDelete")));  // NOI18N
600
601        setColumnToHoldButton(table, CLEARCOL,
602                new JButton(Bundle.getMessage("ButtonClear")));  // NOI18N
603
604        JComboBox<String> typeCombo = new JComboBox<>(NXTYPE_NAMES);
605
606        TableColumn col = table.getColumnModel().getColumn(TYPECOL);
607        col.setCellEditor(new DefaultCellEditor(typeCombo));
608    }
609
610    /**
611     * Service method to set up a column so that it will hold a button for its
612     * values.
613     *
614     * @param table  the table
615     * @param column the column
616     * @param sample Typical button, used for size
617     */
618    protected void setColumnToHoldButton(JTable table, int column, JButton sample) {
619        //TableColumnModel tcm = table.getColumnModel();
620        // install a button renderer & editor
621        ButtonRenderer buttonRenderer = new ButtonRenderer();
622        table.setDefaultRenderer(JButton.class, buttonRenderer);
623        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
624        table.setDefaultEditor(JButton.class, buttonEditor);
625        // ensure the table rows, columns have enough room for buttons
626        table.setRowHeight(sample.getPreferredSize().height);
627        table.getColumnModel().getColumn(column)
628                .setPreferredWidth((sample.getPreferredSize().width) + 4);
629    }
630
631    static class ValidPoints {
632
633        NamedBean bean;
634        String description;
635
636        ValidPoints(NamedBean bean, String description) {
637            this.bean = bean;
638            this.description = description;
639        }
640
641        NamedBean getPoint() {
642            return bean;
643        }
644
645        String getDescription() {
646            return description;
647        }
648    }
649
650    // Variables for the Options menu item
651    JmriJFrame optionsFrame = null;
652    Container optionsPane = null;
653
654    String[] clearOptions = {
655            Bundle.getMessage("PromptUser"),   // NOI18N
656            Bundle.getMessage("ClearRoute"),   // NOI18N
657            Bundle.getMessage("CancelRoute"),  // NOI18N
658            Bundle.getMessage("StackRoute")};  // NOI18N
659    JComboBox<String> clearEntry = new JComboBox<>(clearOptions);
660
661    String[] overlapOptions = {
662            Bundle.getMessage("PromptUser"),   // NOI18N
663            Bundle.getMessage("CancelRoute"),  // NOI18N
664            Bundle.getMessage("StackRoute")};  // NOI18N
665    JComboBox<String> overlapEntry = new JComboBox<>(overlapOptions);
666
667    NamedBeanComboBox<jmri.Memory> memoryComboBox = new NamedBeanComboBox<>(
668            InstanceManager.getDefault(jmri.MemoryManager.class));
669    JSpinner memoryClearDelay = new JSpinner(new SpinnerNumberModel(0, 0, 99, 1));
670
671    JTextField durationSetting = new JTextField(10);
672    String[] colorText = {"ColorClear", "Black", "DarkGray", "Gray",  // NOI18N
673        "LightGray", "White", "Red", "Pink", "Orange",  // NOI18N
674        "Yellow", "Green", "Blue", "Magenta", "Cyan"}; // NOI18N
675    Color[] colorCode = {null, Color.black, Color.darkGray, Color.gray,
676        Color.lightGray, Color.white, Color.red, Color.pink, Color.orange,
677        Color.yellow, Color.green, Color.blue, Color.magenta, Color.cyan};
678    int numColors = 14;  // number of entries in the above arrays
679
680    JCheckBox useAbsSignalMode = new JCheckBox(Bundle.getMessage("UseAbsSignalMode"));  // NOI18N
681    JCheckBox dispatcherUse = new JCheckBox(Bundle.getMessage("DispatcherInt"));  // NOI18N
682
683    JComboBox<String> settingTrackColorBox = new JComboBox<>();
684
685    private void initializeColorCombo(JComboBox<String> colorCombo) {
686        colorCombo.removeAllItems();
687        for (int i = 0; i < numColors; i++) {
688            colorCombo.addItem(Bundle.getMessage(colorText[i])); // I18N using Bundle.getMessage from higher level color list
689        }
690    }
691
692    private void setColorCombo(JComboBox<String> colorCombo, Color color) {
693        for (int i = 0; i < numColors; i++) {
694            if (color == colorCode[i]) {
695                colorCombo.setSelectedIndex(i);
696                return;
697            }
698        }
699    }
700
701    private Color getSelectedColor(JComboBox<String> colorCombo) {
702        return (colorCode[colorCombo.getSelectedIndex()]);
703    }
704
705    /**
706     * Build the Options window
707     * @param e the action event
708     */
709    protected void optionWindow(ActionEvent e) {
710        if (optionsFrame == null) {
711            optionsFrame = new JmriJFrame(Bundle.getMessage("OptionsTitle"), false, true);  // NOI18N
712            //optionsFrame.addHelpMenu("package.jmri.jmrit.dispatcher.Options", true);
713            optionsPane = optionsFrame.getContentPane();
714            optionsPane.setLayout(new BoxLayout(optionsFrame.getContentPane(), BoxLayout.Y_AXIS));
715
716            clearEntry.setSelectedIndex(nxPairs.getClearDownOption());
717            JPanel p1 = new JPanel();
718            //clearEntry.addActionListener(clearEntryListener);
719            clearEntry.setToolTipText(Bundle.getMessage("ReselectionTip"));  // NOI18N
720            p1.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("Reselection"))));  // NOI18N
721            p1.add(clearEntry);
722            optionsPane.add(p1);
723
724            overlapEntry.setSelectedIndex(nxPairs.getOverlapOption());
725            JPanel p1a = new JPanel();
726            overlapEntry.setToolTipText(Bundle.getMessage("OverlapSelectedTip"));  // NOI18N
727            p1a.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("OverlapSelected"))));  // NOI18N
728            p1a.add(overlapEntry);
729            optionsPane.add(p1a);
730
731            LayoutEditor.setupComboBox(memoryComboBox, false, true, false);
732            memoryComboBox.setSelectedItemByName(nxPairs.getMemoryOption());
733            JPanel p1b = new JPanel();
734            memoryComboBox.setToolTipText(Bundle.getMessage("MemoryOptionTip"));  // NOI18N
735            p1b.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("MemoryOption"))));  // NOI18N
736            p1b.add(memoryComboBox);
737            optionsPane.add(p1b);
738
739            memoryClearDelay.setValue(nxPairs.getMemoryClearDelay());
740            JPanel p1c = new JPanel();
741            memoryClearDelay.setToolTipText(Bundle.getMessage("MemoryClearTip"));  // NOI18N
742            p1c.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("MemoryClear"))));  // NOI18N
743            p1c.add(memoryClearDelay);
744            optionsPane.add(p1c);
745
746            JPanel p2 = new JPanel();
747            initializeColorCombo(settingTrackColorBox);
748            setColorCombo(settingTrackColorBox, nxPairs.getSettingRouteColor());
749            ActionListener settingTrackColorListener = (ActionEvent e1) -> {
750                if (getSelectedColor(settingTrackColorBox) != null) {
751                    durationSetting.setEnabled(true);
752                } else {
753                    durationSetting.setEnabled(false);
754                }
755            };
756
757            settingTrackColorBox.addActionListener(settingTrackColorListener);
758            p2.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("RouteSetColour"))));  // NOI18N
759            p2.add(settingTrackColorBox);
760            optionsPane.add(p2);
761            durationSetting.setText("" + nxPairs.getSettingTimer());
762            if (nxPairs.useDifferentColorWhenSetting()) {
763                durationSetting.setEnabled(true);
764            } else {
765                durationSetting.setEnabled(false);
766            }
767            JPanel p3 = new JPanel();
768            p3.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("SettingDuration"))));  // NOI18N
769            p3.add(durationSetting);
770            optionsPane.add(p3);
771
772            JPanel p4 = new JPanel();
773            p4.add(useAbsSignalMode);
774            useAbsSignalMode.setSelected(nxPairs.isAbsSignalMode());
775            optionsPane.add(p4);
776
777            JPanel p5 = new JPanel();
778            p5.add(dispatcherUse);
779            dispatcherUse.setSelected(nxPairs.getDispatcherIntegration());
780            optionsPane.add(p5);
781
782            JButton ok = new JButton(Bundle.getMessage("ButtonOK"));  // NOI18N
783            optionsPane.add(ok);
784            ok.addActionListener((ActionEvent e1) -> {
785                optionSaveButton();
786            });
787        }
788        optionsFrame.pack();
789        optionsFrame.setVisible(true);
790    }
791
792    /**
793     * Save the option updates
794     */
795    void optionSaveButton() {
796        int settingTimer = 2000; // in milliseconds
797        try {
798            settingTimer = Integer.parseInt(durationSetting.getText());
799        } catch (Exception e) {
800            JmriJOptionPane.showMessageDialog(durationSetting, Bundle.getMessage("ValueBeNumber"));  // NOI18N
801            return;
802        }
803        nxPairs.setSettingTimer(settingTimer);
804        nxPairs.setSettingRouteColor(getSelectedColor(settingTrackColorBox));
805        nxPairs.setClearDownOption(clearEntry.getSelectedIndex());
806        nxPairs.setOverlapOption(overlapEntry.getSelectedIndex());
807        nxPairs.setMemoryClearDelay((int) memoryClearDelay.getValue());
808        nxPairs.setAbsSignalMode(useAbsSignalMode.isSelected());
809        nxPairs.setDispatcherIntegration(dispatcherUse.isSelected());
810
811        String memoryName = memoryComboBox.getSelectedItemDisplayName();
812        nxPairs.setMemoryOption((memoryName == null) ? "" : memoryName);
813
814        optionsFrame.setVisible(false);
815
816    }
817
818    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AddEntryExitPairPanel.class);
819
820}