source: josm/trunk/src/org/openstreetmap/josm/actions/JosmAction.java@ 15588

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

tools update: checkstyle 8.27, pmd 6.20, spotbugs 3.1.12

  • Property svn:eol-style set to native
File size: 22.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.GridBagLayout;
7import java.awt.event.KeyEvent;
8import java.util.Collection;
9import java.util.List;
10import java.util.concurrent.CancellationException;
11import java.util.concurrent.ExecutionException;
12import java.util.concurrent.Future;
13
14import javax.swing.AbstractAction;
15import javax.swing.JOptionPane;
16import javax.swing.JPanel;
17
18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.data.osm.DataSelectionListener;
20import org.openstreetmap.josm.data.osm.DataSet;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmUtils;
23import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
24import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
25import org.openstreetmap.josm.gui.MainApplication;
26import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
27import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
28import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
29import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
30import org.openstreetmap.josm.gui.layer.MainLayerManager;
31import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
32import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
33import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
34import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
35import org.openstreetmap.josm.tools.Destroyable;
36import org.openstreetmap.josm.tools.ImageProvider;
37import org.openstreetmap.josm.tools.ImageResource;
38import org.openstreetmap.josm.tools.Logging;
39import org.openstreetmap.josm.tools.Shortcut;
40
41/**
42 * Base class helper for all Actions in JOSM. Just to make the life easier.
43 *
44 * This action allows you to set up an icon, a tooltip text, a globally registered shortcut, register it in the main toolbar and set up
45 * layer/selection listeners that call {@link #updateEnabledState()} whenever the global context is changed.
46 *
47 * A JosmAction can register a {@link LayerChangeListener} and a {@link DataSelectionListener}. Upon
48 * a layer change event or a selection change event it invokes {@link #updateEnabledState()}.
49 * Subclasses can override {@link #updateEnabledState()} in order to update the {@link #isEnabled()}-state
50 * of a JosmAction depending on the {@link #getLayerManager()} state.
51 *
52 * destroy() from interface Destroyable is called e.g. for MapModes, when the last layer has
53 * been removed and so the mapframe will be destroyed. For other JosmActions, destroy() may never
54 * be called (currently).
55 *
56 * @author imi
57 */
58public abstract class JosmAction extends AbstractAction implements Destroyable {
59
60 protected transient Shortcut sc;
61 private transient LayerChangeAdapter layerChangeAdapter;
62 private transient ActiveLayerChangeAdapter activeLayerChangeAdapter;
63 private transient SelectionChangeAdapter selectionChangeAdapter;
64
65 /**
66 * Constructs a {@code JosmAction}.
67 *
68 * @param name the action's text as displayed on the menu (if it is added to a menu)
69 * @param icon the icon to use
70 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
71 * that html is not supported for menu actions on some platforms.
72 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
73 * do want a shortcut, remember you can always register it with group=none, so you
74 * won't be assigned a shortcut unless the user configures one. If you pass null here,
75 * the user CANNOT configure a shortcut for your action.
76 * @param registerInToolbar register this action for the toolbar preferences?
77 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null
78 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
79 */
80 public JosmAction(String name, ImageProvider icon, String tooltip, Shortcut shortcut, boolean registerInToolbar,
81 String toolbarId, boolean installAdapters) {
82 super(name);
83 if (icon != null) {
84 ImageResource resource = icon.getResource();
85 if (resource != null) {
86 try {
87 resource.attachImageIcon(this, true);
88 } catch (RuntimeException e) {
89 Logging.warn("Unable to attach image icon {0} for action {1}", icon, name);
90 Logging.error(e);
91 }
92 }
93 }
94 setHelpId();
95 sc = shortcut;
96 if (sc != null && !sc.isAutomatic()) {
97 MainApplication.registerActionShortcut(this, sc);
98 }
99 setTooltip(tooltip);
100 if (getValue("toolbar") == null) {
101 putValue("toolbar", toolbarId);
102 }
103 if (registerInToolbar && MainApplication.getToolbar() != null) {
104 MainApplication.getToolbar().register(this);
105 }
106 if (installAdapters) {
107 installAdapters();
108 }
109 }
110
111 /**
112 * The new super for all actions.
113 *
114 * Use this super constructor to setup your action.
115 *
116 * @param name the action's text as displayed on the menu (if it is added to a menu)
117 * @param iconName the filename of the icon to use
118 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
119 * that html is not supported for menu actions on some platforms.
120 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
121 * do want a shortcut, remember you can always register it with group=none, so you
122 * won't be assigned a shortcut unless the user configures one. If you pass null here,
123 * the user CANNOT configure a shortcut for your action.
124 * @param registerInToolbar register this action for the toolbar preferences?
125 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null
126 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
127 */
128 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar,
129 String toolbarId, boolean installAdapters) {
130 this(name, iconName == null ? null : new ImageProvider(iconName).setOptional(true), tooltip, shortcut, registerInToolbar,
131 toolbarId == null ? iconName : toolbarId, installAdapters);
132 }
133
134 /**
135 * Constructs a new {@code JosmAction}.
136 *
137 * Use this super constructor to setup your action.
138 *
139 * @param name the action's text as displayed on the menu (if it is added to a menu)
140 * @param iconName the filename of the icon to use
141 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
142 * that html is not supported for menu actions on some platforms.
143 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
144 * do want a shortcut, remember you can always register it with group=none, so you
145 * won't be assigned a shortcut unless the user configures one. If you pass null here,
146 * the user CANNOT configure a shortcut for your action.
147 * @param registerInToolbar register this action for the toolbar preferences?
148 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
149 */
150 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar, boolean installAdapters) {
151 this(name, iconName, tooltip, shortcut, registerInToolbar, null, installAdapters);
152 }
153
154 /**
155 * Constructs a new {@code JosmAction}.
156 *
157 * Use this super constructor to setup your action.
158 *
159 * @param name the action's text as displayed on the menu (if it is added to a menu)
160 * @param iconName the filename of the icon to use
161 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
162 * that html is not supported for menu actions on some platforms.
163 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
164 * do want a shortcut, remember you can always register it with group=none, so you
165 * won't be assigned a shortcut unless the user configures one. If you pass null here,
166 * the user CANNOT configure a shortcut for your action.
167 * @param registerInToolbar register this action for the toolbar preferences?
168 */
169 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar) {
170 this(name, iconName, tooltip, shortcut, registerInToolbar, null, true);
171 }
172
173 /**
174 * Constructs a new {@code JosmAction}.
175 */
176 public JosmAction() {
177 this(true);
178 }
179
180 /**
181 * Constructs a new {@code JosmAction}.
182 *
183 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
184 */
185 public JosmAction(boolean installAdapters) {
186 setHelpId();
187 if (installAdapters) {
188 installAdapters();
189 }
190 }
191
192 /**
193 * Constructs a new {@code JosmAction}.
194 *
195 * Use this super constructor to setup your action.
196 *
197 * @param name the action's text as displayed on the menu (if it is added to a menu)
198 * @param iconName the filename of the icon to use
199 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
200 * that html is not supported for menu actions on some platforms.
201 * @param shortcuts ready-created shortcut objects
202 * @since 14012
203 */
204 public JosmAction(String name, String iconName, String tooltip, List<Shortcut> shortcuts) {
205 this(name, iconName, tooltip, shortcuts.get(0), true, null, true);
206 for (int i = 1; i < shortcuts.size(); i++) {
207 MainApplication.registerActionShortcut(this, shortcuts.get(i));
208 }
209 }
210
211 /**
212 * Installs the listeners to this action.
213 * <p>
214 * This should either never be called or only called in the constructor of this action.
215 * <p>
216 * All registered adapters should be removed in {@link #destroy()}
217 */
218 protected void installAdapters() {
219 // make this action listen to layer change and selection change events
220 if (listenToLayerChange()) {
221 layerChangeAdapter = buildLayerChangeAdapter();
222 activeLayerChangeAdapter = buildActiveLayerChangeAdapter();
223 getLayerManager().addLayerChangeListener(layerChangeAdapter);
224 getLayerManager().addActiveLayerChangeListener(activeLayerChangeAdapter);
225 }
226 if (listenToSelectionChange()) {
227 selectionChangeAdapter = new SelectionChangeAdapter();
228 SelectionEventManager.getInstance().addSelectionListenerForEdt(selectionChangeAdapter);
229 }
230 initEnabledState();
231 }
232
233 /**
234 * Override this if calling {@link #updateEnabledState()} on layer change events is not enough.
235 * @return the {@link LayerChangeAdapter} that will be called on layer change events
236 * @since 15404
237 */
238 protected LayerChangeAdapter buildLayerChangeAdapter() {
239 return new LayerChangeAdapter();
240 }
241
242 /**
243 * Override this if calling {@link #updateEnabledState()} on active layer change event is not enough.
244 * @return the {@link LayerChangeAdapter} that will be called on active layer change event
245 * @since 15404
246 */
247 protected ActiveLayerChangeAdapter buildActiveLayerChangeAdapter() {
248 return new ActiveLayerChangeAdapter();
249 }
250
251 /**
252 * Overwrite this if {@link #updateEnabledState()} should be called when the active / available layers change. Default is true.
253 * @return <code>true</code> if a {@link LayerChangeListener} and a {@link ActiveLayerChangeListener} should be registered.
254 * @since 10353
255 */
256 protected boolean listenToLayerChange() {
257 return true;
258 }
259
260 /**
261 * Overwrite this if {@link #updateEnabledState()} should be called when the selection changed. Default is true.
262 * @return <code>true</code> if a {@link DataSelectionListener} should be registered.
263 * @since 10353
264 */
265 protected boolean listenToSelectionChange() {
266 return true;
267 }
268
269 @Override
270 public void destroy() {
271 if (sc != null && !sc.isAutomatic()) {
272 MainApplication.unregisterActionShortcut(this);
273 }
274 if (layerChangeAdapter != null) {
275 getLayerManager().removeLayerChangeListener(layerChangeAdapter);
276 getLayerManager().removeActiveLayerChangeListener(activeLayerChangeAdapter);
277 }
278 if (selectionChangeAdapter != null) {
279 SelectionEventManager.getInstance().removeSelectionListener(selectionChangeAdapter);
280 }
281 if (MainApplication.getToolbar() != null) {
282 MainApplication.getToolbar().unregister(this);
283 }
284 }
285
286 private void setHelpId() {
287 String helpId = "Action/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
288 if (helpId.endsWith("Action")) {
289 helpId = helpId.substring(0, helpId.length()-6);
290 }
291 setHelpId(helpId);
292 }
293
294 protected void setHelpId(String helpId) {
295 putValue("help", helpId);
296 }
297
298 /**
299 * Returns the shortcut for this action.
300 * @return the shortcut for this action, or "No shortcut" if none is defined
301 */
302 public Shortcut getShortcut() {
303 if (sc == null) {
304 sc = Shortcut.registerShortcut("core:none", tr("No Shortcut"), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE);
305 // as this shortcut is shared by all action that don't want to have a shortcut,
306 // we shouldn't allow the user to change it...
307 // this is handled by special name "core:none"
308 }
309 return sc;
310 }
311
312 /**
313 * Sets the tooltip text of this action.
314 * @param tooltip The text to display in tooltip. Can be {@code null}
315 */
316 public final void setTooltip(String tooltip) {
317 if (tooltip != null && sc != null) {
318 sc.setTooltip(this, tooltip);
319 } else if (tooltip != null) {
320 putValue(SHORT_DESCRIPTION, tooltip);
321 }
322 }
323
324 /**
325 * Gets the layer manager used for this action. Defaults to the main layer manager but you can overwrite this.
326 * <p>
327 * The layer manager must be available when {@link #installAdapters()} is called and must not change.
328 *
329 * @return The layer manager.
330 * @since 10353
331 */
332 public MainLayerManager getLayerManager() {
333 return MainApplication.getLayerManager();
334 }
335
336 protected static void waitFuture(final Future<?> future, final PleaseWaitProgressMonitor monitor) {
337 MainApplication.worker.submit(() -> {
338 try {
339 future.get();
340 } catch (InterruptedException | ExecutionException | CancellationException e) {
341 Logging.error(e);
342 return;
343 }
344 monitor.close();
345 });
346 }
347
348 /**
349 * Override in subclasses to init the enabled state of an action when it is
350 * created. Default behaviour is to call {@link #updateEnabledState()}
351 *
352 * @see #updateEnabledState()
353 * @see #updateEnabledState(Collection)
354 */
355 protected void initEnabledState() {
356 updateEnabledState();
357 }
358
359 /**
360 * Override in subclasses to update the enabled state of the action when
361 * something in the JOSM state changes, i.e. when a layer is removed or added.
362 *
363 * See {@link #updateEnabledState(Collection)} to respond to changes in the collection
364 * of selected primitives.
365 *
366 * Default behavior is empty.
367 *
368 * @see #updateEnabledState(Collection)
369 * @see #initEnabledState()
370 * @see #listenToLayerChange()
371 */
372 protected void updateEnabledState() {
373 }
374
375 /**
376 * Override in subclasses to update the enabled state of the action if the
377 * collection of selected primitives changes. This method is called with the
378 * new selection.
379 *
380 * @param selection the collection of selected primitives; may be empty, but not null
381 *
382 * @see #updateEnabledState()
383 * @see #initEnabledState()
384 * @see #listenToSelectionChange()
385 */
386 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
387 }
388
389 /**
390 * Updates enabled state according to primitives currently selected in edit data set, if any.
391 * Can be called in {@link #updateEnabledState()} implementations.
392 * @see #updateEnabledStateOnCurrentSelection(boolean)
393 * @since 10409
394 */
395 protected final void updateEnabledStateOnCurrentSelection() {
396 updateEnabledStateOnCurrentSelection(false);
397 }
398
399 /**
400 * Updates enabled state according to primitives currently selected in active data set, if any.
401 * Can be called in {@link #updateEnabledState()} implementations.
402 * @param allowReadOnly if {@code true}, read-only data sets are considered
403 * @since 13434
404 */
405 protected final void updateEnabledStateOnCurrentSelection(boolean allowReadOnly) {
406 DataSet ds = getLayerManager().getActiveDataSet();
407 if (ds != null && (allowReadOnly || !ds.isLocked())) {
408 updateEnabledState(ds.getSelected());
409 } else {
410 setEnabled(false);
411 }
412 }
413
414 /**
415 * Updates enabled state according to selected primitives, if any.
416 * Enables action if the collection is not empty and references primitives in a modifiable data layer.
417 * Can be called in {@link #updateEnabledState(Collection)} implementations.
418 * @param selection the collection of selected primitives
419 * @since 13434
420 */
421 protected final void updateEnabledStateOnModifiableSelection(Collection<? extends OsmPrimitive> selection) {
422 setEnabled(OsmUtils.isOsmCollectionEditable(selection));
423 }
424
425 /**
426 * Adapter for layer change events. Runs updateEnabledState() whenever the active layer changed.
427 */
428 protected class LayerChangeAdapter implements LayerChangeListener {
429 @Override
430 public void layerAdded(LayerAddEvent e) {
431 updateEnabledState();
432 }
433
434 @Override
435 public void layerRemoving(LayerRemoveEvent e) {
436 updateEnabledState();
437 }
438
439 @Override
440 public void layerOrderChanged(LayerOrderChangeEvent e) {
441 updateEnabledState();
442 }
443
444 @Override
445 public String toString() {
446 return "LayerChangeAdapter [" + JosmAction.this + ']';
447 }
448 }
449
450 /**
451 * Adapter for layer change events. Runs updateEnabledState() whenever the active layer changed.
452 */
453 protected class ActiveLayerChangeAdapter implements ActiveLayerChangeListener {
454 @Override
455 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
456 updateEnabledState();
457 }
458
459 @Override
460 public String toString() {
461 return "ActiveLayerChangeAdapter [" + JosmAction.this + ']';
462 }
463 }
464
465 /**
466 * Adapter for selection change events. Runs updateEnabledState() whenever the selection changed.
467 */
468 protected class SelectionChangeAdapter implements DataSelectionListener {
469 @Override
470 public void selectionChanged(SelectionChangeEvent event) {
471 updateEnabledState(event.getSelection());
472 }
473
474 @Override
475 public String toString() {
476 return "SelectionChangeAdapter [" + JosmAction.this + ']';
477 }
478 }
479
480 /**
481 * Check whether user is about to operate on data outside of the download area.
482 * Request confirmation if he is.
483 *
484 * @param operation the operation name which is used for setting some preferences
485 * @param dialogTitle the title of the dialog being displayed
486 * @param outsideDialogMessage the message text to be displayed when data is outside of the download area
487 * @param incompleteDialogMessage the message text to be displayed when data is incomplete
488 * @param primitives the primitives to operate on
489 * @param ignore {@code null} or a primitive to be ignored
490 * @return true, if operating on outlying primitives is OK; false, otherwise
491 * @since 12749 (moved from Command)
492 */
493 public static boolean checkAndConfirmOutlyingOperation(String operation,
494 String dialogTitle, String outsideDialogMessage, String incompleteDialogMessage,
495 Collection<? extends OsmPrimitive> primitives,
496 Collection<? extends OsmPrimitive> ignore) {
497 int checkRes = Command.checkOutlyingOrIncompleteOperation(primitives, ignore);
498 if ((checkRes & Command.IS_OUTSIDE) != 0) {
499 JPanel msg = new JPanel(new GridBagLayout());
500 msg.add(new JMultilineLabel("<html>" + outsideDialogMessage + "</html>"));
501 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
502 operation + "_outside_nodes",
503 MainApplication.getMainFrame(),
504 msg,
505 dialogTitle,
506 JOptionPane.YES_NO_OPTION,
507 JOptionPane.QUESTION_MESSAGE,
508 JOptionPane.YES_OPTION);
509 if (!answer)
510 return false;
511 }
512 if ((checkRes & Command.IS_INCOMPLETE) != 0) {
513 JPanel msg = new JPanel(new GridBagLayout());
514 msg.add(new JMultilineLabel("<html>" + incompleteDialogMessage + "</html>"));
515 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
516 operation + "_incomplete",
517 MainApplication.getMainFrame(),
518 msg,
519 dialogTitle,
520 JOptionPane.YES_NO_OPTION,
521 JOptionPane.QUESTION_MESSAGE,
522 JOptionPane.YES_OPTION);
523 if (!answer)
524 return false;
525 }
526 return true;
527 }
528}
Note: See TracBrowser for help on using the repository browser.