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