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}