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