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

Last change on this file since 18208 was 18208, checked in by Don-vip, 3 years ago

global use of Utils.isEmpty/isBlank

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