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

Last change on this file since 12131 was 12051, checked in by michael2402, 7 years ago

Trigger JosmAction selection change on edit layer change.

  • Property svn:eol-style set to native
File size: 16.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.event.KeyEvent;
7import java.util.Collection;
8import java.util.concurrent.CancellationException;
9import java.util.concurrent.ExecutionException;
10import java.util.concurrent.Future;
11
12import javax.swing.AbstractAction;
13
14import org.openstreetmap.josm.Main;
15import org.openstreetmap.josm.data.SelectionChangedListener;
16import org.openstreetmap.josm.data.osm.DataSet;
17import org.openstreetmap.josm.data.osm.OsmPrimitive;
18import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
19import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
20import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
21import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
22import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
23import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
24import org.openstreetmap.josm.gui.layer.MainLayerManager;
25import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
26import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
27import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
28import org.openstreetmap.josm.tools.Destroyable;
29import org.openstreetmap.josm.tools.ImageProvider;
30import org.openstreetmap.josm.tools.Shortcut;
31
32/**
33 * Base class helper for all Actions in JOSM. Just to make the life easier.
34 *
35 * 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
36 * layer/selection listeners that call {@link #updateEnabledState()} whenever the global context is changed.
37 *
38 * A JosmAction can register a {@link LayerChangeListener} and a {@link SelectionChangedListener}. Upon
39 * a layer change event or a selection change event it invokes {@link #updateEnabledState()}.
40 * Subclasses can override {@link #updateEnabledState()} in order to update the {@link #isEnabled()}-state
41 * of a JosmAction depending on the {@link #getLayerManager()} state.
42 *
43 * destroy() from interface Destroyable is called e.g. for MapModes, when the last layer has
44 * been removed and so the mapframe will be destroyed. For other JosmActions, destroy() may never
45 * be called (currently).
46 *
47 * @author imi
48 */
49public abstract class JosmAction extends AbstractAction implements Destroyable {
50
51 protected transient Shortcut sc;
52 private transient LayerChangeAdapter layerChangeAdapter;
53 private transient ActiveLayerChangeAdapter activeLayerChangeAdapter;
54 private transient SelectionChangeAdapter selectionChangeAdapter;
55
56 /**
57 * Constructs a {@code JosmAction}.
58 *
59 * @param name the action's text as displayed on the menu (if it is added to a menu)
60 * @param icon the icon to use
61 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
62 * that html is not supported for menu actions on some platforms.
63 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
64 * do want a shortcut, remember you can always register it with group=none, so you
65 * won't be assigned a shortcut unless the user configures one. If you pass null here,
66 * the user CANNOT configure a shortcut for your action.
67 * @param registerInToolbar register this action for the toolbar preferences?
68 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null
69 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
70 */
71 public JosmAction(String name, ImageProvider icon, String tooltip, Shortcut shortcut, boolean registerInToolbar,
72 String toolbarId, boolean installAdapters) {
73 super(name);
74 if (icon != null)
75 icon.getResource().attachImageIcon(this, true);
76 setHelpId();
77 sc = shortcut;
78 if (sc != null) {
79 Main.registerActionShortcut(this, sc);
80 }
81 setTooltip(tooltip);
82 if (getValue("toolbar") == null) {
83 putValue("toolbar", toolbarId);
84 }
85 if (registerInToolbar && Main.toolbar != null) {
86 Main.toolbar.register(this);
87 }
88 if (installAdapters) {
89 installAdapters();
90 }
91 }
92
93 /**
94 * The new super for all actions.
95 *
96 * Use this super constructor to setup your action.
97 *
98 * @param name the action's text as displayed on the menu (if it is added to a menu)
99 * @param iconName the filename of the icon to use
100 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
101 * that html is not supported for menu actions on some platforms.
102 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
103 * do want a shortcut, remember you can always register it with group=none, so you
104 * won't be assigned a shortcut unless the user configures one. If you pass null here,
105 * the user CANNOT configure a shortcut for your action.
106 * @param registerInToolbar register this action for the toolbar preferences?
107 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null
108 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
109 */
110 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar,
111 String toolbarId, boolean installAdapters) {
112 this(name, iconName == null ? null : new ImageProvider(iconName), tooltip, shortcut, registerInToolbar,
113 toolbarId == null ? iconName : toolbarId, installAdapters);
114 }
115
116 /**
117 * Constructs a new {@code JosmAction}.
118 *
119 * Use this super constructor to setup your action.
120 *
121 * @param name the action's text as displayed on the menu (if it is added to a menu)
122 * @param iconName the filename of the icon to use
123 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
124 * that html is not supported for menu actions on some platforms.
125 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
126 * do want a shortcut, remember you can always register it with group=none, so you
127 * won't be assigned a shortcut unless the user configures one. If you pass null here,
128 * the user CANNOT configure a shortcut for your action.
129 * @param registerInToolbar register this action for the toolbar preferences?
130 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
131 */
132 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar, boolean installAdapters) {
133 this(name, iconName, tooltip, shortcut, registerInToolbar, null, installAdapters);
134 }
135
136 /**
137 * Constructs a new {@code JosmAction}.
138 *
139 * Use this super constructor to setup your action.
140 *
141 * @param name the action's text as displayed on the menu (if it is added to a menu)
142 * @param iconName the filename of the icon to use
143 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note
144 * that html is not supported for menu actions on some platforms.
145 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
146 * do want a shortcut, remember you can always register it with group=none, so you
147 * won't be assigned a shortcut unless the user configures one. If you pass null here,
148 * the user CANNOT configure a shortcut for your action.
149 * @param registerInToolbar register this action for the toolbar preferences?
150 */
151 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar) {
152 this(name, iconName, tooltip, shortcut, registerInToolbar, null, true);
153 }
154
155 /**
156 * Constructs a new {@code JosmAction}.
157 */
158 public JosmAction() {
159 this(true);
160 }
161
162 /**
163 * Constructs a new {@code JosmAction}.
164 *
165 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters
166 */
167 public JosmAction(boolean installAdapters) {
168 setHelpId();
169 if (installAdapters) {
170 installAdapters();
171 }
172 }
173
174 /**
175 * Installs the listeners to this action.
176 * <p>
177 * This should either never be called or only called in the constructor of this action.
178 * <p>
179 * All registered adapters should be removed in {@link #destroy()}
180 */
181 protected void installAdapters() {
182 // make this action listen to layer change and selection change events
183 if (listenToLayerChange()) {
184 layerChangeAdapter = new LayerChangeAdapter();
185 activeLayerChangeAdapter = new ActiveLayerChangeAdapter();
186 getLayerManager().addLayerChangeListener(layerChangeAdapter);
187 getLayerManager().addActiveLayerChangeListener(activeLayerChangeAdapter);
188 }
189 if (listenToSelectionChange()) {
190 selectionChangeAdapter = new SelectionChangeAdapter();
191 SelectionEventManager.getInstance()
192 .addSelectionListener(selectionChangeAdapter, FireMode.IN_EDT_CONSOLIDATED);
193 }
194 initEnabledState();
195 }
196
197 /**
198 * Overwrite this if {@link #updateEnabledState()} should be called when the active / availabe layers change. Default is true.
199 * @return <code>true</code> if a {@link LayerChangeListener} and a {@link ActiveLayerChangeListener} should be registered.
200 * @since 10353
201 */
202 protected boolean listenToLayerChange() {
203 return true;
204 }
205
206 /**
207 * Overwrite this if {@link #updateEnabledState()} should be called when the selection changed. Default is true.
208 * @return <code>true</code> if a {@link SelectionChangedListener} should be registered.
209 * @since 10353
210 */
211 protected boolean listenToSelectionChange() {
212 return true;
213 }
214
215 @Override
216 public void destroy() {
217 if (sc != null) {
218 Main.unregisterActionShortcut(this);
219 }
220 if (layerChangeAdapter != null) {
221 getLayerManager().removeLayerChangeListener(layerChangeAdapter);
222 getLayerManager().removeActiveLayerChangeListener(activeLayerChangeAdapter);
223 }
224 if (selectionChangeAdapter != null) {
225 DataSet.removeSelectionListener(selectionChangeAdapter);
226 }
227 }
228
229 private void setHelpId() {
230 String helpId = "Action/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
231 if (helpId.endsWith("Action")) {
232 helpId = helpId.substring(0, helpId.length()-6);
233 }
234 putValue("help", helpId);
235 }
236
237 /**
238 * Returns the shortcut for this action.
239 * @return the shortcut for this action, or "No shortcut" if none is defined
240 */
241 public Shortcut getShortcut() {
242 if (sc == null) {
243 sc = Shortcut.registerShortcut("core:none", tr("No Shortcut"), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE);
244 // as this shortcut is shared by all action that don't want to have a shortcut,
245 // we shouldn't allow the user to change it...
246 // this is handled by special name "core:none"
247 }
248 return sc;
249 }
250
251 /**
252 * Sets the tooltip text of this action.
253 * @param tooltip The text to display in tooltip. Can be {@code null}
254 */
255 public final void setTooltip(String tooltip) {
256 if (tooltip != null) {
257 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc));
258 }
259 }
260
261 /**
262 * Gets the layer manager used for this action. Defaults to the main layer manager but you can overwrite this.
263 * <p>
264 * The layer manager must be available when {@link #installAdapters()} is called and must not change.
265 *
266 * @return The layer manager.
267 * @since 10353
268 */
269 public MainLayerManager getLayerManager() {
270 return Main.getLayerManager();
271 }
272
273 protected static void waitFuture(final Future<?> future, final PleaseWaitProgressMonitor monitor) {
274 Main.worker.submit(() -> {
275 try {
276 future.get();
277 } catch (InterruptedException | ExecutionException | CancellationException e) {
278 Main.error(e);
279 return;
280 }
281 monitor.close();
282 });
283 }
284
285 /**
286 * Override in subclasses to init the enabled state of an action when it is
287 * created. Default behaviour is to call {@link #updateEnabledState()}
288 *
289 * @see #updateEnabledState()
290 * @see #updateEnabledState(Collection)
291 */
292 protected void initEnabledState() {
293 updateEnabledState();
294 }
295
296 /**
297 * Override in subclasses to update the enabled state of the action when
298 * something in the JOSM state changes, i.e. when a layer is removed or added.
299 *
300 * See {@link #updateEnabledState(Collection)} to respond to changes in the collection
301 * of selected primitives.
302 *
303 * Default behavior is empty.
304 *
305 * @see #updateEnabledState(Collection)
306 * @see #initEnabledState()
307 * @see #listenToLayerChange()
308 */
309 protected void updateEnabledState() {
310 }
311
312 /**
313 * Override in subclasses to update the enabled state of the action if the
314 * collection of selected primitives changes. This method is called with the
315 * new selection.
316 *
317 * @param selection the collection of selected primitives; may be empty, but not null
318 *
319 * @see #updateEnabledState()
320 * @see #initEnabledState()
321 * @see #listenToSelectionChange()
322 */
323 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
324 }
325
326 /**
327 * Updates enabled state according to primitives currently selected in edit data set, if any.
328 * Can be called in {@link #updateEnabledState()} implementations.
329 * @since 10409
330 */
331 protected final void updateEnabledStateOnCurrentSelection() {
332 DataSet ds = getLayerManager().getEditDataSet();
333 if (ds == null) {
334 setEnabled(false);
335 } else {
336 updateEnabledState(ds.getSelected());
337 }
338 }
339
340 /**
341 * Adapter for layer change events. Runs updateEnabledState() whenever the active layer changed.
342 */
343 protected class LayerChangeAdapter implements LayerChangeListener {
344 @Override
345 public void layerAdded(LayerAddEvent e) {
346 updateEnabledState();
347 }
348
349 @Override
350 public void layerRemoving(LayerRemoveEvent e) {
351 updateEnabledState();
352 }
353
354 @Override
355 public void layerOrderChanged(LayerOrderChangeEvent e) {
356 updateEnabledState();
357 }
358
359 @Override
360 public String toString() {
361 return "LayerChangeAdapter [" + JosmAction.this + ']';
362 }
363 }
364
365 /**
366 * Adapter for layer change events. Runs updateEnabledState() whenever the active layer changed.
367 */
368 protected class ActiveLayerChangeAdapter implements ActiveLayerChangeListener {
369 @Override
370 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
371 updateEnabledState();
372 }
373
374 @Override
375 public String toString() {
376 return "ActiveLayerChangeAdapter [" + JosmAction.this + ']';
377 }
378 }
379
380 /**
381 * Adapter for selection change events. Runs updateEnabledState() whenever the selection changed.
382 */
383 protected class SelectionChangeAdapter implements SelectionChangedListener {
384 @Override
385 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
386 updateEnabledState(newSelection);
387 }
388
389 @Override
390 public String toString() {
391 return "SelectionChangeAdapter [" + JosmAction.this + ']';
392 }
393 }
394}
Note: See TracBrowser for help on using the repository browser.