001package jmri.jmrit.beantable; 002 003import java.awt.*; 004import java.awt.event.ItemEvent; 005import java.beans.PropertyVetoException; 006import java.io.PrintWriter; 007import java.io.StringWriter; 008import java.util.HashMap; 009import java.util.Map; 010 011import javax.annotation.Nonnull; 012import javax.swing.*; 013import javax.swing.table.TableCellEditor; 014import javax.swing.table.TableColumn; 015import javax.swing.tree.TreeCellEditor; 016 017import jmri.*; 018import jmri.jmrit.logixng.*; 019import jmri.jmrit.logixng.tools.swing.AbstractLogixNGEditor; 020import jmri.jmrit.logixng.tools.swing.LogixNGEditor; 021import jmri.util.JmriJFrame; 022import jmri.util.swing.TriStateJCheckBox; 023import jmri.util.swing.XTableColumnModel; 024 025import org.apache.commons.lang3.mutable.MutableInt; 026 027/** 028 * Swing action to create and register a LogixNG Table. 029 * <p> 030 Also contains the panes to create, edit, and delete a LogixNG. 031 <p> 032 * Most of the text used in this GUI is in BeanTableBundle.properties, accessed 033 * via Bundle.getMessage(). 034 * 035 * @author Dave Duchamp Copyright (C) 2007 (LogixTableAction) 036 * @author Pete Cressman Copyright (C) 2009, 2010, 2011 (LogixTableAction) 037 * @author Matthew Harris copyright (c) 2009 (LogixTableAction) 038 * @author Dave Sand copyright (c) 2017 (LogixTableAction) 039 * @author Daniel Bergqvist copyright (c) 2019 040 * @author Dave Sand copyright (c) 2021 041 */ 042public class LogixNGTableAction extends AbstractLogixNGTableAction<LogixNG> { 043 044 /** 045 * Create a LogixNGTableAction instance. 046 * 047 * @param s the Action title, not the title of the resulting frame. Perhaps 048 * this should be changed? 049 */ 050 public LogixNGTableAction(String s) { 051 super(s); 052 } 053 054 /** 055 * Create a LogixNGTableAction instance with default title. 056 */ 057 public LogixNGTableAction() { 058 this(Bundle.getMessage("TitleLogixNGTable")); 059 } 060 061 @Override 062 protected void createModel() { 063 m = new TableModel(); 064 m.setFilter((LogixNG t) -> !t.isInline()); 065 } 066 067 @Override 068 protected AbstractLogixNGEditor<LogixNG> getEditor(BeanTableDataModel<LogixNG> m, String sName) { 069 return new LogixNGEditor(m, sName); 070 } 071 072 @Override 073 protected Manager<LogixNG> getManager() { 074 return InstanceManager.getDefault(LogixNG_Manager.class); 075 } 076 077 @Override 078 protected void setEnabled(LogixNG logixNG, boolean enable) { 079 logixNG.setEnabled(enable); 080 } 081 082 @Override 083 protected boolean isEnabled(LogixNG logixNG) { 084 return logixNG.isEnabled(); 085 } 086 087 @Override 088 protected void enableAll(boolean enable) { 089 for (LogixNG x : getManager().getNamedBeanSet()) { 090 x.setEnabled(enable); 091 } 092 m.fireTableDataChanged(); 093 } 094 095 @Override 096 protected LogixNG createBean(String userName) { 097 LogixNG logixNG = 098 InstanceManager.getDefault(LogixNG_Manager.class) 099 .createLogixNG(userName); 100 logixNG.activate(); 101 logixNG.setEnabled(true); 102 logixNG.clearStartup(); 103 return logixNG; 104 } 105 106 @Override 107 protected LogixNG createBean(String systemName, String userName) { 108 LogixNG logixNG = 109 InstanceManager.getDefault(LogixNG_Manager.class) 110 .createLogixNG(systemName, userName); 111 logixNG.activate(); 112 logixNG.setEnabled(true); 113 logixNG.clearStartup(); 114 return logixNG; 115 } 116 117 @Override 118 public void deleteBean(LogixNG logixNG) { 119 logixNG.setEnabled(false); 120 try { 121 InstanceManager.getDefault(LogixNG_Manager.class).deleteBean(logixNG, "DoDelete"); 122 } catch (PropertyVetoException e) { 123 //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto 124 log.error("{} : Could not Delete.", e.getMessage()); 125 } 126 } 127 128 private void copyConditionalNGToLogixNG( 129 @Nonnull ConditionalNG sourceConditionalNG, 130 @Nonnull LogixNG targetBean) { 131 132 // Create ConditionalNG 133 String sysName = InstanceManager.getDefault(ConditionalNG_Manager.class).getAutoSystemName(); 134 String oldUserName = sourceConditionalNG.getUserName(); 135 String userName = oldUserName != null ? Bundle.getMessage("CopyOfConditionalNG", oldUserName) : null; 136 ConditionalNG targetConditionalNG = 137 InstanceManager.getDefault(ConditionalNG_Manager.class) 138 .createConditionalNG(targetBean, sysName, userName); 139 140 sourceConditionalNG.getFemaleSocket().unregisterListeners(); 141 targetConditionalNG.getFemaleSocket().unregisterListeners(); 142 Map<String, String> systemNames = new HashMap<>(); 143 Map<String, String> userNames = new HashMap<>(); 144 try { 145 FemaleSocket femaleSourceSocket = sourceConditionalNG.getFemaleSocket(); 146 if (femaleSourceSocket.isConnected()) { 147 targetConditionalNG.getFemaleSocket().connect( 148 (MaleSocket) femaleSourceSocket.getConnectedSocket() 149 .getDeepCopy(systemNames, userNames)); 150 } 151 } catch (JmriException ex) { 152 log.error("Could not Copy ConditionalNG.", ex); 153 } 154 sourceConditionalNG.getFemaleSocket().registerListeners(); 155 targetConditionalNG.getFemaleSocket().registerListeners(); 156 } 157 158 @Override 159 protected void copyBean(@Nonnull LogixNG sourceBean, @Nonnull LogixNG targetBean) { 160 for (int i = 0; i < sourceBean.getNumConditionalNGs(); i++) { 161 copyConditionalNGToLogixNG(sourceBean.getConditionalNG(i), targetBean); 162 } 163 } 164 165 @Override 166 protected boolean isCopyBeanSupported() { 167 return true; 168 } 169 170 @Override 171 protected boolean isExecuteSupported() { 172 return true; 173 } 174 175 @Override 176 protected void execute(@Nonnull LogixNG logixNG) { 177 if (!logixNG.isActivated() && !logixNG.isEnabled()) { 178 JOptionPane.showMessageDialog(f, Bundle.getMessage("LogixNG_CantExecuteLogixNG_InactiveAndNotEnabled"), Bundle.getMessage("LogixNG_Error"), JOptionPane.ERROR_MESSAGE); 179 } else if (!logixNG.isActivated()) { 180 JOptionPane.showMessageDialog(f, Bundle.getMessage("LogixNG_CantExecuteLogixNG_Inactive"), Bundle.getMessage("LogixNG_Error"), JOptionPane.ERROR_MESSAGE); 181 } else if (!logixNG.isEnabled()) { 182 JOptionPane.showMessageDialog(f, Bundle.getMessage("LogixNG_CantExecuteLogixNG_NotEnabled"), Bundle.getMessage("LogixNG_Error"), JOptionPane.ERROR_MESSAGE); 183 } else { 184 logixNG.execute(); 185 } 186 } 187 188 @Override 189 protected String getBeanText(LogixNG e, Base.PrintTreeSettings printTreeSettings) { 190 StringWriter writer = new StringWriter(); 191 _curNamedBean.printTree(printTreeSettings, new PrintWriter(writer), " ", new MutableInt(0)); 192 return writer.toString(); 193 } 194 195 @Override 196 protected String getBrowserTitle() { 197 return Bundle.getMessage("LogixNG_Browse_Title"); 198 } 199 200 @Override 201 protected String getAddTitleKey() { 202 return "TitleAddLogixNG"; 203 } 204 205 @Override 206 protected String getCreateButtonHintKey() { 207 return "LogixNGCreateButtonHint"; 208 } 209 210 /** 211 * Create or copy bean frame. 212 * 213 * @param titleId property key to fetch as title of the frame (using Bundle) 214 * @param startMessageId part 1 of property key to fetch as user instruction on 215 * pane, either 1 or 2 is added to form the whole key 216 * @return the button JPanel 217 */ 218 @Override 219 protected JPanel makeAddFrame(String titleId, String startMessageId) { 220 addLogixNGFrame = new JmriJFrame(Bundle.getMessage(titleId)); 221 addLogixNGFrame.addHelpMenu( 222 "package.jmri.jmrit.beantable.LogixNGTable", true); // NOI18N 223 addLogixNGFrame.setLocation(50, 30); 224 Container contentPane = addLogixNGFrame.getContentPane(); 225 contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); 226 227 JPanel p; 228 p = new JPanel(); 229 p.setLayout(new FlowLayout()); 230 p.setLayout(new java.awt.GridBagLayout()); 231 java.awt.GridBagConstraints c = new java.awt.GridBagConstraints(); 232 c.gridwidth = 1; 233 c.gridheight = 1; 234 c.gridx = 0; 235 c.gridy = 0; 236 c.anchor = java.awt.GridBagConstraints.EAST; 237 p.add(_sysNameLabel, c); 238 _sysNameLabel.setLabelFor(_systemName); 239 c.gridy = 1; 240 p.add(_userNameLabel, c); 241 _userNameLabel.setLabelFor(_addUserName); 242 c.gridx = 1; 243 c.gridy = 0; 244 c.anchor = java.awt.GridBagConstraints.WEST; 245 c.weightx = 1.0; 246 c.fill = java.awt.GridBagConstraints.HORIZONTAL; // text field will expand 247 p.add(_systemName, c); 248 c.gridy = 1; 249 p.add(_addUserName, c); 250 c.gridx = 2; 251 c.gridy = 1; 252 c.anchor = java.awt.GridBagConstraints.WEST; 253 c.weightx = 1.0; 254 c.fill = java.awt.GridBagConstraints.HORIZONTAL; // text field will expand 255 c.gridy = 0; 256 p.add(_autoSystemName, c); 257 _addUserName.setToolTipText(Bundle.getMessage("LogixNGUserNameHint")); // NOI18N 258 _systemName.setToolTipText(Bundle.getMessage("LogixNGSystemNameHint")); // NOI18N 259 contentPane.add(p); 260 // set up message 261 JPanel panel3 = new JPanel(); 262 panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS)); 263 JPanel panel31 = new JPanel(); 264 panel31.setLayout(new FlowLayout()); 265 JLabel message1 = new JLabel(Bundle.getMessage(startMessageId + "LogixNGMessage1")); // NOI18N 266 panel31.add(message1); 267 JPanel panel32 = new JPanel(); 268 JLabel message2 = new JLabel(Bundle.getMessage(startMessageId + "LogixNGMessage2")); // NOI18N 269 panel32.add(message2); 270 panel3.add(panel31); 271 panel3.add(panel32); 272 contentPane.add(panel3); 273 274 // set up create and cancel buttons 275 JPanel panel5 = new JPanel(); 276 panel5.setLayout(new FlowLayout()); 277 // Cancel 278 JButton cancel = new JButton(Bundle.getMessage("ButtonCancel")); // NOI18N 279 panel5.add(cancel); 280 cancel.addActionListener(this::cancelAddPressed); 281 cancel.setToolTipText(Bundle.getMessage("CancelLogixNGButtonHint")); // NOI18N 282 283 addLogixNGFrame.addWindowListener(new java.awt.event.WindowAdapter() { 284 @Override 285 public void windowClosing(java.awt.event.WindowEvent e) { 286 cancelAddPressed(null); 287 } 288 }); 289 contentPane.add(panel5); 290 291 _autoSystemName.addItemListener((ItemEvent e) -> { 292 autoSystemName(); 293 }); 294 return panel5; 295 } 296 297 @Override 298 protected void getListenerRefsIncludingChildren(LogixNG logixNG, java.util.List<String> list) { 299 logixNG.getListenerRefsIncludingChildren(list); 300 } 301 302 @Override 303 protected boolean hasChildren(LogixNG logixNG) { 304 return logixNG.getNumConditionalNGs() > 0; 305 } 306 307 308 protected class TableModel extends AbstractLogixNGTableAction<LogixNG>.TableModel { 309 310 // overlay the state column with the edit column 311 static public final int STARTUP_COL = NUMCOLUMN; 312 313 /** {@inheritDoc} */ 314 @Override 315 public void configureTable(JTable table) { 316 super.configureTable(table); 317 318 table.setDefaultRenderer(TriStateJCheckBox.State.class, new EnablingTriStateCheckboxRenderer()); 319 320 TriStateJCheckBox startupCheckBox = new TriStateJCheckBox(); 321 TableColumn col = ((XTableColumnModel)table.getColumnModel()) 322 .getColumnByModelIndex(TableModel.STARTUP_COL); 323 col.setCellEditor(new CellEditor(startupCheckBox)); 324 } 325 326 /** {@inheritDoc} */ 327 @Override 328 public int getColumnCount() { 329 return STARTUP_COL + 1; 330 } 331 332 @Override 333 public String getColumnName(int col) { 334 if (col == STARTUP_COL) { 335 return Bundle.getMessage("ColumnLogixNGStartup"); 336 } 337 return super.getColumnName(col); 338 } 339 340 @Override 341 public Class<?> getColumnClass(int col) { 342 if (col == STARTUP_COL) { 343 return TriStateJCheckBox.State.class; 344 } 345 return super.getColumnClass(col); 346 } 347 348 @Override 349 public int getPreferredWidth(int col) { 350 // override default value for SystemName and UserName columns 351 if (col == STARTUP_COL) { 352 return new JTextField(5).getPreferredSize().width; 353 } 354 return super.getPreferredWidth(col); 355 } 356 357 @Override 358 public boolean isCellEditable(int row, int col) { 359 if (col == STARTUP_COL) { 360 return true; 361 } 362 return super.isCellEditable(row, col); 363 } 364 365 @SuppressWarnings("unchecked") // Unchecked cast from Object to E 366 @Override 367 public Object getValueAt(int row, int col) { 368 if (col == STARTUP_COL) { 369 LogixNG x = (LogixNG) getValueAt(row, SYSNAMECOL); 370 if (x == null) { 371 return null; 372 } 373 boolean anyTrue = false; 374 boolean anyFalse = false; 375 for (int i=0; i < x.getNumConditionalNGs(); i++) { 376 ConditionalNG cng = x.getConditionalNG(i); 377 if (cng.isExecuteAtStartup()) anyTrue = true; 378 else anyFalse = true; 379 } 380 if (anyTrue && anyFalse) { 381 return TriStateJCheckBox.State.PARTIAL; 382 } else if (anyTrue) { 383 return TriStateJCheckBox.State.CHECKED; 384 } else { 385 return TriStateJCheckBox.State.UNCHECKED; 386 } 387 } else { 388 return super.getValueAt(row, col); 389 } 390 } 391 392 @SuppressWarnings("unchecked") // Unchecked cast from Object to E 393 @Override 394 public void setValueAt(Object value, int row, int col) { 395 if (col == STARTUP_COL) { 396 // alternate 397 LogixNG x = (LogixNG) getValueAt(row, SYSNAMECOL); 398 399 for (int i=0; i < x.getNumConditionalNGs(); i++) { 400 ConditionalNG cng = x.getConditionalNG(i); 401 cng.setExecuteAtStartup(value == TriStateJCheckBox.State.CHECKED); 402 } 403 } else { 404 super.setValueAt(value, row, col); 405 } 406 } 407 408 @Override 409 public String getValue(String s) { 410 return ""; 411 } 412 } 413 414 private static class CellEditor extends AbstractCellEditor 415 implements TableCellEditor, TreeCellEditor { 416 417 TriStateJCheckBox _tristateCheckBox; 418 419 public CellEditor(TriStateJCheckBox tristateCheckBox) { 420 this._tristateCheckBox = tristateCheckBox; 421 } 422 423 @Override 424 public Object getCellEditorValue() { 425 return _tristateCheckBox.getState(); 426 } 427 428 @Override 429 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 430 return _tristateCheckBox; 431 } 432 433 @Override 434 public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { 435 return _tristateCheckBox; 436 } 437 438 } 439 440 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNGTableAction.class); 441 442}