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.trainbuilder.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 String[] item = { itemName }; 165 if (_attribute.equals(ROAD)) { 166 if (!OperationsXml.checkFileName(itemName)) { // NOI18N 167 JmriJOptionPane.showMessageDialog(this, 168 Bundle.getMessage("NameResChar") + NEW_LINE + Bundle.getMessage("ReservedChar"), 169 MessageFormat.format(errorMessage, new Object[] { _attribute }), JmriJOptionPane.ERROR_MESSAGE); 170 return false; 171 } 172 item = itemName.split(TrainCommon.HYPHEN); 173 } 174 if (_attribute.equals(TYPE)) { 175 // can't have the " & " as part of the type name 176 if (itemName.contains(CarLoad.SPLIT_CHAR)) { 177 JmriJOptionPane.showMessageDialog(this, 178 Bundle.getMessage("carNameNoAndChar", 179 CarLoad.SPLIT_CHAR), 180 MessageFormat.format(errorMessage, new Object[] { _attribute }), JmriJOptionPane.ERROR_MESSAGE); 181 return false; 182 } 183 item = itemName.split(TrainCommon.HYPHEN); 184 } 185 if (item[0].length() > Control.max_len_string_attibute) { 186 JmriJOptionPane.showMessageDialog(this, 187 Bundle.getMessage("rsAttributeName", 188 Control.max_len_string_attibute), 189 MessageFormat.format(errorMessage, new Object[] { _attribute }), JmriJOptionPane.ERROR_MESSAGE); 190 return false; 191 } 192 return true; 193 } 194 195 protected void deleteAttributeName(String deleteItem) { 196 log.debug("delete attribute {}", deleteItem); 197 if (_attribute.equals(ROAD)) { 198 // purge train and locations by using replace 199 InstanceManager.getDefault(CarRoads.class).replaceName(deleteItem, null); 200 } 201 if (_attribute.equals(OWNER)) { 202 InstanceManager.getDefault(CarOwners.class).deleteName(deleteItem); 203 } 204 } 205 206 protected void addAttributeName(String addItem) { 207 if (_attribute.equals(ROAD)) { 208 InstanceManager.getDefault(CarRoads.class).addName(addItem); 209 } 210 if (_attribute.equals(OWNER)) { 211 InstanceManager.getDefault(CarOwners.class).addName(addItem); 212 } 213 } 214 215 protected void replaceItem(String oldItem, String newItem) { 216 if (_attribute.equals(ROAD)) { 217 InstanceManager.getDefault(CarRoads.class).replaceName(oldItem, newItem); 218 } 219 if (_attribute.equals(OWNER)) { 220 InstanceManager.getDefault(CarOwners.class).replaceName(oldItem, newItem); 221 } 222 } 223 224 protected void loadCombobox() { 225 if (_attribute.equals(ROAD)) { 226 comboBox = InstanceManager.getDefault(CarRoads.class).getComboBox(); 227 InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this); 228 } 229 if (_attribute.equals(OWNER)) { 230 comboBox = InstanceManager.getDefault(CarOwners.class).getComboBox(); 231 InstanceManager.getDefault(CarOwners.class).addPropertyChangeListener(this); 232 } 233 } 234 235 public static final String FAILED = "failed"; 236 237 public String convertLength(String addItem) { 238 // convert from inches to feet if needed 239 if (addItem.endsWith("\"")) { // NOI18N 240 addItem = addItem.substring(0, addItem.length() - 1); 241 try { 242 double inches = Double.parseDouble(addItem); 243 int feet = (int) (inches * Setup.getScaleRatio() / 12); 244 addItem = Integer.toString(feet); 245 } catch (NumberFormatException e) { 246 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("CanNotConvertFeet"), 247 Bundle.getMessage("ErrorRsLength"), JmriJOptionPane.ERROR_MESSAGE); 248 return FAILED; 249 } 250 addTextBox.setText(addItem); 251 } 252 if (addItem.endsWith("cm")) { // NOI18N 253 addItem = addItem.substring(0, addItem.length() - 2); 254 try { 255 double cm = Double.parseDouble(addItem); 256 int meter = (int) (cm * Setup.getScaleRatio() / 100); 257 addItem = Integer.toString(meter); 258 } catch (NumberFormatException e) { 259 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("CanNotConvertMeter"), 260 Bundle.getMessage("ErrorRsLength"), JmriJOptionPane.ERROR_MESSAGE); 261 return FAILED; 262 } 263 addTextBox.setText(addItem); 264 } 265 // confirm that length is a number and less than 10000 feet 266 try { 267 int length = Integer.parseInt(addItem); 268 if (length < 0) { 269 log.error("length ({}) has to be a positive number", addItem); 270 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorRsLength"), 271 Bundle.getMessage("canNotAdd", _attribute), 272 JmriJOptionPane.ERROR_MESSAGE); 273 return FAILED; 274 } 275 if (addItem.length() > Control.max_len_string_length_name) { 276 JmriJOptionPane.showMessageDialog(this, 277 Bundle.getMessage("RsAttribute", 278 Control.max_len_string_length_name), 279 Bundle.getMessage("canNotAdd", _attribute), 280 JmriJOptionPane.ERROR_MESSAGE); 281 return FAILED; 282 } 283 } catch (NumberFormatException e) { 284 log.error("length ({}) is not an integer", addItem); 285 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorRsLength"), 286 Bundle.getMessage("canNotAdd", _attribute), 287 JmriJOptionPane.ERROR_MESSAGE); 288 return FAILED; 289 } 290 return addItem; 291 } 292 293 @Override 294 protected void comboBoxActionPerformed(java.awt.event.ActionEvent ae) { 295 log.debug("Combo box action"); 296 updateAttributeQuanity(); 297 } 298 299 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "GUI ease of use") 300 public void toggleShowQuanity() { 301 showQuanity = !showQuanity; 302 quanity.setVisible(showQuanity); 303 updateAttributeQuanity(); 304 } 305 306 protected boolean deleteUnused = false; 307 protected boolean cancel = false; 308 309 public void deleteUnusedAttributes() { 310 if (!showQuanity) { 311 toggleShowQuanity(); 312 } 313 deleteUnused = true; 314 cancel = false; 315 int items = comboBox.getItemCount() - 1; 316 for (int i = items; i >= 0; i--) { 317 comboBox.setSelectedIndex(i); 318 } 319 deleteUnused = false; // done 320 comboBox.setSelectedIndex(0); 321 } 322 323 protected void confirmDelete(String item) { 324 if (!cancel) { 325 int results = JmriJOptionPane.showOptionDialog(this, 326 MessageFormat 327 .format(Bundle.getMessage("ConfirmDeleteAttribute"), new Object[] { _attribute, item }), 328 Bundle.getMessage("DeleteAttribute?"), JmriJOptionPane.DEFAULT_OPTION, 329 JmriJOptionPane.QUESTION_MESSAGE, null, new Object[] { Bundle.getMessage("ButtonYes"), 330 Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonCancel") }, 331 Bundle.getMessage("ButtonYes")); 332 if (results == 0 ) { // array position 0 333 deleteAttributeName((String) comboBox.getSelectedItem()); 334 } 335 if (results == 2 || results == JmriJOptionPane.CLOSED_OPTION) { // array position 2 or Dialog closed 336 cancel = true; 337 } 338 } 339 } 340 341 protected abstract void updateAttributeQuanity(); 342 343 @Override 344 public void dispose() { 345 InstanceManager.getDefault(CarRoads.class).removePropertyChangeListener(this); 346 InstanceManager.getDefault(CarOwners.class).removePropertyChangeListener(this); 347 firePropertyChange(DISPOSE, _attribute, null); 348 super.dispose(); 349 } 350 351 @Override 352 public void propertyChange(java.beans.PropertyChangeEvent e) { 353 if (e.getPropertyName().equals(CarRoads.CARROADS_CHANGED_PROPERTY)) { 354 InstanceManager.getDefault(CarRoads.class).updateComboBox(comboBox); 355 } 356 if (e.getPropertyName().equals(CarOwners.CAROWNERS_CHANGED_PROPERTY)) { 357 InstanceManager.getDefault(CarOwners.class).updateComboBox(comboBox); 358 } 359 comboBox.setSelectedItem(addTextBox.getText().trim()); // has to be the last line for propertyChange 360 } 361 362 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RollingStockAttributeEditFrame.class); 363}