Ticket #15414: v1-0002-SlippyMapBBoxChooser-redesign-SourceButton-using-a-r.patch

File v1-0002-SlippyMapBBoxChooser-redesign-SourceButton-using-a-r.patch, 15.6 KB (added by ris, 7 years ago)
  • src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java

    From de6d4e61e53bc79f3b08f3cea0aca215aeef8f6d Mon Sep 17 00:00:00 2001
    From: Robert Scott <code@humanleg.org.uk>
    Date: Sun, 8 Oct 2017 10:28:26 +0100
    Subject: [PATCH 2/3] SlippyMapBBoxChooser: redesign SourceButton using a
     regular drop-down JPopupMenu
    
    this behaves more similarly to other ui components in the application and allows
    for more extensibility in that we're now able to allow the "show downloaded area"
    feature to be enabled or disabled through a simple JCheckBoxMenuItem
    ---
     .../josm/gui/bbox/SlippyMapBBoxChooser.java        |  24 ++-
     .../openstreetmap/josm/gui/bbox/SourceButton.java  | 216 +++++++++------------
     2 files changed, 113 insertions(+), 127 deletions(-)
    
    diff --git a/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java b/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
    index 6635f4f..93d4358 100644
    a b import java.util.Map;  
    2121import java.util.Set;
    2222import java.util.concurrent.CopyOnWriteArrayList;
    2323
     24import javax.swing.ButtonModel;
     25import javax.swing.JToggleButton;
    2426import javax.swing.JOptionPane;
    2527import javax.swing.SpringLayout;
     28import javax.swing.event.ChangeListener;
     29import javax.swing.event.ChangeEvent;
    2630
    2731import org.openstreetmap.gui.jmapviewer.Coordinate;
    2832import org.openstreetmap.gui.jmapviewer.JMapViewer;
    import org.openstreetmap.josm.tools.Logging;  
    5559/**
    5660 * This panel displays a map and lets the user chose a {@link BBox}.
    5761 */
    58 public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, MainLayerManager.ActiveLayerChangeListener {
     62public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, ChangeListener, MainLayerManager.ActiveLayerChangeListener {
    5963
    6064    /**
    6165     * A list of tile sources that can be used for displaying the map.
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    130134    private final transient OsmTileLoader uncachedLoader;
    131135
    132136    private final SizeButton iSizeButton;
     137    private final ButtonModel showDownloadAreaButtonModel = new JToggleButton.ToggleButtonModel();
    133138    private final SourceButton iSourceButton;
    134139    private transient Bounds bbox;
    135140
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    172177
    173178        List<TileSource> tileSources = getAllTileSources();
    174179
    175         iSourceButton = new SourceButton(this, tileSources);
     180        this.showDownloadAreaButtonModel.addChangeListener(this);
     181        iSourceButton = new SourceButton(this, tileSources, this.showDownloadAreaButtonModel);
    176182        add(iSourceButton);
    177         springLayout.putConstraint(SpringLayout.EAST, iSourceButton, 0, SpringLayout.EAST, this);
    178         springLayout.putConstraint(SpringLayout.NORTH, iSourceButton, 30, SpringLayout.NORTH, this);
     183        springLayout.putConstraint(SpringLayout.EAST, iSourceButton, -2, SpringLayout.EAST, this);
     184        springLayout.putConstraint(SpringLayout.NORTH, iSourceButton, 2, SpringLayout.NORTH, this);
    179185
    180186        iSizeButton = new SizeButton(this);
    181187        add(iSizeButton);
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    230236        // and it has defined bounds. Routine is analogous to that in OsmDataLayer's paint routine (but just different
    231237        // enough to make sharing code impractical)
    232238        final OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
    233         if (editLayer != null && Config.getPref().getBoolean("draw.data.downloaded_area", true) && !editLayer.data.getDataSources().isEmpty()) {
     239        if (editLayer != null && this.showDownloadAreaButtonModel.isSelected() && !editLayer.data.getDataSources().isEmpty()) {
    234240            // initialize area with current viewport
    235241            Rectangle b = this.getBounds();
    236242            // ensure we comfortably cover full area
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    273279        this.repaint();
    274280    }
    275281
     282    @Override
     283    public void stateChanged(ChangeEvent e) {
     284        this.repaint();
     285    }
     286
    276287    /**
    277288     * Enables the disk tile cache.
    278289     * @param enabled true to enable, false to disable
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    341352        this.tileController.setTileCache(new MemoryTileCache());
    342353        this.setTileSource(tileSource);
    343354        PROP_MAPSTYLE.put(tileSource.getName()); // TODO Is name really unique?
     355        if (this.iSourceButton.getCurrentSource() != tileSource) { // prevent infinite recursion
     356            this.iSourceButton.setCurrentMap(tileSource);
     357        }
    344358    }
    345359
    346360    @Override
  • src/org/openstreetmap/josm/gui/bbox/SourceButton.java

    diff --git a/src/org/openstreetmap/josm/gui/bbox/SourceButton.java b/src/org/openstreetmap/josm/gui/bbox/SourceButton.java
    index 6c995a1..8b44a39 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.bbox;
    33
    4 import java.awt.Color;
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
    56import java.awt.Dimension;
    6 import java.awt.Font;
    7 import java.awt.FontMetrics;
    8 import java.awt.Graphics;
    9 import java.awt.Graphics2D;
    10 import java.awt.Point;
    11 import java.awt.RenderingHints;
    12 import java.awt.event.MouseAdapter;
    13 import java.awt.event.MouseEvent;
    14 import java.awt.event.MouseListener;
     7import java.awt.event.ActionListener;
     8import java.awt.event.ActionEvent;
     9import java.util.ArrayList;
    1510import java.util.Collection;
     11import java.util.Collections;
     12import java.util.Enumeration;
     13import java.util.List;
    1614
     15import javax.swing.AbstractButton;
     16import javax.swing.ButtonGroup;
     17import javax.swing.ButtonModel;
    1718import javax.swing.ImageIcon;
    1819import javax.swing.JComponent;
     20import javax.swing.JCheckBoxMenuItem;
     21import javax.swing.JPopupMenu;
     22import javax.swing.JRadioButtonMenuItem;
     23import javax.swing.JToggleButton;
    1924
    2025import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
     26import org.openstreetmap.josm.gui.widgets.PopupMenuButton;
    2127import org.openstreetmap.josm.tools.CheckParameterUtil;
    2228import org.openstreetmap.josm.tools.ImageProvider;
    2329
    import org.openstreetmap.josm.tools.ImageProvider;  
    2531 * Button that allows to choose the imagery source used for slippy map background.
    2632 * @since 1390
    2733 */
    28 public class SourceButton extends JComponent {
    29 
    30     private static final int LAYER_HEIGHT = 20;
    31     private static final int LEFT_PADDING = 5;
    32     private static final int TOP_PADDING = 5;
    33     private static final int BOTTOM_PADDING = 5;
    34 
    35     private transient TileSource[] sources;
    36 
    37     private final ImageIcon enlargeImage;
    38     private final ImageIcon shrinkImage;
    39     private final Dimension hiddenDimension;
    40 
    41     // Calculated after component is added to container
    42     private int barWidth;
    43     private Dimension shownDimension;
    44     private Font font;
     34public class SourceButton extends PopupMenuButton {
     35    protected class TileSourceButtonModel extends JToggleButton.ToggleButtonModel implements ActionListener {
     36        protected final TileSource tileSource;
     37
     38        public TileSourceButtonModel(TileSource tileSource_) {
     39            super();
     40            this.tileSource = tileSource_;
     41            this.addActionListener(this);
     42        }
    4543
    46     private boolean isEnlarged;
     44        @Override
     45        public void actionPerformed(ActionEvent e) {
     46            if (SourceButton.this.slippyMapBBoxChooser.getTileController().getTileSource() != this.tileSource) { // prevent infinite recursion
     47                SourceButton.this.slippyMapBBoxChooser.toggleMapSource(this.tileSource);
     48            }
     49        }
     50    }
    4751
    48     private int currentMap;
    49     private final SlippyMapBBoxChooser slippyMapBBoxChooser;
     52    protected final SlippyMapBBoxChooser slippyMapBBoxChooser;
     53    protected final ButtonModel showDownloadAreaButtonModel;
     54    private List<TileSource> sources;
     55    private ButtonGroup sourceButtonGroup;
    5056
    5157    /**
    5258     * Constructs a new {@code SourceButton}.
    5359     * @param slippyMapBBoxChooser parent slippy map
    5460     * @param sources list of imagery sources to display
    5561     */
    56     public SourceButton(SlippyMapBBoxChooser slippyMapBBoxChooser, Collection<TileSource> sources) {
    57         this.slippyMapBBoxChooser = slippyMapBBoxChooser;
    58         setSources(sources);
    59         enlargeImage = ImageProvider.get("layer-switcher-maximize");
    60         shrinkImage = ImageProvider.get("layer-switcher-minimize");
     62    public SourceButton(
     63        SlippyMapBBoxChooser slippyMapBBoxChooser_,
     64        Collection<TileSource> sources_,
     65        ButtonModel showDownloadAreaButtonModel_
     66    ) {
     67        super(new ImageProvider("dialogs/layerlist").getResource().getImageIcon(new Dimension(16, 16)));
     68        this.showDownloadAreaButtonModel = showDownloadAreaButtonModel_;
     69        this.slippyMapBBoxChooser = slippyMapBBoxChooser_;
     70        this.setPreferredSize(new Dimension(24, 24));
     71        this.setSources(sources_);
     72    }
     73
     74    protected void generatePopupMenu() {
     75        JPopupMenu pm = new JPopupMenu();
     76        this.sourceButtonGroup = new ButtonGroup();
     77        for (TileSource ts : this.sources) {
     78            JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(ts.getName());
     79            TileSourceButtonModel buttonModel = new TileSourceButtonModel(ts);
     80            menuItem.setModel(buttonModel);
     81            pm.add(menuItem);
     82            this.sourceButtonGroup.add(menuItem);
     83
     84            // attempt to initialize button group matching current state of slippyMapBBoxChooser
     85            buttonModel.setSelected(this.slippyMapBBoxChooser.getTileController().getTileSource() == ts);
     86        }
     87
     88        pm.addSeparator();
    6189
    62         hiddenDimension = new Dimension(enlargeImage.getIconWidth(), enlargeImage.getIconHeight());
    63         setPreferredSize(hiddenDimension);
     90        JCheckBoxMenuItem showDownloadAreaItem = new JCheckBoxMenuItem(tr("Show downloaded area"));
     91        showDownloadAreaItem.setModel(this.showDownloadAreaButtonModel);
     92        pm.add(showDownloadAreaItem);
    6493
    65         addMouseListener(mouseListener);
     94        this.setPopupMenu(pm);
    6695    }
    6796
    68     private final transient MouseListener mouseListener = new MouseAdapter() {
    69         @Override
    70         public void mouseReleased(MouseEvent e) {
    71             if (e.getButton() == MouseEvent.BUTTON1) {
    72                 Point point = e.getPoint();
    73                 if (isEnlarged) {
    74                     if (barWidth < point.x && point.y < shrinkImage.getIconHeight()) {
    75                         toggle();
    76                     } else {
    77                         int result = (point.y - 5) / LAYER_HEIGHT;
    78                         if (result >= 0 && result < SourceButton.this.sources.length) {
    79                             SourceButton.this.slippyMapBBoxChooser.toggleMapSource(SourceButton.this.sources[result]);
    80                             currentMap = result;
    81                             toggle();
    82                         }
    83                     }
    84                 } else {
    85                     toggle();
    86                 }
    87             }
     97    private void setSourceDefault() {
     98        Enumeration<AbstractButton> elems = this.sourceButtonGroup.getElements();
     99        if (elems.hasMoreElements()) {
     100            elems.nextElement().setSelected(true);
    88101        }
    89     };
     102    }
    90103
    91104    /**
    92105     * Set the tile sources.
    93106     * @param sources The tile sources to display
    94107     * @since 6364
    95108     */
    96     public final void setSources(Collection<TileSource> sources) {
    97         CheckParameterUtil.ensureParameterNotNull(sources, "sources");
    98         this.sources = sources.toArray(new TileSource[sources.size()]);
    99         shownDimension = null;
     109    public final void setSources(Collection<TileSource> sources_) {
     110        CheckParameterUtil.ensureParameterNotNull(sources_, "sources_");
     111        this.sources = new ArrayList<TileSource>(sources_);
     112        this.generatePopupMenu();
     113        if (this.sourceButtonGroup.getSelection() == null) {
     114            this.setSourceDefault();
     115        }
    100116    }
    101117
    102     @Override
    103     protected void paintComponent(Graphics graphics) {
    104         Graphics2D g = (Graphics2D) graphics.create();
    105         try {
    106             calculateShownDimension();
    107             g.setFont(font);
    108             if (isEnlarged) {
    109                 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    110                 int radioButtonSize = 10;
    111 
    112                 g.setColor(new Color(0, 0, 139, 179));
    113                 g.fillRoundRect(0, 0, barWidth + shrinkImage.getIconWidth(),
    114                         sources.length * LAYER_HEIGHT + TOP_PADDING + BOTTOM_PADDING, 10, 10);
    115                 for (int i = 0; i < sources.length; i++) {
    116                     g.setColor(Color.WHITE);
    117                     g.fillOval(LEFT_PADDING, TOP_PADDING + i * LAYER_HEIGHT + 6, radioButtonSize, radioButtonSize);
    118                     g.drawString(sources[i].getName(), LEFT_PADDING + radioButtonSize + LEFT_PADDING,
    119                             TOP_PADDING + i * LAYER_HEIGHT + g.getFontMetrics().getHeight());
    120                     if (currentMap == i) {
    121                         g.setColor(Color.BLACK);
    122                         g.fillOval(LEFT_PADDING + 1, TOP_PADDING + 7 + i * LAYER_HEIGHT, radioButtonSize - 2, radioButtonSize - 2);
    123                     }
    124                 }
    125 
    126                 g.drawImage(shrinkImage.getImage(), barWidth, 0, null);
    127             } else {
    128                 g.drawImage(enlargeImage.getImage(), 0, 0, null);
    129             }
    130         } finally {
    131             g.dispose();
    132         }
     118    public final Collection<TileSource> getSources() {
     119        return Collections.unmodifiableCollection(this.sources);
    133120    }
    134121
    135     /**
    136      * Toggle the visibility of imagery source list.
    137      */
    138     public void toggle() {
    139         this.isEnlarged = !this.isEnlarged;
    140         calculateShownDimension();
    141         setPreferredSize(isEnlarged ? shownDimension : hiddenDimension);
    142         revalidate();
     122    public final TileSource getCurrentSource() {
     123        TileSourceButtonModel buttonModel = (TileSourceButtonModel) this.sourceButtonGroup.getSelection();
     124        if (buttonModel != null) {
     125            return buttonModel.tileSource;
     126        }
     127        return null;
    143128    }
    144129
    145130    /**
    public class SourceButton extends JComponent {  
    147132     * @param tileSource the new imagery source to use
    148133     */
    149134    public void setCurrentMap(TileSource tileSource) {
    150         for (int i = 0; i < sources.length; i++) {
    151             if (sources[i].equals(tileSource)) {
    152                 currentMap = i;
     135        Enumeration<AbstractButton> elems = this.sourceButtonGroup.getElements();
     136        while (elems.hasMoreElements()) {
     137            AbstractButton b = elems.nextElement();
     138            if (((TileSourceButtonModel) b.getModel()).tileSource == tileSource) {
     139                b.setSelected(true);
    153140                return;
    154141            }
    155142        }
    156         currentMap = 0;
    157     }
    158 
    159     private void calculateShownDimension() {
    160         if (shownDimension == null) {
    161             font = getFont().deriveFont(Font.BOLD).deriveFont(15.0f);
    162             int textWidth = 0;
    163             FontMetrics fm = getFontMetrics(font);
    164             for (TileSource source: sources) {
    165                 int width = fm.stringWidth(source.getName());
    166                 if (width > textWidth) {
    167                     textWidth = width;
    168                 }
    169             }
    170             barWidth = textWidth + 50;
    171             shownDimension = new Dimension(barWidth + shrinkImage.getIconWidth(), sources.length * LAYER_HEIGHT + TOP_PADDING + BOTTOM_PADDING);
    172         }
     143        // failed to find the correct one
     144        this.setSourceDefault();
    173145    }
    174146}