001package jmri.jmrit.operations.rollingstock;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.Dimension;
006import java.awt.GridBagLayout;
007import java.text.MessageFormat;
008
009import javax.swing.*;
010
011import jmri.InstanceManager;
012import jmri.jmrit.operations.OperationsFrame;
013import jmri.jmrit.operations.OperationsXml;
014import jmri.jmrit.operations.rollingstock.cars.*;
015import jmri.jmrit.operations.setup.Control;
016import jmri.jmrit.operations.setup.Setup;
017import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
018import jmri.util.swing.JmriJOptionPane;
019
020/**
021 * Frame for editing a rolling stock attribute.
022 *
023 * @author Daniel Boudreau Copyright (C) 2020
024 */
025public abstract class RollingStockAttributeEditFrame extends OperationsFrame implements java.beans.PropertyChangeListener {
026
027    // labels
028    public JLabel textAttribute = new JLabel();
029    JLabel textSep = new JLabel();
030    public JLabel quanity = new JLabel("0");
031
032    // major buttons
033    public JButton addButton = new JButton(Bundle.getMessage("Add"));
034    public JButton deleteButton = new JButton(Bundle.getMessage("ButtonDelete"));
035    public JButton replaceButton = new JButton(Bundle.getMessage("Replace"));
036
037    // combo box
038    public JComboBox<String> comboBox;
039
040    // text box
041    public JTextField addTextBox = new JTextField(Control.max_len_string_attibute);
042
043    // ROAD, OWNER and COLOR are the attributes shared between Cars and Engines
044    public static final String ROAD = "Road";
045    public static final String OWNER = "Owner";
046    public static final String COLOR = "Color";
047    public static final String TYPE = "Type"; // cars and engines have different types
048    public static final String LENGTH = "Length"; // cars and engines have different lengths
049
050    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("MS_CANNOT_BE_FINAL") // needs access in subpackage
051    protected static boolean showDialogBox = true;
052    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("MS_CANNOT_BE_FINAL") // needs access in subpackage
053    protected static boolean showQuanity = false;
054
055    // property change
056    public static final String DISPOSE = "dispose"; // NOI18N
057
058    public RollingStockAttributeEditFrame() {
059    }
060
061    public String _attribute; // used to determine which attribute is being edited
062
063    public void initComponents(String attribute, String name) {
064        getContentPane().removeAll();
065
066        // track which attribute is being edited
067        _attribute = attribute;
068        loadCombobox();
069        comboBox.setSelectedItem(name);
070
071        // general GUI config
072        getContentPane().setLayout(new GridBagLayout());
073
074        textAttribute.setText(attribute);
075        quanity.setVisible(showQuanity);
076
077        // row 1
078        addItem(textAttribute, 2, 1);
079        // row 2
080        addItem(addTextBox, 2, 2);
081        addItem(addButton, 3, 2);
082
083        // row 3
084        addItem(quanity, 1, 3);
085        addItem(comboBox, 2, 3);
086        addItem(deleteButton, 3, 3);
087
088        // row 4
089        addItem(replaceButton, 3, 4);
090
091        addButtonAction(addButton);
092        addButtonAction(deleteButton);
093        addButtonAction(replaceButton);
094        
095        addComboBoxAction(comboBox);
096        
097        updateAttributeQuanity();
098
099        deleteButton.setToolTipText(
100                Bundle.getMessage("TipDeleteAttributeName", attribute));
101        addButton.setToolTipText(
102                Bundle.getMessage("TipAddAttributeName", attribute));
103        replaceButton.setToolTipText(
104                Bundle.getMessage("TipReplaceAttributeName", attribute));
105
106        initMinimumSize(new Dimension(Control.panelWidth400, Control.panelHeight250));
107    }
108
109    // add, delete, or replace button
110    @Override
111    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "GUI ease of use")
112    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
113        log.debug("edit frame button activated");
114        if (ae.getSource() == addButton) {
115            if (!checkItemName(Bundle.getMessage("canNotAdd"))) {
116                return;
117            }
118            addAttributeName(addTextBox.getText().trim());
119        }
120        if (ae.getSource() == deleteButton) {
121            deleteAttributeName((String) comboBox.getSelectedItem());
122        }
123        if (ae.getSource() == replaceButton) {
124            if (!checkItemName(Bundle.getMessage("canNotReplace"))) {
125                return;
126            }
127            String newItem = addTextBox.getText().trim();
128            String oldItem = (String) comboBox.getSelectedItem();
129            if (JmriJOptionPane.showConfirmDialog(this,
130                    Bundle.getMessage("replaceMsg", oldItem, newItem),
131                    Bundle.getMessage("replaceAll"), JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
132                return;
133            }
134            if (newItem.equals(oldItem)) {
135                return;
136            }
137            // don't show dialog, save current state
138            boolean oldShow = showDialogBox;
139            showDialogBox = false;
140            addAttributeName(newItem);
141            showDialogBox = oldShow;
142            replaceItem(oldItem, newItem);
143            deleteAttributeName(oldItem);
144        }
145    }
146
147    protected boolean checkItemName(String errorMessage) {
148        String itemName = addTextBox.getText().trim();
149        if (itemName.isEmpty()) {
150            return false;
151        }
152        // hyphen feature needs at least one character to work properly
153        if (itemName.contains(TrainCommon.HYPHEN)) {
154            String[] s = itemName.split(TrainCommon.HYPHEN);
155            if (s.length == 0) {
156                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("HyphenFeature"),
157                        MessageFormat.format(errorMessage, new Object[] { _attribute }), JmriJOptionPane.ERROR_MESSAGE);
158                return false;
159            }
160        }
161        if (_attribute.equals(LENGTH)) {
162            if (convertLength(itemName).equals(FAILED)) {
163                return false;
164            }
165        }
166        String[] item = { itemName };
167        if (_attribute.equals(ROAD)) {
168            if (!OperationsXml.checkFileName(itemName)) { // NOI18N
169                JmriJOptionPane.showMessageDialog(this,
170                        Bundle.getMessage("NameResChar") + NEW_LINE + Bundle.getMessage("ReservedChar"),
171                        MessageFormat.format(errorMessage, new Object[] { _attribute }), JmriJOptionPane.ERROR_MESSAGE);
172                return false;
173            }
174            item = itemName.split(TrainCommon.HYPHEN);
175        }
176        if (_attribute.equals(TYPE)) {
177            // can't have the " & " as part of the type name
178            if (itemName.contains(CarLoad.SPLIT_CHAR)) {
179                JmriJOptionPane.showMessageDialog(this,
180                        Bundle.getMessage("carNameNoAndChar",
181                                CarLoad.SPLIT_CHAR),
182                        MessageFormat.format(errorMessage, new Object[] { _attribute }), JmriJOptionPane.ERROR_MESSAGE);
183                return false;
184            }
185            item = itemName.split(TrainCommon.HYPHEN);
186        }
187        if (item[0].length() > Control.max_len_string_attibute) {
188            JmriJOptionPane.showMessageDialog(this,
189                    Bundle.getMessage("rsAttributeName",
190                            Control.max_len_string_attibute),
191                    MessageFormat.format(errorMessage, new Object[] { _attribute }), JmriJOptionPane.ERROR_MESSAGE);
192            return false;
193        }
194        return true;
195    }
196
197    protected void deleteAttributeName(String deleteItem) {
198        log.debug("delete attribute {}", deleteItem);
199        if (_attribute.equals(ROAD)) {
200            // purge train and locations by using replace
201            InstanceManager.getDefault(CarRoads.class).replaceName(deleteItem, null);
202        }
203        if (_attribute.equals(OWNER)) {
204            InstanceManager.getDefault(CarOwners.class).deleteName(deleteItem);
205        }
206        if (_attribute.equals(COLOR)) {
207            InstanceManager.getDefault(CarColors.class).deleteName(deleteItem);
208        }
209    }
210
211    protected void addAttributeName(String addItem) {
212        if (_attribute.equals(ROAD)) {
213            InstanceManager.getDefault(CarRoads.class).addName(addItem);
214        }
215        if (_attribute.equals(OWNER)) {
216            InstanceManager.getDefault(CarOwners.class).addName(addItem);
217        }
218        if (_attribute.equals(COLOR)) {
219            InstanceManager.getDefault(CarColors.class).addName(addItem);
220        }
221    }
222
223    protected void replaceItem(String oldItem, String newItem) {
224        if (_attribute.equals(ROAD)) {
225            InstanceManager.getDefault(CarRoads.class).replaceName(oldItem, newItem);
226        }
227        if (_attribute.equals(OWNER)) {
228            InstanceManager.getDefault(CarOwners.class).replaceName(oldItem, newItem);
229        }
230        if (_attribute.equals(COLOR)) {
231            InstanceManager.getDefault(CarColors.class).replaceName(oldItem, newItem);
232        }
233    }
234
235    protected void loadCombobox() {
236        if (_attribute.equals(ROAD)) {
237            comboBox = InstanceManager.getDefault(CarRoads.class).getComboBox();
238            InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this);
239        }
240        if (_attribute.equals(OWNER)) {
241            comboBox = InstanceManager.getDefault(CarOwners.class).getComboBox();
242            InstanceManager.getDefault(CarOwners.class).addPropertyChangeListener(this);
243        }
244        if (_attribute.equals(COLOR)) {
245            comboBox = InstanceManager.getDefault(CarColors.class).getComboBox();
246            InstanceManager.getDefault(CarColors.class).addPropertyChangeListener(this);
247        }
248    }
249
250    public static final String FAILED = "failed";
251
252    public String convertLength(String addItem) {
253        // convert from inches to feet if needed
254        if (addItem.endsWith("\"")) { // NOI18N
255            addItem = addItem.substring(0, addItem.length() - 1);
256            try {
257                double inches = Double.parseDouble(addItem);
258                int feet = (int) (inches * Setup.getScaleRatio() / 12);
259                addItem = Integer.toString(feet);
260            } catch (NumberFormatException e) {
261                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("CanNotConvertFeet"),
262                        Bundle.getMessage("ErrorRsLength"), JmriJOptionPane.ERROR_MESSAGE);
263                return FAILED;
264            }
265            addTextBox.setText(addItem);
266        }
267        if (addItem.endsWith("cm")) { // NOI18N
268            addItem = addItem.substring(0, addItem.length() - 2);
269            try {
270                double cm = Double.parseDouble(addItem);
271                int meter = (int) (cm * Setup.getScaleRatio() / 100);
272                addItem = Integer.toString(meter);
273            } catch (NumberFormatException e) {
274                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("CanNotConvertMeter"),
275                        Bundle.getMessage("ErrorRsLength"), JmriJOptionPane.ERROR_MESSAGE);
276                return FAILED;
277            }
278            addTextBox.setText(addItem);
279        }
280        // confirm that length is a number and less than 10000 feet
281        try {
282            int length = Integer.parseInt(addItem);
283            if (length < 0) {
284                log.error("length ({}) has to be a positive number", addItem);
285                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorRsLength"),
286                        Bundle.getMessage("canNotAdd", _attribute),
287                        JmriJOptionPane.ERROR_MESSAGE);
288                return FAILED;
289            }
290            if (addItem.length() > Control.max_len_string_length_name) {
291                JmriJOptionPane.showMessageDialog(this,
292                        Bundle.getMessage("RsAttribute",
293                                Control.max_len_string_length_name),
294                        Bundle.getMessage("canNotAdd", _attribute),
295                        JmriJOptionPane.ERROR_MESSAGE);
296                return FAILED;
297            }
298        } catch (NumberFormatException e) {
299            log.error("length ({}) is not an integer", addItem);
300            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorRsLength"),
301                    Bundle.getMessage("canNotAdd",  _attribute),
302                    JmriJOptionPane.ERROR_MESSAGE);
303            return FAILED;
304        }
305        return addItem;
306    }
307    
308    @Override
309    protected void comboBoxActionPerformed(java.awt.event.ActionEvent ae) {
310        log.debug("Combo box action");
311        updateAttributeQuanity();
312    }
313    
314    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "GUI ease of use")
315    public void toggleShowQuanity() {
316        showQuanity = !showQuanity;
317        quanity.setVisible(showQuanity);
318        updateAttributeQuanity();
319    }
320    
321    protected boolean deleteUnused = false;
322    protected boolean cancel = false;
323
324    public void deleteUnusedAttributes() {
325        if (!showQuanity) {
326            toggleShowQuanity();
327        }
328        deleteUnused = true;
329        cancel = false;
330        int items = comboBox.getItemCount() - 1;
331        for (int i = items; i >= 0; i--) {
332            comboBox.setSelectedIndex(i);
333        }
334        deleteUnused = false; // done
335        comboBox.setSelectedIndex(0);
336    }
337    
338    protected void confirmDelete(String item) {
339        if (!cancel) {
340            int results = JmriJOptionPane.showOptionDialog(this,
341                    MessageFormat
342                            .format(Bundle.getMessage("ConfirmDeleteAttribute"), new Object[] { _attribute, item }),
343                    Bundle.getMessage("DeleteAttribute?"), JmriJOptionPane.DEFAULT_OPTION,
344                    JmriJOptionPane.QUESTION_MESSAGE, null, new Object[] { Bundle.getMessage("ButtonYes"),
345                            Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonCancel") },
346                    Bundle.getMessage("ButtonYes"));
347            if (results == 0 ) { // array position 0
348                deleteAttributeName((String) comboBox.getSelectedItem());
349            }
350            if (results == 2 || results == JmriJOptionPane.CLOSED_OPTION) { // array position 2 or Dialog closed
351                cancel = true;
352            }
353        }
354    }
355    
356    protected abstract void updateAttributeQuanity();
357
358    @Override
359    public void dispose() {
360        InstanceManager.getDefault(CarRoads.class).removePropertyChangeListener(this);
361        InstanceManager.getDefault(CarOwners.class).removePropertyChangeListener(this);
362        firePropertyChange(DISPOSE, _attribute, null);
363        super.dispose();
364    }
365
366    @Override
367    public void propertyChange(java.beans.PropertyChangeEvent e) {
368        if (e.getPropertyName().equals(CarRoads.CARROADS_CHANGED_PROPERTY)) {
369            InstanceManager.getDefault(CarRoads.class).updateComboBox(comboBox);
370        }
371        if (e.getPropertyName().equals(CarOwners.CAROWNERS_CHANGED_PROPERTY)) {
372            InstanceManager.getDefault(CarOwners.class).updateComboBox(comboBox);
373        }
374        if (e.getPropertyName().equals(CarColors.CARCOLORS_CHANGED_PROPERTY)) {
375            InstanceManager.getDefault(CarColors.class).updateComboBox(comboBox);
376        }
377        comboBox.setSelectedItem(addTextBox.getText().trim()); // has to be the last line for propertyChange
378    }
379
380    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RollingStockAttributeEditFrame.class);
381}