001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.event.KeyEvent; 007import java.util.Collection; 008 009import javax.swing.AbstractAction; 010import javax.swing.Icon; 011 012import org.openstreetmap.josm.Main; 013import org.openstreetmap.josm.data.SelectionChangedListener; 014import org.openstreetmap.josm.data.osm.DataSet; 015import org.openstreetmap.josm.data.osm.OsmPrimitive; 016import org.openstreetmap.josm.gui.MapView; 017import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 018import org.openstreetmap.josm.gui.layer.Layer; 019import org.openstreetmap.josm.gui.layer.OsmDataLayer; 020import org.openstreetmap.josm.gui.util.GuiHelper; 021import org.openstreetmap.josm.tools.Destroyable; 022import org.openstreetmap.josm.tools.ImageProvider; 023import org.openstreetmap.josm.tools.Shortcut; 024 025/** 026 * Base class helper for all Actions in JOSM. Just to make the life easier. 027 * 028 * A JosmAction is a {@link LayerChangeListener} and a {@link SelectionChangedListener}. Upon 029 * a layer change event or a selection change event it invokes {@link #updateEnabledState()}. 030 * Subclasses can override {@link #updateEnabledState()} in order to update the {@link #isEnabled()}-state 031 * of a JosmAction depending on the {@link #getCurrentDataSet()} and the current layers 032 * (see also {@link #getEditLayer()}). 033 * 034 * destroy() from interface Destroyable is called e.g. for MapModes, when the last layer has 035 * been removed and so the mapframe will be destroyed. For other JosmActions, destroy() may never 036 * be called (currently). 037 * 038 * @author imi 039 */ 040public abstract class JosmAction extends AbstractAction implements Destroyable { 041 042 protected Shortcut sc; 043 private LayerChangeAdapter layerChangeAdapter; 044 private SelectionChangeAdapter selectionChangeAdapter; 045 046 /** 047 * Returns the shortcut for this action. 048 * @return the shortcut for this action, or "No shortcut" if none is defined 049 */ 050 public Shortcut getShortcut() { 051 if (sc == null) { 052 sc = Shortcut.registerShortcut("core:none", tr("No Shortcut"), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE); 053 // as this shortcut is shared by all action that don't want to have a shortcut, 054 // we shouldn't allow the user to change it... 055 // this is handled by special name "core:none" 056 } 057 return sc; 058 } 059 060 /** 061 * Constructs a {@code JosmAction}. 062 * 063 * @param name the action's text as displayed on the menu (if it is added to a menu) 064 * @param icon the icon to use 065 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note 066 * that html is not supported for menu actions on some platforms. 067 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always 068 * do want a shortcut, remember you can always register it with group=none, so you 069 * won't be assigned a shortcut unless the user configures one. If you pass null here, 070 * the user CANNOT configure a shortcut for your action. 071 * @param registerInToolbar register this action for the toolbar preferences? 072 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null 073 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters 074 */ 075 public JosmAction(String name, Icon icon, String tooltip, Shortcut shortcut, boolean registerInToolbar, String toolbarId, boolean installAdapters) { 076 super(name, icon); 077 setHelpId(); 078 sc = shortcut; 079 if (sc != null) { 080 Main.registerActionShortcut(this, sc); 081 } 082 setTooltip(tooltip); 083 if (getValue("toolbar") == null) { 084 putValue("toolbar", toolbarId); 085 } 086 if (registerInToolbar && Main.toolbar != null) { 087 Main.toolbar.register(this); 088 } 089 if (installAdapters) { 090 installAdapters(); 091 } 092 } 093 094 /** 095 * The new super for all actions. 096 * 097 * Use this super constructor to setup your action. 098 * 099 * @param name the action's text as displayed on the menu (if it is added to a menu) 100 * @param iconName the filename of the icon to use 101 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note 102 * that html is not supported for menu actions on some platforms. 103 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always 104 * do want a shortcut, remember you can always register it with group=none, so you 105 * won't be assigned a shortcut unless the user configures one. If you pass null here, 106 * the user CANNOT configure a shortcut for your action. 107 * @param registerInToolbar register this action for the toolbar preferences? 108 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null 109 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters 110 */ 111 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar, String toolbarId, boolean installAdapters) { 112 this(name, iconName == null ? null : ImageProvider.get(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 @Override 175 public void destroy() { 176 if (sc != null) { 177 Main.unregisterActionShortcut(this); 178 } 179 MapView.removeLayerChangeListener(layerChangeAdapter); 180 DataSet.removeSelectionListener(selectionChangeAdapter); 181 } 182 183 private void setHelpId() { 184 String helpId = "Action/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1); 185 if (helpId.endsWith("Action")) { 186 helpId = helpId.substring(0, helpId.length()-6); 187 } 188 putValue("help", helpId); 189 } 190 191 /** 192 * Sets the tooltip text of this action. 193 * @param tooltip The text to display in tooltip. Can be {@code null} 194 */ 195 public final void setTooltip(String tooltip) { 196 if (tooltip != null) { 197 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 198 } 199 } 200 201 /** 202 * Replies the current edit layer 203 * 204 * @return the current edit layer. null, if no edit layer exists 205 */ 206 protected static OsmDataLayer getEditLayer() { 207 return Main.main != null ? Main.main.getEditLayer() : null; 208 } 209 210 /** 211 * Replies the current dataset 212 * 213 * @return the current dataset. null, if no current dataset exists 214 */ 215 protected static DataSet getCurrentDataSet() { 216 return Main.main != null ? Main.main.getCurrentDataSet() : null; 217 } 218 219 protected void installAdapters() { 220 // make this action listen to layer change and selection change events 221 // 222 layerChangeAdapter = new LayerChangeAdapter(); 223 selectionChangeAdapter = new SelectionChangeAdapter(); 224 MapView.addLayerChangeListener(layerChangeAdapter); 225 DataSet.addSelectionListener(selectionChangeAdapter); 226 initEnabledState(); 227 } 228 229 /** 230 * Override in subclasses to init the enabled state of an action when it is 231 * created. Default behaviour is to call {@link #updateEnabledState()} 232 * 233 * @see #updateEnabledState() 234 * @see #updateEnabledState(Collection) 235 */ 236 protected void initEnabledState() { 237 updateEnabledState(); 238 } 239 240 /** 241 * Override in subclasses to update the enabled state of the action when 242 * something in the JOSM state changes, i.e. when a layer is removed or added. 243 * 244 * See {@link #updateEnabledState(Collection)} to respond to changes in the collection 245 * of selected primitives. 246 * 247 * Default behavior is empty. 248 * 249 * @see #updateEnabledState(Collection) 250 * @see #initEnabledState() 251 */ 252 protected void updateEnabledState() { 253 } 254 255 /** 256 * Override in subclasses to update the enabled state of the action if the 257 * collection of selected primitives changes. This method is called with the 258 * new selection. 259 * 260 * @param selection the collection of selected primitives; may be empty, but not null 261 * 262 * @see #updateEnabledState() 263 * @see #initEnabledState() 264 */ 265 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 266 } 267 268 /** 269 * Adapter for layer change events 270 * 271 */ 272 private class LayerChangeAdapter implements MapView.LayerChangeListener { 273 private void updateEnabledStateInEDT() { 274 GuiHelper.runInEDT(new Runnable() { 275 @Override public void run() { 276 updateEnabledState(); 277 } 278 }); 279 } 280 @Override 281 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 282 updateEnabledStateInEDT(); 283 } 284 285 @Override 286 public void layerAdded(Layer newLayer) { 287 updateEnabledStateInEDT(); 288 } 289 290 @Override 291 public void layerRemoved(Layer oldLayer) { 292 updateEnabledStateInEDT(); 293 } 294 } 295 296 /** 297 * Adapter for selection change events 298 * 299 */ 300 private class SelectionChangeAdapter implements SelectionChangedListener { 301 @Override 302 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 303 updateEnabledState(newSelection); 304 } 305 } 306}