001package jmri.jmrit.display; 002 003import java.awt.Color; 004import java.awt.Container; 005import java.awt.Dimension; 006import java.awt.Graphics; 007import java.awt.Graphics2D; 008import java.awt.RenderingHints; 009import java.awt.event.ActionEvent; 010import java.awt.event.ActionListener; 011import java.awt.geom.AffineTransform; 012import java.awt.geom.Point2D; 013import java.awt.image.BufferedImage; 014import java.beans.PropertyVetoException; 015import java.util.Objects; 016import java.util.HashSet; 017import java.util.Set; 018 019import javax.annotation.CheckForNull; 020import javax.annotation.Nonnull; 021import javax.swing.AbstractAction; 022import javax.swing.JCheckBoxMenuItem; 023import javax.swing.JComponent; 024import javax.swing.JFrame; 025import javax.swing.JLabel; 026import javax.swing.JPopupMenu; 027import javax.swing.JScrollPane; 028 029import jmri.InstanceManager; 030import jmri.jmrit.catalog.NamedIcon; 031import jmri.jmrit.display.palette.IconItemPanel; 032import jmri.jmrit.display.palette.ItemPanel; 033import jmri.jmrit.display.palette.TextItemPanel; 034import jmri.jmrit.logixng.*; 035import jmri.jmrit.logixng.tools.swing.DeleteBean; 036import jmri.util.MathUtil; 037import jmri.util.SystemType; 038import jmri.util.ThreadingUtil; 039import jmri.util.swing.JmriMouseEvent; 040 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044/** 045 * PositionableLabel is a JLabel that can be dragged around the inside of the 046 * enclosing Container using a right-drag. 047 * <p> 048 * The positionable parameter is a global, set from outside. The 'fixed' 049 * parameter is local, set from the popup here. 050 * 051 * <a href="doc-files/Heirarchy.png"><img src="doc-files/Heirarchy.png" alt="UML class diagram for package" height="33%" width="33%"></a> 052 * @author Bob Jacobsen Copyright (c) 2002 053 */ 054public class PositionableLabel extends JLabel implements Positionable { 055 056 protected Editor _editor; 057 058 private String _id; // user's Id or null if no Id 059 private final Set<String> _classes = new HashSet<>(); // user's classes 060 061 protected boolean _icon = false; 062 protected boolean _text = false; 063 protected boolean _control = false; 064 protected NamedIcon _namedIcon; 065 066 protected ToolTip _tooltip; 067 protected boolean _showTooltip = true; 068 protected boolean _editable = true; 069 protected boolean _positionable = true; 070 protected boolean _viewCoordinates = true; 071 protected boolean _controlling = true; 072 protected boolean _hidden = false; 073 protected boolean _emptyHidden = false; 074 protected boolean _valueEditDisabled = false; 075 protected int _displayLevel; 076 077 protected String _unRotatedText; 078 protected boolean _rotateText = false; 079 private int _degrees; 080 081 private LogixNG _logixNG; 082 private String _logixNG_SystemName; 083 084 /** 085 * Create a new Positionable Label. 086 * @param s label string. 087 * @param editor where this label is displayed. 088 */ 089 public PositionableLabel(String s, @Nonnull Editor editor) { 090 super(s); 091 _editor = editor; 092 _text = true; 093 _unRotatedText = s; 094 log.debug("PositionableLabel ctor (text) {}", s); 095 setHorizontalAlignment(JLabel.CENTER); 096 setVerticalAlignment(JLabel.CENTER); 097 setPopupUtility(new PositionablePopupUtil(this, this)); 098 } 099 100 public PositionableLabel(@CheckForNull NamedIcon s, @Nonnull Editor editor) { 101 super(s); 102 _editor = editor; 103 _icon = true; 104 _namedIcon = s; 105 log.debug("PositionableLabel ctor (icon) {}", s != null ? s.getName() : null); 106 setPopupUtility(new PositionablePopupUtil(this, this)); 107 } 108 109 /** {@inheritDoc} */ 110 @Override 111 public void setId(String id) throws Positionable.DuplicateIdException { 112 if (Objects.equals(this._id, id)) return; 113 _editor.positionalIdChange(this, id); 114 this._id = id; 115 } 116 117 /** {@inheritDoc} */ 118 @Override 119 public String getId() { 120 return _id; 121 } 122 123 /** {@inheritDoc} */ 124 @Override 125 public void addClass(String className) { 126 _editor.positionalAddClass(this, className); 127 _classes.add(className); 128 } 129 130 /** {@inheritDoc} */ 131 @Override 132 public void removeClass(String className) { 133 _editor.positionalRemoveClass(this, className); 134 _classes.remove(className); 135 } 136 137 /** {@inheritDoc} */ 138 @Override 139 public void removeAllClasses() { 140 for (String className : _classes) { 141 _editor.positionalRemoveClass(this, className); 142 } 143 _classes.clear(); 144 } 145 146 /** {@inheritDoc} */ 147 @Override 148 public Set<String> getClasses() { 149 return java.util.Collections.unmodifiableSet(_classes); 150 } 151 152 public final boolean isIcon() { 153 return _icon; 154 } 155 156 public final boolean isText() { 157 return _text; 158 } 159 160 public final boolean isControl() { 161 return _control; 162 } 163 164 @Override 165 public @Nonnull Editor getEditor() { 166 return _editor; 167 } 168 169 @Override 170 public void setEditor(@Nonnull Editor ed) { 171 _editor = ed; 172 } 173 174 // *************** Positionable methods ********************* 175 @Override 176 public void setPositionable(boolean enabled) { 177 _positionable = enabled; 178 } 179 180 @Override 181 public final boolean isPositionable() { 182 return _positionable; 183 } 184 185 @Override 186 public void setEditable(boolean enabled) { 187 _editable = enabled; 188 showHidden(); 189 } 190 191 @Override 192 public boolean isEditable() { 193 return _editable; 194 } 195 196 @Override 197 public void setViewCoordinates(boolean enabled) { 198 _viewCoordinates = enabled; 199 } 200 201 @Override 202 public boolean getViewCoordinates() { 203 return _viewCoordinates; 204 } 205 206 @Override 207 public void setControlling(boolean enabled) { 208 _controlling = enabled; 209 } 210 211 @Override 212 public boolean isControlling() { 213 return _controlling; 214 } 215 216 @Override 217 public void setHidden(boolean hide) { 218 if (_hidden != hide) { 219 _hidden = hide; 220 showHidden(); 221 } 222 } 223 224 @Override 225 public boolean isHidden() { 226 return _hidden; 227 } 228 229 @Override 230 public void showHidden() { 231 if (!_hidden || _editor.isEditable()) { 232 setVisible(true); 233 } else { 234 setVisible(false); 235 } 236 } 237 238 @Override 239 public void setEmptyHidden(boolean hide) { 240 _emptyHidden = hide; 241 } 242 243 @Override 244 public boolean isEmptyHidden() { 245 return _emptyHidden; 246 } 247 248 @Override 249 public void setValueEditDisabled(boolean isDisabled) { 250 _valueEditDisabled = isDisabled; 251 } 252 253 @Override 254 public boolean isValueEditDisabled() { 255 return _valueEditDisabled; 256 } 257 258 /** 259 * Delayed setDisplayLevel for DnD. 260 * 261 * @param l the level to set 262 */ 263 public void setLevel(int l) { 264 _displayLevel = l; 265 } 266 267 @Override 268 public void setDisplayLevel(int l) { 269 int oldDisplayLevel = _displayLevel; 270 _displayLevel = l; 271 if (oldDisplayLevel != l) { 272 log.debug("Changing label display level from {} to {}", oldDisplayLevel, _displayLevel); 273 _editor.displayLevelChange(this); 274 } 275 } 276 277 @Override 278 public int getDisplayLevel() { 279 return _displayLevel; 280 } 281 282 @Override 283 public void setShowToolTip(boolean set) { 284 _showTooltip = set; 285 } 286 287 @Override 288 public boolean showToolTip() { 289 return _showTooltip; 290 } 291 292 @Override 293 public void setToolTip(ToolTip tip) { 294 _tooltip = tip; 295 } 296 297 @Override 298 public ToolTip getToolTip() { 299 return _tooltip; 300 } 301 302 @Override 303 @Nonnull 304 public String getTypeString() { 305 return Bundle.getMessage("PositionableType_PositionableLabel"); 306 } 307 308 @Override 309 @Nonnull 310 public String getNameString() { 311 if (_icon && _displayLevel > Editor.BKG) { 312 return "Icon"; 313 } else if (_text) { 314 return "Text Label"; 315 } else { 316 return "Background"; 317 } 318 } 319 320 /** 321 * When text is rotated or in an icon mode, the return of getText() may be 322 * null or some other value 323 * 324 * @return original defining text set by user 325 */ 326 public String getUnRotatedText() { 327 return _unRotatedText; 328 } 329 330 public void setUnRotatedText(String s) { 331 _unRotatedText = s; 332 } 333 334 @Override 335 @Nonnull 336 public Positionable deepClone() { 337 PositionableLabel pos; 338 if (_icon) { 339 NamedIcon icon = new NamedIcon((NamedIcon) getIcon()); 340 pos = new PositionableLabel(icon, _editor); 341 } else { 342 pos = new PositionableLabel(getText(), _editor); 343 } 344 return finishClone(pos); 345 } 346 347 protected @Nonnull Positionable finishClone(@Nonnull PositionableLabel pos) { 348 pos._text = _text; 349 pos._icon = _icon; 350 pos._control = _control; 351// pos._rotateText = _rotateText; 352 pos._unRotatedText = _unRotatedText; 353 pos.setLocation(getX(), getY()); 354 pos._displayLevel = _displayLevel; 355 pos._controlling = _controlling; 356 pos._hidden = _hidden; 357 pos._positionable = _positionable; 358 pos._showTooltip = _showTooltip; 359 pos.setToolTip(getToolTip()); 360 pos._editable = _editable; 361 if (getPopupUtility() == null) { 362 pos.setPopupUtility(null); 363 } else { 364 pos.setPopupUtility(getPopupUtility().clone(pos, pos.getTextComponent())); 365 } 366 pos.setOpaque(isOpaque()); 367 if (_namedIcon != null) { 368 pos._namedIcon = cloneIcon(_namedIcon, pos); 369 pos.setIcon(pos._namedIcon); 370 pos.rotate(_degrees); //this will change text in icon with a new _namedIcon. 371 } 372 pos.updateSize(); 373 return pos; 374 } 375 376 @Override 377 public @Nonnull JComponent getTextComponent() { 378 return this; 379 } 380 381 public static @Nonnull NamedIcon cloneIcon(NamedIcon icon, PositionableLabel pos) { 382 if (icon.getURL() != null) { 383 return new NamedIcon(icon, pos); 384 } else { 385 NamedIcon clone = new NamedIcon(icon.getImage()); 386 clone.scale(icon.getScale(), pos); 387 clone.rotate(icon.getDegrees(), pos); 388 return clone; 389 } 390 } 391 392 // overide where used - e.g. momentary 393 @Override 394 public void doMousePressed(JmriMouseEvent event) { 395 } 396 397 @Override 398 public void doMouseReleased(JmriMouseEvent event) { 399 } 400 401 @Override 402 public void doMouseClicked(JmriMouseEvent event) { 403 } 404 405 @Override 406 public void doMouseDragged(JmriMouseEvent event) { 407 } 408 409 @Override 410 public void doMouseMoved(JmriMouseEvent event) { 411 } 412 413 @Override 414 public void doMouseEntered(JmriMouseEvent event) { 415 } 416 417 @Override 418 public void doMouseExited(JmriMouseEvent event) { 419 } 420 421 @Override 422 public boolean storeItem() { 423 return true; 424 } 425 426 @Override 427 public boolean doViemMenu() { 428 return true; 429 } 430 431 /* 432 * ************** end Positionable methods ********************* 433 */ 434 /** 435 * ************************************************************* 436 */ 437 PositionablePopupUtil _popupUtil; 438 439 @Override 440 public void setPopupUtility(PositionablePopupUtil tu) { 441 _popupUtil = tu; 442 } 443 444 @Override 445 public PositionablePopupUtil getPopupUtility() { 446 return _popupUtil; 447 } 448 449 /** 450 * Update the AWT and Swing size information due to change in internal 451 * state, e.g. if one or more of the icons that might be displayed is 452 * changed 453 */ 454 @Override 455 public void updateSize() { 456 int width = maxWidth(); 457 int height = maxHeight(); 458 log.trace("updateSize() w= {}, h= {} _namedIcon= {}", width, height, _namedIcon); 459 460 setSize(width, height); 461 if (_namedIcon != null && _text) { 462 //we have a combined icon/text therefore the icon is central to the text. 463 setHorizontalTextPosition(CENTER); 464 } 465 } 466 467 @Override 468 public int maxWidth() { 469 if (_rotateText && _namedIcon != null) { 470 return _namedIcon.getIconWidth(); 471 } 472 if (_popupUtil == null) { 473 return maxWidthTrue(); 474 } 475 476 switch (_popupUtil.getOrientation()) { 477 case PositionablePopupUtil.VERTICAL_DOWN: 478 case PositionablePopupUtil.VERTICAL_UP: 479 return maxHeightTrue(); 480 default: 481 return maxWidthTrue(); 482 } 483 } 484 485 @Override 486 public int maxHeight() { 487 if (_rotateText && _namedIcon != null) { 488 return _namedIcon.getIconHeight(); 489 } 490 if (_popupUtil == null) { 491 return maxHeightTrue(); 492 } 493 switch (_popupUtil.getOrientation()) { 494 case PositionablePopupUtil.VERTICAL_DOWN: 495 case PositionablePopupUtil.VERTICAL_UP: 496 return maxWidthTrue(); 497 default: 498 return maxHeightTrue(); 499 } 500 } 501 502 public int maxWidthTrue() { 503 int result = 0; 504 if (_popupUtil != null && _popupUtil.getFixedWidth() != 0) { 505 result = _popupUtil.getFixedWidth(); 506 result += _popupUtil.getBorderSize() * 2; 507 if (result < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 508 _popupUtil.setFixedWidth(PositionablePopupUtil.MIN_SIZE); 509 result = PositionablePopupUtil.MIN_SIZE; 510 } 511 } else { 512 if (_text && getText() != null) { 513 if (getText().trim().length() == 0) { 514 // show width of 1 blank character 515 if (getFont() != null) { 516 result = getFontMetrics(getFont()).stringWidth("0"); 517 } 518 } else { 519 result = getFontMetrics(getFont()).stringWidth(getText()); 520 } 521 } 522 if (_icon && _namedIcon != null) { 523 result = Math.max(_namedIcon.getIconWidth(), result); 524 } 525 if (_text && _popupUtil != null) { 526 result += _popupUtil.getMargin() * 2; 527 result += _popupUtil.getBorderSize() * 2; 528 } 529 if (result < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 530 result = PositionablePopupUtil.MIN_SIZE; 531 } 532 } 533 if (log.isTraceEnabled()) { // avoid AWT size computation 534 log.trace("maxWidth= {} preferred width= {}", result, getPreferredSize().width); 535 } 536 return result; 537 } 538 539 public int maxHeightTrue() { 540 int result = 0; 541 if (_popupUtil != null && _popupUtil.getFixedHeight() != 0) { 542 result = _popupUtil.getFixedHeight(); 543 result += _popupUtil.getBorderSize() * 2; 544 if (result < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 545 _popupUtil.setFixedHeight(PositionablePopupUtil.MIN_SIZE); 546 } 547 } else { 548 //if(_text) { 549 if (_text && getText() != null && getFont() != null) { 550 result = getFontMetrics(getFont()).getHeight(); 551 } 552 if (_icon && _namedIcon != null) { 553 result = Math.max(_namedIcon.getIconHeight(), result); 554 } 555 if (_text && _popupUtil != null) { 556 result += _popupUtil.getMargin() * 2; 557 result += _popupUtil.getBorderSize() * 2; 558 } 559 if (result < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 560 result = PositionablePopupUtil.MIN_SIZE; 561 } 562 } 563 if (log.isTraceEnabled()) { // avoid AWT size computation 564 log.trace("maxHeight= {} preferred height= {}", result, getPreferredSize().height); 565 } 566 return result; 567 } 568 569 public boolean isBackground() { 570 return (_displayLevel == Editor.BKG); 571 } 572 573 public boolean isRotated() { 574 return _rotateText; 575 } 576 577 public void updateIcon(NamedIcon s) { 578 ThreadingUtil.runOnLayoutEventually(() -> { 579 _namedIcon = s; 580 super.setIcon(_namedIcon); 581 updateSize(); 582 repaint(); 583 }); 584 } 585 586 /* 587 * ***** Methods to add menu items to popup ******* 588 */ 589 590 /** 591 * Call to a Positionable that has unique requirements - e.g. 592 * RpsPositionIcon, SecurityElementIcon 593 */ 594 @Override 595 public boolean showPopUp(JPopupMenu popup) { 596 return false; 597 } 598 599 /** 600 * Rotate othogonally return true if popup is set 601 */ 602 @Override 603 public boolean setRotateOrthogonalMenu(JPopupMenu popup) { 604 605 if (isIcon() && (_displayLevel > Editor.BKG) && (_namedIcon != null)) { 606 popup.add(new AbstractAction(Bundle.getMessage("RotateOrthoSign", 607 (_namedIcon.getRotation() * 90))) { // Bundle property includes degree symbol 608 @Override 609 public void actionPerformed(ActionEvent e) { 610 rotateOrthogonal(); 611 } 612 }); 613 return true; 614 } 615 return false; 616 } 617 618 protected void rotateOrthogonal() { 619 _namedIcon.setRotation(_namedIcon.getRotation() + 1, this); 620 super.setIcon(_namedIcon); 621 updateSize(); 622 repaint(); 623 } 624 625 /* 626 * ********** Methods for Item Popups in Panel editor ************************ 627 */ 628 JFrame _iconEditorFrame; 629 IconAdder _iconEditor; 630 631 @Override 632 public boolean setEditIconMenu(JPopupMenu popup) { 633 if (_icon && !_text) { 634 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("Icon")); 635 popup.add(new AbstractAction(txt) { 636 637 @Override 638 public void actionPerformed(ActionEvent e) { 639 edit(); 640 } 641 }); 642 return true; 643 } 644 return false; 645 } 646 647 /** 648 * For item popups in Panel Editor. 649 * 650 * @param pos the container 651 * @param name the name 652 * @param table true if creating a table; false otherwise 653 * @param editor the associated editor 654 */ 655 protected void makeIconEditorFrame(Container pos, String name, boolean table, IconAdder editor) { 656 if (editor != null) { 657 _iconEditor = editor; 658 } else { 659 _iconEditor = new IconAdder(name); 660 } 661 _iconEditorFrame = _editor.makeAddIconFrame(name, false, table, _iconEditor); 662 _iconEditorFrame.addWindowListener(new java.awt.event.WindowAdapter() { 663 @Override 664 public void windowClosing(java.awt.event.WindowEvent e) { 665 _iconEditorFrame.dispose(); 666 _iconEditorFrame = null; 667 } 668 }); 669 _iconEditorFrame.setLocationRelativeTo(pos); 670 _iconEditorFrame.toFront(); 671 _iconEditorFrame.setVisible(true); 672 } 673 674 protected void edit() { 675 makeIconEditorFrame(this, "Icon", false, null); 676 NamedIcon icon = new NamedIcon(_namedIcon); 677 _iconEditor.setIcon(0, "plainIcon", icon); 678 _iconEditor.makeIconPanel(false); 679 680 ActionListener addIconAction = (ActionEvent a) -> editIcon(); 681 _iconEditor.complete(addIconAction, true, false, true); 682 683 } 684 685 protected void editIcon() { 686 String url = _iconEditor.getIcon("plainIcon").getURL(); 687 _namedIcon = NamedIcon.getIconByName(url); 688 super.setIcon(_namedIcon); 689 updateSize(); 690 _iconEditorFrame.dispose(); 691 _iconEditorFrame = null; 692 _iconEditor = null; 693 invalidate(); 694 repaint(); 695 } 696 697 public jmri.jmrit.display.DisplayFrame _paletteFrame; 698 699 // ********** Methods for Item Popups in Control Panel editor ******************* 700 /** 701 * Create a palette window. 702 * 703 * @param title the name of the palette 704 * @return DisplayFrame for palette item 705 */ 706 public DisplayFrame makePaletteFrame(String title) { 707 jmri.jmrit.display.palette.ItemPalette.loadIcons(); 708 DisplayFrame frame = new DisplayFrame(title, _editor); 709 return frame; 710 } 711 712 public void initPaletteFrame(DisplayFrame paletteFrame, @Nonnull ItemPanel itemPanel) { 713 Dimension dim = itemPanel.getPreferredSize(); 714 JScrollPane sp = new JScrollPane(itemPanel); 715 dim = new Dimension(dim.width + 25, dim.height + 25); 716 sp.setPreferredSize(dim); 717 paletteFrame.add(sp); 718 paletteFrame.pack(); 719 paletteFrame.addWindowListener(new PaletteFrameCloser(itemPanel)); 720 721 jmri.InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(_editor, this, paletteFrame); 722 paletteFrame.setVisible(true); 723 } 724 725 static class PaletteFrameCloser extends java.awt.event.WindowAdapter { 726 ItemPanel ip; 727 PaletteFrameCloser( @Nonnull ItemPanel itemPanel) { 728 super(); 729 ip = itemPanel; 730 } 731 @Override 732 public void windowClosing(java.awt.event.WindowEvent e) { 733 ip.closeDialogs(); 734 } 735 } 736 737 public void finishItemUpdate(DisplayFrame paletteFrame, @Nonnull ItemPanel itemPanel) { 738 itemPanel.closeDialogs(); 739 paletteFrame.dispose(); 740 invalidate(); 741 } 742 743 @Override 744 public boolean setEditItemMenu(@Nonnull JPopupMenu popup) { 745 if (!_icon) { 746 return false; 747 } 748 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("Icon")); 749 popup.add(new AbstractAction(txt) { 750 751 @Override 752 public void actionPerformed(ActionEvent e) { 753 editIconItem(); 754 } 755 }); 756 return true; 757 } 758 759 IconItemPanel _iconItemPanel; 760 761 protected void editIconItem() { 762 _paletteFrame = makePaletteFrame( 763 java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameTurnout"))); 764 _iconItemPanel = new IconItemPanel(_paletteFrame, "Icon"); // NOI18N 765 ActionListener updateAction = (ActionEvent a) -> updateIconItem(); 766 _iconItemPanel.init(updateAction); 767 _iconItemPanel.setUpdateIcon((NamedIcon)getIcon()); 768 initPaletteFrame(_paletteFrame, _iconItemPanel); 769 } 770 771 private void updateIconItem() { 772 NamedIcon icon = _iconItemPanel.getUpdateIcon(); 773 if (icon != null) { 774 String url = icon.getURL(); 775 setIcon(NamedIcon.getIconByName(url)); 776 updateSize(); 777 } 778 finishItemUpdate(_paletteFrame, _iconItemPanel); 779 } 780 781 /* Case for isIcon 782 @Override 783 public boolean setEditItemMenu(JPopupMenu popup) { 784 return setEditIconMenu(popup); 785 }*/ 786 787 public boolean setEditTextItemMenu(JPopupMenu popup) { 788 popup.add(new AbstractAction(Bundle.getMessage("SetTextSizeColor")) { 789 @Override 790 public void actionPerformed(ActionEvent e) { 791 editTextItem(); 792 } 793 }); 794 return true; 795 } 796 797 TextItemPanel _itemPanel; 798 799 protected void editTextItem() { 800 _paletteFrame = makePaletteFrame(Bundle.getMessage("SetTextSizeColor")); 801 _itemPanel = new TextItemPanel(_paletteFrame, "Text"); 802 ActionListener updateAction = (ActionEvent a) -> updateTextItem(); 803 _itemPanel.init(updateAction, this); 804 initPaletteFrame(_paletteFrame, _itemPanel); 805 } 806 807 protected void updateTextItem() { 808 PositionablePopupUtil util = _itemPanel.getPositionablePopupUtil(); 809 _itemPanel.setAttributes(this); 810 if (_editor._selectionGroup != null) { 811 _editor.setSelectionsAttributes(util, this); 812 } else { 813 _editor.setAttributes(util, this); 814 } 815 finishItemUpdate(_paletteFrame, _itemPanel); 816 } 817 818 /** 819 * Rotate degrees return true if popup is set. 820 */ 821 @Override 822 public boolean setRotateMenu(@Nonnull JPopupMenu popup) { 823 if (_displayLevel > Editor.BKG) { 824 popup.add(CoordinateEdit.getRotateEditAction(this)); 825 } 826 return false; 827 } 828 829 /** 830 * Scale percentage form display. 831 * 832 * @return true if popup is set 833 */ 834 @Override 835 public boolean setScaleMenu(@Nonnull JPopupMenu popup) { 836 if (isIcon() && _displayLevel > Editor.BKG) { 837 popup.add(CoordinateEdit.getScaleEditAction(this)); 838 return true; 839 } 840 return false; 841 } 842 843 @Override 844 public boolean setTextEditMenu(@Nonnull JPopupMenu popup) { 845 if (isText()) { 846 popup.add(CoordinateEdit.getTextEditAction(this, "EditText")); 847 return true; 848 } 849 return false; 850 } 851 852 JCheckBoxMenuItem disableItem = null; 853 854 @Override 855 public boolean setDisableControlMenu(@Nonnull JPopupMenu popup) { 856 if (_control) { 857 disableItem = new JCheckBoxMenuItem(Bundle.getMessage("Disable")); 858 disableItem.setSelected(!_controlling); 859 popup.add(disableItem); 860 disableItem.addActionListener((java.awt.event.ActionEvent e) -> setControlling(!disableItem.isSelected())); 861 return true; 862 } 863 return false; 864 } 865 866 @Override 867 public void setScale(double s) { 868 if (_namedIcon != null) { 869 _namedIcon.scale(s, this); 870 super.setIcon(_namedIcon); 871 updateSize(); 872 repaint(); 873 } 874 } 875 876 @Override 877 public double getScale() { 878 if (_namedIcon == null) { 879 return 1.0; 880 } 881 return ((NamedIcon) getIcon()).getScale(); 882 } 883 884 public void setIcon(NamedIcon icon) { 885 _namedIcon = icon; 886 super.setIcon(icon); 887 } 888 889 @Override 890 public void rotate(int deg) { 891 if (log.isDebugEnabled()) { 892 log.debug("rotate({}) with _rotateText {}, _text {}, _icon {}", deg, _rotateText, _text, _icon); 893 } 894 _degrees = deg; 895 896 if ((deg != 0) && (_popupUtil.getOrientation() != PositionablePopupUtil.HORIZONTAL)) { 897 _popupUtil.setOrientation(PositionablePopupUtil.HORIZONTAL); 898 } 899 900 if (_rotateText || deg == 0) { 901 if (deg == 0) { // restore unrotated whatever 902 _rotateText = false; 903 if (_text) { 904 if (log.isDebugEnabled()) { 905 log.debug(" super.setText(\"{}\");", _unRotatedText); 906 } 907 super.setText(_unRotatedText); 908 if (_popupUtil != null) { 909 setOpaque(_popupUtil.hasBackground()); 910 _popupUtil.setBorder(true); 911 } 912 if (_namedIcon != null) { 913 String url = _namedIcon.getURL(); 914 if (url == null) { 915 if (_text & _icon) { // create new text over icon 916 _namedIcon = makeTextOverlaidIcon(_unRotatedText, _namedIcon); 917 _namedIcon.rotate(deg, this); 918 } else if (_text) { 919 _namedIcon = null; 920 } 921 } else { 922 _namedIcon = new NamedIcon(url, url); 923 } 924 } 925 super.setIcon(_namedIcon); 926 } else { 927 if (_namedIcon != null) { 928 _namedIcon.rotate(deg, this); 929 } 930 super.setIcon(_namedIcon); 931 } 932 } else { 933 if (_text & _icon) { // update text over icon 934 _namedIcon = makeTextOverlaidIcon(_unRotatedText, _namedIcon); 935 } else if (_text) { // update text only icon image 936 _namedIcon = makeTextIcon(_unRotatedText); 937 } 938 if (_namedIcon != null) { 939 _namedIcon.rotate(deg, this); 940 super.setIcon(_namedIcon); 941 setOpaque(false); // rotations cannot be opaque 942 } 943 } 944 } else { // first time text or icon is rotated from horizontal 945 if (_text && _icon) { // text overlays icon e.g. LocoIcon 946 _namedIcon = makeTextOverlaidIcon(_unRotatedText, _namedIcon); 947 super.setText(null); 948 _rotateText = true; 949 setOpaque(false); 950 } else if (_text) { 951 _namedIcon = makeTextIcon(_unRotatedText); 952 super.setText(null); 953 _rotateText = true; 954 setOpaque(false); 955 } 956 if (_popupUtil != null) { 957 _popupUtil.setBorder(false); 958 } 959 if (_namedIcon != null) { // it is possible that the icon did not get created yet. 960 _namedIcon.rotate(deg, this); 961 super.setIcon(_namedIcon); 962 } 963 } 964 updateSize(); 965 repaint(); 966 } // rotate 967 968 /** 969 * Create an image of icon with overlaid text. 970 * 971 * @param text the text to overlay 972 * @param ic the icon containing the image 973 * @return the icon overlaying text on ic 974 */ 975 protected NamedIcon makeTextOverlaidIcon(String text, @Nonnull NamedIcon ic) { 976 String url = ic.getURL(); 977 if (url == null) { 978 return null; 979 } 980 NamedIcon icon = new NamedIcon(url, url); 981 982 int iconWidth = icon.getIconWidth(); 983 int iconHeight = icon.getIconHeight(); 984 985 int textWidth = getFontMetrics(getFont()).stringWidth(text); 986 int textHeight = getFontMetrics(getFont()).getHeight(); 987 988 int width = Math.max(textWidth, iconWidth); 989 int height = Math.max(textHeight, iconHeight); 990 991 int hOffset = Math.max((textWidth - iconWidth) / 2, 0); 992 int vOffset = Math.max((textHeight - iconHeight) / 2, 0); 993 994 if (_popupUtil != null) { 995 if (_popupUtil.getFixedWidth() != 0) { 996 switch (_popupUtil.getJustification()) { 997 case PositionablePopupUtil.LEFT: 998 hOffset = _popupUtil.getBorderSize(); 999 break; 1000 case PositionablePopupUtil.RIGHT: 1001 hOffset = _popupUtil.getFixedWidth() - width; 1002 hOffset += _popupUtil.getBorderSize(); 1003 break; 1004 default: 1005 hOffset = Math.max((_popupUtil.getFixedWidth() - width) / 2, 0); 1006 hOffset += _popupUtil.getBorderSize(); 1007 break; 1008 } 1009 width = _popupUtil.getFixedWidth() + 2 * _popupUtil.getBorderSize(); 1010 } else { 1011 width += 2 * (_popupUtil.getMargin() + _popupUtil.getBorderSize()); 1012 hOffset += _popupUtil.getMargin() + _popupUtil.getBorderSize(); 1013 } 1014 if (_popupUtil.getFixedHeight() != 0) { 1015 vOffset = Math.max(vOffset + (_popupUtil.getFixedHeight() - height) / 2, 0); 1016 vOffset += _popupUtil.getBorderSize(); 1017 height = _popupUtil.getFixedHeight() + 2 * _popupUtil.getBorderSize(); 1018 } else { 1019 height += 2 * (_popupUtil.getMargin() + _popupUtil.getBorderSize()); 1020 vOffset += _popupUtil.getMargin() + _popupUtil.getBorderSize(); 1021 } 1022 } 1023 1024 BufferedImage bufIm = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 1025 Graphics2D g2d = bufIm.createGraphics(); 1026 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 1027 RenderingHints.VALUE_RENDER_QUALITY); 1028 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 1029 RenderingHints.VALUE_ANTIALIAS_ON); 1030 g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, 1031 RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 1032// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // Turned off due to poor performance, see Issue #3850 and PR #3855 for background 1033// RenderingHints.VALUE_INTERPOLATION_BICUBIC); 1034 1035 if (_popupUtil != null) { 1036 if (_popupUtil.hasBackground()) { 1037 g2d.setColor(_popupUtil.getBackground()); 1038 g2d.fillRect(0, 0, width, height); 1039 } 1040 if (_popupUtil.getBorderSize() != 0) { 1041 g2d.setColor(_popupUtil.getBorderColor()); 1042 g2d.setStroke(new java.awt.BasicStroke(2 * _popupUtil.getBorderSize())); 1043 g2d.drawRect(0, 0, width, height); 1044 } 1045 } 1046 1047 g2d.drawImage(icon.getImage(), AffineTransform.getTranslateInstance(hOffset, vOffset + 1), this); 1048 1049 icon = new NamedIcon(bufIm); 1050 g2d.dispose(); 1051 icon.setURL(url); 1052 return icon; 1053 } 1054 1055 /** 1056 * Create a text image whose bit map can be rotated. 1057 */ 1058 private NamedIcon makeTextIcon(String text) { 1059 if (text == null || text.equals("")) { 1060 text = " "; 1061 } 1062 int width = getFontMetrics(getFont()).stringWidth(text); 1063 int height = getFontMetrics(getFont()).getHeight(); 1064 // int hOffset = 0; // variable has no effect, see Issue #5662 1065 // int vOffset = getFontMetrics(getFont()).getAscent(); 1066 if (_popupUtil != null) { 1067 if (_popupUtil.getFixedWidth() != 0) { 1068 switch (_popupUtil.getJustification()) { 1069 case PositionablePopupUtil.LEFT: 1070 // hOffset = _popupUtil.getBorderSize(); // variable has no effect, see Issue #5662 1071 break; 1072 case PositionablePopupUtil.RIGHT: 1073 // hOffset = _popupUtil.getFixedWidth() - width; // variable has no effect, see Issue #5662 1074 // hOffset += _popupUtil.getBorderSize(); // variable has no effect, see Issue #5662 1075 break; 1076 default: 1077 // hOffset = Math.max((_popupUtil.getFixedWidth() - width) / 2, 0); // variable has no effect, see Issue #5662 1078 // hOffset += _popupUtil.getBorderSize(); // variable has no effect, see Issue #5662 1079 break; 1080 } 1081 width = _popupUtil.getFixedWidth() + 2 * _popupUtil.getBorderSize(); 1082 } else { 1083 width += 2 * (_popupUtil.getMargin() + _popupUtil.getBorderSize()); 1084 // hOffset += _popupUtil.getMargin() + _popupUtil.getBorderSize(); // variable has no effect, see Issue #5662 1085 } 1086 if (_popupUtil.getFixedHeight() != 0) { 1087 // vOffset = Math.max(vOffset + (_popupUtil.getFixedHeight() - height) / 2, 0); 1088 // vOffset += _popupUtil.getBorderSize(); 1089 height = _popupUtil.getFixedHeight() + 2 * _popupUtil.getBorderSize(); 1090 } else { 1091 height += 2 * (_popupUtil.getMargin() + _popupUtil.getBorderSize()); 1092 // vOffset += _popupUtil.getMargin() + _popupUtil.getBorderSize(); 1093 } 1094 } 1095 1096 BufferedImage bufIm = new BufferedImage(width + 2, height + 2, BufferedImage.TYPE_INT_ARGB); 1097 Graphics2D g2d = bufIm.createGraphics(); 1098 1099 g2d.setBackground(new Color(0, 0, 0, 0)); 1100 g2d.clearRect(0, 0, bufIm.getWidth(), bufIm.getHeight()); 1101 1102 g2d.setFont(getFont()); 1103 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 1104 RenderingHints.VALUE_RENDER_QUALITY); 1105 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 1106 RenderingHints.VALUE_ANTIALIAS_ON); 1107 g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, 1108 RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 1109// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // Turned off due to poor performance, see Issue #3850 and PR #3855 for background 1110// RenderingHints.VALUE_INTERPOLATION_BICUBIC); 1111 1112 if (_popupUtil != null) { 1113 if (_popupUtil.hasBackground()) { 1114 g2d.setColor(_popupUtil.getBackground()); 1115 g2d.fillRect(0, 0, width, height); 1116 } 1117 if (_popupUtil.getBorderSize() != 0) { 1118 g2d.setColor(_popupUtil.getBorderColor()); 1119 g2d.setStroke(new java.awt.BasicStroke(2 * _popupUtil.getBorderSize())); 1120 g2d.drawRect(0, 0, width, height); 1121 } 1122 } 1123 1124 NamedIcon icon = new NamedIcon(bufIm); 1125 g2d.dispose(); 1126 return icon; 1127 } 1128 1129 public void setDegrees(int deg) { 1130 _degrees = deg; 1131 } 1132 1133 @Override 1134 public int getDegrees() { 1135 return _degrees; 1136 } 1137 1138 /** 1139 * Clean up when this object is no longer needed. Should not be called while 1140 * the object is still displayed; see remove() 1141 */ 1142 public void dispose() { 1143 } 1144 1145 /** 1146 * Removes this object from display and persistance 1147 */ 1148 @Override 1149 public void remove() { 1150 // If this Positionable has an Inline LogixNG, that LogixNG might be in use. 1151 LogixNG logixNG = getLogixNG(); 1152 if (logixNG != null) { 1153 DeleteBean<LogixNG> deleteBean = new DeleteBean<>( 1154 InstanceManager.getDefault(LogixNG_Manager.class)); 1155 1156 boolean hasChildren = logixNG.getNumConditionalNGs() > 0; 1157 1158 deleteBean.delete(logixNG, hasChildren, (t)->{deleteLogixNG(t);}, 1159 (t,list)->{logixNG.getListenerRefsIncludingChildren(list);}, 1160 jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName()); 1161 } else { 1162 doRemove(); 1163 } 1164 } 1165 1166 private void deleteLogixNG(LogixNG logixNG) { 1167 logixNG.setEnabled(false); 1168 try { 1169 InstanceManager.getDefault(LogixNG_Manager.class).deleteBean(logixNG, "DoDelete"); 1170 setLogixNG(null); 1171 doRemove(); 1172 } catch (PropertyVetoException e) { 1173 //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto 1174 log.error("{} : Could not Delete.", e.getMessage()); 1175 } 1176 } 1177 1178 private void doRemove() { 1179 if (_editor.removeFromContents(this)) { 1180 // Modified to support conditional delete for NX sensors 1181 // remove from persistance by flagging inactive 1182 active = false; 1183 dispose(); 1184 } 1185 } 1186 1187 boolean active = true; 1188 1189 /** 1190 * Check if the component is still displayed, and should be stored. 1191 * 1192 * @return true if active; false otherwise 1193 */ 1194 public boolean isActive() { 1195 return active; 1196 } 1197 1198 protected void setSuperText(String text) { 1199 _unRotatedText = text; 1200 super.setText(text); 1201 } 1202 1203 @Override 1204 public void setText(String text) { 1205 if (this instanceof BlockContentsIcon || this instanceof MemoryIcon || this instanceof GlobalVariableIcon) { 1206 if (_editor != null && !_editor.isEditable()) { 1207 if (isEmptyHidden()) { 1208 log.debug("label setText: {} :: {}", text, getNameString()); 1209 if (text == null || text.isEmpty()) { 1210 setVisible(false); 1211 } else { 1212 setVisible(true); 1213 } 1214 } 1215 } 1216 } 1217 1218 _unRotatedText = text; 1219 _text = (text != null && text.length() > 0); // when "" is entered for text, and a font has been specified, the descender distance moves the position 1220 if (/*_rotateText &&*/!isIcon() && (_namedIcon != null || _degrees != 0)) { 1221 log.debug("setText calls rotate({})", _degrees); 1222 rotate(_degrees); //this will change text label as a icon with a new _namedIcon. 1223 } else { 1224 log.debug("setText calls super.setText()"); 1225 super.setText(text); 1226 } 1227 } 1228 1229 private boolean needsRotate; 1230 1231 @Override 1232 public Dimension getSize() { 1233 if (!needsRotate) { 1234 return super.getSize(); 1235 } 1236 1237 Dimension size = super.getSize(); 1238 if (_popupUtil == null) { 1239 return super.getSize(); 1240 } 1241 switch (_popupUtil.getOrientation()) { 1242 case PositionablePopupUtil.VERTICAL_DOWN: 1243 case PositionablePopupUtil.VERTICAL_UP: 1244 if (_degrees != 0) { 1245 rotate(0); 1246 } 1247 return new Dimension(size.height, size.width); // flip dimension 1248 default: 1249 return super.getSize(); 1250 } 1251 } 1252 1253 @Override 1254 public int getHeight() { 1255 return getSize().height; 1256 } 1257 1258 @Override 1259 public int getWidth() { 1260 return getSize().width; 1261 } 1262 1263 @Override 1264 protected void paintComponent(Graphics g) { 1265 if (_popupUtil == null) { 1266 super.paintComponent(g); 1267 } else { 1268 Graphics2D g2d = (Graphics2D) g.create(); 1269 1270 // set antialiasing hint for macOS and Windows 1271 // note: antialiasing has performance problems on some variants of Linux (Raspberry pi) 1272 if (SystemType.isMacOSX() || SystemType.isWindows()) { 1273 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 1274 RenderingHints.VALUE_RENDER_QUALITY); 1275 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 1276 RenderingHints.VALUE_ANTIALIAS_ON); 1277 g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, 1278 RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 1279// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // Turned off due to poor performance, see Issue #3850 and PR #3855 for background 1280// RenderingHints.VALUE_INTERPOLATION_BICUBIC); 1281 } 1282 1283 switch (_popupUtil.getOrientation()) { 1284 case PositionablePopupUtil.VERTICAL_UP: 1285 g2d.translate(0, getSize().getHeight()); 1286 g2d.transform(AffineTransform.getQuadrantRotateInstance(-1)); 1287 break; 1288 case PositionablePopupUtil.VERTICAL_DOWN: 1289 g2d.transform(AffineTransform.getQuadrantRotateInstance(1)); 1290 g2d.translate(0, -getSize().getWidth()); 1291 break; 1292 case 0: 1293 // routine value (not initialized) for no change 1294 break; 1295 default: 1296 // unexpected orientation value 1297 jmri.util.LoggingUtil.warnOnce(log, "Unexpected orientation = {}", _popupUtil.getOrientation()); 1298 break; 1299 } 1300 1301 needsRotate = true; 1302 super.paintComponent(g2d); 1303 needsRotate = false; 1304 1305 if (_popupUtil.getOrientation() == PositionablePopupUtil.HORIZONTAL) { 1306 if ((_unRotatedText != null) && (_degrees != 0)) { 1307 double angleRAD = Math.toRadians(_degrees); 1308 1309 int iconWidth = getWidth(); 1310 int iconHeight = getHeight(); 1311 1312 int textWidth = getFontMetrics(getFont()).stringWidth(_unRotatedText); 1313 int textHeight = getFontMetrics(getFont()).getHeight(); 1314 1315 Point2D textSizeRotated = MathUtil.rotateRAD(textWidth, textHeight, angleRAD); 1316 int textWidthRotated = (int) textSizeRotated.getX(); 1317 int textHeightRotated = (int) textSizeRotated.getY(); 1318 1319 int width = Math.max(textWidthRotated, iconWidth); 1320 int height = Math.max(textHeightRotated, iconHeight); 1321 1322 int iconOffsetX = width / 2; 1323 int iconOffsetY = height / 2; 1324 1325 g2d.transform(AffineTransform.getRotateInstance(angleRAD, iconOffsetX, iconOffsetY)); 1326 1327 int hOffset = iconOffsetX - (textWidth / 2); 1328 //int vOffset = iconOffsetY + ((textHeight - getFontMetrics(getFont()).getAscent()) / 2); 1329 int vOffset = iconOffsetY + (textHeight / 4); // why 4? Don't know, it just looks better 1330 1331 g2d.setFont(getFont()); 1332 g2d.setColor(getForeground()); 1333 g2d.drawString(_unRotatedText, hOffset, vOffset); 1334 } 1335 } 1336 } 1337 } // paintComponent 1338 1339 /** 1340 * Provide a generic method to return the bean associated with the 1341 * Positionable. 1342 */ 1343 @Override 1344 public jmri.NamedBean getNamedBean() { 1345 return null; 1346 } 1347 1348 /** {@inheritDoc} */ 1349 @Override 1350 public LogixNG getLogixNG() { 1351 return _logixNG; 1352 } 1353 1354 /** {@inheritDoc} */ 1355 @Override 1356 public void setLogixNG(LogixNG logixNG) { 1357 this._logixNG = logixNG; 1358 } 1359 1360 /** {@inheritDoc} */ 1361 @Override 1362 public void setLogixNG_SystemName(String systemName) { 1363 this._logixNG_SystemName = systemName; 1364 } 1365 1366 /** {@inheritDoc} */ 1367 @Override 1368 public void setupLogixNG() { 1369 _logixNG = InstanceManager.getDefault(LogixNG_Manager.class) 1370 .getBySystemName(_logixNG_SystemName); 1371 if (_logixNG == null) { 1372 throw new RuntimeException(String.format( 1373 "LogixNG %s is not found for positional %s in panel %s", 1374 _logixNG_SystemName, getNameString(), getEditor().getName())); 1375 } 1376 _logixNG.setInlineLogixNG(this); 1377 } 1378 1379 private final static Logger log = LoggerFactory.getLogger(PositionableLabel.class); 1380 1381}