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