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}