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