Changeset 2109 in josm for trunk/src/org/openstreetmap/josm


Ignore:
Timestamp:
2009-09-12T23:13:00+02:00 (15 years ago)
Author:
stoecker
Message:

applied #3441 - patch by xeen - improve middle click to allow better selections

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/MapStatus.java

    r2039 r2109  
    77import java.awt.AWTEvent;
    88import java.awt.Color;
     9import java.awt.Component;
    910import java.awt.Cursor;
    1011import java.awt.Dimension;
     
    1314import java.awt.GridBagLayout;
    1415import java.awt.Point;
     16import java.awt.SystemColor;
    1517import java.awt.Toolkit;
    1618import java.awt.event.AWTEventListener;
     
    2325import java.awt.event.MouseListener;
    2426import java.awt.event.MouseMotionListener;
    25 import java.lang.reflect.InvocationTargetException;
     27import java.util.ArrayList;
    2628import java.util.Collection;
    2729import java.util.ConcurrentModificationException;
     30import java.util.List;
    2831import java.util.Map.Entry;
    2932
     
    113116    private final class Collector implements Runnable {
    114117        /**
    115          * The last object displayed in status line.
    116          */
    117         Collection<OsmPrimitive> osmStatus;
    118         /**
    119          * The old modifiers that was pressed the last time this collector ran.
    120          */
    121         private int oldModifiers;
     118         * the mouse position of the previous iteration. This is used to show
     119         * the popup until the cursor is moved.
     120         */
     121        private Point oldMousePos;
     122        /**
     123         * Contains the labels that are currently shown in the information
     124         * popup
     125         */
     126        private List<JLabel> popupLabels = null;
    122127        /**
    123128         * The popup displayed to show additional information
     
    144149                if (parent != Main.map)
    145150                    return; // exit, if new parent.
    146                 if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0 || ms.mousePos == null) {
    147                     continue; // freeze display when holding down ctrl
    148                 }
    149 
    150                 if (mv.center == null) {
     151
     152                // Do nothing, if required data is missing
     153                if(ms.mousePos == null || mv.center == null) {
     154                    continue;
     155                }
     156
     157                // Freeze display when holding down CTRL
     158                if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
     159                    // update the information popup's labels though, because
     160                    // the selection might have changed from the outside
     161                    popupUpdateLabels();
    151162                    continue;
    152163                }
     
    157168                // the data.
    158169                try {
    159                     OsmPrimitive osmNearest = null;
    160170                    // Set the text label in the bottom status bar
    161                     osmNearest = mv.getNearest(ms.mousePos);
    162                     if (osmNearest != null) {
    163                         nameText.setText(osmNearest.getDisplayName(DefaultNameFormatter.getInstance()));
    164                     } else {
    165                         nameText.setText(tr("(no object)"));
    166                     }
     171                    statusBarElementUpdate(ms);
     172
     173                    // The popup != null check is required because a left-click
     174                    // produces several events as well, which would make this
     175                    // variable true. Of course we only want the popup to show
     176                    // if the middle mouse button has been pressed in the first
     177                    // place
     178                    boolean isAtOldPosition = (oldMousePos != null
     179                            && oldMousePos.equals(ms.mousePos)
     180                            && popup != null);
     181                    boolean middleMouseDown = (ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0;
     182
    167183
    168184                    // Popup Information
    169                     if ((ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0 ) {
     185                    // display them if the middle mouse button is pressed and
     186                    // keep them until the mouse is moved
     187                    if (middleMouseDown || isAtOldPosition)
     188                    {
    170189                        Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos);
    171190
     
    173192                            continue;
    174193                        }
    175                         if (osms.equals(osmStatus) && ms.modifiers == oldModifiers) {
    176                             continue;
     194
     195                        JPanel c = new JPanel(new GridBagLayout());
     196                        c.setBorder(BorderFactory.createRaisedBevelBorder());
     197                        final JLabel lbl = new JLabel(
     198                                "<html>"+tr("Middle click again, to cycle through.<br>"+
     199                                "Hold CTRL to select something from this list.<hr>")+"</html>",
     200                                null,
     201                                JLabel.HORIZONTAL
     202                        );
     203                        lbl.setHorizontalAlignment(JLabel.LEFT);
     204                        c.add(lbl, GBC.eol().insets(2, 0, 2, 0));
     205
     206                        // Only cycle if the mouse has not been moved and the
     207                        // middle mouse button has been pressed at least twice
     208                        // (the reason for this is the popup != null check for
     209                        // isAtOldPosition, see above. This is a nice side
     210                        // effect though, because it does not change selection
     211                        // of the first middle click)
     212                        if(isAtOldPosition && middleMouseDown) {
     213                            popupCycleSelection(osms);
    177214                        }
    178215
    179                         if (popup != null) {
    180                             try {
    181                                 EventQueue.invokeAndWait(new Runnable() {
    182                                     public void run() {
    183                                         popup.hide();
    184                                     }
    185                                 });
    186                             } catch (InterruptedException e) {
    187                             } catch (InvocationTargetException e) {
    188                                 throw new RuntimeException(e);
    189                             }
     216                        // These labels may need to be updated from the outside
     217                        // so collect them
     218                        List<JLabel> lbls = new ArrayList<JLabel>();
     219                        for (final OsmPrimitive osm : osms) {
     220                            JLabel l = popupBuildPrimitiveLabels(osm);
     221                            lbls.add(l);
     222                            c.add(l, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 2));
    190223                        }
    191224
    192                         JPanel c = new JPanel(new GridBagLayout());
    193                         for (final OsmPrimitive osm : osms) {
    194                             final StringBuilder text = new StringBuilder();
    195                             String name = osm.getDisplayName(DefaultNameFormatter.getInstance());
    196                             if (osm.getId() == 0 || osm.isModified()) {
    197                                 name = "<i><b>"+ osm.getDisplayName(DefaultNameFormatter.getInstance())+"*</b></i>";
    198                             }
    199                             text.append(name);
    200                             if (osm.getId() != 0) {
    201                                 text.append("<br>id="+osm.getId());
    202                             }
    203                             for (Entry<String, String> e : osm.entrySet()) {
    204                                 text.append("<br>"+e.getKey()+"="+e.getValue());
    205                             }
    206                             final JLabel l = new JLabel(
    207                                     "<html>"+text.toString()+"</html>",
    208                                     ImageProvider.get(OsmPrimitiveType.from(osm)),
    209                                     JLabel.HORIZONTAL
    210                             );
    211                             l.setFont(l.getFont().deriveFont(Font.PLAIN));
    212                             l.setVerticalTextPosition(JLabel.TOP);
    213                             l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    214                             l.addMouseListener(new MouseAdapter(){
    215                                 @Override public void mouseEntered(MouseEvent e) {
    216                                     l.setText("<html><u color='blue'>"+text.toString()+"</u></html>");
    217                                 }
    218                                 @Override public void mouseExited(MouseEvent e) {
    219                                     l.setText("<html>"+text.toString()+"</html>");
    220                                 }
    221                                 @Override public void mouseClicked(MouseEvent e) {
    222                                     Main.main.getCurrentDataSet().setSelected(osm);
    223                                     mv.repaint();
    224                                 }
    225                             });
    226                             c.add(l, GBC.eol());
    227                         }
    228 
    229                         Point p = mv.getLocationOnScreen();
    230                         popup = PopupFactory.getSharedInstance().getPopup(mv, c, p.x+ms.mousePos.x+16, p.y+ms.mousePos.y+16);
    231                         final Popup staticPopup = popup;
    232                         EventQueue.invokeLater(new Runnable(){
    233                             public void run() {
    234                                 staticPopup.show();
    235                             }
    236                         });
    237                     } else if (popup != null) {
    238                         final Popup staticPopup = popup;
    239                         popup = null;
    240                         EventQueue.invokeLater(new Runnable(){
    241                             public void run() {
    242                                 staticPopup.hide();
    243                             }
    244                         });
     225                        popupShowPopup(popupCreatePopup(c, ms), lbls);
     226                    } else {
     227                        popupHidePopup();
    245228                    }
     229
     230                    oldMousePos = ms.mousePos;
    246231                } catch (ConcurrentModificationException x) {
     232                    //x.printStackTrace();
    247233                } catch (NullPointerException x) {
    248                 }
    249             }
     234                    //x.printStackTrace();
     235                }
     236            }
     237        }
     238
     239        /**
     240         * Creates a popup for the given content next to the cursor. Tries to
     241         * keep the popup on screen.
     242         * @param content
     243         * @param ms
     244         * @return popup
     245         */
     246        private final Popup popupCreatePopup(Component content, MouseState ms) {
     247            Point p = mv.getLocationOnScreen();
     248            Dimension scrn = Toolkit.getDefaultToolkit().getScreenSize();
     249            Dimension dim = content.getPreferredSize();
     250
     251            int xPos = p.x + ms.mousePos.x + 16;
     252            // Display the popup to the left of the cursor if it would be cut
     253            // off on its right
     254            if(xPos + dim.width > scrn.width) {
     255                xPos = p.x + ms.mousePos.x - 4 - dim.width;
     256            }
     257            int yPos = p.y + ms.mousePos.y + 16;
     258            // Move the popup up if it would be cut off at its bottom
     259            if(yPos + dim.height > scrn.height - 5) {
     260                yPos = scrn.height - dim.height - 5;
     261            }
     262
     263            PopupFactory pf = PopupFactory.getSharedInstance();
     264            return pf.getPopup(mv, content, xPos, yPos);
     265        }
     266
     267        /**
     268         * Calls this to update the element that is shown in the statusbar
     269         * @param ms
     270         */
     271        private final void statusBarElementUpdate(MouseState ms) {
     272            final OsmPrimitive osmNearest = mv.getNearest(ms.mousePos);
     273            if (osmNearest != null) {
     274                nameText.setText(osmNearest.getDisplayName(DefaultNameFormatter.getInstance()));
     275            } else {
     276                nameText.setText(tr("(no object)"));
     277            }
     278        }
     279
     280        /**
     281         * Call this with a set of primitives to cycle through them. Method
     282         * will automatically select the next item and update the map
     283         * @param osms
     284         */
     285        private final void popupCycleSelection(Collection<OsmPrimitive> osms) {
     286            // Find some items that are required for cycling through
     287            OsmPrimitive firstItem = null;
     288            OsmPrimitive firstSelected = null;
     289            OsmPrimitive nextSelected = null;
     290            for (final OsmPrimitive osm : osms) {
     291                if(firstItem == null) {
     292                    firstItem = osm;
     293                }
     294                if(firstSelected != null && nextSelected == null) {
     295                    nextSelected = osm;
     296                }
     297                if(firstSelected == null && osm.isSelected()) {
     298                    firstSelected = osm;
     299                }
     300            }
     301
     302            // This will cycle through the available items.
     303            if(firstSelected == null) {
     304                firstItem.setSelected(true);
     305            } else {
     306                firstSelected.setSelected(false);
     307                if(nextSelected != null) {
     308                    nextSelected.setSelected(true);
     309                }
     310            }
     311            mv.repaint();
     312        }
     313
     314        /**
     315         * Tries to hide the given popup
     316         * @param popup
     317         */
     318        private final void popupHidePopup() {
     319            popupLabels = null;
     320            if(popup == null)
     321                return;
     322            final Popup staticPopup = popup;
     323            popup = null;
     324            EventQueue.invokeLater(new Runnable(){
     325                public void run() { staticPopup.hide(); }});
     326        }
     327
     328        /**
     329         * Tries to show the given popup, can be hidden using popupHideOldPopup
     330         * If an old popup exists, it will be automatically hidden
     331         * @param popup
     332         */
     333        private final void popupShowPopup(Popup newPopup, List<JLabel> lbls) {
     334            final Popup staticPopup = newPopup;
     335            if(this.popup != null) {
     336                // If an old popup exists, remove it when the new popup has been
     337                // drawn to keep flickering to a minimum
     338                final Popup staticOldPopup = this.popup;
     339                EventQueue.invokeLater(new Runnable(){
     340                    public void run() {
     341                        staticPopup.show();
     342                        staticOldPopup.hide();
     343                    }
     344                });
     345            } else {
     346                // There is no old popup
     347                EventQueue.invokeLater(new Runnable(){
     348                    public void run() { staticPopup.show(); }});
     349            }
     350            this.popupLabels = lbls;
     351            this.popup = newPopup;
     352        }
     353
     354        /**
     355         * This method should be called if the selection may have changed from
     356         * outside of this class. This is the case when CTRL is pressed and the
     357         * user clicks on the map instead of the popup.
     358         */
     359        private final void popupUpdateLabels() {
     360            if(this.popup == null || this.popupLabels == null)
     361                return;
     362            for(JLabel l : this.popupLabels) {
     363                l.validate();
     364            }
     365        }
     366
     367        /**
     368         * Sets the colors for the given label depending on the selected status of
     369         * the given OsmPrimitive
     370         *
     371         * @param lbl The label to color
     372         * @param osm The primitive to derive the colors from
     373         */
     374        private final void popupSetLabelColors(JLabel lbl, OsmPrimitive osm) {
     375            if(osm.isSelected()) {
     376                lbl.setBackground(SystemColor.textHighlight);
     377                lbl.setForeground(SystemColor.textHighlightText);
     378            } else {
     379                lbl.setBackground(SystemColor.control);
     380                lbl.setForeground(SystemColor.controlText);
     381            }
     382        }
     383
     384        /**
     385         * Builds the labels with all necessary listeners for the info popup for the
     386         * given OsmPrimitive
     387         * @param osm  The primitive to create the label for
     388         * @return
     389         */
     390        private final JLabel popupBuildPrimitiveLabels(final OsmPrimitive osm) {
     391            final StringBuilder text = new StringBuilder();
     392            String name = osm.getDisplayName(DefaultNameFormatter.getInstance());
     393            if (osm.getId() == 0 || osm.isModified()) {
     394                name = "<i><b>"+ osm.getDisplayName(DefaultNameFormatter.getInstance())+"*</b></i>";
     395            }
     396            text.append(name);
     397            if (osm.getId() != 0) {
     398                text.append("<br>id="+osm.getId());
     399            }
     400            for (Entry<String, String> e1 : osm.entrySet()) {
     401                text.append("<br>"+e1.getKey()+"="+e1.getValue());
     402            }
     403
     404            final JLabel l = new JLabel(
     405                    "<html>"+text.toString()+"</html>",
     406                    ImageProvider.get(OsmPrimitiveType.from(osm)),
     407                    JLabel.HORIZONTAL
     408            ) {
     409                // This is necessary so the label updates its colors when the
     410                // selection is changed from the outside
     411                @Override public void validate() {
     412                    super.validate();
     413                    popupSetLabelColors(this, osm);
     414                }
     415            };
     416            l.setOpaque(true);
     417            popupSetLabelColors(l, osm);
     418            l.setFont(l.getFont().deriveFont(Font.PLAIN));
     419            l.setVerticalTextPosition(JLabel.TOP);
     420            l.setHorizontalAlignment(JLabel.LEFT);
     421            l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
     422            l.addMouseListener(new MouseAdapter(){
     423                @Override public void mouseEntered(MouseEvent e) {
     424                    l.setBackground(SystemColor.info);
     425                    l.setForeground(SystemColor.infoText);
     426                }
     427                @Override public void mouseExited(MouseEvent e) {
     428                    popupSetLabelColors(l, osm);
     429                }
     430                @Override public void mouseClicked(MouseEvent e) {
     431                    // Let the user toggle the selection
     432                    osm.setSelected(!osm.isSelected());
     433                    mv.repaint();
     434                    l.validate();
     435                }
     436            });
     437            // Sometimes the mouseEntered event is not catched, thus the label
     438            // will not be highlighted, making it confusing. The MotionListener
     439            // can correct this defect.
     440            l.addMouseMotionListener(new MouseMotionListener() {
     441                public void mouseMoved(MouseEvent e) {
     442                    l.setBackground(SystemColor.info);
     443                    l.setForeground(SystemColor.infoText);
     444                }
     445                public void mouseDragged(MouseEvent e) {
     446                    l.setBackground(SystemColor.info);
     447                    l.setForeground(SystemColor.infoText);
     448                }
     449            });
     450            return l;
    250451        }
    251452    }
Note: See TracChangeset for help on using the changeset viewer.