001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.actions; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.event.KeyEvent; 007 import java.util.Collection; 008 009 import javax.swing.AbstractAction; 010 import javax.swing.Icon; 011 012 import org.openstreetmap.josm.Main; 013 import org.openstreetmap.josm.data.SelectionChangedListener; 014 import org.openstreetmap.josm.data.osm.DataSet; 015 import org.openstreetmap.josm.data.osm.OsmPrimitive; 016 import org.openstreetmap.josm.gui.MapView; 017 import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 018 import org.openstreetmap.josm.gui.layer.Layer; 019 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 020 import org.openstreetmap.josm.tools.Destroyable; 021 import org.openstreetmap.josm.tools.ImageProvider; 022 import org.openstreetmap.josm.tools.Shortcut; 023 024 /** 025 * Base class helper for all Actions in JOSM. Just to make the life easier. 026 * 027 * A JosmAction is a {@link LayerChangeListener} and a {@link SelectionChangedListener}. Upon 028 * a layer change event or a selection change event it invokes {@link #updateEnabledState()}. 029 * Subclasses can override {@link #updateEnabledState()} in order to update the {@link #isEnabled()}-state 030 * of a JosmAction depending on the {@link #getCurrentDataSet()} and the current layers 031 * (see also {@link #getEditLayer()}). 032 * 033 * destroy() from interface Destroyable is called e.g. for MapModes, when the last layer has 034 * been removed and so the mapframe will be destroyed. For other JosmActions, destroy() may never 035 * be called (currently). 036 * 037 * @author imi 038 */ 039 abstract public class JosmAction extends AbstractAction implements Destroyable { 040 041 protected Shortcut sc; 042 private LayerChangeAdapter layerChangeAdapter; 043 private SelectionChangeAdapter selectionChangeAdapter; 044 045 public Shortcut getShortcut() { 046 if (sc == null) { 047 sc = Shortcut.registerShortcut("core:none", tr("No Shortcut"), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE); 048 // as this shortcut is shared by all action that don't want to have a shortcut, 049 // we shouldn't allow the user to change it... 050 // this is handled by special name "core:none" 051 } 052 return sc; 053 } 054 055 /** 056 * Constructs a {@code JosmAction}. 057 * 058 * @param name the action's text as displayed on the menu (if it is added to a menu) 059 * @param icon the icon to use 060 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note 061 * that html is not supported for menu actions on some platforms. 062 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always 063 * do want a shortcut, remember you can always register it with group=none, so you 064 * won't be assigned a shortcut unless the user configures one. If you pass null here, 065 * the user CANNOT configure a shortcut for your action. 066 * @param registerInToolbar register this action for the toolbar preferences? 067 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null 068 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters 069 */ 070 public JosmAction(String name, Icon icon, String tooltip, Shortcut shortcut, boolean registerInToolbar, String toolbarId, boolean installAdapters) { 071 super(name, icon); 072 setHelpId(); 073 sc = shortcut; 074 if (sc != null) { 075 Main.registerActionShortcut(this, sc); 076 } 077 setTooltip(tooltip); 078 if (getValue("toolbar") == null) { 079 putValue("toolbar", toolbarId); 080 } 081 if (registerInToolbar) { 082 Main.toolbar.register(this); 083 } 084 if (installAdapters) { 085 installAdapters(); 086 } 087 } 088 089 /** 090 * The new super for all actions. 091 * 092 * Use this super constructor to setup your action. 093 * 094 * @param name the action's text as displayed on the menu (if it is added to a menu) 095 * @param iconName the filename of the icon to use 096 * @param tooltip a longer description of the action that will be displayed in the tooltip. Please note 097 * that html is not supported for menu actions on some platforms. 098 * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always 099 * do want a shortcut, remember you can always register it with group=none, so you 100 * won't be assigned a shortcut unless the user configures one. If you pass null here, 101 * the user CANNOT configure a shortcut for your action. 102 * @param register register this action for the toolbar preferences? 103 * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null 104 * @param installAdapters false, if you don't want to install layer changed and selection changed adapters 105 */ 106 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean register, String toolbarId, boolean installAdapters) { 107 this(name, iconName == null ? null : ImageProvider.get(iconName), tooltip, shortcut, register, 108 toolbarId == null ? iconName : toolbarId, installAdapters); 109 } 110 111 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean register, boolean installAdapters) { 112 this(name, iconName, tooltip, shortcut, register, null, installAdapters); 113 } 114 115 public JosmAction(String name, String iconName, String tooltip, Shortcut shortcut, boolean register) { 116 this(name, iconName, tooltip, shortcut, register, null, true); 117 } 118 119 public JosmAction() { 120 this(true); 121 } 122 123 public JosmAction(boolean installAdapters) { 124 setHelpId(); 125 if (installAdapters) { 126 installAdapters(); 127 } 128 } 129 130 @Override 131 public void destroy() { 132 if (sc != null) { 133 Main.unregisterActionShortcut(this); 134 } 135 MapView.removeLayerChangeListener(layerChangeAdapter); 136 DataSet.removeSelectionListener(selectionChangeAdapter); 137 } 138 139 private void setHelpId() { 140 String helpId = "Action/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1); 141 if (helpId.endsWith("Action")) { 142 helpId = helpId.substring(0, helpId.length()-6); 143 } 144 putValue("help", helpId); 145 } 146 147 public void setTooltip(String tooltip) { 148 if (tooltip != null) { 149 putValue(SHORT_DESCRIPTION, Main.platform.makeTooltip(tooltip, sc)); 150 } 151 } 152 153 /** 154 * Replies the current edit layer 155 * 156 * @return the current edit layer. null, if no edit layer exists 157 */ 158 protected static OsmDataLayer getEditLayer() { 159 return Main.main.getEditLayer(); 160 } 161 162 /** 163 * Replies the current dataset 164 * 165 * @return the current dataset. null, if no current dataset exists 166 */ 167 protected static DataSet getCurrentDataSet() { 168 return Main.main.getCurrentDataSet(); 169 } 170 171 protected void installAdapters() { 172 // make this action listen to layer change and selection change events 173 // 174 layerChangeAdapter = new LayerChangeAdapter(); 175 selectionChangeAdapter = new SelectionChangeAdapter(); 176 MapView.addLayerChangeListener(layerChangeAdapter); 177 DataSet.addSelectionListener(selectionChangeAdapter); 178 initEnabledState(); 179 } 180 181 /** 182 * Override in subclasses to init the enabled state of an action when it is 183 * created. Default behaviour is to call {@link #updateEnabledState()} 184 * 185 * @see #updateEnabledState() 186 * @see #updateEnabledState(Collection) 187 */ 188 protected void initEnabledState() { 189 updateEnabledState(); 190 } 191 192 /** 193 * Override in subclasses to update the enabled state of the action when 194 * something in the JOSM state changes, i.e. when a layer is removed or added. 195 * 196 * See {@link #updateEnabledState(Collection)} to respond to changes in the collection 197 * of selected primitives. 198 * 199 * Default behavior is empty. 200 * 201 * @see #updateEnabledState(Collection) 202 * @see #initEnabledState() 203 */ 204 protected void updateEnabledState() { 205 } 206 207 /** 208 * Override in subclasses to update the enabled state of the action if the 209 * collection of selected primitives changes. This method is called with the 210 * new selection. 211 * 212 * @param selection the collection of selected primitives; may be empty, but not null 213 * 214 * @see #updateEnabledState() 215 * @see #initEnabledState() 216 */ 217 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 218 } 219 220 /** 221 * Adapter for layer change events 222 * 223 */ 224 private class LayerChangeAdapter implements MapView.LayerChangeListener { 225 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 226 updateEnabledState(); 227 } 228 229 public void layerAdded(Layer newLayer) { 230 updateEnabledState(); 231 } 232 233 public void layerRemoved(Layer oldLayer) { 234 updateEnabledState(); 235 } 236 } 237 238 /** 239 * Adapter for selection change events 240 * 241 */ 242 private class SelectionChangeAdapter implements SelectionChangedListener { 243 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 244 updateEnabledState(newSelection); 245 } 246 } 247 }