001package jmri.jmrit.display; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005 006import javax.annotation.Nonnull; 007import javax.swing.AbstractAction; 008import javax.swing.ButtonGroup; 009import javax.swing.JMenu; 010import javax.swing.JPopupMenu; 011import javax.swing.JRadioButtonMenuItem; 012 013import jmri.InstanceManager; 014import jmri.NamedBeanHandle; 015import jmri.SignalMast; 016import jmri.Transit; 017import jmri.NamedBean.DisplayOptions; 018import jmri.jmrit.catalog.NamedIcon; 019import jmri.jmrit.display.palette.SignalMastItemPanel; 020import jmri.jmrit.picker.PickListModel; 021import jmri.util.swing.JmriJOptionPane; 022import jmri.util.swing.JmriMouseEvent; 023 024/** 025 * An icon to display a status of a {@link jmri.SignalMast}. 026 * <p> 027 * The icons displayed are loaded from the {@link jmri.SignalAppearanceMap} in 028 * the {@link jmri.SignalMast}. 029 * 030 * @see jmri.SignalMastManager 031 * @see jmri.InstanceManager 032 * @author Bob Jacobsen Copyright (C) 2009, 2014 033 */ 034public class SignalMastIcon extends PositionableIcon implements java.beans.PropertyChangeListener { 035 036 public SignalMastIcon(Editor editor) { 037 // super ctor call to make sure this is an icon label 038 super(editor); 039 _control = true; 040 } 041 042 private NamedBeanHandle<SignalMast> namedMast; 043 044 public void setShowAutoText(boolean state) { 045 _text = state; 046 _icon = !_text; 047 } 048 049 @Override 050 public Positionable deepClone() { 051 SignalMastIcon pos = new SignalMastIcon(_editor); 052 return finishClone(pos); 053 } 054 055 protected Positionable finishClone(SignalMastIcon pos) { 056 pos.setSignalMast(getNamedSignalMast()); 057 pos._iconMap = cloneMap(_iconMap, pos); 058 pos.setClickMode(getClickMode()); 059 pos.setLitMode(getLitMode()); 060 pos.useIconSet(useIconSet()); 061 return super.finishClone(pos); 062 } 063 064 /** 065 * Attached a signalmast element to this display item 066 * 067 * @param sh Specific SignalMast handle 068 */ 069 public void setSignalMast(NamedBeanHandle<SignalMast> sh) { 070 if (namedMast != null) { 071 getSignalMast().removePropertyChangeListener(this); 072 } 073 namedMast = sh; 074 if (namedMast != null) { 075 getIcons(); 076 displayState(mastState()); 077 getSignalMast().addPropertyChangeListener(this, namedMast.getName(), "SignalMast Icon"); 078 } 079 } 080 081 /** 082 * Taken from the layout editor Attached a numbered element to this display 083 * item 084 * 085 * @param pName Used as a system/user name to lookup the SignalMast object 086 */ 087 public void setSignalMast(String pName) { 088 SignalMast mMast = InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBean(pName); 089 if (mMast == null) { 090 log.warn("did not find a SignalMast named {}", pName); 091 } else { 092 setSignalMast(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, mMast)); 093 } 094 } 095 096 private void getIcons() { 097 _iconMap = new java.util.HashMap<>(); 098 java.util.Enumeration<String> e = getSignalMast().getAppearanceMap().getAspects(); 099 boolean error = false; 100 while (e.hasMoreElements()) { 101 String aspect = e.nextElement(); 102 error = loadIcons(aspect); 103 } 104 if (error) { 105 JmriJOptionPane.showMessageDialog(_editor.getTargetFrame(), 106 java.text.MessageFormat.format(Bundle.getMessage("SignalMastIconLoadError"), 107 new Object[]{getSignalMast().getDisplayName()}), 108 Bundle.getMessage("SignalMastIconLoadErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 109 } 110 //Add in specific appearances for dark and held 111 loadIcons("$dark"); 112 loadIcons("$held"); 113 } 114 115 private boolean loadIcons(String aspect) { 116 String s = getSignalMast().getAppearanceMap().getImageLink(aspect, useIconSet); 117 if (s.isEmpty()) { 118 if (aspect.startsWith("$")) { 119 log.debug("No icon found for specific appearance {}", aspect); 120 } else { 121 log.error("No icon found for appearance {}", aspect); 122 } 123 return true; 124 } else { 125 if (!s.contains("preference:")) { 126 s = s.substring(s.indexOf("resources")); 127 } 128 NamedIcon n; 129 try { 130 n = new NamedIcon(s, s); 131 } catch (java.lang.NullPointerException e) { 132 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("SignalMastIconLoadError2", new Object[]{aspect, s, getNameString()}), 133 Bundle.getMessage("SignalMastIconLoadErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 134 log.error("{} : Cannot load Icon", Bundle.getMessage("SignalMastIconLoadError2", aspect, s, getNameString())); 135 return true; 136 } 137 _iconMap.put(s, n); 138 if (_rotate != 0) { 139 n.rotate(_rotate, this); 140 } 141 if (_scale != 1.0) { 142 n.scale(_scale, this); 143 } 144 } 145 return false; 146 } 147 148 public NamedBeanHandle<SignalMast> getNamedSignalMast() { 149 return namedMast; 150 } 151 152 public SignalMast getSignalMast() { 153 if (namedMast == null) { 154 return null; 155 } 156 return namedMast.getBean(); 157 } 158 159 @Override 160 public jmri.NamedBean getNamedBean() { 161 return getSignalMast(); 162 } 163 164 /** 165 * Get current appearance of the mast 166 * 167 * @return An aspect from the SignalMast 168 */ 169 public String mastState() { 170 if (getSignalMast() == null) { 171 return "<empty>"; 172 } else { 173 return getSignalMast().getAspect(); 174 } 175 } 176 177 // update icon as state of turnout changes 178 @Override 179 public void propertyChange(java.beans.PropertyChangeEvent e) { 180 log.debug("property change: {} current state: {}", e.getPropertyName(), mastState()); 181 displayState(mastState()); 182 _editor.getTargetPanel().repaint(); 183 } 184 185 @Override 186 @Nonnull 187 public String getTypeString() { 188 return Bundle.getMessage("PositionableType_SignalMastIcon"); 189 } 190 191// public String getPName() { return namedMast.getName(); } 192 @Override 193 public String getNameString() { 194 String name; 195 if (getSignalMast() == null) { 196 name = Bundle.getMessage("NotConnected"); 197 } else { 198 name = getSignalMast().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME); 199 } 200 return name; 201 } 202 203 ButtonGroup litButtonGroup = null; 204 205 /** 206 * Pop-up just displays the name 207 */ 208 @Override 209 public boolean showPopUp(JPopupMenu popup) { 210 if (isEditable()) { 211 212 JMenu clickMenu = new JMenu(Bundle.getMessage("WhenClicked")); 213 ButtonGroup clickButtonGroup = new ButtonGroup(); 214 JRadioButtonMenuItem r; 215 r = new JRadioButtonMenuItem(Bundle.getMessage("ChangeAspect")); 216 r.addActionListener(e -> setClickMode(0)); 217 clickButtonGroup.add(r); 218 if (clickMode == 0) { 219 r.setSelected(true); 220 } else { 221 r.setSelected(false); 222 } 223 clickMenu.add(r); 224 225 r = new JRadioButtonMenuItem(Bundle.getMessage("AlternateLit")); 226 r.addActionListener(e -> setClickMode(1)); 227 clickButtonGroup.add(r); 228 if (clickMode == 1) { 229 r.setSelected(true); 230 } else { 231 r.setSelected(false); 232 } 233 clickMenu.add(r); 234 r = new JRadioButtonMenuItem(Bundle.getMessage("AlternateHeld")); 235 r.addActionListener(e -> setClickMode(2)); 236 clickButtonGroup.add(r); 237 if (clickMode == 2) { 238 r.setSelected(true); 239 } else { 240 r.setSelected(false); 241 } 242 clickMenu.add(r); 243 popup.add(clickMenu); 244 245 // add menu to select handling of lit parameter 246 JMenu litMenu = new JMenu(Bundle.getMessage("WhenNotLit")); 247 litButtonGroup = new ButtonGroup(); 248 r = new JRadioButtonMenuItem(Bundle.getMessage("ShowAppearance")); 249 r.setIconTextGap(10); 250 r.addActionListener(e -> { 251 setLitMode(false); 252 displayState(mastState()); 253 }); 254 litButtonGroup.add(r); 255 if (!litMode) { 256 r.setSelected(true); 257 } else { 258 r.setSelected(false); 259 } 260 litMenu.add(r); 261 r = new JRadioButtonMenuItem(Bundle.getMessage("ShowDarkIcon")); 262 r.setIconTextGap(10); 263 r.addActionListener(e -> { 264 setLitMode(true); 265 displayState(mastState()); 266 }); 267 litButtonGroup.add(r); 268 if (litMode) { 269 r.setSelected(true); 270 } else { 271 r.setSelected(false); 272 } 273 litMenu.add(r); 274 popup.add(litMenu); 275 276 if (namedMast != null) { 277 java.util.Enumeration<String> en = getSignalMast().getSignalSystem().getImageTypeList(); 278 if (en.hasMoreElements()) { 279 JMenu iconSetMenu = new JMenu(Bundle.getMessage("SignalMastIconSet")); 280 ButtonGroup iconTypeGroup = new ButtonGroup(); 281 setImageTypeList(iconTypeGroup, iconSetMenu, "default"); 282 while (en.hasMoreElements()) { 283 setImageTypeList(iconTypeGroup, iconSetMenu, en.nextElement()); 284 } 285 popup.add(iconSetMenu); 286 } 287 popup.add(new jmri.jmrit.signalling.SignallingSourceAction(Bundle.getMessage("SignalMastLogic"), getSignalMast())); 288 JMenu aspect = new JMenu(Bundle.getMessage("ChangeAspect")); 289 final java.util.Vector<String> aspects = getSignalMast().getValidAspects(); 290 for (int i = 0; i < aspects.size(); i++) { 291 final int index = i; 292 aspect.add(new AbstractAction(aspects.elementAt(index)) { 293 @Override 294 public void actionPerformed(ActionEvent e) { 295 getSignalMast().setAspect(aspects.elementAt(index)); 296 } 297 }); 298 } 299 popup.add(aspect); 300 } 301 addTransitPopup(popup); 302 } else { 303 if (!isControlling()) { 304 log.debug("The signal mast icon is disabled, skip the aspect list popup"); 305 return false; 306 } 307 final java.util.Vector<String> aspects = getSignalMast().getValidAspects(); 308 for (int i = 0; i < aspects.size(); i++) { 309 final int index = i; 310 popup.add(new AbstractAction(aspects.elementAt(index)) { 311 @Override 312 public void actionPerformed(ActionEvent e) { 313 getSignalMast().setAspect(aspects.elementAt(index)); 314 } 315 }); 316 } 317 } 318 return true; 319 } 320 321 private void addTransitPopup(JPopupMenu popup) { 322 if ((InstanceManager.getDefault(jmri.SectionManager.class).getNamedBeanSet().size()) > 0 323 && jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 324 325 if (tct == null) { 326 tct = new jmri.jmrit.display.layoutEditor.TransitCreationTool(); 327 } 328 popup.addSeparator(); 329 String addString = Bundle.getMessage("MenuTransitCreate"); 330 if (tct.isToolInUse()) { 331 addString = Bundle.getMessage("MenuTransitAddTo"); 332 } 333 popup.add(new AbstractAction(addString) { 334 @Override 335 public void actionPerformed(ActionEvent e) { 336 try { 337 tct.addNamedBean(getSignalMast()); 338 } catch (jmri.JmriException ex) { 339 JmriJOptionPane.showMessageDialog(null, ex.getMessage(), 340 Bundle.getMessage("TransitErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 341 } 342 } 343 }); 344 if (tct.isToolInUse()) { 345 popup.add(new AbstractAction(Bundle.getMessage("MenuTransitAddComplete")) { 346 @Override 347 public void actionPerformed(ActionEvent e) { 348 Transit created; 349 try { 350 tct.addNamedBean(getSignalMast()); 351 created = tct.createTransit(); 352 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TransitCreatedMessage", created.getDisplayName()), 353 Bundle.getMessage("TransitCreatedTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 354 } catch (jmri.JmriException ex) { 355 JmriJOptionPane.showMessageDialog(null, ex.getMessage(), 356 Bundle.getMessage("TransitErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 357 } 358 } 359 }); 360 popup.add(new AbstractAction(Bundle.getMessage("MenuTransitCancel")) { 361 @Override 362 public void actionPerformed(ActionEvent e) { 363 tct.cancelTransitCreate(); 364 } 365 }); 366 } 367 popup.addSeparator(); 368 } 369 } 370 371 static volatile jmri.jmrit.display.layoutEditor.TransitCreationTool tct; 372 373 private void setImageTypeList(ButtonGroup iconTypeGroup, JMenu iconSetMenu, final String item) { 374 JRadioButtonMenuItem im; 375 im = new JRadioButtonMenuItem(item); 376 im.addActionListener(e -> useIconSet(item)); 377 iconTypeGroup.add(im); 378 if (useIconSet.equals(item)) { 379 im.setSelected(true); 380 } else { 381 im.setSelected(false); 382 } 383 iconSetMenu.add(im); 384 385 } 386 387 @Override 388 public boolean setRotateOrthogonalMenu(JPopupMenu popup) { 389 return false; 390 } 391 392 SignalMastItemPanel _itemPanel; 393 394 @Override 395 public boolean setEditItemMenu(JPopupMenu popup) { 396 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSignalMast")); 397 popup.add(new AbstractAction(txt) { 398 @Override 399 public void actionPerformed(ActionEvent e) { 400 editItem(); 401 } 402 }); 403 return true; 404 } 405 406 protected void editItem() { 407 _paletteFrame = makePaletteFrame(java.text.MessageFormat.format(Bundle.getMessage("EditItem"), 408 Bundle.getMessage("BeanNameSignalMast"))); 409 _itemPanel = new SignalMastItemPanel(_paletteFrame, "SignalMast", getFamily(), 410 PickListModel.signalMastPickModelInstance()); 411 ActionListener updateAction = a -> updateItem(); 412 // _iconMap keys with local names - Let SignalHeadItemPanel figure this out 413 _itemPanel.init(updateAction, _iconMap); 414 _itemPanel.setSelection(getSignalMast()); 415 initPaletteFrame(_paletteFrame, _itemPanel); 416 } 417 418 void updateItem() { 419 setSignalMast(_itemPanel.getTableSelection().getSystemName()); 420 setFamily(_itemPanel.getFamilyName()); 421 finishItemUpdate(_paletteFrame, _itemPanel); 422 } 423 424 /** 425 * Change the SignalMast aspect when the icon is clicked. 426 * 427 */ 428 @Override 429 public void doMouseClicked(JmriMouseEvent e) { 430 if (!_editor.getFlag(Editor.OPTION_CONTROLS, isControlling())) { 431 return; 432 } 433 performMouseClicked(e); 434 } 435 436 /** 437 * Handle mouse clicks when no modifier keys are pressed. Mouse clicks with 438 * modifier keys pressed can be processed by the containing component. 439 * 440 * @param e the mouse click event 441 */ 442 public void performMouseClicked(JmriMouseEvent e) { 443 if (e.isMetaDown() || e.isAltDown()) { 444 return; 445 } 446 if (getSignalMast() == null) { 447 log.error("No turnout connection, can't process click"); 448 return; 449 } 450 switch (clickMode) { 451 case 0: 452 java.util.Vector<String> aspects = getSignalMast().getValidAspects(); 453 int idx = aspects.indexOf(getSignalMast().getAspect()) + 1; 454 if (idx >= aspects.size()) { 455 idx = 0; 456 } 457 getSignalMast().setAspect(aspects.elementAt(idx)); 458 return; 459 case 1: 460 getSignalMast().setLit(!getSignalMast().getLit()); 461 return; 462 case 2: 463 getSignalMast().setHeld(!getSignalMast().getHeld()); 464 return; 465 default: 466 log.error("Click in mode {}", clickMode); 467 } 468 } 469 470 String useIconSet = "default"; 471 472 public void useIconSet(String icon) { 473 if (icon == null) { 474 icon = "default"; 475 } 476 if (useIconSet.equals(icon)) { 477 return; 478 } 479 //clear the old icon map out. 480 _iconMap = null; 481 useIconSet = icon; 482 getIcons(); 483 displayState(mastState()); 484 _editor.getTargetPanel().repaint(); 485 } 486 487 public String useIconSet() { 488 return useIconSet; 489 } 490 491 /** 492 * Set display of ClipBoard copied or duplicated mast 493 */ 494 @Override 495 public void displayState(int s) { 496 displayState(mastState()); 497 } 498 499 /** 500 * Drive the current state of the display from the state of the underlying 501 * SignalMast object. 502 * 503 * @param state the state to display 504 */ 505 public void displayState(String state) { 506 updateSize(); 507 if (log.isDebugEnabled()) { // Avoid signal lookup unless needed 508 if (getSignalMast() == null) { 509 log.debug("Display state {}, disconnected", state); 510 } else { 511 log.debug("Display state {} for {}", state, getSignalMast().getSystemName()); 512 } 513 } 514 if (isText()) { 515 if (getSignalMast().getHeld()) { 516 if (isText()) { 517 super.setText(Bundle.getMessage("Held")); 518 } 519 return; 520 } else if (getLitMode() && !getSignalMast().getLit()) { 521 super.setText(Bundle.getMessage("Dark")); 522 return; 523 } 524 super.setText(state); 525 } 526 if (isIcon()) { 527 if ((state != null) && (getSignalMast() != null)) { 528 String s = getSignalMast().getAppearanceMap().getImageLink(state, useIconSet); 529 if ((getSignalMast().getHeld()) && (getSignalMast().getAppearanceMap().getSpecificAppearance(jmri.SignalAppearanceMap.HELD) != null)) { 530 s = getSignalMast().getAppearanceMap().getImageLink("$held", useIconSet); 531 } else if (getLitMode() && !getSignalMast().getLit() && (getSignalMast().getAppearanceMap().getImageLink("$dark", useIconSet) != null)) { 532 s = getSignalMast().getAppearanceMap().getImageLink("$dark", useIconSet); 533 } 534 if (s.equals("")) { 535 /*We have no appearance to set, therefore we will exit at this point. 536 This can be considered normal if we are requesting an appearance 537 that is not support or configured, such as dark or held */ 538 return; 539 } 540 if (!s.contains("preference:")) { 541 s = s.substring(s.indexOf("resources")); 542 } 543 544 // tiny global cache, due to number of icons 545 if (_iconMap == null) { 546 getIcons(); 547 } 548 NamedIcon n = _iconMap.get(s); 549 super.setIcon(n); 550 updateSize(); 551 setSize(n.getIconWidth(), n.getIconHeight()); 552 } 553 } else { 554 super.setIcon(null); 555 } 556 return; 557 } 558 559 @Override 560 public boolean setEditIconMenu(JPopupMenu popup) { 561 return false; 562 } 563 564 @Override 565 protected void rotateOrthogonal() { 566 super.rotateOrthogonal(); 567 // bug fix, must repaint icons that have same width and height 568 displayState(mastState()); 569 repaint(); 570 } 571 572 @Override 573 public void rotate(int deg) { 574 super.rotate(deg); 575 if (getSignalMast() != null) { 576 displayState(mastState()); 577 } 578 } 579 580 @Override 581 public void setScale(double s) { 582 super.setScale(s); 583 if (getSignalMast() != null) { 584 displayState(mastState()); 585 } 586 } 587 588 /** 589 * What to do on click? 0 means sequence through aspects; 1 means alternate 590 * the "lit" aspect; 2 means alternate the 591 * {@link jmri.SignalAppearanceMap#HELD} aspect. 592 */ 593 protected int clickMode = 0; 594 595 public void setClickMode(int mode) { 596 clickMode = mode; 597 } 598 599 public int getClickMode() { 600 return clickMode; 601 } 602 603 /** 604 * How to handle lit vs not lit? 605 * <p> 606 * False means ignore (always show R/Y/G/etc appearance on screen); True 607 * means show {@link jmri.SignalAppearanceMap#DARK} if lit is set false. 608 */ 609 protected boolean litMode = false; 610 611 public void setLitMode(boolean mode) { 612 litMode = mode; 613 } 614 615 public boolean getLitMode() { 616 return litMode; 617 } 618 619 @Override 620 public void dispose() { 621 if (namedMast != null) { 622 getSignalMast().removePropertyChangeListener(this); 623 } 624 super.dispose(); 625 } 626 627 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastIcon.class); 628 629}