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}