001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trc; 006 007import java.awt.Component; 008import java.awt.GraphicsEnvironment; 009import java.awt.MenuComponent; 010import java.awt.event.ActionEvent; 011import java.util.ArrayList; 012import java.util.Collection; 013import java.util.Iterator; 014import java.util.List; 015import java.util.Set; 016import java.util.TreeSet; 017 018import javax.swing.Action; 019import javax.swing.JComponent; 020import javax.swing.JMenu; 021import javax.swing.JMenuItem; 022import javax.swing.JPopupMenu; 023import javax.swing.MenuElement; 024import javax.swing.event.MenuEvent; 025import javax.swing.event.MenuListener; 026 027import org.openstreetmap.josm.Main; 028import org.openstreetmap.josm.actions.AddImageryLayerAction; 029import org.openstreetmap.josm.actions.JosmAction; 030import org.openstreetmap.josm.actions.MapRectifierWMSmenuAction; 031import org.openstreetmap.josm.data.coor.LatLon; 032import org.openstreetmap.josm.data.imagery.ImageryInfo; 033import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; 034import org.openstreetmap.josm.data.imagery.Shape; 035import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 036import org.openstreetmap.josm.gui.layer.ImageryLayer; 037import org.openstreetmap.josm.gui.layer.Layer; 038import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference; 039import org.openstreetmap.josm.tools.ImageProvider; 040 041/** 042 * Imagery menu, holding entries for imagery preferences, offset actions and dynamic imagery entries 043 * depending on current maview coordinates. 044 * @since 3737 045 */ 046public class ImageryMenu extends JMenu implements LayerChangeListener { 047 048 private Action offsetAction = new JosmAction( 049 tr("Imagery offset"), "mapmode/adjustimg", tr("Adjust imagery offset"), null, false, false) { 050 { 051 putValue("toolbar", "imagery-offset"); 052 Main.toolbar.register(this); 053 } 054 @Override 055 public void actionPerformed(ActionEvent e) { 056 Collection<ImageryLayer> layers = Main.map.mapView.getLayersOfType(ImageryLayer.class); 057 if (layers.isEmpty()) { 058 setEnabled(false); 059 return; 060 } 061 Component source = null; 062 if (e.getSource() instanceof Component) { 063 source = (Component)e.getSource(); 064 } 065 JPopupMenu popup = new JPopupMenu(); 066 if (layers.size() == 1) { 067 JComponent c = layers.iterator().next().getOffsetMenuItem(popup); 068 if (c instanceof JMenuItem) { 069 ((JMenuItem) c).getAction().actionPerformed(e); 070 } else { 071 if (source == null) return; 072 popup.show(source, source.getWidth()/2, source.getHeight()/2); 073 } 074 return; 075 } 076 if (source == null) return; 077 for (ImageryLayer layer : layers) { 078 JMenuItem layerMenu = layer.getOffsetMenuItem(); 079 layerMenu.setText(layer.getName()); 080 layerMenu.setIcon(layer.getIcon()); 081 popup.add(layerMenu); 082 } 083 popup.show(source, source.getWidth()/2, source.getHeight()/2); 084 } 085 }; 086 087 private final JMenuItem singleOffset = new JMenuItem(offsetAction); 088 private JMenuItem offsetMenuItem = singleOffset; 089 private final MapRectifierWMSmenuAction rectaction = new MapRectifierWMSmenuAction(); 090 091 /** 092 * Constructs a new {@code ImageryMenu}. 093 * @param subMenu submenu in that contains plugin-managed additional imagery layers 094 */ 095 public ImageryMenu(JMenu subMenu) { 096 super(tr("Imagery")); 097 setupMenuScroller(); 098 MapView.addLayerChangeListener(this); 099 // build dynamically 100 addMenuListener(new MenuListener() { 101 @Override 102 public void menuSelected(MenuEvent e) { 103 refreshImageryMenu(); 104 } 105 106 @Override 107 public void menuDeselected(MenuEvent e) { 108 } 109 110 @Override 111 public void menuCanceled(MenuEvent e) { 112 } 113 }); 114 MainMenu.add(subMenu, rectaction); 115 } 116 117 private void setupMenuScroller() { 118 if (!GraphicsEnvironment.isHeadless()) { 119 MenuScroller.setScrollerFor(this, 150, 2); 120 } 121 } 122 123 /** 124 * Refresh imagery menu. 125 * 126 * Outside this class only called in {@link ImageryPreference#initialize()}. 127 * (In order to have actions ready for the toolbar, see #8446.) 128 */ 129 public void refreshImageryMenu() { 130 removeDynamicItems(); 131 132 addDynamic(offsetMenuItem); 133 addDynamicSeparator(); 134 135 // for each configured ImageryInfo, add a menu entry. 136 for (final ImageryInfo u : ImageryLayerInfo.instance.getLayers()) { 137 addDynamic(new AddImageryLayerAction(u)); 138 } 139 140 // list all imagery entries where the current map location 141 // is within the imagery bounds 142 if (Main.isDisplayingMapView()) { 143 MapView mv = Main.map.mapView; 144 LatLon pos = mv.getProjection().eastNorth2latlon(mv.getCenter()); 145 final Set<ImageryInfo> inViewLayers = new TreeSet<>(); 146 147 for (ImageryInfo i : ImageryLayerInfo.instance.getDefaultLayers()) { 148 if (i.getBounds() != null && i.getBounds().contains(pos)) { 149 inViewLayers.add(i); 150 } 151 } 152 // Do not suggest layers already in use 153 inViewLayers.removeAll(ImageryLayerInfo.instance.getLayers()); 154 // For layers containing complex shapes, check that center is in one 155 // of its shapes (fix #7910) 156 for (Iterator<ImageryInfo> iti = inViewLayers.iterator(); iti.hasNext(); ) { 157 List<Shape> shapes = iti.next().getBounds().getShapes(); 158 if (shapes != null && !shapes.isEmpty()) { 159 boolean found = false; 160 for (Iterator<Shape> its = shapes.iterator(); its.hasNext() && !found; ) { 161 found = its.next().contains(pos); 162 } 163 if (!found) { 164 iti.remove(); 165 } 166 } 167 } 168 if (!inViewLayers.isEmpty()) { 169 addDynamicSeparator(); 170 for (ImageryInfo i : inViewLayers) { 171 addDynamic(new AddImageryLayerAction(i)); 172 } 173 } 174 } 175 176 addDynamicSeparator(); 177 JMenu subMenu = Main.main.menu.imagerySubMenu; 178 int heightUnrolled = 30*(getItemCount()+subMenu.getItemCount()); 179 if (heightUnrolled < Main.panel.getHeight()) { 180 // add all items of submenu if they will fit on screen 181 int n = subMenu.getItemCount(); 182 for (int i=0; i<n; i++) { 183 addDynamic(subMenu.getItem(i).getAction()); 184 } 185 } else { 186 // or add the submenu itself 187 addDynamic(subMenu); 188 } 189 } 190 191 private JMenuItem getNewOffsetMenu(){ 192 if (!Main.isDisplayingMapView()) { 193 offsetAction.setEnabled(false); 194 return singleOffset; 195 } 196 Collection<ImageryLayer> layers = Main.map.mapView.getLayersOfType(ImageryLayer.class); 197 if (layers.isEmpty()) { 198 offsetAction.setEnabled(false); 199 return singleOffset; 200 } 201 offsetAction.setEnabled(true); 202 JMenu newMenu = new JMenu(trc("layer","Offset")); 203 newMenu.setIcon(ImageProvider.get("mapmode", "adjustimg")); 204 newMenu.setAction(offsetAction); 205 if (layers.size() == 1) 206 return (JMenuItem)layers.iterator().next().getOffsetMenuItem(newMenu); 207 for (ImageryLayer layer : layers) { 208 JMenuItem layerMenu = layer.getOffsetMenuItem(); 209 layerMenu.setText(layer.getName()); 210 layerMenu.setIcon(layer.getIcon()); 211 newMenu.add(layerMenu); 212 } 213 return newMenu; 214 } 215 216 public void refreshOffsetMenu() { 217 offsetMenuItem = getNewOffsetMenu(); 218 } 219 220 @Override 221 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 222 } 223 224 @Override 225 public void layerAdded(Layer newLayer) { 226 if (newLayer instanceof ImageryLayer) { 227 refreshOffsetMenu(); 228 } 229 } 230 231 @Override 232 public void layerRemoved(Layer oldLayer) { 233 if (oldLayer instanceof ImageryLayer) { 234 refreshOffsetMenu(); 235 } 236 } 237 238 /** 239 * Collection to store temporary menu items. They will be deleted 240 * (and possibly recreated) when refreshImageryMenu() is called. 241 * @since 5803 242 */ 243 private List <Object> dynamicItems = new ArrayList<>(20); 244 245 /** 246 * Remove all the items in @field dynamicItems collection 247 * @since 5803 248 */ 249 private void removeDynamicItems() { 250 for (Object item : dynamicItems) { 251 if (item instanceof JMenuItem) { 252 remove((JMenuItem)item); 253 } 254 if (item instanceof MenuComponent) { 255 remove((MenuComponent)item); 256 } 257 if (item instanceof Component) { 258 remove((Component)item); 259 } 260 } 261 dynamicItems.clear(); 262 } 263 264 private void addDynamicSeparator() { 265 JPopupMenu.Separator s = new JPopupMenu.Separator(); 266 dynamicItems.add(s); 267 add(s); 268 } 269 270 private void addDynamic(Action a) { 271 dynamicItems.add( this.add(a) ); 272 } 273 274 private void addDynamic(JMenuItem it) { 275 dynamicItems.add( this.add(it) ); 276 } 277}