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