001package jmri.jmrit.beantable.oblock; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.text.ParseException; 006 007import java.util.*; 008//import java.util.concurrent.CopyOnWriteArrayList; 009 010import javax.swing.*; 011import javax.swing.table.AbstractTableModel; 012import jmri.InstanceManager; 013import jmri.NamedBean; 014import jmri.jmrit.logix.OBlock; 015import jmri.jmrit.logix.OBlockManager; 016import jmri.jmrit.logix.Portal; 017import jmri.jmrit.logix.PortalManager; 018import jmri.util.IntlUtilities; 019import jmri.util.gui.GuiLafPreferencesManager; 020import jmri.util.swing.JmriJOptionPane; 021 022/** 023 * GUI to define the Signals within an OBlock. 024 * <p> 025 * Can be used with two interfaces: 026 * <ul> 027 * <li>original "desktop" InternalFrames (parent class TableFrames, an extended JmriJFrame) 028 * <li>JMRI standard Tabbed tables (parent class JPanel) 029 * </ul> 030 * <hr> 031 * This file is part of JMRI. 032 * <p> 033 * JMRI is free software; you can redistribute it and/or modify it under the 034 * terms of version 2 of the GNU General Public License as published by the Free 035 * Software Foundation. See the "COPYING" file for a copy of this license. 036 * <p> 037 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 038 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 039 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 040 * 041 * @author Pete Cressman (C) 2010 042 * @author Egbert Broerse (C) 2020 043 */ 044public class SignalTableModel extends AbstractTableModel implements PropertyChangeListener { 045 046 public static final int NAME_COLUMN = 0; 047 public static final int FROM_BLOCK_COLUMN = 1; 048 public static final int PORTAL_COLUMN = 2; 049 public static final int TO_BLOCK_COLUMN = 3; 050 public static final int LENGTHCOL = 4; 051 public static final int UNITSCOL = 5; 052 public static final int DELETE_COL = 6; 053 static public final int EDIT_COL = 7; // only used on _tabbed UI 054 public static final int NUMCOLS = 7; // returns 7+1 for _tabbed 055 int _lastIdx; // for debug 056 057 PortalManager _portalMgr; 058 TableFrames _parent; 059 private SignalArray _signalList = new SignalArray(); 060 private final boolean _tabbed; // updated from prefs (restart required) 061 private float _tempLen = 0.0f; // mm for length col of tempRow 062 private String[] tempRow; 063 boolean inEditMode = false; 064 java.text.DecimalFormat twoDigit = new java.text.DecimalFormat("0.00"); 065 066 protected static class SignalRow { 067 068 NamedBean _signal; 069 OBlock _fromBlock; 070 Portal _portal; 071 OBlock _toBlock; 072 float _length; // offset from signal to speed change point, stored in mm 073 boolean _isMetric; 074 075 SignalRow(NamedBean signal, OBlock fromBlock, Portal portal, OBlock toBlock, float length, boolean isMetric) { 076 _signal = signal; 077 _fromBlock = fromBlock; 078 _portal = portal; 079 _toBlock = toBlock; 080 _length = length; 081 _isMetric = isMetric; 082 } 083 084 void setSignal(NamedBean signal) { 085 _signal = signal; 086 } 087 NamedBean getSignal() { 088 return _signal; 089 } 090 void setFromBlock(OBlock fromBlock) { 091 _fromBlock = fromBlock; 092 } 093 OBlock getFromBlock() { 094 return _fromBlock; 095 } 096 void setPortal(Portal portal) { 097 _portal = portal; 098 } 099 Portal getPortal() { 100 return _portal; 101 } 102 void setToBlock(OBlock toBlock) { 103 _toBlock = toBlock; 104 } 105 OBlock getToBlock() { 106 return _toBlock; 107 } 108 void setLength(float length) { 109 _length = length; 110 } 111 float getLength() { 112 return _length; 113 } 114 void setMetric(boolean isMetric) { 115 _isMetric = isMetric; 116 } 117 boolean isMetric() { 118 return _isMetric; 119 } 120 121 } 122 123 static class SignalArray extends ArrayList<SignalRow> { 124 125 public int numberOfSignals() { 126 return size(); 127 } 128 129 } 130 131 public SignalTableModel(TableFrames parent) { 132 super(); 133 _parent = parent; 134 _portalMgr = InstanceManager.getDefault(PortalManager.class); 135 _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed(); 136 } 137 138 public void init() { 139 makeList(); 140 initTempRow(); 141 } 142 143 void initTempRow() { 144 if (!_tabbed) { 145 tempRow = new String[NUMCOLS]; 146 tempRow[LENGTHCOL] = twoDigit.format(0.0); 147 tempRow[UNITSCOL] = Bundle.getMessage("in"); 148 tempRow[DELETE_COL] = Bundle.getMessage("ButtonClear"); 149 } 150 } 151 152 // Rebuild _signalList CopyOnWriteArrayList<SignalRow>, copying Signals from Portal table 153 private void makeList() { 154 //CopyOnWriteArrayList<SignalRow> tempList = new CopyOnWriteArrayList<>(); 155 SignalArray tempList = new SignalArray(); 156 Collection<Portal> portals = _portalMgr.getPortalSet(); 157 for (Portal portal : portals) { 158 // check portal is well formed 159 OBlock fromBlock = portal.getFromBlock(); 160 OBlock toBlock = portal.getToBlock(); 161 if (fromBlock != null && toBlock != null) { 162 SignalRow sr; 163 NamedBean signal = portal.getFromSignal(); 164 if (signal != null) { 165 sr = new SignalRow(signal, fromBlock, portal, toBlock, portal.getFromSignalOffset(), toBlock.isMetric()); 166 //_signalList.add(sr); 167 addToList(tempList, sr); 168 //log.debug("1 SR added to tempList, new size = {}", tempList.numberOfSignals()); 169 } 170 signal = portal.getToSignal(); 171 if (signal != null) { 172 sr = new SignalRow(signal, toBlock, portal, fromBlock, portal.getToSignalOffset(), fromBlock.isMetric()); 173 //_signalList.add(sr); 174 addToList(tempList, sr); 175 //log.debug("1 SR added to tempList, new size = {}", tempList.numberOfSignals()); 176 } 177 } else { 178 // Can't get jmri.util.JUnitAppender.assertErrorMessage recognized in TableFramesTest! OK just warn then 179 log.warn("Portal {} needs an OBlock on each side", portal.getName()); 180 } 181 } 182 //_signalList = tempList; 183 _signalList = (SignalArray) tempList.clone(); 184 _lastIdx = tempList.numberOfSignals(); 185 //log.debug("TempList copied, size = {}", tempList.numberOfSignals()); 186 _signalList.sort(new NameSorter()); 187 //log.debug("makeList exit: _signalList size {} items.", _signalList.numberOfSignals()); 188 } 189 190 private static void addToList(SignalArray array, SignalRow sr) { 191 // not in array, for the sort, insert at correct position // TODO add + sort instead? 192 boolean add = true; 193 for (int j = 0; j < array.numberOfSignals(); j++) { 194 if (sr.getSignal().getDisplayName().compareTo(array.get(j).getSignal().getDisplayName()) < 0) { 195 array.add(j, sr); // added first time 196 add = false; 197 //log.debug("comparing list item {} name {}", j, sr.getSignal().getDisplayName()); 198 break; 199 } 200 } 201 if (add) { 202 array.add(sr); 203 //log.debug("comparing list item at last pos {} name {}", array.numberOfSignals() , sr.getSignal().getDisplayName()); 204 } 205 } 206 207 public static class NameSorter implements Comparator<SignalRow> 208 { 209 @Override 210 public int compare(SignalRow o1, SignalRow o2) { 211 return o2.getSignal().compareTo(o1.getSignal()); 212 } 213 } 214 215 private String checkSignalRow(SignalRow sr) { 216 Portal portal = sr.getPortal(); 217 OBlock fromBlock = sr.getFromBlock(); 218 OBlock toBlock = sr.getToBlock(); 219 String msg = null; 220 if (portal != null) { 221 if (toBlock == null && fromBlock == null) { 222 msg = Bundle.getMessage("SignalDirection", 223 portal.getName(), 224 portal.getFromBlock().getDisplayName(), 225 portal.getToBlock().getDisplayName()); 226 return msg; 227 } 228 OBlock pToBlk = portal.getToBlock(); 229 OBlock pFromBlk = portal.getFromBlock(); 230 if (pToBlk.equals(toBlock)) { 231 if (fromBlock == null) { 232 sr.setFromBlock(pFromBlk); 233 } 234 } else if (pFromBlk.equals(toBlock)) { 235 if (fromBlock == null) { 236 sr.setFromBlock(pToBlk); 237 } 238 } else if (pToBlk.equals(fromBlock)) { 239 if (toBlock == null) { 240 sr.setToBlock(pFromBlk); 241 } 242 } else if (pFromBlk.equals(fromBlock)) { 243 if (toBlock == null) { 244 sr.setToBlock(pToBlk); 245 } 246 } else { 247 msg = Bundle.getMessage("PortalBlockConflict", portal.getName(), 248 (toBlock != null ? toBlock.getDisplayName() : "(null to-block reference)")); 249 } 250 } else if (fromBlock != null && toBlock != null) { 251 Portal p = getPortalWithBlocks(fromBlock, toBlock); 252 if (p == null) { 253 msg = Bundle.getMessage("NoSuchPortal", fromBlock.getDisplayName(), toBlock.getDisplayName()); 254 } else { 255 sr.setPortal(p); 256 } 257 } 258 if (msg == null && fromBlock != null && fromBlock.equals(toBlock)) { 259 msg = Bundle.getMessage("SametoFromBlock", fromBlock.getDisplayName()); 260 } 261 return msg; 262 } 263 264 // From the PortalSet get the single portal using the given To and From OBlock. 265 private Portal getPortalWithBlocks(OBlock fromBlock, OBlock toBlock) { 266 Collection<Portal> portals = _portalMgr.getPortalSet(); 267 for (Portal portal : portals) { 268 OBlock fromBlk = portal.getFromBlock(); 269 OBlock toBlk = portal.getToBlock(); 270 if ((fromBlk.equals(fromBlock) && toBlk.equals(toBlock)) || 271 (fromBlk.equals(toBlock) && toBlk.equals(fromBlock))) { 272 return portal; 273 } 274 } 275 return null; 276 } 277 278 protected String checkDuplicateSignal(NamedBean signal) { 279 //log.debug("checkDuplSig checking for duplicate Signal in list by the same name"); 280 if (signal == null) { 281 return null; 282 } 283 for (SignalRow srow : _signalList) { 284 if (signal.equals(srow.getSignal())) { 285 return Bundle.getMessage("DuplSignalName", signal.getDisplayName(), 286 srow.getToBlock().getDisplayName(), srow.getPortal().getName(), 287 srow.getFromBlock().getDisplayName()); 288 } 289 } 290 return null; 291 } 292 293 private String checkDuplicateSignal(SignalRow row) { 294 //log.debug("checkDuplSig checking for duplicate Signal in list using new entry row"); 295 NamedBean signal = row.getSignal(); 296 if (signal == null) { 297 return null; 298 } 299 for (SignalRow srow : _signalList) { 300 if (srow.equals(row)) { 301 continue; 302 } 303 if (signal.equals(srow.getSignal())) { 304 return Bundle.getMessage("DuplSignalName", signal.getDisplayName(), srow.getToBlock().getDisplayName(), srow.getPortal().getName(), srow.getFromBlock().getDisplayName()); 305 306 } 307 } 308 return null; 309 } 310 311 private String checkDuplicateProtection(SignalRow row) { 312 Portal portal = row.getPortal(); 313 OBlock block = row.getToBlock(); 314 if (block == null || portal == null) { 315 return null; 316 } 317 for (SignalRow srow : _signalList) { 318 if (srow.equals(row)) { 319 continue; 320 } 321 if (block.equals(srow.getToBlock()) && portal.equals(srow.getPortal())) { 322 return Bundle.getMessage("DuplProtection", block.getDisplayName(), portal.getName(), srow.getFromBlock().getDisplayName(), srow.getSignal().getDisplayName()); 323 } 324 } 325 return null; 326 } 327 328 @Override 329 public int getColumnCount() { 330 return NUMCOLS + (_tabbed ? 1 : 0); // add Edit column on _tabbed 331 } 332 333 @Override 334 public int getRowCount() { 335 return _signalList.numberOfSignals() + (_tabbed ? 0 : 1); // + 1 row in _desktop to create entry row 336 // +1 adds the extra empty row at the bottom of the table display, causes IOB when called externally when _tabbed 337 } 338 339 @Override 340 public String getColumnName(int col) { 341 switch (col) { 342 case NAME_COLUMN: 343 return Bundle.getMessage("SignalName"); 344 case FROM_BLOCK_COLUMN: 345 return Bundle.getMessage("FromBlockName"); 346 case PORTAL_COLUMN: 347 return Bundle.getMessage("ThroughPortal"); 348 case TO_BLOCK_COLUMN: 349 return Bundle.getMessage("ToBlockName"); 350 case LENGTHCOL: 351 return Bundle.getMessage("Offset"); 352 case UNITSCOL: 353 case EDIT_COL: 354 return " "; 355 default: 356 // fall through 357 break; 358 } 359 return ""; 360 } 361 362 @Override 363 public Object getValueAt(int rowIndex, int columnIndex) { 364 if (!_tabbed && (rowIndex == _signalList.numberOfSignals())) { // this must be tempRow, a new entry, read values from tempRow 365 if (columnIndex == LENGTHCOL) { 366 //log.debug("GetValue SignalTable length entered {} =============== in row {}", _tempLen, rowIndex); 367 if (tempRow[UNITSCOL].equals(Bundle.getMessage("cm"))) { 368 return (twoDigit.format(_tempLen/10)); 369 } 370 return (twoDigit.format(_tempLen/25.4f)); 371 } 372 if (columnIndex == UNITSCOL) { 373 return tempRow[UNITSCOL].equals(Bundle.getMessage("cm")); // TODO renderer/special class 374 } 375 return tempRow[columnIndex]; 376 } 377 if (rowIndex >= _signalList.numberOfSignals() || rowIndex >= _lastIdx) { 378 //log.error("SignalTable requested ROW {}, SIZE is {}, expected {}", rowIndex, _signalList.numberOfSignals(), _lastIdx); 379 //log.debug("items in list: {}", _signalList.numberOfSignals()); // debug 380 return columnIndex + "" + rowIndex + "?"; 381 } 382 383 SignalRow signalRow = _signalList.get(rowIndex); // edit an existing array entry 384 switch (columnIndex) { 385 case NAME_COLUMN: 386 if (signalRow.getSignal() != null) { 387 return signalRow.getSignal().getDisplayName(); 388 } 389 break; 390 case FROM_BLOCK_COLUMN: 391 if (signalRow.getFromBlock() != null) { 392 return signalRow.getFromBlock().getDisplayName(); 393 } 394 break; 395 case PORTAL_COLUMN: 396 if (signalRow.getPortal() != null) { 397 return signalRow.getPortal().getName(); 398 } 399 break; 400 case TO_BLOCK_COLUMN: 401 if (signalRow.getToBlock() != null) { 402 return signalRow.getToBlock().getDisplayName(); 403 } 404 break; 405 case LENGTHCOL: 406 if (signalRow.isMetric()) { 407 return (twoDigit.format(signalRow.getLength()/10)); 408 } 409 return (twoDigit.format(signalRow.getLength()/25.4f)); 410 case UNITSCOL: 411 return signalRow.isMetric(); 412 case DELETE_COL: 413 return Bundle.getMessage("ButtonDelete"); 414 case EDIT_COL: 415 return Bundle.getMessage("ButtonEdit"); 416 default: 417 // fall through 418 break; 419 } 420 return ""; 421 } 422 423 @Override 424 public void setValueAt(Object value, int row, int col) { 425 String msg = null; 426 if (_signalList.numberOfSignals() == row) { // this is the new entry in tempRow, not yet in _signalList 427 if (col == DELETE_COL) { // labeled "Clear" in tempRow 428 initTempRow(); 429 fireTableRowsUpdated(row, row); 430 return; 431 } else if (col == UNITSCOL) { 432 if (value.equals(true)) { 433 tempRow[UNITSCOL] = Bundle.getMessage("cm"); 434 } else { 435 tempRow[UNITSCOL] = Bundle.getMessage("in"); 436 } 437 fireTableRowsUpdated(row, row); 438 return; 439 } else if (col == LENGTHCOL) { 440 //log.debug("SetValue SignalTable length set {} in row {}", value.toString(), row); 441 try { 442 _tempLen = IntlUtilities.floatValue(value.toString()); 443 //log.debug("setValue _tempLen = {} {}", _tempLen, tempRow[UNITSCOL]); 444 if (tempRow[UNITSCOL].equals(Bundle.getMessage("cm"))) { 445 _tempLen *= 10f; 446 } else { 447 _tempLen *= 25.4f; 448 } 449 } catch (ParseException e) { 450 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BadNumber", tempRow[LENGTHCOL]), 451 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE); 452 } 453 return; 454 } 455 String str = (String) value; 456 if (str == null || str.trim().length() == 0) { 457 tempRow[col] = null; 458 return; 459 } 460 tempRow[col] = str.trim(); 461 // try to add new value into new row in SignalTable 462 OBlock fromBlock = null; 463 OBlock toBlock = null; 464 Portal portal = null; 465 NamedBean signal; 466 OBlockManager OBlockMgr = InstanceManager.getDefault(OBlockManager.class); 467 if (tempRow[FROM_BLOCK_COLUMN] != null) { 468 fromBlock = OBlockMgr.getOBlock(tempRow[FROM_BLOCK_COLUMN]); 469 if (fromBlock == null) { 470 msg = Bundle.getMessage("NoSuchBlock", tempRow[FROM_BLOCK_COLUMN]); 471 } 472 } 473 if (msg == null && tempRow[TO_BLOCK_COLUMN] != null) { 474 toBlock = OBlockMgr.getOBlock(tempRow[TO_BLOCK_COLUMN]); 475 if (toBlock == null) { 476 msg = Bundle.getMessage("NoSuchBlock", tempRow[TO_BLOCK_COLUMN]); 477 } 478 } 479 if (msg == null) { 480 if (tempRow[PORTAL_COLUMN] != null) { 481 portal = _portalMgr.getPortal(tempRow[PORTAL_COLUMN]); 482 if (portal == null) { 483 msg = Bundle.getMessage("NoSuchPortalName", tempRow[PORTAL_COLUMN]); 484 } 485 } else { 486 if (fromBlock != null && toBlock != null) { 487 portal = getPortalWithBlocks(fromBlock, toBlock); 488 if (portal == null) { 489 msg = Bundle.getMessage("NoSuchPortal", tempRow[FROM_BLOCK_COLUMN], tempRow[TO_BLOCK_COLUMN]); 490 } else { 491 tempRow[PORTAL_COLUMN] = portal.getName(); 492 } 493 } 494 } 495 } 496 if (msg == null && tempRow[NAME_COLUMN] != null) { 497 signal = Portal.getSignal(tempRow[NAME_COLUMN]); 498 if (signal == null) { 499 msg = Bundle.getMessage("NoSuchSignal", tempRow[NAME_COLUMN]); 500 } else { 501 msg = checkDuplicateSignal(signal); 502 } 503 if (msg == null) { 504 if (fromBlock != null && toBlock != null) { 505 portal = getPortalWithBlocks(fromBlock, toBlock); 506 if (portal == null) { 507 msg = Bundle.getMessage("NoSuchPortal", tempRow[FROM_BLOCK_COLUMN], tempRow[TO_BLOCK_COLUMN]); 508 } else { 509 tempRow[PORTAL_COLUMN] = portal.getName(); 510 } 511 } else { 512 return; 513 } 514 } 515 if (msg == null) { 516 float length = 0.0f; 517 boolean isMetric = tempRow[UNITSCOL].equals(Bundle.getMessage("cm")); 518 try { 519 length = IntlUtilities.floatValue(tempRow[LENGTHCOL]); 520 if (isMetric) { 521 length *= 10f; 522 } else { 523 length *= 25.4f; 524 } 525 } catch (ParseException e) { 526 msg = Bundle.getMessage("BadNumber", tempRow[LENGTHCOL]); 527 } 528 if (isMetric) { 529 tempRow[UNITSCOL] = Bundle.getMessage("cm"); 530 } else { 531 tempRow[UNITSCOL] = Bundle.getMessage("in"); 532 } 533 if (msg == null) { 534 // all checks passed, create new SignalRow to add to _signalList 535 SignalRow signalRow = new SignalRow(signal, fromBlock, portal, toBlock, length, isMetric); 536 msg = setSignal(signalRow, false); 537 //if (msg == null) { 538 //if (signalRow.getLength() == 0) { 539 //log.error("#544 empty tempRow added to SignalList (now {})", _signalList.numberOfSignals()); 540 //} 541 //_signalList.add(signalRow); // BUG no need to do this, as the table will be updated from the OBlock settings 542 // it caused the ghost row, which is squasehed out when the list is rebuilt 543 //} 544 initTempRow(); 545 fireTableDataChanged(); 546 } 547 } 548 } 549 } else { // Editing an existing signal configuration row 550 SignalRow signalRow; 551 try { 552 signalRow = _signalList.get(row); 553 //log.debug("SetValue fetched SignalRow {}", row); 554 } catch (IndexOutOfBoundsException e) { 555 // ignore, happened in 4.21.2 for some reason, showed as a duplicate row after new entry, now fixed 556 log.warn("setValue out of range"); 557 return; 558 } 559 OBlockManager OBlockMgr = InstanceManager.getDefault(OBlockManager.class); 560 switch (col) { 561 case NAME_COLUMN: 562 NamedBean signal = Portal.getSignal((String) value); 563 if (signal == null) { 564 msg = Bundle.getMessage("NoSuchSignal", value); 565 // signalRow.setSignal(null); 566 break; 567 } 568 Portal portal = signalRow.getPortal(); 569 if (portal != null && signalRow.getToBlock() != null) { 570 NamedBean oldSignal = signalRow.getSignal(); 571 signalRow.setSignal(signal); 572 msg = checkDuplicateSignal(signalRow); 573 if (msg == null) { 574 deleteSignal(signalRow); // delete old 575 msg = setSignal(signalRow, false); 576 fireTableRowsUpdated(row, row); 577 } else { 578 signalRow.setSignal(oldSignal); 579 } 580 } 581 break; 582 case FROM_BLOCK_COLUMN: 583 OBlock block = OBlockMgr.getOBlock((String) value); 584 if (block == null) { 585 msg = Bundle.getMessage("NoSuchBlock", value); 586 break; 587 } 588 if (block.equals(signalRow.getFromBlock())) { 589 break; // no change 590 } 591 deleteSignal(signalRow); // delete old 592 signalRow.setFromBlock(block); 593 portal = signalRow.getPortal(); 594 if (checkPortalBlock(portal, block)) { 595 signalRow.setToBlock(null); 596 } else { 597 // get new portal 598 portal = getPortalWithBlocks(block, signalRow.getToBlock()); 599 signalRow.setPortal(portal); 600 } 601 msg = checkSignalRow(signalRow); 602 if (msg == null) { 603 msg = checkDuplicateProtection(signalRow); 604 } else { 605 signalRow.setPortal(null); 606 break; 607 } 608 if (msg == null && signalRow.getPortal() != null) { 609 msg = setSignal(signalRow, true); 610 } else { 611 signalRow.setPortal(null); 612 } 613 fireTableRowsUpdated(row, row); 614 break; 615 case PORTAL_COLUMN: 616 portal = _portalMgr.getPortal((String) value); 617 if (portal == null) { 618 msg = Bundle.getMessage("NoSuchPortalName", value); 619 break; 620 } 621 deleteSignal(signalRow); // delete old in Portal 622 signalRow.setPortal(portal); 623 block = signalRow.getToBlock(); 624 if (checkPortalBlock(portal, block)) { 625 signalRow.setFromBlock(null); 626 } else { 627 block = signalRow.getFromBlock(); 628 if (checkPortalBlock(portal, block)) { 629 signalRow.setToBlock(null); 630 } 631 } 632 msg = checkSignalRow(signalRow); 633 if (msg == null) { 634 msg = checkDuplicateProtection(signalRow); 635 } else { 636 signalRow.setToBlock(null); 637 break; 638 } 639 if (msg == null) { 640 signalRow.setPortal(portal); 641 msg = setSignal(signalRow, false); 642 fireTableRowsUpdated(row, row); 643 } 644 break; 645 case TO_BLOCK_COLUMN: 646 block = OBlockMgr.getOBlock((String) value); 647 if (block == null) { 648 msg = Bundle.getMessage("NoSuchBlock", value); 649 break; 650 } 651 if (block.equals(signalRow.getToBlock())) { 652 break; // no change 653 } 654 deleteSignal(signalRow); // delete old in Portal 655 signalRow.setToBlock(block); 656 portal = signalRow.getPortal(); 657 if (checkPortalBlock(portal, block)) { 658 signalRow.setFromBlock(null); 659 } else { 660 // get new portal 661 portal = getPortalWithBlocks(signalRow.getFromBlock(), block); 662 signalRow.setPortal(portal); 663 } 664 msg = checkSignalRow(signalRow); 665 if (msg == null) { 666 msg = checkDuplicateProtection(signalRow); 667 } else { 668 signalRow.setPortal(null); 669 break; 670 } 671 if (msg == null && signalRow.getPortal() != null) { 672 msg = setSignal(signalRow, true); 673 } else { 674 signalRow.setPortal(null); 675 } 676 fireTableRowsUpdated(row, row); 677 break; 678 case LENGTHCOL: // named "Offset" in table header, will be stored on ToBlock 679 //log.debug("SetValue SignalTable length set {} in row {}", value.toString(), row); 680 try { 681 float len = IntlUtilities.floatValue(value.toString()); 682 //log.debug("SetValue Offset copied to: {} in row {}", len, row); 683 if (signalRow.isMetric()) { 684 signalRow.setLength(len * 10.0f); 685 } else { 686 signalRow.setLength(len * 25.4f); 687 } 688 //log.debug("Length stored in SR as {}", signalRow.getLength()); 689 //fireTableRowsUpdated(row, row); // reads (GetValue) from portal signal as configured? ignores the new entry 690 } catch (ParseException e) { 691 msg = Bundle.getMessage("BadNumber", value); 692 //log.error("SetValue BadNumber {}", value); 693 } 694 if (msg == null && signalRow.getPortal() != null) { 695 msg = setSignal(signalRow, false); // configures Portal & OBlock 696 } else { 697 signalRow.setPortal(null); 698 } 699 //fireTableRowsUpdated(row, row); // not needed, change will be picked up from the OBlockTable PropertyChange 700 break; 701 case UNITSCOL: 702 signalRow.setMetric((Boolean)value); 703 fireTableRowsUpdated(row, row); 704 break; 705 case DELETE_COL: 706 deleteSignal(signalRow); 707 _signalList.remove(signalRow); 708 fireTableDataChanged(); 709 break; 710 case EDIT_COL: 711 editSignal(Portal.getSignal(signalRow.getSignal().getDisplayName()), signalRow); 712 break; 713 default: 714 // fall through 715 break; 716 } 717 } 718 719 if (msg != null) { 720 JmriJOptionPane.showMessageDialog(null, msg, 721 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 722 // doesn't close by clicking OK after DnD as focus lost, only Esc in JMRI 4.21.2 on macOS 723 } 724 } 725 726 // also used in _tabbed EditSignalPane 727 protected void deleteSignal(SignalRow signalRow) { 728 Portal portal = signalRow.getPortal(); 729 if (portal == null) { 730 portal = getPortalWithBlocks(signalRow.getFromBlock(), signalRow.getToBlock()); 731 } 732 if (portal != null) { 733 // remove signal from previous portal 734 portal.deleteSignal(signalRow.getSignal()); 735 } 736 } 737 738 private void editSignal(NamedBean signal, SignalRow sr) { 739 if (_tabbed && signal != null && !inEditMode) { 740 inEditMode = true; 741 // open SignalEditFrame 742 SignalEditFrame sef = new SignalEditFrame(Bundle.getMessage("TitleSignalEditor", sr.getSignal().getDisplayName()), 743 signal, sr, this); 744 // TODO run on separate thread? 745 sef.setVisible(true); 746 } 747 } 748 749 static private String setSignal(SignalRow signalRow, boolean deletePortal) { 750 Portal portal = signalRow.getPortal(); 751 float length = signalRow.getLength(); 752 if (portal.setProtectSignal(signalRow.getSignal(), length, signalRow.getToBlock())) { 753 if (signalRow.getFromBlock() == null) { 754 signalRow.setFromBlock(portal.getOpposingBlock(signalRow.getToBlock())); 755 } 756 } else { 757 if (deletePortal) { 758 signalRow.setPortal(null); 759 } else { 760 signalRow.setToBlock(null); 761 } 762 return Bundle.getMessage("PortalBlockConflict", portal.getName(), 763 signalRow.getToBlock().getDisplayName()); 764 } 765 return null; 766 } 767 768 static private boolean checkPortalBlock(Portal portal, OBlock block) { 769 if (block == null) { 770 return false; 771 } 772 return (block.equals(portal.getToBlock()) || block.equals(portal.getFromBlock())); 773 } 774 775 @Override 776 public boolean isCellEditable(int row, int col) { 777 return true; 778 } 779 780 @Override 781 public Class<?> getColumnClass(int col) { 782 switch (col) { 783 case DELETE_COL: 784 case EDIT_COL: 785 return JButton.class; 786 case UNITSCOL: 787 return JToggleButton.class; 788 case NAME_COLUMN: 789 default: 790 return String.class; 791 } 792 } 793 794 public static int getPreferredWidth(int col) { 795 switch (col) { 796 case NAME_COLUMN: 797 case FROM_BLOCK_COLUMN: 798 case PORTAL_COLUMN: 799 case TO_BLOCK_COLUMN: 800 return new JTextField(12).getPreferredSize().width; 801 case LENGTHCOL: 802 return new JTextField(6).getPreferredSize().width; 803 case UNITSCOL: 804 return new JTextField(5).getPreferredSize().width; 805 case DELETE_COL: 806 return new JButton("DELETE").getPreferredSize().width; // NOI18N 807 case EDIT_COL: 808 return new JButton("EDIT").getPreferredSize().width; // NOI18N 809 default: 810 // fall through 811 break; 812 } 813 return 5; 814 } 815 816 public boolean editMode() { 817 return inEditMode; 818 } 819 820 public void setEditMode(boolean editing) { 821 inEditMode = editing; 822 } 823 824 @Override 825 public void propertyChange(PropertyChangeEvent e) { 826 String property = e.getPropertyName(); 827 if (property.equals("length") || property.equals("portalCount") 828 || property.equals("UserName") || property.equals("signalChange")) { 829 makeList(); 830 fireTableDataChanged(); 831 } 832 } 833 834 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalTableModel.class); 835 836}