001package jmri.jmrit.signalling; 002 003import java.awt.BorderLayout; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006import java.beans.PropertyChangeListener; 007import java.util.List; 008import java.util.Set; 009 010import javax.swing.BorderFactory; 011import javax.swing.BoxLayout; 012import javax.swing.JButton; 013import javax.swing.JLabel; 014import javax.swing.JPanel; 015import javax.swing.JScrollPane; 016import javax.swing.JTable; 017import javax.swing.JTextField; 018import javax.swing.SortOrder; 019import javax.swing.table.AbstractTableModel; 020import javax.swing.table.TableCellEditor; 021import javax.swing.table.TableRowSorter; 022 023import jmri.InstanceManager; 024import jmri.SignalMast; 025import jmri.SignalMastLogic; 026import jmri.jmrit.display.EditorManager; 027import jmri.jmrit.display.layoutEditor.LayoutBlockManager; 028import jmri.jmrit.display.layoutEditor.LayoutEditor; 029import jmri.swing.RowSorterUtil; 030import jmri.util.JmriJFrame; 031import jmri.util.swing.JmriJOptionPane; 032import jmri.util.table.ButtonEditor; 033import jmri.util.table.ButtonRenderer; 034 035/** 036 * Frame for the Signal Mast Table - Edit Logic Pane. 037 * 038 * @author Kevin Dickerson Copyright (C) 2011 039 * @author Egbert Broerse Copyright (C) 2017 040 */ 041public class SignallingSourcePanel extends jmri.util.swing.JmriPanel implements PropertyChangeListener { 042 043 SignalMastLogic sml; 044 SignalMast sourceMast; 045 JLabel fixedSourceMastLabel = new JLabel(); 046 047 JButton discoverPairs = new JButton(Bundle.getMessage("ButtonDiscover")); // NOI18N 048 049 SignalMastAspectModel _AppearanceModel; 050 JScrollPane _SignalAppearanceScrollPane; 051 052 /** 053 * Create a Signalling Source configuration Pane showing a list of defined 054 * destination masts and allowing creation of new source-destination pairs 055 * as well as showing a button to start Autodetect configuration. 056 * @param sourceMast The source mast for this SML Source Pairs pane 057 */ 058 public SignallingSourcePanel(final SignalMast sourceMast) { 059 super(); 060 sml = InstanceManager.getDefault(jmri.SignalMastLogicManager.class).getSignalMastLogic(sourceMast); 061 this.sourceMast = sourceMast; 062 fixedSourceMastLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("SourceMast")) + " " + sourceMast.getDisplayName()); // NOI18N 063 if (sml != null) { 064 _signalMastList = sml.getDestinationList(); 065 } 066 067 InstanceManager.getDefault(LayoutBlockManager.class).addPropertyChangeListener(this); 068 InstanceManager.getDefault(jmri.SignalMastLogicManager.class).addPropertyChangeListener(this); 069 070 setLayout(new BorderLayout()); 071 072 JPanel header = new JPanel(); 073 header.setLayout(new BoxLayout(header, BoxLayout.Y_AXIS)); 074 075 JPanel sourcePanel = new JPanel(); 076 sourcePanel.add(fixedSourceMastLabel); 077 header.add(sourcePanel); 078 add(header, BorderLayout.NORTH); 079 080 _AppearanceModel = new SignalMastAspectModel(); 081 JTable table = new JTable(_AppearanceModel); 082 TableRowSorter<SignalMastAspectModel> sorter = new TableRowSorter<>(_AppearanceModel); 083 RowSorterUtil.setSortOrder(sorter, SignalMastAspectModel.SYSNAME_COLUMN, SortOrder.ASCENDING); 084 table.setRowSorter(sorter); 085 table.setRowSelectionAllowed(false); 086 table.setPreferredScrollableViewportSize(new java.awt.Dimension(600, 120)); 087 _AppearanceModel.configureTable(table); 088 _SignalAppearanceScrollPane = new JScrollPane(table); 089 _AppearanceModel.fireTableDataChanged(); 090 add(_SignalAppearanceScrollPane, BorderLayout.CENTER); 091 092 JPanel footer = new JPanel(); 093 094 footer.add(discoverPairs); 095 discoverPairs.addActionListener(new ActionListener() { 096 @Override 097 public void actionPerformed(ActionEvent e) { 098 discoverPressed(e); 099 } 100 }); 101 102 JButton addLogic = new JButton(Bundle.getMessage("AddLogic")); // NOI18N 103 footer.add(addLogic); 104 addLogic.addActionListener(new ActionListener() { 105 @Override 106 public void actionPerformed(ActionEvent e) { 107 class WindowMaker implements Runnable { 108 109 WindowMaker() { 110 } 111 112 @Override 113 public void run() { 114 SignallingAction sigLog = new SignallingAction(); // opens a frame, opens a panel in that frame 115 sigLog.setMast(sourceMast, null); 116 sigLog.actionPerformed(null); 117 // unable to receive changes directly from created panel, so listen to common SML ancestor 118 } 119 } 120 WindowMaker t = new WindowMaker(); 121 javax.swing.SwingUtilities.invokeLater(t); 122 } 123 }); 124 125 add(footer, BorderLayout.SOUTH); 126 } 127 128 /** 129 * Remove references to and from this object, so that it can eventually be 130 * garbage-collected. 131 */ 132 @Override 133 public void dispose() { 134 InstanceManager.getDefault(LayoutBlockManager.class).removePropertyChangeListener(this); 135 InstanceManager.getDefault(jmri.SignalMastLogicManager.class).removePropertyChangeListener(this); 136 super.dispose(); 137 } 138 139 JmriJFrame signalMastLogicFrame = null; 140 JLabel sourceLabel = new JLabel(); 141 142 /** 143 * Respond to the Discover button being pressed. 144 * Check whether AdvancedRouting is turned on and any Layout Editor Panels 145 * are present. For each LE Panel, call discoverSignallingDest() 146 * {@link jmri.SignalMastLogicManager#discoverSignallingDest(SignalMast, LayoutEditor)} 147 * @param e The button event 148 */ 149 void discoverPressed(ActionEvent e) { 150 if (!InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 151 int response = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("EnableLayoutBlockRouting"), 152 Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION); 153 if ( response == JmriJOptionPane.YES_OPTION ) { 154 InstanceManager.getDefault(LayoutBlockManager.class).enableAdvancedRouting(true); 155 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("LayoutBlockRoutingEnabledShort")); // NOI18N 156 } 157 } 158 159 Set<LayoutEditor> layout = InstanceManager.getDefault(EditorManager.class).getAll(LayoutEditor.class); 160 if (!layout.isEmpty()) { 161 signalMastLogicFrame = new JmriJFrame(Bundle.getMessage("DiscoverMastsTitle"), false, false); // NOI18N 162 signalMastLogicFrame.setPreferredSize(null); 163 JPanel panel1 = new JPanel(); 164 sourceLabel = new JLabel(Bundle.getMessage("DiscoveringMasts")); // NOI18N 165 sourceLabel.setBorder(BorderFactory.createEmptyBorder(5, 20, 5, 20)); 166 panel1.add(sourceLabel); 167 signalMastLogicFrame.add(sourceLabel); 168 signalMastLogicFrame.pack(); 169 signalMastLogicFrame.setVisible(true); 170 171 for (LayoutEditor editor : layout) { 172 try { 173 InstanceManager.getDefault(jmri.SignalMastLogicManager.class).discoverSignallingDest(sourceMast, editor); 174 } catch (jmri.JmriException ex) { 175 signalMastLogicFrame.setVisible(false); 176 JmriJOptionPane.showMessageDialog(this, ex.toString()); 177 } 178 } 179 signalMastLogicFrame.setVisible(false); 180 signalMastLogicFrame.dispose(); 181 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("GenComplete")); // NOI18N 182 } else { 183 // don't take the trouble of searching 184 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("GenSkipped")); // NOI18N 185 } 186 } 187 188 /** 189 * Listen for property changes in the SML that's being configured. 190 * @param e The button event 191 */ 192 @Override 193 public void propertyChange(java.beans.PropertyChangeEvent e) { 194 if (e.getPropertyName().equals("autoSignalMastGenerateComplete")) { // NOI18N 195 if (sml == null) { 196 updateDetails(); 197 } 198 log.debug("Generate complete for a LE panel ({}): mast = {}", this.hashCode(), sourceMast); 199 } 200 if (e.getPropertyName().equals("advancedRoutingEnabled")) { // NOI18N 201 boolean newValue = (Boolean) e.getNewValue(); 202 discoverPairs.setEnabled(newValue); 203 } 204 log.debug("SSP 173 Event: {}; Source: {}", e.getPropertyName(), e.toString()); // doesn't get notified, newDestination // NOI18N 205 if (e.getPropertyName().equals("length")) { // redraw the Pairs table // NOI18N 206 updateDetails(); 207 } 208 } 209 210 private List<SignalMast> _signalMastList; 211 212 /** 213 * Refresh the list of destination Signal Masts available for edit in the current SML. 214 */ 215 private void updateDetails() { 216 SignalMastLogic old = sml; 217 sml = InstanceManager.getDefault(jmri.SignalMastLogicManager.class).getSignalMastLogic(sourceMast); 218 if (sml != null) { 219 _signalMastList = sml.getDestinationList(); 220 _AppearanceModel.updateSignalMastLogic(old, sml); 221 } 222 } 223 224 /** 225 * TableModel to store SML control Signal Masts and their Set To Aspect. 226 */ 227 public class SignalMastAspectModel extends AbstractTableModel implements PropertyChangeListener { 228 229 SignalMastAspectModel() { 230 super(); 231 if (sml != null) { 232 sml.addPropertyChangeListener(this); // pick up creation of a new pair in the sml 233 } 234 } 235 236 void updateSignalMastLogic(SignalMastLogic smlOld, SignalMastLogic smlNew) { 237 if (smlOld != null) { 238 smlOld.removePropertyChangeListener(this); 239 } 240 if (smlNew != null) { 241 smlNew.addPropertyChangeListener(this); 242 } 243 fireTableDataChanged(); 244 } 245 246 @Override 247 public Class<?> getColumnClass(int c) { 248 if (c == ACTIVE_COLUMN) { 249 return Boolean.class; 250 } 251 if (c == ENABLE_COLUMN) { 252 return Boolean.class; 253 } 254 if (c == EDIT_COLUMN) { 255 return JButton.class; 256 } 257 if (c == DEL_COLUMN) { 258 return JButton.class; 259 } 260 return String.class; 261 } 262 263 public void configureTable(JTable table) { 264 // allow reordering of the columns 265 table.getTableHeader().setReorderingAllowed(true); 266 267 // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541) 268 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 269 270 // resize columns as requested 271 for (int i = 0; i < table.getColumnCount(); i++) { 272 int width = getPreferredWidth(i); 273 table.getColumnModel().getColumn(i).setPreferredWidth(width); 274 } 275 table.sizeColumnsToFit(-1); 276 277 configEditColumn(table); 278 } 279 280 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", 281 justification="better to keep cases in column order rather than to combine") 282 public int getPreferredWidth(int col) { 283 switch (col) { 284 case SYSNAME_COLUMN: 285 return new JTextField(15).getPreferredSize().width; 286 case ENABLE_COLUMN: 287 case ACTIVE_COLUMN: 288 return new JTextField(5).getPreferredSize().width; 289 case USERNAME_COLUMN: 290 return new JTextField(15).getPreferredSize().width; 291 case EDIT_COLUMN: // not actually used due to the configureTable, setColumnToHoldButton, configureButton 292 return new JTextField(22).getPreferredSize().width; 293 case DEL_COLUMN: // not actually used due to the configureTable, setColumnToHoldButton, configureButton 294 return new JTextField(22).getPreferredSize().width; 295 default: 296 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 297 return new JTextField(8).getPreferredSize().width; 298 } 299 } 300 301 @Override 302 public String getColumnName(int col) { 303 if (col == USERNAME_COLUMN) { 304 return Bundle.getMessage("ColumnUserName"); // NOI18N 305 } 306 if (col == SYSNAME_COLUMN) { 307 return Bundle.getMessage("DestMast"); // NOI18N 308 } 309 if (col == ACTIVE_COLUMN) { 310 return Bundle.getMessage("SensorStateActive"); // "Active" // NOI18N 311 } 312 if (col == ENABLE_COLUMN) { 313 return Bundle.getMessage("ColumnHeadEnabled"); // NOI18N 314 } 315 if (col == EDIT_COLUMN) { 316 return ""; //no title above Edit buttons 317 } 318 if (col == DEL_COLUMN) { 319 return ""; //no title above Delete buttons 320 } 321 return ""; 322 } 323 324 /** 325 * Remove references to and from this object, so that it can eventually be 326 * garbage-collected. 327 */ 328 public void dispose() { 329 if (sml != null) { 330 sml.removePropertyChangeListener(this); 331 } 332 } 333 334 /** 335 * Listen for changes to specific properties of the displayed Signal Masts. 336 * @param e The ChangeEvent heard 337 */ 338 @Override 339 public void propertyChange(java.beans.PropertyChangeEvent e) { 340 if (e.getPropertyName().equals("length")) { // NOI18N 341 // should pick up adding a new destination mast, but doesn't refresh table by itself 342 _signalMastList = sml.getDestinationList(); 343 int length = (Integer) e.getNewValue(); 344 if (length == 0) { 345 sml.removePropertyChangeListener(this); 346 sml = null; 347 } 348 fireTableDataChanged(); 349 } else if (e.getPropertyName().equals("updatedDestination")) { // NOI18N 350 // a new NamedBean is available in the manager 351 _signalMastList = sml.getDestinationList(); 352 fireTableDataChanged(); 353 } else if ((e.getPropertyName().equals("state")) || (e.getPropertyName().equals("Enabled"))) { // NOI18N 354 fireTableDataChanged(); 355 fireTableRowsUpdated(0, _signalMastList.size()-1); 356 } 357 log.debug("SSP 310 Event: {}", e.getPropertyName()); // NOI18N 358 } 359 360 /** 361 * Display buttons in 2 columns of the manual control signal masts table. 362 * @param table The control signal mast table to be configured 363 */ 364 protected void configEditColumn(JTable table) { 365 // have the Delete column hold a button 366 setColumnToHoldButton(table, EDIT_COLUMN, 367 new JButton(Bundle.getMessage("ButtonEdit"))); // NOI18N 368 setColumnToHoldButton(table, DEL_COLUMN, 369 new JButton(Bundle.getMessage("ButtonDelete"))); // NOI18N 370 } 371 372 /** 373 * Helper function for {@link #configEditColumn(JTable)}. 374 * @param table The control signal mast table to be configured 375 * @param column Index for the column to put the button in 376 * @param sample JButton to put there 377 */ 378 protected void setColumnToHoldButton(JTable table, int column, JButton sample) { 379 // install a button renderer & editor 380 ButtonRenderer buttonRenderer = new ButtonRenderer(); 381 table.setDefaultRenderer(JButton.class, buttonRenderer); 382 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 383 table.setDefaultEditor(JButton.class, buttonEditor); 384 // ensure the table rows, columns have enough room for buttons 385 table.setRowHeight(sample.getPreferredSize().height); 386 table.getColumnModel().getColumn(column) 387 .setPreferredWidth((sample.getPreferredSize().width) + 4); 388 } 389 390 /** 391 * Get the number of columns in the signal masts table. 392 * @return Fixed value of 6 393 */ 394 @Override 395 public int getColumnCount() { 396 return 6; 397 } 398 399 /** 400 * Query whether the cells in a table column should respond to clicks. 401 * @param r Index for the cell row 402 * @param c Index for the cell column 403 */ 404 @Override 405 public boolean isCellEditable(int r, int c) { 406 if (c == EDIT_COLUMN) { 407 return true; 408 } 409 if (c == DEL_COLUMN) { 410 return true; 411 } 412 if (c == ENABLE_COLUMN) { 413 return true; 414 } 415 return ((c == USERNAME_COLUMN)); 416 } 417 418 /** 419 * Respond to the Edit Logic button being clicked. 420 * @param r Index for the cell row 421 */ 422 protected void editPair(int r) { 423 424 class WindowMaker implements Runnable { 425 426 int row; 427 428 WindowMaker(int r) { 429 row = r; 430 } 431 432 @Override 433 public void run() { 434 log.debug("SML Edit existing logic started"); // NOI18N 435 SignallingAction sigLog = new SignallingAction(); 436 sigLog.setMast(sourceMast, _signalMastList.get(row)); 437 sigLog.actionPerformed(null); 438 // Note: we cannot tell if Edit pair was cancelled 439 } 440 } 441 WindowMaker t = new WindowMaker(r); 442 javax.swing.SwingUtilities.invokeLater(t); 443 } 444 445 protected void deletePair(int r) { 446 InstanceManager.getDefault(jmri.SignalMastLogicManager.class).removeSignalMastLogic(sml, _signalMastList.get(r)); 447 } 448 449 public static final int SYSNAME_COLUMN = 0; 450 public static final int USERNAME_COLUMN = 1; 451 public static final int ACTIVE_COLUMN = 2; 452 public static final int ENABLE_COLUMN = 3; 453 public static final int EDIT_COLUMN = 4; 454 public static final int DEL_COLUMN = 5; 455 456 public void setSetToState(String x) { 457 } 458 459 /** 460 * Get the number of Included signal masts for this SML. 461 */ 462 @Override 463 public int getRowCount() { 464 if (_signalMastList == null) { 465 return 0; 466 } 467 return _signalMastList.size(); 468 } 469 470 /** 471 * Retrieve the contents to display in a cell in the table, in terms of model 472 * @param r index for the cell row 473 * @param c index for the cell column 474 * @return The value (text) stored in the cell 475 */ 476 @Override 477 public Object getValueAt(int r, int c) { 478 if (sml == null) { 479 return null; 480 } 481 // some error checking 482 if (r >= _signalMastList.size()) { 483 log.debug("row is greater than turnout list size"); // NOI18N 484 return null; 485 } 486 switch (c) { 487 case USERNAME_COLUMN: 488 return _signalMastList.get(r).getUserName(); 489 case SYSNAME_COLUMN: // slot number 490 return _signalMastList.get(r).getSystemName(); 491 case ACTIVE_COLUMN: 492 return sml.isActive(_signalMastList.get(r)); 493 case ENABLE_COLUMN: 494 return sml.isEnabled(_signalMastList.get(r)); 495 case EDIT_COLUMN: 496 return Bundle.getMessage("ButtonEdit"); // NOI18N 497 case DEL_COLUMN: 498 return Bundle.getMessage("ButtonDelete"); // NOI18N 499 default: 500 return null; 501 } 502 } 503 504 /** 505 * Process the contents from a table cell, in terms of model 506 * @param type the object type of the cell contents 507 * @param r index for the cell row 508 * @param c index for the cell column 509 */ 510 @Override 511 public void setValueAt(Object type, int r, int c) { 512 if (c == EDIT_COLUMN) { 513 editPair(r); 514 } else if (c == DEL_COLUMN) { 515 deletePair(r); 516 } else if (c == ENABLE_COLUMN) { 517 boolean b = ((Boolean) type).booleanValue(); 518 if (b) { 519 sml.setEnabled(_signalMastList.get(r)); 520 } else { 521 sml.setDisabled(_signalMastList.get(r)); 522 } 523 524 } 525 } 526 } 527 528 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignallingSourcePanel.class); 529 530}