/* * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.actionSystem; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.PossiblyDumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.util.SmartList; import com.intellij.util.ui.UIUtil; import org.intellij.lang.annotations.JdkConstants; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.List; /** * Represents an entity that has a state, a presentation and can be performed. * * For an action to be useful, you need to implement {@link AnAction#actionPerformed} * and optionally to override {@link AnAction#update}. By overriding the * {@link AnAction#update} method you can dynamically change action's presentation * depending on the place (for more information on places see {@link com.intellij.openapi.actionSystem.ActionPlaces}. * * The same action can have various presentations. * * <pre> * public class MyAction extends AnAction { * public MyAction() { * // ... * } * * public void update(AnActionEvent e) { * Presentation presentation = e.getPresentation(); * if (e.getPlace().equals(ActionPlaces.MAIN_MENU)) { * presentation.setText("My Menu item name"); * } else if (e.getPlace().equals(ActionPlaces.MAIN_TOOLBAR)) { * presentation.setText("My Toolbar item name"); * } * } * * public void actionPerformed(AnActionEvent e) { ... } * } * </pre> * * @see AnActionEvent * @see Presentation * @see com.intellij.openapi.actionSystem.ActionPlaces */ public abstract class AnAction implements PossiblyDumbAware { private static final Logger LOG = Logger.getInstance(AnAction.class); public static final Key<List<AnAction>> ACTIONS_KEY = Key.create("AnAction.shortcutSet"); public static final AnAction[] EMPTY_ARRAY = new AnAction[0]; private Presentation myTemplatePresentation; private ShortcutSet myShortcutSet; private boolean myEnabledInModalContext; private boolean myIsDefaultIcon = true; private boolean myWorksInInjected; private boolean myIsGlobal; // action is registered in ActionManager /** * Creates a new action with its text, description and icon set to {@code null}. */ public AnAction(){ this(null, null, null); } /** * Creates a new action with {@code icon} provided. Its text, description set to {@code null}. * * @param icon Default icon to appear in toolbars and menus (Note some platform don't have icons in menu). */ public AnAction(Icon icon){ this(null, null, icon); } /** * Creates a new action with the specified text. Description and icon are * set to {@code null}. * * @param text Serves as a tooltip when the presentation is a button and the name of the * menu item when the presentation is a menu item. */ public AnAction(@Nullable String text){ this(text, null, null); } /** * Constructs a new action with the specified text, description and icon. * * @param text Serves as a tooltip when the presentation is a button and the name of the * menu item when the presentation is a menu item * * @param description Describes current action, this description will appear on * the status bar when presentation has focus * * @param icon Action's icon */ public AnAction(@Nullable String text, @Nullable String description, @Nullable Icon icon){ myShortcutSet = CustomShortcutSet.EMPTY; myEnabledInModalContext = false; Presentation presentation = getTemplatePresentation(); presentation.setText(text); presentation.setDescription(description); presentation.setIcon(icon); } /** * Returns the shortcut set associated with this action. * * @return shortcut set associated with this action */ public final ShortcutSet getShortcutSet(){ return myShortcutSet; } /** * Registers a set of shortcuts that will be processed when the specified component * is the ancestor of focused component. Note that the action doesn't have * to be registered in action manager in order for that shortcut to work. * * @param shortcutSet the shortcuts for the action. * @param component the component for which the shortcuts will be active. */ public final void registerCustomShortcutSet(@NotNull ShortcutSet shortcutSet, @Nullable JComponent component) { registerCustomShortcutSet(shortcutSet, component, null); } public final void registerCustomShortcutSet(int keyCode, @JdkConstants.InputEventMask int modifiers, @Nullable JComponent component) { registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(keyCode, modifiers)), component); } public final void registerCustomShortcutSet(@NotNull ShortcutSet shortcutSet, @Nullable JComponent component, @Nullable Disposable parentDisposable) { setShortcutSet(shortcutSet); registerCustomShortcutSet(component, parentDisposable); } public final void registerCustomShortcutSet(@Nullable JComponent component, @Nullable Disposable parentDisposable) { if (component == null) return; List<AnAction> actionList = UIUtil.getClientProperty(component, ACTIONS_KEY); if (actionList == null) { UIUtil.putClientProperty(component, ACTIONS_KEY, actionList = new SmartList<>()); } if (!actionList.contains(this)) { actionList.add(this); } if (parentDisposable != null) { Disposer.register(parentDisposable, () -> unregisterCustomShortcutSet(component)); } } public final void unregisterCustomShortcutSet(@Nullable JComponent component) { List<AnAction> actionList = UIUtil.getClientProperty(component, ACTIONS_KEY); if (actionList != null) { actionList.remove(this); } } /** * Copies template presentation and shortcuts set from {@code sourceAction}. * * @param sourceAction cannot be {@code null} */ public final void copyFrom(@NotNull AnAction sourceAction){ Presentation sourcePresentation = sourceAction.getTemplatePresentation(); Presentation presentation = getTemplatePresentation(); presentation.copyFrom(sourcePresentation); copyShortcutFrom(sourceAction); } public final void copyShortcutFrom(@NotNull AnAction sourceAction) { setShortcutSet(sourceAction.getShortcutSet()); } public final boolean isEnabledInModalContext() { return myEnabledInModalContext; } protected final void setEnabledInModalContext(boolean enabledInModalContext) { myEnabledInModalContext = enabledInModalContext; } /** * Override with true returned if your action has to display its text along with the icon when placed in the toolbar */ public boolean displayTextInToolbar() { return false; } /** * Updates the state of the action. Default implementation does nothing. * Override this method to provide the ability to dynamically change action's * state and(or) presentation depending on the context (For example * when your action state depends on the selection you can check for * selection and change the state accordingly). * This method can be called frequently, for instance, if an action is added to a toolbar, * it will be updated twice a second. This means that this method is supposed to work really fast, * no real work should be done at this phase. For example, checking selection in a tree or a list, * is considered valid, but working with a file system is not. If you cannot understand the state of * the action fast you should do it in the {@link #actionPerformed(AnActionEvent)} method and notify * the user that action cannot be executed if it's the case. * * @param e Carries information on the invocation place and data available */ public void update(AnActionEvent e) { } /** * Same as {@link #update(AnActionEvent)} but is calls immediately before actionPerformed() as final check guard. * Default implementation delegates to {@link #update(AnActionEvent)}. * * @param e Carries information on the invocation place and data available */ public void beforeActionPerformedUpdate(@NotNull AnActionEvent e) { boolean worksInInjected = isInInjectedContext(); e.setInjectedContext(worksInInjected); update(e); if (!e.getPresentation().isEnabled() && worksInInjected) { e.setInjectedContext(false); update(e); } } /** * Returns a template presentation that will be used * as a template for created presentations. * * @return template presentation */ @NotNull public final Presentation getTemplatePresentation() { Presentation presentation = myTemplatePresentation; if (presentation == null){ myTemplatePresentation = presentation = new Presentation(); } return presentation; } /** * Implement this method to provide your action handler. * * @param e Carries information on the invocation place */ public abstract void actionPerformed(AnActionEvent e); protected void setShortcutSet(ShortcutSet shortcutSet) { if (myIsGlobal && myShortcutSet != shortcutSet) { LOG.warn("ShortcutSet of global AnActions should not be changed outside of KeymapManager.\n" + "This is likely not what you wanted to do. Consider setting shortcut in keymap defaults, inheriting from other action " + "using `use-shortcut-of` or wrapping with EmptyAction.wrap().", new Throwable()); } myShortcutSet = shortcutSet; } /** * Sets the flag indicating whether the action has an internal or a user-customized icon. * @param isDefaultIconSet true if the icon is internal, false if the icon is customized by the user. */ public void setDefaultIcon(boolean isDefaultIconSet) { myIsDefaultIcon = isDefaultIconSet; } /** * Returns true if the action has an internal, not user-customized icon. * @return true if the icon is internal, false if the icon is customized by the user. */ public boolean isDefaultIcon() { return myIsDefaultIcon; } /** * Enables automatic detection of injected fragments in editor. Values in DataContext, passed to the action, like EDITOR, PSI_FILE * will refer to an injected fragment, if caret is currently positioned on it. */ public void setInjectedContext(boolean worksInInjected) { myWorksInInjected = worksInInjected; } public boolean isInInjectedContext() { return myWorksInInjected; } public boolean isTransparentUpdate() { return this instanceof TransparentUpdate; } @Override public boolean isDumbAware() { return this instanceof DumbAware; } /** * @return whether this action should be wrapped into a single transaction. PSI/VFS-related actions * that can show progresses or modal dialogs should return true. The default value is false, to prevent * transaction-related assertions from actions in harmless dialogs like "Enter password" shown inside invokeLater. * @see com.intellij.openapi.application.TransactionGuard */ public boolean startInTransaction() { return false; } public interface TransparentUpdate { } @Nullable public static Project getEventProject(AnActionEvent e) { return e == null ? null : e.getData(CommonDataKeys.PROJECT); } @Override public String toString() { return getTemplatePresentation().toString(); } void markAsGlobal() { myIsGlobal = true; } }