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