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}