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                    // show the type the turnout's own copy was made from, else the first type as before
207                    TurnoutOperation definitive = turnOp.getDefinitive();
208                    int index = (definitive != null) ? strings.indexOf(definitive.getName()) : -1;
209                    cb.setSelectedIndex(index > 1 ? index : 2);
210                } else {
211                    cb.setSelectedItem(turnOp.getName());
212                }
213            }
214        }
215        // Set custom renderer with tooltips
216        cb.setRenderer(new DefaultListCellRenderer() {
217            @Override
218            public Component getListCellRendererComponent(JList<?> list, Object value,
219                int index, boolean isSelected, boolean cellHasFocus) {
220                JLabel lbl =  (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
221                if (value != null) {
222                    lbl.setToolTipText(InstanceManager.getDefault(TurnoutOperationManager.class)
223                        .getTooltipForOperator(value.toString(), t));
224                }
225                return lbl;
226            }
227        });
228    }
229
230    /**
231     * Show a pane to configure closed and thrown turnout speed defaults.
232     *
233     * @param _who parent JFrame to center the pane on
234     */
235    protected void setDefaultSpeeds(JFrame _who) {
236        JComboBox<String> thrownCombo = new JComboBox<>((( TurnoutTableDataModel)m).speedListThrown);
237        JComboBox<String> closedCombo = new JComboBox<>((( TurnoutTableDataModel)m).speedListClosed);
238        thrownCombo.setEditable(true);
239        closedCombo.setEditable(true);
240
241        JComboBoxUtil.setupComboBoxMaxRows(thrownCombo);
242        JComboBoxUtil.setupComboBoxMaxRows(closedCombo);
243
244        JPanel thrown = new JPanel();
245        thrown.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ThrownSpeed"))));
246        thrown.add(thrownCombo);
247
248        JPanel closed = new JPanel();
249        closed.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ClosedSpeed"))));
250        closed.add(closedCombo);
251
252        thrownCombo.removeItem((( TurnoutTableDataModel)m).defaultThrownSpeedText);
253        closedCombo.removeItem((( TurnoutTableDataModel)m).defaultClosedSpeedText);
254
255        thrownCombo.setSelectedItem(turnoutManager.getDefaultThrownSpeed());
256        closedCombo.setSelectedItem(turnoutManager.getDefaultClosedSpeed());
257
258        // block of options above row of buttons; gleaned from Maintenance.makeDialog()
259        // can be accessed by Jemmy in GUI test
260        String title = Bundle.getMessage("TurnoutGlobalSpeedMessageTitle");
261        // build JPanel for comboboxes
262        JPanel speedspanel = new JPanel();
263        speedspanel.setLayout(new BoxLayout(speedspanel, BoxLayout.PAGE_AXIS));
264        speedspanel.add(new JLabel(Bundle.getMessage("TurnoutGlobalSpeedMessage")));
265        //default LEFT_ALIGNMENT
266        thrown.setAlignmentX(Component.LEFT_ALIGNMENT);
267        speedspanel.add(thrown);
268        closed.setAlignmentX(Component.LEFT_ALIGNMENT);
269        speedspanel.add(closed);
270
271        int retval = JmriJOptionPane.showConfirmDialog(_who,
272                speedspanel,
273                title,
274                JmriJOptionPane.OK_CANCEL_OPTION,
275                JmriJOptionPane.INFORMATION_MESSAGE);
276        log.debug("Retval = {}", retval);
277        if (retval != JmriJOptionPane.OK_OPTION) { // OK button not clicked
278            return;
279        }
280        String closedValue = (String) closedCombo.getSelectedItem();
281        String thrownValue = (String) thrownCombo.getSelectedItem();
282
283        // We will allow the turnout manager to handle checking whether the values have changed
284        try {
285            assert thrownValue != null;
286            turnoutManager.setDefaultThrownSpeed(thrownValue);
287        } catch (jmri.JmriException ex) {
288            JmriJOptionPane.showMessageDialog(null, ex.getMessage() + "\n" + thrownValue);
289        }
290
291        try {
292            assert closedValue != null;
293            turnoutManager.setDefaultClosedSpeed(closedValue);
294        } catch (jmri.JmriException ex) {
295            JmriJOptionPane.showMessageDialog(null, ex.getMessage() + "\n" + closedValue);
296        }
297    }
298
299    private final JCheckBox doAutomationBox = new JCheckBox(Bundle.getMessage("AutomaticRetry"));
300    private final TriStateJCheckBox showFeedbackBox = new TriStateJCheckBox(Bundle.getMessage("ShowFeedbackInfo"));
301    private final TriStateJCheckBox showLockBox = new TriStateJCheckBox(Bundle.getMessage("ShowLockInfo"));
302    private final TriStateJCheckBox showTurnoutSpeedBox = new TriStateJCheckBox(Bundle.getMessage("ShowTurnoutSpeedDetails"));
303    private final TriStateJCheckBox showStateForgetAndQueryBox = new TriStateJCheckBox(Bundle.getMessage("ShowStateForgetAndQuery"));
304
305    private void initCheckBoxes(){
306        doAutomationBox.setSelected(InstanceManager.getDefault(TurnoutOperationManager.class).getDoOperations());
307        doAutomationBox.setToolTipText(Bundle.getMessage("TurnoutDoAutomationBoxTooltip"));
308        doAutomationBox.addActionListener(e -> InstanceManager.getDefault(TurnoutOperationManager.class).setDoOperations(doAutomationBox.isSelected()));
309
310        showFeedbackBox.setToolTipText(Bundle.getMessage("TurnoutFeedbackToolTip"));
311        showLockBox.setToolTipText(Bundle.getMessage("TurnoutLockToolTip"));
312        showTurnoutSpeedBox.setToolTipText(Bundle.getMessage("TurnoutSpeedToolTip"));
313        showStateForgetAndQueryBox.setToolTipText(Bundle.getMessage("StateForgetAndQueryBoxToolTip"));
314    }
315
316    @Override
317    protected void configureTable(JTable table){
318        super.configureTable(table);
319        showStateForgetAndQueryBox.addActionListener(e ->
320            ((TurnoutTableDataModel) m).showStateForgetAndQueryChanged(showStateForgetAndQueryBox.isSelected(),table));
321        showTurnoutSpeedBox.addActionListener(e ->
322            ((TurnoutTableDataModel) m).showTurnoutSpeedChanged(showTurnoutSpeedBox.isSelected(),table));
323        showFeedbackBox.addActionListener(e ->
324            ((TurnoutTableDataModel) m).showFeedbackChanged(showFeedbackBox.isSelected(), table));
325        showLockBox.addActionListener(e ->
326            ((TurnoutTableDataModel) m).showLockChanged(showLockBox.isSelected(),table));
327    }
328
329    /**
330     * Add the check boxes to show/hide extra columns to the Turnout table
331     * frame.
332     * <p>
333     * Keep contents synchronized with
334     * {@link #addToPanel(AbstractTableTabAction)}
335     *
336     * @param f a Turnout table frame
337     */
338    @Override
339    public void addToFrame(BeanTableFrame<Turnout> f) {
340        initCheckBoxes();
341        f.addToBottomBox(doAutomationBox, this.getClass().getName());
342        f.addToBottomBox(showFeedbackBox, this.getClass().getName());
343        f.addToBottomBox(showLockBox, this.getClass().getName());
344        f.addToBottomBox(showTurnoutSpeedBox, this.getClass().getName());
345        f.addToBottomBox(showStateForgetAndQueryBox, this.getClass().getName());
346    }
347
348    /**
349     * Place the check boxes to show/hide extra columns to the tabbed Turnout
350     * table panel.
351     * <p>
352     * Keep contents synchronized with {@link #addToFrame(BeanTableFrame)}
353     *
354     * @param f a Turnout table action
355     */
356    @Override
357    public void addToPanel(AbstractTableTabAction<Turnout> f) {
358        String connectionName = turnoutManager.getMemo().getUserName();
359        if (turnoutManager.getClass().getName().contains("ProxyTurnoutManager")) {
360            connectionName = "All"; // NOI18N
361        }
362        initCheckBoxes();
363        f.addToBottomBox(doAutomationBox, connectionName);
364        f.addToBottomBox(showFeedbackBox, connectionName);
365        f.addToBottomBox(showLockBox, connectionName);
366        f.addToBottomBox(showTurnoutSpeedBox, connectionName);
367        f.addToBottomBox(showStateForgetAndQueryBox, connectionName);
368    }
369
370    /**
371     * Override to update column select checkboxes.
372     * {@inheritDoc}
373     */
374    @Override
375    protected void columnsVisibleUpdated(boolean[] colsVisible){
376        log.debug("columns updated {}",colsVisible);
377        showFeedbackBox.setState(new boolean[]{
378            colsVisible[TurnoutTableDataModel.KNOWNCOL],
379            colsVisible[TurnoutTableDataModel.MODECOL],
380            colsVisible[TurnoutTableDataModel.SENSOR1COL],
381            colsVisible[TurnoutTableDataModel.SENSOR2COL],
382            colsVisible[TurnoutTableDataModel.OPSONOFFCOL],
383            colsVisible[TurnoutTableDataModel.OPSEDITCOL]});
384
385        showLockBox.setState(new boolean[]{
386            colsVisible[TurnoutTableDataModel.LOCKDECCOL],
387            colsVisible[TurnoutTableDataModel.LOCKOPRCOL]});
388
389        showTurnoutSpeedBox.setState(new boolean[]{
390            colsVisible[TurnoutTableDataModel.STRAIGHTCOL],
391            colsVisible[TurnoutTableDataModel.DIVERGCOL]});
392
393        showStateForgetAndQueryBox.setState(new boolean[]{
394            colsVisible[TurnoutTableDataModel.FORGETCOL],
395            colsVisible[TurnoutTableDataModel.QUERYCOL]});
396
397    }
398
399    /**
400     * Insert table specific Automation and Speeds menus. Account for the Window and Help
401     * menus, which are already added to the menu bar as part of the creation of
402     * the JFrame, by adding the Automation menu 2 places earlier unless the
403     * table is part of the ListedTableFrame, that adds the Help menu later on.
404     *
405     * @param f the JFrame of this table
406     */
407    @Override
408    public void setMenuBar(BeanTableFrame<Turnout> f) {
409        final jmri.util.JmriJFrame finalF = f;   // needed for anonymous ActionListener class
410        JMenuBar menuBar = f.getJMenuBar();
411        // check for menu
412        boolean menuAbsent = true;
413        for (int i = 0; i < menuBar.getMenuCount(); ++i) {
414            String name = menuBar.getMenu(i).getAccessibleContext().getAccessibleName();
415            if (name.equals(Bundle.getMessage("TurnoutAutomationMenu"))) {
416                // using first menu for check, should be identical to next JMenu Bundle
417                menuAbsent = false;
418                break;
419            }
420        }
421        if (menuAbsent) { // create it
422            int pos = menuBar.getMenuCount() - 1; // count the number of menus to insert the TableMenu before 'Window' and 'Help'
423            int offset = 1;
424            log.debug("setMenuBar number of menu items = {}", pos);
425            for (int i = 0; i <= pos; i++) {
426                if (menuBar.getComponent(i) instanceof JMenu) {
427                    if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) {
428                        offset = -1; // correct for use as part of ListedTableAction where the Help Menu is not yet present
429                    }
430                }
431            }
432            JMenu opsMenu = new JMenu(Bundle.getMessage("TurnoutAutomationMenu"));
433            JMenuItem item = new JMenuItem(Bundle.getMessage("TurnoutAutomationMenuItemEdit"));
434            opsMenu.add(item);
435            item.addActionListener(e -> new TurnoutOperationFrame(finalF));
436            menuBar.add(opsMenu, pos + offset);
437
438            JMenu speedMenu = new JMenu(Bundle.getMessage("SpeedsMenu"));
439            item = new JMenuItem(Bundle.getMessage("SpeedsMenuItemDefaults"));
440            speedMenu.add(item);
441            item.addActionListener(e -> setDefaultSpeeds(finalF));
442            menuBar.add(speedMenu, pos + offset + 1); // add this menu to the right of the previous
443        }
444    }
445
446    void cancelPressed(ActionEvent e) {
447        removePrefixBoxListener(prefixBox);
448        addFrame.setVisible(false);
449        addFrame.dispose();
450        addFrame = null;
451    }
452
453    /**
454     * Respond to Create new item button pressed on Add Turnout pane.
455     *
456     * @param e the click event
457     */
458    void createPressed(ActionEvent e) {
459
460        int numberOfTurnouts = 1;
461
462        if (rangeBox.isSelected()) {
463            numberOfTurnouts = (Integer) numberToAddSpinner.getValue();
464        }
465        if (numberOfTurnouts >= 65 // limited by JSpinnerModel to 100
466            && JmriJOptionPane.showConfirmDialog(addFrame,
467                Bundle.getMessage("WarnExcessBeans", Bundle.getMessage("Turnouts"), numberOfTurnouts),
468                Bundle.getMessage("WarningTitle"),
469                JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION ) {
470            return;
471        }
472
473        String sName;
474        String prefix = Objects.requireNonNull(prefixBox.getSelectedItem()).getSystemPrefix();
475
476        String curAddress = hardwareAddressTextField.getText();
477        // initial check for empty entry
478        if (curAddress.length() < 1) {
479            statusBarLabel.setText(Bundle.getMessage("WarningEmptyHardwareAddress"));
480            statusBarLabel.setForeground(Color.red);
481            hardwareAddressTextField.setBackground(Color.red);
482            return;
483        } else {
484            hardwareAddressTextField.setBackground(Color.white);
485        }
486
487        String uName = userNameTextField.getText();
488        if (uName.isEmpty()) {
489            uName = null;
490        }
491
492        // Add some entry pattern checking, before assembling sName and handing it to the TurnoutManager
493        StringBuilder statusMessage = new StringBuilder(Bundle.getMessage("ItemCreateFeedback", Bundle.getMessage("BeanNameTurnout")));
494
495        // Compose the proposed system name from parts:
496        sName = prefix + InstanceManager.getDefault(TurnoutManager.class).typeLetter() + curAddress;
497
498        int iType = 0;
499        int iNum = 1;
500        boolean useLastBit = false;
501        boolean useLastType = false;
502
503        for (int x = 0; x < numberOfTurnouts; x++) {
504
505            Turnout t;
506
507            // test for a Light by the same hardware address (number):
508            // String testSN = prefix + "L" + curAddress;  <========= from sName instead
509            StringBuilder sb = new StringBuilder(sName);
510            int prefixLength = Manager.getSystemPrefixLength(sName);
511            sb.replace(prefixLength, prefixLength+1, "L");
512            String testSN = new String(sb);
513            log.trace("{} maps to {}", sName, testSN);
514
515            jmri.Light testLight = InstanceManager.lightManagerInstance().
516                    getBySystemName(testSN);
517            if (testLight != null) {
518                // Address (number part) is already used as a Light
519                log.warn("Requested Turnout {} uses same address as Light {}", sName, testSN);
520                if (!noWarn) {
521                    int selectedValue = JmriJOptionPane.showOptionDialog(addFrame,
522                            Bundle.getMessage("TurnoutWarn1", sName, testSN)
523                            + ".\n" + Bundle.getMessage("TurnoutWarn3"), Bundle.getMessage("WarningTitle"),
524                            JmriJOptionPane.YES_NO_CANCEL_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
525                            new Object[]{Bundle.getMessage("ButtonYes"), Bundle.getMessage("ButtonNo"),
526                                Bundle.getMessage("ButtonYesPlus")}, Bundle.getMessage("ButtonNo")); // default choice = No
527                    if (selectedValue == 1) { // ButtonNo
528                        // Show error message in statusBarLabel
529                        statusBarLabel.setText(Bundle.getMessage("WarningOverlappingAddress", sName));
530                        statusBarLabel.setForeground(Color.gray);
531                        return;   // return without creating if "No" response
532                    }
533                    if (selectedValue == 2) { // ButtonYesPlus
534                        // Suppress future warnings, and continue
535                        noWarn = true;
536                    }
537                }
538            }
539
540            // Ask about two bit turnout control if appropriate
541            if (!useLastBit) {
542                iNum = InstanceManager.getDefault(TurnoutManager.class).askNumControlBits(sName);
543                if ((InstanceManager.getDefault(TurnoutManager.class).isNumControlBitsSupported(sName)) && (rangeBox.isSelected())) {
544                    // Add a pop up here asking if the user wishes to use the same value for all
545                    if (JmriJOptionPane.showConfirmDialog(addFrame,
546                            Bundle.getMessage("UseForAllTurnouts"), Bundle.getMessage("UseSetting"),
547                            JmriJOptionPane.YES_NO_OPTION) == 0) {
548                        useLastBit = true;
549                    }
550                } else {
551                    // as isNumControlBits is not supported, we will always use the same value.
552                    useLastBit = true;
553                }
554            }
555            if (iNum == 0) {
556                // User specified more bits, but bits are not available - return without creating
557                // Display message in statusBarLabel
558                statusBarLabel.setText(Bundle.getMessage("WarningBitsNotSupported", sName));
559                statusBarLabel.setForeground(Color.red);
560                return;
561            } else {
562
563                // Create the new turnout
564                try {
565                    t = InstanceManager.getDefault(TurnoutManager.class).provideTurnout(sName);
566                } catch (IllegalArgumentException ex) {
567                    // user input no good
568                    handleCreateException(ex, sName); // displays message dialog to the user
569                    return; // without creating
570                }
571                if ((uName != null) && !uName.isEmpty()) {
572                    if (InstanceManager.getDefault(TurnoutManager.class).getByUserName(uName) == null) {
573                        t.setUserName(uName);
574                    } else if (!pref.getPreferenceState(getClassName(), "duplicateUserName")) {
575                        InstanceManager.getDefault(jmri.UserPreferencesManager.class).
576                                showErrorMessage(Bundle.getMessage("ErrorTitle"),
577                                        Bundle.getMessage("ErrorDuplicateUserName", uName),
578                                        getClassName(), "duplicateUserName", false, true);
579                    }
580                }
581
582                t.setNumberControlBits(iNum);
583                // Ask about the type of turnout control if appropriate
584                if (!useLastType) {
585                    iType = InstanceManager.getDefault(TurnoutManager.class).askControlType(sName);
586                    if ((InstanceManager.getDefault(TurnoutManager.class).isControlTypeSupported(sName)) && (rangeBox.isSelected())) {
587                        if (JmriJOptionPane.showConfirmDialog(addFrame,
588                                Bundle.getMessage("UseForAllTurnouts"), Bundle.getMessage("UseSetting"),
589                                JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION ) // Add a pop up here asking if the uName wishes to use the same value for all
590                        {
591                            useLastType = true;
592                        }
593                    }
594                }
595                t.setControlType(iType);
596
597                // add first and last names to statusMessage uName feedback string
598                if (x == 0 || x == numberOfTurnouts - 1) {
599                    statusMessage.append(" ").append(sName).append(" (").append(uName).append(")");
600                }
601                if (x == numberOfTurnouts - 2) {
602                    statusMessage.append(" ").append(Bundle.getMessage("ItemCreateUpTo")).append(" ");
603                }
604                // only mention first and last of rangeBox added
605            }
606
607            // except on last pass
608            if (x < numberOfTurnouts-1) {
609                // bump system name
610                try {
611                    sName = InstanceManager.getDefault(TurnoutManager.class).getNextValidSystemName(t);
612                } catch (jmri.JmriException ex) {
613                    displayHwError(curAddress, ex);
614                    // directly add to statusBarLabel (but never called?)
615                    statusBarLabel.setText(Bundle.getMessage("ErrorConvertHW", curAddress));
616                    statusBarLabel.setForeground(Color.red);
617                    return;
618                }
619
620                // bump user name
621                if ((uName != null) && !uName.isEmpty()) {
622                    uName = nextName(uName);
623                }
624            }
625            // end of for loop creating rangeBox of Turnouts
626        }
627
628        // provide successfeedback to uName
629        statusBarLabel.setText(statusMessage.toString());
630        statusBarLabel.setForeground(Color.gray);
631
632        pref.setComboBoxLastSelection(systemSelectionCombo, prefixBox.getSelectedItem().getMemo().getUserName()); // store user pref
633        removePrefixBoxListener(prefixBox);
634        addFrame.setVisible(false);
635        addFrame.dispose();
636        addFrame = null;
637    }
638
639    private String addEntryToolTip;
640
641    /**
642     * Activate Add a rangeBox option if manager accepts adding more than 1
643     * Turnout and set a manager specific tooltip on the AddNewHardwareDevice
644     * pane.
645     */
646    private void canAddRange(ActionEvent e) {
647        rangeBox.setEnabled(false);
648        log.debug("T Add box disabled");
649        rangeBox.setSelected(false);
650        if (prefixBox.getSelectedIndex() == -1) {
651            prefixBox.setSelectedIndex(0);
652        }
653        Manager<Turnout> manager = prefixBox.getSelectedItem();
654        assert manager != null;
655        String systemPrefix = manager.getSystemPrefix();
656        rangeBox.setEnabled(((TurnoutManager) manager).allowMultipleAdditions(systemPrefix));
657        addEntryToolTip = manager.getEntryToolTip();
658        // show sysName (HW address) field tooltip in the Add Turnout pane that matches system connection selected from combobox
659        hardwareAddressTextField.setToolTipText(
660                Bundle.getMessage("AddEntryToolTipLine1",
661                        manager.getMemo().getUserName(),
662                        Bundle.getMessage("Turnouts"),
663                        addEntryToolTip));
664        hardwareAddressValidator.setToolTipText(hardwareAddressTextField.getToolTipText());
665        hardwareAddressValidator.verify(hardwareAddressTextField);
666    }
667
668    void handleCreateException(Exception ex, String sysName) {
669        String err = Bundle.getMessage("ErrorBeanCreateFailed",
670            InstanceManager.getDefault(TurnoutManager.class).getBeanTypeHandled(),sysName);
671        if (ex.getMessage() != null) {
672            statusBarLabel.setText(ex.getLocalizedMessage());
673            JmriJOptionPane.showMessageDialog(addFrame,
674                    ex.getLocalizedMessage(),
675                    err,
676                    JmriJOptionPane.ERROR_MESSAGE);
677        } else {
678            statusBarLabel.setText(Bundle.getMessage("WarningInvalidRange"));
679            JmriJOptionPane.showMessageDialog(addFrame,
680                    err + "\n" + Bundle.getMessage("ErrorAddFailedCheck"),
681                    err,
682                    JmriJOptionPane.ERROR_MESSAGE);
683        }
684        statusBarLabel.setForeground(Color.red);
685    }
686
687    private boolean noWarn = false;
688
689    /**
690     * {@inheritDoc}
691     */
692    @Override
693    protected String getClassName() {
694        return TurnoutTableAction.class.getName();
695    }
696
697    /**
698     * {@inheritDoc}
699     */
700    @Override
701    public void setMessagePreferencesDetails() {
702        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class)
703                .setPreferenceItemDetails(getClassName(), "duplicateUserName", Bundle.getMessage("DuplicateUserNameWarn"));
704        super.setMessagePreferencesDetails();
705    }
706
707    /**
708     * {@inheritDoc}
709     */
710    @Override
711    public String getClassDescription() {
712        return Bundle.getMessage("TitleTurnoutTable");
713    }
714
715    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TurnoutTableAction.class);
716
717}