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