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 * @deprecated do not pass Icon, pass ImageProvider instead 075 */ 076 @Deprecated 077 public JosmAction(String name, Icon icon, String tooltip, Shortcut shortcut, boolean registerInToolbar, String toolbarId, boolean installAdapters) { 078 super(name, icon); 079 setHelpId(); 080 sc = shortcut; 081 if (sc != null) { 082 Main.registerActionShortcut(this, sc); 083 } 084 setTooltip(tooltip); 085 if (getValue("toolbar") == null) { 086 putValue("toolbar", toolbarId); 087 } 088 if (registerInToolbar && Main.toolbar != null) { 089 Main.toolbar.register(this); 090 } 091 if (installAdapters) { 092 installAdapters(); 093 } 094 } 095 096 /** 097 * Constructs a {@code JosmAction}. 098 * 099 * @param name the action's text as displayed on the menu (if it is added to a menu) 100 * @param icon 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 * TODO: do not pass Icon, pass ImageProvider instead 111 */ 112 public JosmAction(String name, ImageProvider icon, String tooltip, Shortcut shortcut, boolean registerInToolbar, String toolbarId, boolean installAdapters) { 113 super(name); 114 if(icon != null) 115 icon.getResource().getImageIcon(this); 116 setHelpId(); 117 sc = shortcut; 118 if (sc != null) { 119 Main.registerActionShortcut(this, sc); 120 } 121 setTooltip(tooltip); 122 if (getValue("toolbar") == null) { 123 putValue("toolbar", toolbarId); 124 } 125 if (registerInToolbar && Main.toolbar != null) { 126 Main.toolbar.register(this); 127 } 128 if (installAdapters) { 129 installAdapters(); 130 } 131 } 132 133 /** 134 * The new super for all actions. 135 * 136 * Use this super constructor to setup your action. 137 * 138 * @param name the action's text as displayed on the menu (if it is added to a menu) 139 * @param iconName the filename of the icon to use 140 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note 141 * that html is not supported for menu actions on some platforms. 142 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always 143 * do want a shortcut, remember you can always register it with group=none, so you 144 * won't be assigned a shortcut unless the user configures one. If you pass null here, 145 * the user CANNOT configure a shortcut for your action. 146 * @param registerInToolbar register this action for the toolbar preferences? 147 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null 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, String toolbarId, boolean installAdapters) { 151 this(name, iconName == null ? null : new ImageProvider(iconName), tooltip, shortcut, registerInToolbar, 152 toolbarId == null ? iconName : toolbarId, installAdapters); 153 } 154 155 /** 156 * Constructs a new {@code JosmAction}. 157 * 158 * Use this super constructor to setup your action. 159 * 160 * @param name the action's text as displayed on the menu (if it is added to a menu) 161 * @param iconName the filename of the icon to use 162 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note 163 * that html is not supported for menu actions on some platforms. 164 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always 165 * do want a shortcut, remember you can always register it with group=none, so you 166 * won't be assigned a shortcut unless the user configures one. If you pass null here, 167 * the user CANNOT configure a shortcut for your action. 168 * @param registerInToolbar register this action for the toolbar preferences? 169 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters 170 */ 171 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar, boolean installAdapters) { 172 this(name, iconName, tooltip, shortcut, registerInToolbar, null, installAdapters); 173 } 174 175 /** 176 * Constructs a new {@code JosmAction}. 177 * 178 * Use this super constructor to setup your action. 179 * 180 * @param name the action's text as displayed on the menu (if it is added to a menu) 181 * @param iconName the filename of the icon to use 182 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note 183 * that html is not supported for menu actions on some platforms. 184 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always 185 * do want a shortcut, remember you can always register it with group=none, so you 186 * won't be assigned a shortcut unless the user configures one. If you pass null here, 187 * the user CANNOT configure a shortcut for your action. 188 * @param registerInToolbar register this action for the toolbar preferences? 189 */ 190 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean registerInToolbar) { 191 this(name, iconName, tooltip, shortcut, registerInToolbar, null, true); 192 } 193 194 /** 195 * Constructs a new {@code JosmAction}. 196 */ 197 public JosmAction() { 198 this(true); 199 } 200 201 /** 202 * Constructs a new {@code JosmAction}. 203 * 204 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters 205 */ 206 public JosmAction(boolean installAdapters) { 207 setHelpId(); 208 if (installAdapters) { 209 installAdapters(); 210 } 211 } 212 213 @Override 214 public void destroy() { 215 if (sc != null) { 216 Main.unregisterActionShortcut(this); 217 } 218 MapView.removeLayerChangeListener(layerChangeAdapter); 219 DataSet.removeSelectionListener(selectionChangeAdapter); 220 } 221 222 private void setHelpId() { 223 String helpId = "Action/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1); 224 if (helpId.endsWith("Action")) { 225 helpId = helpId.substring(0, helpId.length()-6); 226 } 227 putValue("help", helpId); 228 } 229 230 /** 231 * Sets the tooltip text of this action. 232 * @param tooltip The text to display in tooltip. Can be {@code null} 233 */ 234 public final void setTooltip(String tooltip) { 235 if (tooltip != null) { 236 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 237 } 238 } 239 240 /** 241 * Replies the current edit layer 242 * 243 * @return the current edit layer. null, if no edit layer exists 244 */ 245 protected static OsmDataLayer getEditLayer() { 246 return Main.main != null ? Main.main.getEditLayer() : null; 247 } 248 249 /** 250 * Replies the current dataset 251 * 252 * @return the current dataset. null, if no current dataset exists 253 */ 254 protected static DataSet getCurrentDataSet() { 255 return Main.main != null ? Main.main.getCurrentDataSet() : null; 256 } 257 258 protected void installAdapters() { 259 // make this action listen to layer change and selection change events 260 // 261 layerChangeAdapter = new LayerChangeAdapter(); 262 selectionChangeAdapter = new SelectionChangeAdapter(); 263 MapView.addLayerChangeListener(layerChangeAdapter); 264 DataSet.addSelectionListener(selectionChangeAdapter); 265 initEnabledState(); 266 } 267 268 /** 269 * Override in subclasses to init the enabled state of an action when it is 270 * created. Default behaviour is to call {@link #updateEnabledState()} 271 * 272 * @see #updateEnabledState() 273 * @see #updateEnabledState(Collection) 274 */ 275 protected void initEnabledState() { 276 updateEnabledState(); 277 } 278 279 /** 280 * Override in subclasses to update the enabled state of the action when 281 * something in the JOSM state changes, i.e. when a layer is removed or added. 282 * 283 * See {@link #updateEnabledState(Collection)} to respond to changes in the collection 284 * of selected primitives. 285 * 286 * Default behavior is empty. 287 * 288 * @see #updateEnabledState(Collection) 289 * @see #initEnabledState() 290 */ 291 protected void updateEnabledState() { 292 } 293 294 /** 295 * Override in subclasses to update the enabled state of the action if the 296 * collection of selected primitives changes. This method is called with the 297 * new selection. 298 * 299 * @param selection the collection of selected primitives; may be empty, but not null 300 * 301 * @see #updateEnabledState() 302 * @see #initEnabledState() 303 */ 304 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 305 } 306 307 /** 308 * Adapter for layer change events 309 * 310 */ 311 private class LayerChangeAdapter implements MapView.LayerChangeListener { 312 private void updateEnabledStateInEDT() { 313 GuiHelper.runInEDT(new Runnable() { 314 @Override public void run() { 315 updateEnabledState(); 316 } 317 }); 318 } 319 @Override 320 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 321 updateEnabledStateInEDT(); 322 } 323 324 @Override 325 public void layerAdded(Layer newLayer) { 326 updateEnabledStateInEDT(); 327 } 328 329 @Override 330 public void layerRemoved(Layer oldLayer) { 331 updateEnabledStateInEDT(); 332 } 333 } 334 335 /** 336 * Adapter for selection change events 337 */ 338 private class SelectionChangeAdapter implements SelectionChangedListener { 339 @Override 340 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 341 updateEnabledState(newSelection); 342 } 343 } 344}