[7937] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 | import static org.openstreetmap.josm.tools.I18n.trc;
|
---|
| 6 |
|
---|
| 7 | import java.awt.Component;
|
---|
| 8 | import java.awt.GraphicsEnvironment;
|
---|
| 9 | import java.awt.MenuComponent;
|
---|
| 10 | import java.awt.event.ActionEvent;
|
---|
| 11 | import java.util.ArrayList;
|
---|
| 12 | import java.util.Collection;
|
---|
[7966] | 13 | import java.util.Comparator;
|
---|
[7937] | 14 | import java.util.Iterator;
|
---|
| 15 | import java.util.List;
|
---|
[8404] | 16 | import java.util.Locale;
|
---|
[11654] | 17 | import java.util.Optional;
|
---|
[8286] | 18 |
|
---|
[7937] | 19 | import javax.swing.Action;
|
---|
| 20 | import javax.swing.JComponent;
|
---|
| 21 | import javax.swing.JMenu;
|
---|
| 22 | import javax.swing.JMenuItem;
|
---|
| 23 | import javax.swing.JPopupMenu;
|
---|
| 24 | import javax.swing.event.MenuEvent;
|
---|
| 25 | import javax.swing.event.MenuListener;
|
---|
[8286] | 26 |
|
---|
[7937] | 27 | import org.openstreetmap.josm.actions.AddImageryLayerAction;
|
---|
| 28 | import org.openstreetmap.josm.actions.JosmAction;
|
---|
| 29 | import org.openstreetmap.josm.actions.MapRectifierWMSmenuAction;
|
---|
| 30 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
| 31 | import org.openstreetmap.josm.data.imagery.ImageryInfo;
|
---|
| 32 | import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
|
---|
| 33 | import org.openstreetmap.josm.data.imagery.Shape;
|
---|
| 34 | import org.openstreetmap.josm.gui.layer.ImageryLayer;
|
---|
[10345] | 35 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
|
---|
| 36 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
|
---|
| 37 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
|
---|
| 38 | import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
|
---|
[7937] | 39 | import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
|
---|
| 40 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
| 41 |
|
---|
| 42 | /**
|
---|
| 43 | * Imagery menu, holding entries for imagery preferences, offset actions and dynamic imagery entries
|
---|
[11348] | 44 | * depending on current mapview coordinates.
|
---|
[7937] | 45 | * @since 3737
|
---|
| 46 | */
|
---|
| 47 | public class ImageryMenu extends JMenu implements LayerChangeListener {
|
---|
| 48 |
|
---|
[11348] | 49 | static final class AdjustImageryOffsetAction extends JosmAction {
|
---|
[7966] | 50 |
|
---|
[11348] | 51 | AdjustImageryOffsetAction() {
|
---|
| 52 | super(tr("Imagery offset"), "mapmode/adjustimg", tr("Adjust imagery offset"), null, false, false);
|
---|
[7937] | 53 | putValue("toolbar", "imagery-offset");
|
---|
[12637] | 54 | MainApplication.getToolbar().register(this);
|
---|
[7937] | 55 | }
|
---|
[8510] | 56 |
|
---|
[7937] | 57 | @Override
|
---|
| 58 | public void actionPerformed(ActionEvent e) {
|
---|
[12636] | 59 | Collection<ImageryLayer> layers = MainApplication.getLayerManager().getLayersOfType(ImageryLayer.class);
|
---|
[7937] | 60 | if (layers.isEmpty()) {
|
---|
| 61 | setEnabled(false);
|
---|
| 62 | return;
|
---|
| 63 | }
|
---|
| 64 | Component source = null;
|
---|
| 65 | if (e.getSource() instanceof Component) {
|
---|
[8510] | 66 | source = (Component) e.getSource();
|
---|
[7937] | 67 | }
|
---|
| 68 | JPopupMenu popup = new JPopupMenu();
|
---|
| 69 | if (layers.size() == 1) {
|
---|
| 70 | JComponent c = layers.iterator().next().getOffsetMenuItem(popup);
|
---|
| 71 | if (c instanceof JMenuItem) {
|
---|
| 72 | ((JMenuItem) c).getAction().actionPerformed(e);
|
---|
| 73 | } else {
|
---|
| 74 | if (source == null) return;
|
---|
| 75 | popup.show(source, source.getWidth()/2, source.getHeight()/2);
|
---|
| 76 | }
|
---|
| 77 | return;
|
---|
| 78 | }
|
---|
[11203] | 79 | if (source == null || !source.isShowing()) return;
|
---|
[7937] | 80 | for (ImageryLayer layer : layers) {
|
---|
| 81 | JMenuItem layerMenu = layer.getOffsetMenuItem();
|
---|
| 82 | layerMenu.setText(layer.getName());
|
---|
| 83 | layerMenu.setIcon(layer.getIcon());
|
---|
| 84 | popup.add(layerMenu);
|
---|
| 85 | }
|
---|
| 86 | popup.show(source, source.getWidth()/2, source.getHeight()/2);
|
---|
| 87 | }
|
---|
[11348] | 88 | }
|
---|
[7937] | 89 |
|
---|
[11348] | 90 | /**
|
---|
| 91 | * Compare ImageryInfo objects alphabetically by name.
|
---|
| 92 | *
|
---|
| 93 | * ImageryInfo objects are normally sorted by country code first
|
---|
| 94 | * (for the preferences). We don't want this in the imagery menu.
|
---|
| 95 | */
|
---|
| 96 | public static final Comparator<ImageryInfo> alphabeticImageryComparator =
|
---|
| 97 | (ii1, ii2) -> ii1.getName().toLowerCase(Locale.ENGLISH).compareTo(ii2.getName().toLowerCase(Locale.ENGLISH));
|
---|
| 98 |
|
---|
| 99 | private final transient Action offsetAction = new AdjustImageryOffsetAction();
|
---|
| 100 |
|
---|
[7937] | 101 | private final JMenuItem singleOffset = new JMenuItem(offsetAction);
|
---|
| 102 | private JMenuItem offsetMenuItem = singleOffset;
|
---|
| 103 | private final MapRectifierWMSmenuAction rectaction = new MapRectifierWMSmenuAction();
|
---|
| 104 |
|
---|
| 105 | /**
|
---|
| 106 | * Constructs a new {@code ImageryMenu}.
|
---|
| 107 | * @param subMenu submenu in that contains plugin-managed additional imagery layers
|
---|
| 108 | */
|
---|
| 109 | public ImageryMenu(JMenu subMenu) {
|
---|
[9261] | 110 | /* I18N: mnemonic: I */
|
---|
[8792] | 111 | super(trc("menu", "Imagery"));
|
---|
[7937] | 112 | setupMenuScroller();
|
---|
[12636] | 113 | MainApplication.getLayerManager().addLayerChangeListener(this);
|
---|
[7937] | 114 | // build dynamically
|
---|
| 115 | addMenuListener(new MenuListener() {
|
---|
| 116 | @Override
|
---|
| 117 | public void menuSelected(MenuEvent e) {
|
---|
| 118 | refreshImageryMenu();
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | @Override
|
---|
| 122 | public void menuDeselected(MenuEvent e) {
|
---|
[10173] | 123 | // Do nothing
|
---|
[7937] | 124 | }
|
---|
| 125 |
|
---|
| 126 | @Override
|
---|
| 127 | public void menuCanceled(MenuEvent e) {
|
---|
[10173] | 128 | // Do nothing
|
---|
[7937] | 129 | }
|
---|
| 130 | });
|
---|
| 131 | MainMenu.add(subMenu, rectaction);
|
---|
| 132 | }
|
---|
| 133 |
|
---|
| 134 | private void setupMenuScroller() {
|
---|
| 135 | if (!GraphicsEnvironment.isHeadless()) {
|
---|
| 136 | MenuScroller.setScrollerFor(this, 150, 2);
|
---|
| 137 | }
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | /**
|
---|
| 141 | * Refresh imagery menu.
|
---|
| 142 | *
|
---|
| 143 | * Outside this class only called in {@link ImageryPreference#initialize()}.
|
---|
| 144 | * (In order to have actions ready for the toolbar, see #8446.)
|
---|
| 145 | */
|
---|
| 146 | public void refreshImageryMenu() {
|
---|
| 147 | removeDynamicItems();
|
---|
| 148 |
|
---|
| 149 | addDynamic(offsetMenuItem);
|
---|
| 150 | addDynamicSeparator();
|
---|
| 151 |
|
---|
| 152 | // for each configured ImageryInfo, add a menu entry.
|
---|
[7966] | 153 | final List<ImageryInfo> savedLayers = new ArrayList<>(ImageryLayerInfo.instance.getLayers());
|
---|
[10619] | 154 | savedLayers.sort(alphabeticImageryComparator);
|
---|
[7966] | 155 | for (final ImageryInfo u : savedLayers) {
|
---|
[7937] | 156 | addDynamic(new AddImageryLayerAction(u));
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | // list all imagery entries where the current map location
|
---|
| 160 | // is within the imagery bounds
|
---|
[12630] | 161 | if (MainApplication.isDisplayingMapView()) {
|
---|
| 162 | MapView mv = MainApplication.getMap().mapView;
|
---|
[7937] | 163 | LatLon pos = mv.getProjection().eastNorth2latlon(mv.getCenter());
|
---|
[7966] | 164 | final List<ImageryInfo> inViewLayers = new ArrayList<>();
|
---|
[7937] | 165 |
|
---|
| 166 | for (ImageryInfo i : ImageryLayerInfo.instance.getDefaultLayers()) {
|
---|
| 167 | if (i.getBounds() != null && i.getBounds().contains(pos)) {
|
---|
| 168 | inViewLayers.add(i);
|
---|
| 169 | }
|
---|
| 170 | }
|
---|
| 171 | // Do not suggest layers already in use
|
---|
| 172 | inViewLayers.removeAll(ImageryLayerInfo.instance.getLayers());
|
---|
| 173 | // For layers containing complex shapes, check that center is in one
|
---|
| 174 | // of its shapes (fix #7910)
|
---|
[8465] | 175 | for (Iterator<ImageryInfo> iti = inViewLayers.iterator(); iti.hasNext();) {
|
---|
[7937] | 176 | List<Shape> shapes = iti.next().getBounds().getShapes();
|
---|
| 177 | if (shapes != null && !shapes.isEmpty()) {
|
---|
| 178 | boolean found = false;
|
---|
[8465] | 179 | for (Iterator<Shape> its = shapes.iterator(); its.hasNext() && !found;) {
|
---|
[7937] | 180 | found = its.next().contains(pos);
|
---|
| 181 | }
|
---|
| 182 | if (!found) {
|
---|
| 183 | iti.remove();
|
---|
| 184 | }
|
---|
| 185 | }
|
---|
| 186 | }
|
---|
| 187 | if (!inViewLayers.isEmpty()) {
|
---|
[10619] | 188 | inViewLayers.sort(alphabeticImageryComparator);
|
---|
[7937] | 189 | addDynamicSeparator();
|
---|
| 190 | for (ImageryInfo i : inViewLayers) {
|
---|
| 191 | addDynamic(new AddImageryLayerAction(i));
|
---|
| 192 | }
|
---|
| 193 | }
|
---|
| 194 | }
|
---|
| 195 |
|
---|
| 196 | addDynamicSeparator();
|
---|
[12643] | 197 | JMenu subMenu = MainApplication.getMenu().imagerySubMenu;
|
---|
[7937] | 198 | int heightUnrolled = 30*(getItemCount()+subMenu.getItemCount());
|
---|
[12642] | 199 | if (heightUnrolled < MainApplication.getMainPanel().getHeight()) {
|
---|
[7937] | 200 | // add all items of submenu if they will fit on screen
|
---|
| 201 | int n = subMenu.getItemCount();
|
---|
[8510] | 202 | for (int i = 0; i < n; i++) {
|
---|
[7937] | 203 | addDynamic(subMenu.getItem(i).getAction());
|
---|
| 204 | }
|
---|
| 205 | } else {
|
---|
| 206 | // or add the submenu itself
|
---|
| 207 | addDynamic(subMenu);
|
---|
| 208 | }
|
---|
| 209 | }
|
---|
| 210 |
|
---|
[8510] | 211 | private JMenuItem getNewOffsetMenu() {
|
---|
[12636] | 212 | Collection<ImageryLayer> layers = MainApplication.getLayerManager().getLayersOfType(ImageryLayer.class);
|
---|
[7937] | 213 | if (layers.isEmpty()) {
|
---|
| 214 | offsetAction.setEnabled(false);
|
---|
| 215 | return singleOffset;
|
---|
| 216 | }
|
---|
| 217 | offsetAction.setEnabled(true);
|
---|
[8510] | 218 | JMenu newMenu = new JMenu(trc("layer", "Offset"));
|
---|
[7937] | 219 | newMenu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
|
---|
| 220 | newMenu.setAction(offsetAction);
|
---|
| 221 | if (layers.size() == 1)
|
---|
[8510] | 222 | return (JMenuItem) layers.iterator().next().getOffsetMenuItem(newMenu);
|
---|
[7937] | 223 | for (ImageryLayer layer : layers) {
|
---|
| 224 | JMenuItem layerMenu = layer.getOffsetMenuItem();
|
---|
| 225 | layerMenu.setText(layer.getName());
|
---|
| 226 | layerMenu.setIcon(layer.getIcon());
|
---|
| 227 | newMenu.add(layerMenu);
|
---|
| 228 | }
|
---|
| 229 | return newMenu;
|
---|
| 230 | }
|
---|
| 231 |
|
---|
[11348] | 232 | /**
|
---|
| 233 | * Refresh offset menu item.
|
---|
| 234 | */
|
---|
[7937] | 235 | public void refreshOffsetMenu() {
|
---|
| 236 | offsetMenuItem = getNewOffsetMenu();
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | @Override
|
---|
[10345] | 240 | public void layerAdded(LayerAddEvent e) {
|
---|
| 241 | if (e.getAddedLayer() instanceof ImageryLayer) {
|
---|
| 242 | refreshOffsetMenu();
|
---|
| 243 | }
|
---|
[7937] | 244 | }
|
---|
| 245 |
|
---|
| 246 | @Override
|
---|
[10345] | 247 | public void layerRemoving(LayerRemoveEvent e) {
|
---|
| 248 | if (e.getRemovedLayer() instanceof ImageryLayer) {
|
---|
[7937] | 249 | refreshOffsetMenu();
|
---|
| 250 | }
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | @Override
|
---|
[10345] | 254 | public void layerOrderChanged(LayerOrderChangeEvent e) {
|
---|
| 255 | refreshOffsetMenu();
|
---|
[7937] | 256 | }
|
---|
| 257 |
|
---|
| 258 | /**
|
---|
| 259 | * Collection to store temporary menu items. They will be deleted
|
---|
| 260 | * (and possibly recreated) when refreshImageryMenu() is called.
|
---|
| 261 | * @since 5803
|
---|
| 262 | */
|
---|
[9078] | 263 | private final List<Object> dynamicItems = new ArrayList<>(20);
|
---|
[7937] | 264 |
|
---|
| 265 | /**
|
---|
[11654] | 266 | * Remove all the items in dynamic items collection
|
---|
[7937] | 267 | * @since 5803
|
---|
| 268 | */
|
---|
| 269 | private void removeDynamicItems() {
|
---|
| 270 | for (Object item : dynamicItems) {
|
---|
| 271 | if (item instanceof JMenuItem) {
|
---|
[12637] | 272 | Optional.ofNullable(((JMenuItem) item).getAction()).ifPresent(MainApplication.getToolbar()::unregister);
|
---|
[8510] | 273 | remove((JMenuItem) item);
|
---|
[11654] | 274 | } else if (item instanceof MenuComponent) {
|
---|
[8510] | 275 | remove((MenuComponent) item);
|
---|
[11654] | 276 | } else if (item instanceof Component) {
|
---|
[8510] | 277 | remove((Component) item);
|
---|
[7937] | 278 | }
|
---|
| 279 | }
|
---|
| 280 | dynamicItems.clear();
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | private void addDynamicSeparator() {
|
---|
[10378] | 284 | JPopupMenu.Separator s = new JPopupMenu.Separator();
|
---|
[7937] | 285 | dynamicItems.add(s);
|
---|
| 286 | add(s);
|
---|
| 287 | }
|
---|
| 288 |
|
---|
| 289 | private void addDynamic(Action a) {
|
---|
[8443] | 290 | dynamicItems.add(this.add(a));
|
---|
[7937] | 291 | }
|
---|
| 292 |
|
---|
| 293 | private void addDynamic(JMenuItem it) {
|
---|
[8443] | 294 | dynamicItems.add(this.add(it));
|
---|
[7937] | 295 | }
|
---|
| 296 | }
|
---|