/* * 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}. *
* Instances of this class should be retrieved from the {@link org.netbeans.api.editor.mimelookup.MimeLookup} * for a given mime-type. *
* This class must NOT be extended by any API clients * * @author Jan Jancura */ public final class KeyBindingSettingsImpl extends KeyBindingSettingsFactory { private static final Logger LOG = Logger.getLogger(KeyBindingSettingsImpl.class.getName()); private static final Map> INSTANCES = new WeakHashMap>(); public static synchronized KeyBindingSettingsImpl get(MimePath mimePath) { WeakReference 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 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 getKeyBindings(String profile) { profile = getInternalKeymapProfile(profile); return Collections.unmodifiableList(new ArrayList<>(getShortcuts(profile, false).values())); } private Map, MultiKeyBinding> getShortcuts(String profile, boolean defaults) { EditorSettingsStorage, MultiKeyBinding> ess = EditorSettingsStorage.get(KeyMapsStorage.ID); try { return ess.load(mimePath, profile, defaults); } catch (IOException ioe) { LOG.log(Level.WARNING, null, ioe); return Collections., 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 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 keyBindings ) { init (); profile = getInternalKeymapProfile(profile); EditorSettingsStorage, MultiKeyBinding> ess = EditorSettingsStorage.get(KeyMapsStorage.ID); try { if (keyBindings == null) { ess.delete(mimePath, profile, false); } else { Map, 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, MultiKeyBinding> allShortcuts = new HashMap<>(); // Add base shortcuts if (baseKBS != null) { Map, MultiKeyBinding> baseShortcuts = baseKBS.getShortcuts(profile, false); allShortcuts.putAll(baseShortcuts); } // Add local shortcuts Map, MultiKeyBinding> localShortcuts = getShortcuts(profile, false); allShortcuts.putAll(localShortcuts); // Prepare the result List result = new ArrayList<>(allShortcuts.values()); return new Immutable(result); } private static final class Listener extends WeakReference implements PropertyChangeListener, Runnable { private final KeyBindingSettingsFactory baseKBS; private final EditorSettingsStorage, 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 keyBindings; public Immutable(List keyBindings) { this.keyBindings = keyBindings; } @Override public List getKeyBindings() { return Collections.unmodifiableList(keyBindings); } } }