/* * 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.keymap.impl; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.keymap.KeyMapBundle; import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.KeymapManagerListener; import com.intellij.openapi.keymap.ex.KeymapManagerEx; import com.intellij.openapi.options.BaseSchemeProcessor; import com.intellij.openapi.options.SchemesManager; import com.intellij.openapi.options.SchemesManagerFactory; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.registry.Registry; import com.intellij.util.containers.ContainerUtil; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; import java.util.*; @State( name = "KeymapManager", storages = {@Storage(file = StoragePathMacros.APP_CONFIG + "/keymap.xml", roamingType = RoamingType.PER_PLATFORM)} ) public class KeymapManagerImpl extends KeymapManagerEx implements PersistentStateComponent, ExportableApplicationComponent { private static final Logger LOG = Logger.getInstance("#com.intellij.keymap.KeymapManager"); private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private String myActiveKeymapName; private final Map myBoundShortcuts = new HashMap(); @NonNls private static final String KEYMAP = "keymap"; @NonNls private static final String KEYMAPS = "keymaps"; @NonNls private static final String ACTIVE_KEYMAP = "active_keymap"; @NonNls private static final String NAME_ATTRIBUTE = "name"; private final SchemesManager mySchemesManager; public static boolean ourKeymapManagerInitialized = false; KeymapManagerImpl(DefaultKeymap defaultKeymap, SchemesManagerFactory factory) { mySchemesManager = factory.createSchemesManager( StoragePathMacros.ROOT_CONFIG + "/keymaps", new BaseSchemeProcessor() { @Override public KeymapImpl readScheme(@NotNull final Document schemeContent) throws InvalidDataException, IOException, JDOMException { return readKeymap(schemeContent); } @Override public Element writeScheme(@NotNull final KeymapImpl scheme) throws WriteExternalException { return scheme.writeExternal(); } @Override public boolean shouldBeSaved(@NotNull final KeymapImpl scheme) { return scheme.canModify(); } }, RoamingType.PER_USER); Keymap[] keymaps = defaultKeymap.getKeymaps(); String systemDefaultKeymap = defaultKeymap.getDefaultKeymapName(); for (Keymap keymap : keymaps) { addKeymap(keymap); if (systemDefaultKeymap.equals(keymap.getName())) { setActiveKeymap(keymap); } } load(); if (Registry.is("editor.add.carets.on.double.control.arrows")) { ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_EDITOR_CLONE_CARET_ABOVE, KeyEvent.VK_CONTROL, KeyEvent.VK_UP); ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_EDITOR_CLONE_CARET_BELOW, KeyEvent.VK_CONTROL, KeyEvent.VK_DOWN); ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_EDITOR_MOVE_CARET_LEFT_WITH_SELECTION, KeyEvent.VK_CONTROL, KeyEvent.VK_LEFT); ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT_WITH_SELECTION, KeyEvent.VK_CONTROL, KeyEvent.VK_RIGHT); } ourKeymapManagerInitialized = true; } @Override @NotNull public File[] getExportFiles() { return new File[]{new File(PathManager.getOptionsPath() + File.separatorChar + "keymap.xml"), getKeymapDirectory(true)}; } @Override @NotNull public String getPresentableName() { return KeyMapBundle.message("key.maps.name"); } @Override public Keymap[] getAllKeymaps() { List answer = new ArrayList(); for (Keymap keymap : mySchemesManager.getAllSchemes()) { if (!keymap.getPresentableName().startsWith("$")) { answer.add(keymap); } } return answer.toArray(new Keymap[answer.size()]); } public Keymap[] getAllIncludingDefaultsKeymaps() { Collection keymaps = mySchemesManager.getAllSchemes(); return keymaps.toArray(new Keymap[keymaps.size()]); } @Override @Nullable public Keymap getKeymap(String name) { return mySchemesManager.findSchemeByName(name); } @Override public Keymap getActiveKeymap() { return mySchemesManager.getCurrentScheme(); } @Override public void setActiveKeymap(Keymap activeKeymap) { mySchemesManager.setCurrentSchemeName(activeKeymap == null ? null : activeKeymap.getName()); fireActiveKeymapChanged(); } @Override public void bindShortcuts(String sourceActionId, String targetActionId) { myBoundShortcuts.put(targetActionId, sourceActionId); } public void unbindShortcuts(String targetActionId) { myBoundShortcuts.remove(targetActionId); } @Override public Set getBoundActions() { return myBoundShortcuts.keySet(); } @Override public String getActionBinding(String actionId) { return myBoundShortcuts.get(actionId); } @Override public SchemesManager getSchemesManager() { return mySchemesManager; } public void addKeymap(Keymap keymap) { mySchemesManager.addNewScheme(keymap, true); } public void removeAllKeymapsExceptUnmodifiable() { for (Keymap keymap : mySchemesManager.getAllSchemes()) { if (keymap.canModify()) { mySchemesManager.removeScheme(keymap); } } mySchemesManager.setCurrentSchemeName(null); Collection keymaps = mySchemesManager.getAllSchemes(); if (keymaps.size() > 0) { mySchemesManager.setCurrentSchemeName(keymaps.iterator().next().getName()); } } public String getExternalFileName() { return "keymap"; } @Override public Element getState() { Element result = new Element("component"); try { writeExternal(result); } catch (WriteExternalException e) { LOG.error(e); } return result; } @Override public void loadState(final Element state) { try { readExternal(state); } catch (InvalidDataException e) { LOG.error(e); } } public void readExternal(Element element) throws InvalidDataException { Element child = element.getChild(ACTIVE_KEYMAP); if (child != null) { myActiveKeymapName = child.getAttributeValue(NAME_ATTRIBUTE); } if (myActiveKeymapName != null) { Keymap keymap = getKeymap(myActiveKeymapName); if (keymap != null) { setActiveKeymap(keymap); } } } public void writeExternal(Element element) throws WriteExternalException { if (mySchemesManager.getCurrentScheme() != null) { Element e = new Element(ACTIVE_KEYMAP); Keymap currentScheme = mySchemesManager.getCurrentScheme(); if (currentScheme != null) { e.setAttribute(NAME_ATTRIBUTE, currentScheme.getName()); } element.addContent(e); } } private void load() { mySchemesManager.loadSchemes(); } @NotNull private KeymapImpl readKeymap(Document document) throws JDOMException, InvalidDataException, IOException { if (document == null) throw new InvalidDataException(); Element root = document.getRootElement(); if (root == null || !KEYMAP.equals(root.getName())) { throw new InvalidDataException(); } KeymapImpl keymap = new KeymapImpl(); keymap.readExternal(root, getAllIncludingDefaultsKeymaps()); return keymap; } @Nullable private static File getKeymapDirectory(boolean toCreate) { String directoryPath = PathManager.getConfigPath() + File.separator + KEYMAPS; File directory = new File(directoryPath); if (!directory.exists()) { if (!toCreate) return null; if (!directory.mkdir()) { LOG.error("Cannot create directory: " + directory.getAbsolutePath()); return null; } } return directory; } private void fireActiveKeymapChanged() { for (KeymapManagerListener listener : myListeners) { listener.activeKeymapChanged(mySchemesManager.getCurrentScheme()); } } @Override public void addKeymapManagerListener(@NotNull KeymapManagerListener listener) { pollQueue(); myListeners.add(listener); } private void pollQueue() { // assume it is safe to remove elements during iteration, as is the case with the COWAL for (KeymapManagerListener listener : myListeners) { if (listener instanceof WeakKeymapManagerListener && ((WeakKeymapManagerListener)listener).isDead()) { myListeners.remove(listener); } } } @Override public void removeKeymapManagerListener(@NotNull KeymapManagerListener listener) { pollQueue(); myListeners.remove(listener); } @Override public void addWeakListener(@NotNull KeymapManagerListener listener) { addKeymapManagerListener(new WeakKeymapManagerListener(this, listener)); } @Override public void removeWeakListener(@NotNull KeymapManagerListener listenerToRemove) { // assume it is safe to remove elements during iteration, as is the case with the COWAL for (KeymapManagerListener listener : myListeners) { if (listener instanceof WeakKeymapManagerListener && ((WeakKeymapManagerListener)listener).isWrapped(listenerToRemove)) { myListeners.remove(listener); } } } @Override @NotNull public String getComponentName() { return "KeymapManager"; } @Override public void initComponent() { } @Override public void disposeComponent() { } }