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