/*
* 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.Disposable;
import com.intellij.openapi.components.*;
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.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@State(
name = "KeymapManager",
storages = @Storage(file = StoragePathMacros.APP_CONFIG + "/keymap.xml", roamingType = RoamingType.PER_PLATFORM),
additionalExportFile = KeymapManagerImpl.KEYMAPS_DIR_PATH
)
public class KeymapManagerImpl extends KeymapManagerEx implements PersistentStateComponent<Element>, ApplicationComponent {
static final String KEYMAPS_DIR_PATH = StoragePathMacros.ROOT_CONFIG + "/keymaps";
private final List<KeymapManagerListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private String myActiveKeymapName;
private final Map<String, String> myBoundShortcuts = new HashMap<String, String>();
@NonNls private static final String ACTIVE_KEYMAP = "active_keymap";
@NonNls private static final String NAME_ATTRIBUTE = "name";
private final SchemesManager<Keymap, KeymapImpl> mySchemesManager;
public static boolean ourKeymapManagerInitialized = false;
KeymapManagerImpl(DefaultKeymap defaultKeymap, SchemesManagerFactory factory) {
mySchemesManager = factory.createSchemesManager(KEYMAPS_DIR_PATH, new BaseSchemeProcessor<KeymapImpl>() {
@NotNull
@Override
public KeymapImpl readScheme(@NotNull Element element) throws InvalidDataException {
KeymapImpl keymap = new KeymapImpl();
keymap.readExternal(element, getAllIncludingDefaultsKeymaps());
return keymap;
}
@Override
public Element writeScheme(@NotNull final KeymapImpl scheme) {
return scheme.writeExternal();
}
@NotNull
@Override
public State getState(@NotNull KeymapImpl scheme) {
return scheme.canModify() ? State.POSSIBLY_CHANGED : State.NON_PERSISTENT;
}
}, RoamingType.PER_USER);
Keymap[] keymaps = defaultKeymap.getKeymaps();
for (Keymap keymap : keymaps) {
addKeymap(keymap);
String systemDefaultKeymap = defaultKeymap.getDefaultKeymapName();
if (systemDefaultKeymap.equals(keymap.getName())) {
setActiveKeymap(keymap);
}
}
mySchemesManager.loadSchemes();
//noinspection AssignmentToStaticFieldFromInstanceMethod
ourKeymapManagerInitialized = true;
}
@Override
public Keymap[] getAllKeymaps() {
List<Keymap> answer = new ArrayList<Keymap>();
for (Keymap keymap : mySchemesManager.getAllSchemes()) {
if (!keymap.getPresentableName().startsWith("$")) {
answer.add(keymap);
}
}
return answer.toArray(new Keymap[answer.size()]);
}
public Keymap[] getAllIncludingDefaultsKeymaps() {
Collection<Keymap> keymaps = mySchemesManager.getAllSchemes();
return keymaps.toArray(new Keymap[keymaps.size()]);
}
@Override
@Nullable
public Keymap getKeymap(@NotNull 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);
}
@Override
public void unbindShortcuts(String targetActionId) {
myBoundShortcuts.remove(targetActionId);
}
@Override
public Set<String> getBoundActions() {
return myBoundShortcuts.keySet();
}
@Override
public String getActionBinding(String actionId) {
Set<String> visited = null;
String id = actionId, next;
while ((next = myBoundShortcuts.get(id)) != null) {
if (visited == null) visited = ContainerUtil.newHashSet();
if (!visited.add(id = next)) break;
}
return Comparing.equal(id, actionId) ? null : id;
}
@Override
public SchemesManager<Keymap, KeymapImpl> getSchemesManager() {
return mySchemesManager;
}
public void addKeymap(Keymap keymap) {
mySchemesManager.addNewScheme(keymap, true);
}
public void removeAllKeymapsExceptUnmodifiable() {
List<Keymap> schemes = mySchemesManager.getAllSchemes();
for (int i = schemes.size() - 1; i >= 0; i--) {
Keymap keymap = schemes.get(i);
if (keymap.canModify()) {
mySchemesManager.removeScheme(keymap);
}
}
mySchemesManager.setCurrentSchemeName(null);
Collection<Keymap> keymaps = mySchemesManager.getAllSchemes();
if (!keymaps.isEmpty()) {
mySchemesManager.setCurrentSchemeName(keymaps.iterator().next().getName());
}
}
@Override
public Element getState() {
Element result = new Element("component");
if (mySchemesManager.getCurrentScheme() != null) {
Element e = new Element(ACTIVE_KEYMAP);
Keymap currentScheme = mySchemesManager.getCurrentScheme();
if (currentScheme != null) {
e.setAttribute(NAME_ATTRIBUTE, currentScheme.getName());
}
result.addContent(e);
}
return result;
}
@Override
public void loadState(final Element state) {
Element child = state.getChild(ACTIVE_KEYMAP);
if (child != null) {
myActiveKeymapName = child.getAttributeValue(NAME_ATTRIBUTE);
}
if (myActiveKeymapName != null) {
Keymap keymap = getKeymap(myActiveKeymapName);
if (keymap != null) {
setActiveKeymap(keymap);
}
}
}
private void fireActiveKeymapChanged() {
for (KeymapManagerListener listener : myListeners) {
listener.activeKeymapChanged(mySchemesManager.getCurrentScheme());
}
}
@Override
public void addKeymapManagerListener(@NotNull KeymapManagerListener listener) {
pollQueue();
myListeners.add(listener);
}
@Override
public void addKeymapManagerListener(@NotNull final KeymapManagerListener listener, @NotNull Disposable parentDisposable) {
pollQueue();
myListeners.add(listener);
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
removeKeymapManagerListener(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() {
}
}