001package jmri.jmrit.beantable.oblock; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005 006import javax.annotation.Nonnull; 007import javax.swing.*; 008import javax.swing.table.AbstractTableModel; 009 010import jmri.*; 011import jmri.jmrit.beantable.RowComboBoxPanel; 012import jmri.jmrit.logix.OPath; 013import jmri.util.gui.GuiLafPreferencesManager; 014import jmri.util.swing.JmriJOptionPane; 015 016/** 017 * GUI to define Path-Turnout combos for OBlocks. 018 * <p> 019 * Can be used with two interfaces: 020 * <ul> 021 * <li>original "desktop" InternalFrames (parent class TableFrames, an extended JmriJFrame) 022 * <li>JMRI standard Tabbed tables (parent class JPanel) 023 * </ul> 024 * The _tabbed field decides, it is set in prefs (restart required). 025 * <hr> 026 * This file is part of JMRI. 027 * <p> 028 * JMRI is free software; you can redistribute it and/or modify it under the 029 * terms of version 2 of the GNU General Public License as published by the Free 030 * Software Foundation. See the "COPYING" file for a copy of this license. 031 * <p> 032 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 033 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 034 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 035 * 036 * @author Pete Cressman (C) 2010 037 * @author Egbert Broerse (C) 2020 038 */ 039public class PathTurnoutTableModel extends AbstractTableModel implements PropertyChangeListener { 040 041 public static final int TURNOUT_NAME_COL = 0; 042 public static final int STATE_COL = 1; 043 public static final int DELETE_COL = 2; 044 public static final int NUMCOLS = 3; 045 // also 046 private static final String SET_CLOSED = jmri.InstanceManager.turnoutManagerInstance().getClosedText(); 047 private static final String SET_THROWN = jmri.InstanceManager.turnoutManagerInstance().getThrownText(); 048 049 private OPath _path; 050 private final String[] tempRow = new String[NUMCOLS]; 051 private TableFrames.PathTurnoutFrame _parent; 052 private final boolean _tabbed; // updated from prefs (restart required) 053 054 public PathTurnoutTableModel() { 055 super(); 056 _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed(); 057 } 058 059 public PathTurnoutTableModel(OPath path, @Nonnull TableFrames.PathTurnoutFrame parent) { // for _desktop 060 super(); 061 _path = path; 062 _path.getBlock().addPropertyChangeListener(this); 063 _tabbed = false; 064 _parent = parent; // is used to change the title, or dispose when item is deleted 065 } 066 067 public PathTurnoutTableModel(OPath path) { // for _tabbed 068 super(); 069 _path = path; 070 _path.getBlock().addPropertyChangeListener(this); 071 _tabbed = true; 072 //_tabbedParent = parent; // is -not- used to change the title, or dispose when item is deleted 073 } 074 075 public void removeListener() { 076 Block block = _path.getBlock(); 077 if (block == null) { 078 return; 079 } 080 try { 081 _path.getBlock().removePropertyChangeListener(this); 082 } catch (NullPointerException npe) { // OK when block is removed 083 } 084 } 085 086 void initTempRow() { 087 for (int i = 0; i < NUMCOLS; i++) { 088 tempRow[i] = null; 089 } 090 tempRow[DELETE_COL] = Bundle.getMessage("ButtonClear"); 091 } 092 093 @Override 094 public int getColumnCount() { 095 return NUMCOLS; 096 } 097 098 @Override 099 public int getRowCount() { 100 return _path.getSettings().size() + (_tabbed ? 0 : 1); // + 1 row in _desktop to create entry row 101 } 102 103 @Override 104 public String getColumnName(int col) { 105 switch (col) { 106 case TURNOUT_NAME_COL: 107 return Bundle.getMessage("LabelItemName"); 108 case STATE_COL: 109 return Bundle.getMessage("TurnoutState"); // state 110 default: 111 // fall through 112 break; 113 } 114 return ""; 115 } 116 117 @Override 118 public Object getValueAt(int rowIndex, int columnIndex) { 119 if (_path.getSettings().size() == rowIndex) { // this must be tempRow 120 return tempRow[columnIndex]; 121 } 122 // some error checking 123 if (rowIndex >= _path.getSettings().size()) { 124 log.debug("row greater than bean list size"); 125 return "Error bean list"; 126 } 127 BeanSetting bs = _path.getSettings().get(rowIndex); 128 // some error checking 129 if (bs == null) { 130 log.debug("bean is null"); 131 return "Error no bean"; 132 } 133 switch (columnIndex) { 134 case TURNOUT_NAME_COL: 135 return bs.getBeanName(); 136 case STATE_COL: 137 switch (bs.getSetting()) { 138 case Turnout.CLOSED: 139 return SET_CLOSED; 140 case Turnout.THROWN: 141 return SET_THROWN; 142 default: 143 return ""; 144 } 145 case DELETE_COL: 146 return Bundle.getMessage("ButtonDelete"); 147 default: 148 // fall through 149 break; 150 } 151 return ""; 152 } 153 154 @Override 155 public void setValueAt(Object value, int row, int col) { 156 if (_path.getSettings().size() == row) { // last entry row, only possible in _desktop interface 157 switch (col) { 158 case TURNOUT_NAME_COL: 159 tempRow[TURNOUT_NAME_COL] = (String) value; 160 if (tempRow[STATE_COL] == null) { 161 return; 162 } 163 break; 164 case STATE_COL: 165 tempRow[STATE_COL] = (String) value; 166 if (tempRow[TURNOUT_NAME_COL] == null) { 167 return; 168 } 169 break; 170 case DELETE_COL: // called "Clear" 171 initTempRow(); 172 fireTableRowsUpdated(row, row); 173 return; 174 default: 175 // fall through 176 break; 177 } 178 Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(tempRow[TURNOUT_NAME_COL]); 179 if (t != null) { 180 int s; 181 if (tempRow[STATE_COL].equals(SET_CLOSED)) { 182 s = Turnout.CLOSED; 183 } else if (tempRow[STATE_COL].equals(SET_THROWN)) { 184 s = Turnout.THROWN; 185 } else { 186 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TurnoutMustBeSet", SET_CLOSED, SET_THROWN), 187 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE); 188 return; 189 } 190 BeanSetting bs = new BeanSetting(t, tempRow[TURNOUT_NAME_COL], s); 191 _path.addSetting(bs); 192 fireTableRowsUpdated(row, row); 193 } else { 194 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchTurnout", tempRow[TURNOUT_NAME_COL]), 195 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE); 196 return; 197 } 198 if (!_tabbed) { 199 initTempRow(); 200 } 201 return; 202 } 203 204 BeanSetting bs = _path.getSettings().get(row); 205 Turnout t; 206 switch (col) { 207 case TURNOUT_NAME_COL: 208 t = InstanceManager.turnoutManagerInstance().getTurnout((String) value); 209 if (t != null) { 210 if (!t.equals(bs.getBean())) { 211 _path.removeSetting(bs); 212 _path.addSetting(new BeanSetting(t, (String) value, bs.getSetting())); 213 } 214 } else { 215 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchTurnout", (String) value), 216 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE); 217 return; 218 } 219 fireTableDataChanged(); 220 break; 221 case STATE_COL: 222 String setting = (String) value; 223 if (setting.equals(SET_CLOSED)) { 224 //bs.setSetting(Turnout.CLOSED); - This was the form before BeanSetting was returned to Immutable 225 _path.getSettings().set(row, new BeanSetting(bs.getBean(), bs.getBeanName(), Turnout.CLOSED)); 226 } else if (setting.equals(SET_THROWN)) { 227 //bs.setSetting(Turnout.THROWN); 228 _path.getSettings().set(row, new BeanSetting(bs.getBean(), bs.getBeanName(), Turnout.THROWN)); 229 } else { 230 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TurnoutMustBeSet", SET_CLOSED, SET_THROWN), 231 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE); 232 return; 233 } 234 fireTableRowsUpdated(row, row); 235 break; 236 case DELETE_COL: 237 if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("DeleteTurnoutConfirm"), 238 Bundle.getMessage("WarningTitle"), 239 JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE) 240 == JmriJOptionPane.YES_OPTION) { 241 _path.removeSetting(bs); 242 fireTableDataChanged(); 243 } 244 break; 245 default: 246 log.warn("Unhandled col: {}", col); 247 break; 248 } 249 } 250 251 @Override 252 public boolean isCellEditable(int row, int col) { 253 return true; 254 } 255 256 @Override 257 public Class<?> getColumnClass(int col) { 258 switch (col) { 259 case DELETE_COL: 260 return JButton.class; 261 case STATE_COL: 262 return StateComboBoxPanel.class; 263 case TURNOUT_NAME_COL: 264 default: 265 return String.class; 266 } 267 } 268 269 /** 270 * Provide a table cell renderer looking like a JComboBox as an 271 * editor/renderer for the turnout tables. 272 * <p> 273 * This is a lightweight version of the 274 * {@link jmri.jmrit.beantable.RowComboBoxPanel} RowComboBox cell editor 275 * class, some of the hashtables not needed here since we only need 276 * identical options for all rows in a column. 277 * 278 * see jmri.jmrit.signalling.SignallingPanel.SignalMastModel.AspectComboBoxPanel for a full application with 279 * row specific comboBox choices. 280 */ 281 public class StateComboBoxPanel extends RowComboBoxPanel { 282 283 @Override 284 protected final void eventEditorMousePressed() { 285 this.editor.add(getEditorBox(table.convertRowIndexToModel(this.currentRow))); // add editorBox to JPanel 286 this.editor.revalidate(); 287 SwingUtilities.invokeLater(this.comboBoxFocusRequester); 288 log.debug("eventEditorMousePressed in row: {})", this.currentRow); // NOI18N 289 } 290 291 /** 292 * Call the method in the surrounding method for the 293 * SignalHeadTable. 294 * 295 * @param row the user clicked on in the table 296 * @return an appropriate combobox for this signal head 297 */ 298 @Override 299 protected JComboBox<String> getEditorBox(int row) { 300 return getStateEditorBox(row); 301 } 302 303 } 304 // end of methods to display STATE_COLUMN ComboBox 305 306 /** 307 * Provide a static JComboBox element to display inside the JPanel 308 * CellEditor. When not yet present, create, store and return a new one. 309 * 310 * @param row Index number (in TableDataModel) 311 * @return A combobox containing the valid aspect names for this mast 312 */ 313 JComboBox<String> getStateEditorBox(int row) { 314 // create dummy comboBox, override in extended classes for each bean 315 JComboBox<String> editCombo = new JComboBox<>(); 316 editCombo.addItem(SET_THROWN); 317 editCombo.addItem(SET_CLOSED); 318 editCombo.putClientProperty("JComponent.sizeVariant", "small"); 319 editCombo.putClientProperty("JComboBox.buttonType", "square"); 320 return editCombo; 321 } 322 323 /** 324 * Customize the Turnout State column to show an appropriate ComboBox of 325 * available options. 326 * 327 * @param table a JTable of beans 328 */ 329 protected void configTurnoutStateColumn(JTable table) { 330 // have the state column hold a JPanel with a JComboBox for States 331 table.setDefaultEditor(StateComboBoxPanel.class, new StateComboBoxPanel()); 332 table.setDefaultRenderer(StateComboBoxPanel.class, new StateComboBoxPanel()); // use same class as renderer 333 // Set more things? 334 } 335 336 public int getPreferredWidth(int col) { 337 switch (col) { 338 case TURNOUT_NAME_COL: 339 return new JTextField(20).getPreferredSize().width; 340 case STATE_COL: 341 return new JTextField(10).getPreferredSize().width; 342 case DELETE_COL: 343 return new JButton("DELETE").getPreferredSize().width; 344 default: 345 // fall through 346 break; 347 } 348 return 5; 349 } 350 351 @Override 352 public void propertyChange(PropertyChangeEvent e) { 353 if (_path.getBlock().equals(e.getSource())) { 354 String property = e.getPropertyName(); 355 if (property.equals("pathCount")) { 356 fireTableDataChanged(); 357 if (_path.equals(e.getOldValue())) { // path was deleted 358 removeListener(); 359 if (!_tabbed) { 360 _parent.dispose(); 361 } 362 } 363 } else if (property.equals("pathName")) { 364 String title = Bundle.getMessage("TitlePathTurnoutTable", _path.getBlock().getDisplayName(), e.getOldValue()); 365 if (!_tabbed && _parent.getTitle().equals(title)) { 366 title = Bundle.getMessage("TitlePathTurnoutTable", _path.getBlock().getDisplayName(), e.getNewValue()); 367 } 368 if (!_tabbed) { 369 _parent.setTitle(title); 370 } 371 } 372 } 373 } 374 375 void dispose() { 376 InstanceManager.turnoutManagerInstance().removePropertyChangeListener(this); 377 } 378 379 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PathTurnoutTableModel.class); 380 381}