source: josm/trunk/src/org/openstreetmap/josm/gui/ImageryMenu.java@ 17534

Last change on this file since 17534 was 16953, checked in by simon04, 4 years ago

Java 8: use Comparator.comparing

  • Property svn:eol-style set to native
File size: 13.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trc;
6
7import java.awt.Component;
8import java.awt.GraphicsEnvironment;
9import java.awt.MenuComponent;
10import java.awt.event.ActionEvent;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Comparator;
14import java.util.EnumMap;
15import java.util.List;
16import java.util.Locale;
17import java.util.Map;
18import java.util.Map.Entry;
19import java.util.stream.Collectors;
20
21import javax.swing.Action;
22import javax.swing.JComponent;
23import javax.swing.JMenu;
24import javax.swing.JMenuItem;
25import javax.swing.JPopupMenu;
26import javax.swing.event.MenuEvent;
27import javax.swing.event.MenuListener;
28
29import org.openstreetmap.josm.actions.AddImageryLayerAction;
30import org.openstreetmap.josm.actions.JosmAction;
31import org.openstreetmap.josm.actions.MapRectifierWMSmenuAction;
32import org.openstreetmap.josm.data.coor.LatLon;
33import org.openstreetmap.josm.data.imagery.ImageryInfo;
34import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryCategory;
35import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
36import org.openstreetmap.josm.data.imagery.Shape;
37import org.openstreetmap.josm.gui.layer.ImageryLayer;
38import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
39import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
40import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
41import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
42import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
43import org.openstreetmap.josm.tools.ImageProvider;
44import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
45import org.openstreetmap.josm.tools.Logging;
46
47/**
48 * Imagery menu, holding entries for imagery preferences, offset actions and dynamic imagery entries
49 * depending on current mapview coordinates.
50 * @since 3737
51 */
52public class ImageryMenu extends JMenu implements LayerChangeListener {
53
54 static final class AdjustImageryOffsetAction extends JosmAction {
55
56 AdjustImageryOffsetAction() {
57 super(tr("Imagery offset"), "mapmode/adjustimg", tr("Adjust imagery offset"), null, false, false);
58 setToolbarId("imagery-offset");
59 MainApplication.getToolbar().register(this);
60 }
61
62 @Override
63 public void actionPerformed(ActionEvent e) {
64 Collection<ImageryLayer> layers = MainApplication.getLayerManager().getLayersOfType(ImageryLayer.class);
65 if (layers.isEmpty()) {
66 setEnabled(false);
67 return;
68 }
69 Component source = null;
70 if (e.getSource() instanceof Component) {
71 source = (Component) e.getSource();
72 }
73 JPopupMenu popup = new JPopupMenu();
74 if (layers.size() == 1) {
75 JComponent c = layers.iterator().next().getOffsetMenuItem(popup);
76 if (c instanceof JMenuItem) {
77 ((JMenuItem) c).getAction().actionPerformed(e);
78 } else {
79 if (source == null || !source.isShowing()) return;
80 popup.show(source, source.getWidth()/2, source.getHeight()/2);
81 }
82 return;
83 }
84 if (source == null || !source.isShowing()) return;
85 for (ImageryLayer layer : layers) {
86 JMenuItem layerMenu = layer.getOffsetMenuItem();
87 layerMenu.setText(layer.getName());
88 layerMenu.setIcon(layer.getIcon());
89 popup.add(layerMenu);
90 }
91 popup.show(source, source.getWidth()/2, source.getHeight()/2);
92 }
93 }
94
95 /**
96 * Compare ImageryInfo objects alphabetically by name.
97 *
98 * ImageryInfo objects are normally sorted by country code first
99 * (for the preferences). We don't want this in the imagery menu.
100 */
101 public static final Comparator<ImageryInfo> alphabeticImageryComparator =
102 Comparator.comparing(ii -> ii.getName().toLowerCase(Locale.ENGLISH));
103
104 private final transient Action offsetAction = new AdjustImageryOffsetAction();
105
106 private final JMenuItem singleOffset = new JMenuItem(offsetAction);
107 private JMenuItem offsetMenuItem = singleOffset;
108 private final MapRectifierWMSmenuAction rectaction = new MapRectifierWMSmenuAction();
109
110 /**
111 * Constructs a new {@code ImageryMenu}.
112 * @param subMenu submenu in that contains plugin-managed additional imagery layers
113 */
114 public ImageryMenu(JMenu subMenu) {
115 /* I18N: mnemonic: I */
116 super(trc("menu", "Imagery"));
117 setupMenuScroller();
118 MainApplication.getLayerManager().addLayerChangeListener(this);
119 // build dynamically
120 addMenuListener(new MenuListener() {
121 @Override
122 public void menuSelected(MenuEvent e) {
123 refreshImageryMenu();
124 }
125
126 @Override
127 public void menuDeselected(MenuEvent e) {
128 // Do nothing
129 }
130
131 @Override
132 public void menuCanceled(MenuEvent e) {
133 // Do nothing
134 }
135 });
136 MainMenu.add(subMenu, rectaction);
137 }
138
139 private void setupMenuScroller() {
140 if (!GraphicsEnvironment.isHeadless()) {
141 MenuScroller.setScrollerFor(this, 150, 2);
142 }
143 }
144
145 /**
146 * For layers containing complex shapes, check that center is in one of its shapes (fix #7910)
147 * @param info layer info
148 * @param pos center
149 * @return {@code true} if center is in one of info shapes
150 */
151 private static boolean isPosInOneShapeIfAny(ImageryInfo info, LatLon pos) {
152 List<Shape> shapes = info.getBounds().getShapes();
153 return shapes == null || shapes.isEmpty() || shapes.stream().anyMatch(s -> s.contains(pos));
154 }
155
156 /**
157 * Refresh imagery menu.
158 *
159 * Outside this class only called in {@link ImageryPreference#initialize()}.
160 * (In order to have actions ready for the toolbar, see #8446.)
161 */
162 public void refreshImageryMenu() {
163 removeDynamicItems();
164
165 addDynamic(offsetMenuItem);
166 addDynamicSeparator();
167
168 // for each configured ImageryInfo, add a menu entry.
169 final List<ImageryInfo> savedLayers = new ArrayList<>(ImageryLayerInfo.instance.getLayers());
170 savedLayers.sort(alphabeticImageryComparator);
171 for (final ImageryInfo u : savedLayers) {
172 addDynamic(trackJosmAction(new AddImageryLayerAction(u)), null);
173 }
174
175 // list all imagery entries where the current map location is within the imagery bounds
176 if (MainApplication.isDisplayingMapView()) {
177 MapView mv = MainApplication.getMap().mapView;
178 LatLon pos = mv.getProjection().eastNorth2latlon(mv.getCenter());
179 final List<ImageryInfo> alreadyInUse = ImageryLayerInfo.instance.getLayers();
180 final List<ImageryInfo> inViewLayers = ImageryLayerInfo.instance.getDefaultLayers()
181 .stream().filter(i -> i.getBounds() != null && i.getBounds().contains(pos)
182 && !alreadyInUse.contains(i) && isPosInOneShapeIfAny(i, pos))
183 .sorted(alphabeticImageryComparator)
184 .collect(Collectors.toList());
185 if (!inViewLayers.isEmpty()) {
186 if (inViewLayers.stream().anyMatch(i -> i.getImageryCategory() == ImageryCategory.PHOTO)) {
187 addDynamicSeparator();
188 }
189 for (ImageryInfo i : inViewLayers) {
190 addDynamic(trackJosmAction(new AddImageryLayerAction(i)), i.getImageryCategory());
191 }
192 }
193 if (!dynamicNonPhotoItems.isEmpty()) {
194 addDynamicSeparator();
195 for (Entry<ImageryCategory, List<JMenuItem>> e : dynamicNonPhotoItems.entrySet()) {
196 ImageryCategory cat = e.getKey();
197 List<JMenuItem> list = e.getValue();
198 if (list.size() > 1) {
199 JMenuItem categoryMenu = new JMenu(cat.getDescription());
200 categoryMenu.setIcon(cat.getIcon(ImageSizes.MENU));
201 for (JMenuItem it : list) {
202 categoryMenu.add(it);
203 }
204 dynamicNonPhotoMenus.add(add(categoryMenu));
205 } else if (!list.isEmpty()) {
206 dynamicNonPhotoMenus.add(add(list.get(0)));
207 }
208 }
209 }
210 }
211
212 addDynamicSeparator();
213 JMenu subMenu = MainApplication.getMenu().imagerySubMenu;
214 int heightUnrolled = 30*(getItemCount()+subMenu.getItemCount());
215 if (heightUnrolled < MainApplication.getMainPanel().getHeight()) {
216 // add all items of submenu if they will fit on screen
217 int n = subMenu.getItemCount();
218 for (int i = 0; i < n; i++) {
219 addDynamic(subMenu.getItem(i).getAction(), null);
220 }
221 } else {
222 // or add the submenu itself
223 addDynamic(subMenu);
224 }
225 }
226
227 private JMenuItem getNewOffsetMenu() {
228 Collection<ImageryLayer> layers = MainApplication.getLayerManager().getLayersOfType(ImageryLayer.class);
229 if (layers.isEmpty()) {
230 offsetAction.setEnabled(false);
231 return singleOffset;
232 }
233 offsetAction.setEnabled(true);
234 JMenu newMenu = new JMenu(trc("layer", "Offset"));
235 newMenu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
236 newMenu.setAction(offsetAction);
237 if (layers.size() == 1)
238 return (JMenuItem) layers.iterator().next().getOffsetMenuItem(newMenu);
239 for (ImageryLayer layer : layers) {
240 JMenuItem layerMenu = layer.getOffsetMenuItem();
241 layerMenu.setText(layer.getName());
242 layerMenu.setIcon(layer.getIcon());
243 newMenu.add(layerMenu);
244 }
245 return newMenu;
246 }
247
248 /**
249 * Refresh offset menu item.
250 */
251 public void refreshOffsetMenu() {
252 offsetMenuItem = getNewOffsetMenu();
253 }
254
255 @Override
256 public void layerAdded(LayerAddEvent e) {
257 if (e.getAddedLayer() instanceof ImageryLayer) {
258 refreshOffsetMenu();
259 }
260 }
261
262 @Override
263 public void layerRemoving(LayerRemoveEvent e) {
264 if (e.getRemovedLayer() instanceof ImageryLayer) {
265 refreshOffsetMenu();
266 }
267 }
268
269 @Override
270 public void layerOrderChanged(LayerOrderChangeEvent e) {
271 refreshOffsetMenu();
272 }
273
274 /**
275 * List to store temporary "photo" menu items. They will be deleted
276 * (and possibly recreated) when refreshImageryMenu() is called.
277 */
278 private final List<Object> dynamicItems = new ArrayList<>(20);
279 /**
280 * Map to store temporary "not photo" menu items. They will be deleted
281 * (and possibly recreated) when refreshImageryMenu() is called.
282 */
283 private final Map<ImageryCategory, List<JMenuItem>> dynamicNonPhotoItems = new EnumMap<>(ImageryCategory.class);
284 /**
285 * List to store temporary "not photo" submenus. They will be deleted
286 * (and possibly recreated) when refreshImageryMenu() is called.
287 */
288 private final List<JMenuItem> dynamicNonPhotoMenus = new ArrayList<>(20);
289 private final List<JosmAction> dynJosmActions = new ArrayList<>(20);
290
291 /**
292 * Remove all the items in dynamic items collection
293 * @since 5803
294 */
295 private void removeDynamicItems() {
296 dynJosmActions.forEach(JosmAction::destroy);
297 dynJosmActions.clear();
298 dynamicItems.forEach(this::removeDynamicItem);
299 dynamicItems.clear();
300 dynamicNonPhotoMenus.forEach(this::removeDynamicItem);
301 dynamicItems.clear();
302 dynamicNonPhotoItems.clear();
303 }
304
305 private void removeDynamicItem(Object item) {
306 if (item instanceof JMenuItem) {
307 remove((JMenuItem) item);
308 } else if (item instanceof MenuComponent) {
309 remove((MenuComponent) item);
310 } else if (item instanceof Component) {
311 remove((Component) item);
312 } else {
313 Logging.error("Unknown imagery menu item type: {0}", item);
314 }
315 }
316
317 private void addDynamicSeparator() {
318 JPopupMenu.Separator s = new JPopupMenu.Separator();
319 dynamicItems.add(s);
320 add(s);
321 }
322
323 private void addDynamic(Action a, ImageryCategory category) {
324 JMenuItem item = createActionComponent(a);
325 item.setAction(a);
326 doAddDynamic(item, category);
327 }
328
329 private void addDynamic(JMenuItem it) {
330 doAddDynamic(it, null);
331 }
332
333 private void doAddDynamic(JMenuItem item, ImageryCategory category) {
334 if (category == null || category == ImageryCategory.PHOTO) {
335 dynamicItems.add(this.add(item));
336 } else {
337 dynamicNonPhotoItems.computeIfAbsent(category, x -> new ArrayList<>()).add(item);
338 }
339 }
340
341 private Action trackJosmAction(Action action) {
342 if (action instanceof JosmAction) {
343 dynJosmActions.add((JosmAction) action);
344 }
345 return action;
346 }
347
348}
Note: See TracBrowser for help on using the repository browser.