001package jmri.jmrit.beantable;
002
003import java.awt.Color;
004import java.awt.Component;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.util.Objects;
008import java.util.Vector;
009
010import javax.annotation.Nonnull;
011import javax.swing.*;
012
013import jmri.*;
014import jmri.jmrit.beantable.turnout.TurnoutTableDataModel;
015import jmri.jmrit.turnoutoperations.TurnoutOperationFrame;
016import jmri.swing.ManagerComboBox;
017import jmri.swing.SystemNameValidator;
018import jmri.util.JmriJFrame;
019import jmri.util.swing.*;
020
021/**
022 * Swing action to create and register a TurnoutTable GUI.
023 *
024 * @author Bob Jacobsen Copyright (C) 2003, 2004, 2007
025 * @author Egbert Broerse Copyright (C) 2017
026 */
027public class TurnoutTableAction extends AbstractTableAction<Turnout> {
028
029    /**
030     * Create an action with a specific title.
031     * <p>
032     * Note that the argument is the Action title, not the title of the
033     * resulting frame. Perhaps this should be changed?
034     *
035     * @param actionName title of the action
036     */
037    public TurnoutTableAction(String actionName) {
038        super(actionName);
039
040        // disable ourself if there is no primary turnout manager available
041        if (turnoutManager == null) {
042            super.setEnabled(false);
043        }
044    }
045
046    public TurnoutTableAction() {
047        this(Bundle.getMessage("TitleTurnoutTable"));
048    }
049
050    protected TurnoutManager turnoutManager = InstanceManager.getDefault(TurnoutManager.class);
051
052    /**
053     * {@inheritDoc}
054     */
055    @Override
056    public void setManager(@Nonnull Manager<Turnout> man) {
057        if (man instanceof TurnoutManager) {
058            log.debug("setting manager of TTAction {} to {}",this,man.getClass());
059            turnoutManager = (TurnoutManager) man;
060            if (m!=null){ // also update Table Model
061                m.setManager(man);
062            }
063        }
064    }
065
066    /**
067     * Create the JTable DataModel, along with the changes for the specific case
068     * of Turnouts.
069     */
070    @Override
071    protected void createModel() {
072        m = new TurnoutTableDataModel(turnoutManager);
073    }
074
075    /**
076     * {@inheritDoc}
077     */
078    @Override
079    protected void setTitle() {
080        f.setTitle(Bundle.getMessage("TitleTurnoutTable"));
081    }
082
083    /**
084     * {@inheritDoc}
085     */
086    @Override
087    protected String helpTarget() {
088        return "package.jmri.jmrit.beantable.TurnoutTable";
089    }
090
091    JmriJFrame addFrame = null;
092
093    JTextField hardwareAddressTextField = new JTextField(20);
094    // initially allow any 20 char string, updated to prefixBox selection by canAddRange()
095    JTextField userNameTextField = new JTextField(40);
096    ManagerComboBox<Turnout> prefixBox = new ManagerComboBox<>();
097    SpinnerNumberModel rangeSpinner = new SpinnerNumberModel(1, 1, 100, 1); // maximum 100 items
098    JSpinner numberToAddSpinner = new JSpinner(rangeSpinner);
099    JCheckBox rangeBox = new JCheckBox(Bundle.getMessage("AddRangeBox"));
100    String systemSelectionCombo = this.getClass().getName() + ".SystemSelected";
101    JButton addButton;
102    JLabel statusBarLabel = new JLabel(Bundle.getMessage("HardwareAddStatusEnter"), JLabel.LEADING);
103    jmri.UserPreferencesManager pref;
104    SystemNameValidator hardwareAddressValidator;
105
106    /**
107     * {@inheritDoc}
108     */
109    @Override
110    protected void addPressed(ActionEvent e) {
111        pref = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class);
112
113        if (addFrame == null) {
114            addFrame = new JmriJFrame(Bundle.getMessage("TitleAddTurnout"), false, true);
115            addFrame.addHelpMenu("package.jmri.jmrit.beantable.TurnoutAddEdit", true);
116            addFrame.getContentPane().setLayout(new BoxLayout(addFrame.getContentPane(), BoxLayout.Y_AXIS));
117
118            ActionListener cancelListener = this::cancelPressed;
119            // add rangeBox box turned on/off
120            ActionListener rangeListener = this::canAddRange;
121
122            /* We use the proxy manager in this instance so that we can deal with
123             duplicate usernames in multiple classes */
124            configureManagerComboBox(prefixBox, turnoutManager, TurnoutManager.class);
125            userNameTextField.setName("userNameTextField"); // NOI18N
126            prefixBox.setName("prefixBox"); // NOI18N
127            // set up validation, zero text = false
128            addButton = new JButton(Bundle.getMessage("ButtonCreate"));
129            addButton.addActionListener(this::createPressed);
130            // create panel
131
132            if (hardwareAddressValidator==null){
133                hardwareAddressValidator = new SystemNameValidator(hardwareAddressTextField, Objects.requireNonNull(prefixBox.getSelectedItem()), true);
134            } else {
135                hardwareAddressValidator.setManager(prefixBox.getSelectedItem());
136            }
137
138            addFrame.add(new AddNewHardwareDevicePanel(hardwareAddressTextField, hardwareAddressValidator, userNameTextField, prefixBox,
139                    numberToAddSpinner, rangeBox, addButton, cancelListener, rangeListener, statusBarLabel));
140            // tooltip for hardwareAddressTextField will be assigned next by canAddRange()
141            canAddRange(null);
142        }
143        hardwareAddressTextField.setName("hwAddressTextField"); // for GUI test NOI18N
144        addButton.setName("createButton"); // for GUI test NOI18N
145
146        addFrame.setEscapeKeyClosesWindow(true);
147        addFrame.getRootPane().setDefaultButton(addButton);
148
149        // reset statusBarLabel text
150        statusBarLabel.setText(Bundle.getMessage("HardwareAddStatusEnter"));
151        statusBarLabel.setForeground(Color.gray);
152
153        addFrame.pack();
154        addFrame.setVisible(true);
155    }
156
157    /**
158     * Add the content and make the appropriate selection to a combo box for a
159     * turnout's automation choices.
160     *
161     * @param t  turnout
162     * @param cb the JComboBox
163     */
164    public static void updateAutomationBox(Turnout t, JComboBox<String> cb) {
165        TurnoutOperation[] ops = InstanceManager.getDefault(TurnoutOperationManager.class).getTurnoutOperations();
166        cb.removeAllItems();
167        Vector<String> strings = new Vector<>(20);
168        Vector<String> defStrings = new Vector<>(20);
169        log.debug("opsCombo start {}", ops.length);
170        for (TurnoutOperation op : ops) {
171            if (log.isDebugEnabled()) {
172                log.debug("isDef {} mFMM {} isNonce {}", op.isDefinitive(), op.matchFeedbackMode(t.getFeedbackMode()), op.isNonce());
173            }
174            if (!op.isDefinitive() && op.matchFeedbackMode(t.getFeedbackMode()) && !op.isNonce()) {
175                strings.addElement(op.getName());
176            }
177        }
178        log.debug("opsCombo end");
179        for (TurnoutOperation op : ops) {
180            if (op.isDefinitive() && op.matchFeedbackMode(t.getFeedbackMode())) {
181                defStrings.addElement(op.getName());
182            }
183        }
184        java.util.Collections.sort(strings);
185        java.util.Collections.sort(defStrings);
186        strings.insertElementAt(Bundle.getMessage("TurnoutOperationOff"), 0);
187        strings.insertElementAt(Bundle.getMessage("TurnoutOperationDefault"), 1);
188        for (int i = 0; i < defStrings.size(); ++i) {
189            try {
190                strings.insertElementAt(defStrings.elementAt(i), i + 2);
191            } catch (java.lang.ArrayIndexOutOfBoundsException obe) {
192                // just catch it
193            }
194        }
195        for (int i = 0; i < strings.size(); ++i) {
196            cb.addItem(strings.elementAt(i));
197        }
198        if (t.getInhibitOperation()) {
199            cb.setSelectedIndex(0);
200        } else {
201            TurnoutOperation turnOp = t.getTurnoutOperation();
202            if (turnOp == null) {
203                cb.setSelectedIndex(1);
204            } else {
205                if (turnOp.isNonce()) {
206                    cb.setSelectedIndex(2);
207                } else {
208                    cb.setSelectedItem(turnOp.getName());
209                }
210            }
211        }
212        // Set custom renderer with tooltips
213        cb.setRenderer(new DefaultListCellRenderer() {
214            @Override
215            public Component getListCellRendererComponent(JList<?> list, Object value,
216                int index, boolean isSelected, boolean cellHasFocus) {
217                JLabel lbl =  (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
218                if (value != null) {
219                    lbl.setToolTipText(InstanceManager.getDefault(TurnoutOperationManager.class)
220                        .getTooltipForOperator(value.toString(), t));
221                }
222                return lbl;
223            }
224        });
225    }
226
227    /**
228     * Show a pane to configure closed and thrown turnout speed defaults.
229     *
230     * @param _who parent JFrame to center the pane on
231     */
232    protected void setDefaultSpeeds(JFrame _who) {
233        JComboBox<String> thrownCombo = new JComboBox<>((( TurnoutTableDataModel)m).speedListThrown);
234        JComboBox<String> closedCombo = new JComboBox<>((( TurnoutTableDataModel)m).speedListClosed);
235        thrownCombo.setEditable(true);
236        closedCombo.setEditable(true);
237
238        JComboBoxUtil.setupComboBoxMaxRows(thrownCombo);
239        JComboBoxUtil.setupComboBoxMaxRows(closedCombo);
240
241        JPanel thrown = new JPanel();
242        thrown.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ThrownSpeed"))));
243        thrown.add(thrownCombo);
244
245        JPanel closed = new JPanel();
246        closed.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ClosedSpeed"))));
247        closed.add(closedCombo);
248
249        thrownCombo.removeItem((( TurnoutTableDataModel)m).defaultThrownSpeedText);
250        closedCombo.removeItem((( TurnoutTableDataModel)m).defaultClosedSpeedText);
251
252        thrownCombo.setSelectedItem(turnoutManager.getDefaultThrownSpeed());
253        closedCombo.setSelectedItem(turnoutManager.getDefaultClosedSpeed());
254
255        // block of options above row of buttons; gleaned from Maintenance.makeDialog()
256        // can be accessed by Jemmy in GUI test
257        String title = Bundle.getMessage("TurnoutGlobalSpeedMessageTitle");
258        // build JPanel for comboboxes
259        JPanel speedspanel = new JPanel();
260        speedspanel.setLayout(new BoxLayout(speedspanel, BoxLayout.PAGE_AXIS));
261        speedspanel.add(new JLabel(Bundle.getMessage("TurnoutGlobalSpeedMessage")));
262        //default LEFT_ALIGNMENT
263        thrown.setAlignmentX(Component.LEFT_ALIGNMENT);
264        speedspanel.add(thrown);
265        closed.setAlignmentX(Component.LEFT_ALIGNMENT);
266        speedspanel.add(closed);
267
268        int retval = JmriJOptionPane.showConfirmDialog(_who,
269                speedspanel,
270                title,
271                JmriJOptionPane.OK_CANCEL_OPTION,
272                JmriJOptionPane.INFORMATION_MESSAGE);
273        log.debug("Retval = {}", retval);
274        if (retval != JmriJOptionPane.OK_OPTION) { // OK button not clicked
275            return;
276        }
277        String closedValue = (String) closedCombo.getSelectedItem();
278        String thrownValue = (String) thrownCombo.getSelectedItem();
279
280        // We will allow the turnout manager to handle checking whether the values have changed
281        try {
282            assert thrownValue != null;
283            turnoutManager.setDefaultThrownSpeed(thrownValue);
284        } catch (jmri.JmriException ex) {
285            JmriJOptionPane.showMessageDialog(null, ex.getMessage() + "\n" + thrownValue);
286        }
287
288        try {
289            assert closedValue != null;
290            turnoutManager.setDefaultClosedSpeed(closedValue);
291        } catch (jmri.JmriException ex) {
292            JmriJOptionPane.showMessageDialog(null, ex.getMessage() + "\n" + closedValue);
293        }
294    }
295
296    private final JCheckBox doAutomationBox = new JCheckBox(Bundle.getMessage("AutomaticRetry"));
297    private final TriStateJCheckBox showFeedbackBox = new TriStateJCheckBox(Bundle.getMessage("ShowFeedbackInfo"));
298    private final TriStateJCheckBox showLockBox = new TriStateJCheckBox(Bundle.getMessage("ShowLockInfo"));
299    private final TriStateJCheckBox showTurnoutSpeedBox = new TriStateJCheckBox(Bundle.getMessage("ShowTurnoutSpeedDetails"));
300    private final TriStateJCheckBox showStateForgetAndQueryBox = new TriStateJCheckBox(Bundle.getMessage("ShowStateForgetAndQuery"));
301
302    private void initCheckBoxes(){
303        doAutomationBox.setSelected(InstanceManager.getDefault(TurnoutOperationManager.class).getDoOperations());
304        doAutomationBox.setToolTipText(Bundle.getMessage("TurnoutDoAutomationBoxTooltip"));
305        doAutomationBox.addActionListener(e -> InstanceManager.getDefault(TurnoutOperationManager.class).setDoOperations(doAutomationBox.isSelected()));
306
307        showFeedbackBox.setToolTipText(Bundle.getMessage("TurnoutFeedbackToolTip"));
308        showLockBox.setToolTipText(Bundle.getMessage("TurnoutLockToolTip"));
309        showTurnoutSpeedBox.setToolTipText(Bundle.getMessage("TurnoutSpeedToolTip"));
310        showStateForgetAndQueryBox.setToolTipText(Bundle.getMessage("StateForgetAndQueryBoxToolTip"));
311    }
312
313    @Override
314    protected void configureTable(JTable table){
315        super.configureTable(table);
316        showStateForgetAndQueryBox.addActionListener(e ->
317            ((TurnoutTableDataModel) m).showStateForgetAndQueryChanged(showStateForgetAndQueryBox.isSelected(),table));
318        showTurnoutSpeedBox.addActionListener(e ->
319            ((TurnoutTableDataModel) m).showTurnoutSpeedChanged(showTurnoutSpeedBox.isSelected(),table));
320        showFeedbackBox.addActionListener(e ->
321            ((TurnoutTableDataModel) m).showFeedbackChanged(showFeedbackBox.isSelected(), table));
322        showLockBox.addActionListener(e ->
323            ((TurnoutTableDataModel) m).showLockChanged(showLockBox.isSelected(),table));
324    }
325
326    /**
327     * Add the check boxes to show/hide extra columns to the Turnout table
328     * frame.
329     * <p>
330     * Keep contents synchronized with
331     * {@link #addToPanel(AbstractTableTabAction)}
332     *
333     * @param f a Turnout table frame
334     */
335    @Override
336    public void addToFrame(BeanTableFrame<Turnout> f) {
337        initCheckBoxes();
338        f.addToBottomBox(doAutomationBox, this.getClass().getName());
339        f.addToBottomBox(showFeedbackBox, this.getClass().getName());
340        f.addToBottomBox(showLockBox, this.getClass().getName());
341        f.addToBottomBox(showTurnoutSpeedBox, this.getClass().getName());
342        f.addToBottomBox(showStateForgetAndQueryBox, this.getClass().getName());
343    }
344
345    /**
346     * Place the check boxes to show/hide extra columns to the tabbed Turnout
347     * table panel.
348     * <p>
349     * Keep contents synchronized with {@link #addToFrame(BeanTableFrame)}
350     *
351     * @param f a Turnout table action
352     */
353    @Override
354    public void addToPanel(AbstractTableTabAction<Turnout> f) {
355        String connectionName = turnoutManager.getMemo().getUserName();
356        if (turnoutManager.getClass().getName().contains("ProxyTurnoutManager")) {
357            connectionName = "All"; // NOI18N
358        }
359        initCheckBoxes();
360        f.addToBottomBox(doAutomationBox, connectionName);
361        f.addToBottomBox(showFeedbackBox, connectionName);
362        f.addToBottomBox(showLockBox, connectionName);
363        f.addToBottomBox(showTurnoutSpeedBox, connectionName);
364        f.addToBottomBox(showStateForgetAndQueryBox, connectionName);
365    }
366
367    /**
368     * Override to update column select checkboxes.
369     * {@inheritDoc}
370     */
371    @Override
372    protected void columnsVisibleUpdated(boolean[] colsVisible){
373        log.debug("columns updated {}",colsVisible);
374        showFeedbackBox.setState(new boolean[]{
375            colsVisible[TurnoutTableDataModel.KNOWNCOL],
376            colsVisible[TurnoutTableDataModel.MODECOL],
377            colsVisible[TurnoutTableDataModel.SENSOR1COL],
378            colsVisible[TurnoutTableDataModel.SENSOR2COL],
379            colsVisible[TurnoutTableDataModel.OPSONOFFCOL],
380            colsVisible[TurnoutTableDataModel.OPSEDITCOL]});
381
382        showLockBox.setState(new boolean[]{
383            colsVisible[TurnoutTableDataModel.LOCKDECCOL],
384            colsVisible[TurnoutTableDataModel.LOCKOPRCOL]});
385
386        showTurnoutSpeedBox.setState(new boolean[]{
387            colsVisible[TurnoutTableDataModel.STRAIGHTCOL],
388            colsVisible[TurnoutTableDataModel.DIVERGCOL]});
389
390        showStateForgetAndQueryBox.setState(new boolean[]{
391            colsVisible[TurnoutTableDataModel.FORGETCOL],
392            colsVisible[TurnoutTableDataModel.QUERYCOL]});
393
394    }
395
396    /**
397     * Insert table specific Automation and Speeds menus. Account for the Window and Help
398     * menus, which are already added to the menu bar as part of the creation of
399     * the JFrame, by adding the Automation menu 2 places earlier unless the
400     * table is part of the ListedTableFrame, that adds the Help menu later on.
401     *
402     * @param f the JFrame of this table
403     */
404    @Override
405    public void setMenuBar(BeanTableFrame<Turnout> f) {
406        final jmri.util.JmriJFrame finalF = f;   // needed for anonymous ActionListener class
407        JMenuBar menuBar = f.getJMenuBar();
408        // check for menu
409        boolean menuAbsent = true;
410        for (int i = 0; i < menuBar.getMenuCount(); ++i) {
411            String name = menuBar.getMenu(i).getAccessibleContext().getAccessibleName();
412            if (name.equals(Bundle.getMessage("TurnoutAutomationMenu"))) {
413                // using first menu for check, should be identical to next JMenu Bundle
414                menuAbsent = false;
415                break;
416            }
417        }
418        if (menuAbsent) { // create it
419            int pos = menuBar.getMenuCount() - 1; // count the number of menus to insert the TableMenu before 'Window' and 'Help'
420            int offset = 1;
421            log.debug("setMenuBar number of menu items = {}", pos);
422            for (int i = 0; i <= pos; i++) {
423                if (menuBar.getComponent(i) instanceof JMenu) {
424                    if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) {
425                        offset = -1; // correct for use as part of ListedTableAction where the Help Menu is not yet present
426                    }
427                }
428            }
429            JMenu opsMenu = new JMenu(Bundle.getMessage("TurnoutAutomationMenu"));
430            JMenuItem item = new JMenuItem(Bundle.getMessage("TurnoutAutomationMenuItemEdit"));
431            opsMenu.add(item);
432            item.addActionListener(e -> new TurnoutOperationFrame(finalF));
433            menuBar.add(opsMenu, pos + offset);
434
435            JMenu speedMenu = new JMenu(Bundle.getMessage("SpeedsMenu"));
436            item = new JMenuItem(Bundle.getMessage("SpeedsMenuItemDefaults"));
437            speedMenu.add(item);
438            item.addActionListener(e -> setDefaultSpeeds(finalF));
439            menuBar.add(speedMenu, pos + offset + 1); // add this menu to the right of the previous
440        }
441    }
442
443    void cancelPressed(ActionEvent e) {
444        removePrefixBoxListener(prefixBox);
445        addFrame.setVisible(false);
446        addFrame.dispose();
447        addFrame = null;
448    }
449
450    /**
451     * Respond to Create new item button pressed on Add Turnout pane.
452     *
453     * @param e the click event
454     */
455    void createPressed(ActionEvent e) {
456
457        int numberOfTurnouts = 1;
458
459        if (rangeBox.isSelected()) {
460            numberOfTurnouts = (Integer) numberToAddSpinner.getValue();
461        }
462        if (numberOfTurnouts >= 65 // limited by JSpinnerModel to 100
463            && JmriJOptionPane.showConfirmDialog(addFrame,
464                Bundle.getMessage("WarnExcessBeans", Bundle.getMessage("Turnouts"), numberOfTurnouts),
465                Bundle.getMessage("WarningTitle"),
466                JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION ) {
467            return;
468        }
469
470        String sName;
471        String prefix = Objects.requireNonNull(prefixBox.getSelectedItem()).getSystemPrefix();
472
473        String curAddress = hardwareAddressTextField.getText();
474        // initial check for empty entry
475        if (curAddress.length() < 1) {
476            statusBarLabel.setText(Bundle.getMessage("WarningEmptyHardwareAddress"));
477            statusBarLabel.setForeground(Color.red);
478            hardwareAddressTextField.setBackground(Color.red);
479            return;
480        } else {
481            hardwareAddressTextField.setBackground(Color.white);
482        }
483
484        String uName = userNameTextField.getText();
485        if (uName.isEmpty()) {
486            uName = null;
487        }
488
489        // Add some entry pattern checking, before assembling sName and handing it to the TurnoutManager
490        StringBuilder statusMessage = new StringBuilder(Bundle.getMessage("ItemCreateFeedback", Bundle.getMessage("BeanNameTurnout")));
491
492        // Compose the proposed system name from parts:
493        sName = prefix + InstanceManager.getDefault(TurnoutManager.class).typeLetter() + curAddress;
494
495        int iType = 0;
496        int iNum = 1;
497        boolean useLastBit = false;
498        boolean useLastType = false;
499
500        for (int x = 0; x < numberOfTurnouts; x++) {
501
502            Turnout t;
503
504            // test for a Light by the same hardware address (number):
505            // String testSN = prefix + "L" + curAddress;  <========= from sName instead
506            StringBuilder sb = new StringBuilder(sName);
507            int prefixLength = Manager.getSystemPrefixLength(sName);
508            sb.replace(prefixLength, prefixLength+1, "L");
509            String testSN = new String(sb);
510            log.trace("{} maps to {}", sName, testSN);
511
512            jmri.Light testLight = InstanceManager.lightManagerInstance().
513                    getBySystemName(testSN);
514            if (testLight != null) {
515                // Address (number part) is already used as a Light
516                log.warn("Requested Turnout {} uses same address as Light {}", sName, testSN);
517                if (!noWarn) {
518                    int selectedValue = JmriJOptionPane.showOptionDialog(addFrame,
519                            Bundle.getMessage("TurnoutWarn1", sName, testSN)
520                            + ".\n" + Bundle.getMessage("TurnoutWarn3"), Bundle.getMessage("WarningTitle"),
521                            JmriJOptionPane.YES_NO_CANCEL_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
522                            new Object[]{Bundle.getMessage("ButtonYes"), Bundle.getMessage("ButtonNo"),
523                                Bundle.getMessage("ButtonYesPlus")}, Bundle.getMessage("ButtonNo")); // default choice = No
524                    if (selectedValue == 1) { // ButtonNo
525                        // Show error message in statusBarLabel
526                        statusBarLabel.setText(Bundle.getMessage("WarningOverlappingAddress", sName));
527                        statusBarLabel.setForeground(Color.gray);
528                        return;   // return without creating if "No" response
529                    }
530                    if (selectedValue == 2) { // ButtonYesPlus
531                        // Suppress future warnings, and continue
532                        noWarn = true;
533                    }
534                }
535            }
536
537            // Ask about two bit turnout control if appropriate
538            if (!useLastBit) {
539                iNum = InstanceManager.getDefault(TurnoutManager.class).askNumControlBits(sName);
540                if ((InstanceManager.getDefault(TurnoutManager.class).isNumControlBitsSupported(sName)) && (rangeBox.isSelected())) {
541                    // Add a pop up here asking if the user wishes to use the same value for all
542                    if (JmriJOptionPane.showConfirmDialog(addFrame,
543                            Bundle.getMessage("UseForAllTurnouts"), Bundle.getMessage("UseSetting"),
544                            JmriJOptionPane.YES_NO_OPTION) == 0) {
545                        useLastBit = true;
546                    }
547                } else {
548                    // as isNumControlBits is not supported, we will always use the same value.
549                    useLastBit = true;
550                }
551            }
552            if (iNum == 0) {
553                // User specified more bits, but bits are not available - return without creating
554                // Display message in statusBarLabel
555                statusBarLabel.setText(Bundle.getMessage("WarningBitsNotSupported", sName));
556                statusBarLabel.setForeground(Color.red);
557                return;
558            } else {
559
560                // Create the new turnout
561                try {
562                    t = InstanceManager.getDefault(TurnoutManager.class).provideTurnout(sName);
563                } catch (IllegalArgumentException ex) {
564                    // user input no good
565                    handleCreateException(ex, sName); // displays message dialog to the user
566                    return; // without creating
567                }
568                if ((uName != null) && !uName.isEmpty()) {
569                    if (InstanceManager.getDefault(TurnoutManager.class).getByUserName(uName) == null) {
570                        t.setUserName(uName);
571                    } else if (!pref.getPreferenceState(getClassName(), "duplicateUserName")) {
572                        InstanceManager.getDefault(jmri.UserPreferencesManager.class).
573                                showErrorMessage(Bundle.getMessage("ErrorTitle"),
574                                        Bundle.getMessage("ErrorDuplicateUserName", uName),
575                                        getClassName(), "duplicateUserName", false, true);
576                    }
577                }
578
579                t.setNumberControlBits(iNum);
580                // Ask about the type of turnout control if appropriate
581                if (!useLastType) {
582                    iType = InstanceManager.getDefault(TurnoutManager.class).askControlType(sName);
583                    if ((InstanceManager.getDefault(TurnoutManager.class).isControlTypeSupported(sName)) && (rangeBox.isSelected())) {
584                        if (JmriJOptionPane.showConfirmDialog(addFrame,
585                                Bundle.getMessage("UseForAllTurnouts"), Bundle.getMessage("UseSetting"),
586                                JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION ) // Add a pop up here asking if the uName wishes to use the same value for all
587                        {
588                            useLastType = true;
589                        }
590                    }
591                }
592                t.setControlType(iType);
593
594                // add first and last names to statusMessage uName feedback string
595                if (x == 0 || x == numberOfTurnouts - 1) {
596                    statusMessage.append(" ").append(sName).append(" (").append(uName).append(")");
597                }
598                if (x == numberOfTurnouts - 2) {
599                    statusMessage.append(" ").append(Bundle.getMessage("ItemCreateUpTo")).append(" ");
600                }
601                // only mention first and last of rangeBox added
602            }
603
604            // except on last pass
605            if (x < numberOfTurnouts-1) {
606                // bump system name
607                try {
608                    sName = InstanceManager.getDefault(TurnoutManager.class).getNextValidSystemName(t);
609                } catch (jmri.JmriException ex) {
610                    displayHwError(curAddress, ex);
611                    // directly add to statusBarLabel (but never called?)
612                    statusBarLabel.setText(Bundle.getMessage("ErrorConvertHW", curAddress));
613                    statusBarLabel.setForeground(Color.red);
614                    return;
615                }
616
617                // bump user name
618                if ((uName != null) && !uName.isEmpty()) {
619                    uName = nextName(uName);
620                }
621            }
622            // end of for loop creating rangeBox of Turnouts
623        }
624
625        // provide successfeedback to uName
626        statusBarLabel.setText(statusMessage.toString());
627        statusBarLabel.setForeground(Color.gray);
628
629        pref.setComboBoxLastSelection(systemSelectionCombo, prefixBox.getSelectedItem().getMemo().getUserName()); // store user pref
630        removePrefixBoxListener(prefixBox);
631        addFrame.setVisible(false);
632        addFrame.dispose();
633        addFrame = null;
634    }
635
636    private String addEntryToolTip;
637
638    /**
639     * Activate Add a rangeBox option if manager accepts adding more than 1
640     * Turnout and set a manager specific tooltip on the AddNewHardwareDevice
641     * pane.
642     */
643    private void canAddRange(ActionEvent e) {
644        rangeBox.setEnabled(false);
645        log.debug("T Add box disabled");
646        rangeBox.setSelected(false);
647        if (prefixBox.getSelectedIndex() == -1) {
648            prefixBox.setSelectedIndex(0);
649        }
650        Manager<Turnout> manager = prefixBox.getSelectedItem();
651        assert manager != null;
652        String systemPrefix = manager.getSystemPrefix();
653        rangeBox.setEnabled(((TurnoutManager) manager).allowMultipleAdditions(systemPrefix));
654        addEntryToolTip = manager.getEntryToolTip();
655        // show sysName (HW address) field tooltip in the Add Turnout pane that matches system connection selected from combobox
656        hardwareAddressTextField.setToolTipText(
657                Bundle.getMessage("AddEntryToolTipLine1",
658                        manager.getMemo().getUserName(),
659                        Bundle.getMessage("Turnouts"),
660                        addEntryToolTip));
661        hardwareAddressValidator.setToolTipText(hardwareAddressTextField.getToolTipText());
662        hardwareAddressValidator.verify(hardwareAddressTextField);
663    }
664
665    void handleCreateException(Exception ex, String sysName) {
666        String err = Bundle.getMessage("ErrorBeanCreateFailed",
667            InstanceManager.getDefault(TurnoutManager.class).getBeanTypeHandled(),sysName);
668        if (ex.getMessage() != null) {
669            statusBarLabel.setText(ex.getLocalizedMessage());
670            JmriJOptionPane.showMessageDialog(addFrame,
671                    ex.getLocalizedMessage(),
672                    err,
673                    JmriJOptionPane.ERROR_MESSAGE);
674        } else {
675            statusBarLabel.setText(Bundle.getMessage("WarningInvalidRange"));
676            JmriJOptionPane.showMessageDialog(addFrame,
677                    err + "\n" + Bundle.getMessage("ErrorAddFailedCheck"),
678                    err,
679                    JmriJOptionPane.ERROR_MESSAGE);
680        }
681        statusBarLabel.setForeground(Color.red);
682    }
683
684    private boolean noWarn = false;
685
686    /**
687     * {@inheritDoc}
688     */
689    @Override
690    protected String getClassName() {
691        return TurnoutTableAction.class.getName();
692    }
693
694    /**
695     * {@inheritDoc}
696     */
697    @Override
698    public void setMessagePreferencesDetails() {
699        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class)
700                .setPreferenceItemDetails(getClassName(), "duplicateUserName", Bundle.getMessage("DuplicateUserNameWarn"));
701        super.setMessagePreferencesDetails();
702    }
703
704    /**
705     * {@inheritDoc}
706     */
707    @Override
708    public String getClassDescription() {
709        return Bundle.getMessage("TitleTurnoutTable");
710    }
711
712    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TurnoutTableAction.class);
713
714}