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 final java.util.Vector<String> aspects = getSignalMast().getValidAspects(); 304 for (int i = 0; i < aspects.size(); i++) { 305 final int index = i; 306 popup.add(new AbstractAction(aspects.elementAt(index)) { 307 @Override 308 public void actionPerformed(ActionEvent e) { 309 getSignalMast().setAspect(aspects.elementAt(index)); 310 } 311 }); 312 } 313 } 314 return true; 315 } 316 317 private void addTransitPopup(JPopupMenu popup) { 318 if ((InstanceManager.getDefault(jmri.SectionManager.class).getNamedBeanSet().size()) > 0 319 && jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).isAdvancedRoutingEnabled()) { 320 321 if (tct == null) { 322 tct = new jmri.jmrit.display.layoutEditor.TransitCreationTool(); 323 } 324 popup.addSeparator(); 325 String addString = Bundle.getMessage("MenuTransitCreate"); 326 if (tct.isToolInUse()) { 327 addString = Bundle.getMessage("MenuTransitAddTo"); 328 } 329 popup.add(new AbstractAction(addString) { 330 @Override 331 public void actionPerformed(ActionEvent e) { 332 try { 333 tct.addNamedBean(getSignalMast()); 334 } catch (jmri.JmriException ex) { 335 JmriJOptionPane.showMessageDialog(null, ex.getMessage(), 336 Bundle.getMessage("TransitErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 337 } 338 } 339 }); 340 if (tct.isToolInUse()) { 341 popup.add(new AbstractAction(Bundle.getMessage("MenuTransitAddComplete")) { 342 @Override 343 public void actionPerformed(ActionEvent e) { 344 Transit created; 345 try { 346 tct.addNamedBean(getSignalMast()); 347 created = tct.createTransit(); 348 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TransitCreatedMessage", created.getDisplayName()), 349 Bundle.getMessage("TransitCreatedTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 350 } catch (jmri.JmriException ex) { 351 JmriJOptionPane.showMessageDialog(null, ex.getMessage(), 352 Bundle.getMessage("TransitErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 353 } 354 } 355 }); 356 popup.add(new AbstractAction(Bundle.getMessage("MenuTransitCancel")) { 357 @Override 358 public void actionPerformed(ActionEvent e) { 359 tct.cancelTransitCreate(); 360 } 361 }); 362 } 363 popup.addSeparator(); 364 } 365 } 366 367 static volatile jmri.jmrit.display.layoutEditor.TransitCreationTool tct; 368 369 private void setImageTypeList(ButtonGroup iconTypeGroup, JMenu iconSetMenu, final String item) { 370 JRadioButtonMenuItem im; 371 im = new JRadioButtonMenuItem(item); 372 im.addActionListener(e -> useIconSet(item)); 373 iconTypeGroup.add(im); 374 if (useIconSet.equals(item)) { 375 im.setSelected(true); 376 } else { 377 im.setSelected(false); 378 } 379 iconSetMenu.add(im); 380 381 } 382 383 @Override 384 public boolean setRotateOrthogonalMenu(JPopupMenu popup) { 385 return false; 386 } 387 388 SignalMastItemPanel _itemPanel; 389 390 @Override 391 public boolean setEditItemMenu(JPopupMenu popup) { 392 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameSignalMast")); 393 popup.add(new AbstractAction(txt) { 394 @Override 395 public void actionPerformed(ActionEvent e) { 396 editItem(); 397 } 398 }); 399 return true; 400 } 401 402 protected void editItem() { 403 _paletteFrame = makePaletteFrame(java.text.MessageFormat.format(Bundle.getMessage("EditItem"), 404 Bundle.getMessage("BeanNameSignalMast"))); 405 _itemPanel = new SignalMastItemPanel(_paletteFrame, "SignalMast", getFamily(), 406 PickListModel.signalMastPickModelInstance()); 407 ActionListener updateAction = a -> updateItem(); 408 // _iconMap keys with local names - Let SignalHeadItemPanel figure this out 409 _itemPanel.init(updateAction, _iconMap); 410 _itemPanel.setSelection(getSignalMast()); 411 initPaletteFrame(_paletteFrame, _itemPanel); 412 } 413 414 void updateItem() { 415 setSignalMast(_itemPanel.getTableSelection().getSystemName()); 416 setFamily(_itemPanel.getFamilyName()); 417 finishItemUpdate(_paletteFrame, _itemPanel); 418 } 419 420 /** 421 * Change the SignalMast aspect when the icon is clicked. 422 * 423 */ 424 @Override 425 public void doMouseClicked(JmriMouseEvent e) { 426 if (!_editor.getFlag(Editor.OPTION_CONTROLS, isControlling())) { 427 return; 428 } 429 performMouseClicked(e); 430 } 431 432 /** 433 * Handle mouse clicks when no modifier keys are pressed. Mouse clicks with 434 * modifier keys pressed can be processed by the containing component. 435 * 436 * @param e the mouse click event 437 */ 438 public void performMouseClicked(JmriMouseEvent e) { 439 if (e.isMetaDown() || e.isAltDown()) { 440 return; 441 } 442 if (getSignalMast() == null) { 443 log.error("No turnout connection, can't process click"); 444 return; 445 } 446 switch (clickMode) { 447 case 0: 448 java.util.Vector<String> aspects = getSignalMast().getValidAspects(); 449 int idx = aspects.indexOf(getSignalMast().getAspect()) + 1; 450 if (idx >= aspects.size()) { 451 idx = 0; 452 } 453 getSignalMast().setAspect(aspects.elementAt(idx)); 454 return; 455 case 1: 456 getSignalMast().setLit(!getSignalMast().getLit()); 457 return; 458 case 2: 459 getSignalMast().setHeld(!getSignalMast().getHeld()); 460 return; 461 default: 462 log.error("Click in mode {}", clickMode); 463 } 464 } 465 466 String useIconSet = "default"; 467 468 public void useIconSet(String icon) { 469 if (icon == null) { 470 icon = "default"; 471 } 472 if (useIconSet.equals(icon)) { 473 return; 474 } 475 //clear the old icon map out. 476 _iconMap = null; 477 useIconSet = icon; 478 getIcons(); 479 displayState(mastState()); 480 _editor.getTargetPanel().repaint(); 481 } 482 483 public String useIconSet() { 484 return useIconSet; 485 } 486 487 /** 488 * Set display of ClipBoard copied or duplicated mast 489 */ 490 @Override 491 public void displayState(int s) { 492 displayState(mastState()); 493 } 494 495 /** 496 * Drive the current state of the display from the state of the underlying 497 * SignalMast object. 498 * 499 * @param state the state to display 500 */ 501 public void displayState(String state) { 502 updateSize(); 503 if (log.isDebugEnabled()) { // Avoid signal lookup unless needed 504 if (getSignalMast() == null) { 505 log.debug("Display state {}, disconnected", state); 506 } else { 507 log.debug("Display state {} for {}", state, getSignalMast().getSystemName()); 508 } 509 } 510 if (isText()) { 511 if (getSignalMast().getHeld()) { 512 if (isText()) { 513 super.setText(Bundle.getMessage("Held")); 514 } 515 return; 516 } else if (getLitMode() && !getSignalMast().getLit()) { 517 super.setText(Bundle.getMessage("Dark")); 518 return; 519 } 520 super.setText(state); 521 } 522 if (isIcon()) { 523 if ((state != null) && (getSignalMast() != null)) { 524 String s = getSignalMast().getAppearanceMap().getImageLink(state, useIconSet); 525 if ((getSignalMast().getHeld()) && (getSignalMast().getAppearanceMap().getSpecificAppearance(jmri.SignalAppearanceMap.HELD) != null)) { 526 s = getSignalMast().getAppearanceMap().getImageLink("$held", useIconSet); 527 } else if (getLitMode() && !getSignalMast().getLit() && (getSignalMast().getAppearanceMap().getImageLink("$dark", useIconSet) != null)) { 528 s = getSignalMast().getAppearanceMap().getImageLink("$dark", useIconSet); 529 } 530 if (s.equals("")) { 531 /*We have no appearance to set, therefore we will exit at this point. 532 This can be considered normal if we are requesting an appearance 533 that is not support or configured, such as dark or held */ 534 return; 535 } 536 if (!s.contains("preference:")) { 537 s = s.substring(s.indexOf("resources")); 538 } 539 540 // tiny global cache, due to number of icons 541 if (_iconMap == null) { 542 getIcons(); 543 } 544 NamedIcon n = _iconMap.get(s); 545 super.setIcon(n); 546 updateSize(); 547 setSize(n.getIconWidth(), n.getIconHeight()); 548 } 549 } else { 550 super.setIcon(null); 551 } 552 return; 553 } 554 555 @Override 556 public boolean setEditIconMenu(JPopupMenu popup) { 557 return false; 558 } 559 560 @Override 561 protected void rotateOrthogonal() { 562 super.rotateOrthogonal(); 563 // bug fix, must repaint icons that have same width and height 564 displayState(mastState()); 565 repaint(); 566 } 567 568 @Override 569 public void rotate(int deg) { 570 super.rotate(deg); 571 if (getSignalMast() != null) { 572 displayState(mastState()); 573 } 574 } 575 576 @Override 577 public void setScale(double s) { 578 super.setScale(s); 579 if (getSignalMast() != null) { 580 displayState(mastState()); 581 } 582 } 583 584 /** 585 * What to do on click? 0 means sequence through aspects; 1 means alternate 586 * the "lit" aspect; 2 means alternate the 587 * {@link jmri.SignalAppearanceMap#HELD} aspect. 588 */ 589 protected int clickMode = 0; 590 591 public void setClickMode(int mode) { 592 clickMode = mode; 593 } 594 595 public int getClickMode() { 596 return clickMode; 597 } 598 599 /** 600 * How to handle lit vs not lit? 601 * <p> 602 * False means ignore (always show R/Y/G/etc appearance on screen); True 603 * means show {@link jmri.SignalAppearanceMap#DARK} if lit is set false. 604 */ 605 protected boolean litMode = false; 606 607 public void setLitMode(boolean mode) { 608 litMode = mode; 609 } 610 611 public boolean getLitMode() { 612 return litMode; 613 } 614 615 @Override 616 public void dispose() { 617 if (namedMast != null) { 618 getSignalMast().removePropertyChangeListener(this); 619 } 620 super.dispose(); 621 } 622 623 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastIcon.class); 624 625}