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