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