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}