blob: 09f583f16f8734c7162ae47114632f6d26220692 [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.settings.storage.keybindings;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
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.KeyBindingSettings;
import org.netbeans.api.editor.settings.MultiKeyBinding;
import org.netbeans.modules.editor.settings.storage.EditorSettingsImpl;
import org.netbeans.modules.editor.settings.storage.ProfilesTracker;
import org.netbeans.modules.editor.settings.storage.api.EditorSettings;
import org.netbeans.modules.editor.settings.storage.api.EditorSettingsStorage;
import org.netbeans.modules.editor.settings.storage.api.KeyBindingSettingsFactory;
import org.openide.util.Utilities;
/**
* KeyBindings settings are represented by List of keybindings.
* The List contains the instances of {@link MultiKeyBinding}.
* <br>
* Instances of this class should be retrieved from the {@link org.netbeans.api.editor.mimelookup.MimeLookup}
* for a given mime-type.
* <br>
* <font color="red">This class must NOT be extended by any API clients</font>
*
* @author Jan Jancura
*/
public final class KeyBindingSettingsImpl extends KeyBindingSettingsFactory {
private static final Logger LOG = Logger.getLogger(KeyBindingSettingsImpl.class.getName());
private static final Map<MimePath, WeakReference<KeyBindingSettingsImpl>> INSTANCES =
new WeakHashMap<MimePath, WeakReference<KeyBindingSettingsImpl>>();
public static synchronized KeyBindingSettingsImpl get(MimePath mimePath) {
WeakReference<KeyBindingSettingsImpl> reference = INSTANCES.get(mimePath);
KeyBindingSettingsImpl result = reference == null ? null : reference.get();
if (result == null) {
result = new KeyBindingSettingsImpl(mimePath);
INSTANCES.put(mimePath, new WeakReference<>(result));
}
return result;
}
private final MimePath mimePath;
private final PropertyChangeSupport pcs;
private KeyBindingSettingsImpl baseKBS;
private Listener listener;
private String logActionName = null;
/**
* Construction prohibited for API clients.
*/
private KeyBindingSettingsImpl (MimePath mimePath) {
this.mimePath = mimePath;
pcs = new PropertyChangeSupport (this);
// init logging
String myClassName = KeyBindingSettingsImpl.class.getName ();
String value = System.getProperty(myClassName);
if (value != null) {
if (!value.equals("true")) {
logActionName = System.getProperty(myClassName);
}
} else if (mimePath.size() == 1) {
logActionName = System.getProperty(myClassName + '.' + mimePath.getMimeType(0));
}
}
private boolean init = false;
private void init () {
if (init) return;
init = true;
if (mimePath.size() > 0) {
baseKBS = get(MimePath.EMPTY);
}
listener = new Listener(this, baseKBS);
}
/**
* Translates profile's display name to its Id. If the profile's display name
* can't be translated this method will simply return the profile's display name
* without translation.
*/
private String getInternalKeymapProfile (String profile) {
ProfilesTracker tracker = ProfilesTracker.get(KeyMapsStorage.ID, EditorSettingsImpl.EDITORS_FOLDER);
ProfilesTracker.ProfileDescription pd = tracker.getProfileByDisplayName(profile);
return pd == null ? profile : pd.getId();
}
/**
* Gets the keybindings list, where items are instances of {@link MultiKeyBinding}
*
* @return List of {@link MultiKeyBinding}
*/
@Override
public List<MultiKeyBinding> getKeyBindings() {
return getKeyBindings(EditorSettingsImpl.getInstance().getCurrentKeyMapProfile());
}
/**
* Gets the keybindings list, where items are instances of {@link MultiKeyBinding}
*
* @return List of {@link MultiKeyBinding}
*/
@Override
public List<MultiKeyBinding> getKeyBindings(String profile) {
profile = getInternalKeymapProfile(profile);
return Collections.unmodifiableList(new ArrayList<>(getShortcuts(profile, false).values()));
}
private Map<Collection<KeyStroke>, MultiKeyBinding> getShortcuts(String profile, boolean defaults) {
EditorSettingsStorage<Collection<KeyStroke>, MultiKeyBinding> ess = EditorSettingsStorage.get(KeyMapsStorage.ID);
try {
return ess.load(mimePath, profile, defaults);
} catch (IOException ioe) {
LOG.log(Level.WARNING, null, ioe);
return Collections.<Collection<KeyStroke>, MultiKeyBinding>emptyMap();
}
}
/**
* Returns default keybindings list for given keymap name, where items
* are instances of {@link MultiKeyBinding}.
*
* @return List of {@link MultiKeyBinding}
*/
@Override
public List<MultiKeyBinding> getKeyBindingDefaults(String profile) {
profile = getInternalKeymapProfile(profile);
return Collections.unmodifiableList(new ArrayList<>(getShortcuts(profile, true).values()));
}
/**
* Gets the keybindings list, where items are instances of
* {@link MultiKeyBinding}.
*
* @return List of {@link MultiKeyBinding}
*/
@Override
public void setKeyBindings (
String profile,
List<MultiKeyBinding> keyBindings
) {
init ();
profile = getInternalKeymapProfile(profile);
EditorSettingsStorage<Collection<KeyStroke>, MultiKeyBinding> ess = EditorSettingsStorage.get(KeyMapsStorage.ID);
try {
if (keyBindings == null) {
ess.delete(mimePath, profile, false);
} else {
Map<Collection<KeyStroke>, MultiKeyBinding> shortcuts = new HashMap<>();
for(MultiKeyBinding mkb : keyBindings) {
shortcuts.put(mkb.getKeyStrokeList(), mkb);
}
listener.removeListeners(); // ???
ess.save(mimePath, profile, false, shortcuts);
listener.addListeners();
pcs.firePropertyChange (null, null, null);
}
} catch (IOException ioe) {
LOG.log(Level.WARNING, null, ioe);
}
}
/**
* PropertyChangeListener registration.
*
* @param l a PropertyChangeListener to be registerred
*/
@Override
public void addPropertyChangeListener (PropertyChangeListener l) {
pcs.addPropertyChangeListener (l);
}
/**
* PropertyChangeListener registration.
*
* @param l a PropertyChangeListener to be unregisterred
*/
@Override
public void removePropertyChangeListener (PropertyChangeListener l) {
pcs.removePropertyChangeListener (l);
}
// other methods ...........................................................
private void log (String text, Collection keymap) {
if (!LOG.isLoggable(Level.FINE)) {
return;
}
if (text.length() != 0) {
if (mimePath.size() == 1) {
text += " " + mimePath.getMimeType(0);
}
text += " " + EditorSettingsImpl.getInstance().getCurrentKeyMapProfile();
}
if (keymap == null) {
LOG.fine(text + " : null");
return;
}
LOG.fine(text);
Iterator it = keymap.iterator ();
while (it.hasNext ()) {
Object mkb = it.next ();
if (logActionName == null || !(mkb instanceof MultiKeyBinding)) {
LOG.fine(" " + mkb);
} else if (mkb instanceof MultiKeyBinding &&
logActionName.equals(((MultiKeyBinding) mkb).getActionName ()))
{
LOG.fine(" " + mkb);
}
}
}
public Object createInstanceForLookup() {
init ();
// 1) get real profile
String profile = getInternalKeymapProfile(EditorSettingsImpl.getInstance().getCurrentKeyMapProfile());
Map<Collection<KeyStroke>, MultiKeyBinding> allShortcuts = new HashMap<>();
// Add base shortcuts
if (baseKBS != null) {
Map<Collection<KeyStroke>, MultiKeyBinding> baseShortcuts = baseKBS.getShortcuts(profile, false);
allShortcuts.putAll(baseShortcuts);
}
// Add local shortcuts
Map<Collection<KeyStroke>, MultiKeyBinding> localShortcuts = getShortcuts(profile, false);
allShortcuts.putAll(localShortcuts);
// Prepare the result
List<MultiKeyBinding> result = new ArrayList<>(allShortcuts.values());
return new Immutable(result);
}
private static final class Listener extends WeakReference<KeyBindingSettingsImpl> implements PropertyChangeListener, Runnable {
private final KeyBindingSettingsFactory baseKBS;
private final EditorSettingsStorage<Collection<KeyStroke>, MultiKeyBinding> storage;
public Listener (
KeyBindingSettingsImpl kb,
KeyBindingSettingsFactory baseKBS
) {
super(kb, Utilities.activeReferenceQueue());
this.baseKBS = baseKBS;
this.storage = EditorSettingsStorage.get(KeyMapsStorage.ID);
addListeners ();
}
private KeyBindingSettingsImpl getSettings () {
KeyBindingSettingsImpl r = get ();
if (r != null) return r;
removeListeners ();
return null;
}
private void addListeners () {
EditorSettingsImpl.getInstance().addPropertyChangeListener(
EditorSettings.PROP_CURRENT_KEY_MAP_PROFILE,
this
);
storage.addPropertyChangeListener(this);
if (baseKBS != null) {
baseKBS.addPropertyChangeListener(this);
}
}
private void removeListeners () {
if (baseKBS != null) {
baseKBS.removePropertyChangeListener(this);
}
storage.removePropertyChangeListener(this);
EditorSettingsImpl.getInstance().removePropertyChangeListener(
EditorSettings.PROP_CURRENT_KEY_MAP_PROFILE,
this
);
}
@Override
public void propertyChange (PropertyChangeEvent evt) {
KeyBindingSettingsImpl r = getSettings ();
if (r == null) return;
r.log ("refresh2", Collections.EMPTY_SET);
r.pcs.firePropertyChange (null, null, null);
}
@Override
public void run() {
removeListeners();
}
}
/* package */ static final class Immutable extends KeyBindingSettings {
private final List<MultiKeyBinding> keyBindings;
public Immutable(List<MultiKeyBinding> keyBindings) {
this.keyBindings = keyBindings;
}
@Override
public List<MultiKeyBinding> getKeyBindings() {
return Collections.unmodifiableList(keyBindings);
}
}
}