001package jmri.jmrit.display.controlPanelEditor.shape;
002
003import java.awt.Color;
004import java.awt.Graphics2D;
005import java.awt.Point;
006import java.awt.Rectangle;
007import java.awt.Shape;
008import java.awt.event.ActionEvent;
009import java.awt.geom.GeneralPath;
010import java.awt.geom.PathIterator;
011import java.util.ArrayList;
012
013import javax.swing.JPopupMenu;
014
015import jmri.jmrit.display.Editor;
016import jmri.jmrit.display.Positionable;
017import jmri.util.swing.JmriMouseEvent;
018
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022/**
023 * @author Pete cresman Copyright (c) 2013
024 */
025public class PositionablePolygon extends PositionableShape {
026
027    private ArrayList<Rectangle> _vertexHandles;
028    private boolean _editing = false;   // during popUp or create, allows override of drawHandles etc.
029//    protected boolean _isClosed;
030
031    // there is no default PositionablePolygon
032    private PositionablePolygon(Editor editor) {
033        super(editor);
034    }
035
036    public PositionablePolygon(Editor editor, Shape shape) {
037        super(editor, shape);
038    }
039
040    @Override
041    public Positionable deepClone() {
042        PositionablePolygon pos = new PositionablePolygon(_editor);
043        return finishClone(pos);
044    }
045
046    @Override
047    protected Positionable finishClone(PositionableShape pos) {
048        GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
049        path.append(getPathIterator(null), false);
050        /*
051         PathIterator iter = _shape.getPathIterator(null);
052         float[] coord = new float[6];
053         while (!iter.isDone()) {
054         int type = iter.currentSegment(coord);
055         switch (type) {
056         case PathIterator.SEG_MOVETO:
057         path.moveTo(coord[0], coord[1]);
058         break;
059         case PathIterator.SEG_LINETO:
060         path.lineTo(coord[0], coord[1]);
061         break;
062         case PathIterator.SEG_QUADTO:
063         path.quadTo(coord[0], coord[1], coord[2], coord[3]);
064         break;
065         case PathIterator.SEG_CUBICTO:
066         path.curveTo(coord[0], coord[1], coord[2], coord[3], coord[4], coord[53]);
067         break;
068         case PathIterator.SEG_CLOSE:
069         path.closePath();
070         break;
071         }
072         }
073         */
074        pos.setShape(path);
075        return super.finishClone(pos);
076    }
077
078    protected void editing(boolean edit) {
079        _editing = edit;
080        log.debug("set _editing = {}", _editing);
081    }
082
083    @Override
084    public boolean setEditItemMenu(JPopupMenu popup) {
085        String txt = Bundle.getMessage("editShape", Bundle.getMessage("Polygon"));
086        popup.add(new javax.swing.AbstractAction(txt) {
087            @Override
088            public void actionPerformed(ActionEvent e) {
089                makeEditFrame(false);
090            }
091        });
092        return true;
093    }
094
095    @Override
096    protected DrawFrame makeEditFrame(boolean create) {
097        _editFrame = new DrawPolygon("editShape", "Polygon", this, getEditor(), create);
098        _editFrame.setDisplayParams(this);
099        return _editFrame;
100    }
101
102    @Override
103    public void removeHandles() {
104        _vertexHandles = null;
105        super.removeHandles();
106    }
107
108    @Override
109    public void drawHandles() {
110        if (_editing) {
111            _vertexHandles = new ArrayList<>();
112            PathIterator iter = getPathIterator(null);
113            float[] coord = new float[6];
114            while (!iter.isDone()) {
115                iter.currentSegment(coord);
116                int x = Math.round(coord[0]);
117                int y = Math.round(coord[1]);
118                _vertexHandles.add(new Rectangle(x - SIZE, y - SIZE, 2 * SIZE, 2 * SIZE));
119                iter.next();
120            }
121        } else {
122            super.drawHandles();
123        }
124    }
125
126    @Override
127    public void doMousePressed(JmriMouseEvent event) {
128        _hitIndex = -1;
129        if (!_editor.isEditable()) {
130            return;
131        }
132        if (_editing) {
133            if (_vertexHandles != null) {
134                _lastX = event.getX();
135                _lastY = event.getY();
136                int x = _lastX - getX();//-SIZE/2;
137                int y = _lastY - getY();//-SIZE/2;
138                Point pt;
139                try {
140                    pt = getInversePoint(x, y);
141                } catch (java.awt.geom.NoninvertibleTransformException nte) {
142                    log.error("Can't locate Hit Rectangles {}", nte.getMessage());
143                    return;
144                }
145                for (int i = 0; i < _vertexHandles.size(); i++) {
146                    if (_vertexHandles.get(i).contains(pt.x, pt.y)) {
147                        _hitIndex = i;
148                    }
149                }
150            }
151            log.debug("doMousePressed _editing = {}, _hitIndex= {}", _editing, _hitIndex);
152        } else {
153            super.doMousePressed(event);
154        }
155    }
156
157    @Override
158    protected boolean doHandleMove(JmriMouseEvent event) {
159        if (_hitIndex >= 0 && _editor.isEditable()) {
160            if (_editing) {
161                Point pt = new Point(event.getX() - _lastX, event.getY() - _lastY);
162                Rectangle rect = _vertexHandles.get(_hitIndex);
163                rect.x += pt.x;
164                rect.y += pt.y;
165                DrawPolygon editFrame = (DrawPolygon) getEditFrame();
166                if (editFrame != null) {
167                    if (event.getX() - getX() < 0) {
168                        _editor.moveItem(this, event.getX() - getX(), 0);
169                    } else if (isLeftMost(rect.x)) {
170                        _editor.moveItem(this, event.getX() - _lastX, 0);
171                    }
172                    if (event.getY() - getY() < 0) {
173                        _editor.moveItem(this, 0, event.getY() - getY());
174                    } else if (isTopMost(rect.y)) {
175                        _editor.moveItem(this, 0, event.getY() - _lastY);
176                    }
177
178                    editFrame.doHandleMove(_hitIndex, pt);
179                }
180                _lastX = event.getX();
181                _lastY = event.getY();
182            } else {
183                float deltaX = event.getX() - _lastX;
184                float deltaY = event.getY() - _lastY;
185                float width = _width;
186                float height = _height;
187                if (_height < SIZE || _width < SIZE) {
188                    log.error("Bad size _width= {}, _height= {}", _width, _height);
189                }
190                GeneralPath path = null;
191                switch (_hitIndex) {
192                    case TOP:
193                        if (height - deltaY > SIZE) {
194                            path = scale(1, (height - deltaY) / height);
195                            _editor.moveItem(this, 0, (int) deltaY);
196                        } else {
197                            path = scale(1, SIZE / height);
198                            _editor.moveItem(this, 0, _height - SIZE);
199                        }
200                        break;
201                    case RIGHT:
202                        path = scale(Math.max(SIZE / width, (width + deltaX) / width), 1);
203                        break;
204                    case BOTTOM:
205                        path = scale(1, Math.max(SIZE / height, (height + deltaY) / height));
206                        break;
207                    case LEFT:
208                        if (_width - deltaX > SIZE) {
209                            path = scale((width - deltaX) / width, 1);
210                            _editor.moveItem(this, (int) deltaX, 0);
211                        } else {
212                            path = scale(SIZE / width, 1);
213                            _editor.moveItem(this, _width - SIZE, 0);
214                        }
215                        break;
216                    default:
217                        log.warn("Unhandled direction code: {}", _hitIndex);
218                }
219                if (path != null) {
220                    setShape(path);
221                }
222            }
223            drawHandles();
224            repaint();
225            updateSize();
226            _lastX = event.getX();
227            _lastY = event.getY();
228            log.debug("doHandleMove _editing = {}, _hitIndex= {}", _editing, _hitIndex);
229            return true;
230        }
231        return false;
232    }
233
234    private boolean isLeftMost(int x) {
235        for (Rectangle vertexHandle : _vertexHandles) {
236            if (vertexHandle.x < x) {
237                return false;
238            }
239        }
240        return true;
241    }
242
243    private boolean isTopMost(int y) {
244        for (Rectangle vertexHandle : _vertexHandles) {
245            if (vertexHandle.y < y) {
246                return false;
247            }
248        }
249        return true;
250    }
251
252    private GeneralPath scale(float ratioX, float ratioY) {
253//     log.info("scale("+ratioX+" , "+ratioY+")");
254        GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
255        PathIterator iter = getPathIterator(null);
256        float[] coord = new float[6];
257        while (!iter.isDone()) {
258            int type = iter.currentSegment(coord);
259            switch (type) {
260                case PathIterator.SEG_MOVETO:
261                    path.moveTo(coord[0] * ratioX, coord[1] * ratioY);
262                    break;
263                case PathIterator.SEG_LINETO:
264                    path.lineTo(coord[0] * ratioX, coord[1] * ratioY);
265                    break;
266                case PathIterator.SEG_QUADTO:
267                    path.quadTo(coord[0], coord[1], coord[2], coord[3]);
268                    break;
269                case PathIterator.SEG_CUBICTO:
270                    path.curveTo(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]);
271                    break;
272                case PathIterator.SEG_CLOSE:
273                    path.closePath();
274                    break;
275                default:
276                    log.warn("Unhandled path iterator type: {}", type);
277                    break;
278            }
279//      log.debug("type= "+type+"  x= "+coord[0]+", y= "+ coord[1]);
280            iter.next();
281        }
282        return path;
283    }
284
285    @Override
286    protected void paintHandles(Graphics2D g2d) {
287        if (_editing) {
288            if (_vertexHandles != null) {
289                g2d.setStroke(new java.awt.BasicStroke(2.0f));
290                for (Rectangle rect : _vertexHandles) {
291                    g2d.setColor(Color.BLUE);
292                    g2d.fill(rect);
293                    g2d.setColor(Editor.HIGHLIGHT_COLOR);
294                    g2d.draw(rect);
295                }
296                if (_hitIndex >= 0) {
297                    Rectangle rect = _vertexHandles.get(_hitIndex);
298                    g2d.setColor(Color.RED);
299                    g2d.fill(rect);
300                    g2d.draw(rect);
301                }
302            }
303        } else {
304            super.paintHandles(g2d);
305        }
306    }
307
308    @Override
309    protected void invalidateShape() {
310        // do nothing to prevent PositionableShape from invalidating this path
311    }
312
313    @Override
314    protected Shape makeShape() {
315        // return an empty shape so it can be appended to
316        return new GeneralPath(GeneralPath.WIND_EVEN_ODD);
317    }
318
319    private final static Logger log = LoggerFactory.getLogger(PositionablePolygon.class);
320}