001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.bbox;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Dimension;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.Collections;
012import java.util.Enumeration;
013import java.util.List;
014import java.util.Objects;
015
016import javax.swing.AbstractButton;
017import javax.swing.ButtonGroup;
018import javax.swing.ButtonModel;
019import javax.swing.JCheckBoxMenuItem;
020import javax.swing.JPopupMenu;
021import javax.swing.JRadioButtonMenuItem;
022import javax.swing.JToggleButton;
023
024import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
025import org.openstreetmap.josm.gui.widgets.PopupMenuButton;
026import org.openstreetmap.josm.tools.ImageProvider;
027
028/**
029 * Button that allows to choose the imagery source used for slippy map background.
030 * @since 1390
031 */
032public class SourceButton extends PopupMenuButton {
033    protected class TileSourceButtonModel extends JToggleButton.ToggleButtonModel implements ActionListener {
034        protected final TileSource tileSource;
035
036        public TileSourceButtonModel(TileSource tileSource) {
037            this.tileSource = tileSource;
038            this.addActionListener(this);
039        }
040
041        @Override
042        public void actionPerformed(ActionEvent e) {
043            if (SourceButton.this.slippyMapBBoxChooser.getTileController().getTileSource() != this.tileSource) { // prevent infinite recursion
044                SourceButton.this.slippyMapBBoxChooser.toggleMapSource(this.tileSource);
045            }
046        }
047    }
048
049    protected final SlippyMapBBoxChooser slippyMapBBoxChooser;
050    protected final ButtonModel showDownloadAreaButtonModel;
051    private List<TileSource> sources;
052    private ButtonGroup sourceButtonGroup;
053
054    /**
055     * Constructs a new {@code SourceButton}.
056     * @param slippyMapBBoxChooser parent slippy map
057     * @param sources list of imagery sources to display
058     * @param showDownloadAreaButtonModel model for the "Show downloaded area" button
059     * @since 12955
060     */
061    public SourceButton(
062        SlippyMapBBoxChooser slippyMapBBoxChooser,
063        Collection<TileSource> sources,
064        ButtonModel showDownloadAreaButtonModel
065    ) {
066        super(new ImageProvider("dialogs/layerlist").getResource().getImageIcon(new Dimension(16, 16)));
067        this.showDownloadAreaButtonModel = showDownloadAreaButtonModel;
068        this.slippyMapBBoxChooser = slippyMapBBoxChooser;
069        this.setPreferredSize(new Dimension(24, 24));
070        this.setSources(sources);
071    }
072
073    protected void generatePopupMenu() {
074        JPopupMenu pm = new JPopupMenu();
075        this.sourceButtonGroup = new ButtonGroup();
076        for (TileSource ts : this.sources) {
077            JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(ts.getName());
078            TileSourceButtonModel buttonModel = new TileSourceButtonModel(ts);
079            menuItem.setModel(buttonModel);
080            pm.add(menuItem);
081            this.sourceButtonGroup.add(menuItem);
082
083            // attempt to initialize button group matching current state of slippyMapBBoxChooser
084            buttonModel.setSelected(this.slippyMapBBoxChooser.getTileController().getTileSource() == ts);
085        }
086
087        pm.addSeparator();
088
089        JCheckBoxMenuItem showDownloadAreaItem = new JCheckBoxMenuItem(tr("Show downloaded area"));
090        showDownloadAreaItem.setModel(this.showDownloadAreaButtonModel);
091        pm.add(showDownloadAreaItem);
092
093        this.setPopupMenu(pm);
094    }
095
096    private void setSourceDefault() {
097        Enumeration<AbstractButton> elems = this.sourceButtonGroup.getElements();
098        if (elems.hasMoreElements()) {
099            elems.nextElement().setSelected(true);
100        }
101    }
102
103    /**
104     * Set the tile sources.
105     * @param sources The tile sources to display
106     * @since 6364
107     */
108    public final void setSources(Collection<TileSource> sources) {
109        this.sources = new ArrayList<>(Objects.requireNonNull(sources, "sources"));
110        this.generatePopupMenu();
111        if (this.sourceButtonGroup.getSelection() == null) {
112            this.setSourceDefault();
113        }
114    }
115
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);
122    }
123
124    /**
125     * Get the currently-selected tile source.
126     * @return currently-selected tile source
127     */
128    public final TileSource getCurrentSource() {
129        TileSourceButtonModel buttonModel = (TileSourceButtonModel) this.sourceButtonGroup.getSelection();
130        if (buttonModel != null) {
131            return buttonModel.tileSource;
132        }
133        return null;
134    }
135
136    /**
137     * Changes the current imagery source used for slippy map background.
138     * @param tileSource the new imagery source to use
139     */
140    public void setCurrentMap(TileSource tileSource) {
141        Enumeration<AbstractButton> elems = this.sourceButtonGroup.getElements();
142        while (elems.hasMoreElements()) {
143            AbstractButton b = elems.nextElement();
144            if (((TileSourceButtonModel) b.getModel()).tileSource == tileSource) {
145                b.setSelected(true);
146                return;
147            }
148        }
149        // failed to find the correct one
150        this.setSourceDefault();
151    }
152}