--- a/core.output2/nbproject/project.xml +++ a/core.output2/nbproject/project.xml @@ -58,6 +58,14 @@ + org.netbeans.modules.options.keymap + + + + 1.17 + + + org.openide.actions --- a/core.output2/src/org/netbeans/core/output2/OutputTab.java +++ a/core.output2/src/org/netbeans/core/output2/OutputTab.java @@ -82,6 +82,7 @@ import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.text.Document; +import org.netbeans.core.options.keymap.api.KeyStrokeUtils; import org.netbeans.core.output2.Controller.ControllerOutputEvent; import org.netbeans.core.output2.ui.AbstractOutputPane; import org.netbeans.core.output2.ui.AbstractOutputTab; @@ -99,7 +100,7 @@ import org.openide.xml.XMLUtil; import static org.netbeans.core.output2.OutputTab.ACTION.*; -import org.openide.windows.IOProvider; +import org.netbeans.core.output2.ui.OutputKeymapManager; /** @@ -716,6 +717,7 @@ private final Map actions = new EnumMap(ACTION.class);; private void createActions() { + KeyStrokeUtils.refreshActionCache(); for (ACTION a : ACTION.values()) { TabAction action; switch(a) { @@ -782,10 +784,22 @@ TabAction(ACTION action, String bundleKey) { if (bundleKey != null) { String name = NbBundle.getMessage(OutputTab.class, bundleKey); - KeyStroke accelerator = getAcceleratorFor(bundleKey); + List accels = getAcceleratorsFor(action); this.action = action; putValue(NAME, name); - putValue(ACCELERATOR_KEY, accelerator); + if (accels != null && accels.size() > 0) { + List l = new ArrayList(accels.size()); + for (KeyStroke[] ks : accels) { + if (ks.length == 1) { // ignore multi-key accelerators + l.add(ks[0]); + } + } + if (l.size() > 0) { + putValue(ACCELERATORS_KEY, + l.toArray(new KeyStroke[l.size()])); + putValue(ACCELERATOR_KEY, l.get(0)); + } + } } } @@ -814,15 +828,62 @@ * Get a keyboard accelerator from the resource bundle, with special handling * for the mac keyboard layout. * - * @param name The bundle key prefix + * @param action Action to get accelerator for. * @return A keystroke */ - private KeyStroke getAcceleratorFor(String name) { - String key = name + ".accel"; //NOI18N - if (Utilities.isMac()) { - key += ".mac"; //NOI18N + private List getAcceleratorsFor(ACTION action) { + switch (action) { + case COPY: + return KeyStrokeUtils.getKeyStrokesForAction( + "copy-to-clipboard", null); //NOI18N + case PASTE: + return KeyStrokeUtils.getKeyStrokesForAction( + "paste-from-clipboard", null); //NOI18N + case SAVEAS: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.SAVE_AS_ACTION_ID, null); + case CLOSE: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.CLOSE_ACTION_ID, null); + case NEXT_ERROR: + return KeyStrokeUtils.getKeyStrokesForAction( + "next-error", null); //NOI18N + case PREV_ERROR: + return KeyStrokeUtils.getKeyStrokesForAction( + "previous-error", null); //NOI18N + case SELECT_ALL: + return KeyStrokeUtils.getKeyStrokesForAction( + "select-all", null); //NOI18N + case FIND: + return KeyStrokeUtils.getKeyStrokesForAction( + "incremental-search-forward", null); //NOI18N + case FIND_NEXT: + return KeyStrokeUtils.getKeyStrokesForAction( + "find-next", null); //NOI18N + case FIND_PREVIOUS: + return KeyStrokeUtils.getKeyStrokesForAction( + "find-previous", null); //NOI18N + case FILTER: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.FILTER_ACTION_ID, null); + case LARGER_FONT: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.LARGER_FONT_ACTION_ID, null); + case SMALLER_FONT: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.SMALLER_FONT_ACTION_ID, null); + case FONT_TYPE: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.FONT_TYPE_ACTION_ID, null); + case CLEAR: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.CLEAR_ACTION_ID, null); + case WRAP: + return KeyStrokeUtils.getKeyStrokesForAction( + OutputKeymapManager.WRAP_ACTION_ID, null); + default: + return null; } - return Utilities.stringToKey(NbBundle.getMessage(OutputTab.class, key)); } public ACTION getAction() { --- a/core.output2/src/org/netbeans/core/output2/ui/AbstractOutputTab.java +++ a/core.output2/src/org/netbeans/core/output2/ui/AbstractOutputTab.java @@ -68,6 +68,7 @@ private boolean inputVisible = false; private AbstractOutputPane outputPane; private Action[] actions = new Action[0]; + protected static final String ACCELERATORS_KEY = "ACCELERATORS_KEY";//NOI18N private Component toFocus; @@ -191,22 +192,28 @@ //It is a Controller.ControllerAction - don't create a memory leak by listening to it a = new WeakAction(a); } - KeyStroke accel = null; + KeyStroke[] accels = null; String name; - Object o = a.getValue (Action.ACCELERATOR_KEY); - if (o instanceof KeyStroke) { - accel = (KeyStroke) o; + Object o = a.getValue(ACCELERATORS_KEY); + if (o instanceof KeyStroke[]) { + accels = (KeyStroke[]) o; } name = (String) a.getValue(Action.NAME); - if (accel != null) { - if (Controller.LOG) Controller.log ("Installed action " + name + " on " + accel); - // if the logic here changes, check the popup escaping hack in Controller - // it temporarily removes the VK_ESCAPE from input maps.. - JComponent c = getOutputPane().textView; - c.getInputMap().put(accel, name); - c.getActionMap().put(name, a); - getInputMap (WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put (accel, name); - getActionMap().put(name, a); + if (accels != null) { + for (KeyStroke accel : accels) { + if (Controller.LOG) { + Controller.log("Installed action " //NOI18N + + name + " on " + accel); //NOI18N + } + // if the logic here changes, check the popup escaping hack in + // Controller it temporarily removes the VK_ESCAPE from input + // maps.. + JComponent c = getOutputPane().textView; + c.getInputMap().put(accel, name); + c.getActionMap().put(name, a); + getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(accel, name); + getActionMap().put(name, a); + } } } --- a/core.output2/src/org/netbeans/core/output2/ui/OutputKeymapManager.java +++ a/core.output2/src/org/netbeans/core/output2/ui/OutputKeymapManager.java @@ -0,0 +1,334 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.core.output2.ui; + +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +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 org.netbeans.core.options.keymap.api.ShortcutAction; +import org.netbeans.core.options.keymap.spi.KeymapManager; +import org.netbeans.core.output2.NbIOProvider; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author jhavlin + */ +@ServiceProvider(service = KeymapManager.class) +public class OutputKeymapManager extends KeymapManager { + + private static final Logger LOG = Logger.getLogger( + OutputKeymapManager.class.getName()); + private static final String CATEGORY_NAME = NbBundle.getMessage( + NbIOProvider.class, "OpenIDE-Module-Name"); //NOI18N + /** + * ID of actions in keymap settings panel. + */ + public static final String CLEAR_ACTION_ID = + "output-window-clear"; //NOI18N + public static final String FILTER_ACTION_ID = + "output-window-filter"; //NOI18N + public static final String LARGER_FONT_ACTION_ID = + "output-window-larger-font"; //NOI18N + public static final String SMALLER_FONT_ACTION_ID = + "output-window-smaller-font"; //NOI18N + public static final String CLOSE_ACTION_ID = + "output-window-close"; //NOI18N + public static final String FONT_TYPE_ACTION_ID = + "output-window-font-type"; //NOI18N + public static final String SAVE_AS_ACTION_ID = + "output-window-save-as"; //NOI18N + public static final String WRAP_ACTION_ID = + "output-window-wrap"; //NOI18N + /** + * Constants for persistence. + */ + public static final String STORAGE_DIR = + "org-netbeans-core-output2/actions/"; //NOI18N + public static final String SHORTCUT_PREFIX = "sc"; //NOI18N + /** + * Actions + */ + private final OutWinShortCutAction wrap = new OutWinShortCutAction( + WRAP_ACTION_ID, "ACTION_WRAP"); //NOI18N + private final OutWinShortCutAction clear = new OutWinShortCutAction( + CLEAR_ACTION_ID, "ACTION_CLEAR"); //NOI18N + private final OutWinShortCutAction filter = new OutWinShortCutAction( + FILTER_ACTION_ID, "ACTION_FILTER"); //NOI18N + private final OutWinShortCutAction largerFont = new OutWinShortCutAction( + LARGER_FONT_ACTION_ID, "ACTION_LARGER_FONT"); //NOI18N + private final OutWinShortCutAction smallerFont = new OutWinShortCutAction( + SMALLER_FONT_ACTION_ID, "ACTION_SMALLER_FONT"); //NOI18N + private final OutWinShortCutAction closeWindow = new OutWinShortCutAction( + CLOSE_ACTION_ID, "ACTION_CLOSE"); //NOI18N + private final OutWinShortCutAction fontType = new OutWinShortCutAction( + FONT_TYPE_ACTION_ID, "ACTION_FONT_TYPE"); //NOI18N + private final OutWinShortCutAction saveAs = new OutWinShortCutAction( + SAVE_AS_ACTION_ID, "ACTION_SAVEAS"); //NOI18N + /** + * Map of keymaps. Keys are profile names. + */ + Map>> keymaps = + new HashMap>>(); + /** + * The default keymap. Used if keys for a profile are not set. + */ + Map> defaultKeymap = + new HashMap>(); + /** + * List of all actions. + */ + private final Set allActions = + new HashSet(); + /** + * Map of actions of categories. There is only one category in this case. + */ + Map> actions = + new HashMap>(); + + public OutputKeymapManager() { + super("OutputWindowKeymapManager"); //NOI18N + actions = new HashMap>(); + Collections.addAll(allActions, wrap, clear, filter, largerFont, + smallerFont, closeWindow, fontType, saveAs); + Set set = new HashSet(); + set.addAll(allActions); + actions.put(CATEGORY_NAME, set); + fillDefaultKeyMap(); + loadShortCuts(); + } + + private void fillDefaultKeyMap() { + for (OutWinShortCutAction a : allActions) { + String dflt = a.getDefaultShortcut(); + defaultKeymap.put(a, (dflt != null && !dflt.isEmpty()) + ? Collections.singleton(dflt) + : Collections.emptySet()); + } + } + + @Override + public Map> getActions() { + return actions; + } + + @Override + public void refreshActions() { + } + + @Override + public Map> getKeymap(String profileName) { + Map> km = keymaps.get(profileName); + if (km == null) { + km = new HashMap>(defaultKeymap); + keymaps.put(profileName, km); + } + return km; + } + + @Override + public Map> getDefaultKeymap( + String profileName) { + return defaultKeymap; + } + + @Override + public void saveKeymap(String profileName, + Map> actionToShortcuts) { + + Map> newShortcuts = + new HashMap>(); + keymaps.put(profileName, newShortcuts); + for (OutWinShortCutAction owsa : allActions) { + Set shortcuts = actionToShortcuts.get(owsa); + if (shortcuts == null) { + shortcuts = Collections.emptySet(); + } + newShortcuts.put(owsa, shortcuts); + } + storeShortCuts(profileName); + } + + @Override + public List getProfiles() { + return null; + } + + @Override + public String getCurrentProfile() { + return null; + } + + @Override + public void setCurrentProfile(String profileName) { + } + + @Override + public void deleteProfile(String profileName) { + } + + @Override + public boolean isCustomProfile(String profileName) { + return false; + } + + private class OutWinShortCutAction implements ShortcutAction { + + private String id; + private String bundleKey; + private String displayName; + private String defaultShortcut; + + public OutWinShortCutAction(String id, String bundleKey) { + this.id = id; + this.bundleKey = bundleKey; + this.displayName = NbBundle.getMessage( + NbIOProvider.class, bundleKey); + String nbKeysBundleKey = Utilities.isMac() + ? bundleKey + ".accel.mac" //NOI18N + : bundleKey + ".accel"; //NOI18N + String nbKeys = NbBundle.getMessage(NbIOProvider.class, + nbKeysBundleKey); + this.defaultShortcut = nbKeys; + } + + public String getId() { + return id; + } + + public String getBundleKey() { + return bundleKey; + } + + public String getDisplayName() { + return displayName; + } + + public String getDefaultShortcut() { + return defaultShortcut; + } + + @Override + public String getDelegatingActionId() { + return null; + } + + @Override + public ShortcutAction getKeymapManagerInstance( + String keymapManagerName) { + return null; + } + } + + private void storeShortCuts(String profileName) { + FileObject root = FileUtil.getConfigRoot(); + try { + FileObject actionsDir = FileUtil.createFolder( + root, STORAGE_DIR + profileName); + for (OutWinShortCutAction a : allActions) { + FileObject data = actionsDir.getFileObject(a.getId()); + if (data == null) { + data = actionsDir.createData(a.getId()); + } else if (data.isFolder()) { + throw new IOException(data + " is a folder."); //NOI18N + } + Enumeration atts = data.getAttributes(); + while (atts.hasMoreElements()) { + String attName = atts.nextElement(); + data.setAttribute(attName, null); + } + int index = 1; + if (keymaps.get(profileName).get(a) == null) { + continue; + } + for (String shortCut : keymaps.get(profileName).get(a)) { + data.setAttribute(SHORTCUT_PREFIX + index++, shortCut); + } + } + } catch (IOException e) { + LOG.log(Level.WARNING, "Cannot create folder", e); //NOI18N + } + } + + private void loadShortCuts() { + FileObject root = FileUtil.getConfigRoot(); + FileObject actionsDir = root.getFileObject(STORAGE_DIR); + if (actionsDir == null) { + return; + } + for (FileObject profileDir : actionsDir.getChildren()) { + if (!profileDir.isFolder()) { + continue; + } + Map> keymap = + new HashMap>(); + keymaps.put(profileDir.getName(), keymap); + for (OutWinShortCutAction a : allActions) { + FileObject actionFile = profileDir.getFileObject(a.getId()); + if (actionFile == null || !actionFile.isData()) { + keymap.put(a, Collections.emptySet()); + continue; + } + Enumeration atts = actionFile.getAttributes(); + Set strokes = new HashSet(); + while (atts.hasMoreElements()) { + String att = atts.nextElement(); + if (att.startsWith(SHORTCUT_PREFIX)) { + strokes.add((String) actionFile.getAttribute(att)); + } + } + keymap.put(a, strokes); + } + } + } +} --- a/options.keymap/nbproject/project.xml +++ a/options.keymap/nbproject/project.xml @@ -50,6 +50,15 @@ org.netbeans.modules.options.keymap + org.netbeans.api.annotations.common + + + + 1 + 1.15 + + + org.netbeans.modules.options.api @@ -152,6 +161,7 @@ + org.netbeans.core.output2 org.netbeans.modules.editor.macros org.netbeans.modules.jumpto org.netbeans.modules.jvi --- a/options.keymap/src/org/netbeans/modules/options/keymap/Utils.java +++ a/options.keymap/src/org/netbeans/modules/options/keymap/Utils.java @@ -42,10 +42,23 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.modules.options.keymap; +package org.netbeans.core.options.keymap.api; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.KeyStroke; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.openide.util.Lookup; import org.openide.util.Utilities; @@ -53,17 +66,29 @@ * * @author Jan Jancura */ -class Utils { - - - static String getKeyStrokesAsText (KeyStroke[] keyStrokes, String delim) { - if (keyStrokes == null) return ""; - if (keyStrokes.length == 0) return ""; - StringBuffer sb = new StringBuffer (getKeyStrokeAsText (keyStrokes [0])); +public class KeyStrokeUtils { + + private static final Logger LOG = Logger.getLogger( + KeyStrokeUtils.class.getName()); + + /** + * Convert an array of {@link KeyStroke key stroke} to a string composed of + * human-readable names of these key strokes, delimited by {@code delim}. + */ + public static String getKeyStrokesAsText( + @NullAllowed KeyStroke[] keyStrokes, @NonNull String delim) { + if (keyStrokes == null) { + return ""; //NOI18N + } + if (keyStrokes.length == 0) { + return ""; //NOI18N + } + StringBuilder sb = new StringBuilder(getKeyStrokeAsText(keyStrokes[0])); int i, k = keyStrokes.length; - for (i = 1; i < k; i++) - sb.append (delim).append (getKeyStrokeAsText (keyStrokes [i])); - return new String (sb); + for (i = 1; i < k; i++) { + sb.append(delim).append(getKeyStrokeAsText(keyStrokes[i])); + } + return new String(sb); } // Important: keep in sync with Editor Settings Storage StorageSupport @@ -90,7 +115,12 @@ } } - static KeyStroke getKeyStroke (String keyStroke) { + /** + * Convert human-readable keystroke name to {@link KeyStroke} object. + */ + public static @CheckForNull KeyStroke getKeyStroke( + @NonNull String keyStroke) { + int modifiers = 0; while (true) { if (keyStroke.startsWith(EMACS_CTRL)) { @@ -124,7 +154,10 @@ } } - static String getKeyStrokeAsText (KeyStroke keyStroke) { + /** + * Get human-readable name for a {@link KeyStroke}. + */ + public static String getKeyStrokeAsText(@NonNull KeyStroke keyStroke) { int modifiers = keyStroke.getModifiers (); StringBuilder sb = new StringBuilder (); if ((modifiers & InputEvent.CTRL_DOWN_MASK) > 0) { @@ -150,4 +183,124 @@ } return sb.toString (); } + + /** + * Converts a textual representation of key strokes to an array of KeyStroke + * objects. Please see {@link #keyStrokesToString(Collection, boolean)} + * ror details about the available formats. + * + * @param key The textual representation of keystorkes to convert. Its format + * depends on the value of emacsStyle parameter. + * + * @return The KeyStrokes that were represented by the key + * text or null if the textual representation was malformed. + * @since 1.16 + */ + public static @CheckForNull KeyStroke[] getKeyStrokes(@NonNull String key) { + assert key != null : "The parameter key must not be null"; //NOI18N + + List result = new ArrayList(); + String delimiter = " "; //NOI18N + + for(StringTokenizer st = new StringTokenizer(key, delimiter); st.hasMoreTokens();) { //NOI18N + String ks = st.nextToken().trim(); + KeyStroke keyStroke = getKeyStroke(ks); + + if (keyStroke != null) { + result.add(keyStroke); + } else { + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, + "Invalid keystroke string: ''{0}''", ks); //NOI18N + } + return null; + } + } + + return result.toArray(new KeyStroke[result.size()]); + } + + /** + * Find key strokes for an action. The currently selected profile is used. + * If there are more than one key strokes, the most convenient one is the + * first one in the array (index 0). If no key stroke is found and + * {@code defaultKeyStroke} is not null, an array containing only + * {@code defaultKeyStroke} is returned. If no key stroke is found and + * {@code defaultKeyStroke} is null, an empty array is returned. + * + * @param actionId ID of action. + * @param defaultKeyStroke Default key stroke, used in case no key stroke is + * found for the action. Can be null. + * @return Array of key strokes, or an empty array if no key stroke is + * available. + */ + public static List getKeyStrokesForAction(@NonNull String actionId, + @NullAllowed KeyStroke defaultKeyStroke) { + for (ShortcutsFinder sf : Lookup.getDefault().lookupAll( + ShortcutsFinder.class)) { + ShortcutAction sa = sf.findActionForId(actionId); + if (sa != null) { + String[] shortcuts = sf.getShortcuts(sa); + if (shortcuts != null && shortcuts.length > 0) { + List ks = new LinkedList(); + for (int i = 0; i < shortcuts.length; i++) { + if (shortcuts[i] != null) { + KeyStroke s[] = getKeyStrokes(shortcuts[i]); + if (s != null) { + ks.add(s); + } + } + } + return sortKeyStrokesByPreference(ks); + } + } + } + return defaultKeyStroke == null + ? Collections.emptyList() + : Collections.singletonList(new KeyStroke[]{defaultKeyStroke}); + } + + /** + * Sort the list, so that the most appropriate accelerator is at index 0. + */ + private static List sortKeyStrokesByPreference( + List keystrokes) { + if (keystrokes.size() < 2) { + return keystrokes; + } + KeyStroke best[] = null; + boolean isSolaris = + Utilities.getOperatingSystem() == Utilities.OS_SOLARIS; + for (int i = 0; i < keystrokes.size(); i++) { + KeyStroke[] ks = keystrokes.get(i); + if (ks.length > 1) { + continue; + } + boolean solarisKey = ks[0].getKeyCode() >= KeyEvent.VK_STOP + && ks[0].getKeyCode() <= KeyEvent.VK_CUT; + if (isSolaris == solarisKey + && (best == null + || best[0].getKeyCode() > ks[0].getKeyCode())) { + //Solaris key on solaris OS or other key on other OS. + best = ks; + } + } + if (best != null) { + keystrokes.remove(best); + keystrokes.add(0, best); + } + return keystrokes; + } + + /** + * Force caches to be refreshed, so that + * {@link #getKeyStrokesForAction(String, KeyStroke)} returns correct and + * up-to-date results. + */ + public static void refreshActionCache() { + for (ShortcutsFinder sf : + Lookup.getDefault().lookupAll(ShortcutsFinder.class)) { + sf.refreshActions(); + } + } } --- a/options.keymap/src/org/netbeans/modules/options/keymap/KeymapViewModel.java +++ a/options.keymap/src/org/netbeans/modules/options/keymap/KeymapViewModel.java @@ -68,11 +68,13 @@ import javax.swing.SwingUtilities; import javax.swing.event.TableModelEvent; import javax.swing.table.DefaultTableModel; +import org.netbeans.core.options.keymap.api.KeyStrokeUtils; import org.netbeans.core.options.keymap.api.ShortcutAction; import org.netbeans.core.options.keymap.api.ShortcutsFinder; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.ErrorManager; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; @@ -625,6 +627,7 @@ public void refreshActions () { categoryToActionsCache = new HashMap> (); + shortcutsCache = new HashMap>>(); model.refreshActions (); } @@ -658,7 +661,7 @@ shortcutsCache = new HashMap>> (); model = new KeymapModel (); applyInProgress = false; - } + } }); } @@ -766,7 +769,7 @@ Set shortcuts = new LinkedHashSet (); for (String emacsShortcut: entry.getValue()) { KeyStroke[] keyStroke = Utilities.stringToKeys (emacsShortcut); - shortcuts.add (Utils.getKeyStrokesAsText (keyStroke, " ")); + shortcuts.add (KeyStrokeUtils.getKeyStrokesAsText (keyStroke, " ")); } result.put (action, shortcuts); } @@ -784,7 +787,7 @@ List result = new ArrayList (); while (st.hasMoreTokens ()) { String ks = st.nextToken ().trim (); - KeyStroke keyStroke = Utils.getKeyStroke (ks); + KeyStroke keyStroke = KeyStrokeUtils.getKeyStroke (ks); if (keyStroke == null) return null; // text is not parsable result.add (keyStroke); } --- a/options.keymap/src/org/netbeans/modules/options/keymap/ProfilesPanel.java +++ a/options.keymap/src/org/netbeans/modules/options/keymap/ProfilesPanel.java @@ -56,6 +56,7 @@ import javax.swing.AbstractListModel; import javax.swing.JFileChooser; import javax.swing.KeyStroke; +import org.netbeans.core.options.keymap.api.KeyStrokeUtils; import org.netbeans.core.options.keymap.api.ShortcutAction; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; @@ -347,7 +348,7 @@ for(StringTokenizer st = new StringTokenizer(key, delimiter); st.hasMoreTokens();) { //NOI18N String ks = st.nextToken().trim(); - KeyStroke keyStroke = Utils.getKeyStroke(ks); + KeyStroke keyStroke = KeyStrokeUtils.getKeyStroke(ks); if (keyStroke != null) { buf.append(Utilities.keyToString(keyStroke, true)); @@ -373,7 +374,7 @@ KeyStroke keyStroke = Utilities.stringToKey(ks); if (keyStroke != null) { - buf.append(Utils.getKeyStrokeAsText(keyStroke)); + buf.append(KeyStrokeUtils.getKeyStrokeAsText(keyStroke)); if (st.hasMoreTokens()) buf.append(' '); } else { --- a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutListener.java +++ a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutListener.java @@ -45,6 +45,7 @@ import java.awt.event.KeyListener; import javax.swing.JTextField; import javax.swing.KeyStroke; +import org.netbeans.core.options.keymap.api.KeyStrokeUtils; /** * KeyListener trasforming keystrokes to human-readable and displaying them @@ -125,7 +126,7 @@ } private void addKeyStroke(KeyStroke keyStroke, boolean add) { - String k = Utils.getKeyStrokeAsText(keyStroke); + String k = KeyStrokeUtils.getKeyStrokeAsText(keyStroke); if (key.equals("")) { //NOI18N textField.setText(k); if (add) --- a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutProvider.java +++ a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutProvider.java @@ -47,6 +47,7 @@ import java.util.LinkedHashSet; import java.util.Set; import javax.swing.KeyStroke; +import org.netbeans.core.options.keymap.api.KeyStrokeUtils; import org.openide.util.Utilities; /** @@ -129,35 +130,35 @@ //CTRL for (int i = 0; i < letters.length; i++) { - shortcutSet.add(Utils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.CTRL_MASK))); + shortcutSet.add(KeyStrokeUtils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.CTRL_MASK))); } if (Utilities.isMac()) //META for (int i = 0; i < letters.length; i++) { - shortcutSet.add(Utils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.META_MASK))); + shortcutSet.add(KeyStrokeUtils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.META_MASK))); } else //ALT for (int i = 0; i < letters.length; i++) { - shortcutSet.add(Utils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.ALT_MASK))); + shortcutSet.add(KeyStrokeUtils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.ALT_MASK))); } //CTRL+SHIFT for (int i = 0; i < letters.length; i++) { - shortcutSet.add(Utils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK))); + shortcutSet.add(KeyStrokeUtils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK))); } if (Utilities.isMac()) //SHIFT+META for (int i = 0; i < letters.length; i++) { - shortcutSet.add(Utils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.SHIFT_MASK | InputEvent.META_MASK))); + shortcutSet.add(KeyStrokeUtils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.SHIFT_MASK | InputEvent.META_MASK))); } else //SHIFT+ALT for (int i = 0; i < letters.length; i++) { - shortcutSet.add(Utils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.SHIFT_MASK | InputEvent.ALT_MASK))); + shortcutSet.add(KeyStrokeUtils.getKeyStrokeAsText(KeyStroke.getKeyStroke(letters[i], InputEvent.SHIFT_MASK | InputEvent.ALT_MASK))); } } return shortcutSet; --- a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutsDialog.form +++ a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutsDialog.form @@ -1,4 +1,4 @@ - +
--- a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutsDialog.java +++ a/options.keymap/src/org/netbeans/modules/options/keymap/ShortcutsDialog.java @@ -58,6 +58,7 @@ import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.KeyStroke; +import org.netbeans.core.options.keymap.api.KeyStrokeUtils; import org.netbeans.core.options.keymap.api.ShortcutAction; import org.netbeans.core.options.keymap.api.ShortcutsFinder; import org.openide.awt.Mnemonics; @@ -273,7 +274,7 @@ } private void addKeyStroke (KeyStroke keyStroke, boolean add) { - String k = Utils.getKeyStrokeAsText (keyStroke); + String k = KeyStrokeUtils.getKeyStrokeAsText (keyStroke); if (key.equals ("")) { //NOI18N getTfShortcut().setText (k); if (add) key = k; --- a/options.keymap/test/unit/src/org/netbeans/core/options/keymap/api/KeyStrokeUtilsTest.java +++ a/options.keymap/test/unit/src/org/netbeans/core/options/keymap/api/KeyStrokeUtilsTest.java @@ -0,0 +1,189 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.core.options.keymap.api; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.swing.KeyStroke; +import org.netbeans.core.options.keymap.spi.KeymapManager; +import org.netbeans.junit.MockServices; +import org.netbeans.junit.NbTestCase; + +/** + * + * @author jhavlin + */ +public class KeyStrokeUtilsTest extends NbTestCase { + + private static final String DEFAULT_PROFILE = "NetBeans"; //NOI18N + + public KeyStrokeUtilsTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + MockServices.setServices(TestKeymapManager.class); + } + + public void testGetKeyStrokesForAction() { + List s1 = KeyStrokeUtils.getKeyStrokesForAction( + "testX1", null); + List s2 = KeyStrokeUtils.getKeyStrokesForAction( + "testX2", null); + assertEquals(1, s1.size()); + assertEquals(1, s2.size()); + assertEquals(1, s1.get(0).length); + assertEquals(2, s2.get(0).length); + assertEquals((int) 'O', s1.get(0)[0].getKeyCode()); + assertEquals((int) 'T', s2.get(0)[0].getKeyCode()); + assertEquals((int) 'M', s2.get(0)[1].getKeyCode()); + } + + public static class TestKeymapManager extends KeymapManager { + + private String profile = DEFAULT_PROFILE; + private ShortcutAction sa1 = new TestShortcutAction("testX1"); + private ShortcutAction sa2 = new TestShortcutAction("testX2"); + private Map> defaultKeyMap = + new HashMap>(); + private Map> currentKeyMap = + new HashMap>(); + private Map> actionMap = + new HashMap>(); + + public TestKeymapManager() { + super("Test"); + Set allActions = new HashSet(); + Collections.addAll(allActions, sa1, sa2); + actionMap.put(DEFAULT_PROFILE, allActions); + defaultKeyMap.put(sa1, Collections.singleton("C-O")); + defaultKeyMap.put(sa2, Collections.singleton("M-T M-M")); + currentKeyMap.putAll(defaultKeyMap); + } + + @Override + public Map> getActions() { + return actionMap; + } + + @Override + public void refreshActions() { + } + + @Override + public Map> getKeymap(String profileName) { + return currentKeyMap; + } + + @Override + public Map> getDefaultKeymap(String profileName) { + return defaultKeyMap; + } + + @Override + public void saveKeymap(String profileName, Map> actionToShortcuts) { + // nothing + } + + @Override + public List getProfiles() { + return Collections.singletonList(profile); + } + + @Override + public String getCurrentProfile() { + return profile; + } + + @Override + public void setCurrentProfile(String profileName) { + this.profile = profileName; + } + + @Override + public void deleteProfile(String profileName) { + // nothing + } + + @Override + public boolean isCustomProfile(String profileName) { + return !profileName.equals(DEFAULT_PROFILE); + } + + private class TestShortcutAction implements ShortcutAction { + + public TestShortcutAction(String name) { + this.name = name; + } + private String name; + + @Override + public String getDisplayName() { + return name; + } + + @Override + public String getId() { + return name; + } + + @Override + public String getDelegatingActionId() { + return null; + } + + @Override + public ShortcutAction getKeymapManagerInstance(String keymapManagerName) { + if (keymapManagerName.equals(TestKeymapManager.this.getName())) { + return this; + } else { + return null; + } + } + } + } +}