001package jmri.jmrit.display.controlPanelEditor.shape;
002
003import java.awt.Color;
004import java.awt.FlowLayout;
005import java.awt.GraphicsDevice;
006import java.awt.Point;
007import java.awt.Rectangle;
008import java.awt.event.ActionEvent;
009
010import javax.swing.Box;
011import javax.swing.BoxLayout;//
012import javax.swing.ButtonGroup;
013import javax.swing.JButton;
014import javax.swing.JColorChooser;
015import javax.swing.JComboBox;
016import javax.swing.JComponent;
017import javax.swing.JLabel;
018import javax.swing.JPanel;
019import javax.swing.JRadioButton;
020import javax.swing.JScrollPane;
021import javax.swing.JSlider;
022import javax.swing.JTextField;
023import javax.swing.SwingConstants;
024import javax.swing.event.ChangeEvent;
025
026import jmri.InstanceManager;
027import jmri.Sensor;
028import jmri.SensorManager;
029import jmri.NamedBean.DisplayOptions;
030import jmri.jmrit.display.Editor;
031import jmri.jmrit.display.Positionable;
032import jmri.jmrit.display.controlPanelEditor.ControlPanelEditor;
033import jmri.swing.NamedBeanComboBox;
034import jmri.util.ThreadingUtil;
035import jmri.util.swing.JmriColorChooser;
036import jmri.util.swing.JmriJOptionPane;
037
038/**
039 * Frame to create/edit a Control Panel shape PositionableShape object.
040 *
041 * @author Pete Cressman Copyright (c) 2012
042 */
043abstract class DrawFrame extends jmri.util.JmriJFrame {
044
045    private final Editor _editor;
046    protected PositionableShape _shape;       // for use while editing
047    private PositionableShape _originalShape; // saved for use if cancelled
048    protected boolean _create;
049
050    int _lineWidth;
051    private Color _lineColor;
052    private Color _fillColor;
053    private JColorChooser _chooser;
054    private JRadioButton _lineColorButon;
055    private JRadioButton _fillColorButon;
056    private JSlider _lineSlider;
057    private JSlider _alphaSlider;
058    private final transient NamedBeanComboBox<Sensor> _sensorBox = new NamedBeanComboBox<>(
059        InstanceManager.getDefault(SensorManager.class), null, DisplayOptions.DISPLAYNAME);
060    private JRadioButton _hideShape;
061    private JRadioButton _changeLevel;
062    private JComboBox<String> _levelComboBox;
063    private final JPanel _contentPanel;
064
065    protected DrawFrame(String which, String title, PositionableShape ps, Editor ed, boolean create) {
066        super(false, false);
067        _shape = ps;
068        _editor = ed;
069        _create = create;
070        setTitle(Bundle.getMessage(which, Bundle.getMessage(title)));
071
072        _lineWidth = 1;
073        _lineColor = Color.black;
074
075        _contentPanel = new JPanel();
076        _contentPanel.setLayout(new java.awt.BorderLayout(10, 10));
077        _contentPanel.setLayout(new BoxLayout(_contentPanel, BoxLayout.Y_AXIS));
078
079        if (_shape == null) {
080            _contentPanel.add(makeCreatePanel(title));
081        } else {
082            // closingEvent will re-establish listener
083            _shape.removeListener();            
084        }
085
086        setContentPane(new JScrollPane(_contentPanel));
087
088        addWindowListener(new java.awt.event.WindowAdapter() {
089            @Override
090            public void windowClosing(java.awt.event.WindowEvent e) {
091                closingEvent(true);
092            }
093        });
094        ThreadingUtil.runOnGUI( () -> {
095            super.pack();
096            if (_shape == null) {
097                Point loc = _editor.getLocationOnScreen();
098                loc.x = Math.max(loc.x + 200, 0);
099                loc.y = Math.max(loc.y, 0);
100                setLocation(loc);
101                setVisible(true);
102                setAlwaysOnTop(true);
103            }
104        });
105        //_shape != null finishes construction at setDisplayParams()
106    }
107
108    private static void addLabel(JPanel panel, String text) {
109        JLabel label = new JLabel(text);
110        label.setAlignmentX(JComponent.LEFT_ALIGNMENT);
111        panel.add(label);
112    }
113
114    private JPanel makeCreatePanel(String type) {
115        JPanel panel = new JPanel();
116        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
117        java.awt.Dimension dim = new java.awt.Dimension(250, 8);
118        panel.add(Box.createRigidArea(dim));
119        JPanel p = new JPanel();
120        p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
121        p.add(Box.createHorizontalStrut(10));
122        JPanel pp = new JPanel();
123        pp.setLayout(new BoxLayout(pp, BoxLayout.PAGE_AXIS));
124        if (type != null && type.equals("Polygon")) {
125            addLabel(pp, Bundle.getMessage("drawInstructions2a"));
126            addLabel(pp, Bundle.getMessage("drawInstructions2b"));
127        } else {
128            addLabel(pp, Bundle.getMessage("drawInstructions2", type));
129       }
130        p.add(pp);
131        p.add(Box.createHorizontalStrut(10));
132        panel.add(p);
133        panel.add(Box.createRigidArea(dim));
134        setVisible(false);
135        setUndecorated(true);
136        setBackground(new Color(0.8f, 0.8f, 0.8f, 1.0f));
137        return panel;
138    }
139
140    private JPanel makeEditPanel() {
141        JPanel panel = new JPanel();
142        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
143        JPanel p = new JPanel();
144        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
145        p.add(new JLabel(Bundle.getMessage("lineWidth")));
146        JPanel pp = new JPanel();
147        pp.add(new JLabel(Bundle.getMessage("thin")));
148        _lineSlider = new JSlider(SwingConstants.HORIZONTAL, 1, 30, _lineWidth);
149        _lineSlider.addChangeListener((ChangeEvent e) -> widthChange());
150        pp.add(_lineSlider);
151        pp.add(new JLabel(Bundle.getMessage("thick")));
152        p.add(pp);
153        panel.add(p);
154        p = new JPanel();
155        ButtonGroup bg = new ButtonGroup();
156        _lineColorButon = new JRadioButton(Bundle.getMessage("lineColor"));
157        p.add(_lineColorButon);
158        bg.add(_lineColorButon);
159        _fillColorButon = new JRadioButton(Bundle.getMessage("fillColor"));
160        p.add(_fillColorButon);
161        bg.add(_fillColorButon);
162        _lineColorButon.setSelected(true);
163        panel.add(p);
164        _chooser = new JColorChooser(_lineColor);
165        _chooser.getSelectionModel().addChangeListener((ChangeEvent e) -> colorChange());
166        _chooser.setPreviewPanel(new JPanel());
167        _chooser = JmriColorChooser.extendColorChooser(_chooser);
168        panel.add(_chooser);
169        p = new JPanel();
170        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
171        p.add(new JLabel(Bundle.getMessage("transparency")));
172        pp = new JPanel();
173        pp.add(new JLabel(Bundle.getMessage("transparent")));
174        _alphaSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 255, _lineColor.getAlpha());
175        _alphaSlider.addChangeListener((ChangeEvent e) -> alphaChange());
176        pp.add(_alphaSlider);
177        _lineColorButon.addChangeListener((ChangeEvent e) -> buttonChange());
178        pp.add(new JLabel(Bundle.getMessage("opaque")));
179        p.add(pp);
180        panel.add(p);
181        return panel;
182    }
183
184    protected final JPanel makeSensorPanel() {
185        JPanel panel = new JPanel();
186        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
187        panel.add(new JLabel(Bundle.getMessage("SensorMsg")));
188        panel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("VisibleSensor"))));
189        _sensorBox.setAllowNull(true); // already filled with names of all existing sensors
190        _sensorBox.addActionListener((ActionEvent e) -> {
191            String msg = _shape.setControlSensor(_sensorBox.getSelectedItemDisplayName());
192            log.debug("Setting sensor to {} after action", _sensorBox.getSelectedItemDisplayName());
193            if (msg != null) {
194                JmriJOptionPane.showMessageDialog(panel, msg, Bundle.getMessage("ErrorSensor"),
195                        JmriJOptionPane.INFORMATION_MESSAGE); // NOI18N
196                _sensorBox.setSelectedItem(null);
197            }
198            updateShape();
199        });
200        JPanel p = new JPanel();
201        p.add(_sensorBox);
202        p.add(Box.createVerticalGlue());
203        panel.add(p);
204        panel.add(Box.createVerticalGlue());
205
206        _hideShape = new JRadioButton(Bundle.getMessage("HideOnSensor"));
207        _changeLevel = new JRadioButton(Bundle.getMessage("ChangeLevel"));
208        ButtonGroup bg = new ButtonGroup();
209        bg.add(_hideShape);
210        bg.add(_changeLevel);
211        _levelComboBox = new JComboBox<>();
212        _levelComboBox.addItem(Bundle.getMessage("SameLevel"));
213        for (int i = 1; i < 11; i++) {
214            _levelComboBox.addItem(Bundle.getMessage("Level") + " " + i);
215        }
216        _levelComboBox.addActionListener((ActionEvent evt) -> {
217            int level = _levelComboBox.getSelectedIndex();
218            _shape.setChangeLevel(level);
219        });
220        _hideShape.addActionListener((ActionEvent a) -> {
221            _shape.setHide(true);
222            _levelComboBox.setEnabled(false);
223        });
224        _changeLevel.addActionListener((ActionEvent a) -> {
225            _shape.setHide(false);
226            _levelComboBox.setEnabled(true);
227        });
228        JPanel p1 = new JPanel();
229        p1.setLayout(new BoxLayout(p1, BoxLayout.Y_AXIS));
230        p1.add(_hideShape);
231        p1.add(_changeLevel);
232
233        JPanel p2 = new JPanel();
234        p2.setLayout(new BoxLayout(p2, BoxLayout.Y_AXIS));
235        p = new JPanel();
236        p.add(_levelComboBox);
237        p2.add(p);
238        JPanel p3 = new JPanel();
239        p3.setLayout(new BoxLayout(p3, BoxLayout.X_AXIS));
240        p3.add(p1);
241        p3.add(Box.createHorizontalGlue());
242        p3.add(p2);
243
244        panel.add(Box.createVerticalGlue());
245        panel.add(p3);
246        panel.add(Box.createVerticalGlue());
247        
248        JPanel pp = new JPanel();
249        pp.setLayout(new BoxLayout(pp, BoxLayout.X_AXIS));
250        pp.add(Box.createHorizontalStrut(80));
251        pp.add(Box.createHorizontalGlue());
252        pp.add(panel, java.awt.BorderLayout.CENTER);
253        pp.add(Box.createHorizontalGlue());
254        return pp;
255    }
256
257    /**
258     * Create a panel for setting parameters for the PositionableShape.
259     *
260     * @return a parameters panel
261     */
262    protected JPanel makeParamsPanel() {
263        JPanel panel = new JPanel();
264        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
265        return panel;
266    }
267
268    /**
269     * Set parameters on the popup that will edit the PositionableShape.
270     * Called both for creation and editing by the PositionableShape
271     * @param ps a PositionableShape
272     */
273    protected void setDisplayParams(PositionableShape ps) {
274        if (!_create) {
275            makeCopy(ps);
276        }
277        ShapeDrawer sd = ((ControlPanelEditor)ps.getEditor()).getShapeDrawer();
278        if (!sd.setDrawFrame(this)) {
279            closingEvent(true);
280            return;
281        }
282        _contentPanel.removeAll();
283        JPanel panel = new JPanel();
284        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
285        panel.add(makeEditPanel());
286        panel.add(makeParamsPanel());
287        javax.swing.JTabbedPane tPanel = new javax.swing.JTabbedPane();
288        tPanel.addTab(Bundle.getMessage("attributeTab"), null,
289                panel, Bundle.getMessage("drawInstructions1"));
290        
291        _lineWidth = _shape.getLineWidth();
292        _lineSlider.setValue(_lineWidth);
293        _lineColor = _shape.getLineColor();
294        _fillColor = _shape.getFillColor();
295        int alpha = _lineColor.getAlpha();
296        if (alpha > 5) {
297            _alphaSlider.setValue(alpha);
298            _lineColorButon.setSelected(true);
299            _chooser.setColor(_lineColor);
300        } else {
301            _alphaSlider.setValue(_fillColor.getAlpha());
302            _lineColorButon.setSelected(false);
303            _chooser.setColor(_fillColor);
304        }
305
306        tPanel.addTab(Bundle.getMessage("advancedTab"), null,
307                makeSensorPanel(), Bundle.getMessage("drawInstructions3a"));
308        _sensorBox.setSelectedItem(_shape.getControlSensor());
309        _levelComboBox.setSelectedIndex(_shape.getChangeLevel());
310        if (_shape.isHideOnSensor()) {
311            _hideShape.setSelected(true);
312            _levelComboBox.setEnabled(false);
313        } else {
314            _changeLevel.setSelected(true);
315        }
316        _contentPanel.add(tPanel);
317        _contentPanel.add(makeDoneButtonPanel());
318        pack();
319        InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(_editor, _shape, this);
320        setVisible(true);
321        setAlwaysOnTop(true);
322        if (log.isDebugEnabled()) {
323            Point pt1 = getLocation();
324            GraphicsDevice device = getGraphicsConfiguration().getDevice();
325            log.debug("setDisplayParams Screen device= {}: getLocation()= [{}, {}]",
326                    device.getIDstring(), pt1.x, pt1.y);
327        }
328    }
329
330    /**
331     * Editing an existing shape (only make copy for cancel of edits).
332     *
333     * @param ps shape
334     */
335    private void makeCopy(PositionableShape ps) {
336        // make a copy, but keep it out of editor's content
337        _originalShape = (PositionableShape) ps.deepClone();
338        // cloning adds to editor's targetPane - (maybe fix needed in editor)
339        _originalShape.remove();
340        // closingEvent will re-establish listener
341        _originalShape.removeListener();
342        log.debug("_originalShape made");
343    }
344
345    private JPanel makeDoneButtonPanel() {
346        JPanel panel = new JPanel();
347        panel.setLayout(new FlowLayout());
348//        panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
349        panel.add(Box.createHorizontalGlue());
350        JButton doneButton = new JButton(Bundle.getMessage("ButtonDone"));
351        doneButton.addActionListener((ActionEvent a) -> {
352            closingEvent(false);
353            JmriColorChooser.addRecentColor(_lineColor);
354            if (_fillColor != null) {
355                JmriColorChooser.addRecentColor(_fillColor);
356           }
357        });
358        JPanel p =new JPanel();
359        p.add(doneButton);
360        panel.add(p);
361        panel.add(Box.createHorizontalGlue());
362
363        JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
364        cancelButton.addActionListener((ActionEvent a) -> closingEvent(true));
365        p =new JPanel();
366        p.add(cancelButton);
367        panel.add(p);
368        panel.add(Box.createHorizontalGlue());
369        return panel;
370    }
371
372    private void buttonChange() {
373        if (_lineColorButon.isSelected()) {
374//            _chooser.getSelectionModel().setSelectedColor(_lineColor);
375            _chooser.setColor(_lineColor);
376            _alphaSlider.setValue(_lineColor.getAlpha());
377        } else if (_fillColor != null) {
378//            _chooser.getSelectionModel().setSelectedColor(_fillColor);
379            _chooser.setColor(_fillColor);
380            _alphaSlider.setValue(_fillColor.getAlpha());
381        } else {
382            _alphaSlider.setValue(255);
383        }
384        _alphaSlider.revalidate();
385        _alphaSlider.repaint();
386    }
387
388    private void widthChange() {
389        _lineWidth = _lineSlider.getValue();
390        if (_shape == null) {
391            return;
392        }
393        _shape.setLineWidth(_lineWidth);
394        updateShape();
395    }
396
397    private void colorChange() {
398        Color c = _chooser.getColor();
399        int alpha =  c.getAlpha();
400        log.debug("colorChange: color= {}, alpha= {} ", c, alpha);
401        _alphaSlider.setValue(c.getAlpha());
402        if (_lineColorButon.isSelected()) {
403            _lineColor = new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
404            if (_shape != null) {
405                _shape.setLineColor(_lineColor);
406            }
407        } else {
408            _fillColor = new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
409            if (_shape != null) {
410                _shape.setFillColor(_fillColor);                
411            }
412        }
413        updateShape();
414    }
415
416    private void alphaChange() {
417        int alpha = _alphaSlider.getValue();
418        if (_lineColorButon.isSelected()) {
419            _lineColor = new Color(_lineColor.getRed(), _lineColor.getGreen(), _lineColor.getBlue(), alpha);
420            if (_shape != null) {
421                _shape.setLineColor(_lineColor);
422            }
423        } else if (_fillColorButon.isSelected() && _fillColor != null) {
424            _fillColor = new Color(_fillColor.getRed(), _fillColor.getGreen(), _fillColor.getBlue(), alpha);
425            if (_shape != null) {
426                _shape.setFillColor(_fillColor);
427            }
428        }
429        updateShape();
430    }
431
432    protected void closingEvent(boolean cancel) {
433        log.debug("closingEvent for {}", getTitle());
434        if (_shape != null) {
435            if (cancel) {
436                _shape.remove();
437                if (_originalShape != null) {
438                    try {
439                        _originalShape.getEditor().putItem(_originalShape);
440                    } catch (Positionable.DuplicateIdException e) {
441                        // This should never happen
442                        log.error("Editor.putItem() with has thrown DuplicateIdException", e);
443                    }
444                    _originalShape.setListener();
445                }
446            } else {
447                _shape.setListener();
448                ((ControlPanelEditor)_editor).setShapeSelect(true);
449            }
450            _shape.removeHandles();
451            if(_shape instanceof PositionablePolygon) {
452                ((PositionablePolygon)_shape).editing(false);
453            }
454        }
455        ((ControlPanelEditor)_editor).getShapeDrawer().setDrawFrame(null);
456        _create = false;
457        _shape = null;  // tells ShapeDrawer creation and editing is finished. 
458        dispose();
459    }
460
461    protected int getInteger(JTextField field, int value) {
462        try {
463            int i = Integer.parseInt(field.getText());
464            if (i > 0) {
465                if (i < PositionableShape.SIZE) {
466                    i = PositionableShape.SIZE;
467                }
468                return i;
469            }
470        } catch (NumberFormatException nfe) {
471            JmriJOptionPane.showMessageDialog(this, nfe,
472                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
473        }
474        field.setText(Integer.toString(value));
475        return value;
476    }
477
478    protected void updateShape() {
479        if (_shape == null) {
480            return;
481        }
482        _shape.removeHandles();
483        _shape.drawHandles();
484        _shape.updateSize();
485        _shape.getEditor().getTargetPanel().repaint();
486    }
487
488    // these 2 methods update the JTextfields when mouse moves handles
489    abstract void setDisplayWidth(int w);
490    abstract void setDisplayHeight(int h);
491
492    protected abstract PositionableShape makeFigure(Rectangle r, Editor ed);
493
494    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DrawFrame.class);
495
496}