001package jmri.jmrit.beantable.oblock; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.text.ParseException; 006import java.util.ArrayList; 007import javax.annotation.Nonnull; 008import javax.swing.*; 009import javax.swing.table.AbstractTableModel; 010import jmri.InstanceManager; 011import jmri.jmrit.logix.OBlock; 012import jmri.jmrit.logix.OPath; 013import jmri.jmrit.logix.Portal; 014import jmri.jmrit.logix.PortalManager; 015import jmri.util.IntlUtilities; 016import jmri.util.gui.GuiLafPreferencesManager; 017import jmri.util.swing.JmriJOptionPane; 018 019/** 020 * GUI to define the OPaths within an OBlock. An OPath is the setting of turnouts 021 * from one Portal to another Portal within an OBlock. It may also be assigned 022 * a length. 023 * <hr> 024 * This file is part of JMRI. 025 * <p> 026 * JMRI is free software; you can redistribute it and/or modify it under the 027 * terms of version 2 of the GNU General Public License as published by the Free 028 * Software Foundation. See the "COPYING" file for a copy of this license. 029 * <p> 030 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 031 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 032 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 033 * 034 * @author Pete Cressman (C) 2010 035 */ 036 037public class BlockPathTableModel extends AbstractTableModel implements PropertyChangeListener { 038 039 public static final int FROM_PORTAL_COLUMN = 0; 040 public static final int NAME_COLUMN = 1; 041 public static final int TO_PORTAL_COLUMN = 2; 042 static public final int LENGTHCOL = 3; 043 static public final int UNITSCOL = 4; 044 public static final int EDIT_COL = 5; 045 public static final int DELETE_COL = 6; 046 public static final int NUMCOLS = 7; 047 048 private final String[] tempRow = new String[NUMCOLS]; 049 050 private OBlock _block; 051 private TableFrames _parent; 052 private final boolean _tabbed; // read from prefs (restart required) 053 private ArrayList<Boolean> _units; // list to toggle units of length col for each path 054 055 java.text.DecimalFormat twoDigit = new java.text.DecimalFormat("0.00"); 056 057 public BlockPathTableModel() { 058 super(); 059 _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed(); 060 } 061 062 public BlockPathTableModel(@Nonnull OBlock block, @Nonnull TableFrames parent) { 063 super(); 064 _block = block; 065 _parent = parent; 066 _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed(); 067 initTempRow(); 068 _block.addPropertyChangeListener(this); 069 } 070 071 public void removeListener() { 072 if (_block == null) { 073 return; 074 } 075 try { 076 _block.removePropertyChangeListener(this); 077 } catch (NullPointerException npe) { 078 // OK when block is removed 079 } 080 } 081 082 protected OBlock getBlock() { 083 return _block; 084 } 085 086 void initTempRow() { 087 if (!_tabbed) { 088 for (int i = 0; i < NUMCOLS; i++) { 089 tempRow[i] = null; 090 } 091 tempRow[LENGTHCOL] = twoDigit.format(0.0); 092 if (_block.isMetric()) { 093 tempRow[UNITSCOL] = Bundle.getMessage("cm"); 094 } else { 095 tempRow[UNITSCOL] = Bundle.getMessage("in"); 096 } 097 tempRow[DELETE_COL] = Bundle.getMessage("ButtonClear"); 098 } 099 // refresh the Unit column values list 100 _units = new ArrayList<>(); 101 for (int i = 0; i <= _block.getPaths().size(); i++) { 102 _units.add(_block.isMetric()); 103 } 104 } 105 106 @Override 107 public int getColumnCount() { 108 return NUMCOLS; // Edit Turnouts column button is replaced by Edit for _tabbed 109 } 110 111 @Override 112 public int getRowCount() { 113 return _block.getPaths().size() + (_tabbed ? 0 : 1); 114 // +1 adds the extra empty row at the bottom of the table display for _desktop 115 } 116 117 @Override 118 public String getColumnName(int col) { 119 switch (col) { 120 case FROM_PORTAL_COLUMN: 121 return Bundle.getMessage("FromPortal"); 122 case NAME_COLUMN: 123 return Bundle.getMessage("PathName"); 124 case TO_PORTAL_COLUMN: 125 return Bundle.getMessage("ToPortal"); 126 case LENGTHCOL: 127 return Bundle.getMessage("BlockLengthColName"); 128 case UNITSCOL: 129 return " "; 130 default: 131 // fall through 132 break; 133 } 134 return ""; 135 } 136 137 @Override 138 public Object getValueAt(int rowIndex, int columnIndex) { 139 OPath path = null; 140 if (rowIndex < _block.getPaths().size()) { 141 path = (OPath) _block.getPaths().get(rowIndex); 142 } 143 switch (columnIndex) { 144 case FROM_PORTAL_COLUMN: 145 if (path != null) { 146 Portal portal = path.getFromPortal(); 147 if (portal == null) { 148 return ""; 149 } 150 return portal.getName(); 151 } else { 152 return tempRow[columnIndex]; 153 } 154 case NAME_COLUMN: 155 if (path != null) { 156 return path.getName(); 157 } else { 158 return tempRow[columnIndex]; 159 } 160 case TO_PORTAL_COLUMN: 161 if (path != null) { 162 Portal portal = path.getToPortal(); 163 if (portal == null) { 164 return ""; 165 } 166 return portal.getName(); 167 } else { 168 return tempRow[columnIndex]; 169 } 170 case LENGTHCOL: 171 if (path != null) { 172 if (_units.get(rowIndex)) { 173 return (twoDigit.format(path.getLengthCm())); 174 } else { 175 return (twoDigit.format(path.getLengthIn())); 176 } 177 } 178 break; 179 case UNITSCOL: 180 return _units.get(rowIndex); 181 case EDIT_COL: 182 if (path != null) { 183 if (_tabbed) { 184 return Bundle.getMessage("ButtonEdit"); 185 } else { 186 return Bundle.getMessage("ButtonEditTO"); 187 } 188 } else { 189 return ""; 190 } 191 case DELETE_COL: 192 if (path != null) { 193 return Bundle.getMessage("ButtonDelete"); 194 } else { 195 return Bundle.getMessage("ButtonClear"); // for _desktop 196 } 197 default: 198 // fall through 199 break; 200 } 201 return ""; 202 } 203 204 @Override 205 public void setValueAt(Object value, int row, int col) { 206 String msg = null; 207 if (_block.getPaths().size() == row) { // must be a new BlockPath in tempRow (_desktop interface) 208 switch (col) { 209 case NAME_COLUMN: 210 String strValue = (String)value; 211 if (_block.getPathByName(strValue) != null) { // check for duplicate Path name in this OBlock 212 msg = Bundle.getMessage("DuplPathName", strValue); 213 tempRow[col] = strValue; 214 } else { 215 Portal fromPortal = _block.getPortalByName(tempRow[FROM_PORTAL_COLUMN]); 216 Portal toPortal = _block.getPortalByName(tempRow[TO_PORTAL_COLUMN]); 217 if (fromPortal != null || toPortal != null) { 218 OPath path = new OPath(strValue, _block, fromPortal, toPortal, null); 219 float len = 0.0f; 220 try { 221 len = IntlUtilities.floatValue(tempRow[LENGTHCOL]); 222 } catch (ParseException e) { 223 msg = Bundle.getMessage("BadNumber", tempRow[LENGTHCOL]); 224 } 225 if (tempRow[UNITSCOL].equals((Bundle.getMessage("cm")))) { 226 path.setLength(len * 10.0f); 227 } else { 228 path.setLength(len * 25.4f); 229 } 230 231 if (!_block.addPath(path)) { 232 msg = Bundle.getMessage("AddPathFailed", strValue); 233 tempRow[NAME_COLUMN] = strValue; 234 } else { 235 initTempRow(); 236 _parent.updateOBlockTablesMenu(); 237 fireTableDataChanged(); 238 } 239 } else { 240 tempRow[NAME_COLUMN] = strValue; // initial entry of Path name in cell 241 } 242 } 243 break; 244 case UNITSCOL: 245 _units.set(row, (Boolean)value); // true = metric 246 fireTableRowsUpdated(row, row); 247 return; 248 case DELETE_COL: 249 initTempRow(); 250 fireTableRowsUpdated(row, row); 251 break; 252 default: 253 // fall through 254 break; 255 } 256 tempRow[col] = (String)value; 257 if (msg != null) { 258 JmriJOptionPane.showMessageDialog(null, msg, 259 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 260 } 261 return; 262 } 263 264 // edit existing BlockPath row 265 OPath path = (OPath) _block.getPaths().get(row); 266 switch (col) { 267 case FROM_PORTAL_COLUMN: 268 String strValue = (String)value; 269 if (strValue != null) { 270 PortalManager portalMgr = InstanceManager.getDefault(PortalManager.class); 271 Portal portal = _block.getPortalByName(strValue); 272 if (portal == null || portalMgr.getPortal(strValue) == null) { 273 int val = _parent.verifyWarning(Bundle.getMessage("BlockPortalConflict", value, _block.getDisplayName())); 274 if (val == 2) { 275 break; 276 } 277 portal = portalMgr.providePortal(strValue); 278 if (portal == null) { 279 msg = Bundle.getMessage("NoSuchPortalName", strValue); 280 break; 281 } else { 282 if (!portal.setFromBlock(_block, false)) { 283 val = _parent.verifyWarning(Bundle.getMessage("BlockPathsConflict", value, portal.getFromBlockName())); 284 if (val == 2) { 285 break; 286 } 287 } 288 portal.setFromBlock(_block, true); 289 _parent.getPortalTableModel().fireTableDataChanged(); 290 } 291 } 292 path.setFromPortal(portal); 293 if (!portal.addPath(path)) { 294 msg = Bundle.getMessage("AddPathFailed", strValue); 295 } 296 } else { 297 path.setFromPortal(null); 298 } 299 fireTableRowsUpdated(row, row); 300 break; 301 case NAME_COLUMN: 302 strValue = (String)value; 303 if (strValue != null) { 304 if (_block.getPathByName(strValue) != null) { 305 msg = Bundle.getMessage("DuplPathName", strValue); 306 } 307 path.setName(strValue); 308 fireTableRowsUpdated(row, row); 309 } 310 break; 311 case TO_PORTAL_COLUMN: 312 strValue = (String)value; 313 if (strValue != null) { 314 PortalManager portalMgr = InstanceManager.getDefault(PortalManager.class); 315 Portal portal = _block.getPortalByName(strValue); 316 if (portal == null || portalMgr.getPortal(strValue) == null) { 317 int val = _parent.verifyWarning(Bundle.getMessage("BlockPortalConflict", value, _block.getDisplayName())); 318 if (val == 2) { 319 break; // no response 320 } 321 portal = portalMgr.providePortal(strValue); 322 if (portal == null) { 323 msg = Bundle.getMessage("NoSuchPortalName", strValue); 324 break; 325 } else { 326 if (!portal.setToBlock(_block, false)) { 327 val = _parent.verifyWarning(Bundle.getMessage("BlockPathsConflict", value, portal.getToBlockName())); 328 if (val == 2) { 329 break; 330 } 331 } 332 portal.setToBlock(_block, true); 333 _parent.getPortalTableModel().fireTableDataChanged(); 334 } 335 } 336 path.setToPortal(portal); 337 if (!portal.addPath(path)) { 338 msg = Bundle.getMessage("AddPathFailed", strValue); 339 } 340 } else { 341 path.setToPortal(null); 342 } 343 fireTableRowsUpdated(row, row); 344 break; 345 case LENGTHCOL: 346 try { 347 float len = IntlUtilities.floatValue(value.toString()); 348 if (_units.get(row)) { 349 path.setLength(len * 10.0f); 350 } else { 351 path.setLength(len * 25.4f); 352 } 353 fireTableRowsUpdated(row, row); 354 } catch (ParseException e) { 355 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BadNumber", value), 356 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE); 357 } 358 return; 359 case UNITSCOL: 360 _units.set(row, (Boolean)value); 361 fireTableRowsUpdated(row, row); 362 return; 363 case EDIT_COL: // button is called [Edit] (_tabbed) or [Edit Turnouts] (_desktop) 364 if (_tabbed && !_parent.isPathEdit()) { // everything is handled in BlockPathEdit panel 365 _parent.openPathEditor(_block.getSystemName(), path.getName(), this); 366 } else { 367 _parent.openPathTurnoutFrame(_parent.makePathTurnoutName(_block.getSystemName(), path.getName())); 368 } 369 break; 370 case DELETE_COL: 371 if (deletePath(path)) { 372 _units.remove(row); 373 fireTableDataChanged(); 374 } 375 break; 376 default: 377 // fall through 378 break; 379 } 380 if (msg != null) { 381 JmriJOptionPane.showMessageDialog(null, msg, 382 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 383 } 384 } 385 386 boolean deletePath(OPath path) { 387 int val = _parent.verifyWarning(Bundle.getMessage("DeletePathConfirm", path.getName())); 388 if (val == 2) { 389 return false; 390 } 391 return _block.removeOPath(path); 392 } 393 394 @Override 395 public boolean isCellEditable(int row, int col) { 396 return true; 397 } 398 399 @Override 400 public Class<?> getColumnClass(int col) { 401 switch (col) { 402 case DELETE_COL: 403 case EDIT_COL: 404 return JButton.class; 405 case UNITSCOL: 406 return JToggleButton.class; 407 default: 408 return String.class; 409 } 410 } 411 412 public int getPreferredWidth(int col) { 413 switch (col) { 414 case FROM_PORTAL_COLUMN: 415 case NAME_COLUMN: 416 case TO_PORTAL_COLUMN: 417 return new JTextField(25).getPreferredSize().width; 418 case LENGTHCOL: 419 return new JTextField(10).getPreferredSize().width; 420 case UNITSCOL: 421 return new JTextField(6).getPreferredSize().width; 422 case EDIT_COL: 423 return new JButton("TURNOUT").getPreferredSize().width; 424 case DELETE_COL: 425 return new JButton("DELETE").getPreferredSize().width; 426 default: 427 // fall through 428 break; 429 } 430 return 5; 431 } 432 433 @Override 434 public void propertyChange(PropertyChangeEvent e) { 435 if (_block.equals(e.getSource())) { 436 String property = e.getPropertyName(); 437 if (log.isDebugEnabled()) { 438 log.debug("propertyChange \"{}\". source= {}", property, e.getSource()); 439 } 440 if (property.equals("portalCount") || 441 property.equals("pathCount") || property.equals("pathName")) { 442 fireTableDataChanged(); 443 } else if (property.equals("deleted")) { 444 _parent.disposeBlockPathFrame(_block); 445 } 446 } 447 } 448 449 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockPathTableModel.class); 450 451}