blob: cb9eb31e87a23923ea07f0635eac834ca7312e9b [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.editor.macros;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.KeyStroke;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.editor.settings.MultiKeyBinding;
import org.netbeans.modules.editor.macros.storage.MacroDescription;
import org.netbeans.modules.editor.macros.storage.MacrosStorage;
import org.netbeans.modules.editor.settings.storage.api.EditorSettingsStorage;
import org.netbeans.modules.editor.settings.storage.spi.StorageFilter;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
/**
*
* @author Vita Stejskal
*/
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.modules.editor.settings.storage.spi.StorageFilter.class)
public final class MacroShortcutsInjector extends StorageFilter<Collection<KeyStroke>, MultiKeyBinding> implements PropertyChangeListener {
/**
* How many times the Injector will try to load the macros from the editor storage.
* Because of issue #223802, editor storage may not be yet initialized when the Injector is run.
*/
private static final int MAX_RETRY_COUNT = 2;
public static void refreshShortcuts() {
Collection<? extends MacroShortcutsInjector> injectors = Lookup.getDefault().lookupAll(MacroShortcutsInjector.class);
if (injectors.size() == 0) {
LOG.warning("No MacroShortcutsInjector found in default Lookup"); //NOI18N
return;
} else if (injectors.size() > 1) {
LOG.warning("Too many MacroShortcutsInjector instances found in default Lookup:"); //NOI18N
for(MacroShortcutsInjector msi : injectors) {
LOG.warning(" " + msi); //NOI18N
}
}
injectors.iterator().next().notifyChanges();
// assert instance != null;
// LOG.fine("Shortcuts refresh forced, notifying 'Keybindings' storage..."); //NOI18N
// instance.notifyChanges();
}
public MacroShortcutsInjector() {
super("Keybindings"); //NOI18N
}
// ------------------------------------------------------------------------
// StorageFilter implementation
// ------------------------------------------------------------------------
@Override
public void afterLoad(Map<Collection<KeyStroke>, MultiKeyBinding> map, MimePath mimePath, String profile, boolean defaults) {
Map<String, MacroDescription> macros = new HashMap<String, MacroDescription>();
if (!collectMacroActions(mimePath, macros)) {
return;
}
for(MacroDescription macro : macros.values()) {
List<? extends MultiKeyBinding> shortcuts = macro.getShortcuts();
for(MultiKeyBinding shortcut : shortcuts) {
Collection<KeyStroke> keys = shortcut.getKeyStrokeList();
// A macro shortcut never replaces shortcuts for ordinary editor actions
if (!map.containsKey(keys)) {
map.put(keys, shortcut);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("afterLoad: injecting " + keys + " for macro '" //NOI18N
+ macro.getName() + "'; mimePath='" + mimePath.getPath() + "'"); //NOI18N
}
} else {
LOG.warning("Shortcut " + keys + " is bound to '" + map.get(keys).getActionName() //NOI18N
+ "' for '" + mimePath.getPath() + "' and will not be assigned to '" + macro.getName() + "' macro!"); //NOI18N
}
}
}
}
@Override
public void beforeSave(Map<Collection<KeyStroke>, MultiKeyBinding> map, MimePath mimePath, String profile, boolean defaults) {
Set<Collection<KeyStroke>> keysToFilterOut = new HashSet<Collection<KeyStroke>>();
for(Collection<KeyStroke> keys : map.keySet()) {
MultiKeyBinding shortcut = map.get(keys);
if (shortcut.getActionName().equals(MacroDialogSupport.RunMacroAction.runMacroAction)) {
keysToFilterOut.add(keys);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("beforeSave: filtering out macro shortcut " + keys + "; mimePath='" + mimePath.getPath() + "'"); //NOI18N
}
}
}
map.keySet().removeAll(keysToFilterOut);
}
// ------------------------------------------------------------------------
// PropertyChangeListener implementation
// ------------------------------------------------------------------------
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName() == null || EditorSettingsStorage.PROP_DATA.equals(evt.getPropertyName())) {
LOG.fine("Macros storage changed, notifying 'Keybindings' storage...");
notifyChanges();
}
}
// ------------------------------------------------------------------------
// private implementation
// ------------------------------------------------------------------------
private static final Logger LOG = Logger.getLogger(MacroShortcutsInjector.class.getName());
private EditorSettingsStorage<String, MacroDescription> storage = null;
/**
* Counter of attempted refreshes
*/
private int retryCount;
private RequestProcessor.Task delayed;
private boolean collectMacroActions(MimePath mimePath, Map<String, MacroDescription> macros) {
if (storage == null) {
storage = EditorSettingsStorage.<String, MacroDescription>find(MacrosStorage.ID);
if (storage == null) {
synchronized (this) {
if (delayed != null) {
return false;
}
if (retryCount++ < MAX_RETRY_COUNT) {
delayed = RequestProcessor.getDefault().post(new Runnable() {
public void run() {
synchronized (MacroShortcutsInjector.this) {
delayed = null;
}
refreshShortcuts();
}
}, 500);
} else {
LOG.warning("Could not create macro shortcuts, editor settings storage not initialized yet.");
}
}
return false;
}
storage.addPropertyChangeListener(WeakListeners.propertyChange(this, storage));
}
try {
macros.putAll(storage.load(mimePath, null, false));
} catch (IOException ioe) {
LOG.log(Level.WARNING, null, ioe);
}
return true;
}
}