001package jmri.jmrit.beantable; 002 003import java.awt.Color; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006import javax.annotation.Nonnull; 007import javax.swing.*; 008 009import jmri.InstanceManager; 010import jmri.Manager; 011import jmri.StringIO; 012import jmri.StringIOManager; 013import jmri.UserPreferencesManager; 014import jmri.swing.ManagerComboBox; 015import jmri.swing.SystemNameValidator; 016import jmri.util.JmriJFrame; 017import jmri.util.swing.JmriJOptionPane; 018 019/** 020 * Swing action to create and register a StringIOTable GUI. 021 * 022 * @author Bob Jacobsen Copyright (C) 2024 023 */ 024public class StringIOTableAction extends AbstractTableAction<StringIO> { 025 026 /** 027 * Create an action with a specific title. 028 * <p> 029 * Note that the argument is the Action title, not the title of the 030 * resulting frame. Perhaps this should be changed? 031 * 032 * @param actionName title of the action 033 */ 034 public StringIOTableAction(String actionName) { 035 super(actionName); 036 037 // disable ourself if there is no primary StringIO manager available 038 if (stringIOManager == null) { 039 super.setEnabled(false); 040 } 041 } 042 043 public StringIOTableAction() { 044 this(Bundle.getMessage("TitleStringIOTable")); 045 } 046 047 protected StringIOManager stringIOManager = InstanceManager.getDefault(StringIOManager.class); 048 049 /** 050 * {@inheritDoc} 051 */ 052 @Override 053 public void setManager(@Nonnull Manager<StringIO> man) { 054 if (man instanceof StringIOManager) { 055 stringIOManager = (StringIOManager) man; 056 } 057 } 058 059 060 /** 061 * Create the JTable DataModel, along with the changes for the specific case 062 * of StringIOs. 063 */ 064 @Override 065 protected void createModel() { 066 m = new StringIOTableDataModel(stringIOManager); 067 } 068 069 /** 070 * {@inheritDoc} 071 */ 072 @Override 073 protected void setTitle() { 074 f.setTitle(Bundle.getMessage("TitleStringIOTable")); 075 } 076 077 /** 078 * {@inheritDoc} 079 */ 080 @Override 081 protected String helpTarget() { 082 return "package.jmri.jmrit.beantable.StringIOTable"; 083 } 084 085 private JmriJFrame addFrame = null; 086 private final JTextField hardwareAddressTextField = new JTextField(20); 087 private final JTextField userNameTextField = new JTextField(20); 088 089 private final ManagerComboBox<StringIO> prefixBox = new ManagerComboBox<>(); 090 091 092 private final SpinnerNumberModel rangeSpinner = new SpinnerNumberModel(1, 1, 100, 1); // maximum 100 items 093 private final JSpinner numberToAddSpinner = new JSpinner(rangeSpinner); 094 private final JCheckBox rangeCheckBox = new JCheckBox(Bundle.getMessage("AddRangeBox")); 095 private final String systemSelectionCombo = this.getClass().getName() + ".SystemSelected"; 096 private JButton addButton; 097 private final JLabel statusBarLabel = new JLabel(Bundle.getMessage("HardwareAddStatusEnter"), JLabel.LEADING); 098 private Manager<StringIO> connectionChoice = null; 099 private UserPreferencesManager pref; 100 private SystemNameValidator hardwareAddressValidator; 101 102 /** 103 * {@inheritDoc} 104 */ 105 @Override 106 protected void addPressed(ActionEvent e) { 107 pref = InstanceManager.getDefault(UserPreferencesManager.class); 108 if (addFrame == null) { 109 addFrame = new JmriJFrame(Bundle.getMessage("TitleAddStringIO"), false, true); 110 addFrame.addHelpMenu("package.jmri.jmrit.beantable.StringIOAddEdit", true); 111 ActionListener createListener = this::createPressed; 112 ActionListener cancelListener = this::cancelPressed; 113 ActionListener rangeListener = this::canAddRange; 114 configureManagerComboBox(prefixBox, stringIOManager, StringIOManager.class); 115 userNameTextField.setName("userName"); // NOI18N 116 prefixBox.setName("prefixBox"); // NOI18N 117 addButton = new JButton(Bundle.getMessage("ButtonCreate")); 118 addButton.addActionListener(createListener); 119 120 if (hardwareAddressValidator==null){ 121 hardwareAddressValidator = new SystemNameValidator(hardwareAddressTextField, java.util.Objects.requireNonNull(prefixBox.getSelectedItem(), "encountered null system selection"), true); 122 } else { 123 log.trace("on add, prefixBox.getSelected is {}", prefixBox.getSelectedItem()); 124 hardwareAddressValidator.setManager(prefixBox.getSelectedItem()); 125 } 126 127 // create panel 128 addFrame.add(new AddNewHardwareDevicePanel(hardwareAddressTextField, hardwareAddressValidator, userNameTextField, prefixBox, 129 numberToAddSpinner, rangeCheckBox, addButton, cancelListener, rangeListener, statusBarLabel)); 130 // tooltip for hardwareAddressTextField will be assigned next by canAddRange() 131 canAddRange(null); 132 } 133 hardwareAddressTextField.setName("sysName"); // for GUI test NOI18N 134 hardwareAddressTextField.setName("hwAddressTextField"); // for GUI test NOI18N 135 addButton.setName("createButton"); // for GUI test NOI18N 136 // reset statusBarLabel text 137 statusBarLabel.setText(Bundle.getMessage("HardwareAddStatusEnter")); 138 statusBarLabel.setForeground(Color.gray); 139 addFrame.setEscapeKeyClosesWindow(true); 140 addFrame.getRootPane().setDefaultButton(addButton); 141 addFrame.pack(); 142 addFrame.setVisible(true); 143 } 144 145 void cancelPressed(ActionEvent e) { 146 removePrefixBoxListener(prefixBox); 147 addFrame.setVisible(false); 148 addFrame.dispose(); 149 addFrame = null; 150 } 151 152 /** 153 * Respond to Create new item button pressed on Add StringIO pane. 154 * 155 * @param e the click event 156 */ 157 void createPressed(ActionEvent e) { 158 159 int numberOfStringIOs = 1; 160 161 if (rangeCheckBox.isSelected()) { 162 numberOfStringIOs = (Integer) numberToAddSpinner.getValue(); 163 } 164 if (numberOfStringIOs >= 65 // limited by JSpinnerModel to 100 165 && JmriJOptionPane.showConfirmDialog(addFrame, 166 Bundle.getMessage("WarnExcessBeans", Bundle.getMessage("StringIOs"), numberOfStringIOs), 167 Bundle.getMessage("WarningTitle"), 168 JmriJOptionPane.YES_NO_OPTION ) != JmriJOptionPane.YES_OPTION ) { 169 return; 170 } 171 String rName; 172 String stringIOPrefix = prefixBox.getSelectedItem().getSystemPrefix(); 173 String curAddress = hardwareAddressTextField.getText(); 174 // initial check for empty entry 175 if (curAddress.isEmpty()) { 176 statusBarLabel.setText(Bundle.getMessage("WarningEmptyHardwareAddress")); 177 statusBarLabel.setForeground(Color.red); 178 hardwareAddressTextField.setBackground(Color.red); 179 return; 180 } else { 181 hardwareAddressTextField.setBackground(Color.white); 182 } 183 184 // Add some entry pattern checking, before assembling sName and handing it to the StringIOManager 185 StringBuilder statusMessage = new StringBuilder( 186 Bundle.getMessage("ItemCreateFeedback", Bundle.getMessage("BeanNameStringIO"))); 187 String uName = userNameTextField.getText(); 188 189 190 // Compose the proposed system name from parts: 191 rName = stringIOPrefix + stringIOManager.typeLetter() + curAddress; 192 log.trace("will create first with name {}", rName); 193 194 for (int x = 0; x < numberOfStringIOs; x++) { 195 196 // create the next StringIO 197 StringIO r; 198 try { 199 r = stringIOManager.provideStringIO(rName); 200 log.trace("created {} from {}", r, stringIOManager); 201 } catch (IllegalArgumentException ex) { 202 // user input no good 203 handleCreateException(ex, rName); // displays message dialog to the user 204 return; // without creating 205 } 206 207 // handle setting user name 208 if (!uName.isEmpty()) { 209 if ((stringIOManager.getByUserName(uName) == null)) { 210 r.setUserName(uName); 211 } else { 212 pref.showErrorMessage(Bundle.getMessage("ErrorTitle"), 213 Bundle.getMessage("ErrorDuplicateUserName", uName), 214 getClassName(), "duplicateUserName", false, true); 215 } 216 } 217 218 // add first and last names to statusMessage user feedback string 219 // only mention first and last of rangeCheckBox added 220 if (x == 0 || x == numberOfStringIOs - 1) { 221 statusMessage.append(" ").append(rName).append(" (").append(uName).append(")"); 222 } 223 if (x == numberOfStringIOs - 2) { 224 statusMessage.append(" ").append(Bundle.getMessage("ItemCreateUpTo")).append(" "); 225 } 226 227 // except on last pass 228 if (x < numberOfStringIOs-1) { 229 // bump system name 230 try { 231 rName = InstanceManager.getDefault(StringIOManager.class).getNextValidSystemName(r); 232 } catch (jmri.JmriException ex) { 233 displayHwError(r.getSystemName(), ex); 234 // directly add to statusBarLabel (but never called?) 235 statusBarLabel.setText(Bundle.getMessage("ErrorConvertHW", rName)); 236 statusBarLabel.setForeground(Color.red); 237 return; 238 } 239 240 // bump user name 241 if (!uName.isEmpty()) { 242 uName = nextName(uName); 243 } 244 } 245 // end of for loop creating rangeCheckBox of StringIOs 246 } 247 // provide success feedback to uName 248 statusBarLabel.setText(statusMessage.toString()); 249 statusBarLabel.setForeground(Color.gray); 250 251 pref.setComboBoxLastSelection(systemSelectionCombo, prefixBox.getSelectedItem().getMemo().getUserName()); 252 removePrefixBoxListener(prefixBox); 253 addFrame.setVisible(false); 254 addFrame.dispose(); 255 addFrame = null; 256 } 257 258 private String addEntryToolTip; 259 260 /** 261 * Activate Add a rangeCheckBox option if manager accepts adding more than 1 262 * StringIO and set a manager specific tooltip on the AddNewHardwareDevice 263 * pane. 264 */ 265 private void canAddRange(ActionEvent e) { 266 rangeCheckBox.setEnabled(false); 267 rangeCheckBox.setSelected(false); 268 if (prefixBox.getSelectedIndex() == -1) { 269 prefixBox.setSelectedIndex(0); 270 } 271 connectionChoice = prefixBox.getSelectedItem(); // store in Field for CheckedTextField 272 String systemPrefix = connectionChoice.getSystemPrefix(); 273 rangeCheckBox.setEnabled(((StringIOManager) connectionChoice).allowMultipleAdditions(systemPrefix)); 274 addEntryToolTip = connectionChoice.getEntryToolTip(); 275 // show hwAddressTextField field tooltip in the Add StringIO pane that matches system connection selected from combobox 276 hardwareAddressTextField.setToolTipText( 277 Bundle.getMessage("AddEntryToolTipLine1", 278 connectionChoice.getMemo().getUserName(), 279 Bundle.getMessage("StringIOs"), 280 addEntryToolTip)); 281 hardwareAddressValidator.setToolTipText(hardwareAddressTextField.getToolTipText()); 282 hardwareAddressValidator.verify(hardwareAddressTextField); 283 } 284 285 void handleCreateException(Exception ex, String sysName) { 286 statusBarLabel.setText(ex.getLocalizedMessage()); 287 statusBarLabel.setForeground(Color.red); 288 String err = Bundle.getMessage("ErrorBeanCreateFailed", 289 InstanceManager.getDefault(StringIOManager.class).getBeanTypeHandled(),sysName); 290 JmriJOptionPane.showMessageDialog(addFrame, err + "\n" + ex.getLocalizedMessage(), 291 err, JmriJOptionPane.ERROR_MESSAGE); 292 } 293 294 /** 295 * {@inheritDoc} 296 */ 297 @Override 298 protected String getClassName() { 299 return StringIOTableAction.class.getName(); 300 } 301 302 /** 303 * {@inheritDoc} 304 */ 305 @Override 306 public String getClassDescription() { 307 return Bundle.getMessage("TitleStringIOTable"); 308 } 309 310 @Override 311 public void setMessagePreferencesDetails() { 312 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 313 setPreferenceItemDetails(getClassName(), "duplicateUserName", Bundle.getMessage("DuplicateUserNameWarn")); // NOI18N 314 super.setMessagePreferencesDetails(); 315 } 316 317 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(StringIOTableAction.class); 318 319}