001package jmri.jmrit.display.controlPanelEditor; 002 003import java.awt.Color; 004import java.awt.Dimension; 005import java.awt.datatransfer.DataFlavor; 006import java.awt.datatransfer.UnsupportedFlavorException; 007import java.io.IOException; 008import java.util.ArrayList; 009import java.util.Iterator; 010import java.util.List; 011import java.util.SortedSet; 012 013import javax.swing.BorderFactory; 014import javax.swing.Box; 015import javax.swing.BoxLayout; 016import javax.swing.JButton; 017import javax.swing.JComponent; 018import javax.swing.JLabel; 019import javax.swing.JPanel; 020import javax.swing.JScrollPane; 021import javax.swing.JTextField; 022import javax.swing.event.ListSelectionEvent; 023import javax.swing.event.ListSelectionListener; 024 025import jmri.InstanceManager; 026import jmri.jmrit.catalog.DragJLabel; 027import jmri.jmrit.catalog.NamedIcon; 028import jmri.jmrit.display.Editor; 029import jmri.jmrit.display.Positionable; 030import jmri.jmrit.logix.OBlock; 031import jmri.jmrit.logix.OBlockManager; 032import jmri.jmrit.logix.Portal; 033import jmri.jmrit.logix.PortalManager; 034import jmri.util.swing.JmriJOptionPane; 035 036/** 037 * 038 * @author Pete Cressman Copyright: Copyright (c) 2011 039 */ 040public class EditPortalFrame extends EditFrame implements ListSelectionListener { 041 042 private PortalList _portalList; 043 private JTextField _portalName; 044 private Portal _currentPortal; 045 046 /* Ctor for fix a portal error */ 047 public EditPortalFrame(String title, CircuitBuilder parent, OBlock block, Portal portal, PortalIcon icon) { 048 this(title, parent, block); 049 String name = portal.getName(); 050 _portalName.setText(name); 051 052 StringBuilder sb = new StringBuilder(); 053 if (icon != null) { 054 setSelected(icon); 055 } else { 056 sb.append(Bundle.getMessage("portalHasNoIcon", name)); 057 sb.append("\n"); 058 } 059 if (_canEdit) { 060 String msg = _parent.checkForPortals(block, "BlockPaths"); 061 if (msg.length() > 0) { 062 sb.append(msg); 063 sb.append("\n"); 064 sb.append(Bundle.getMessage("portIconPosition1")); 065 sb.append("\n"); 066 sb.append(Bundle.getMessage("portIconPosition2")); 067 sb.append("\n"); 068 } else { 069 msg = _parent.checkForPortalIcons(block, "DirectionArrow"); 070 if (msg.length() > 0) { 071 sb.append(msg); 072 sb.append("\n"); 073 } 074 } 075 } 076 if (sb.toString().length() > 0) { 077 JmriJOptionPane.showMessageDialog(EditPortalFrame.this, sb.toString(), 078 Bundle.getMessage("incompleteCircuit"), JmriJOptionPane.INFORMATION_MESSAGE); 079 } 080 } 081 082 public EditPortalFrame(String title, CircuitBuilder parent, OBlock block) { 083 super(title, parent, block); 084 pack(); 085 String msg = _parent.checkForTrackIcons(block, "BlockPortals"); 086 if (msg.length() > 0) { 087 _canEdit = false; 088 JmriJOptionPane.showMessageDialog(EditPortalFrame.this, msg, 089 Bundle.getMessage("incompleteCircuit"), JmriJOptionPane.INFORMATION_MESSAGE); 090 } 091 } 092 093 @Override 094 protected JPanel makeContentPanel() { 095 JPanel portalPanel = new JPanel(); 096 portalPanel.setLayout(new BoxLayout(portalPanel, BoxLayout.Y_AXIS)); 097 098 JPanel panel = new JPanel(); 099 panel.add(new JLabel(Bundle.getMessage("PortalTitle", _homeBlock.getDisplayName()))); 100 portalPanel.add(panel); 101 _portalName = new JTextField(); 102 _portalList = new PortalList(_homeBlock, this); 103 _portalList.addListSelectionListener(this); 104 portalPanel.add(new JScrollPane(_portalList)); 105 106 JButton clearButton = new JButton(Bundle.getMessage("buttonClearSelection")); 107 clearButton.addActionListener(a -> clearListSelection()); 108 panel = new JPanel(); 109 panel.add(clearButton); 110 portalPanel.add(panel); 111 portalPanel.add(Box.createVerticalStrut(STRUT_SIZE)); 112 113 panel = new JPanel(); 114 panel.add(CircuitBuilder.makeTextBoxPanel( 115 false, _portalName, "portalName", true, null)); 116 _portalName.setPreferredSize(new Dimension(300, _portalName.getPreferredSize().height)); 117 _portalName.setToolTipText(Bundle.getMessage("TooltipPortalName")); 118 portalPanel.add(panel); 119 120 panel = new JPanel(); 121 JButton changeButton = new JButton(Bundle.getMessage("buttonChangeName")); 122 changeButton.addActionListener(a -> changePortalName()); 123 changeButton.setToolTipText(Bundle.getMessage("ToolTipChangeName")); 124 panel.add(changeButton); 125 126 JButton deleteButton = new JButton(Bundle.getMessage("buttonDeletePortal")); 127 deleteButton.addActionListener(a -> deletePortal()); 128 deleteButton.setToolTipText(Bundle.getMessage("ToolTipDeletePortal")); 129 panel.add(deleteButton); 130 portalPanel.add(panel); 131 132 panel = new JPanel(); 133 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 134 portalPanel.add(Box.createVerticalStrut(STRUT_SIZE)); 135 JLabel l = new JLabel(Bundle.getMessage("enterNameToDrag")); 136 l.setAlignmentX(JComponent.LEFT_ALIGNMENT); 137 panel.add(l); 138 l = new JLabel(Bundle.getMessage("dragNewIcon")); 139 l.setAlignmentX(JComponent.LEFT_ALIGNMENT); 140 panel.add(l); 141 panel.add(Box.createVerticalStrut(STRUT_SIZE / 2)); 142 l = new JLabel(Bundle.getMessage("selectPortal")); 143 l.setAlignmentX(JComponent.LEFT_ALIGNMENT); 144 panel.add(l); 145 panel.add(Box.createVerticalStrut(STRUT_SIZE / 2)); 146 l = new JLabel(Bundle.getMessage("portIconPosition1")); 147 l.setAlignmentX(JComponent.LEFT_ALIGNMENT); 148 panel.add(l); 149 l = new JLabel(Bundle.getMessage("portIconPosition2")); 150 l.setAlignmentX(JComponent.LEFT_ALIGNMENT); 151 panel.add(l); 152 JPanel p = new JPanel(); 153 p.add(panel); 154 portalPanel.add(p); 155 156 portalPanel.add(makeDndIconPanel()); 157 portalPanel.add(Box.createVerticalStrut(STRUT_SIZE)); 158 portalPanel.add(makeDoneButtonPanel()); 159 return portalPanel; 160 } 161 162 @Override 163 protected void clearListSelection() { 164 _portalList.clearSelection(); 165 _portalName.setText(null); 166 _parent._editor.highlight(null); 167 } 168 169 @Override 170 public void valueChanged(ListSelectionEvent e) { 171 if (askForNameChange()) { 172 return; 173 } 174 Portal portal = _portalList.getSelectedValue(); 175 if (portal != null) { 176 _portalName.setText(portal.getName()); 177 hightLightIcon(portal); 178 _currentPortal = portal; 179 } else { 180 _portalName.setText(null); 181 } 182 } 183 184 private void hightLightIcon(Portal portal) { 185 _parent._editor.highlight(null); 186 List<PortalIcon> piArray = _parent.getPortalIcons(portal); 187 for (PortalIcon pi : piArray) { 188 _parent._editor.highlight(pi); 189 } 190 } 191 192 private boolean askForNameChange() { 193 String name = _portalName.getText(); 194 if (_currentPortal != null && !_currentPortal.getName().equals(name)) { 195 if (name.length() > 0) { 196 int answer = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("changeOrCancel", 197 _currentPortal.getName(), name, Bundle.getMessage("BeanNamePortal")), 198 Bundle.getMessage("makePortal"), JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE); 199 if (answer == JmriJOptionPane.YES_OPTION) { 200 setName(_currentPortal, name); 201 return true; 202 } 203 } 204 } 205 return false; 206 } 207 208 protected void setSelected(PortalIcon icon) { 209 if (!canEdit()) { 210 return; 211 } 212 Portal portal = icon.getPortal(); 213 if (portal != null ) { 214 if (!portal.equals(_portalList.getSelectedValue())) { 215 _parent._editor.highlight(null); 216 } 217 List<PortalIcon> piArray = _parent.getPortalIcons(portal); 218 for (PortalIcon pi : piArray) { 219 _parent._editor.highlight(pi); 220 } 221 } 222 _portalList.setSelectedValue(portal, true); 223 } 224 225 /* 226 * *********************** end setup ************************* 227 */ 228 229 private void changePortalName() { 230 Portal portal = _portalList.getSelectedValue(); 231 String name = _portalName.getText(); 232 if (portal == null || name == null || name.trim().length() == 0) { 233 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("changePortalName", Bundle.getMessage("buttonChangeName")), 234 Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE); 235 return; 236 } 237 setName(portal, name); 238 } 239 240 private void setName(Portal portal, String name) { 241 String msg = portal.setName(name); 242 if (msg == null) { 243 _portalList.dataChange(); 244 hightLightIcon(portal); 245 } else { 246 JmriJOptionPane.showMessageDialog(this, msg, 247 Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE); 248 } 249 } 250 private void deletePortal() { 251 String name = _portalName.getText(); 252 if (name == null || name.length() == 0) { 253 return; 254 } 255 Portal portal = _portalList.getSelectedValue(); 256 if (portal == null) { 257 PortalManager portalMgr = InstanceManager.getDefault(jmri.jmrit.logix.PortalManager.class); 258 portal = portalMgr.getPortal(name); 259 } 260 if (portal == null) { 261 return; 262 } 263 if (!_suppressWarnings) { 264 int val = JmriJOptionPane.showOptionDialog(this, Bundle.getMessage("confirmPortalDelete", portal.getName()), 265 Bundle.getMessage("WarningTitle"), JmriJOptionPane.DEFAULT_OPTION, 266 JmriJOptionPane.QUESTION_MESSAGE, null, 267 new Object[]{Bundle.getMessage("ButtonYes"), 268 Bundle.getMessage("ButtonYesPlus"), 269 Bundle.getMessage("ButtonNo"),}, 270 Bundle.getMessage("ButtonNo")); // default NO 271 if ( val == 2 || val == -1 ) { // array position 2 or dialog cancelled 272 return; 273 } 274 if (val == 1) { // array position 1 suppress future warnings 275 _suppressWarnings = true; 276 } 277 } 278 if (portal.dispose()) { 279 _portalList.dataChange(); 280 _portalName.setText(null); 281 OBlock oppBlock = portal.getOpposingBlock(_homeBlock); 282 ArrayList<PortalIcon> removeList = new ArrayList<>(_parent.getPortalIcons(portal)); 283 for (PortalIcon icon : removeList) { 284 _parent.getCircuitIcons(oppBlock).remove(icon); 285 icon.remove(); // will call _parent.deletePortalIcon(icon) 286 } 287 } 288 } 289 290 @Override 291 protected void closingEvent(boolean close) { 292 StringBuffer sb = new StringBuffer(); 293 String msg = _parent.checkForPortals(_homeBlock, "BlockPaths"); 294 if(msg.length() > 0) { 295 sb.append(msg); 296 sb.append("\n"); 297 } 298 if (_canEdit) { 299 msg = _parent.checkForPortalIcons(_homeBlock, "BlockPaths"); 300 if(msg.length() > 0) { 301 sb.append(msg); 302 sb.append("\n"); 303 } 304 } 305 closingEvent(close, sb.toString()); 306 } 307 308 protected String checkPortalIcons(Portal portal, boolean moved, String key) { 309 List<PortalIcon> iconMap = _parent.getPortalIcons(portal); 310 if (iconMap.isEmpty()) { 311 return Bundle.getMessage("noPortalIcon", portal.getName(), Bundle.getMessage(key)); 312 } 313 314 String name = portal.getName(); 315 boolean homeBlockCovered = false; 316 boolean adjacentBlockCovered = false; 317 OBlock adjacentBlock = null; 318 for (PortalIcon icon : iconMap) { 319 Portal p = icon.getPortal(); 320 if (p == null) { 321 _parent.deletePortalIcon(icon); 322 log.error("Removed PortalIcon without Portal"); 323 } else { 324 OBlock fromBlock = portal.getFromBlock(); 325 OBlock toBlock = portal.getToBlock(); 326 if (!_homeBlock.equals(fromBlock) && !_homeBlock.equals(toBlock)) { 327 log.error("HomeBlock \"{}\" does not know {}", _homeBlock.getDisplayName(), portal.getDescription()); 328 return showIntersectMessage(_homeBlock, portal, moved); 329 } 330 boolean homeCovered = _parent.iconIntersectsBlock(icon, _homeBlock); 331 332 if (_homeBlock.equals(fromBlock)) { 333 adjacentBlock = toBlock; 334 } else { 335 adjacentBlock = fromBlock; 336 } 337 boolean adjacentCovered = adjacentBlock != null &&_parent.iconIntersectsBlock(icon, adjacentBlock); 338 339 OBlock block = findAdjacentBlock(icon); 340 if (adjacentBlock == null) { // maybe first time 341 if (block != null) { 342 boolean valid; 343 if (_homeBlock.equals(fromBlock)) { 344 valid = portal.setToBlock(block, true); 345 } else { 346 valid = portal.setFromBlock(block, true); 347 } 348 _portalList.dataChange(); 349 log.debug("Adjacent block change of null to {} is {} valid.", 350 block.getDisplayName(), (valid?"":"NOT")); 351 adjacentBlock = block; 352 if (homeCovered) { 353 return null; // home and adjacent covered by icon 354 } 355 adjacentCovered = true; 356 } 357 } else { 358 if (block != null) { 359 if (moved) { 360 if (!block.equals(adjacentBlock)) { 361 int result = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("repositionPortal", 362 name, _homeBlock.getDisplayName(), block.getDisplayName()), 363 Bundle.getMessage("makePortal"), JmriJOptionPane.YES_NO_OPTION, 364 JmriJOptionPane.QUESTION_MESSAGE); 365 if (result == JmriJOptionPane.YES_OPTION) { 366 boolean valid; 367 if (_homeBlock.equals(fromBlock)) { 368 valid = portal.setToBlock(block, true); 369 } else { 370 valid = portal.setFromBlock(block, true); 371 } 372 _portalList.dataChange(); 373 log.debug("Adjacent block change of {} to {} is {} valid.", 374 adjacentBlock.getDisplayName(), block.getDisplayName(), (valid?"":"NOT")); 375 adjacentBlock = block; 376 if (homeCovered) { 377 return null; // home and adjacent covered by icon 378 } 379 } 380 } 381 } else { 382 if (!block.equals(adjacentBlock)) { 383 log.error("Icon NOT moved, but Adjacent block change of {} to {}!", 384 adjacentBlock.getDisplayName(), block.getDisplayName()); 385 } 386 } 387 adjacentCovered = true; 388 } else { 389 adjacentCovered = false; 390 } 391 } 392 if (homeCovered) { 393 homeBlockCovered = true; 394 } 395 if (adjacentCovered) { 396 adjacentBlockCovered = true; 397 } 398 log.debug("checkPortalIcons for {} homeCovered= {} adjacentCovered= {}", name, homeBlockCovered, adjacentBlockCovered); 399 } 400 } 401 if (!homeBlockCovered) { 402 return showIntersectMessage(_homeBlock, portal, moved); 403 } 404 if (!adjacentBlockCovered) { 405 return showIntersectMessage(adjacentBlock, portal, moved); 406 } 407 return null; 408 } 409 410 private String showIntersectMessage(OBlock block, Portal portal, boolean moved) { 411 String msg = null; 412 if (block == null) { 413 msg = Bundle.getMessage("icondNeedsAdjacent", portal.getDescription()); 414 } else { 415 List<Positionable> list = _parent.getCircuitIcons(block); 416 if (list.isEmpty()) { 417 msg = Bundle.getMessage("needIcons", block.getDisplayName(), Bundle.getMessage("BlockPortals")); 418 } else { 419 msg = Bundle.getMessage("iconNotOnBlock", block.getDisplayName(), portal.getDescription()); 420 } 421 } 422 if (moved) { 423 JmriJOptionPane.showMessageDialog(this, msg, 424 Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE); 425 } 426 return msg; 427 } 428 429 /* 430 * If icon is on the home block, find another intersecting block. 431 */ 432 private OBlock findAdjacentBlock(PortalIcon icon) { 433 ArrayList<OBlock> neighbors = new ArrayList<>(); 434 OBlockManager manager = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class); 435 SortedSet<OBlock> oblocks = manager.getNamedBeanSet(); 436 for (OBlock block : oblocks) { 437 if (block.equals(_homeBlock)) { 438 continue; 439 } 440 if (_parent.iconIntersectsBlock(icon, block)) { 441 neighbors.add(block); 442 } 443 } 444 OBlock block = null; 445 if (neighbors.size() == 1) { 446 block = neighbors.get(0); 447 } else if (neighbors.size() > 1) { 448 // show list 449 block = neighbors.get(0); 450 String[] selects = new String[neighbors.size()]; 451 Iterator<OBlock> iter = neighbors.iterator(); 452 int i = 0; 453 while (iter.hasNext()) { 454 selects[i++] = iter.next().getDisplayName(); 455 } 456 Object select = JmriJOptionPane.showInputDialog(this, Bundle.getMessage("multipleBlockSelections", 457 _homeBlock.getDisplayName()), Bundle.getMessage("QuestionTitle"), 458 JmriJOptionPane.QUESTION_MESSAGE, null, selects, null); 459 if (select != null) { 460 iter = neighbors.iterator(); 461 while (iter.hasNext()) { 462 block = iter.next(); 463 if (((String) select).equals(block.getDisplayName())) { 464 break; 465 } 466 } 467 } 468 } 469/* if (log.isDebugEnabled()) { 470 log.debug("findAdjacentBlock: neighbors.size()= {} return {}", 471 neighbors.size(), (block == null ? "null" : block.getDisplayName())); 472 }*/ 473 return block; 474 } 475 476 //////////////////////////// DnD //////////////////////////// 477 protected JPanel makeDndIconPanel() { 478 JPanel dndPanel = new JPanel(); 479 dndPanel.setLayout(new BoxLayout(dndPanel, BoxLayout.Y_AXIS)); 480 481 JPanel p = new JPanel(); 482 JLabel l = new JLabel(Bundle.getMessage("dragIcon")); 483 p.add(l); 484 dndPanel.add(p); 485 486 NamedIcon icon = _parent._editor.getPortalIconMap().get(PortalIcon.VISIBLE); 487 JPanel panel = new JPanel(); 488 panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), 489 Bundle.getMessage("BeanNamePortal"))); 490 try { 491 JLabel label = new IconDragJLabel(new DataFlavor(Editor.POSITIONABLE_FLAVOR)); 492 label.setIcon(icon); 493 label.setName(Bundle.getMessage("BeanNamePortal")); 494 panel.add(label); 495 } catch (java.lang.ClassNotFoundException cnfe) { 496 log.error("Unable to find class supporting {}", Editor.POSITIONABLE_FLAVOR, cnfe); 497 } 498 dndPanel.add(panel); 499 return dndPanel; 500 } 501 502 public class IconDragJLabel extends DragJLabel { 503 504 boolean addSecondIcon = false; 505 506 public IconDragJLabel(DataFlavor flavor) { 507 super(flavor); 508 } 509 510 @Override 511 protected boolean okToDrag() { 512 String name = _portalName.getText(); 513 if (name == null || name.trim().length() == 0) { 514 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("needPortalName"), 515 Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE); 516 return false; 517 } 518 PortalManager portalMgr = InstanceManager.getDefault(jmri.jmrit.logix.PortalManager.class); 519 Portal portal = portalMgr.getPortal(name); 520 if (portal == null) { 521 return true; 522 } 523 OBlock toBlock = portal.getToBlock(); 524 OBlock fromBlock = portal.getFromBlock(); 525 if (!_homeBlock.equals(fromBlock) && !_homeBlock.equals(toBlock)) { 526 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("portalNeedsBlock", name, fromBlock, toBlock), 527 Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE); 528 return false; 529 } 530 List<PortalIcon> piArray = _parent.getPortalIcons(portal); 531 for (PortalIcon pi : piArray) { 532 _parent._editor.highlight(pi); 533 } 534 switch (piArray.size()) { 535 case 0: 536 return true; 537 case 1: 538 PortalIcon i = piArray.get(0); 539 if (_parent.iconIntersectsBlock(i, toBlock) && _parent.iconIntersectsBlock(i,fromBlock)) { 540 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("portalIconExists", name), 541 Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE); 542 return false; 543 } 544 if (addSecondIcon) { 545 return true; 546 } 547 int result = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("portalWant2Icons", name), 548 Bundle.getMessage("makePortal"), JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE); 549 if (result == JmriJOptionPane.YES_OPTION) { 550 addSecondIcon = true; 551 return true; 552 } 553 break; 554 default: 555 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("portalIconExists", name), 556 Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE); 557 } 558 return false; 559 } 560 561 @Override 562 public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { 563 if (!isDataFlavorSupported(flavor)) { 564 return null; 565 } 566 if (DataFlavor.stringFlavor.equals(flavor)) { 567 return null; 568 } 569 String name = _portalName.getText(); 570 Portal portal = _homeBlock.getPortalByName(name); 571 if (portal == null) { 572 PortalManager portalMgr = InstanceManager.getDefault(PortalManager.class); 573 portal = portalMgr.createNewPortal(name); 574 portal.setFromBlock(_homeBlock, false); 575 _portalList.dataChange(); 576 } 577 addSecondIcon = false; 578 PortalIcon icon = new PortalIcon(_parent._editor, portal); 579 ArrayList<Positionable> group = _parent.getCircuitIcons(_homeBlock); 580 group.add(icon); 581 _parent.getPortalIcons(portal).add(icon); 582 _parent._editor.setSelectionGroup(group); 583 icon.setLevel(Editor.MARKERS); 584 icon.setStatus(PortalIcon.VISIBLE); 585 return icon; 586 } 587 } 588 589 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EditPortalFrame.class); 590 591}