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 @Override 203 public void setValueEditDisabled(boolean isDisabled) { 204 } 205 206 @Override 207 public boolean isValueEditDisabled() { 208 return false; 209 } 210 211 public void setLevel(int l) { 212 _displayLevel = l; 213 } 214 215 @Override 216 public void setDisplayLevel(int l) { 217 int oldDisplayLevel = _displayLevel; 218 _displayLevel = l; 219 if (oldDisplayLevel != l) { 220 log.debug("Changing label display level from {} to {}", oldDisplayLevel, _displayLevel); 221 _editor.displayLevelChange(this); 222 } 223 } 224 225 @Override 226 public int getDisplayLevel() { 227 return _displayLevel; 228 } 229 230 @Override 231 public void setShowToolTip(boolean set) { 232 _showTooltip = set; 233 } 234 235 @Override 236 public boolean showToolTip() { 237 return _showTooltip; 238 } 239 240 @Override 241 public void setToolTip(ToolTip tip) { 242 _tooltip = tip; 243 } 244 245 @Override 246 public ToolTip getToolTip() { 247 return _tooltip; 248 } 249 250 @Override 251 public void setScale(double s) { 252 _scale = s; 253 } 254 255 @Override 256 public double getScale() { 257 return _scale; 258 } 259 260 // no subclasses support rotations (yet) 261 @Override 262 public void rotate(int deg) { 263 } 264 265 @Override 266 public int getDegrees() { 267 return 0; 268 } 269 270 @Override 271 public JComponent getTextComponent() { 272 return this; 273 } 274 275 @Override 276 @Nonnull 277 public String getTypeString() { 278 return Bundle.getMessage("PositionableType_PositionableJPanel"); 279 } 280 281 @Override 282 public String getNameString() { 283 return getName(); 284 } 285 286 @Override 287 public Editor getEditor() { 288 return _editor; 289 } 290 291 @Override 292 public void setEditor(Editor ed) { 293 _editor = ed; 294 } 295 296 public boolean setEditTextItemMenu(JPopupMenu popup) { 297 popup.add(new AbstractAction(Bundle.getMessage("SetTextSizeColor")) { 298 @Override 299 public void actionPerformed(ActionEvent e) { 300 editTextItem(); 301 } 302 }); 303 return true; 304 } 305 306 TextItemPanel _itemPanel; 307 308 protected void editTextItem() { 309 _paletteFrame = makePaletteFrame(Bundle.getMessage("SetTextSizeColor")); 310 _itemPanel = new TextItemPanel(_paletteFrame, "Text"); 311 ActionListener updateAction = (ActionEvent a) -> updateTextItem(); 312 _itemPanel.init(updateAction, this); 313 initPaletteFrame(_paletteFrame, _itemPanel); 314 } 315 316 protected void updateTextItem() { 317 PositionablePopupUtil util = _itemPanel.getPositionablePopupUtil(); 318 _itemPanel.setAttributes(this); 319 if (_editor._selectionGroup != null) { 320 _editor.setSelectionsAttributes(util, this); 321 } else { 322 _editor.setAttributes(util, this); 323 } 324 finishItemUpdate(_paletteFrame, _itemPanel); 325 } 326 327 public jmri.jmrit.display.DisplayFrame _paletteFrame; 328 329 // ********** Methods for Item Popups in Control Panel editor ******************* 330 /** 331 * Create a palette window. 332 * 333 * @param title the name of the palette 334 * @return DisplayFrame for palette item 335 */ 336 public DisplayFrame makePaletteFrame(String title) { 337 jmri.jmrit.display.palette.ItemPalette.loadIcons(); 338 339 return new DisplayFrame(title, _editor); 340 } 341 342 public void initPaletteFrame(DisplayFrame paletteFrame, @Nonnull ItemPanel itemPanel) { 343 Dimension dim = itemPanel.getPreferredSize(); 344 JScrollPane sp = new JScrollPane(itemPanel); 345 dim = new Dimension(dim.width + 25, dim.height + 25); 346 sp.setPreferredSize(dim); 347 paletteFrame.add(sp); 348 paletteFrame.pack(); 349 jmri.InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(_editor, this, paletteFrame); 350 paletteFrame.setVisible(true); 351 } 352 353 public void finishItemUpdate(DisplayFrame paletteFrame, @Nonnull ItemPanel itemPanel) { 354 itemPanel.closeDialogs(); 355 paletteFrame.dispose(); 356 invalidate(); 357 } 358 359 // overide where used - e.g. momentary 360 @Override 361 public void doMousePressed(JmriMouseEvent event) { 362 } 363 364 @Override 365 public void doMouseReleased(JmriMouseEvent event) { 366 } 367 368 @Override 369 public void doMouseClicked(JmriMouseEvent event) { 370 } 371 372 @Override 373 public void doMouseDragged(JmriMouseEvent event) { 374 } 375 376 @Override 377 public void doMouseMoved(JmriMouseEvent event) { 378 } 379 380 @Override 381 public void doMouseEntered(JmriMouseEvent event) { 382 } 383 384 @Override 385 public void doMouseExited(JmriMouseEvent event) { 386 } 387 388 @Override 389 public boolean storeItem() { 390 return true; 391 } 392 393 @Override 394 public boolean doViemMenu() { 395 return true; 396 } 397 398 /** 399 * For over-riding in the using classes: add item specific menu choices 400 */ 401 @Override 402 public boolean setRotateOrthogonalMenu(JPopupMenu popup) { 403 return false; 404 } 405 406 @Override 407 public boolean setRotateMenu(JPopupMenu popup) { 408 return false; 409 } 410 411 @Override 412 public boolean setScaleMenu(JPopupMenu popup) { 413 return false; 414 } 415 416 @Override 417 public boolean setDisableControlMenu(JPopupMenu popup) { 418 return false; 419 } 420 421 @Override 422 public boolean setTextEditMenu(JPopupMenu popup) { 423 return false; 424 } 425 426 @Override 427 public boolean showPopUp(JPopupMenu popup) { 428 return false; 429 } 430 431 JFrame _iconEditorFrame; 432 IconAdder _iconEditor; 433 434 @Override 435 public boolean setEditIconMenu(JPopupMenu popup) { 436 return false; 437 } 438 439 @Override 440 public boolean setEditItemMenu(JPopupMenu popup) { 441 return setEditIconMenu(popup); 442 } 443 444 protected void makeIconEditorFrame(Container pos, String name, boolean table, IconAdder editor) { 445 if (editor != null) { 446 _iconEditor = editor; 447 } else { 448 _iconEditor = new IconAdder(name); 449 } 450 _iconEditorFrame = _editor.makeAddIconFrame(name, false, table, _iconEditor); 451 _iconEditorFrame.addWindowListener(new java.awt.event.WindowAdapter() { 452 @Override 453 public void windowClosing(java.awt.event.WindowEvent e) { 454 _iconEditorFrame.dispose(); 455 _iconEditorFrame = null; 456 } 457 }); 458 _iconEditorFrame.setLocationRelativeTo(pos); 459 _iconEditorFrame.toFront(); 460 _iconEditorFrame.setVisible(true); 461 } 462 463 void edit() { 464 } 465 466 /* 467 ************** end Positionable methods ********************* 468 */ 469 /** 470 * Removes this object from display and persistance 471 */ 472 @Override 473 public void remove() { 474 _editor.removeFromContents(this); 475 cleanup(); 476 // remove from persistance by flagging inactive 477 active = false; 478 } 479 480 /** 481 * To be overridden if any special work needs to be done 482 */ 483 void cleanup() { 484 } 485 486 boolean active = true; 487 488 /** 489 * @return true if this object is still displayed, and should be stored; 490 * false otherwise 491 */ 492 public boolean isActive() { 493 return active; 494 } 495 496 @Override 497 public void mousePressed(JmriMouseEvent e) { 498 _editor.mousePressed(new JmriMouseEvent(this, e.getID(), e.getWhen(), e.getModifiersEx(), 499 e.getX() + this.getX(), e.getY() + this.getY(), 500 e.getClickCount(), e.isPopupTrigger())); 501 } 502 503 @Override 504 public void mouseReleased(JmriMouseEvent e) { 505 _editor.mouseReleased(new JmriMouseEvent(this, e.getID(), e.getWhen(), e.getModifiersEx(), 506 e.getX() + this.getX(), e.getY() + this.getY(), 507 e.getClickCount(), e.isPopupTrigger())); 508 } 509 510 @Override 511 public void mouseClicked(JmriMouseEvent e) { 512 _editor.mouseClicked(new JmriMouseEvent(this, e.getID(), e.getWhen(), e.getModifiersEx(), 513 e.getX() + this.getX(), e.getY() + this.getY(), 514 e.getClickCount(), e.isPopupTrigger())); 515 } 516 517 @Override 518 public void mouseExited(JmriMouseEvent e) { 519// transferFocus(); 520 _editor.mouseExited(new JmriMouseEvent(this, e.getID(), e.getWhen(), e.getModifiersEx(), 521 e.getX() + this.getX(), e.getY() + this.getY(), 522 e.getClickCount(), e.isPopupTrigger())); 523 } 524 525 @Override 526 public void mouseEntered(JmriMouseEvent e) { 527 _editor.mouseEntered(new JmriMouseEvent(this, e.getID(), e.getWhen(), e.getModifiersEx(), 528 e.getX() + this.getX(), e.getY() + this.getY(), 529 e.getClickCount(), e.isPopupTrigger())); 530 } 531 532 @Override 533 public void mouseMoved(JmriMouseEvent e) { 534 _editor.mouseMoved(new JmriMouseEvent(this, e.getID(), e.getWhen(), e.getModifiersEx(), 535 e.getX() + this.getX(), e.getY() + this.getY(), 536 e.getClickCount(), e.isPopupTrigger())); 537 } 538 539 @Override 540 public void mouseDragged(JmriMouseEvent e) { 541 _editor.mouseDragged(new JmriMouseEvent(this, e.getID(), e.getWhen(), e.getModifiersEx(), 542 e.getX() + this.getX(), e.getY() + this.getY(), 543 e.getClickCount(), e.isPopupTrigger())); 544 } 545 546 /** 547 * ************************************************************ 548 */ 549 PositionablePopupUtil _popupUtil; 550 551 @Override 552 public void setPopupUtility(PositionablePopupUtil tu) { 553 _popupUtil = tu; 554 } 555 556 @Override 557 public PositionablePopupUtil getPopupUtility() { 558 return _popupUtil; 559 } 560 561 /** 562 * Update the AWT and Swing size information due to change in internal 563 * state, e.g. if one or more of the icons that might be displayed is 564 * changed 565 */ 566 @Override 567 public void updateSize() { 568 invalidate(); 569 setSize(maxWidth(), maxHeight()); 570 if (log.isTraceEnabled()) { 571 // the following fails when run on Jenkins under Xvfb with an NPE in non-JMRI code 572 log.trace("updateSize: {}, text: w={} h={}", 573 _popupUtil.toString(), 574 getFontMetrics(_popupUtil.getFont()).stringWidth(_popupUtil.getText()), 575 getFontMetrics(_popupUtil.getFont()).getHeight()); 576 } 577 validate(); 578 repaint(); 579 } 580 581 @Override 582 public int maxWidth() { 583 int max = 0; 584 if (_popupUtil != null) { 585 if (_popupUtil.getFixedWidth() != 0) { 586 max = _popupUtil.getFixedWidth(); 587 max += _popupUtil.getMargin() * 2; 588 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 589 _popupUtil.setFixedWidth(PositionablePopupUtil.MIN_SIZE); 590 max = PositionablePopupUtil.MIN_SIZE; 591 } 592 } else { 593 max = getPreferredSize().width; 594 /* 595 if(_popupUtil._textComponent instanceof javax.swing.JTextField) { 596 javax.swing.JTextField text = (javax.swing.JTextField)_popupUtil._textComponent; 597 max = getFontMetrics(text.getFont()).stringWidth(text.getText()); 598 } */ 599 max += _popupUtil.getMargin() * 2; 600 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 601 max = PositionablePopupUtil.MIN_SIZE; 602 } 603 } 604 } 605 log.debug("maxWidth= {} preferred width= {}", max, getPreferredSize().width); 606 return max; 607 } 608 609 @Override 610 public int maxHeight() { 611 int max = 0; 612 if (_popupUtil != null) { 613 if (_popupUtil.getFixedHeight() != 0) { 614 max = _popupUtil.getFixedHeight(); 615 max += _popupUtil.getMargin() * 2; 616 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 617 _popupUtil.setFixedHeight(PositionablePopupUtil.MIN_SIZE); 618 max = PositionablePopupUtil.MIN_SIZE; 619 } 620 } else { 621 max = getPreferredSize().height; 622 /* 623 if(_popupUtil._textComponent!=null) { 624 max = getFontMetrics(_popupUtil._textComponent.getFont()).getHeight(); 625 } */ 626 if (_popupUtil != null) { 627 max += _popupUtil.getMargin() * 2; 628 } 629 if (max < PositionablePopupUtil.MIN_SIZE) { // don't let item disappear 630 max = PositionablePopupUtil.MIN_SIZE; 631 } 632 } 633 } 634 log.debug("maxHeight= {} preferred width= {}", max, getPreferredSize().height); 635 return max; 636 } 637 638 @Override 639 public jmri.NamedBean getNamedBean() { 640 return null; 641 } 642 643 /** {@inheritDoc} */ 644 @Override 645 public LogixNG getLogixNG() { 646 return _logixNG; 647 } 648 649 /** {@inheritDoc} */ 650 @Override 651 public void setLogixNG(LogixNG logixNG) { 652 this._logixNG = logixNG; 653 } 654 655 /** {@inheritDoc} */ 656 @Override 657 public void setLogixNG_SystemName(String systemName) { 658 this._logixNG_SystemName = systemName; 659 } 660 661 /** {@inheritDoc} */ 662 @Override 663 public void setupLogixNG() { 664 _logixNG = InstanceManager.getDefault(LogixNG_Manager.class) 665 .getBySystemName(_logixNG_SystemName); 666 if (_logixNG == null) { 667 throw new RuntimeException(String.format( 668 "LogixNG %s is not found for positional %s in panel %s", 669 _logixNG_SystemName, getNameString(), getEditor().getName())); 670 } 671 _logixNG.setInlineLogixNG(this); 672 } 673 674 private final static Logger log = LoggerFactory.getLogger(PositionableJPanel.class); 675}