001package jmri.jmrit.beantable; 002 003import java.awt.Color; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006 007import javax.annotation.Nonnull; 008import javax.swing.*; 009 010import jmri.Block; 011import jmri.InstanceManager; 012import jmri.Manager; 013import jmri.NamedBean; 014import jmri.UserPreferencesManager; 015import jmri.jmrit.beantable.block.BlockTableDataModel; 016import jmri.BlockManager; 017import jmri.util.JmriJFrame; 018 019import org.slf4j.Logger; 020import org.slf4j.LoggerFactory; 021 022/** 023 * Swing action to create and register a BlockTable GUI. 024 * 025 * @author Bob Jacobsen Copyright (C) 2003, 2008 026 * @author Egbert Broerse Copyright (C) 2017 027 */ 028public class BlockTableAction extends AbstractTableAction<Block> { 029 030 /** 031 * Create an action with a specific title. 032 * <p> 033 * Note that the argument is the Action title, not the title of the 034 * resulting frame. Perhaps this should be changed? 035 * 036 * @param actionName the Action title 037 */ 038 public BlockTableAction(String actionName) { 039 super(actionName); 040 041 // disable ourself if there is no primary Block manager available 042 if (jmri.InstanceManager.getNullableDefault(jmri.BlockManager.class) == null) { 043 BlockTableAction.this.setEnabled(false); 044 } 045 } 046 047 public BlockTableAction() { 048 this(Bundle.getMessage("TitleBlockTable")); 049 } 050 051 /** 052 * Create the JTable DataModel, along with the changes for the specific case 053 * of Block objects. 054 */ 055 @Override 056 protected void createModel() { 057 m = new BlockTableDataModel(getManager()); 058 } 059 060 @Nonnull 061 @Override 062 protected Manager<Block> getManager() { 063 return InstanceManager.getDefault(BlockManager.class); 064 } 065 066 @Override 067 protected void setTitle() { 068 f.setTitle(Bundle.getMessage("TitleBlockTable")); // NOI18N 069 } 070 071 private final JRadioButton inchBox = new JRadioButton(Bundle.getMessage("LengthInches")); // NOI18N 072 private final JRadioButton centimeterBox = new JRadioButton(Bundle.getMessage("LengthCentimeters")); // NOI18N 073 public final static String BLOCK_METRIC_PREF = BlockTableAction.class.getName() + ":LengthUnitMetric"; // NOI18N 074 075 private void initRadioButtons(){ 076 077 inchBox.setToolTipText(Bundle.getMessage("InchBoxToolTip")); // NOI18N 078 centimeterBox.setToolTipText(Bundle.getMessage("CentimeterBoxToolTip")); // NOI18N 079 080 ButtonGroup group = new ButtonGroup(); 081 group.add(inchBox); 082 group.add(centimeterBox); 083 inchBox.setSelected(true); 084 centimeterBox.setSelected( InstanceManager.getDefault(UserPreferencesManager.class) 085 .getSimplePreferenceState(BLOCK_METRIC_PREF)); 086 087 inchBox.addActionListener(this::metricSelectionChanged); 088 centimeterBox.addActionListener(this::metricSelectionChanged); 089 090 // disabling keyboard input as when focused, does not fire actionlistener 091 // and appears selected causing mismatch with button selected and what the table thinks is selected. 092 inchBox.setFocusable(false); 093 centimeterBox.setFocusable(false); 094 } 095 096 /** 097 * Add the radioButtons (only 1 may be selected). 098 */ 099 @Override 100 public void addToFrame(BeanTableFrame<Block> f) { 101 initRadioButtons(); 102 f.addToBottomBox(inchBox, this.getClass().getName()); 103 f.addToBottomBox(centimeterBox, this.getClass().getName()); 104 } 105 106 /** 107 * Insert 2 table specific menus. 108 * <p> 109 * Account for the Window and Help menus, 110 * which are already added to the menu bar as part of the creation of the 111 * JFrame, by adding the menus 2 places earlier unless the table is part of 112 * the ListedTableFrame, that adds the Help menu later on. 113 * 114 * @param f the JFrame of this table 115 */ 116 @Override 117 public void setMenuBar(BeanTableFrame<Block> f) { 118 final jmri.util.JmriJFrame finalF = f; // needed for anonymous ActionListener class 119 JMenuBar menuBar = f.getJMenuBar(); 120 int pos = menuBar.getMenuCount() - 1; // count the number of menus to insert the TableMenus before 'Window' and 'Help' 121 int offset = 1; 122 log.debug("setMenuBar number of menu items = {}", pos); 123 for (int i = 0; i <= pos; i++) { 124 if (menuBar.getComponent(i) instanceof JMenu) { 125 if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) { 126 offset = -1; // correct for use as part of ListedTableAction where the Help Menu is not yet present 127 } 128 } 129 } 130 131 JMenu pathMenu = new JMenu(Bundle.getMessage("MenuPaths")); 132 JMenuItem item = new JMenuItem(Bundle.getMessage("MenuItemDeletePaths")); 133 pathMenu.add(item); 134 item.addActionListener((ActionEvent e) -> { 135 deletePaths(finalF); 136 }); 137 menuBar.add(pathMenu, pos + offset); 138 139 JMenu speedMenu = new JMenu(Bundle.getMessage("SpeedsMenu")); 140 item = new JMenuItem(Bundle.getMessage("SpeedsMenuItemDefaults")); 141 speedMenu.add(item); 142 item.addActionListener((ActionEvent e) -> { 143 ((BlockTableDataModel)m).setDefaultSpeeds(finalF); 144 }); 145 menuBar.add(speedMenu, pos + offset + 1); // put it to the right of the Paths menu 146 } 147 148 private void metricSelectionChanged(ActionEvent e) { 149 InstanceManager.getDefault(UserPreferencesManager.class) 150 .setSimplePreferenceState(BLOCK_METRIC_PREF, centimeterBox.isSelected()); 151 ((BlockTableDataModel)m).setMetric(centimeterBox.isSelected()); 152 } 153 154 @Override 155 protected String helpTarget() { 156 return "package.jmri.jmrit.beantable.BlockTable"; 157 } 158 159 JmriJFrame addFrame = null; 160 JTextField sysName = new JTextField(20); 161 JTextField userName = new JTextField(20); 162 JLabel sysNameLabel = new JLabel(Bundle.getMessage("LabelSystemName")); 163 JLabel userNameLabel = new JLabel(Bundle.getMessage("LabelUserName")); 164 165 SpinnerNumberModel numberToAddSpinnerNumberModel = new SpinnerNumberModel(1, 1, 100, 1); // maximum 100 items 166 JSpinner numberToAddSpinner = new JSpinner(numberToAddSpinnerNumberModel); 167 JCheckBox addRangeCheckBox = new JCheckBox(Bundle.getMessage("AddRangeBox")); 168 JCheckBox _autoSystemNameCheckBox = new JCheckBox(Bundle.getMessage("LabelAutoSysName")); 169 JLabel statusBar = new JLabel(Bundle.getMessage("AddBeanStatusEnter"), JLabel.LEADING); 170 private JButton newButton = null; 171 172 @Override 173 protected void addPressed(ActionEvent e) { 174 if (addFrame == null) { 175 addFrame = new JmriJFrame(Bundle.getMessage("TitleAddBlock"), false, true); 176 addFrame.setEscapeKeyClosesWindow(true); 177 addFrame.addHelpMenu("package.jmri.jmrit.beantable.BlockAddEdit", true); // NOI18N 178 addFrame.getContentPane().setLayout(new BoxLayout(addFrame.getContentPane(), BoxLayout.Y_AXIS)); 179 ActionListener oklistener = this::okPressed; 180 ActionListener cancellistener = this::cancelPressed; 181 182 AddNewBeanPanel anbp = new AddNewBeanPanel(sysName, userName, numberToAddSpinner, addRangeCheckBox, _autoSystemNameCheckBox, "ButtonCreate", oklistener, cancellistener, statusBar); 183 addFrame.add(anbp); 184 newButton = anbp.ok; 185 sysName.setToolTipText(Bundle.getMessage("SysNameToolTip", "B")); // override tooltip with bean specific letter 186 } 187 sysName.setBackground(Color.white); 188 // reset statusBar text 189 statusBar.setText(Bundle.getMessage("AddBeanStatusEnter")); 190 statusBar.setForeground(Color.gray); 191 if (InstanceManager.getDefault(jmri.UserPreferencesManager.class).getSimplePreferenceState(systemNameAuto)) { 192 _autoSystemNameCheckBox.setSelected(true); 193 } 194 if (newButton!=null){ 195 addFrame.getRootPane().setDefaultButton(newButton); 196 } 197 addRangeCheckBox.setSelected(false); 198 addFrame.pack(); 199 addFrame.setVisible(true); 200 } 201 202 String systemNameAuto = this.getClass().getName() + ".AutoSystemName"; 203 204 void cancelPressed(ActionEvent e) { 205 addFrame.setVisible(false); 206 addFrame.dispose(); 207 addFrame = null; 208 } 209 210 /** 211 * Respond to Create new item pressed on Add Block pane. 212 * 213 * @param e the click event 214 */ 215 void okPressed(ActionEvent e) { 216 217 int numberOfBlocks = 1; 218 219 if (addRangeCheckBox.isSelected()) { 220 numberOfBlocks = (Integer) numberToAddSpinner.getValue(); 221 } 222 if (numberOfBlocks >= 65) { // limited by JSpinnerModel to 100 223 if (JOptionPane.showConfirmDialog(addFrame, 224 Bundle.getMessage("WarnExcessBeans", Bundle.getMessage("Blocks"), numberOfBlocks), 225 Bundle.getMessage("WarningTitle"), 226 JOptionPane.YES_NO_OPTION) == 1) { 227 return; 228 } 229 } 230 String user = NamedBean.normalizeUserName(userName.getText()); 231 if (user == null || user.isEmpty()) { 232 user = null; 233 } 234 String uName = user; // keep result separate to prevent recursive manipulation 235 String system = ""; 236 237 if (!_autoSystemNameCheckBox.isSelected()) { 238 system = InstanceManager.getDefault(jmri.BlockManager.class).makeSystemName(sysName.getText()); 239 } 240 String sName = system; // keep result separate to prevent recursive manipulation 241 // initial check for empty entry using the raw name 242 if (sName.length() < 3 && !_autoSystemNameCheckBox.isSelected()) { // Using 3 to catch a plain IB 243 statusBar.setText(Bundle.getMessage("WarningSysNameEmpty")); 244 statusBar.setForeground(Color.red); 245 sysName.setBackground(Color.red); 246 return; 247 } else { 248 sysName.setBackground(Color.white); 249 } 250 251 // Add some entry pattern checking, before assembling sName and handing it to the blockManager 252 StringBuilder statusMessage = new StringBuilder(Bundle.getMessage("ItemCreateFeedback", Bundle.getMessage("BeanNameBlock"))); 253 254 for (int x = 0; x < numberOfBlocks; x++) { 255 if (x != 0) { // start at 2nd Block 256 if (!_autoSystemNameCheckBox.isSelected()) { 257 // Find first block with unused system name 258 while (true) { 259 system = nextName(system); 260 // log.warn("Trying " + system); 261 Block blk = InstanceManager.getDefault(jmri.BlockManager.class).getBySystemName(system); 262 if (blk == null) { 263 sName = system; 264 break; 265 } 266 } 267 } 268 if (user != null) { 269 // Find first block with unused user name 270 while (true) { 271 user = nextName(user); 272 //log.warn("Trying " + user); 273 Block blk = InstanceManager.getDefault(jmri.BlockManager.class).getByUserName(user); 274 if (blk == null) { 275 uName = user; 276 break; 277 } 278 } 279 } 280 } 281 Block blk; 282 String xName = ""; 283 try { 284 if (_autoSystemNameCheckBox.isSelected()) { 285 blk = InstanceManager.getDefault(jmri.BlockManager.class).createNewBlock(uName); 286 if (blk == null) { 287 xName = uName; 288 throw new java.lang.IllegalArgumentException(); 289 } 290 } else { 291 blk = InstanceManager.getDefault(jmri.BlockManager.class).createNewBlock(sName, uName); 292 if (blk == null) { 293 xName = sName; 294 throw new java.lang.IllegalArgumentException(); 295 } 296 } 297 } catch (IllegalArgumentException ex) { 298 // user input no good 299 handleCreateException(xName); 300 statusBar.setText(Bundle.getMessage("ErrorAddFailedCheck")); 301 statusBar.setForeground(Color.red); 302 return; // without creating 303 } 304 305 // add first and last names to statusMessage user feedback string 306 if (x == 0 || x == numberOfBlocks - 1) { 307 statusMessage.append(" ").append(sName).append(" (").append(user).append(")"); 308 } 309 if (x == numberOfBlocks - 2) { 310 statusMessage.append(" ").append(Bundle.getMessage("ItemCreateUpTo")).append(" "); 311 } 312 // only mention first and last of addRangeCheckBox added 313 } // end of for loop creating addRangeCheckBox of Blocks 314 315 // provide feedback to user 316 statusBar.setText(statusMessage.toString()); 317 statusBar.setForeground(Color.gray); 318 319 InstanceManager.getDefault(UserPreferencesManager.class) 320 .setSimplePreferenceState(systemNameAuto, _autoSystemNameCheckBox.isSelected()); 321 } 322 323 void handleCreateException(String sysName) { 324 JOptionPane.showMessageDialog(addFrame, 325 Bundle.getMessage("ErrorBlockAddFailed", sysName) + "\n" + Bundle.getMessage("ErrorAddFailedCheck"), 326 Bundle.getMessage("ErrorTitle"), 327 JOptionPane.ERROR_MESSAGE); 328 } 329 //private boolean noWarn = false; 330 331 void deletePaths(jmri.util.JmriJFrame f) { 332 // Set option to prevent the path information from being saved. 333 334 Object[] options = {Bundle.getMessage("ButtonRemove"), 335 Bundle.getMessage("ButtonKeep")}; 336 337 int retval = JOptionPane.showOptionDialog(f, 338 Bundle.getMessage("BlockPathMessage"), 339 Bundle.getMessage("BlockPathSaveTitle"), 340 JOptionPane.YES_NO_OPTION, 341 JOptionPane.QUESTION_MESSAGE, null, options, options[1]); 342 if (retval != 0) { 343 InstanceManager.getDefault(jmri.BlockManager.class).setSavedPathInfo(true); 344 log.info("Requested to save path information via Block Menu."); 345 } else { 346 InstanceManager.getDefault(jmri.BlockManager.class).setSavedPathInfo(false); 347 log.info("Requested not to save path information via Block Menu."); 348 } 349 } 350 351 @Override 352 public String getClassDescription() { 353 return Bundle.getMessage("TitleBlockTable"); 354 } 355 356 @Override 357 protected String getClassName() { 358 return BlockTableAction.class.getName(); 359 } 360 361 private final static Logger log = LoggerFactory.getLogger(BlockTableAction.class); 362 363}