Ticket #2891: JOSM.patch

File JOSM.patch, 32.4 KB (added by Landwirt, 16 years ago)
  • src/org/openstreetmap/josm/actions/mapmode/CircleAction.java

     
     1// License: GPL. Copyright 2007 by Immanuel Scholz and others
     2package org.openstreetmap.josm.actions.mapmode;
     3
     4import static org.openstreetmap.josm.tools.I18n.marktr;
     5import static org.openstreetmap.josm.tools.I18n.tr;
     6
     7import java.awt.BasicStroke;
     8import java.awt.Color;
     9import java.awt.Cursor;
     10import java.awt.Graphics;
     11import java.awt.Graphics2D;
     12import java.awt.Point;
     13import java.awt.event.ActionEvent;
     14import java.awt.event.InputEvent;
     15import java.awt.event.KeyEvent;
     16import java.awt.event.MouseEvent;
     17import java.awt.geom.GeneralPath;
     18import java.util.ArrayList;
     19import java.util.Collection;
     20import java.util.LinkedList;
     21import java.util.List;
     22import java.math.*;
     23
     24import javax.swing.JOptionPane;
     25
     26import org.openstreetmap.josm.Main;
     27import org.openstreetmap.josm.command.AddCommand;
     28import org.openstreetmap.josm.command.ChangeCommand;
     29import org.openstreetmap.josm.command.Command;
     30import org.openstreetmap.josm.command.MoveCommand;
     31import org.openstreetmap.josm.command.RotateCommand;
     32import org.openstreetmap.josm.command.SequenceCommand;
     33import org.openstreetmap.josm.data.coor.EastNorth;
     34import org.openstreetmap.josm.data.osm.Node;
     35import org.openstreetmap.josm.data.osm.OsmPrimitive;
     36import org.openstreetmap.josm.data.osm.Way;
     37import org.openstreetmap.josm.data.osm.WaySegment;
     38import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
     39import org.openstreetmap.josm.gui.MapFrame;
     40import org.openstreetmap.josm.gui.MapView;
     41import org.openstreetmap.josm.gui.layer.MapViewPaintable;
     42import org.openstreetmap.josm.gui.layer.Layer;
     43import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     44import org.openstreetmap.josm.tools.ImageProvider;
     45import org.openstreetmap.josm.tools.Shortcut;
     46
     47/**
     48 * Makes a circle from a point or way
     49 *
     50 */
     51public class CircleAction extends MapMode implements MapViewPaintable {
     52
     53    public static boolean needMouseMove = false;
     54    enum Mode { circle, rotate, select }
     55    private Mode mode = null;
     56    private long mouseDownTime = 0;
     57    private Node selectedNode = null;
     58    private Way selectedWay = null;
     59    private WaySegment selectedSegment = null;
     60    private Color selectedColor;
     61    private boolean ctrl;
     62    private boolean alt;
     63    private boolean shift;
     64
     65    double xoff;
     66    double yoff;
     67    double distance;
     68    int NodeCount;
     69
     70    private Cursor oldCursor;
     71    private Point mousePos;
     72    private Point oldMousePos;
     73    private Point initialMousePos;
     74    private int initialMoveDelay = 200;
     75
     76    /**
     77     * Create a new SelectAction
     78     * @param mapFrame The MapFrame this action belongs to.
     79     */
     80    public CircleAction(MapFrame mapFrame) {
     81        super(tr("Create circle"), "circle/circle", tr("Create circle"),
     82                Shortcut.registerShortcut("mapmode:circle", tr("Mode: {0}", tr("Create circle")), KeyEvent.VK_C, Shortcut.GROUP_EDIT),
     83                mapFrame,
     84                getCursor("normal", "circle", Cursor.DEFAULT_CURSOR));
     85        putValue("help", "Action/Extrude/Circle");
     86        initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200);
     87        selectedColor = Main.pref.getColor(marktr("selected"), Color.red);
     88
     89        NodeCount = Main.pref.getInteger("createcircle.nodecount", 12); // TODO: re-use createcircle.nodecount or rename?
     90        if (NodeCount < 4) {
     91            NodeCount = 4;
     92        }
     93        if (NodeCount > 128) {
     94            NodeCount = 128;
     95        }
     96        if (NodeCount % 2 != 0) {
     97            // allow only even numbers
     98            NodeCount += 1;
     99        }
     100        // TODO: update preference accordingly?
     101    }
     102
     103    private static Cursor getCursor(String name, String mod, int def) {
     104        try {
     105            return ImageProvider.getCursor(name, mod);
     106        } catch (Exception e) {
     107        }
     108        return Cursor.getPredefinedCursor(def);
     109    }
     110
     111    private void setCursor(Cursor c) {
     112        if (oldCursor == null) {
     113            oldCursor = Main.map.mapView.getCursor();
     114            Main.map.mapView.setCursor(c);
     115        }
     116    }
     117
     118    private void restoreCursor() {
     119        if (oldCursor != null) {
     120            Main.map.mapView.setCursor(oldCursor);
     121            oldCursor = null;
     122        }
     123    }
     124
     125    @Override public void enterMode() {
     126        super.enterMode();
     127        Main.map.mapView.addMouseListener(this);
     128        Main.map.mapView.addMouseMotionListener(this);
     129    }
     130
     131    @Override public void exitMode() {
     132        super.exitMode();
     133        Main.map.mapView.removeMouseListener(this);
     134        Main.map.mapView.removeMouseMotionListener(this);
     135        Main.map.mapView.removeTemporaryLayer(this);
     136
     137    }
     138
     139    @Override public void mouseMoved(MouseEvent e) {
     140        if(!Main.map.mapView.isActiveLayerDrawable())
     141            return;
     142        if (needMouseMove && mode == Mode.rotate) {
     143            mouseDragged(e);
     144        }
     145
     146        updateKeyModifiers(e);
     147        mousePos = e.getPoint();
     148    }
     149
     150    private void updateKeyModifiers(MouseEvent e) {
     151        ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     152        alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
     153        shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     154    }
     155
     156    @Override public void mouseDragged(MouseEvent e) {
     157        if(!Main.map.mapView.isActiveLayerVisible())
     158            return;
     159
     160        if (mode == Mode.select) return;
     161
     162        // do not count anything as a move if it lasts less than 100 milliseconds.
     163        if (((mode == Mode.circle) || (mode == Mode.rotate)) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return;
     164
     165        if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
     166            return;
     167
     168        if (mousePos == null) {
     169            mousePos = e.getPoint();
     170            return;
     171        }
     172
     173        if (mode == Mode.rotate) {
     174            EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY());
     175            EastNorth mouseStartEN = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y);
     176            double dx = mouseEN.east() - mouseStartEN.east();
     177            double dy = mouseEN.north() - mouseStartEN.north();
     178            if (dx == 0 && dy == 0)
     179                return;
     180
     181            Collection<OsmPrimitive> selection = Main.ds.getSelectedNodesAndWays();
     182            Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
     183
     184            if (affectedNodes.size() < 2) return;
     185
     186            Command c = !Main.main.undoRedo.commands.isEmpty() ? Main.main.undoRedo.commands.getLast() : null;
     187            if (c instanceof SequenceCommand) {
     188                c = ((SequenceCommand)c).getLastCommand();
     189            }
     190
     191            if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand)c).getRotatedNodes())) {
     192                ((RotateCommand)c).rotateAgain(mouseStartEN, mouseEN);
     193            } else {
     194                Main.main.undoRedo.add(new RotateCommand(selection, mouseStartEN, mouseEN));
     195            }
     196        }
     197
     198        Main.map.mapView.repaint();
     199        mousePos = e.getPoint();
     200
     201    }
     202
     203    @Override public void mousePressed(MouseEvent e) {
     204        if(!Main.map.mapView.isActiveLayerVisible())
     205            return;
     206        if (!(Boolean)this.getValue("active")) return;
     207        if (e.getButton() != MouseEvent.BUTTON1)
     208            return;
     209
     210        ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     211        alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
     212        shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     213
     214        mouseDownTime = System.currentTimeMillis();
     215
     216        if (e.getClickCount() > 1 && mousePos != null && mousePos.equals(oldMousePos)) {
     217            // double-click, work with the selected way right here
     218
     219            List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelected());
     220            if (sel.size() == 1 && sel.get(0) instanceof Way) {
     221                selectedWay = (Way) sel.get(0);
     222            } else {
     223                selectedWay = null;
     224            }
     225
     226            if (selectedWay != null) {
     227                if (selectedWay.nodes.get(0).equals(selectedWay.nodes.get(selectedWay.nodes.size()-1))) {
     228                    // we have a closed way
     229                    Collection<Command> cmds = new LinkedList<Command>();
     230
     231                    makeCircle(null, selectedWay, cmds);
     232
     233                    if (selectedWay.nodes.size() < NodeCount) {
     234                        Main.main.undoRedo.add(new SequenceCommand(tr("Adding nodes and align circle"), cmds));
     235                    } else {
     236                        Main.main.undoRedo.add(new SequenceCommand(tr("Align circle"), cmds));
     237                    }
     238                } else {
     239                    // we have an open way
     240                    // check if segment is part of a longer way
     241                    if (selectedWay.nodes.size() == 2) {
     242                        Collection<Command> cmds = new LinkedList<Command>();
     243
     244                        makeCircle(null, selectedWay, cmds);
     245
     246                        Main.main.undoRedo.add(new SequenceCommand(tr("Convert way into a circle"), cmds));
     247                    }
     248                }
     249            }
     250        } else {
     251            // single-click
     252
     253            if (ctrl && shift) {
     254                // start rotating
     255
     256                if (!Main.ds.getSelected().isEmpty()) {
     257                    mode = Mode.rotate;
     258                    setCursor(ImageProvider.getCursor("rotate", null));
     259                }
     260            } else {
     261                List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelectedNodesAndWays());
     262                if (sel.size() == 1) {
     263                    if (sel.get(0) instanceof Node) {
     264                        selectedNode = (Node) sel.get(0);
     265                    }
     266                    if (sel.get(0) instanceof Way) {
     267                        selectedWay = (Way) sel.get(0);
     268                    }
     269                }
     270
     271                Node nearestNode = Main.map.mapView.getNearestNode(e.getPoint());
     272                Way nearestWay = Main.map.mapView.getNearestWay(e.getPoint());
     273
     274                if (nearestNode == null && nearestWay == null) {
     275                    // nothing selected: deselect
     276                    mode = Mode.select;
     277
     278                    selectedNode = null;
     279                    selectedWay = null;
     280
     281                    Main.ds.setSelected();
     282                } else {
     283                    if (nearestNode == null) {
     284                        // only way select
     285                        selectedNode = null;
     286
     287                        if (selectedWay != nearestWay) {
     288                            // change selection
     289                            mode = Mode.select;
     290
     291                            selectedWay = nearestWay;
     292
     293                            Main.ds.setSelected(nearestWay);
     294
     295                        }
     296                    } else {
     297                        // only node select
     298                        selectedWay = null;
     299
     300                        if (selectedNode != nearestNode) {
     301                            // change selection
     302                            mode = Mode.select;
     303
     304                            selectedNode = nearestNode;
     305
     306                            Main.ds.setSelected(nearestNode);
     307                        } else {
     308                            // switch to circle mode
     309                            mode = Mode.circle;
     310                        }
     311                    }
     312                }
     313                oldCursor = Main.map.mapView.getCursor();
     314            }
     315
     316        }
     317        oldMousePos = mousePos;
     318
     319        updateStatusLine();
     320        Main.map.mapView.addTemporaryLayer(this);
     321        Main.map.mapView.repaint();
     322
     323        mousePos = e.getPoint();
     324        initialMousePos = e.getPoint();
     325    }
     326
     327    @Override public void mouseReleased(MouseEvent e) {
     328        if(!Main.map.mapView.isActiveLayerVisible())
     329            return;
     330
     331        if (mode == Mode.circle) {
     332            if (selectedNode == null) {
     333                mode = null;
     334                return;
     335            }
     336            if (mousePos.distance(initialMousePos) > 10) {
     337                // Create the Circle
     338
     339                Collection<Command> cmds = new LinkedList<Command>();
     340                Collection<Node> nodes = new LinkedList<Node>();
     341
     342                nodes.add(selectedNode);
     343                Node n2 = Main.map.mapView.getNearestNode(e.getPoint());
     344                if (n2 == null) {
     345                    n2 = new Node(Main.proj.eastNorth2latlon(Main.map.mapView.getEastNorth(mousePos.x, mousePos.y)));
     346                    cmds.add(new AddCommand(n2));
     347                }
     348                nodes.add(n2);
     349
     350                makeCircle(nodes, null, cmds);
     351
     352                Main.main.undoRedo.add(new SequenceCommand(tr("Create Circle"), cmds));
     353
     354            }
     355            selectedNode = null;
     356        }
     357
     358        restoreCursor();
     359
     360        Main.map.mapView.removeTemporaryLayer(this);
     361        mode = null;
     362        updateStatusLine();
     363        Main.map.mapView.repaint();
     364    }
     365
     366    private EastNorth calcCenter(Collection<Node> nodes) {
     367        // this is a more precise version of the one introduced in Rev.1713 of the AlignInCircle action
     368
     369        BigDecimal area = new BigDecimal("0");
     370        BigDecimal north = new BigDecimal("0");
     371        BigDecimal east = new BigDecimal("0");
     372
     373        // Integrate the area, east and north centroid, we'll compute the final value based on the result of integration
     374        for (int i = 0; i < nodes.size() - 1; i++) {
     375            EastNorth n0 = ((Node) nodes.toArray()[i]).getEastNorth();
     376            EastNorth n1 = ((Node) nodes.toArray()[i+1]).getEastNorth();
     377
     378            BigDecimal x0 = new BigDecimal(n0.east());
     379            BigDecimal y0 = new BigDecimal(n0.north());
     380            BigDecimal x1 = new BigDecimal(n1.east());
     381            BigDecimal y1 = new BigDecimal(n1.north());
     382
     383            BigDecimal k = x0.multiply(y1, MathContext.DECIMAL128).subtract(y0.multiply(x1, MathContext.DECIMAL128));
     384
     385            area = area.add(k, MathContext.DECIMAL128);
     386            east = east.add(k.multiply(x0.add(x1, MathContext.DECIMAL128), MathContext.DECIMAL128));
     387            north = north.add(k.multiply(y0.add(y1, MathContext.DECIMAL128), MathContext.DECIMAL128));
     388
     389        }
     390
     391        BigDecimal d = new BigDecimal("2");
     392        area  = area.divide(d, MathContext.DECIMAL128);
     393        d = new BigDecimal("6");
     394        north = north.divide(d.multiply(area, MathContext.DECIMAL128), MathContext.DECIMAL128);
     395        east = east.divide(d.multiply(area, MathContext.DECIMAL128), MathContext.DECIMAL128);
     396
     397        EastNorth center = new EastNorth(east.doubleValue(), north.doubleValue());
     398
     399        return center;
     400    }
     401
     402    private double calcAngle(double xc, double yc, double x, double y) {
     403        // calculate the angle from xc|yc to x|y
     404        if (xc == x && yc == y)
     405            return 0; // actually invalid, but we won't have this case in this context
     406        double yd = Math.abs(y - yc);
     407        if (yd == 0 && xc < x)
     408            return 0;
     409        if (yd == 0 && xc > x)
     410            return Math.PI;
     411        double xd = Math.abs(x - xc);
     412        double a = Math.atan2(xd, yd);
     413        if (y > yc) {
     414            a = Math.PI - a;
     415        }
     416        if (x < xc) {
     417            a = -a;
     418        }
     419        a = 1.5*Math.PI + a;
     420        if (a < 0) {
     421            a += 2*Math.PI;
     422        }
     423        if (a >= 2*Math.PI) {
     424            a -= 2*Math.PI;
     425        }
     426        return a;
     427    }
     428
     429    private void fixAngleAndRadius(Collection<Command> cmds, Way way, EastNorth center, double r, int direction, boolean ignoreAngle) {
     430        // run through all nodes and set radius and angles
     431
     432        double step = direction * 2 * Math.PI / (way.nodes.size() - 1);
     433        double a = 0;
     434        for (int i = 0; i < way.nodes.size() - 1; i++) {
     435            Node n = ((Node)way.nodes.toArray()[i]);
     436
     437            if (i == 0 || ignoreAngle) {
     438                a = calcAngle(center.east(), center.north(), n.getEastNorth().east(), n.getEastNorth().north());
     439            }
     440            Node p = newNode(center, r, a);
     441            if (p.getCoor().isOutSideWorld()) {
     442                JOptionPane.showMessageDialog(Main.parent, tr("Cannot move objects outside of the world."));
     443                return;
     444            }
     445            cmds.add(new MoveCommand(n, p.getEastNorth().east() - n.getEastNorth().east(), p.getEastNorth().north() - n.getEastNorth().north()));
     446
     447            a += step;
     448        }
     449
     450    }
     451
     452    private Collection<Node> findNodes(Collection<Node> nodes, EastNorth nc, double ma, double mb) {
     453
     454        Collection<Node> result = new LinkedList<Node>();
     455        double min = ma;
     456        double max = mb;
     457        if (min > max) {
     458            min = mb;
     459            max = ma;
     460        }
     461
     462        for (Node n : nodes) {
     463            double a = calcAngle(nc.east(), nc.north(), n.getEastNorth().east(), n.getEastNorth().north());
     464            if (a >= min && a <= max) {
     465                result.add(n);
     466            }
     467        }
     468
     469        return result;
     470    }
     471
     472    private Node newNode(EastNorth center, double r, double a) {
     473        double x = center.east() + r * Math.cos(a);
     474        double y = center.north() + r * Math.sin(a);
     475        Node p = new Node(Main.proj.eastNorth2latlon(new EastNorth(x, y)));
     476
     477        return p;
     478    }
     479
     480    private void makeCircle(Collection<Node> nodes, Way way, Collection<Command> cmds) {
     481
     482        // sanity checks
     483        if (way == null && nodes == null) return;
     484        if (way != null) {
     485            // if the way has more than 2 nodes, it should be closed
     486            if ((way.nodes.size() > 2) && !way.nodes.get(0).equals(way.nodes.get(way.nodes.size()-1))) return;
     487            // use nodes of the way for the process
     488            if (nodes == null) {
     489                nodes = new LinkedList<Node>();
     490                nodes.addAll(way.nodes);
     491            }
     492        }
     493        if (nodes.size() < 2) return; // we need at least 2 nodes
     494
     495
     496        // now make a circle
     497        if (nodes.size() == 2) {
     498            // 2 nodes or 1 segment for diameter
     499
     500            Node n1 = ((Node)nodes.toArray()[0]);
     501            Node n2 = ((Node)nodes.toArray()[1]);
     502
     503            // calculate the center
     504            EastNorth center = new EastNorth(0.5 * (n1.getEastNorth().east() + n2.getEastNorth().east()), 0.5 * (n1.getEastNorth().north() + n2.getEastNorth().north()));
     505
     506            // calculate the radius (r)
     507            double r = Math.sqrt(Math.pow(center.east() - n1.getEastNorth().east(), 2) + Math.pow(center.north() - n1.getEastNorth().north(), 2));
     508
     509            // find direction
     510            double a1 = calcAngle(center.east(), center.north(), n1.getEastNorth().east(), n1.getEastNorth().north());
     511            double a2 = calcAngle(center.east(), center.north(), n2.getEastNorth().east(), n2.getEastNorth().north());
     512            int direction = -1;
     513            if (a1 < a2) { direction = -direction; }
     514
     515            double step = direction * 2 * Math.PI / NodeCount;
     516
     517            Way newWay;
     518            if (way == null) {
     519                newWay = new Way();
     520            } else {
     521                newWay = new Way(way);
     522                newWay.nodes.clear();
     523            }
     524
     525            // no need for gizmos here - create step by step
     526
     527            // add first node
     528            newWay.nodes.add(((Node)nodes.toArray()[0]));
     529            double a = a1;
     530
     531            // add first bunch of nodes
     532            while (newWay.nodes.size() < NodeCount / 2 ) {
     533                Node n = newNode(center, r, a + step);
     534                if (n.getCoor().isOutSideWorld()) {
     535                    JOptionPane.showMessageDialog(Main.parent, tr("Cannot add a node outside of the world."));
     536                    return;
     537                }
     538                newWay.nodes.add(n);
     539                cmds.add(new AddCommand(n));
     540
     541                a += step;
     542            }
     543
     544            // add second node the hard way
     545            newWay.nodes.add(((Node)nodes.toArray()[1]));
     546            a += step;
     547
     548            // add second bunch of nodes
     549            while (newWay.nodes.size() < NodeCount) {
     550                Node n = newNode(center, r, a + step);
     551                if (n.getCoor().isOutSideWorld()) {
     552                    JOptionPane.showMessageDialog(Main.parent, tr("Cannot add a node outside of the world."));
     553                    return;
     554                }
     555                newWay.nodes.add(n);
     556                cmds.add(new AddCommand(n));
     557
     558                a += step;
     559            }
     560
     561            // close the circle
     562            newWay.nodes.add(newWay.nodes.get(0));
     563
     564            fixAngleAndRadius(cmds, newWay, center, r, direction, false); // not needed actually
     565
     566            if (way == null) {
     567                cmds.add(new AddCommand(newWay));
     568            } else {
     569                cmds.add(new ChangeCommand(way, newWay));
     570            }
     571
     572        } else {
     573            // more than 2 nodes or closed way
     574
     575            // calculate center and direction
     576            EastNorth center = calcCenter(nodes);
     577            double area = 0;
     578            for (int i=0; i<nodes.size(); i++) {
     579                EastNorth n0 = ((Node) nodes.toArray()[i]).getEastNorth();
     580                EastNorth n1 = ((Node) nodes.toArray()[(i + 1) % nodes.size()]).getEastNorth();
     581                area  += n0.east() * n1.north() - n1.east() * n0.north();
     582            }
     583            int direction = -1;
     584            if (area > 0) { direction = -direction; }
     585
     586            // calculate radius
     587            double r = 0;
     588            for (Node n : nodes) {
     589                r += Math.sqrt(Math.pow(center.east() - n.getEastNorth().east(), 2) + Math.pow(center.north() - n.getEastNorth().north(), 2));
     590            }
     591            r /= nodes.size();
     592
     593            // set start angle and step size
     594            double step = direction * 2 * Math.PI / NodeCount;
     595            double a = 0;
     596            if (direction < 0) {
     597                a = 2 * Math.PI;
     598            }
     599
     600            if (!ctrl && nodes.size() < NodeCount) {
     601                // pressing CTRL will not add new nodes to the circle
     602                // add nodes if needed
     603
     604                Way newWay;
     605                if (way == null) {
     606                    newWay = new Way();
     607                } else {
     608                    newWay = new Way(way);
     609                    newWay.nodes.clear();
     610                }
     611
     612                while (newWay.nodes.size() < NodeCount) {
     613                    Collection<Node> oldNodes = findNodes(nodes, center, a, a + step);
     614
     615                    if (oldNodes.isEmpty()) {
     616                        // no existing nodes in sector, create a new one
     617                        Node n = newNode(center, r, a + 0.5 * step);
     618                        if (n.getCoor().isOutSideWorld()) {
     619                            JOptionPane.showMessageDialog(Main.parent, tr("Cannot add a node outside of the world."));
     620                            return;
     621                        }
     622                        newWay.nodes.add(n);
     623                        cmds.add(new AddCommand(n));
     624                    } else {
     625                        for (Node en : oldNodes) {
     626                            if (!newWay.nodes.contains(en)) {
     627                                newWay.nodes.add(en);
     628                            }
     629                        }
     630                    }
     631
     632                    a += step;
     633                }
     634
     635                newWay.nodes.add(newWay.nodes.get(0)); // close the circle
     636
     637                fixAngleAndRadius(cmds, newWay, center, r, direction, alt);
     638                // pressing ALT will not change the angles
     639
     640                if (way == null) {
     641                    cmds.add(new AddCommand(newWay));
     642                } else {
     643                    cmds.add(new ChangeCommand(way, newWay));
     644                }
     645
     646            } else {
     647                // just fix circle
     648                if (way != null) {
     649                    fixAngleAndRadius(cmds, way, center, r, direction, alt);
     650                    // pressing ALT will not change the angles
     651                }
     652            }
     653        }
     654
     655        return;
     656    }
     657
     658    public void paint(Graphics g, MapView mv) {
     659        if (mode == Mode.circle) {
     660            // Draw a simple circle-placeholder
     661            if (selectedNode == null) return;
     662
     663            // find the coordinates
     664            double x1 = selectedNode.getEastNorth().east();
     665            double y1 = selectedNode.getEastNorth().north();
     666
     667            double x2 = mv.getEastNorth(mousePos.x, mousePos.y).east();
     668            double y2 = mv.getEastNorth(mousePos.x, mousePos.y).north();
     669
     670            double xc = 0.5 * (x1 + x2);
     671            double yc = 0.5 * (y1 + y2);
     672
     673            double a = calcAngle(xc, yc, x1, y1);
     674            double r = Math.sqrt(Math.pow(xc - x1, 2) + Math.pow(yc - y1, 2));
     675
     676            // show length of diameter in status
     677            distance = Main.proj.eastNorth2latlon(new EastNorth(x2, y2)).greatCircleDistance(Main.proj.eastNorth2latlon(new EastNorth(x1, y1)));
     678            Main.map.statusLine.setDist(distance);
     679            updateStatusLine();
     680
     681            // draw a circle
     682            Graphics2D g2 = (Graphics2D)g;
     683            g2.setColor(selectedColor);
     684            g2.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
     685            GeneralPath b = new GeneralPath();
     686
     687            for (int i = 0; i <= NodeCount; i++) {
     688                double a1 = a + 2 * Math.PI * (1.0 - i / (double)NodeCount);
     689
     690                double x = xc + r * Math.cos(a1);
     691                double y = yc + r * Math.sin(a1);
     692                Point p = mv.getPoint(new EastNorth(x, y));
     693                if (i == 0) {
     694                    b.moveTo(p.x, p.y);
     695                } else {
     696                    b.lineTo(p.x, p.y);
     697                }
     698
     699            }
     700            g2.draw(b);
     701
     702            // draw the center point
     703            Point p = mv.getPoint(new EastNorth(xc, yc));
     704            g2.drawArc(p.x, p.y, 2, 2, 0, 360);
     705
     706            g2.setStroke(new BasicStroke(1));
     707        } else {
     708            List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelected());
     709            if (sel.size() == 1 && sel.get(0) instanceof Way) {
     710                Way selWay  = (Way) sel.get(0);
     711
     712                if ((selWay.nodes.size() > 2) && selWay.nodes.get(0).equals(selWay.nodes.get(selWay.nodes.size()-1))) {
     713                    // more than 2 nodes
     714
     715                    EastNorth center = calcCenter(selWay.nodes);
     716                    Point p = mv.getPoint(center);
     717
     718                    // draw a point
     719                    Graphics2D g2 = (Graphics2D)g;
     720                    g2.setColor(selectedColor);
     721                    g2.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
     722
     723                    g2.drawArc(p.x, p.y, 2, 2, 0, 360);
     724
     725                    g2.setStroke(new BasicStroke(1));
     726
     727                }
     728            }
     729        }
     730    }
     731
     732    @Override public String getModeHelpText() {
     733        if (mode == Mode.select)
     734            return tr("Release the mouse button to select the objects in the rectangle.");
     735        else if (mode == Mode.circle)
     736            return tr("Make a circle of the desired size, then release the mouse button.");
     737        else if (mode == Mode.rotate)
     738            return tr("Release the mouse button to stop rotating.");
     739        else {
     740            List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(Main.ds.getSelectedNodesAndWays());
     741            if (sel.size() == 1 && sel.get(0) instanceof Way) {
     742                Way selWay  = (Way) sel.get(0);
     743
     744                if (selWay.nodes.size() == 2)
     745                    return tr("Double-click on the selected way to create a circle.");
     746                else if ((selWay.nodes.size() > 2) && selWay.nodes.get(0).equals(selWay.nodes.get(selWay.nodes.size()-1)))
     747                    return tr("Double-click on the selected way to align nodes in a circle.");
     748
     749            } else if (sel.size() == 1 && sel.get(0) instanceof Node)
     750                return tr("Click on the selected node and move the mouse to create a circle.");
     751        }
     752        return tr("Select a way or node to make a circle.");
     753    }
     754
     755    @Override public boolean layerIsSupported(Layer l) {
     756        return l instanceof OsmDataLayer;
     757    }
     758}
  • src/org/openstreetmap/josm/gui/MapFrame.java

     
    1818import org.openstreetmap.josm.actions.mapmode.DeleteAction;
    1919import org.openstreetmap.josm.actions.mapmode.DrawAction;
    2020import org.openstreetmap.josm.actions.mapmode.ExtrudeAction;
     21import org.openstreetmap.josm.actions.mapmode.CircleAction;
    2122import org.openstreetmap.josm.actions.mapmode.MapMode;
    2223import org.openstreetmap.josm.actions.mapmode.SelectAction;
    2324import org.openstreetmap.josm.actions.mapmode.ZoomAction;
     
    9091        addMapMode(new IconToggleButton(new SelectAction(this)));
    9192        addMapMode(new IconToggleButton(new DrawAction(this)));
    9293        addMapMode(new IconToggleButton(new ExtrudeAction(this)));
     94        addMapMode(new IconToggleButton(new CircleAction(this)));
    9395        addMapMode(new IconToggleButton(new ZoomAction(this)));
    9496        addMapMode(new IconToggleButton(new DeleteAction(this)));
    9597
     
    132134     * Delegates the call to all Destroyables within this component (e.g. MapModes)
    133135     */
    134136    public void destroy() {
    135         for (ToggleDialog t : allDialogs)
     137        for (ToggleDialog t : allDialogs) {
    136138            t.close();
     139        }
    137140        for (int i = 0; i < toolBarActions.getComponentCount(); ++i)
    138             if (toolBarActions.getComponent(i) instanceof Destroyable)
     141            if (toolBarActions.getComponent(i) instanceof Destroyable) {
    139142                ((Destroyable)toolBarActions).destroy();
     143            }
    140144        for (int i = 0; i < toolBarToggle.getComponentCount(); ++i)
    141             if (toolBarToggle.getComponent(i) instanceof Destroyable)
     145            if (toolBarToggle.getComponent(i) instanceof Destroyable) {
    142146                ((Destroyable)toolBarToggle).destroy();
     147            }
    143148
    144149        // remove menu entries
    145150        Main.main.menu.viewMenu.setVisible(false);
     
    194199    @Override public void setVisible(boolean aFlag) {
    195200        boolean old = isVisible();
    196201        super.setVisible(aFlag);
    197         if (old != aFlag)
     202        if (old != aFlag) {
    198203            firePropertyChange("visible", old, aFlag);
     204        }
    199205    }
    200206
    201207
     
    208214    public void selectMapMode(MapMode mapMode) {
    209215        if (mapMode == this.mapMode)
    210216            return;
    211         if (this.mapMode != null)
     217        if (this.mapMode != null) {
    212218            this.mapMode.exitMode();
     219        }
    213220        this.mapMode = mapMode;
    214221        mapMode.enterMode();
    215222    }
     
    229236        jb.add(toolBarToggle);
    230237        if(Main.pref.getBoolean("sidetoolbar.visible", true))
    231238        {
    232             if(Main.pref.getBoolean("sidetoolbar.scrollable", true))
     239            if(Main.pref.getBoolean("sidetoolbar.scrollable", true)) {
    233240                panel.add(new ScrollViewport(jb, ScrollViewport.VERTICAL_DIRECTION),
    234                 BorderLayout.WEST);
    235             else
     241                        BorderLayout.WEST);
     242            } else {
    236243                panel.add(jb, BorderLayout.WEST);
     244            }
    237245        }
    238         if (statusLine != null && Main.pref.getBoolean("statusline.visible", true))
     246        if (statusLine != null && Main.pref.getBoolean("statusline.visible", true)) {
    239247            panel.add(statusLine, BorderLayout.SOUTH);
     248        }
    240249    }
    241250}