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}