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