blob: 32e823bd9758e7ed115baa00dbc2603a2e558812 [file] [log] [blame]
/*
* Copyright 2000-2014 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.impl;
import com.intellij.AbstractBundle;
import com.intellij.CommonBundle;
import com.intellij.diagnostic.PluginException;
import com.intellij.ide.ActivityTracker;
import com.intellij.ide.DataManager;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.idea.IdeaLogger;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.ex.AnActionListener;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.ProjectType;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.pico.ConstructorInjectionComponentAdapter;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectIntHashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.Future;
public final class ActionManagerImpl extends ActionManagerEx implements ApplicationComponent {
@NonNls public static final String ACTION_ELEMENT_NAME = "action";
@NonNls public static final String GROUP_ELEMENT_NAME = "group";
@NonNls public static final String ACTIONS_ELEMENT_NAME = "actions";
@NonNls public static final String CLASS_ATTR_NAME = "class";
@NonNls public static final String ID_ATTR_NAME = "id";
@NonNls public static final String INTERNAL_ATTR_NAME = "internal";
@NonNls public static final String ICON_ATTR_NAME = "icon";
@NonNls public static final String ADD_TO_GROUP_ELEMENT_NAME = "add-to-group";
@NonNls public static final String SHORTCUT_ELEMENT_NAME = "keyboard-shortcut";
@NonNls public static final String MOUSE_SHORTCUT_ELEMENT_NAME = "mouse-shortcut";
@NonNls public static final String DESCRIPTION = "description";
@NonNls public static final String TEXT_ATTR_NAME = "text";
@NonNls public static final String POPUP_ATTR_NAME = "popup";
@NonNls public static final String COMPACT_ATTR_NAME = "compact";
@NonNls public static final String SEPARATOR_ELEMENT_NAME = "separator";
@NonNls public static final String REFERENCE_ELEMENT_NAME = "reference";
@NonNls public static final String ABBREVIATION_ELEMENT_NAME = "abbreviation";
@NonNls public static final String GROUPID_ATTR_NAME = "group-id";
@NonNls public static final String ANCHOR_ELEMENT_NAME = "anchor";
@NonNls public static final String FIRST = "first";
@NonNls public static final String LAST = "last";
@NonNls public static final String BEFORE = "before";
@NonNls public static final String AFTER = "after";
@NonNls public static final String SECONDARY = "secondary";
@NonNls public static final String RELATIVE_TO_ACTION_ATTR_NAME = "relative-to-action";
@NonNls public static final String FIRST_KEYSTROKE_ATTR_NAME = "first-keystroke";
@NonNls public static final String SECOND_KEYSTROKE_ATTR_NAME = "second-keystroke";
@NonNls public static final String REMOVE_SHORTCUT_ATTR_NAME = "remove";
@NonNls public static final String REPLACE_SHORTCUT_ATTR_NAME = "replace-all";
@NonNls public static final String KEYMAP_ATTR_NAME = "keymap";
@NonNls public static final String KEYSTROKE_ATTR_NAME = "keystroke";
@NonNls public static final String REF_ATTR_NAME = "ref";
@NonNls public static final String VALUE_ATTR_NAME = "value";
@NonNls public static final String ACTIONS_BUNDLE = "messages.ActionsBundle";
@NonNls public static final String USE_SHORTCUT_OF_ATTR_NAME = "use-shortcut-of";
@NonNls public static final String OVERRIDES_ATTR_NAME = "overrides";
@NonNls public static final String KEEP_CONTENT_ATTR_NAME = "keep-content";
@NonNls public static final String PROJECT_TYPE = "project-type";
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.impl.ActionManagerImpl");
private static final int DEACTIVATED_TIMER_DELAY = 5000;
private static final int TIMER_DELAY = 500;
private static final int UPDATE_DELAY_AFTER_TYPING = 500;
private final Object myLock = new Object();
private final Map<String,AnAction> myId2Action = new THashMap<String, AnAction>();
private final Map<PluginId, THashSet<String>> myPlugin2Id = new THashMap<PluginId, THashSet<String>>();
private final TObjectIntHashMap<String> myId2Index = new TObjectIntHashMap<String>();
private final Map<Object,String> myAction2Id = new THashMap<Object, String>();
private final MultiMap<String,String> myId2GroupId = new MultiMap<String, String>();
private final List<String> myNotRegisteredInternalActionIds = new ArrayList<String>();
private final List<AnActionListener> myActionListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final KeymapManager myKeymapManager;
private final DataManager myDataManager;
private final List<ActionPopupMenuImpl> myPopups = new ArrayList<ActionPopupMenuImpl>();
private final Map<AnAction, DataContext> myQueuedNotifications = new LinkedHashMap<AnAction, DataContext>();
private final Map<AnAction, AnActionEvent> myQueuedNotificationsEvents = new LinkedHashMap<AnAction, AnActionEvent>();
private MyTimer myTimer;
private int myRegisteredActionsCount;
private String myLastPreformedActionId;
private String myPrevPerformedActionId;
private long myLastTimeEditorWasTypedIn = 0;
private Runnable myPreloadActionsRunnable;
private boolean myTransparentOnlyUpdate;
private int myActionsPreloaded = 0;
ActionManagerImpl(KeymapManager keymapManager, DataManager dataManager) {
myKeymapManager = keymapManager;
myDataManager = dataManager;
registerPluginActions();
}
static AnAction convertStub(ActionStub stub) {
Object obj;
String className = stub.getClassName();
try {
Class<?> aClass = Class.forName(className, true, stub.getLoader());
obj = ReflectionUtil.newInstance(aClass);
}
catch (ClassNotFoundException e) {
PluginId pluginId = stub.getPluginId();
if (pluginId != null) {
throw new PluginException("class with name \"" + className + "\" not found", e, pluginId);
}
else {
throw new IllegalStateException("class with name \"" + className + "\" not found");
}
}
catch(UnsupportedClassVersionError e) {
PluginId pluginId = stub.getPluginId();
if (pluginId != null) {
throw new PluginException(e, pluginId);
}
else {
throw new IllegalStateException(e);
}
}
catch (Exception e) {
PluginId pluginId = stub.getPluginId();
if (pluginId != null) {
throw new PluginException("cannot create class \"" + className + "\"", e, pluginId);
}
else {
throw new IllegalStateException("cannot create class \"" + className + "\"", e);
}
}
if (!(obj instanceof AnAction)) {
throw new IllegalStateException("class with name '" + className + "' must be an instance of '" + AnAction.class.getName()+"'; got "+obj);
}
AnAction anAction = (AnAction)obj;
stub.initAction(anAction);
if (StringUtil.isNotEmpty(stub.getText())) {
anAction.getTemplatePresentation().setText(stub.getText());
}
String iconPath = stub.getIconPath();
if (iconPath != null) {
Class<? extends AnAction> actionClass = anAction.getClass();
setIconFromClass(actionClass, actionClass.getClassLoader(), iconPath, anAction.getTemplatePresentation(), stub.getPluginId());
}
return anAction;
}
private static void processAbbreviationNode(Element e, String id) {
final String abbr = e.getAttributeValue(VALUE_ATTR_NAME);
if (!StringUtil.isEmpty(abbr)) {
final AbbreviationManagerImpl abbreviationManager = ((AbbreviationManagerImpl)AbbreviationManager.getInstance());
abbreviationManager.register(abbr, id, true);
}
}
@Nullable
private static ResourceBundle getActionsResourceBundle(ClassLoader loader, IdeaPluginDescriptor plugin) {
@NonNls final String resBundleName = plugin != null && !"com.intellij".equals(plugin.getPluginId().getIdString())
? plugin.getResourceBundleBaseName() : ACTIONS_BUNDLE;
ResourceBundle bundle = null;
if (resBundleName != null) {
bundle = AbstractBundle.getResourceBundle(resBundleName, loader);
}
return bundle;
}
private static boolean isSecondary(Element element) {
return "true".equalsIgnoreCase(element.getAttributeValue(SECONDARY));
}
private static void setIcon(@Nullable final String iconPath,
@NotNull String className,
@NotNull ClassLoader loader,
@NotNull Presentation presentation,
final PluginId pluginId) {
if (iconPath == null) return;
try {
final Class actionClass = Class.forName(className, true, loader);
setIconFromClass(actionClass, loader, iconPath, presentation, pluginId);
}
catch (ClassNotFoundException e) {
LOG.error(e);
reportActionError(pluginId, "class with name \"" + className + "\" not found");
}
catch (NoClassDefFoundError e) {
LOG.error(e);
reportActionError(pluginId, "class with name \"" + className + "\" not found");
}
}
private static void setIconFromClass(@NotNull final Class actionClass,
@NotNull final ClassLoader classLoader,
@NotNull final String iconPath,
@NotNull Presentation presentation,
final PluginId pluginId) {
final IconLoader.LazyIcon lazyIcon = new IconLoader.LazyIcon() {
@Override
protected Icon compute() {
//try to find icon in idea class path
Icon icon = IconLoader.findIcon(iconPath, actionClass, true);
if (icon == null) {
icon = IconLoader.findIcon(iconPath, classLoader);
}
if (icon == null) {
reportActionError(pluginId, "Icon cannot be found in '" + iconPath + "', action '" + actionClass + "'");
}
return icon;
}
@Override
public String toString() {
return "LazyIcon@ActionManagerImpl (path: " + iconPath + ", action class: " + actionClass + ")";
}
};
if (!Registry.is("ide.lazyIconLoading")) {
lazyIcon.load();
}
presentation.setIcon(lazyIcon);
}
private static String loadDescriptionForElement(final Element element, final ResourceBundle bundle, final String id, String elementType) {
final String value = element.getAttributeValue(DESCRIPTION);
if (bundle != null) {
@NonNls final String key = elementType + "." + id + ".description";
return CommonBundle.messageOrDefault(bundle, key, value == null ? "" : value);
} else {
return value;
}
}
private static String loadTextForElement(final Element element, final ResourceBundle bundle, final String id, String elementType) {
final String value = element.getAttributeValue(TEXT_ATTR_NAME);
return CommonBundle.messageOrDefault(bundle, elementType + "." + id + "." + TEXT_ATTR_NAME, value == null ? "" : value);
}
public static boolean checkRelativeToAction(final String relativeToActionId,
@NotNull final Anchor anchor,
@NotNull final String actionName,
@Nullable final PluginId pluginId) {
if ((Anchor.BEFORE == anchor || Anchor.AFTER == anchor) && relativeToActionId == null) {
reportActionError(pluginId, actionName + ": \"relative-to-action\" cannot be null if anchor is \"after\" or \"before\"");
return false;
}
return true;
}
@Nullable
public static Anchor parseAnchor(final String anchorStr,
@Nullable final String actionName,
@Nullable final PluginId pluginId) {
if (anchorStr == null) {
return Anchor.LAST;
}
if (FIRST.equalsIgnoreCase(anchorStr)) {
return Anchor.FIRST;
}
else if (LAST.equalsIgnoreCase(anchorStr)) {
return Anchor.LAST;
}
else if (BEFORE.equalsIgnoreCase(anchorStr)) {
return Anchor.BEFORE;
}
else if (AFTER.equalsIgnoreCase(anchorStr)) {
return Anchor.AFTER;
}
else {
reportActionError(pluginId, actionName + ": anchor should be one of the following constants: \"first\", \"last\", \"before\" or \"after\"");
return null;
}
}
private static void processMouseShortcutNode(Element element, String actionId, PluginId pluginId) {
String keystrokeString = element.getAttributeValue(KEYSTROKE_ATTR_NAME);
if (keystrokeString == null || keystrokeString.trim().isEmpty()) {
reportActionError(pluginId, "\"keystroke\" attribute must be specified for action with id=" + actionId);
return;
}
MouseShortcut shortcut;
try {
shortcut = KeymapUtil.parseMouseShortcut(keystrokeString);
}
catch (Exception ex) {
reportActionError(pluginId, "\"keystroke\" attribute has invalid value for action with id=" + actionId);
return;
}
String keymapName = element.getAttributeValue(KEYMAP_ATTR_NAME);
if (keymapName == null || keymapName.isEmpty()) {
reportActionError(pluginId, "attribute \"keymap\" should be defined");
return;
}
Keymap keymap = KeymapManager.getInstance().getKeymap(keymapName);
if (keymap == null) {
reportActionError(pluginId, "keymap \"" + keymapName + "\" not found");
return;
}
final String removeOption = element.getAttributeValue(REMOVE_SHORTCUT_ATTR_NAME);
if (Boolean.valueOf(removeOption)) {
keymap.removeShortcut(actionId, shortcut);
} else {
keymap.addShortcut(actionId, shortcut);
}
}
private static void assertActionIsGroupOrStub(final AnAction action) {
if (!(action instanceof ActionGroup || action instanceof ActionStub || action instanceof ChameleonAction)) {
LOG.error("Action : " + action + "; class: " + action.getClass());
}
}
private static void reportActionError(final PluginId pluginId, @NonNls @NotNull String message) {
if (pluginId == null) {
LOG.error(message);
}
else {
LOG.error(new PluginException(message, null, pluginId));
}
}
@NonNls
private static String getPluginInfo(@Nullable PluginId id) {
if (id != null) {
final IdeaPluginDescriptor plugin = PluginManager.getPlugin(id);
if (plugin != null) {
String name = plugin.getName();
if (name == null) {
name = id.getIdString();
}
return " Plugin: " + name;
}
}
return "";
}
private static DataContext getContextBy(Component contextComponent) {
final DataManager dataManager = DataManager.getInstance();
return contextComponent != null ? dataManager.getDataContext(contextComponent) : dataManager.getDataContext();
}
@Override
public void initComponent() {}
@Override
public void disposeComponent() {
if (myTimer != null) {
myTimer.stop();
myTimer = null;
}
}
@Override
public void addTimerListener(int delay, final TimerListener listener) {
_addTimerListener(listener, false);
}
@Override
public void removeTimerListener(TimerListener listener) {
_removeTimerListener(listener, false);
}
@Override
public void addTransparentTimerListener(int delay, TimerListener listener) {
_addTimerListener(listener, true);
}
@Override
public void removeTransparentTimerListener(TimerListener listener) {
_removeTimerListener(listener, true);
}
private void _addTimerListener(final TimerListener listener, boolean transparent) {
if (ApplicationManager.getApplication().isUnitTestMode()) return;
if (myTimer == null) {
myTimer = new MyTimer();
myTimer.start();
}
myTimer.addTimerListener(listener, transparent);
}
private void _removeTimerListener(TimerListener listener, boolean transparent) {
if (ApplicationManager.getApplication().isUnitTestMode()) return;
LOG.assertTrue(myTimer != null);
myTimer.removeTimerListener(listener, transparent);
}
public ActionPopupMenu createActionPopupMenu(String place, @NotNull ActionGroup group, @Nullable PresentationFactory presentationFactory) {
return new ActionPopupMenuImpl(place, group, this, presentationFactory);
}
@Override
public ActionPopupMenu createActionPopupMenu(String place, @NotNull ActionGroup group) {
return new ActionPopupMenuImpl(place, group, this, null);
}
@Override
public ActionToolbar createActionToolbar(final String place, @NotNull final ActionGroup group, final boolean horizontal) {
return createActionToolbar(place, group, horizontal, false);
}
@Override
public ActionToolbar createActionToolbar(final String place, @NotNull final ActionGroup group, final boolean horizontal, final boolean decorateButtons) {
return new ActionToolbarImpl(place, group, horizontal, decorateButtons, myDataManager, this, (KeymapManagerEx)myKeymapManager);
}
private void registerPluginActions() {
final IdeaPluginDescriptor[] plugins = PluginManagerCore.getPlugins();
for (IdeaPluginDescriptor plugin : plugins) {
if (PluginManagerCore.shouldSkipPlugin(plugin)) continue;
final List<Element> elementList = plugin.getActionsDescriptionElements();
if (elementList != null) {
for (Element e : elementList) {
processActionsChildElement(plugin.getPluginClassLoader(), plugin.getPluginId(), e);
}
}
}
}
@Override
public AnAction getAction(@NotNull String id) {
return getActionImpl(id, false, null);
}
@Override
public AnAction getAction(@NonNls @NotNull String actionId, @Nullable ProjectType projectType) {
return getActionImpl(actionId, false, projectType);
}
private AnAction getActionImpl(String id, boolean canReturnStub, ProjectType projectType) {
synchronized (myLock) {
AnAction action;
Object o = myId2Action.get(id);
if (o == null) {
return null;
}
if (o instanceof AnAction) {
action = (AnAction)o;
}
else {
//noinspection unchecked
action = ((Map<ProjectType, AnAction>)o).get(projectType);
}
if (!canReturnStub && action instanceof ActionStub) {
action = convert((ActionStub)action);
}
return action;
}
}
/**
* Converts action's stub to normal action.
*/
@NotNull
private AnAction convert(@NotNull ActionStub stub) {
LOG.assertTrue(myAction2Id.containsKey(stub));
myAction2Id.remove(stub);
LOG.assertTrue(myId2Action.containsKey(stub.getId()));
AnAction action = myId2Action.remove(stub.getId());
LOG.assertTrue(action != null);
LOG.assertTrue(action.equals(stub));
AnAction anAction = convertStub(stub);
myAction2Id.put(anAction, stub.getId());
return addToMap(stub.getId(), anAction, stub.getPluginId(), stub.getProjectType());
}
@Override
public String getId(@NotNull AnAction action) {
LOG.assertTrue(!(action instanceof ActionStub));
synchronized (myLock) {
return myAction2Id.get(action);
}
}
@Override
public String[] getActionIds(@NotNull String idPrefix) {
synchronized (myLock) {
ArrayList<String> idList = new ArrayList<String>();
for (String id : myId2Action.keySet()) {
if (id.startsWith(idPrefix)) {
idList.add(id);
}
}
return ArrayUtil.toStringArray(idList);
}
}
@Override
public boolean isGroup(@NotNull String actionId) {
return getActionImpl(actionId, true, null) instanceof ActionGroup;
}
@Override
public JComponent createButtonToolbar(final String actionPlace, @NotNull final ActionGroup messageActionGroup) {
return new ButtonToolbarImpl(actionPlace, messageActionGroup, myDataManager, this);
}
@Override
public AnAction getActionOrStub(String id) {
return getActionImpl(id, true, null);
}
/**
* @return instance of ActionGroup or ActionStub. The method never returns real subclasses
* of <code>AnAction</code>.
*/
@Nullable
private AnAction processActionElement(Element element, final ClassLoader loader, PluginId pluginId) {
final IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
ResourceBundle bundle = getActionsResourceBundle(loader, plugin);
if (!ACTION_ELEMENT_NAME.equals(element.getName())) {
reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\"");
return null;
}
String className = element.getAttributeValue(CLASS_ATTR_NAME);
if (className == null || className.isEmpty()) {
reportActionError(pluginId, "action element should have specified \"class\" attribute");
return null;
}
// read ID and register loaded action
String id = element.getAttributeValue(ID_ATTR_NAME);
if (id == null || id.isEmpty()) {
id = StringUtil.getShortName(className);
}
if (Boolean.valueOf(element.getAttributeValue(INTERNAL_ATTR_NAME)).booleanValue() && !ApplicationManagerEx.getApplicationEx().isInternal()) {
myNotRegisteredInternalActionIds.add(id);
return null;
}
String text = loadTextForElement(element, bundle, id, ACTION_ELEMENT_NAME);
String iconPath = element.getAttributeValue(ICON_ATTR_NAME);
if (text == null) {
@NonNls String message = "'text' attribute is mandatory (action ID=" + id + ";" +
(plugin == null ? "" : " plugin path: "+plugin.getPath()) + ")";
reportActionError(pluginId, message);
return null;
}
String projectType = element.getAttributeValue(PROJECT_TYPE);
ActionStub stub = new ActionStub(className, id, text, loader, pluginId, iconPath, projectType);
Presentation presentation = stub.getTemplatePresentation();
presentation.setText(text);
// description
presentation.setDescription(loadDescriptionForElement(element, bundle, id, ACTION_ELEMENT_NAME));
// process all links and key bindings if any
for (final Object o : element.getChildren()) {
Element e = (Element)o;
if (ADD_TO_GROUP_ELEMENT_NAME.equals(e.getName())) {
processAddToGroupNode(stub, e, pluginId, isSecondary(e));
}
else if (SHORTCUT_ELEMENT_NAME.equals(e.getName())) {
processKeyboardShortcutNode(e, id, pluginId);
}
else if (MOUSE_SHORTCUT_ELEMENT_NAME.equals(e.getName())) {
processMouseShortcutNode(e, id, pluginId);
}
else if (ABBREVIATION_ELEMENT_NAME.equals(e.getName())) {
processAbbreviationNode(e, id);
}
else {
reportActionError(pluginId, "unexpected name of element \"" + e.getName() + "\"");
return null;
}
}
if (element.getAttributeValue(USE_SHORTCUT_OF_ATTR_NAME) != null) {
((KeymapManagerEx)myKeymapManager).bindShortcuts(element.getAttributeValue(USE_SHORTCUT_OF_ATTR_NAME), id);
}
registerOrReplaceActionInner(element, id, stub, pluginId);
return stub;
}
private void registerOrReplaceActionInner(@NotNull Element element, @NotNull String id, @NotNull AnAction action, @Nullable PluginId pluginId) {
synchronized (myLock) {
if (Boolean.valueOf(element.getAttributeValue(OVERRIDES_ATTR_NAME))) {
if (getActionOrStub(id) == null) {
throw new RuntimeException(element.getName() + " '" + id + "' doesn't override anything");
}
AnAction prev = replaceAction(id, action, pluginId);
if (action instanceof DefaultActionGroup && prev instanceof DefaultActionGroup) {
if (Boolean.valueOf(element.getAttributeValue(KEEP_CONTENT_ATTR_NAME))) {
((DefaultActionGroup)action).copyFromGroup((DefaultActionGroup)prev);
}
}
}
else {
registerAction(id, action, pluginId, element.getAttributeValue(PROJECT_TYPE));
}
}
}
private AnAction processGroupElement(Element element, final ClassLoader loader, PluginId pluginId) {
final IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
ResourceBundle bundle = getActionsResourceBundle(loader, plugin);
if (!GROUP_ELEMENT_NAME.equals(element.getName())) {
reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\"");
return null;
}
String className = element.getAttributeValue(CLASS_ATTR_NAME);
if (className == null) { // use default group if class isn't specified
if ("true".equals(element.getAttributeValue(COMPACT_ATTR_NAME))) {
className = DefaultCompactActionGroup.class.getName();
} else {
className = DefaultActionGroup.class.getName();
}
}
try {
ActionGroup group;
if (DefaultActionGroup.class.getName().equals(className)) {
group = new DefaultActionGroup();
} else if (DefaultCompactActionGroup.class.getName().equals(className)) {
group = new DefaultCompactActionGroup();
} else {
Class aClass = Class.forName(className, true, loader);
Object obj = new ConstructorInjectionComponentAdapter(className, aClass).getComponentInstance(ApplicationManager.getApplication().getPicoContainer());
if (!(obj instanceof ActionGroup)) {
reportActionError(pluginId, "class with name \"" + className + "\" should be instance of " + ActionGroup.class.getName());
return null;
}
if (element.getChildren().size() != element.getChildren(ADD_TO_GROUP_ELEMENT_NAME).size() ) { //
if (!(obj instanceof DefaultActionGroup)) {
reportActionError(pluginId, "class with name \"" + className + "\" should be instance of " + DefaultActionGroup.class.getName() +
" because there are children specified");
return null;
}
}
group = (ActionGroup)obj;
}
// read ID and register loaded group
String id = element.getAttributeValue(ID_ATTR_NAME);
if (id != null && id.isEmpty()) {
reportActionError(pluginId, "ID of the group cannot be an empty string");
return null;
}
if (Boolean.valueOf(element.getAttributeValue(INTERNAL_ATTR_NAME)).booleanValue() && !ApplicationManagerEx.getApplicationEx().isInternal()) {
myNotRegisteredInternalActionIds.add(id);
return null;
}
if (id != null) {
registerOrReplaceActionInner(element, id, group, pluginId);
}
Presentation presentation = group.getTemplatePresentation();
// text
String text = loadTextForElement(element, bundle, id, GROUP_ELEMENT_NAME);
// don't override value which was set in API with empty value from xml descriptor
if (!StringUtil.isEmpty(text) || presentation.getText() == null) {
presentation.setText(text);
}
// description
String description = loadDescriptionForElement(element, bundle, id, GROUP_ELEMENT_NAME);
// don't override value which was set in API with empty value from xml descriptor
if (!StringUtil.isEmpty(description) || presentation.getDescription() == null) {
presentation.setDescription(description);
}
// icon
setIcon(element.getAttributeValue(ICON_ATTR_NAME), className, loader, presentation, pluginId);
// popup
String popup = element.getAttributeValue(POPUP_ATTR_NAME);
if (popup != null) {
group.setPopup(Boolean.valueOf(popup).booleanValue());
}
// process all group's children. There are other groups, actions, references and links
for (final Object o : element.getChildren()) {
Element child = (Element)o;
String name = child.getName();
if (ACTION_ELEMENT_NAME.equals(name)) {
AnAction action = processActionElement(child, loader, pluginId);
if (action != null) {
assertActionIsGroupOrStub(action);
addToGroupInner(group, action, Constraints.LAST, isSecondary(child));
}
}
else if (SEPARATOR_ELEMENT_NAME.equals(name)) {
processSeparatorNode((DefaultActionGroup)group, child, pluginId);
}
else if (GROUP_ELEMENT_NAME.equals(name)) {
AnAction action = processGroupElement(child, loader, pluginId);
if (action != null) {
addToGroupInner(group, action, Constraints.LAST, false);
}
}
else if (ADD_TO_GROUP_ELEMENT_NAME.equals(name)) {
processAddToGroupNode(group, child, pluginId, isSecondary(child));
}
else if (REFERENCE_ELEMENT_NAME.equals(name)) {
AnAction action = processReferenceElement(child, pluginId);
if (action != null) {
addToGroupInner(group, action, Constraints.LAST, isSecondary(child));
}
}
else {
reportActionError(pluginId, "unexpected name of element \"" + name + "\n");
return null;
}
}
return group;
}
catch (ClassNotFoundException e) {
reportActionError(pluginId, "class with name \"" + className + "\" not found");
return null;
}
catch (NoClassDefFoundError e) {
reportActionError(pluginId, "class with name \"" + e.getMessage() + "\" not found");
return null;
}
catch(UnsupportedClassVersionError e) {
reportActionError(pluginId, "unsupported class version for " + className);
return null;
}
catch (Exception e) {
final String message = "cannot create class \"" + className + "\"";
if (pluginId == null) {
LOG.error(message, e);
}
else {
LOG.error(new PluginException(message, e, pluginId));
}
return null;
}
}
private void processReferenceNode(final Element element, final PluginId pluginId) {
final AnAction action = processReferenceElement(element, pluginId);
for (final Object o : element.getChildren()) {
Element child = (Element)o;
if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.getName())) {
processAddToGroupNode(action, child, pluginId, isSecondary(child));
}
}
}
/**\
* @param element description of link
*/
private void processAddToGroupNode(AnAction action, Element element, final PluginId pluginId, boolean secondary) {
// Real subclasses of AnAction should not be here
if (!(action instanceof Separator)) {
assertActionIsGroupOrStub(action);
}
String actionName = String.format(
"%s (%s)", action instanceof ActionStub? ((ActionStub)action).getClassName() : action.getClass().getName(),
action instanceof ActionStub ? ((ActionStub)action).getId() : myAction2Id.get(action));
if (!ADD_TO_GROUP_ELEMENT_NAME.equals(element.getName())) {
reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\"");
return;
}
// parent group
final AnAction parentGroup = getParentGroup(element.getAttributeValue(GROUPID_ATTR_NAME), actionName, pluginId);
if (parentGroup == null) {
return;
}
// anchor attribute
final Anchor anchor = parseAnchor(element.getAttributeValue(ANCHOR_ELEMENT_NAME), actionName, pluginId);
if (anchor == null) {
return;
}
final String relativeToActionId = element.getAttributeValue(RELATIVE_TO_ACTION_ATTR_NAME);
if (!checkRelativeToAction(relativeToActionId, anchor, actionName, pluginId)) {
return;
}
addToGroupInner(parentGroup, action, new Constraints(anchor, relativeToActionId), secondary);
}
private void addToGroupInner(AnAction group, AnAction action, Constraints constraints, boolean secondary) {
((DefaultActionGroup)group).addAction(action, constraints, this).setAsSecondary(secondary);
myId2GroupId.putValue(myAction2Id.get(action), myAction2Id.get(group));
}
@Nullable
public AnAction getParentGroup(final String groupId,
@Nullable final String actionName,
@Nullable final PluginId pluginId) {
if (groupId == null || groupId.isEmpty()) {
reportActionError(pluginId, actionName + ": attribute \"group-id\" should be defined");
return null;
}
AnAction parentGroup = getActionImpl(groupId, true, null);
if (parentGroup == null) {
reportActionError(pluginId, actionName + ": group with id \"" + groupId + "\" isn't registered; action will be added to the \"Other\" group");
parentGroup = getActionImpl(IdeActions.GROUP_OTHER_MENU, true, null);
}
if (!(parentGroup instanceof DefaultActionGroup)) {
reportActionError(pluginId, actionName + ": group with id \"" + groupId + "\" should be instance of " + DefaultActionGroup.class.getName() +
" but was " + parentGroup.getClass());
return null;
}
return parentGroup;
}
/**
* @param parentGroup group which is the parent of the separator. It can be <code>null</code> in that
* case separator will be added to group described in the <add-to-group ....> subelement.
* @param element XML element which represent separator.
*/
private void processSeparatorNode(@Nullable DefaultActionGroup parentGroup, Element element, PluginId pluginId) {
if (!SEPARATOR_ELEMENT_NAME.equals(element.getName())) {
reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\"");
return;
}
Separator separator = Separator.getInstance();
if (parentGroup != null) {
parentGroup.add(separator, this);
}
// try to find inner <add-to-parent...> tag
for (final Object o : element.getChildren()) {
Element child = (Element)o;
if (ADD_TO_GROUP_ELEMENT_NAME.equals(child.getName())) {
processAddToGroupNode(separator, child, pluginId, isSecondary(child));
}
}
}
private void processKeyboardShortcutNode(Element element, String actionId, PluginId pluginId) {
String firstStrokeString = element.getAttributeValue(FIRST_KEYSTROKE_ATTR_NAME);
if (firstStrokeString == null) {
reportActionError(pluginId, "\"first-keystroke\" attribute must be specified for action with id=" + actionId);
return;
}
KeyStroke firstKeyStroke = getKeyStroke(firstStrokeString);
if (firstKeyStroke == null) {
reportActionError(pluginId, "\"first-keystroke\" attribute has invalid value for action with id=" + actionId);
return;
}
KeyStroke secondKeyStroke = null;
String secondStrokeString = element.getAttributeValue(SECOND_KEYSTROKE_ATTR_NAME);
if (secondStrokeString != null) {
secondKeyStroke = getKeyStroke(secondStrokeString);
if (secondKeyStroke == null) {
reportActionError(pluginId, "\"second-keystroke\" attribute has invalid value for action with id=" + actionId);
return;
}
}
String keymapName = element.getAttributeValue(KEYMAP_ATTR_NAME);
if (keymapName == null || keymapName.trim().isEmpty()) {
reportActionError(pluginId, "attribute \"keymap\" should be defined");
return;
}
Keymap keymap = myKeymapManager.getKeymap(keymapName);
if (keymap == null) {
reportActionError(pluginId, "keymap \"" + keymapName + "\" not found");
return;
}
final String removeOption = element.getAttributeValue(REMOVE_SHORTCUT_ATTR_NAME);
final KeyboardShortcut shortcut = new KeyboardShortcut(firstKeyStroke, secondKeyStroke);
final String replaceOption = element.getAttributeValue(REPLACE_SHORTCUT_ATTR_NAME);
if (Boolean.valueOf(removeOption)) {
keymap.removeShortcut(actionId, shortcut);
}
if (Boolean.valueOf(replaceOption)) {
keymap.removeAllActionShortcuts(actionId);
}
if (!Boolean.valueOf(removeOption)) {
keymap.addShortcut(actionId, shortcut);
}
}
@Nullable
private AnAction processReferenceElement(Element element, PluginId pluginId) {
if (!REFERENCE_ELEMENT_NAME.equals(element.getName())) {
reportActionError(pluginId, "unexpected name of element \"" + element.getName() + "\"");
return null;
}
String ref = element.getAttributeValue(REF_ATTR_NAME);
if (ref==null) {
// support old style references by id
ref = element.getAttributeValue(ID_ATTR_NAME);
}
if (ref == null || ref.isEmpty()) {
reportActionError(pluginId, "ID of reference element should be defined");
return null;
}
AnAction action = getActionImpl(ref, true, null);
if (action == null) {
if (!myNotRegisteredInternalActionIds.contains(ref)) {
reportActionError(pluginId, "action specified by reference isn't registered (ID=" + ref + ")");
}
return null;
}
assertActionIsGroupOrStub(action);
return action;
}
private void processActionsChildElement(final ClassLoader loader, final PluginId pluginId, final Element child) {
String name = child.getName();
if (ACTION_ELEMENT_NAME.equals(name)) {
AnAction action = processActionElement(child, loader, pluginId);
if (action != null) {
assertActionIsGroupOrStub(action);
}
}
else if (GROUP_ELEMENT_NAME.equals(name)) {
processGroupElement(child, loader, pluginId);
}
else if (SEPARATOR_ELEMENT_NAME.equals(name)) {
processSeparatorNode(null, child, pluginId);
}
else if (REFERENCE_ELEMENT_NAME.equals(name)) {
processReferenceNode(child, pluginId);
}
else {
reportActionError(pluginId, "unexpected name of element \"" + name + "\n");
}
}
@Override
public void registerAction(@NotNull String actionId, @NotNull AnAction action, @Nullable PluginId pluginId) {
registerAction(actionId, action, pluginId, null);
}
public void registerAction(@NotNull String actionId, @NotNull AnAction action, @Nullable PluginId pluginId, @Nullable String projectType) {
synchronized (myLock) {
if (addToMap(actionId, action, pluginId, projectType) == null) return;
if (myAction2Id.containsKey(action)) {
reportActionError(pluginId, "action was already registered for another ID. ID is " + myAction2Id.get(action) +
getPluginInfo(pluginId));
return;
}
myId2Index.put(actionId, myRegisteredActionsCount++);
myAction2Id.put(action, actionId);
if (pluginId != null && !(action instanceof ActionGroup)){
THashSet<String> pluginActionIds = myPlugin2Id.get(pluginId);
if (pluginActionIds == null){
pluginActionIds = new THashSet<String>();
myPlugin2Id.put(pluginId, pluginActionIds);
}
pluginActionIds.add(actionId);
}
action.registerCustomShortcutSet(new ProxyShortcutSet(actionId, myKeymapManager), null);
}
}
private AnAction addToMap(String actionId, AnAction action, PluginId pluginId, String projectType) {
if (projectType != null || myId2Action.containsKey(actionId)) {
return registerChameleon(actionId, action, pluginId, projectType);
}
else {
myId2Action.put(actionId, action);
return action;
}
}
private AnAction registerChameleon(String actionId, AnAction action, PluginId pluginId, String projectType) {
ProjectType type = projectType == null ? null : new ProjectType(projectType);
// make sure id+projectType is unique
AnAction o = myId2Action.get(actionId);
ChameleonAction chameleonAction;
if (o == null) {
chameleonAction = new ChameleonAction(action, type);
myId2Action.put(actionId, chameleonAction);
return chameleonAction;
}
if (o instanceof ChameleonAction) {
chameleonAction = (ChameleonAction)o;
}
else {
chameleonAction = new ChameleonAction(o, type);
myId2Action.put(actionId, chameleonAction);
}
AnAction old = chameleonAction.addAction(action, type);
if (old != null) {
reportActionError(pluginId,
"action with the ID \"" + actionId + "\" was already registered. Action being registered is " + action +
"; Registered action is " +
myId2Action.get(actionId) + getPluginInfo(pluginId));
return null;
}
return chameleonAction;
}
@Override
public void registerAction(@NotNull String actionId, @NotNull AnAction action) {
registerAction(actionId, action, null);
}
@Override
public void unregisterAction(@NotNull String actionId) {
synchronized (myLock) {
if (!myId2Action.containsKey(actionId)) {
if (LOG.isDebugEnabled()) {
LOG.debug("action with ID " + actionId + " wasn't registered");
return;
}
}
AnAction oldValue = myId2Action.remove(actionId);
myAction2Id.remove(oldValue);
myId2Index.remove(actionId);
for (PluginId pluginName : myPlugin2Id.keySet()) {
final THashSet<String> pluginActions = myPlugin2Id.get(pluginName);
if (pluginActions != null) {
pluginActions.remove(actionId);
}
}
}
}
@Override
@NotNull
public String getComponentName() {
return "ActionManager";
}
@Override
public Comparator<String> getRegistrationOrderComparator() {
return new Comparator<String>() {
@Override
public int compare(String id1, String id2) {
return myId2Index.get(id1) - myId2Index.get(id2);
}
};
}
@NotNull
@Override
public String[] getPluginActions(PluginId pluginName) {
if (myPlugin2Id.containsKey(pluginName)){
final THashSet<String> pluginActions = myPlugin2Id.get(pluginName);
return ArrayUtil.toStringArray(pluginActions);
}
return ArrayUtil.EMPTY_STRING_ARRAY;
}
public void addActionPopup(final ActionPopupMenuImpl menu) {
myPopups.add(menu);
}
public void removeActionPopup(final ActionPopupMenuImpl menu) {
final boolean removed = myPopups.remove(menu);
if (removed && myPopups.isEmpty()) {
flushActionPerformed();
}
}
@Override
public void queueActionPerformedEvent(final AnAction action, DataContext context, AnActionEvent event) {
if (!myPopups.isEmpty()) {
myQueuedNotifications.put(action, context);
} else {
fireAfterActionPerformed(action, context, event);
}
}
//@Override
//public AnAction replaceAction(String actionId, @NotNull AnAction newAction) {
// synchronized (myLock) {
// return replaceAction(actionId, newAction, null);
// }
//}
@Override
public boolean isActionPopupStackEmpty() {
return myPopups.isEmpty();
}
@Override
public boolean isTransparentOnlyActionsUpdateNow() {
return myTransparentOnlyUpdate;
}
private AnAction replaceAction(@NotNull String actionId, @NotNull AnAction newAction, @Nullable PluginId pluginId) {
AnAction oldAction = getActionOrStub(actionId);
if (oldAction != null) {
boolean isGroup = oldAction instanceof ActionGroup;
if (isGroup != newAction instanceof ActionGroup) {
throw new IllegalStateException("cannot replace a group with an action and vice versa: " + actionId);
}
unregisterAction(actionId);
if (isGroup) {
myId2GroupId.values().remove(actionId);
}
}
registerAction(actionId, newAction, pluginId);
for (String groupId : myId2GroupId.get(actionId)) {
DefaultActionGroup group = ObjectUtils.assertNotNull((DefaultActionGroup)getActionOrStub(groupId));
group.replaceAction(oldAction, newAction);
}
return oldAction;
}
private void flushActionPerformed() {
final Set<AnAction> actions = myQueuedNotifications.keySet();
for (final AnAction eachAction : actions) {
final DataContext eachContext = myQueuedNotifications.get(eachAction);
fireAfterActionPerformed(eachAction, eachContext, myQueuedNotificationsEvents.get(eachAction));
}
myQueuedNotifications.clear();
myQueuedNotificationsEvents.clear();
}
@Override
public void addAnActionListener(AnActionListener listener) {
myActionListeners.add(listener);
}
@Override
public void addAnActionListener(final AnActionListener listener, final Disposable parentDisposable) {
addAnActionListener(listener);
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
removeAnActionListener(listener);
}
});
}
@Override
public void removeAnActionListener(AnActionListener listener) {
myActionListeners.remove(listener);
}
@Override
public void fireBeforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
if (action != null) {
myPrevPerformedActionId = myLastPreformedActionId;
myLastPreformedActionId = getId(action);
//noinspection AssignmentToStaticFieldFromInstanceMethod
IdeaLogger.ourLastActionId = myLastPreformedActionId;
}
for (AnActionListener listener : myActionListeners) {
listener.beforeActionPerformed(action, dataContext, event);
}
}
@Override
public void fireAfterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
if (action != null) {
myPrevPerformedActionId = myLastPreformedActionId;
myLastPreformedActionId = getId(action);
//noinspection AssignmentToStaticFieldFromInstanceMethod
IdeaLogger.ourLastActionId = myLastPreformedActionId;
}
for (AnActionListener listener : myActionListeners) {
try {
listener.afterActionPerformed(action, dataContext, event);
}
catch(AbstractMethodError ignored) { }
}
}
@Override
public KeyboardShortcut getKeyboardShortcut(@NotNull String actionId) {
AnAction action = ActionManager.getInstance().getAction(actionId);
final ShortcutSet shortcutSet = action.getShortcutSet();
final Shortcut[] shortcuts = shortcutSet.getShortcuts();
for (final Shortcut shortcut : shortcuts) {
// Shortcut can be MouseShortcut here.
// For example IdeaVIM often assigns them
if (shortcut instanceof KeyboardShortcut){
final KeyboardShortcut kb = (KeyboardShortcut)shortcut;
if (kb.getSecondKeyStroke() == null) {
return (KeyboardShortcut)shortcut;
}
}
}
return null;
}
@Override
public void fireBeforeEditorTyping(char c, DataContext dataContext) {
myLastTimeEditorWasTypedIn = System.currentTimeMillis();
for (AnActionListener listener : myActionListeners) {
listener.beforeEditorTyping(c, dataContext);
}
}
@Override
public String getLastPreformedActionId() {
return myLastPreformedActionId;
}
@Override
public String getPrevPreformedActionId() {
return myPrevPerformedActionId;
}
public Set<String> getActionIds(){
synchronized (myLock) {
return new HashSet<String>(myId2Action.keySet());
}
}
public Future<?> preloadActions() {
if (myPreloadActionsRunnable == null) {
myPreloadActionsRunnable = new Runnable() {
@Override
public void run() {
try {
doPreloadActions();
} catch (RuntimeInterruptedException ignore) {
}
}
};
return ApplicationManager.getApplication().executeOnPooledThread(myPreloadActionsRunnable);
}
return null;
}
private void doPreloadActions() {
try {
Thread.sleep(5000); // wait for project initialization to complete
}
catch (InterruptedException e) {
return; // IDEA exited
}
preloadActionGroup(IdeActions.GROUP_EDITOR_POPUP);
preloadActionGroup(IdeActions.GROUP_EDITOR_TAB_POPUP);
preloadActionGroup(IdeActions.GROUP_PROJECT_VIEW_POPUP);
preloadActionGroup(IdeActions.GROUP_MAIN_MENU);
preloadActionGroup(IdeActions.GROUP_NEW);
// TODO anything else?
LOG.debug("Actions preloading completed");
}
public void preloadActionGroup(final String groupId) {
final AnAction action = getAction(groupId);
if (action instanceof ActionGroup) {
preloadActionGroup((ActionGroup) action);
}
}
private void preloadActionGroup(final ActionGroup group) {
final Application application = ApplicationManager.getApplication();
final AnAction[] children = application.runReadAction(new Computable<AnAction[]>() {
@Override
public AnAction[] compute() {
if (application.isDisposed()) {
return AnAction.EMPTY_ARRAY;
}
return group.getChildren(null);
}
});
for (AnAction action : children) {
if (action instanceof PreloadableAction) {
((PreloadableAction)action).preload();
}
else if (action instanceof ActionGroup) {
preloadActionGroup((ActionGroup)action);
}
myActionsPreloaded++;
if (myActionsPreloaded % 10 == 0) {
try {
//noinspection BusyWait
Thread.sleep(300);
}
catch (InterruptedException ignored) {
throw new RuntimeInterruptedException(ignored);
}
}
}
}
@Override
public ActionCallback tryToExecute(@NotNull final AnAction action, @NotNull final InputEvent inputEvent, @Nullable final Component contextComponent, @Nullable final String place,
boolean now) {
final Application app = ApplicationManager.getApplication();
assert app.isDispatchThread();
final ActionCallback result = new ActionCallback();
final Runnable doRunnable = new Runnable() {
@Override
public void run() {
tryToExecuteNow(action, inputEvent, contextComponent, place, result);
}
};
if (now) {
doRunnable.run();
} else {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(doRunnable);
}
return result;
}
private void tryToExecuteNow(final AnAction action, final InputEvent inputEvent, final Component contextComponent, final String place, final ActionCallback result) {
final Presentation presentation = action.getTemplatePresentation().clone();
IdeFocusManager.findInstanceByContext(getContextBy(contextComponent)).doWhenFocusSettlesDown(new Runnable() {
@Override
public void run() {
final DataContext context = getContextBy(contextComponent);
AnActionEvent event = new AnActionEvent(
inputEvent, context,
place != null ? place : ActionPlaces.UNKNOWN,
presentation, ActionManagerImpl.this,
inputEvent.getModifiersEx()
);
ActionUtil.performDumbAwareUpdate(action, event, false);
if (!event.getPresentation().isEnabled()) {
result.setRejected();
return;
}
ActionUtil.lastUpdateAndCheckDumb(action, event, false);
if (!event.getPresentation().isEnabled()) {
result.setRejected();
return;
}
Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(context);
if (component != null && !component.isShowing()) {
result.setRejected();
return;
}
fireBeforeActionPerformed(action, context, event);
UIUtil.addAwtListener(new AWTEventListener() {
@Override
public void eventDispatched(AWTEvent event) {
if (event.getID() == WindowEvent.WINDOW_OPENED ||event.getID() == WindowEvent.WINDOW_ACTIVATED) {
if (!result.isProcessed()) {
final WindowEvent we = (WindowEvent)event;
IdeFocusManager.findInstanceByComponent(we.getWindow()).doWhenFocusSettlesDown(result.createSetDoneRunnable());
}
}
}
}, AWTEvent.WINDOW_EVENT_MASK, result);
ActionUtil.performActionDumbAware(action, event);
result.setDone();
queueActionPerformedEvent(action, context, event);
}
});
}
private class MyTimer extends Timer implements ActionListener {
private final List<TimerListener> myTimerListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final List<TimerListener> myTransparentTimerListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private int myLastTimePerformed;
MyTimer() {
super(TIMER_DELAY, null);
addActionListener(this);
setRepeats(true);
final MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
connection.subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener() {
@Override
public void applicationActivated(IdeFrame ideFrame) {
setDelay(TIMER_DELAY);
restart();
}
@Override
public void applicationDeactivated(IdeFrame ideFrame) {
setDelay(DEACTIVATED_TIMER_DELAY);
}
});
}
@Override
public String toString() {
return "Action manager timer";
}
public void addTimerListener(TimerListener listener, boolean transparent){
(transparent ? myTransparentTimerListeners : myTimerListeners).add(listener);
}
public void removeTimerListener(TimerListener listener, boolean transparent){
(transparent ? myTransparentTimerListeners : myTimerListeners).remove(listener);
}
@Override
public void actionPerformed(ActionEvent e) {
if (myLastTimeEditorWasTypedIn + UPDATE_DELAY_AFTER_TYPING > System.currentTimeMillis()) {
return;
}
if (IdeFocusManager.getInstance(null).isFocusBeingTransferred()) return;
final int lastEventCount = myLastTimePerformed;
myLastTimePerformed = ActivityTracker.getInstance().getCount();
boolean transparentOnly = myLastTimePerformed == lastEventCount;
try {
Set<TimerListener> notified = new HashSet<TimerListener>();
myTransparentOnlyUpdate = transparentOnly;
notifyListeners(myTransparentTimerListeners, notified);
if (transparentOnly) {
return;
}
notifyListeners(myTimerListeners, notified);
}
finally {
myTransparentOnlyUpdate = false;
}
}
private void notifyListeners(final List<TimerListener> timerListeners, final Set<TimerListener> notified) {
for (TimerListener listener : timerListeners) {
if (notified.add(listener)) {
runListenerAction(listener);
}
}
}
private void runListenerAction(final TimerListener listener) {
ModalityState modalityState = listener.getModalityState();
if (modalityState == null) return;
if (!ModalityState.current().dominates(modalityState)) {
try {
listener.run();
}
catch (ProcessCanceledException ex) {
// ignore
}
catch (Throwable e) {
LOG.error(e);
}
}
}
}
}