001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.awt.Dimension;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.awt.event.ItemEvent;
008import java.awt.event.ItemListener;
009import java.text.NumberFormat;
010import java.util.ArrayList;
011import java.util.Iterator;
012import java.util.Map.Entry;
013
014import javax.swing.BorderFactory;
015import javax.swing.Box;
016import javax.swing.BoxLayout;
017import javax.swing.ButtonGroup;
018import javax.swing.JButton;
019import javax.swing.JComboBox;
020import javax.swing.JComponent;
021import javax.swing.JLabel;
022import javax.swing.JPanel;
023import javax.swing.JRadioButton;
024import javax.swing.JScrollPane;
025import javax.swing.JSpinner;
026import javax.swing.JTable;
027import javax.swing.JTextField;
028import javax.swing.SpinnerNumberModel;
029import javax.swing.SwingConstants;
030import javax.swing.table.AbstractTableModel;
031
032import jmri.implementation.SignalSpeedMap;
033import jmri.swing.PreferencesPanel;
034import jmri.util.swing.JComboBoxUtil;
035import jmri.util.swing.JmriJOptionPane;
036
037import org.openide.util.lookup.ServiceProvider;
038
039/**
040 * @author Pete Cressman Copyright (C) 2015
041 */
042@ServiceProvider(service = PreferencesPanel.class)
043public class WarrantPreferencesPanel extends JPanel implements PreferencesPanel, ItemListener {
044
045    static int STRUT_SIZE = 10;
046
047    private boolean _isDirty = false;
048
049    private JComboBox<ScaleData> _layoutScales;
050    private JSpinner _searchDepth;
051    private JSpinner _timeIncre;
052    private JTextField _rampIncre;
053    private JTextField _throttleScale;
054    private JTextField _speedAssistance;
055    private int _interpretation = SignalSpeedMap.PERCENT_NORMAL;
056    private ArrayList<DataPair<String, Float>> _speedNameMap;
057    private SpeedNameTableModel _speedNameModel;
058    private JTable _speedNameTable;
059    private ArrayList<DataPair<String, String>> _appearanceMap;
060    private AppearanceTableModel _appearanceModel;
061    private JTable _appearanceTable;
062    private ArrayList<DataPair<String, Integer>> _stepIncrementMap;
063    private WarrantPreferences.Shutdown _shutdown;
064    private boolean _traceWarrants;
065
066    public WarrantPreferencesPanel() {
067        initGUI();
068    }
069
070    private void initGUI() {
071        this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
072        JPanel leftPanel = new JPanel();
073        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.PAGE_AXIS));
074        JPanel rightPanel = new JPanel();
075        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.PAGE_AXIS));
076        leftPanel.add(layoutScalePanel());
077        leftPanel.add(searchDepthPanel(true));
078        _timeIncre = new JSpinner(new SpinnerNumberModel(750, 200, 10000, 1));
079        leftPanel.add(timeIncrementPanel(true, _timeIncre));
080        _rampIncre = new JTextField(6);
081        leftPanel.add(throttleIncrementPanel(true, _rampIncre));
082        leftPanel.add(throttleScalePanel(true));
083        leftPanel.add(speedRosterPanel(true));
084        leftPanel.add(tracePanel(true));
085        leftPanel.add(speedAssistancePanel(true));
086        rightPanel.add(speedNamesPanel());
087        rightPanel.add(Box.createGlue());
088//        rightPanel.add(interpretationPanel());
089//        rightPanel.add(Box.createGlue());
090        rightPanel.add(appearancePanel());
091        _rampIncre.addActionListener(new ActionListener() {
092            @Override
093            public void actionPerformed(ActionEvent e) {
094                String text = _rampIncre.getText();
095                boolean showdialog = false;
096                try {
097                    float incr = NumberFormat.getNumberInstance().parse(text).floatValue();
098                    showdialog = (incr < 0.5f || incr > 25f);
099                } catch (java.text.ParseException pe) {
100                    showdialog = true;
101                }
102                if (showdialog) {
103                    JmriJOptionPane.showMessageDialog(leftPanel, Bundle.getMessage("rampIncrWarning", text),
104                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
105                }
106            }
107        });
108        JPanel panel = new JPanel();
109        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
110        panel.add(leftPanel);
111        panel.add(rightPanel);
112        add(panel);
113     }
114
115    private JPanel layoutScalePanel() {
116        JPanel panel = new JPanel();
117        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
118        _layoutScales = new JComboBox<>();
119        _layoutScales.addItem(new ScaleData("G", 22.5f));
120        _layoutScales.addItem(new ScaleData("L", 38f));
121        _layoutScales.addItem(new ScaleData("O", 48f));
122        _layoutScales.addItem(new ScaleData("O'", 43f));
123        _layoutScales.addItem(new ScaleData("S", 64f));
124        _layoutScales.addItem(new ScaleData("OO", 76.2f));
125        _layoutScales.addItem(new ScaleData("HO", 87.1f));
126        _layoutScales.addItem(new ScaleData("TT", 120f));
127        _layoutScales.addItem(new ScaleData("N", 160f));
128        _layoutScales.addItem(new ScaleData("Z", 220f));
129        _layoutScales.addItem(new ScaleData("T", 480f));
130        JComboBoxUtil.setupComboBoxMaxRows(_layoutScales);
131        ScaleData sc = makeCustomItem(WarrantPreferences.getDefault().getLayoutScale());
132        _layoutScales.addItem(sc);
133        if (_layoutScales.getSelectedIndex() < 0) {
134            _layoutScales.setSelectedItem(sc);
135        }
136        Dimension dim = _layoutScales.getPreferredSize();
137        dim.width = 3 * dim.width / 2;
138        _layoutScales.setPreferredSize(dim);
139        _layoutScales.addItemListener(this);
140        _layoutScales.setToolTipText(Bundle.getMessage("ToolTipLayoutScale"));
141        JLabel label = new JLabel(Bundle.getMessage("LabelLayoutScale")); // shared key in jmrit.Bundle.properties
142        label.setToolTipText(Bundle.getMessage("ToolTipLayoutScale"));
143        panel.add(label);
144        JPanel p = new JPanel();
145        p.add(_layoutScales);
146        panel.add(p);
147        return panel;
148    }
149
150    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "fixed number of possible values")
151    private ScaleData makeCustomItem(float scale) {
152        int cnt = 0;
153        while (cnt < _layoutScales.getItemCount()) {
154            if (_layoutScales.getItemAt(cnt).scale == scale) {
155                _layoutScales.setSelectedItem(_layoutScales.getItemAt(cnt));
156                return new CustomDialog("custom", 0.0f);
157            }
158            cnt++;
159        }
160        _layoutScales.setSelectedIndex(-1);
161        return new CustomDialog(Bundle.getMessage("custom"), scale);
162    }
163
164    @Override
165    public void itemStateChanged(ItemEvent e) {
166        WarrantPreferences preferences = WarrantPreferences.getDefault();
167        if (e.getStateChange() == ItemEvent.SELECTED) {
168            ScaleData sd = (ScaleData) e.getItem();
169            if (sd instanceof CustomDialog) {
170                boolean ok = false;
171                while (!ok) {
172                    float scale = 0.0f;
173                    String str = JmriJOptionPane.showInputDialog(this, Bundle.getMessage("customInput"),
174                            Bundle.getMessage("customTitle"), JmriJOptionPane.QUESTION_MESSAGE);
175                    try {
176                        if (str == null) {
177                            sd.scale = 0.0f;
178                            makeCustomItem(preferences.getLayoutScale());
179                            ok = true;
180                        } else {
181                            scale = Float.parseFloat(str);
182                            if (scale <= 1.0f) {
183                                throw new NumberFormatException();
184                            }
185                            sd.scale = scale;
186                            preferences.setLayoutScale(scale);
187                            _isDirty = true;
188                            ok = true;
189                        }
190                    } catch (NumberFormatException nfe) {
191                        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("customError", str),
192                                Bundle.getMessage("customTitle"), JmriJOptionPane.ERROR_MESSAGE);
193                    }
194                }
195            } else {
196                preferences.setLayoutScale(sd.scale);
197                _isDirty = true;
198            }
199        }
200    }
201
202    // Preferences left column items
203
204    class ScaleData {
205
206        float scale;
207        String scaleName;
208
209        ScaleData(String scaleName, float scale) {
210            this.scale = scale;
211            this.scaleName = scaleName;
212        }
213
214        @Override
215        public String toString() {
216            return Bundle.getMessage("scaleItem", scaleName, Float.toString(scale));
217        }
218    }
219
220    class CustomDialog extends ScaleData {
221
222        CustomDialog(String scaleName, float scale) {
223            super(scaleName, scale);
224        }
225
226        @Override
227        public String toString() {
228            if (scale < 1.0f) {
229                return Bundle.getMessage("custom");
230            }
231            return super.toString();
232        }
233    }
234
235    private JPanel searchDepthPanel(boolean vertical) {
236        _searchDepth = new JSpinner(new SpinnerNumberModel(20, 1, 50, 1));
237        int searchDepthPref = WarrantPreferences.getDefault().getSearchDepth();
238        if (searchDepthPref >= 1 && searchDepthPref <= 50) {
239            _searchDepth.setValue(searchDepthPref);
240        }
241        JPanel p = new JPanel();
242        p.add(WarrantRoute.makeTextBoxPanel(vertical, _searchDepth, "SearchDepth", "ToolTipSearchDepth"));
243        p.setToolTipText(Bundle.getMessage("ToolTipSearchDepth"));
244        return p;
245    }
246
247    private JPanel throttleScalePanel(boolean vertical) {
248        _throttleScale = new JTextField(5);
249        _throttleScale.setText(NumberFormat.getNumberInstance().format(WarrantPreferences.getDefault().getThrottleScale()));
250        JPanel p = new JPanel();
251        p.add(WarrantRoute.makeTextBoxPanel(vertical, _throttleScale, "ThrottleScale", "ToolTipThrottleScale"));
252        _throttleScale.setColumns(8);
253        p.setToolTipText(Bundle.getMessage("ToolTipThrottleScale"));
254        return p;
255    }
256
257    static protected JPanel timeIncrementPanel(boolean vertical, JSpinner timeIncre) {
258        int timeIncrePrefs = WarrantPreferences.getDefault().getTimeIncrement();
259        if (timeIncrePrefs >= 200 && timeIncrePrefs <= 10000) {
260            timeIncre.setValue(timeIncrePrefs);
261        }
262        JPanel incrPanel = new JPanel();
263        incrPanel.add(timeIncre);
264        incrPanel.add(new JLabel(Bundle.getMessage("milliseconds")));
265        JPanel p = new JPanel();
266        p.add(WarrantFrame.makeTextBoxPanel(vertical, incrPanel, "TimeIncrement", "ToolTipTimeIncrement"));
267        p.setToolTipText(Bundle.getMessage("ToolTipTimeIncrement"));
268        return p;
269    }
270
271    static protected JPanel throttleIncrementPanel(boolean vertical, JTextField rampIncre) {
272        rampIncre.setText(NumberFormat.getNumberInstance().format(WarrantPreferences.getDefault().getThrottleIncrement()*100));
273        JPanel incrPanel = new JPanel();
274        incrPanel.add(rampIncre);
275        incrPanel.add(new JLabel(Bundle.getMessage("percent")));
276        JPanel p = new JPanel();
277        p.add(WarrantFrame.makeTextBoxPanel(vertical, incrPanel, "RampIncrement", "ToolTipRampIncrement"));
278        p.setToolTipText(Bundle.getMessage("ToolTipRampIncrement"));
279        return p;
280    }
281
282    private JPanel tracePanel(boolean vertical) {
283        _traceWarrants = WarrantPreferences.getDefault().getTrace();
284        String text = (_traceWarrants ? "ColumnHeadEnabled" : "Disabled");
285        JButton button = new JButton(Bundle.getMessage(text));
286        button.addActionListener(new ActionListener() {
287            @Override
288            public void actionPerformed(ActionEvent e) {
289                _traceWarrants = !_traceWarrants;
290                String text = (_traceWarrants ? "ColumnHeadEnabled" : "Disabled");
291                button.setText(Bundle.getMessage(text));
292                button.setSelected(false);
293            }
294        });
295        JPanel p = new JPanel();
296        p.add(WarrantFrame.makeTextBoxPanel(vertical, button, "warrantTrace", "ToolTipTraceWarrant"));
297        p.setToolTipText(Bundle.getMessage("ToolTipTimeIncrement"));
298        return p;
299    }
300
301    private JPanel speedAssistancePanel(boolean vertical) {
302        _speedAssistance = new JTextField(6);
303        _speedAssistance.setText(NumberFormat.getNumberInstance().format(WarrantPreferences.getDefault().getSpeedAssistance()*100));
304        JPanel incrPanel = new JPanel();
305        incrPanel.add(_speedAssistance);
306        incrPanel.add(new JLabel(Bundle.getMessage("percent")));
307        JPanel p = new JPanel();
308        p.add(WarrantFrame.makeTextBoxPanel(vertical, incrPanel, "SpeedAssistance", "ToolTipSpeedAssistance"));
309        p.setToolTipText(Bundle.getMessage("ToolTipRampIncrement"));
310        return p;
311    }
312
313    private JPanel speedRosterPanel(boolean vertical) {
314        ButtonGroup bg = new ButtonGroup();
315        JPanel p = new JPanel();
316        p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
317        JRadioButton b1 = new JRadioButton(Bundle.getMessage("NeverMerge"));
318        b1.addActionListener((ActionEvent e) -> {
319            if (b1.isSelected()) {
320                _shutdown = WarrantPreferences.Shutdown.NO_MERGE;
321            }
322        });
323        bg.add(b1);
324        p.add(b1);
325        JRadioButton b2 = new JRadioButton(Bundle.getMessage("PromptMerges"));
326        b2.addActionListener((ActionEvent e) -> {
327            if (b2.isSelected()) {
328                _shutdown = WarrantPreferences.Shutdown.PROMPT;
329            }
330        });
331        bg.add(b2);
332        p.add(b2);
333        JRadioButton b3 = new JRadioButton(Bundle.getMessage("AlwaysMerge"));
334        b3.addActionListener((ActionEvent e) -> {
335            if (b3.isSelected()) {
336                _shutdown = WarrantPreferences.Shutdown.MERGE_ALL;
337            }
338        });
339        bg.add(b3);
340        p.add(b3);
341        _shutdown = WarrantPreferences.getDefault().getShutdown();
342        switch (_shutdown) {
343            case NO_MERGE:
344                b1.setSelected(true);
345                break;
346            case PROMPT:
347                b2.setSelected(true);
348                break;
349            case MERGE_ALL:
350                b3.setSelected(true);
351                break;
352            default:
353                // fall out
354                break;
355        }
356        JPanel panel = new JPanel();
357        panel.add(WarrantRoute.makeTextBoxPanel(vertical, p, "MergeRoster", "ToolTipMergeRoster"));
358        return panel;
359    }
360
361    private JPanel speedNamesPanel() {
362        JPanel panel = new JPanel();
363        panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
364                Bundle.getMessage("LabelSpeedNameTable"),
365                javax.swing.border.TitledBorder.CENTER,
366                javax.swing.border.TitledBorder.TOP));
367        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
368
369        _speedNameMap = new ArrayList<>();
370        Iterator<Entry<String, Float>> it = WarrantPreferences.getDefault().getSpeedNameEntryIterator();
371        while (it.hasNext()) {
372            Entry<String, Float> ent = it.next();
373            _speedNameMap.add(new DataPair<>(ent.getKey(), ent.getValue()));
374        }
375        _speedNameModel = new SpeedNameTableModel();
376        _speedNameTable = new JTable(_speedNameModel);
377        for (int i = 0; i < _speedNameModel.getColumnCount(); i++) {
378            int width = _speedNameModel.getPreferredWidth(i);
379            _speedNameTable.getColumnModel().getColumn(i).setPreferredWidth(width);
380        }
381        ActionListener insertAction = new ActionListener() {
382            @Override
383            public void actionPerformed(ActionEvent e) {
384                insertSpeedNameRow();
385            }
386        };
387        ActionListener deleteAction = new ActionListener() {
388            @Override
389            public void actionPerformed(ActionEvent e) {
390                deleteSpeedNameRow();
391            }
392        };
393        panel.add(tablePanel(_speedNameTable, "ToolTipSpeedNameTable", insertAction, deleteAction));
394        panel.add(interpretationPanel());
395        return panel;
396    }
397
398    private JPanel appearancePanel() {
399        JPanel panel = new JPanel();
400        panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(java.awt.Color.BLACK),
401                Bundle.getMessage("LabelAppearanceTable"),
402                javax.swing.border.TitledBorder.CENTER,
403                javax.swing.border.TitledBorder.TOP));
404        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
405
406        _appearanceMap = new ArrayList<>();
407        Iterator<Entry<String, String>> it = WarrantPreferences.getDefault().getAppearanceEntryIterator();
408        while (it.hasNext()) {
409            Entry<String, String> ent = it.next();
410            _appearanceMap.add(new DataPair<>(ent.getKey(), ent.getValue()));
411        }
412        _appearanceModel = new AppearanceTableModel();
413        _appearanceTable = new JTable(_appearanceModel);
414        for (int i = 0; i < _appearanceModel.getColumnCount(); i++) {
415            int width = _appearanceModel.getPreferredWidth(i);
416            _appearanceTable.getColumnModel().getColumn(i).setPreferredWidth(width);
417        }
418        panel.add(Box.createGlue());
419        panel.add(tablePanel(_appearanceTable, "ToolTipAppearanceTable", null, null));
420        panel.add(Box.createGlue());
421        return panel;
422    }
423
424    static private JPanel tablePanel(JTable table, String toolTip, ActionListener insertAction, ActionListener removeAction) {
425        JPanel tablePanel = new JPanel();
426        tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.LINE_AXIS));
427        JScrollPane scrollPane = new JScrollPane(table);
428        int height = table.getRowHeight();
429        Dimension dim = table.getPreferredSize();
430        dim.height = height * 5;
431        scrollPane.getViewport().setPreferredSize(dim);
432        table.setToolTipText(Bundle.getMessage(toolTip));
433        scrollPane.setToolTipText(Bundle.getMessage(toolTip));
434        tablePanel.add(scrollPane);
435        tablePanel.add(Box.createVerticalStrut(STRUT_SIZE));
436
437        JPanel buttonPanel = new JPanel();
438        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.PAGE_AXIS));
439
440        if (insertAction != null) {
441            JButton insertButton = new JButton(Bundle.getMessage("buttonInsertRow"));
442            insertButton.addActionListener(insertAction);
443            buttonPanel.add(insertButton);
444            buttonPanel.add(Box.createVerticalStrut(2 * STRUT_SIZE));
445        }
446
447        if (removeAction != null) {
448            JButton deleteButton = new JButton(Bundle.getMessage("buttonDeleteRow"));
449            deleteButton.addActionListener(removeAction);
450            buttonPanel.add(deleteButton);
451        }
452        tablePanel.add(buttonPanel);
453        return tablePanel;
454    }
455
456    private void insertSpeedNameRow() {
457        int row = _speedNameTable.getSelectedRow();
458        if (row < 0) {
459            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("selectRow"),
460                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
461            return;
462        }
463        _speedNameMap.add(row, new DataPair<>("", 0f));
464        _speedNameModel.fireTableDataChanged();
465    }
466
467    private void deleteSpeedNameRow() {
468        int row = _speedNameTable.getSelectedRow();
469        if (row < 0) {
470            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("selectRow"),
471                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
472            return;
473        }
474        _speedNameMap.remove(row);
475        _speedNameModel.fireTableDataChanged();
476    }
477
478    private JPanel interpretationPanel() {
479        _interpretation = WarrantPreferences.getDefault().getInterpretation();
480        JPanel buttonPanel = new JPanel();
481        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
482        ButtonGroup group = new ButtonGroup();
483        makeButton(buttonPanel, group, "percentNormal", "ToolTipPercentNormal", SignalSpeedMap.PERCENT_NORMAL);
484        makeButton(buttonPanel, group, "percentThrottle", "ToolTipPercentThrottle", SignalSpeedMap.PERCENT_THROTTLE);
485        makeButton(buttonPanel, group, "speedMph", "ToolTipSpeedMph", SignalSpeedMap.SPEED_MPH);
486        makeButton(buttonPanel, group, "speedKmph", "ToolTipSpeedKmph", SignalSpeedMap.SPEED_KMPH);
487        JPanel panel = new JPanel();
488        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
489        JPanel p = new JPanel();
490        p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
491        p.add(Box.createGlue());
492        JLabel label = new JLabel(Bundle.getMessage("LabelInterpretation", SwingConstants.CENTER));
493        label.setToolTipText(Bundle.getMessage("ToolTipInterpretation"));
494        p.setToolTipText(Bundle.getMessage("ToolTipInterpretation"));
495        p.add(label);
496        p.add(Box.createGlue());
497        panel.add(p, SwingConstants.CENTER);
498        panel.add(buttonPanel, Box.CENTER_ALIGNMENT);
499        return panel;
500    }
501
502    private void makeButton(JPanel panel, ButtonGroup group, String name, String tooltip, int interp) {
503        JRadioButton button = new JRadioButton(Bundle.getMessage(name));
504        group.add(button);
505        panel.add(button);
506        button.setToolTipText(Bundle.getMessage(tooltip));
507        button.addActionListener(new ActionListener() {
508            int value;
509            JRadioButton but;
510
511            @Override
512            public void actionPerformed(ActionEvent e) {
513                if (but.isSelected()) {
514                    _interpretation = value;
515                }
516            }
517
518            ActionListener init(JRadioButton b, int num) {
519                but = b;
520                value = num;
521                return this;
522            }
523        }.init(button, interp));
524        if (_interpretation == interp) {
525            button.setSelected(true);
526        }
527    }
528
529    /**
530     * Compare GUI values with Preferences. When different, update Preferences
531     * and set _isDirty flag.
532     */
533    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "fixed number of possible values")
534    private void setValues() {
535        WarrantPreferences preferences = WarrantPreferences.getDefault();
536
537        int depth = (Integer) _searchDepth.getValue();
538        if (preferences.getSearchDepth() != depth) {
539            preferences.setSearchDepth(depth);
540            _isDirty = true;
541        }
542
543        if (preferences.getInterpretation() != _interpretation) {
544            preferences.setInterpretation(_interpretation);
545            _isDirty = true;
546        }
547
548        int time = (Integer) _timeIncre.getValue();
549        if (preferences.getTimeIncrement() != time) {
550            preferences.setTimeIncrement(time);
551            _isDirty = true;
552        }
553
554        if (preferences.getTrace() != _traceWarrants) {
555            preferences.setTrace(_traceWarrants);
556            _isDirty = true;
557        }
558
559        float scale = preferences.getThrottleIncrement();
560        try {
561            scale = NumberFormat.getNumberInstance().parse(_rampIncre.getText()).floatValue();
562        } catch (java.text.ParseException pe) {
563            _rampIncre.setText(NumberFormat.getNumberInstance().format(preferences.getThrottleIncrement()*100));
564        }
565        if (scale < 0.5f || scale > 25f) {
566            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("rampIncrWarning", _rampIncre.getText()),
567                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
568            _rampIncre.setText(NumberFormat.getNumberInstance().format(WarrantPreferences.getDefault().getThrottleIncrement()*100));
569        } else {
570            scale /= 100;
571            if (preferences.getThrottleIncrement() != scale) {
572                preferences.setThrottleIncrement(scale);
573                _isDirty = true;
574            }
575        }
576
577        scale = preferences.getThrottleScale();
578        try {
579            scale = NumberFormat.getNumberInstance().parse(_throttleScale.getText()).floatValue();
580        } catch (java.text.ParseException pe) {
581            _throttleScale.setText(NumberFormat.getNumberInstance().format(preferences.getThrottleScale()));
582        }
583        if (preferences.getThrottleScale() != scale) {
584            preferences.setThrottleScale(scale);
585            _isDirty = true;
586        }
587
588        scale = preferences.getSpeedAssistance();
589        try {
590            scale = NumberFormat.getNumberInstance().parse(_speedAssistance.getText()).floatValue();
591        } catch (java.text.ParseException pe) {
592            _rampIncre.setText(NumberFormat.getNumberInstance().format(preferences.getSpeedAssistance()*100));
593        }
594        scale /= 100;
595        if (preferences.getSpeedAssistance() != scale) {
596            preferences.setSpeedAssistance(scale);
597            _isDirty = true;
598        }
599
600        WarrantPreferences.Shutdown shutDown = preferences.getShutdown();
601        if (shutDown != _shutdown) {
602            preferences.setShutdown(_shutdown);
603            _isDirty = true;
604        }
605
606        boolean different = false;
607        javax.swing.table.TableCellEditor tce = _speedNameTable.getCellEditor();
608        if (tce != null) {
609            tce.stopCellEditing();
610        }
611        if (preferences.getSpeedNames().size() != _speedNameMap.size()) {
612            different = true;
613        } else {
614            for (int i = 0; i < _speedNameMap.size(); i++) {
615                DataPair<String, Float> dp = _speedNameMap.get(i);
616                String name = dp.getKey();
617                if (preferences.getSpeedNameValue(name) == null
618                        || preferences.getSpeedNameValue(name).floatValue() != dp.getValue().floatValue()) {
619                    different = true;
620                    break;
621                }
622            }
623        }
624        if (different) {
625            preferences.setSpeedNames(_speedNameMap);
626            _isDirty = true;
627        }
628
629        different = false;
630        tce = _appearanceTable.getCellEditor();
631        if (tce != null) {
632            tce.stopCellEditing();
633        }
634        if (preferences.getAppearances().size() != _appearanceMap.size()) {
635            different = true;
636        } else {
637            for (int i = 0; i < _appearanceMap.size(); i++) {
638                DataPair<String, String> dp = _appearanceMap.get(i);
639                String name = dp.getKey();
640                if (preferences.getAppearanceValue(name) == null || !preferences.getAppearanceValue(name).equals(dp.getValue())) {
641                    different = true;
642                    break;
643                }
644            }
645        }
646        if (different) {
647            preferences.setAppearances(_appearanceMap);
648            _isDirty = true;
649        }
650    }
651
652    @Override
653    public String getPreferencesItem() {
654        return "WARRANTS"; // NOI18N
655    }
656
657    @Override
658    public String getPreferencesItemText() {
659        return Bundle.getMessage("TitleWarrantPreferences");
660    }
661
662    @Override
663    public String getTabbedPreferencesTitle() {
664        return null;
665    }
666
667    @Override
668    public String getLabelKey() {
669        return null;
670    }
671
672    @Override
673    public JComponent getPreferencesComponent() {
674        return this;
675    }
676
677    @Override
678    public boolean isPersistant() {
679        return false;
680    }
681
682    @Override
683    public String getPreferencesTooltip() {
684        return Bundle.getMessage("ToolTipLayoutScale");
685    }
686
687    @Override
688    public void savePreferences() {
689        setValues();
690        if (_isDirty) {
691            WarrantPreferences.getDefault().save();
692            _isDirty = false;
693        }
694    }
695
696    @Override
697    public boolean isDirty() {
698        return this._isDirty;
699    }
700
701    @Override
702    public boolean isRestartRequired() {
703        return false;
704    }
705
706    @Override
707    public boolean isPreferencesValid() {
708        return true; // no validity checking performed
709    }
710
711    /**
712     * Retain the key/value pair of a Map or Dictionary as a pair.
713     *
714     * @param <K> key class
715     * @param <V> value class
716     */
717    // Can uses of DataPair be replaced with used of Map.Entry or AbstractMap.SimpleEntry?
718    static class DataPair<K, V> implements Entry<K, V> {
719
720        K key;
721        V value;
722
723        DataPair(K k, V v) {
724            key = k;
725            value = v;
726        }
727
728        DataPair(Entry<K, V> entry) {
729            this.key = entry.getKey();
730            this.value = entry.getValue();
731        }
732
733        @Override
734        public K getKey() {
735            return key;
736        }
737
738        void setKey(K k) {
739            key = k;
740        }
741
742        @Override
743        public V getValue() {
744            return value;
745        }
746
747        @Override
748        public V setValue(V v) {
749            value = v;
750            return value;
751        }
752
753    }
754
755    /* *********************** SpeedName Table ******************************/
756    class SpeedNameTableModel extends AbstractTableModel {
757
758        public SpeedNameTableModel() {
759            super();
760        }
761
762        @Override
763        public int getColumnCount() {
764            return 2;
765        }
766
767        @Override
768        public int getRowCount() {
769            return _speedNameMap.size();
770        }
771
772        @Override
773        public String getColumnName(int col) {
774            if (col == 0) {
775                return Bundle.getMessage("speedName");
776            }
777            return Bundle.getMessage("speedValue");
778        }
779
780        @Override
781        public boolean isCellEditable(int row, int col) {
782            return true;
783        }
784
785        @Override
786        public Class<?> getColumnClass(int col) {
787            return String.class;
788        }
789
790        public int getPreferredWidth(int col) {
791            if (col == 0) {
792                return new JTextField(15).getPreferredSize().width;
793            }
794            return new JTextField(8).getPreferredSize().width;
795        }
796
797        @Override
798        public Object getValueAt(int row, int col) {
799            // some error checking
800            if (row >= _speedNameMap.size()) {
801                log.error("row is greater than aspect speedNames size");
802                return "";
803            }
804            DataPair<String, Float> data = _speedNameMap.get(row);
805            if (data == null) {
806                log.error("Aspect speedName data is null!");
807                return "";
808            }
809            if (col == 0) {
810                return data.getKey();
811            }
812            return data.getValue();
813        }
814
815        @Override
816        public void setValueAt(Object value, int row, int col) {
817            DataPair<String, Float> data = _speedNameMap.get(row);
818            String str = (String) value;
819            String msg = null;
820            if (str == null || data == null) {
821                msg = Bundle.getMessage("NoData");
822            }
823            if (data != null) {
824                if (col == 0) {
825                    data.setKey((String) value);
826                } else {
827                    try {
828                        float f = Float.parseFloat((String) value);
829                        if (f < 0) {
830                            msg = Bundle.getMessage("InvalidNumber", (String) value);
831                        } else {
832                            data.setValue(f);
833                        }
834                    } catch (NumberFormatException nfe) {
835                        msg = Bundle.getMessage("MustBeFloat", (String) value);
836                    }
837                }
838                if (msg != null) {
839                    JmriJOptionPane.showMessageDialog(null, msg,
840                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
841                } else {
842                    fireTableRowsUpdated(row, row);
843                }
844            }
845        }
846    }
847
848    /* *********************** Appearance Table ******************************/
849    class AppearanceTableModel extends AbstractTableModel {
850
851        public AppearanceTableModel() {
852            super();
853        }
854
855        @Override
856        public int getColumnCount() {
857            return 2;
858        }
859
860        @Override
861        public int getRowCount() {
862            return _appearanceMap.size();
863        }
864
865        @Override
866        public String getColumnName(int col) {
867            if (col == 0) {
868                return Bundle.getMessage("appearance");
869            }
870            return Bundle.getMessage("speedName");
871        }
872
873        @Override
874        public boolean isCellEditable(int row, int col) {
875            return (col != 0);
876        }
877
878        @Override
879        public Class<?> getColumnClass(int col) {
880            return String.class;
881        }
882
883        public int getPreferredWidth(int col) {
884            if (col == 0) {
885                return new JTextField(15).getPreferredSize().width;
886            }
887            return new JTextField(15).getPreferredSize().width;
888        }
889
890        @Override
891        public Object getValueAt(int row, int col) {
892            // some error checking
893            if (row >= _appearanceMap.size()) {
894                log.error("row is greater than appearance names size");
895                return "";
896            }
897            DataPair<String, String> data = _appearanceMap.get(row);
898            if (data == null) {
899                log.error("Appearance name data is null!");
900                return "";
901            }
902            if (col == 0) {
903                return data.getKey();
904            }
905            return data.getValue();
906        }
907
908        @Override
909        public void setValueAt(Object value, int row, int col) {
910            DataPair<String, String> data = _appearanceMap.get(row);
911            String str = (String) value;
912            String msg = null;
913            if (str == null || data == null) {
914                msg = Bundle.getMessage("NoData");
915            }
916            if (data != null) {
917                if (col == 0) {
918                    data.setKey((String) value);
919                } else {
920                    data.setValue((String) value);
921                }
922                if (msg != null) {
923                    JmriJOptionPane.showMessageDialog(null, msg,
924                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
925                } else {
926                    fireTableRowsUpdated(row, row);
927                }
928            }
929        }
930    }
931
932    /* *********************** Throttle Step Increment Table *****************************/
933    class StepIncrementTableModel extends AbstractTableModel {
934
935        public StepIncrementTableModel() {
936            super();
937        }
938
939        @Override
940        public int getColumnCount() {
941            return 2;
942        }
943
944        @Override
945        public int getRowCount() {
946            return _stepIncrementMap.size();
947        }
948
949        @Override
950        public String getColumnName(int col) {
951            if (col == 0) {
952                return Bundle.getMessage("throttleStepMode");
953            }
954            return Bundle.getMessage("rampIncrement");
955        }
956
957        @Override
958        public boolean isCellEditable(int row, int col) {
959            return (col != 0);
960        }
961
962        @Override
963        public Class<?> getColumnClass(int col) {
964            return String.class;
965        }
966
967        public int getPreferredWidth(int col) {
968            if (col == 0) {
969                return new JTextField(15).getPreferredSize().width;
970            }
971            return new JTextField(5).getPreferredSize().width;
972        }
973
974        @Override
975        public Object getValueAt(int row, int col) {
976            // some error checking
977            if (row >= _stepIncrementMap.size()) {
978                log.error("row is greater than throttle step modes size");
979                return "";
980            }
981            DataPair<String, Integer> data = _stepIncrementMap.get(row);
982            if (data == null) {
983                log.error("Throttle step data is null!");
984                return "";
985            }
986            if (col == 0) {
987                return data.getKey();
988            }
989            return data.getValue();
990        }
991
992        @Override
993        public void setValueAt(Object value, int row, int col) {
994            DataPair<String, Integer> data = _stepIncrementMap.get(row);
995            String str = (String) value;
996            String msg = null;
997            if (str == null || data == null) {
998                msg = Bundle.getMessage("NoData");
999            }
1000            if (data != null) {
1001                if (col == 0) {
1002                    data.setKey((String) value);
1003                } else {
1004                    try {
1005                        Integer f = Integer.parseInt((String) value);
1006                        if (f < 1) {
1007                            msg = Bundle.getMessage("InvalidNumber", (String) value);
1008                        } else {
1009                            data.setValue(f);
1010                        }
1011                    } catch (NumberFormatException nfe) {
1012                        msg = Bundle.getMessage("InvalidNumber", (String) value);
1013                    }
1014                }
1015                if (msg != null) {
1016                    JmriJOptionPane.showMessageDialog(null, msg,
1017                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
1018                } else {
1019                    fireTableRowsUpdated(row, row);
1020                }
1021            }
1022        }
1023    }
1024
1025    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WarrantPreferencesPanel.class);
1026
1027}