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}