source: subversion/applications/editors/josm/plugins/buildings_tools/src/org/openstreetmap/josm/plugins/buildings_tools/DrawBuildingAction.java

Last change on this file was 34968, checked in by gerdp, 2 weeks ago

fix #17569: building_tools crashes when drawing circular building near 180 longitude

  • use method Node.isOutSideWorld() instead of LatLon?.isOutSideWorld() (requires JOSM r14960)
  • improve code which tries to find an address node below the building to work also with circular buildings
  • fix some SonarLint? inssues Use "==" to perform this enum comparison instead of using "equals"
File size: 17.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins.buildings_tools;
3
4import static org.openstreetmap.josm.plugins.buildings_tools.BuildingsToolsPlugin.latlon2eastNorth;
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.BasicStroke;
9import java.awt.Color;
10import java.awt.Cursor;
11import java.awt.EventQueue;
12import java.awt.Graphics2D;
13import java.awt.Point;
14import java.awt.RenderingHints;
15import java.awt.Toolkit;
16import java.awt.event.KeyEvent;
17import java.awt.event.MouseEvent;
18import java.awt.geom.GeneralPath;
19import java.awt.image.BufferedImage;
20import java.util.Collection;
21import java.util.LinkedList;
22import java.util.Map.Entry;
23
24import org.openstreetmap.josm.actions.mapmode.MapMode;
25import org.openstreetmap.josm.data.Bounds;
26import org.openstreetmap.josm.data.coor.EastNorth;
27import org.openstreetmap.josm.data.osm.DataSelectionListener;
28import org.openstreetmap.josm.data.osm.Node;
29import org.openstreetmap.josm.data.osm.OsmPrimitive;
30import org.openstreetmap.josm.data.osm.Way;
31import org.openstreetmap.josm.data.osm.WaySegment;
32import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
33import org.openstreetmap.josm.data.preferences.NamedColorProperty;
34import org.openstreetmap.josm.gui.MainApplication;
35import org.openstreetmap.josm.gui.MapFrame;
36import org.openstreetmap.josm.gui.MapView;
37import org.openstreetmap.josm.gui.layer.Layer;
38import org.openstreetmap.josm.gui.layer.MapViewPaintable;
39import org.openstreetmap.josm.gui.layer.OsmDataLayer;
40import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
41import org.openstreetmap.josm.gui.util.ModifierExListener;
42import org.openstreetmap.josm.tools.Geometry;
43import org.openstreetmap.josm.tools.ImageProvider;
44import org.openstreetmap.josm.tools.Logging;
45import org.openstreetmap.josm.tools.Shortcut;
46
47public class DrawBuildingAction extends MapMode implements MapViewPaintable, DataSelectionListener,
48        KeyPressReleaseListener, ModifierExListener {
49    private enum Mode {
50        None, Drawing, DrawingWidth, DrawingAngFix
51    }
52
53    private final Cursor cursorCrosshair;
54    private final Cursor cursorJoinNode;
55    private final Cursor cursorJoinWay;
56    private Cursor currCursor;
57    private Cursor customCursor;
58
59    private Mode mode = Mode.None;
60    private Mode nextMode = Mode.None;
61
62    private Color selectedColor = Color.red;
63    private Point drawStartPos;
64    private Point mousePos;
65
66    final transient Building building = new Building();
67
68    public DrawBuildingAction() {
69        super(tr("Draw buildings"), "building", tr("Draw buildings"),
70                Shortcut.registerShortcut("mapmode:buildings",
71                        tr("Mode: {0}", tr("Draw buildings")),
72                        KeyEvent.VK_B, Shortcut.DIRECT),
73                getCursor());
74
75        cursorCrosshair = getCursor();
76        cursorJoinNode = ImageProvider.getCursor("crosshair", "joinnode");
77        cursorJoinWay = ImageProvider.getCursor("crosshair", "joinway");
78        currCursor = cursorCrosshair;
79    }
80
81    private static Cursor getCursor() {
82        try {
83            if (ToolSettings.Shape.CIRCLE == ToolSettings.getShape()) {
84                return ImageProvider.getCursor("crosshair", "silo");
85            } else {
86                return ImageProvider.getCursor("crosshair", "building");
87            }
88        } catch (Exception e) {
89            Logging.error(e);
90        }
91        return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
92    }
93
94    /**
95     * Displays the given cursor instead of the normal one.
96     *
97     * @param c One of the available cursors
98     */
99    private void setCursor(final Cursor c) {
100        if (currCursor.equals(c))
101            return;
102        try {
103            // We invoke this to prevent strange things from happening
104            EventQueue.invokeLater(() -> {
105                MapFrame map = MainApplication.getMap();
106                // Don't change cursor when mode has changed already
107                if (!(map.mapMode instanceof DrawBuildingAction))
108                    return;
109                map.mapView.setCursor(c);
110            });
111            currCursor = c;
112        } catch (Exception e) {
113            Logging.error(e);
114        }
115    }
116
117    private void showAddrDialog(Way w) {
118        AddressDialog dlg = new AddressDialog();
119        if (!alt) {
120            dlg.showDialog();
121            if (dlg.getValue() != 1)
122                return;
123        }
124        dlg.saveValues();
125        String tmp = dlg.getHouseNum();
126        if (tmp != null && !tmp.isEmpty())
127            w.put("addr:housenumber", tmp);
128        tmp = dlg.getStreetName();
129        if (tmp != null && !tmp.isEmpty())
130            w.put("addr:street", tmp);
131    }
132
133    @Override
134    public void enterMode() {
135        super.enterMode();
136        MapFrame map = MainApplication.getMap();
137        if (getLayerManager().getEditDataSet() == null) {
138            map.selectSelectTool(false);
139            return;
140        }
141        selectedColor = new NamedColorProperty(marktr("selected"), selectedColor).get();
142        currCursor = cursorCrosshair;
143        map.mapView.addMouseListener(this);
144        map.mapView.addMouseMotionListener(this);
145        map.mapView.addTemporaryLayer(this);
146        map.keyDetector.addKeyListener(this);
147        map.keyDetector.addModifierExListener(this);
148        SelectionEventManager.getInstance().addSelectionListener(this);
149        updateSnap(getLayerManager().getEditDataSet().getSelected());
150    }
151
152    @Override
153    public void exitMode() {
154        super.exitMode();
155        MapFrame map = MainApplication.getMap();
156        map.mapView.removeMouseListener(this);
157        map.mapView.removeMouseMotionListener(this);
158        map.mapView.removeTemporaryLayer(this);
159        map.keyDetector.removeKeyListener(this);
160        map.keyDetector.removeModifierExListener(this);
161        SelectionEventManager.getInstance().removeSelectionListener(this);
162        if (mode != Mode.None)
163            map.mapView.repaint();
164        mode = Mode.None;
165    }
166
167    public final void cancelDrawing() {
168        mode = Mode.None;
169        MapFrame map = MainApplication.getMap();
170        if (map == null || map.mapView == null)
171            return;
172        map.statusLine.setHeading(-1);
173        map.statusLine.setAngle(-1);
174        building.reset();
175        map.mapView.repaint();
176        updateStatusLine();
177    }
178
179    @Override
180    public void modifiersExChanged(int modifiers) {
181        boolean oldCtrl = ctrl;
182        boolean oldShift = shift;
183        updateKeyModifiersEx(modifiers);
184        if (ctrl != oldCtrl || shift != oldShift) {
185            processMouseEvent(null);
186            updCursor();
187            if (mode != Mode.None)
188                MainApplication.getMap().mapView.repaint();
189        }
190    }
191
192    @Override
193    public void doKeyPressed(KeyEvent e) {
194        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
195            if (mode != Mode.None)
196                e.consume();
197
198            cancelDrawing();
199        }
200    }
201
202    @Override
203    public void doKeyReleased(KeyEvent e) {
204    }
205
206    private EastNorth getEastNorth() {
207        if (!ctrl) {
208            Node n = MainApplication.getMap().mapView.getNearestNode(mousePos, OsmPrimitive::isUsable);
209            if (n != null)
210                return latlon2eastNorth(n.getCoor());
211            WaySegment ws = MainApplication.getMap().mapView.getNearestWaySegment(mousePos,
212                    OsmPrimitive::isSelectable);
213            if (ws != null && ws.way.get("building") != null) {
214                EastNorth p1 = latlon2eastNorth(ws.getFirstNode().getCoor());
215                EastNorth p2 = latlon2eastNorth(ws.getSecondNode().getCoor());
216                EastNorth enX = Geometry.closestPointToSegment(p1, p2,
217                        latlon2eastNorth(MainApplication.getMap().mapView.getLatLon(mousePos.x, mousePos.y)));
218                if (enX != null) {
219                    return enX;
220                }
221            }
222        }
223        return latlon2eastNorth(MainApplication.getMap().mapView.getLatLon(mousePos.x, mousePos.y));
224    }
225
226    private boolean isRectDrawing() {
227        return building.isRectDrawing() && (!shift || ToolSettings.isBBMode())
228                && ToolSettings.Shape.RECTANGLE == ToolSettings.getShape();
229    }
230
231    private Mode modeDrawing() {
232        EastNorth p = getEastNorth();
233        if (isRectDrawing()) {
234            building.setPlaceRect(p);
235            return shift ? Mode.DrawingAngFix : Mode.None;
236        } else if (ToolSettings.Shape.CIRCLE == ToolSettings.getShape()) {
237            if (ToolSettings.getWidth() != 0) {
238                building.setPlaceCircle(p, ToolSettings.getWidth(), shift);
239            } else {
240                building.setPlace(p, ToolSettings.getWidth(), ToolSettings.getLenStep(), shift);
241            }
242            MainApplication.getMap().statusLine.setDist(building.getLength());
243            this.nextMode = Mode.None;
244            return this.nextMode;
245        } else {
246            building.setPlace(p, ToolSettings.getWidth(), ToolSettings.getLenStep(), shift);
247            MainApplication.getMap().statusLine.setDist(building.getLength());
248            this.nextMode = ToolSettings.getWidth() == 0 ? Mode.DrawingWidth : Mode.None;
249            return this.nextMode;
250        }
251    }
252
253    private Mode modeDrawingWidth() {
254        building.setWidth(getEastNorth());
255        MainApplication.getMap().statusLine.setDist(Math.abs(building.getWidth()));
256        return Mode.None;
257    }
258
259    private Mode modeDrawingAngFix() {
260        building.angFix(getEastNorth());
261        return Mode.None;
262    }
263
264    private void processMouseEvent(MouseEvent e) {
265        if (e != null) {
266            mousePos = e.getPoint();
267            updateKeyModifiers(e);
268        }
269        if (mode == Mode.None) {
270            nextMode = Mode.None;
271            return;
272        }
273
274        if (mode == Mode.Drawing) {
275            nextMode = modeDrawing();
276        } else if (mode == Mode.DrawingWidth) {
277            nextMode = modeDrawingWidth();
278        } else if (mode == Mode.DrawingAngFix) {
279            nextMode = modeDrawingAngFix();
280        } else {
281            throw new AssertionError("Invalid drawing mode");
282        }
283    }
284
285    @Override
286    public void paint(Graphics2D g, MapView mv, Bounds bbox) {
287        if (mode == Mode.None || building.getLength() == 0) {
288            return;
289        }
290
291        g.setColor(selectedColor);
292        g.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
293
294        building.paint(g, mv);
295
296        g.setStroke(new BasicStroke(1));
297
298    }
299
300    private void drawingStart(MouseEvent e) {
301        mousePos = e.getPoint();
302        drawStartPos = mousePos;
303        EastNorth en = getEastNorth();
304        building.setBase(en);
305        mode = Mode.Drawing;
306        updateStatusLine();
307    }
308
309    private void drawingAdvance(MouseEvent e) {
310        processMouseEvent(e);
311        if (this.mode != Mode.None && this.nextMode == Mode.None) {
312            drawingFinish();
313        } else {
314            mode = this.nextMode;
315            updateStatusLine();
316        }
317    }
318
319    private void drawingFinish() {
320        if (building.getLength() != 0) {
321            Way w;
322            if (ToolSettings.Shape.CIRCLE == ToolSettings.getShape()) {
323                w = building.createCircle();
324            } else {
325                w = building.createRectangle(ctrl);
326            }
327            if (w != null) {
328                if (!alt || ToolSettings.isUsingAddr())
329                    for (Entry<String, String> kv : ToolSettings.getTags().entrySet()) {
330                        w.put(kv.getKey(), kv.getValue());
331                    }
332                if (ToolSettings.isUsingAddr())
333                    showAddrDialog(w);
334                if (ToolSettings.isAutoSelect()
335                        && (getLayerManager().getEditDataSet().getSelected().isEmpty() || shift)) {
336                    getLayerManager().getEditDataSet().setSelected(w);
337                }
338            }
339        }
340        cancelDrawing();
341    }
342
343    @Override
344    public void mousePressed(MouseEvent e) {
345        if (e.getButton() != MouseEvent.BUTTON1)
346            return;
347        if (!MainApplication.getMap().mapView.isActiveLayerDrawable())
348            return;
349
350        requestFocusInMapView();
351
352        if (mode == Mode.None)
353            drawingStart(e);
354    }
355
356    @Override
357    public void mouseDragged(MouseEvent e) {
358        processMouseEvent(e);
359        updCursor();
360        if (mode != Mode.None)
361            MainApplication.getMap().mapView.repaint();
362    }
363
364    @Override
365    public void mouseReleased(MouseEvent e) {
366        if (e.getButton() != MouseEvent.BUTTON1)
367            return;
368        if (!MainApplication.getMap().mapView.isActiveLayerDrawable())
369            return;
370        boolean dragged = true;
371        if (drawStartPos != null)
372            dragged = e.getPoint().distance(drawStartPos) > 10;
373        drawStartPos = null;
374
375        if (mode == Mode.Drawing && !dragged)
376            return;
377        if (mode == Mode.None)
378            return;
379
380        drawingAdvance(e);
381    }
382
383    private void updCursor() {
384        if (mousePos == null)
385            return;
386        if (!MainApplication.isDisplayingMapView())
387            return;
388        Node n = null;
389        if (!ctrl) {
390            n = MainApplication.getMap().mapView.getNearestNode(mousePos, OsmPrimitive::isSelectable);
391            if (n != null) {
392                setCursor(cursorJoinNode);
393                return;
394            } else {
395                Way w = MainApplication.getMap().mapView.getNearestWay(mousePos, OsmPrimitive::isSelectable);
396                if (w != null && w.get("building") != null) {
397                    setCursor(cursorJoinWay);
398                    return;
399                }
400            }
401        }
402        if (customCursor != null && (!ctrl || isRectDrawing()))
403            setCursor(customCursor);
404        else
405            setCursor(getCursor());
406
407    }
408
409    @Override
410    public void mouseMoved(MouseEvent e) {
411        if (!MainApplication.getMap().mapView.isActiveLayerDrawable())
412            return;
413        processMouseEvent(e);
414        updCursor();
415        if (mode != Mode.None)
416            MainApplication.getMap().mapView.repaint();
417    }
418
419    @Override
420    public String getModeHelpText() {
421        if (mode == Mode.None)
422            return tr("Point on the corner of the building to start drawing");
423        if (mode == Mode.Drawing)
424            return tr("Point on opposite end of the building");
425        if (mode == Mode.DrawingWidth)
426            return tr("Set width of the building");
427        return "";
428    }
429
430    @Override
431    public boolean layerIsSupported(Layer l) {
432        return l instanceof OsmDataLayer;
433    }
434
435    public final void updateSnap(Collection<? extends OsmPrimitive> newSelection) {
436        building.clearAngleSnap();
437        // update snap only if selection isn't too big
438        if (newSelection.size() <= 10) {
439            LinkedList<Node> nodes = new LinkedList<>();
440            LinkedList<Way> ways = new LinkedList<>();
441
442            for (OsmPrimitive p : newSelection) {
443                switch (p.getType()) {
444                case NODE:
445                    nodes.add((Node) p);
446                    break;
447                case WAY:
448                    ways.add((Way) p);
449                    break;
450                default:
451                    break;
452                }
453            }
454
455            building.addAngleSnap(nodes.toArray(new Node[0]));
456            for (Way w : ways) {
457                building.addAngleSnap(w);
458            }
459        }
460        updateCustomCursor();
461    }
462
463    private void updateCustomCursor() {
464        Double angle = building.getDrawingAngle();
465        if (angle == null || !ToolSettings.isSoftCursor()) {
466            customCursor = null;
467            return;
468        }
469        final int R = 9; // crosshair outer radius
470        final int r = 3; // crosshair inner radius
471        BufferedImage img = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
472        Graphics2D g = img.createGraphics();
473
474        GeneralPath b = new GeneralPath();
475        b.moveTo(16 - Math.cos(angle) * R, 16 - Math.sin(angle) * R);
476        b.lineTo(16 - Math.cos(angle) * r, 16 - Math.sin(angle) * r);
477        b.moveTo(16 + Math.cos(angle) * R, 16 + Math.sin(angle) * R);
478        b.lineTo(16 + Math.cos(angle) * r, 16 + Math.sin(angle) * r);
479        b.moveTo(16 + Math.sin(angle) * R, 16 - Math.cos(angle) * R);
480        b.lineTo(16 + Math.sin(angle) * r, 16 - Math.cos(angle) * r);
481        b.moveTo(16 - Math.sin(angle) * R, 16 + Math.cos(angle) * R);
482        b.lineTo(16 - Math.sin(angle) * r, 16 + Math.cos(angle) * r);
483
484        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
485        g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
486
487        g.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
488        g.setColor(Color.WHITE);
489        g.draw(b);
490
491        g.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
492        g.setColor(Color.BLACK);
493        g.draw(b);
494
495        customCursor = Toolkit.getDefaultToolkit().createCustomCursor(img, new Point(16, 16), "custom crosshair");
496
497        updCursor();
498    }
499
500    @Override
501    public void selectionChanged(SelectionChangeEvent event) {
502        updateSnap(event.getSelection());
503    }
504}
Note: See TracBrowser for help on using the repository browser.