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

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

    From 4e0398ffdb88d1cfd4b6118846f48dfe49e251f8 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/2] 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        |  30 ++-
     .../openstreetmap/josm/gui/bbox/SourceButton.java  | 223 +++++++++------------
     2 files changed, 125 insertions(+), 128 deletions(-)
    
    diff --git a/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java b/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
    index 6635f4f..0bdc219 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.data.imagery.ImageryLayerInfo;  
    4347import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader;
    4448import org.openstreetmap.josm.data.imagery.TileLoaderFactory;
    4549import org.openstreetmap.josm.data.osm.BBox;
     50import org.openstreetmap.josm.data.preferences.BooleanProperty;
    4651import org.openstreetmap.josm.data.preferences.StringProperty;
    4752import org.openstreetmap.josm.gui.MainApplication;
    4853import org.openstreetmap.josm.gui.layer.AbstractCachedTileSourceLayer;
    import org.openstreetmap.josm.tools.Logging;  
    5560/**
    5661 * This panel displays a map and lets the user chose a {@link BBox}.
    5762 */
    58 public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, MainLayerManager.ActiveLayerChangeListener {
     63public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, ChangeListener, MainLayerManager.ActiveLayerChangeListener {
    5964
    6065    /**
    6166     * A list of tile sources that can be used for displaying the map.
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    120125    }
    121126
    122127    private static final StringProperty PROP_MAPSTYLE = new StringProperty("slippy_map_chooser.mapstyle", "Mapnik");
     128    private static final BooleanProperty PROP_SHOWDLAREA = new BooleanProperty("slippy_map_chooser.show_downloaded_area", true);
    123129    /**
    124130     * The property name used for the resize button.
    125131     * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    130136    private final transient OsmTileLoader uncachedLoader;
    131137
    132138    private final SizeButton iSizeButton;
     139    private final ButtonModel showDownloadAreaButtonModel;
    133140    private final SourceButton iSourceButton;
    134141    private transient Bounds bbox;
    135142
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    172179
    173180        List<TileSource> tileSources = getAllTileSources();
    174181
    175         iSourceButton = new SourceButton(this, tileSources);
     182        this.showDownloadAreaButtonModel = new JToggleButton.ToggleButtonModel();
     183        this.showDownloadAreaButtonModel.setSelected(PROP_SHOWDLAREA.get());
     184        this.showDownloadAreaButtonModel.addChangeListener(this);
     185        iSourceButton = new SourceButton(this, tileSources, this.showDownloadAreaButtonModel);
    176186        add(iSourceButton);
    177         springLayout.putConstraint(SpringLayout.EAST, iSourceButton, 0, SpringLayout.EAST, this);
    178         springLayout.putConstraint(SpringLayout.NORTH, iSourceButton, 30, SpringLayout.NORTH, this);
     187        springLayout.putConstraint(SpringLayout.EAST, iSourceButton, -2, SpringLayout.EAST, this);
     188        springLayout.putConstraint(SpringLayout.NORTH, iSourceButton, 2, SpringLayout.NORTH, this);
    179189
    180190        iSizeButton = new SizeButton(this);
    181191        add(iSizeButton);
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    230240        // and it has defined bounds. Routine is analogous to that in OsmDataLayer's paint routine (but just different
    231241        // enough to make sharing code impractical)
    232242        final OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
    233         if (editLayer != null && Config.getPref().getBoolean("draw.data.downloaded_area", true) && !editLayer.data.getDataSources().isEmpty()) {
     243        if (editLayer != null && this.showDownloadAreaButtonModel.isSelected() && !editLayer.data.getDataSources().isEmpty()) {
    234244            // initialize area with current viewport
    235245            Rectangle b = this.getBounds();
    236246            // ensure we comfortably cover full area
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    273283        this.repaint();
    274284    }
    275285
     286    @Override
     287    public void stateChanged(ChangeEvent e) {
     288        // fired for the stateChanged event of this.showDownloadAreaButtonModel
     289        PROP_SHOWDLAREA.put(this.showDownloadAreaButtonModel.isSelected());
     290        this.repaint();
     291    }
     292
    276293    /**
    277294     * Enables the disk tile cache.
    278295     * @param enabled true to enable, false to disable
    public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, Mai  
    341358        this.tileController.setTileCache(new MemoryTileCache());
    342359        this.setTileSource(tileSource);
    343360        PROP_MAPSTYLE.put(tileSource.getName()); // TODO Is name really unique?
     361        if (this.iSourceButton.getCurrentSource() != tileSource) { // prevent infinite recursion
     362            this.iSourceButton.setCurrentMap(tileSource);
     363        }
    344364    }
    345365
    346366    @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..adb5e77 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;
    16 
    17 import javax.swing.ImageIcon;
    18 import javax.swing.JComponent;
     11import java.util.Collections;
     12import java.util.Enumeration;
     13import java.util.List;
     14
     15import javax.swing.AbstractButton;
     16import javax.swing.ButtonGroup;
     17import javax.swing.ButtonModel;
     18import javax.swing.JCheckBoxMenuItem;
     19import javax.swing.JPopupMenu;
     20import javax.swing.JRadioButtonMenuItem;
     21import javax.swing.JToggleButton;
    1922
    2023import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
     24import org.openstreetmap.josm.gui.widgets.PopupMenuButton;
    2125import org.openstreetmap.josm.tools.CheckParameterUtil;
    2226import org.openstreetmap.josm.tools.ImageProvider;
    2327
    import org.openstreetmap.josm.tools.ImageProvider;  
    2529 * Button that allows to choose the imagery source used for slippy map background.
    2630 * @since 1390
    2731 */
    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;
     32public class SourceButton extends PopupMenuButton {
     33    protected class TileSourceButtonModel extends JToggleButton.ToggleButtonModel implements ActionListener {
     34        protected final TileSource tileSource;
     35
     36        public TileSourceButtonModel(TileSource tileSource_) {
     37            super();
     38            this.tileSource = tileSource_;
     39            this.addActionListener(this);
     40        }
    4541
    46     private boolean isEnlarged;
     42        @Override
     43        public void actionPerformed(ActionEvent e) {
     44            if (SourceButton.this.slippyMapBBoxChooser.getTileController().getTileSource() != this.tileSource) { // prevent infinite recursion
     45                SourceButton.this.slippyMapBBoxChooser.toggleMapSource(this.tileSource);
     46            }
     47        }
     48    }
    4749
    48     private int currentMap;
    49     private final SlippyMapBBoxChooser slippyMapBBoxChooser;
     50    protected final SlippyMapBBoxChooser slippyMapBBoxChooser;
     51    protected final ButtonModel showDownloadAreaButtonModel;
     52    private List<TileSource> sources;
     53    private ButtonGroup sourceButtonGroup;
    5054
    5155    /**
    5256     * Constructs a new {@code SourceButton}.
    5357     * @param slippyMapBBoxChooser parent slippy map
    5458     * @param sources list of imagery sources to display
    5559     */
    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");
     60    public SourceButton(
     61        SlippyMapBBoxChooser slippyMapBBoxChooser_,
     62        Collection<TileSource> sources_,
     63        ButtonModel showDownloadAreaButtonModel_
     64    ) {
     65        super(new ImageProvider("dialogs/layerlist").getResource().getImageIcon(new Dimension(16, 16)));
     66        this.showDownloadAreaButtonModel = showDownloadAreaButtonModel_;
     67        this.slippyMapBBoxChooser = slippyMapBBoxChooser_;
     68        this.setPreferredSize(new Dimension(24, 24));
     69        this.setSources(sources_);
     70    }
     71
     72    protected void generatePopupMenu() {
     73        JPopupMenu pm = new JPopupMenu();
     74        this.sourceButtonGroup = new ButtonGroup();
     75        for (TileSource ts : this.sources) {
     76            JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(ts.getName());
     77            TileSourceButtonModel buttonModel = new TileSourceButtonModel(ts);
     78            menuItem.setModel(buttonModel);
     79            pm.add(menuItem);
     80            this.sourceButtonGroup.add(menuItem);
     81
     82            // attempt to initialize button group matching current state of slippyMapBBoxChooser
     83            buttonModel.setSelected(this.slippyMapBBoxChooser.getTileController().getTileSource() == ts);
     84        }
    6185
    62         hiddenDimension = new Dimension(enlargeImage.getIconWidth(), enlargeImage.getIconHeight());
    63         setPreferredSize(hiddenDimension);
     86        pm.addSeparator();
    6487
    65         addMouseListener(mouseListener);
     88        JCheckBoxMenuItem showDownloadAreaItem = new JCheckBoxMenuItem(tr("Show downloaded area"));
     89        showDownloadAreaItem.setModel(this.showDownloadAreaButtonModel);
     90        pm.add(showDownloadAreaItem);
     91
     92        this.setPopupMenu(pm);
    6693    }
    6794
    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             }
     95    private void setSourceDefault() {
     96        Enumeration<AbstractButton> elems = this.sourceButtonGroup.getElements();
     97        if (elems.hasMoreElements()) {
     98            elems.nextElement().setSelected(true);
    8899        }
    89     };
     100    }
    90101
    91102    /**
    92103     * Set the tile sources.
    93104     * @param sources The tile sources to display
    94105     * @since 6364
    95106     */
    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;
     107    public final void setSources(Collection<TileSource> sources_) {
     108        CheckParameterUtil.ensureParameterNotNull(sources_, "sources_");
     109        this.sources = new ArrayList<TileSource>(sources_);
     110        this.generatePopupMenu();
     111        if (this.sourceButtonGroup.getSelection() == null) {
     112            this.setSourceDefault();
     113        }
    100114    }
    101115
    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         }
     116    /**
     117     * Get the tile sources.
     118     * @return unmodifiable collection of tile sources
     119     */
     120    public final Collection<TileSource> getSources() {
     121        return Collections.unmodifiableCollection(this.sources);
    133122    }
    134123
    135124    /**
    136      * Toggle the visibility of imagery source list.
     125     * Get the currently-selected tile source.
    137126     */
    138     public void toggle() {
    139         this.isEnlarged = !this.isEnlarged;
    140         calculateShownDimension();
    141         setPreferredSize(isEnlarged ? shownDimension : hiddenDimension);
    142         revalidate();
     127    public final TileSource getCurrentSource() {
     128        TileSourceButtonModel buttonModel = (TileSourceButtonModel) this.sourceButtonGroup.getSelection();
     129        if (buttonModel != null) {
     130            return buttonModel.tileSource;
     131        }
     132        return null;
    143133    }
    144134
    145135    /**
    public class SourceButton extends JComponent {  
    147137     * @param tileSource the new imagery source to use
    148138     */
    149139    public void setCurrentMap(TileSource tileSource) {
    150         for (int i = 0; i < sources.length; i++) {
    151             if (sources[i].equals(tileSource)) {
    152                 currentMap = i;
     140        Enumeration<AbstractButton> elems = this.sourceButtonGroup.getElements();
     141        while (elems.hasMoreElements()) {
     142            AbstractButton b = elems.nextElement();
     143            if (((TileSourceButtonModel) b.getModel()).tileSource == tileSource) {
     144                b.setSelected(true);
    153145                return;
    154146            }
    155147        }
    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         }
     148        // failed to find the correct one
     149        this.setSourceDefault();
    173150    }
    174151}