Ignore:
Timestamp:
2011-08-21T13:12:53+02:00 (15 years ago)
Author:
xeen
Message:

updates visual appearance of highlights and adds them to select and delete action

in more detail:

  • add target highlighting to select action
  • add target cursor to select action
  • add target highlighting to delete action
  • unify ctrl/alt/shift modifier detection in MapMode actions
  • highlights are now a halo around the way/node instead of a color change
  • default highlight color is now the same as the select color (red)
  • ability to highlight WaySegments and VirtualNodes
  • various style/whitespace nits
  • fixes #2411

This patch touches a lot of areas, so please report any regressions in the map mode
tools. Also please add a comment to #2411 if you find to highlighting in select
mode distracting, so it can be fine tuned (or turned off by default).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

    r4191 r4327  
    66import static org.openstreetmap.josm.tools.I18n.trn;
    77
     8import java.awt.AWTEvent;
    89import java.awt.Cursor;
    910import java.awt.Point;
    1011import java.awt.Rectangle;
     12import java.awt.Toolkit;
     13import java.awt.event.AWTEventListener;
    1114import java.awt.event.ActionEvent;
    1215import java.awt.event.InputEvent;
     
    1619import java.util.Collection;
    1720import java.util.Collections;
     21import java.util.HashSet;
    1822import java.util.Iterator;
    1923import java.util.LinkedList;
     24import java.util.Set;
    2025
    2126import javax.swing.JOptionPane;
     
    6065 * @author imi
    6166 */
    62 public class SelectAction extends MapMode implements SelectionEnded {
     67public class SelectAction extends MapMode implements AWTEventListener, SelectionEnded {
     68    // "select" means the selection rectangle and "move" means either dragging
     69    // or select if no mouse movement occurs (i.e. just clicking)
    6370    enum Mode { move, rotate, scale, select }
    64    
     71
     72    // contains all possible cases the cursor can be in the SelectAction except the
     73    // the move pointer (latter is a system one and not an image)
     74    private enum SelectActionCursor {
     75        rect("normal", "selection"),
     76        rect_add("normal", "select_add"),
     77        rect_rm("normal", "select_remove"),
     78        way("normal", "select_way"),
     79        way_add("normal", "select_way_add"),
     80        way_rm("normal", "select_way_remove"),
     81        node("normal", "select_node"),
     82        node_add("normal", "select_node_add"),
     83        node_rm("normal", "select_node_remove"),
     84        virtual_node("normal", "addnode"),
     85        scale("scale", null),
     86        rotate("rotate", null);
     87
     88        private final Cursor c;
     89        private SelectActionCursor(String main, String sub) {
     90            c = ImageProvider.getCursor(main, sub);
     91        }
     92        public Cursor cursor() {
     93            return c;
     94        }
     95    }
     96
     97    // Cache previous mouse event (needed when only the modifier keys are
     98    // pressed but the mouse isn't moved)
     99    private MouseEvent oldEvent = null;
     100
    65101    private Mode mode = null;
    66102    private SelectionManager selectionManager;
    67103    private boolean cancelDrawMode = false;
     104    private boolean drawTargetHighlight;
    68105    private boolean didMouseDrag = false;
    69106    /**
     
    94131    private int initialMoveThreshold;
    95132    private boolean initialMoveThresholdExceeded = false;
     133
     134    /**
     135     * elements that have been highlighted in the previous iteration. Used
     136     * to remove the highlight from them again as otherwise the whole data
     137     * set would have to be checked.
     138     */
     139    private Set<OsmPrimitive> oldHighlights = new HashSet<OsmPrimitive>();
    96140
    97141    /**
     
    109153        initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay", 200);
    110154        initialMoveThreshold = Main.pref.getInteger("edit.initial-move-threshold", 5);
     155        drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
     156        // This is required to update the cursors when ctrl/shift/alt is pressed
     157        try {
     158            Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
     159        } catch (SecurityException ex) {
     160            System.out.println(ex);
     161        }
    111162    }
    112163
     
    127178        mv.removeMouseMotionListener(this);
    128179        mv.setVirtualNodesEnabled(false);
     180        removeHighlighting();
     181    }
     182
     183    /**
     184     * works out which cursor should be displayed for most of SelectAction's
     185     * features. The only exception is the "move" cursor when actually dragging
     186     * primitives.
     187     * @param nearbyStuff  primitives near the cursor
     188     * @return the cursor that should be displayed
     189     */
     190    private Cursor getCursor(Collection<OsmPrimitive> nearbyStuff) {
     191        String c = "rect";
     192        switch(mode) {
     193        case move:
     194            if(virtualNode != null) {
     195                c = "virtual_node";
     196                break;
     197            }
     198
     199            // nearbyStuff cannot be empty as otherwise we would be in
     200            // Move.select and not Move.move
     201            OsmPrimitive osm = nearbyStuff.iterator().next();
     202
     203            c = (osm instanceof Node) ? "node" : c;
     204            c = (osm instanceof Way) ? "way" : c;
     205
     206            if(shift) {
     207                c += "_add";
     208            } else if(ctrl) {
     209                c += osm.isSelected() ? "_rm" : "_add";
     210            }
     211            break;
     212        case rotate:
     213            c = "rotate";
     214            break;
     215        case scale:
     216            c = "scale";
     217            break;
     218        case select:
     219            c = "rect" + (shift ? "_add" : (ctrl ? "_rm" : ""));
     220            break;
     221        }
     222        return SelectActionCursor.valueOf(c).cursor();
     223    }
     224
     225    /**
     226     * Removes all existing highlights.
     227     * @return true if a repaint is required
     228     */
     229    private boolean removeHighlighting() {
     230        boolean needsRepaint = false;
     231        DataSet ds = getCurrentDataSet();
     232        if(ds != null && !ds.getHighlightedVirtualNodes().isEmpty()) {
     233            needsRepaint = true;
     234            ds.clearHighlightedVirtualNodes();
     235        }
     236        if(oldHighlights.isEmpty())
     237            return needsRepaint;
     238
     239        for(OsmPrimitive prim : oldHighlights) {
     240            prim.setHighlighted(false);
     241        }
     242        oldHighlights = new HashSet<OsmPrimitive>();
     243        return true;
     244    }
     245
     246    /**
     247     * handles adding highlights and updating the cursor for the given mouse event.
     248     * @param MouseEvent which should be used as base for the feedback
     249     * @return true if repaint is required
     250     */
     251    private boolean giveUserFeedback(MouseEvent e) {
     252        return giveUserFeedback(e, e.getModifiers());
     253    }
     254
     255    /**
     256     * handles adding highlights and updating the cursor for the given mouse event.
     257     * @param MouseEvent which should be used as base for the feedback
     258     * @param define custom keyboard modifiers if the ones from MouseEvent are outdated or similar
     259     * @return true if repaint is required
     260     */
     261    private boolean giveUserFeedback(MouseEvent e, int modifiers) {
     262        boolean needsRepaint = false;
     263
     264        Collection<OsmPrimitive> c = MapView.asColl(
     265                mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, true));
     266
     267        updateKeyModifiers(modifiers);
     268        determineMapMode(!c.isEmpty());
     269
     270        if(drawTargetHighlight) {
     271            needsRepaint = removeHighlighting();
     272        }
     273
     274        virtualWays.clear();
     275        virtualNode = null;
     276        if(mode == Mode.move && setupVirtual(e)) {
     277            DataSet ds = getCurrentDataSet();
     278            if (ds != null) {
     279                ds.setHighlightedVirtualNodes(virtualWays);
     280            }
     281            mv.setNewCursor(SelectActionCursor.virtual_node.cursor(), this);
     282            // don't highlight anything else if a virtual node will be
     283            return true;
     284        }
     285
     286        mv.setNewCursor(getCursor(c), this);
     287
     288        // return early if there can't be any highlights
     289        if(!drawTargetHighlight || mode != Mode.move || c.isEmpty())
     290            return needsRepaint;
     291
     292        for(OsmPrimitive x : c) {
     293            // only highlight primitives that will change the selection
     294            // when clicked. I.e. don't highlight selected elements unless
     295            // we are in toggle mode.
     296            if(ctrl || !x.isSelected()) {
     297                x.setHighlighted(true);
     298                oldHighlights.add(x);
     299            }
     300        }
     301        return needsRepaint || !oldHighlights.isEmpty();
     302    }
     303
     304    /**
     305     * This is called whenever the keyboard modifier status changes
     306     */
     307    public void eventDispatched(AWTEvent e) {
     308        if(oldEvent == null)
     309            return;
     310        // We don't have a mouse event, so we pass the old mouse event but the
     311        // new modifiers.
     312        giveUserFeedback(oldEvent, ((InputEvent) e).getModifiers());
    129313    }
    130314
     
    264448    public void mouseMoved(MouseEvent e) {
    265449        // Mac OSX simulates with  ctrl + mouse 1  the second mouse button hence no dragging events get fired.
    266         //
    267450        if ((Main.platform instanceof PlatformHookOsx) && (mode == Mode.rotate || mode == Mode.scale)) {
    268451            mouseDragged(e);
    269         }
    270     }
     452            return;
     453        }
     454        oldEvent = e;
     455        if(giveUserFeedback(e)) {
     456            mv.repaint();
     457        }
     458    }
     459
     460    @Override
     461    public void mouseExited(MouseEvent e) {
     462        if(removeHighlighting()) {
     463            mv.repaint();
     464        }
     465    }
     466
    271467    private Node virtualNode = null;
    272468    private Collection<WaySegment> virtualWays = new LinkedList<WaySegment>();
     
    339535            Point p = e.getPoint();
    340536            boolean waitForMouseUp = Main.pref.getBoolean("mappaint.select.waits-for-mouse-up", false);
    341             boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    342             boolean alt = ((e.getModifiers() & (ActionEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0 || Main.pref.getBoolean("selectaction.cycles.multiple.matches", false));
     537            updateKeyModifiers(e);
     538            alt = alt || Main.pref.getBoolean("selectaction.cycles.multiple.matches", false);
    343539
    344540            if (!alt) {
     
    389585
    390586    /**
     587     * sets the mapmode according to key modifiers and if there are any
     588     * selectables nearby. Everything has to be pre-determined for this
     589     * function; its main purpose is to centralize what the modifiers do.
     590     * @param nearSelectables
     591     */
     592    private void determineMapMode(boolean hasSelectionNearby) {
     593        if (shift && ctrl) {
     594            mode = Mode.rotate;
     595        } else if (alt && ctrl) {
     596            mode = Mode.scale;
     597        } else if (hasSelectionNearby) {
     598            mode = Mode.move;
     599        } else {
     600            mode = Mode.select;
     601        }
     602    }
     603
     604    /**
    391605     * Look, whether any object is selected. If not, select the nearest node.
    392606     * If there are no nodes in the dataset, do nothing.
     
    400614    public void mousePressed(MouseEvent e) {
    401615        // return early
    402         if (!mv.isActiveLayerVisible() || !(Boolean) this.getValue("active") || e.getButton() != MouseEvent.BUTTON1) {
    403             return;
    404         }
     616        if (!mv.isActiveLayerVisible() || !(Boolean) this.getValue("active") || e.getButton() != MouseEvent.BUTTON1)
     617            return;
    405618
    406619        // request focus in order to enable the expected keyboard shortcuts
    407620        mv.requestFocus();
    408621
    409         boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    410         boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    411         boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
     622        // update which modifiers are pressed (shift, alt, ctrl)
     623        updateKeyModifiers(e);
    412624
    413625        // We don't want to change to draw tool if the user tries to (de)select
     
    422634                mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, true));
    423635
    424         if (shift && ctrl) {
    425             mode = Mode.rotate;
    426 
     636        determineMapMode(!c.isEmpty());
     637        switch(mode) {
     638        case rotate:
     639        case scale:
    427640            if (getCurrentDataSet().getSelected().isEmpty()) {
    428641                getCurrentDataSet().setSelected(c);
     
    432645            // Mode.move   redraws when mouseDragged is called
    433646            // Mode.rotate redraws here
    434             mv.setNewCursor(ImageProvider.getCursor("rotate", null), this);
    435             mv.repaint();
    436         } else if (alt && ctrl) {
    437             mode = Mode.scale;
    438 
    439             if (getCurrentDataSet().getSelected().isEmpty()) {
    440                 getCurrentDataSet().setSelected(c);
    441             }
    442 
    443             // Mode.select redraws when selectPrims is called
    444             // Mode.move   redraws when mouseDragged is called
    445647            // Mode.scale redraws here
    446             mv.setNewCursor(ImageProvider.getCursor("scale", null), this);
    447             mv.repaint();
    448         } else if (!c.isEmpty()) {
    449             mode = Mode.move;
    450 
     648            break;
     649        case move:
    451650            if (!cancelDrawMode && c.iterator().next() instanceof Way) {
    452651                setupVirtual(e);
     
    454653
    455654            selectPrims(cycleSetup(c, e), e, false, false);
    456         } else {
    457             mode = Mode.select;
    458 
     655            break;
     656        case select:
     657        default:
    459658            selectionManager.register(mv);
    460659            selectionManager.mousePressed(e);
    461         }
    462 
     660            break;
     661        }
     662        giveUserFeedback(e);
     663        mv.repaint();
    463664        updateStatusLine();
    464665    }
     
    466667    @Override
    467668    public void mouseReleased(MouseEvent e) {
    468         if (!mv.isActiveLayerVisible()) {
    469             return;
    470         }
     669        if (!mv.isActiveLayerVisible())
     670            return;
    471671
    472672        startingDraggingPos = null;
    473673
    474         mv.setNewCursor(cursor, this);
    475674        if (mode == Mode.select) {
    476675            selectionManager.unregister(mv);
     
    489688                virtualNode = null;
    490689
    491                 // do nothing if the click was to short to be recognized as a drag,
     690                // do nothing if the click was to short too be recognized as a drag,
    492691                // but the release position is farther than 10px away from the press position
    493                 if (lastMousePos.distanceSq(e.getPoint()) < 100) {
     692                if (lastMousePos == null || lastMousePos.distanceSq(e.getPoint()) < 100) {
    494693                    selectPrims(cyclePrims(cycleList, e), e, true, false);
    495694
     
    500699                        // click and switch back to SelectMode
    501700                        Main.worker.execute(new Runnable() {
    502 
    503701                            public void run() {
    504702                                Main.map.selectDrawTool(true);
     
    539737        }
    540738
    541         // I don't see why we need this.
    542         //updateStatusLine();
    543739        mode = null;
     740        giveUserFeedback(e);
    544741        updateStatusLine();
    545742    }
    546743
    547744    public void selectionEnded(Rectangle r, MouseEvent e) {
    548         boolean alt = (e.getModifiersEx() & (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK)) != 0;
     745        updateKeyModifiers(e);
    549746        selectPrims(selectionManager.getObjectsInRectangle(r, alt), e, true, true);
    550747    }
     
    554751     * selection cycle given by <code>prims</code>.
    555752     * @param prims the primitives that form the selection cycle
    556      * @param shift whether shift is pressed
    557      * @param ctrl whether ctrl is pressed
     753     * @param mouse event
    558754     * @return the next element of cycle list <code>prims</code>.
    559755     */
     
    562758
    563759        if (prims.size() > 1) {
    564             boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    565             boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     760            updateKeyModifiers(e);
    566761
    567762            DataSet ds = getCurrentDataSet();
     
    630825
    631826    private void selectPrims(Collection<OsmPrimitive> prims, MouseEvent e, boolean released, boolean area) {
    632         boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    633         boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     827        updateKeyModifiers(e);
    634828        DataSet ds = getCurrentDataSet();
    635829
    636830        // not allowed together: do not change dataset selection, return early
    637         if ((shift && ctrl) || (ctrl && !released) || (!virtualWays.isEmpty())) {
    638             return;
    639         }
     831        if ((shift && ctrl) || (ctrl && !released) || (!virtualWays.isEmpty()))
     832            return;
    640833
    641834        if (!released) {
     
    666859    @Override
    667860    public String getModeHelpText() {
    668         if (mode == Mode.select) {
     861        if (mode == Mode.select)
    669862            return tr("Release the mouse button to select the objects in the rectangle.");
    670         } else if (mode == Mode.move) {
     863        else if (mode == Mode.move)
    671864            return tr("Release the mouse button to stop moving. Ctrl to merge with nearest node.");
    672         } else if (mode == Mode.rotate) {
     865        else if (mode == Mode.rotate)
    673866            return tr("Release the mouse button to stop rotating.");
    674         } else if (mode == Mode.scale) {
     867        else if (mode == Mode.scale)
    675868            return tr("Release the mouse button to stop scaling.");
    676         } else {
     869        else
    677870            return tr("Move objects by dragging; Shift to add to selection (Ctrl to toggle); Shift-Ctrl to rotate selected; Alt-Ctrl to scale selected; or change selection");
    678         }
    679871    }
    680872
Note: See TracChangeset for help on using the changeset viewer.