001package jmri.jmrit.display; 002 003import java.awt.Component; 004import java.awt.Container; 005import java.awt.Dimension; 006import java.awt.Point; 007import java.awt.event.ActionEvent; 008import java.awt.event.ActionListener; 009import java.util.Objects; 010import java.util.HashSet; 011import java.util.Set; 012 013import javax.annotation.Nonnull; 014import javax.swing.AbstractAction; 015import javax.swing.JCheckBoxMenuItem; 016import javax.swing.JComponent; 017import javax.swing.JFrame; 018import javax.swing.JMenuItem; 019import javax.swing.JPanel; 020import javax.swing.JPopupMenu; 021import javax.swing.JScrollPane; 022import javax.swing.SwingUtilities; 023 024import jmri.InstanceManager; 025 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import jmri.jmrit.display.palette.ItemPanel; 030import jmri.jmrit.display.palette.TextItemPanel; 031import jmri.jmrit.logixng.LogixNG; 032import jmri.jmrit.logixng.LogixNG_Manager; 033import jmri.util.swing.JmriMouseEvent; 034import jmri.util.swing.JmriMouseListener; 035import jmri.util.swing.JmriMouseMotionListener; 036 037/** 038 * <a href="doc-files/Heirarchy.png"><img src="doc-files/Heirarchy.png" alt="UML class diagram for package" height="33%" width="33%"></a> 039 * @author Bob Jacobsen copyright (C) 2009 040 */ 041public class PositionableJPanel extends JPanel implements Positionable, JmriMouseListener, JmriMouseMotionListener { 042 043 protected Editor _editor = null; 044 045 private String _id; // user's Id or null if no Id 046 private final Set<String> _classes = new HashSet<>(); // user's classes 047 048 private ToolTip _tooltip; 049 protected boolean _showTooltip = true; 050 protected boolean _editable = true; 051 protected boolean _positionable = true; 052 protected boolean _viewCoordinates = false; 053 protected boolean _controlling = true; 054 protected boolean _hidden = false; 055 protected boolean _emptyHidden = false; 056 protected int _displayLevel; 057 private double _scale = 1.0; // scaling factor 058 059 JMenuItem lock = null; 060 JCheckBoxMenuItem showTooltipItem = null; 061 062 private LogixNG _logixNG; 063 private String _logixNG_SystemName; 064 065 public PositionableJPanel(Editor editor) { 066 _editor = editor; 067 } 068 069 /** 070 * Hit testing must use the same scaled bounds as painting in an editor 071 * with a paint scale other than 1.0. 072 * 073 * @param x the x coordinate relative to this panel 074 * @param y the y coordinate relative to this panel 075 * @return true if the point is inside the displayed panel 076 */ 077 @Override 078 public boolean contains(int x, int y) { 079 if (_editor == null || _editor.getTargetPanel() == null || getParent() == null) { 080 return super.contains(x, y); 081 } 082 double scale = _editor.getPaintScale(); 083 if (scale == 1.0) { 084 return super.contains(x, y); 085 } 086 087 Point origin = SwingUtilities.convertPoint(this, 0, 0, _editor.getTargetPanel()); 088 double unscaledX = (x + origin.x) / scale - origin.x; 089 double unscaledY = (y + origin.y) / scale - origin.y; 090 return unscaledX >= 0 && unscaledX < getWidth() 091 && unscaledY >= 0 && unscaledY < getHeight(); 092 } 093 094 @Override 095 public Positionable deepClone() { 096 PositionableJPanel pos = new PositionableJPanel(_editor); 097 return finishClone(pos); 098 } 099 100 protected Positionable finishClone(PositionableJPanel pos) { 101 pos.setLocation(getX(), getY()); 102 pos._displayLevel = _displayLevel; 103 pos._controlling = _controlling; 104 pos._hidden = _hidden; 105 pos._positionable = _positionable; 106 pos._showTooltip = _showTooltip; 107 pos.setToolTip(getToolTip()); 108 pos._editable = _editable; 109 if (getPopupUtility() == null) { 110 pos.setPopupUtility(null); 111 } else { 112 pos.setPopupUtility(getPopupUtility().clone(pos, pos.getTextComponent())); 113 } 114 pos.updateSize(); 115 return pos; 116 } 117 118 /** {@inheritDoc} */ 119 @Override 120 public void setId(String id) throws Positionable.DuplicateIdException { 121 if (Objects.equals(this._id, id)) return; 122 _editor.positionalIdChange(this, id); 123 this._id = id; 124 } 125 126 /** {@inheritDoc} */ 127 @Override 128 public String getId() { 129 return _id; 130 } 131 132 /** {@inheritDoc} */ 133 @Override 134 public void addClass(String className) { 135 _editor.positionalAddClass(this, className); 136 _classes.add(className); 137 } 138 139 /** {@inheritDoc} */ 140 @Override 141 public void removeClass(String className) { 142 _editor.positionalRemoveClass(this, className); 143 _classes.remove(className); 144 } 145 146 /** {@inheritDoc} */ 147 @Override 148 public void removeAllClasses() { 149 for (String className : _classes) { 150 _editor.positionalRemoveClass(this, className); 151 } 152 _classes.clear(); 153 } 154 155 /** {@inheritDoc} */ 156 @Override 157 public Set<String> getClasses() { 158 return java.util.Collections.unmodifiableSet(_classes); 159 } 160 161 @Override 162 public void setPositionable(boolean enabled) { 163 _positionable = enabled; 164 } 165 166 @Override 167 public boolean isPositionable() { 168 return _positionable; 169 } 170 171 @Override 172 public void setEditable(boolean enabled) { 173 _editable = enabled; 174 } 175 176 @Override 177 public boolean isEditable() { 178 return _editable; 179 } 180 181 @Override 182 public void setViewCoordinates(boolean enabled) { 183 _viewCoordinates = enabled; 184 } 185 186 @Override 187 public boolean getViewCoordinates() { 188 return _viewCoordinates; 189 } 190 191 @Override 192 public void setControlling(boolean enabled) { 193 _controlling = enabled; 194 } 195 196 @Override 197 public boolean isControlling() { 198 return _controlling; 199 } 200 201 @Override 202 public void setHidden(boolean hide) { 203 if (_hidden != hide) { 204 _hidden = hide; 205 showHidden(); 206 } 207 } 208 209 @Override 210 public boolean isHidden() { 211 return _hidden; 212 } 213 214 @Override 215 public void showHidden() { 216 if (!_hidden || _editor.isEditable()) { 217 setVisible(true); 218 } else { 219 setVisible(false); 220 } 221 } 222 223 @Override 224 public void setEmptyHidden(boolean hide) { 225 _emptyHidden = hide; 226 } 227 228 @Override 229 public boolean isEmptyHidden() { 230 return _emptyHidden; 231 } 232 233 @Override 234 public void setValueEditDisabled(boolean isDisabled) { 235 } 236 237 @Override 238 public boolean isValueEditDisabled() { 239 return false; 240 } 241 242 public void setLevel(int l) { 243 _displayLevel = l; 244 } 245 246 @Override 247 public void setDisplayLevel(int l) { 248 int oldDisplayLevel = _displayLevel; 249 _displayLevel = l; 250 if (oldDisplayLevel != l) { 251 log.debug("Changing label display level from {} to {}", oldDisplayLevel, _displayLevel); 252 _editor.displayLevelChange(this); 253 } 254 } 255 256 @Override 257 public int getDisplayLevel() { 258 return _displayLevel; 259 } 260 261 @Override 262 public void setShowToolTip(boolean set) { 263 _showTooltip = set; 264 } 265 266 @Override 267 public boolean showToolTip() { 268 return _showTooltip; 269 } 270 271 @Override 272 public void setToolTip(ToolTip tip) { 273 _tooltip = tip; 274 } 275 276 @Override 277 public ToolTip getToolTip() { 278 return _tooltip; 279 } 280 281 @Override 282 public void setScale(double s) { 283 _scale = s; 284 } 285 286 @Override 287 public double getScale() { 288 return _scale; 289 } 290 291 // no subclasses support rotations (yet) 292 @Override 293 public void rotate(int deg) { 294 } 295 296 @Override 297 public int getDegrees() { 298 return 0; 299 } 300 301 @Override 302 public JComponent getTextComponent() { 303 return this; 304 } 305 306 @Override 307 @Nonnull 308 public String getTypeString() { 309 return Bundle.getMessage("PositionableType_PositionableJPanel"); 310 } 311 312 @Override 313 public String getNameString() { 314 return getName(); 315 } 316 317 @Override 318 public Editor getEditor() { 319 return _editor; 320 } 321 322 @Override 323 public void setEditor(Editor ed) { 324 _editor = ed; 325 } 326 327 public boolean setEditTextItemMenu(JPopupMenu popup) { 328 popup.add(new AbstractAction(Bundle.getMessage("SetTextSizeColor")) { 329 @Override 330 public void actionPerformed(ActionEvent e) { 331 editTextItem(); 332 } 333 }); 334 return true; 335 } 336 337 TextItemPanel _itemPanel; 338 339 protected void editTextItem() { 340 _paletteFrame = makePaletteFrame(Bundle.getMessage("SetTextSizeColor")); 341 _itemPanel = new TextItemPanel(_paletteFrame, "Text"); 342 ActionListener updateAction = (ActionEvent a) -> updateTextItem(); 343 _itemPanel.init(updateAction, this); 344 initPaletteFrame(_paletteFrame, _itemPanel); 345 } 346 347 protected void updateTextItem() { 348 PositionablePopupUtil util = _itemPanel.getPositionablePopupUtil(); 349 _itemPanel.setAttributes(this); 350 if (_editor._selectionGroup != null) { 351 _editor.setSelectionsAttributes(util, this); 352 } else { 353 _editor.setAttributes(util, this); 354 } 355 finishItemUpdate(_paletteFrame, _itemPanel); 356 } 357 358 public jmri.jmrit.display.DisplayFrame _paletteFrame; 359 360 // ********** Methods for Item Popups in Control Panel editor ******************* 361 /** 362 * Create a palette window. 363 * 364 * @param title the name of the palette 365 * @return DisplayFrame for palette item 366 */ 367 public DisplayFrame makePaletteFrame(String title) { 368 jmri.jmrit.display.palette.ItemPalette.loadIcons(); 369 370 return new DisplayFrame(title, _editor); 371 } 372 373 public void initPaletteFrame(DisplayFrame paletteFrame, @Nonnull ItemPanel itemPanel) { 374 Dimension dim = itemPanel.getPreferredSize(); 375 JScrollPane sp = new JScrollPane(itemPanel); 376 dim = new Dimension(dim.width + 25, dim.height + 25); 377 sp.setPreferredSize(dim); 378 paletteFrame.add(sp); 379 paletteFrame.pack(); 380 jmri.InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(_editor, this, paletteFrame); 381 paletteFrame.setVisible(true); 382 } 383 384 public void finishItemUpdate(DisplayFrame paletteFrame, @Nonnull ItemPanel itemPanel) { 385 itemPanel.closeDialogs(); 386 paletteFrame.dispose(); 387 invalidate(); 388 } 389 390 // overide where used - e.g. momentary 391 @Override 392 public void doMousePressed(JmriMouseEvent event) { 393 } 394 395 @Override 396 public void doMouseReleased(JmriMouseEvent event) { 397 } 398 399 @Override 400 public void doMouseClicked(JmriMouseEvent event) { 401 } 402 403 @Override 404 public void doMouseDragged(JmriMouseEvent event) { 405 } 406 407 @Override 408 public void doMouseMoved(JmriMouseEvent event) { 409 } 410 411 @Override 412 public void doMouseEntered(JmriMouseEvent event) { 413 } 414 415 @Override 416 public void doMouseExited(JmriMouseEvent event) { 417 } 418 419 @Override 420 public boolean storeItem() { 421 return true; 422 } 423 424 @Override 425 public boolean doViemMenu() { 426 return true; 427 } 428 429 /** 430 * For over-riding in the using classes: add item specific menu choices 431 */ 432 @Override 433 public boolean setRotateOrthogonalMenu(JPopupMenu popup) { 434 return false; 435 } 436 437 @Override 438 public boolean setRotateMenu(JPopupMenu popup) { 439 return false; 440 } 441 442 @Override 443 public boolean setScaleMenu(JPopupMenu popup) { 444 return false; 445 } 446 447 @Override 448 public boolean setDisableControlMenu(JPopupMenu popup) { 449 return false; 450 } 451 452 @Override 453 public boolean setTextEditMenu(JPopupMenu popup) { 454 return false; 455 } 456 457 @Override 458 public boolean showPopUp(JPopupMenu popup) { 459 return false; 460 } 461 462 JFrame _iconEditorFrame; 463 IconAdder _iconEditor; 464 465 @Override 466 public boolean setEditIconMenu(JPopupMenu popup) { 467 return false; 468 } 469 470 @Override 471 public boolean setEditItemMenu(JPopupMenu popup) { 472 return setEditIconMenu(popup); 473 } 474 475 protected void makeIconEditorFrame(Container pos, String name, boolean table, IconAdder editor) { 476 if (editor != null) { 477 _iconEditor = editor; 478 } else { 479 _iconEditor = new IconAdder(name); 480 } 481 _iconEditorFrame = _editor.makeAddIconFrame(name, false, table, _iconEditor); 482 _iconEditorFrame.addWindowListener(new java.awt.event.WindowAdapter() { 483 @Override 484 public void windowClosing(java.awt.event.WindowEvent e) { 485 _iconEditorFrame.dispose(); 486 _iconEditorFrame = null; 487 } 488 }); 489 _iconEditorFrame.setLocationRelativeTo(pos); 490 _iconEditorFrame.toFront(); 491 _iconEditorFrame.setVisible(true); 492 } 493 494 void edit() { 495 } 496 497 /* 498 ************** end Positionable methods ********************* 499 */ 500 /** 501 * Removes this object from display and persistance 502 */ 503 @Override 504 public void remove() { 505 _editor.removeFromContents(this); 506 cleanup(); 507 // remove from persistance by flagging inactive 508 active = false; 509 } 510 511 /** 512 * To be overridden if any special work needs to be done 513 */ 514 void cleanup() { 515 } 516 517 boolean active = true; 518 519 /** 520 * @return true if this object is still displayed, and should be stored; 521 * false otherwise 522 */ 523 public boolean isActive() { 524 return active; 525 } 526 527 private JmriMouseEvent toEditorMouseEvent(JmriMouseEvent event) { 528 Component source = event.getComponent(); 529 Point point = SwingUtilities.convertPoint(source, event.getPoint(), _editor.getTargetPanel()); 530 double scale = _editor.getPaintScale(); 531 return new JmriMouseEvent(this, event.getID(), event.getWhen(), event.getModifiersEx(), 532 (int) Math.round(point.x * scale), (int) Math.round(point.y * scale), 533 event.getClickCount(), event.isPopupTrigger(), event.getButton()); 534 } 535 536 @Override 537 public void mousePressed(JmriMouseEvent e) { 538 _editor.mousePressed(toEditorMouseEvent(e)); 539 } 540 541 @Override 542 public void mouseReleased(JmriMouseEvent e) { 543 _editor.mouseReleased(toEditorMouseEvent(e)); 544 } 545 546 @Override 547 public void mouseClicked(JmriMouseEvent e) { 548 _editor.mouseClicked(toEditorMouseEvent(e)); 549 } 550 551 @Override 552 public void mouseExited(JmriMouseEvent e) { 553// transferFocus(); 554 _editor.mouseExited(toEditorMouseEvent(e)); 555 } 556 557 @Override 558 public void mouseEntered(JmriMouseEvent e) { 559 _editor.mouseEntered(toEditorMouseEvent(e)); 560 } 561 562 @Override 563 public void mouseMoved(JmriMouseEvent e) { 564 _editor.mouseMoved(toEditorMouseEvent(e)); 565 } 566 567 @Override 568 public void mouseDragged(JmriMouseEvent e) { 569 _editor.mouseDragged(toEditorMouseEvent(e)); 570 } 571 572 /** 573 * ************************************************************ 574 */ 575 PositionablePopupUtil _popupUtil; 576 577 @Override 578 public void setPopupUtility(PositionablePopupUtil tu) { 579 _popupUtil = tu; 580 } 581 582 @Override 583 public PositionablePopupUtil getPopupUtility() { 584 return _popupUtil; 585 } 586 587 /** 588 * Update the AWT and Swing size information due to change in internal 589 * state, e.g. if one or more of the icons that might be displayed is 590 * changed 591 */ 592 @Override 593 public void updateSize() { 594 invalidate(); 595 setSize(maxWidth(), maxHeight()); 596 if (log.isTraceEnabled()) { 597 // the following fails when run on Jenkins under Xvfb with an NPE in non-JMRI code 598 log.trace("updateSize: {}, text: w={} h={}", 599 _popupUtil.toString(), 600 getFontMetrics(_popupUtil.getFont()).stringWidth(_popupUtil.getText()), 601 getFontMetrics(_popupUtil.getFont()).getHeight()); 602 } 603 validate(); 604 repaint(); 605 } 606 607 @Override 608 public int maxWidth() { 609 int max = 0; 610 if (_popupUtil != null) { 611 if (_popupUtil.getFixedWidth() != 0) { 612 max = _popupUtil.getFixedWidth(); 613 max += _popupUtil.getMargin() * 2; 614 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 615 _popupUtil.setFixedWidth(PositionablePopupUtil.MIN_SIZE); 616 max = PositionablePopupUtil.MIN_SIZE; 617 } 618 } else { 619 max = getPreferredSize().width; 620 /* 621 if(_popupUtil._textComponent instanceof javax.swing.JTextField) { 622 javax.swing.JTextField text = (javax.swing.JTextField)_popupUtil._textComponent; 623 max = getFontMetrics(text.getFont()).stringWidth(text.getText()); 624 } */ 625 max += _popupUtil.getMargin() * 2; 626 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 627 max = PositionablePopupUtil.MIN_SIZE; 628 } 629 } 630 } 631 log.debug("maxWidth= {} preferred width= {}", max, getPreferredSize().width); 632 return max; 633 } 634 635 @Override 636 public int maxHeight() { 637 int max = 0; 638 if (_popupUtil != null) { 639 if (_popupUtil.getFixedHeight() != 0) { 640 max = _popupUtil.getFixedHeight(); 641 max += _popupUtil.getMargin() * 2; 642 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 643 _popupUtil.setFixedHeight(PositionablePopupUtil.MIN_SIZE); 644 max = PositionablePopupUtil.MIN_SIZE; 645 } 646 } else { 647 max = getPreferredSize().height; 648 /* 649 if(_popupUtil._textComponent!=null) { 650 max = getFontMetrics(_popupUtil._textComponent.getFont()).getHeight(); 651 } */ 652 if (_popupUtil != null) { 653 max += _popupUtil.getMargin() * 2; 654 } 655 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 656 max = PositionablePopupUtil.MIN_SIZE; 657 } 658 } 659 } 660 log.debug("maxHeight= {} preferred width= {}", max, getPreferredSize().height); 661 return max; 662 } 663 664 @Override 665 public jmri.NamedBean getNamedBean() { 666 return null; 667 } 668 669 /** {@inheritDoc} */ 670 @Override 671 public LogixNG getLogixNG() { 672 return _logixNG; 673 } 674 675 /** {@inheritDoc} */ 676 @Override 677 public void setLogixNG(LogixNG logixNG) { 678 this._logixNG = logixNG; 679 } 680 681 /** {@inheritDoc} */ 682 @Override 683 public void setLogixNG_SystemName(String systemName) { 684 this._logixNG_SystemName = systemName; 685 } 686 687 /** {@inheritDoc} */ 688 @Override 689 public void setupLogixNG() { 690 _logixNG = InstanceManager.getDefault(LogixNG_Manager.class) 691 .getBySystemName(_logixNG_SystemName); 692 if (_logixNG == null) { 693 throw new RuntimeException(String.format( 694 "LogixNG %s is not found for positional %s in panel %s", 695 _logixNG_SystemName, getNameString(), getEditor().getName())); 696 } 697 _logixNG.setInlineLogixNG(this); 698 } 699 700 private static final Logger log = LoggerFactory.getLogger(PositionableJPanel.class); 701}