001package jmri.jmrit.beantable.signalmast; 002 003import java.util.HashMap; 004import java.util.Vector; 005import javax.annotation.Nonnull; 006import javax.swing.JButton; 007import javax.swing.JComboBox; 008import javax.swing.JTable; 009import javax.swing.JTextField; 010import javax.swing.SwingUtilities; 011import jmri.InstanceManager; 012import jmri.Manager; 013import jmri.NamedBean; 014import jmri.SignalMast; 015import jmri.SignalMastManager; 016import jmri.jmrit.beantable.BeanTableDataModel; 017import jmri.jmrit.beantable.RowComboBoxPanel; 018import jmri.jmrit.signalling.SignallingSourceAction; 019 020/** 021 * Data model for a SignalMastTable 022 * 023 * @author Bob Jacobsen Copyright (C) 2003, 2009 024 * @author Egbert Broerse Copyright (C) 2016 025 */ 026public class SignalMastTableDataModel extends BeanTableDataModel<SignalMast> { 027 028 public static final int EDITMASTCOL = NUMCOLUMN; 029 public static final int EDITLOGICCOL = EDITMASTCOL + 1; 030 public static final int LITCOL = EDITLOGICCOL + 1; 031 public static final int HELDCOL = LITCOL + 1; 032 033 @Override 034 public String getValue(String name) { 035 SignalMast sm = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name); 036 if (sm != null) { 037 return sm.getAspect(); 038 } else { 039 return null; 040 } 041 } 042 043 @Override 044 public int getColumnCount() { 045 return NUMCOLUMN + 4; 046 } 047 048 @Override 049 public String getColumnName(int col) { 050 switch (col) { 051 case VALUECOL: 052 return Bundle.getMessage("LabelAspectType"); 053 case EDITMASTCOL: 054 return ""; // override default, no title for Edit column 055 case EDITLOGICCOL: 056 return ""; // override default, no title for Edit Logic column 057 case LITCOL: 058 return Bundle.getMessage("ColumnHeadLit"); 059 case HELDCOL: 060 return Bundle.getMessage("ColumnHeadHeld"); 061 default: 062 return super.getColumnName(col); 063 } 064 } 065 066 @Override 067 public Class<?> getColumnClass(int col) { 068 switch (col) { 069 case VALUECOL: 070 return RowComboBoxPanel.class; // Use a JPanel containing a custom Aspect ComboBox 071 case EDITMASTCOL: 072 case EDITLOGICCOL: 073 return JButton.class; 074 case LITCOL: 075 case HELDCOL: 076 return Boolean.class; 077 default: 078 return super.getColumnClass(col); 079 } 080 } 081 082 @Override 083 public int getPreferredWidth(int col) { 084 switch (col) { 085 case LITCOL: 086 // I18N use Bundle.getMessage() + length() for PreferredWidth size 087 return new JTextField(Bundle.getMessage("ColumnHeadLit").length()).getPreferredSize().width; 088 case HELDCOL: 089 return new JTextField(Bundle.getMessage("ColumnHeadHeld").length()).getPreferredSize().width; 090 case EDITLOGICCOL: 091 return new JTextField(Bundle.getMessage("EditSignalLogicButton").length()).getPreferredSize().width; 092 case EDITMASTCOL: 093 return new JTextField(Bundle.getMessage("ButtonEdit").length()).getPreferredSize().width; 094 default: 095 return super.getPreferredWidth(col); 096 } 097 } 098 099 @Override 100 public boolean isCellEditable(int row, int col) { 101 switch (col) { 102 case LITCOL: 103 case EDITLOGICCOL: 104 case EDITMASTCOL: 105 case HELDCOL: 106 return true; 107 default: 108 return super.isCellEditable(row, col); 109 } 110 } 111 112 @Override 113 protected Manager<SignalMast> getManager() { 114 return InstanceManager.getDefault(SignalMastManager.class); 115 } 116 117 @Override 118 protected SignalMast getBySystemName(@Nonnull String name) { 119 return InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name); 120 } 121 122 @Override 123 protected SignalMast getByUserName(@Nonnull String name) { 124 return InstanceManager.getDefault(SignalMastManager.class).getByUserName(name); 125 } 126 127 @Override 128 protected String getMasterClassName() { 129 return getClassName(); 130 } 131 132 @Override 133 protected void clickOn(SignalMast t) { 134 log.debug("No action for click on {}",t.getDisplayName()); 135 } 136 137 @Override 138 public Object getValueAt(int row, int col) { 139 // some error checking 140 if (row >= sysNameList.size()) { 141 log.debug("row index is greater than name list"); 142 return "error"; 143 } 144 String name = sysNameList.get(row); 145 SignalMast s = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name); 146 if (s == null) { 147 return false; // if due to race condition, the device is going away 148 } 149 switch (col) { 150 case LITCOL: 151 return s.getLit(); 152 case HELDCOL: 153 return s.getHeld(); 154 case EDITLOGICCOL: 155 return Bundle.getMessage("EditSignalLogicButton"); 156 case EDITMASTCOL: 157 return Bundle.getMessage("ButtonEdit"); 158 case VALUECOL: 159 String aspect = s.getAspect(); 160 if ( aspect != null) { 161 return aspect; 162 } else { 163 //Aspect not set, - too verbose, even at trace 164 //log.trace("Aspect not set, NULL aspect returned for mast in row {}", row); 165 return Bundle.getMessage("BeanStateUnknown"); // use place holder string in table 166 } 167 default: 168 return super.getValueAt(row, col); 169 } 170 } 171 172 @Override 173 public void setValueAt(Object value, int row, int col) { 174 String name = sysNameList.get(row); 175 SignalMast s = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name); 176 if (s == null) { 177 return; // device is going away anyway 178 } 179 switch (col) { 180 case VALUECOL: 181 if ((String) value != null) { 182 log.debug("setValueAt (rowConverted={}; value={})", row, value); 183 s.setAspect((String) value); 184 fireTableRowsUpdated(row, row); 185 } 186 break; 187 case LITCOL: { 188 boolean b = ((Boolean) value); 189 s.setLit(b); 190 break; 191 } 192 case HELDCOL: { 193 boolean b = ((Boolean) value); 194 s.setHeld(b); 195 break; 196 } 197 case EDITLOGICCOL: 198 editLogic(row, col); 199 break; 200 case EDITMASTCOL: 201 editMast(row, col); 202 break; 203 default: 204 super.setValueAt(value, row, col); 205 break; 206 } 207 } 208 209 void editLogic(int row, int col) { 210 SwingUtilities.invokeLater(() -> { 211 SignallingSourceAction action = new SignallingSourceAction(Bundle.getMessage( 212 "TitleSignalMastLogicTable"), getBySystemName(sysNameList.get(row))); 213 action.actionPerformed(null); 214 }); 215 } 216 217 void editMast(int row, int col) { 218 SwingUtilities.invokeLater(() -> { 219 AddSignalMastJFrame editFrame = new AddSignalMastJFrame(getBySystemName(sysNameList.get(row))); 220 editFrame.setVisible(true); 221 }); 222 } 223 224 /** 225 * Respond to change from bean. 226 * 227 * @param e the change event to respond to 228 */ 229 @Override 230 public void propertyChange(java.beans.PropertyChangeEvent e) { 231 if ( (e.getPropertyName().contains("aspectEnabled") || e.getPropertyName().contains("aspectDisabled")) 232 && (e.getSource() instanceof NamedBean ) ) { 233 234 String name = ((NamedBean) e.getSource()).getSystemName(); 235 if (log.isDebugEnabled()) { 236 log.debug("Update cell {}, {} for {}", sysNameList.indexOf(name), VALUECOL, name); 237 } 238 // since we can add columns, the entire row is marked as updated 239 int row = sysNameList.indexOf(name); 240 this.fireTableRowsUpdated(row, row); 241 clearAspectVector(row); 242 } 243 super.propertyChange(e); 244 } 245 246 @Override 247 protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) { 248 if (e.getPropertyName().contains("Aspect") || e.getPropertyName().contains("Lit") 249 || e.getPropertyName().contains("Held") || e.getPropertyName().contains("aspectDisabled") 250 || e.getPropertyName().contains("aspectEnabled")) { 251 252 return true; 253 } 254 return super.matchPropertyName(e); 255 } 256 257 /** 258 * Customize the SignalMast Value (Aspect) column to show an appropriate 259 * ComboBox of available Aspects when the TableDataModel is being called 260 * from ListedTableAction. 261 * 262 * @param table a JTable of Signal Masts 263 */ 264 @Override 265 protected void configValueColumn(JTable table) { 266 // have the value column hold a JPanel with a JComboBox for Aspects 267 setColumnToHoldButton(table, VALUECOL, configureButton()); 268 // add extras, override BeanTableDataModel 269 log.debug("Mast configValueColumn (I am {})", super.toString()); 270 table.setDefaultEditor(RowComboBoxPanel.class, new AspectComboBoxPanel()); 271 // create a separate class for the renderer 272 table.setDefaultRenderer(RowComboBoxPanel.class, new AspectComboBoxPanel()); 273 // Set more things? 274 } 275 276 /** 277 * Set column width. 278 * 279 * @return a button to fit inside the VALUE column 280 */ 281 @Override 282 public JButton configureButton() { 283 // pick a large size 284 JButton b = new JButton("Diverging Approach Medium"); // about the longest Aspect string 285 b.putClientProperty("JComponent.sizeVariant", "small"); 286 b.putClientProperty("JButton.buttonType", "square"); 287 return b; 288 } 289 290 /** 291 * A row specific Aspect combobox cell editor/renderer 292 */ 293 public class AspectComboBoxPanel extends RowComboBoxPanel { 294 295 @Override 296 protected final void eventEditorMousePressed() { 297 this.editor.add(getEditorBox(table.convertRowIndexToModel(this.currentRow))); // add eb to JPanel 298 this.editor.revalidate(); 299 SwingUtilities.invokeLater(this.comboBoxFocusRequester); 300 log.debug("eventEditorMousePressed in row: {}; me = {})", this.currentRow, this.toString()); 301 } 302 303 /** 304 * Call method getAspectEditorBox() in the surrounding method for the SignalMastTable. 305 * @param row Index of the row clicked in the table 306 * @return an appropriate combobox for this signal mast 307 */ 308 @Override 309 protected JComboBox<String> getEditorBox(int row) { 310 return getAspectEditorBox(row); 311 } 312 313 } 314 315 /** 316 * Clear the old aspect comboboxes and force them to be rebuilt 317 * 318 * @param row Index of the signal mast (in TableDataModel) to be rebuilt in 319 * the HashMaps 320 */ 321 public void clearAspectVector(int row) { 322 boxMap.remove(this.getValueAt(row, SYSNAMECOL)); 323 editorMap.remove(this.getValueAt(row, SYSNAMECOL)); 324 } 325 326 // HashMaps for Editors; not used for Renderer) 327 /** 328 * Provide a JComboBox element to display inside the JPanel CellEditor. When 329 * not yet present, create, store and return a new one. 330 * 331 * @param row Index number (in TableDataModel) 332 * @return A combobox containing the valid aspect names for this mast 333 */ 334 JComboBox<String> getAspectEditorBox(int row) { 335 JComboBox<String> editCombo = editorMap.get(this.getValueAt(row, SYSNAMECOL)); 336 if (editCombo == null) { 337 // create a new one with correct aspects 338 editCombo = new JComboBox<>(getAspectVector(row)); 339 editorMap.put(this.getValueAt(row, SYSNAMECOL), editCombo); 340 } 341 return editCombo; 342 } 343 HashMap<Object, JComboBox<String>> editorMap = new HashMap<>(); 344 345 /** 346 * Holds a HashMap of valid aspects per signal mast used by getEditorBox() 347 * 348 * @param row Index number (in TableDataModel) 349 * @return The Vector of valid aspect names for this mast to show in the 350 * JComboBox 351 */ 352 Vector<String> getAspectVector(int row) { 353 Vector<String> comboaspects = boxMap.get(this.getValueAt(row, SYSNAMECOL)); 354 if (comboaspects == null) { 355 // create a new one with right aspects 356 Vector<String> v = ((SignalMast)this.getValueAt(row, SYSNAMECOL)).getValidAspects(); 357 comboaspects = v; 358 boxMap.put(this.getValueAt(row, SYSNAMECOL), comboaspects); 359 } 360 return comboaspects; 361 } 362 363 HashMap<Object, Vector<String>> boxMap = new HashMap<>(); 364 365 // end of methods to display VALUECOL (Aspect) ComboBox 366 protected String getClassName() { 367 return jmri.jmrit.beantable.SignalMastTableAction.class.getName(); 368 } 369 370 public String getClassDescription() { 371 return Bundle.getMessage("TitleSignalMastTable"); 372 } 373 374 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastTableDataModel.class); 375 376}