001package jmri.jmrit.beantable.signalmast; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.event.ActionEvent; 006import java.beans.PropertyChangeListener; 007import java.util.ArrayList; 008import java.util.HashSet; 009import java.util.Set; 010 011import javax.swing.BorderFactory; 012import javax.swing.BoxLayout; 013import javax.swing.JButton; 014import javax.swing.JLabel; 015import javax.swing.JPanel; 016import javax.swing.JScrollPane; 017import javax.swing.JTable; 018import javax.swing.JTextField; 019import javax.swing.SortOrder; 020import javax.swing.border.TitledBorder; 021import javax.swing.table.AbstractTableModel; 022import javax.swing.table.TableCellEditor; 023import javax.swing.table.TableRowSorter; 024 025import jmri.InstanceManager; 026import jmri.JmriException; 027import jmri.SignalMast; 028import jmri.SignalMastManager; 029import jmri.implementation.SignalMastRepeater; 030import jmri.managers.DefaultSignalMastManager; 031import jmri.swing.NamedBeanComboBox; 032import jmri.swing.RowSorterUtil; 033import jmri.util.JmriJFrame; 034import jmri.util.swing.JComboBoxUtil; 035import jmri.util.swing.JmriPanel; 036import jmri.util.swing.JmriJOptionPane; 037import jmri.util.table.ButtonEditor; 038import jmri.util.table.ButtonRenderer; 039 040/** 041 * Frame for Signal Mast Add / Edit Panel 042 * 043 * @author Kevin Dickerson Copyright (C) 2011 044 */ 045public class SignalMastRepeaterPanel extends JmriPanel implements PropertyChangeListener { 046 047 final DefaultSignalMastManager dsmm; 048 049 SignalMastRepeaterModel _RepeaterModel; 050 JScrollPane _SignalAppearanceScrollPane; 051 NamedBeanComboBox<SignalMast> _MasterBox; 052 NamedBeanComboBox<SignalMast> _SlaveBox; 053 JButton _addRepeater; 054 055 public SignalMastRepeaterPanel() { 056 super(); 057 dsmm = (DefaultSignalMastManager) InstanceManager.getDefault(SignalMastManager.class); 058 init(); 059 } 060 061 final void init() { 062 dsmm.addPropertyChangeListener(this); 063 064 setLayout(new BorderLayout()); 065 066 JPanel header = new JPanel(); 067 header.setLayout(new BoxLayout(header, BoxLayout.Y_AXIS)); 068 069 JPanel sourcePanel = new JPanel(); 070 071 header.add(sourcePanel); 072 add(header, BorderLayout.NORTH); 073 074 _RepeaterModel = new SignalMastRepeaterModel(); 075 JTable _RepeaterTable = new JTable(_RepeaterModel); 076 077 TableRowSorter<SignalMastRepeaterModel> sorter = new TableRowSorter<>(_RepeaterModel); // leave default sorting 078 RowSorterUtil.setSortOrder(sorter, SignalMastRepeaterModel.DIR_COLUMN, SortOrder.ASCENDING); 079 _RepeaterTable.setRowSorter(sorter); 080 081 _RepeaterTable.setRowSelectionAllowed(false); 082 _RepeaterTable.setPreferredScrollableViewportSize(new java.awt.Dimension(526, 120)); 083 _RepeaterModel.configureTable(_RepeaterTable); 084 _SignalAppearanceScrollPane = new JScrollPane(_RepeaterTable); 085 _RepeaterModel.fireTableDataChanged(); 086 add(_SignalAppearanceScrollPane, BorderLayout.CENTER); 087 088 JPanel footer = new JPanel(); 089 updateDetails(); 090 091 _MasterBox = new NamedBeanComboBox<>(dsmm); 092 _MasterBox.addActionListener((ActionEvent e) -> { 093 setSlaveBoxLists(); 094 }); 095 JComboBoxUtil.setupComboBoxMaxRows(_MasterBox); 096 097 _SlaveBox = new NamedBeanComboBox<>(dsmm); 098 JComboBoxUtil.setupComboBoxMaxRows(_SlaveBox); 099 _SlaveBox.setEnabled(false); 100 footer.add(new JLabel(Bundle.getMessage("Master") + " : ")); 101 footer.add(_MasterBox); 102 footer.add(new JLabel(Bundle.getMessage("Slave") + " : ")); 103 footer.add(_SlaveBox); 104 _addRepeater = new JButton(Bundle.getMessage("ButtonAddText")); 105 _addRepeater.setEnabled(false); 106 _addRepeater.addActionListener((ActionEvent e) -> { 107 SignalMastRepeater rp = new SignalMastRepeater(_MasterBox.getSelectedItem(), _SlaveBox.getSelectedItem()); 108 try { 109 dsmm.addRepeater(rp); 110 } catch (JmriException ex) { 111 String error = java.text.MessageFormat.format(Bundle.getMessage("MessageAddFailed"), 112 new Object[]{_MasterBox.getSelectedItemDisplayName(), _SlaveBox.getSelectedItemDisplayName()}); 113 log.error("Failed to add Repeater. {} {}", error, ex.getMessage()); 114 JmriJOptionPane.showMessageDialog(this, error, 115 Bundle.getMessage("TitleAddFailed"), JmriJOptionPane.ERROR_MESSAGE); 116 } 117 }); 118 footer.add(_addRepeater); 119 120 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 121 border.setTitle(Bundle.getMessage("AddRepeater")); 122 footer.setBorder(border); 123 124 add(footer, BorderLayout.SOUTH); 125 } 126 127 void setSlaveBoxLists() { 128 SignalMast masterMast = _MasterBox.getSelectedItem(); 129 if (masterMast == null) { 130 _SlaveBox.setEnabled(false); 131 _addRepeater.setEnabled(false); 132 return; 133 } 134 java.util.Iterator<SignalMast> iter 135 = dsmm.getNamedBeanSet().iterator(); 136 137 // don't return an element if there are not sensors to include 138 if (!iter.hasNext()) { 139 return; 140 } 141 Set<SignalMast> excludedSignalMasts = new HashSet<>(); 142 while (iter.hasNext()) { 143 SignalMast s = iter.next(); 144 if (s.getAppearanceMap() != masterMast.getAppearanceMap()) { 145 excludedSignalMasts.add(s); 146 } else if (s == masterMast) { 147 excludedSignalMasts.add(s); 148 } 149 } 150 _SlaveBox.setExcludedItems(excludedSignalMasts); 151 if (excludedSignalMasts.size() == dsmm.getNamedBeanSet().size()) { 152 _SlaveBox.setEnabled(false); 153 _addRepeater.setEnabled(false); 154 } else { 155 _SlaveBox.setEnabled(true); 156 _addRepeater.setEnabled(true); 157 } 158 } 159 160 JmriJFrame signalMastLogicFrame = null; 161 JLabel sourceLabel = new JLabel(); 162 163 @Override 164 public void propertyChange(java.beans.PropertyChangeEvent e) { 165 } 166 167 private ArrayList<SignalMastRepeater> _signalMastRepeaterList; 168 169 private void updateDetails() { 170 _signalMastRepeaterList = new ArrayList<>(dsmm.getRepeaterList()); 171 _RepeaterModel.fireTableDataChanged();//updateSignalMastLogic(old, sml); 172 } 173 174 public class SignalMastRepeaterModel extends AbstractTableModel implements PropertyChangeListener { 175 176 SignalMastRepeaterModel() { 177 super(); 178 init(); 179 } 180 181 final void init(){ 182 dsmm.addPropertyChangeListener(this); 183 } 184 185 @Override 186 public Class<?> getColumnClass(int c) { 187 switch (c) { 188 case DIR_COLUMN: 189 case DEL_COLUMN: 190 return JButton.class; 191 case ENABLE_COLUMN: 192 return Boolean.class; 193 default: 194 return String.class; 195 } 196 } 197 198 public void configureTable(JTable table) { 199 // allow reordering of the columns 200 table.getTableHeader().setReorderingAllowed(true); 201 202 // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541) 203 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 204 205 // resize columns as requested 206 for (int i = 0; i < table.getColumnCount(); i++) { 207 int width = getPreferredWidth(i); 208 table.getColumnModel().getColumn(i).setPreferredWidth(width); 209 } 210 table.sizeColumnsToFit(-1); 211 212 configEditColumn(table); 213 214 } 215 216 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", 217 justification="better to keep cases in column order rather than to combine") 218 public int getPreferredWidth(int col) { 219 switch (col) { 220 case ENABLE_COLUMN: 221 case DIR_COLUMN: 222 return new JTextField(5).getPreferredSize().width; 223 case SLAVE_COLUMN: 224 return new JTextField(15).getPreferredSize().width; 225 case MASTER_COLUMN: 226 return new JTextField(15).getPreferredSize().width; 227 case DEL_COLUMN: // not actually used due to the configureTable, setColumnToHoldButton, configureButton 228 return new JTextField(22).getPreferredSize().width; 229 default: 230 log.warn("Unexpected column in getPreferredWidth: {}", col); 231 return new JTextField(8).getPreferredSize().width; 232 } 233 } 234 235 @Override 236 public String getColumnName(int col) { 237 switch (col) { 238 case MASTER_COLUMN: 239 return Bundle.getMessage("ColumnMaster"); 240 case DIR_COLUMN: 241 return Bundle.getMessage("ColumnDir"); 242 case SLAVE_COLUMN: 243 return Bundle.getMessage("ColumnSlave"); 244 case ENABLE_COLUMN: 245 return Bundle.getMessage("ColumnHeadEnabled"); 246 case DEL_COLUMN: 247 default: 248 return ""; 249 } 250 } 251 252 public void dispose() { 253 254 } 255 256 @Override 257 public void propertyChange(java.beans.PropertyChangeEvent e) { 258 if (e.getPropertyName().equals("repeaterlength")) { 259 updateDetails(); 260 } 261 } 262 263 protected void configEditColumn(JTable table) { 264 // have the delete column hold a button 265 /*AbstractTableAction.Bundle.getMessage("EditDelete")*/ 266 267 JButton b = new JButton("< >"); 268 b.putClientProperty("JComponent.sizeVariant", "small"); 269 b.putClientProperty("JButton.buttonType", "square"); 270 271 setColumnToHoldButton(table, DIR_COLUMN, 272 b); 273 setColumnToHoldButton(table, DEL_COLUMN, 274 new JButton(Bundle.getMessage("ButtonDelete"))); 275 } 276 277 protected void setColumnToHoldButton(JTable table, int column, JButton sample) { 278 //TableColumnModel tcm = table.getColumnModel(); 279 // install a button renderer & editor 280 ButtonRenderer buttonRenderer = new ButtonRenderer(); 281 table.setDefaultRenderer(JButton.class, buttonRenderer); 282 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 283 table.setDefaultEditor(JButton.class, buttonEditor); 284 // ensure the table rows, columns have enough room for buttons 285 table.setRowHeight(sample.getPreferredSize().height); 286 table.getColumnModel().getColumn(column) 287 .setPreferredWidth((sample.getPreferredSize().width) + 4); 288 } 289 290 @Override 291 public int getColumnCount() { 292 return 5; 293 } 294 295 @Override 296 public boolean isCellEditable(int r, int c) { 297 switch (c) { 298 case DEL_COLUMN: 299 case ENABLE_COLUMN: 300 case DIR_COLUMN: 301 return true; 302 default: 303 return false; 304 } 305 } 306 307 protected void deleteRepeater(int r) { 308 dsmm.removeRepeater(_signalMastRepeaterList.get(r)); 309 } 310 311 public static final int MASTER_COLUMN = 0; 312 public static final int DIR_COLUMN = 1; 313 public static final int SLAVE_COLUMN = 2; 314 public static final int ENABLE_COLUMN = 3; 315 public static final int DEL_COLUMN = 4; 316 317 public void setSetToState(String x) { 318 } 319 320 @Override 321 public int getRowCount() { 322 return ( _signalMastRepeaterList == null ? 0 : _signalMastRepeaterList.size() ); 323 } 324 325 @Override 326 public Object getValueAt(int r, int c) { 327 if (r >= _signalMastRepeaterList.size()) { 328 log.debug("row is greater than turnout list size"); 329 return null; 330 } 331 switch (c) { 332 case MASTER_COLUMN: 333 return _signalMastRepeaterList.get(r).getMasterMastName(); 334 case DIR_COLUMN: // slot number 335 switch (_signalMastRepeaterList.get(r).getDirection()) { 336 case SignalMastRepeater.BOTHWAY: 337 return "< >"; 338 case SignalMastRepeater.MASTERTOSLAVE: 339 return " > "; 340 case SignalMastRepeater.SLAVETOMASTER: 341 return " < "; 342 default: 343 return "< >"; 344 } 345 case SLAVE_COLUMN: 346 return _signalMastRepeaterList.get(r).getSlaveMastName(); 347 case ENABLE_COLUMN: 348 return _signalMastRepeaterList.get(r).getEnabled(); 349 case DEL_COLUMN: 350 return Bundle.getMessage("ButtonDelete"); 351 default: 352 return null; 353 } 354 } 355 356 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", 357 justification="better to keep cases in column order rather than to combine") 358 @Override 359 public void setValueAt(Object type, int r, int c) { 360 switch (c) { 361 case DIR_COLUMN: 362 switch (_signalMastRepeaterList.get(r).getDirection()) { 363 case SignalMastRepeater.BOTHWAY: 364 _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.MASTERTOSLAVE); 365 break; 366 case SignalMastRepeater.MASTERTOSLAVE: 367 _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.SLAVETOMASTER); 368 break; 369 case SignalMastRepeater.SLAVETOMASTER: 370 _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.BOTHWAY); 371 break; 372 default: 373 _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.BOTHWAY); 374 break; 375 } 376 _RepeaterModel.fireTableDataChanged(); 377 break; 378 case DEL_COLUMN: 379 deleteRepeater(r); 380 break; 381 case ENABLE_COLUMN: 382 boolean b = ((Boolean) type); 383 _signalMastRepeaterList.get(r).setEnabled(b); 384 break; 385 default: 386 break; 387 } 388 } 389 } 390 391 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastRepeaterPanel.class); 392 393}