This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 203203
Collapse All | Expand All

(-)a/o.n.core/manifest.mf (-1 / +1 lines)
Lines 4-7 Link Here
4
OpenIDE-Module-Layer: org/netbeans/core/resources/mf-layer.xml
4
OpenIDE-Module-Layer: org/netbeans/core/resources/mf-layer.xml
5
AutoUpdate-Show-In-Client: false
5
AutoUpdate-Show-In-Client: false
6
AutoUpdate-Essential-Module: true
6
AutoUpdate-Essential-Module: true
7
OpenIDE-Module-Specification-Version: 3.31
7
OpenIDE-Module-Specification-Version: 3.32
(-)a/o.n.core/src/org/netbeans/core/NbKeymap.java (-8 / +44 lines)
Lines 85-90 Link Here
85
public final class NbKeymap implements Keymap, Comparator<KeyStroke> {
85
public final class NbKeymap implements Keymap, Comparator<KeyStroke> {
86
86
87
    private static final RequestProcessor RP = new RequestProcessor(NbKeymap.class);
87
    private static final RequestProcessor RP = new RequestProcessor(NbKeymap.class);
88
    
89
    /**
90
     * Extension, which indicates that the given binding should be removed by keymap
91
     * profile. The marker is ignored in the 'Shortcuts' base directory.
92
     */
93
    public static final String BINDING_REMOVED = "removed";  // NO18N
94
    
95
    /**
96
     * Extension of the DataShadow files; private in loaders API
97
     */
98
    public static final String SHADOW_EXT = "shadow"; // NOI18N
99
    
88
    //for unit testing only
100
    //for unit testing only
89
    private RequestProcessor.Task refreshTask;
101
    private RequestProcessor.Task refreshTask;
90
102
Lines 196-205 Link Here
196
                }
208
                }
197
            }
209
            }
198
            Map<String,FileObject> id2Dir = new HashMap<String,FileObject>(); // #170677
210
            Map<String,FileObject> id2Dir = new HashMap<String,FileObject>(); // #170677
211
            boolean processingProfile = false;
199
            for (FileObject dir : dirs) {
212
            for (FileObject dir : dirs) {
200
                if (dir != null) {
213
                if (dir != null) {
201
                    for (FileObject def : dir.getChildren()) {
214
                    for (FileObject def : dir.getChildren()) {
202
                        if (def.isData()) {
215
                        if (def.isData()) {
216
                            boolean removed = processingProfile && BINDING_REMOVED.equals(def.getExt());
203
                            KeyStroke[] strokes = Utilities.stringToKeys(def.getName());
217
                            KeyStroke[] strokes = Utilities.stringToKeys(def.getName());
204
                            if (strokes == null || strokes.length == 0) {
218
                            if (strokes == null || strokes.length == 0) {
205
                                LOG.log(Level.WARNING, "could not load parse name of " + def.getPath());
219
                                LOG.log(Level.WARNING, "could not load parse name of " + def.getPath());
Lines 213-229 Link Here
213
                                    sub = null;
227
                                    sub = null;
214
                                }
228
                                }
215
                                if (sub == null) {
229
                                if (sub == null) {
230
                                    if (removed) {
231
                                        // nothing more to remove now
232
                                        break;
233
                                    }
216
                                    binder.put(strokes[i], sub = new Binding());
234
                                    binder.put(strokes[i], sub = new Binding());
217
                                }
235
                                }
218
                                binder = sub.nested;
236
                                binder = sub.nested;
219
                            }
237
                            }
220
                            // XXX warn about conflicts here too:
238
                            if (removed) {
221
                            binder.put(strokes[strokes.length - 1], new Binding(def));
239
                                if (strokes.length == 1) {
222
                            if (strokes.length == 1) {
240
                                    // remove the recorded stroke so it is not found by keyStrokeForAction
223
                                String id = idForFile(def);
241
                                    id2Stroke.values().remove(strokes[0]);
224
                                KeyStroke former = id2Dir.put(id, dir) == dir ? id2Stroke.get(id) : null;
242
                                }
225
                                if (former == null || compare(former, strokes[0]) > 0) {
243
                                
226
                                    id2Stroke.put(id, strokes[0]);
244
                                KeyStroke stroke = strokes[strokes.length - 1];
245
                                Binding b = binder.get(stroke);
246
                                if (b.nested != null) {
247
                                    Binding b2 = new Binding();
248
                                    b2.nested.putAll(b.nested);
249
                                    binder.put(stroke, b2);
250
                                } else {
251
                                    binder.remove(stroke);
252
                                }
253
                            } else {
254
                                // XXX warn about conflicts here too:
255
                                binder.put(strokes[strokes.length - 1], new Binding(def));
256
                                if (strokes.length == 1) {
257
                                    String id = idForFile(def);
258
                                    KeyStroke former = id2Dir.put(id, dir) == dir ? id2Stroke.get(id) : null;
259
                                    if (former == null || compare(former, strokes[0]) > 0) {
260
                                        id2Stroke.put(id, strokes[0]);
261
                                    }
227
                                }
262
                                }
228
                            }
263
                            }
229
                        }
264
                        }
Lines 231-236 Link Here
231
                    dir.removeFileChangeListener(bindingsListener);
266
                    dir.removeFileChangeListener(bindingsListener);
232
                    dir.addFileChangeListener(bindingsListener);
267
                    dir.addFileChangeListener(bindingsListener);
233
                }
268
                }
269
                processingProfile = true;
234
            }
270
            }
235
            if (refresh) {
271
            if (refresh) {
236
                // Update accelerators of existing actions after switching keymap.
272
                // Update accelerators of existing actions after switching keymap.
Lines 394-400 Link Here
394
     * else just returns file path (usual for more modern registrations).
430
     * else just returns file path (usual for more modern registrations).
395
     */
431
     */
396
    private static String idForFile(FileObject f) {
432
    private static String idForFile(FileObject f) {
397
        if (f.hasExt("shadow")) {
433
        if (f.hasExt(SHADOW_EXT)) {
398
            String path = (String) f.getAttribute("originalFile");
434
            String path = (String) f.getAttribute("originalFile");
399
            if (path != null && f.getSize() == 0) {
435
            if (path != null && f.getSize() == 0) {
400
                f = FileUtil.getConfigFile(path);
436
                f = FileUtil.getConfigFile(path);
(-)a/o.n.core/test/unit/src/org/netbeans/core/NbKeymapTest.java (+36 lines)
Lines 220-225 Link Here
220
        assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK), a.getValue(Action.ACCELERATOR_KEY));
220
        assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK), a.getValue(Action.ACCELERATOR_KEY));
221
    }
221
    }
222
222
223
    /*
224
     * Checks that:
225
     * - C-A is properly masked in one profile, visible in other profile
226
     * - from two sequences C-C S-B, C-C S-S, only one is properly masked in a profile
227
     */
228
    public void testKeymapMasksShortcut() throws Exception {
229
        make("Shortcuts/C-A.instance").setAttribute("instanceCreate", new DummyAction("one"));
230
        make("Shortcuts/C-C S-B.instance").setAttribute("instanceCreate", new DummyAction("two"));
231
        make("Shortcuts/C-C S-C.instance").setAttribute("instanceCreate", new DummyAction("four"));
232
        make("Keymaps/NetBeans/C-C S-B.removed");
233
        make("Keymaps/NetBeans/C-A.removed");
234
        make("Keymaps/Eclipse/C-A.instance").setAttribute("instanceCreate", new DummyAction("three"));
235
        NbKeymap km = new NbKeymap();
236
        KeyStroke controlA = KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK);
237
        
238
        assertNull("should be masked", km.getAction(controlA));
239
        
240
        KeyStroke controlC = KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK);
241
        KeyStroke shiftB = KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.SHIFT_MASK);
242
        KeyStroke shiftC = KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.SHIFT_MASK);
243
        
244
        Action a = km.getAction(controlC);
245
        assertNotNull("other binding must prevail", a);
246
        a.actionPerformed(null);
247
        
248
        assertNull("should be masked", km.getAction(shiftB));
249
250
        a = km.getAction(controlC);
251
        a.actionPerformed(null);
252
        assertEquals("four", km.getAction(shiftC).getValue(Action.NAME));
253
        
254
        FileUtil.getConfigFile("Keymaps").setAttribute("currentKeymap", "Eclipse");
255
        assertTrue(km.waitFinished());
256
        assertEquals("three", km.getAction(controlA).getValue(Action.NAME));
257
    }
258
223
    public void testMultiKeyShortcuts() throws Exception {
259
    public void testMultiKeyShortcuts() throws Exception {
224
        final AtomicReference<String> ran = new AtomicReference<String>();
260
        final AtomicReference<String> ran = new AtomicReference<String>();
225
        class A extends AbstractAction {
261
        class A extends AbstractAction {
(-)a/options.keymap/apichanges.xml (+108 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8"?>
2
<!--
3
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
5
Copyright 2012 Oracle and/or its affiliates. All rights reserved.
6
7
Oracle and Java are registered trademarks of Oracle and/or its affiliates.
8
Other names may be trademarks of their respective owners.
9
10
The contents of this file are subject to the terms of either the GNU
11
General Public License Version 2 only ("GPL") or the Common
12
Development and Distribution License("CDDL") (collectively, the
13
"License"). You may not use this file except in compliance with the
14
License. You can obtain a copy of the License at
15
http://www.netbeans.org/cddl-gplv2.html
16
or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
17
specific language governing permissions and limitations under the
18
License.  When distributing the software, include this License Header
19
Notice in each file and include the License file at
20
nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
21
particular file as subject to the "Classpath" exception as provided
22
by Oracle in the GPL Version 2 section of the License file that
23
accompanied this code. If applicable, add the following below the
24
License Header, with the fields enclosed by brackets [] replaced by
25
your own identifying information:
26
"Portions Copyrighted [year] [name of copyright owner]"
27
28
If you wish your version of this file to be governed by only the CDDL
29
or only the GPL Version 2, indicate your decision by adding
30
"[Contributor] elects to include this software in this distribution
31
under the [CDDL or GPL Version 2] license." If you do not indicate a
32
single choice of license, a recipient has the option to distribute
33
your version of this file under either the CDDL, the GPL Version 2 or
34
to extend the choice of license to its licensees as provided above.
35
However, if you add GPL Version 2 code and therefore, elected the GPL
36
Version 2 license, then the option applies only if the new code is
37
made subject to such option by the copyright holder.
38
39
Contributor(s):
40
41
Portions Copyrighted 2012 Sun Microsystems, Inc.
42
-->
43
<?xml-stylesheet type="text/xml" href="../nbbuild/javadoctools/apichanges.xsl"?>
44
<!DOCTYPE apichanges PUBLIC "-//NetBeans//DTD API changes list 1.0//EN" "../nbbuild/javadoctools/apichanges.dtd">
45
<apichanges>
46
    <apidefs>
47
        <apidef name="keymap">Keymap Options API</apidef>
48
    </apidefs>
49
    <changes>
50
        <change id="profile-remove-binding">
51
            <api name="keymap"/>
52
            <summary>Allows a Profile to remove (override) global keymap entry</summary>
53
            <version major="1" minor="19"/>
54
            <date day="22" month="5" year="2012"/>
55
            <author login="sdedic"/>
56
            <compatibility addition="yes"/>
57
            <description>
58
                <p>
59
                    In order to remove a keymap entry, create a file in the profile's
60
                    directory, with extension <code>remove</code>:
61
                </p>
62
                    <pre><code>
63
                        &lt;file name="DS-O.remove"/>
64
                    </code></pre>
65
                <p>
66
                    No attributes are necessary. The system will treat <code>DS-O</code>
67
                    key in the appropriate keymap profile as undefined. The convention should
68
                    be only used in keymap profiles, not in the base keymap.
69
                </p>
70
            </description>
71
            <issue number="203203"/>
72
        </change>
73
    </changes>
74
    <!-- Now the surrounding HTML text and document structure: -->
75
76
    <htmlcontents>
77
<!--
78
79
                            NO NO NO NO NO!
80
81
         ==============>    DO NOT EDIT ME!  <==============
82
83
          AUTOMATICALLY GENERATED FROM APICHANGES.XML, DO NOT EDIT
84
85
                SEE ant/project/apichanges.xml
86
87
-->
88
    <head>
89
      <title>Change History for the Keymap Options API</title>
90
      <link rel="stylesheet" href="prose.css" type="text/css"/>
91
    </head>
92
    <body>
93
94
<p class="overviewlink"><a href="overview-summary.html">Overview</a></p>
95
96
<h1>Introduction</h1>
97
98
<p>This document lists changes made to the <a href="@org-netbeans-modules-options-keymap@/index.html">Keymap Options API</a>.</p>
99
100
<!-- The actual lists of changes, as summaries and details: -->
101
      <hr/>
102
      <standard-changelists module-code-name="org.netbeans.modules.options.keymap"/>
103
104
      <hr/><p>@FOOTER@</p>
105
106
    </body>
107
  </htmlcontents>
108
</apichanges>
(-)a/options.keymap/manifest.mf (-1 / +1 lines)
Lines 2-8 Link Here
2
OpenIDE-Module: org.netbeans.modules.options.keymap
2
OpenIDE-Module: org.netbeans.modules.options.keymap
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/options/keymap/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/options/keymap/Bundle.properties
4
OpenIDE-Module-Layer: org/netbeans/modules/options/keymap/mf-layer.xml
4
OpenIDE-Module-Layer: org/netbeans/modules/options/keymap/mf-layer.xml
5
OpenIDE-Module-Specification-Version: 1.18
5
OpenIDE-Module-Specification-Version: 1.19
6
AutoUpdate-Show-In-Client: false
6
AutoUpdate-Show-In-Client: false
7
AutoUpdate-Essential-Module: true
7
AutoUpdate-Essential-Module: true
8
8
(-)a/options.keymap/nbproject/project.properties (+1 lines)
Lines 1-5 Link Here
1
javac.compilerargs=-Xlint:unchecked
1
javac.compilerargs=-Xlint:unchecked
2
javac.source=1.6
2
javac.source=1.6
3
javadoc.arch=${basedir}/arch.xml
3
javadoc.arch=${basedir}/arch.xml
4
javadoc.apichanges=${basedir}/apichanges.xml
4
5
5
test.config.stableBTD.includes=**/*Test.class
6
test.config.stableBTD.includes=**/*Test.class
(-)a/options.keymap/src/org/netbeans/core/options/keymap/spi/KeymapManager.java (+1 lines)
Lines 44-49 Link Here
44
44
45
package org.netbeans.core.options.keymap.spi;
45
package org.netbeans.core.options.keymap.spi;
46
46
47
import java.util.Collections;
47
import java.util.List;
48
import java.util.List;
48
import java.util.Map;
49
import java.util.Map;
49
import java.util.Set;
50
import java.util.Set;
(-)a/options.keymap/src/org/netbeans/modules/options/keymap/LayersBridge.java (-18 / +156 lines)
Lines 44-51 Link Here
44
44
45
package org.netbeans.modules.options.keymap;
45
package org.netbeans.modules.options.keymap;
46
46
47
import java.awt.event.ActionListener;
47
import java.io.IOException;
48
import java.io.IOException;
49
import java.lang.reflect.Field;
48
import java.util.ArrayList;
50
import java.util.ArrayList;
51
import java.util.Arrays;
49
import java.util.Collections;
52
import java.util.Collections;
50
import java.util.Enumeration;
53
import java.util.Enumeration;
51
import java.util.HashMap;
54
import java.util.HashMap;
Lines 69-74 Link Here
69
import org.openide.loaders.DataFolder;
72
import org.openide.loaders.DataFolder;
70
import org.openide.loaders.DataObject;
73
import org.openide.loaders.DataObject;
71
import org.openide.loaders.DataShadow;
74
import org.openide.loaders.DataShadow;
75
import org.openide.util.Exceptions;
76
import org.openide.util.Lookup;
72
import org.openide.util.NbBundle;
77
import org.openide.util.NbBundle;
73
78
74
/**
79
/**
Lines 79-84 Link Here
79
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.core.options.keymap.spi.KeymapManager.class)
84
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.core.options.keymap.spi.KeymapManager.class)
80
public class LayersBridge extends KeymapManager {
85
public class LayersBridge extends KeymapManager {
81
    
86
    
87
    /**
88
     * Extension for DataObjects, which cause an action to be removed from the parent (general) keymap.
89
     */
90
    private static final String EXT_REMOVED = "removed"; // NOI18N
91
    
82
    private static final Logger LOG = Logger.getLogger(LayersBridge.class.getName());
92
    private static final Logger LOG = Logger.getLogger(LayersBridge.class.getName());
83
    
93
    
84
    static final String         KEYMAPS_FOLDER = "Keymaps";
94
    static final String         KEYMAPS_FOLDER = "Keymaps";
Lines 221-226 Link Here
221
            new HashMap<String, Map<ShortcutAction, Set<String>>> ();
231
            new HashMap<String, Map<ShortcutAction, Set<String>>> ();
222
    
232
    
223
    /**
233
    /**
234
     * The base keymap, shared for all profiles. Used as a baseline when generating
235
     * 'removed' instructions for a profile.
236
     */
237
    private volatile Map<ShortcutAction, Set<String>> baseKeyMap;
238
    
239
    /**
224
     * Returns Map (GlobalAction > Set (String (shortcut))).
240
     * Returns Map (GlobalAction > Set (String (shortcut))).
225
     */
241
     */
226
    public Map<ShortcutAction, Set<String>> getKeymap (String profile) {
242
    public Map<ShortcutAction, Set<String>> getKeymap (String profile) {
Lines 229-238 Link Here
229
            Map<ShortcutAction, Set<String>> m = readKeymap (root);
245
            Map<ShortcutAction, Set<String>> m = readKeymap (root);
230
            root = getRootFolder (KEYMAPS_FOLDER, profile);
246
            root = getRootFolder (KEYMAPS_FOLDER, profile);
231
            overrideWithKeyMap(m, readKeymap(root), profile);
247
            overrideWithKeyMap(m, readKeymap(root), profile);
248
            m.remove(REMOVED);
232
            keymaps.put (profile, m);
249
            keymaps.put (profile, m);
233
        }
250
        }
234
        return Collections.unmodifiableMap (keymaps.get (profile));
251
        return Collections.unmodifiableMap (keymaps.get (profile));
235
    }
252
    }
253
        
254
    private Map<ShortcutAction, Set<String>> getBaseKeyMap() {
255
        if (baseKeyMap == null) {
256
            DataFolder root = getRootFolder (SHORTCUTS_FOLDER, null);
257
            Map<ShortcutAction, Set<String>> m = readKeymap (root);
258
            baseKeyMap = m;
259
        }
260
        return baseKeyMap;
261
    }
236
    
262
    
237
    /**
263
    /**
238
     * Overrides the base shortcut map with contents of the Keymap. If keymap specifies
264
     * Overrides the base shortcut map with contents of the Keymap. If keymap specifies
Lines 286-297 Link Here
286
    /**
312
    /**
287
     * Returns Map (GlobalAction > Set (String (shortcut))).
313
     * Returns Map (GlobalAction > Set (String (shortcut))).
288
     */
314
     */
289
    public Map<ShortcutAction, Set<String>> getDefaultKeymap (String profile) {
315
    public synchronized Map<ShortcutAction, Set<String>> getDefaultKeymap (String profile) {
290
        if (!keymapDefaults.containsKey (profile)) {
316
        if (!keymapDefaults.containsKey (profile)) {
291
            DataFolder root = getRootFolder (SHORTCUTS_FOLDER, null);
317
            DataFolder root = getRootFolder (SHORTCUTS_FOLDER, null);
318
            System.err.println(Arrays.asList(root.getChildren()));
292
            Map<ShortcutAction, Set<String>> m = readKeymap (root);
319
            Map<ShortcutAction, Set<String>> m = readKeymap (root);
293
            root = getRootFolder (KEYMAPS_FOLDER, profile);
320
            root = getRootFolder (KEYMAPS_FOLDER, profile);
294
            m.putAll (readKeymap (root));
321
            overrideWithKeyMap(m, readKeymap(root), profile);
322
            m.remove(REMOVED);
295
            keymapDefaults.put (profile, m);
323
            keymapDefaults.put (profile, m);
296
        }
324
        }
297
        return Collections.unmodifiableMap (keymapDefaults.get (profile));
325
        return Collections.unmodifiableMap (keymapDefaults.get (profile));
Lines 302-310 Link Here
302
    }
330
    }
303
    
331
    
304
    /**
332
    /**
333
     * Placeholder, which indicates shortcut(s) that should be removed. Must be used
334
     * only internally !
335
     */
336
    private static final GlobalAction REMOVED = new GlobalAction(null, null, "<removed>") {
337
        { 
338
            name = ""; // NOI18N
339
        }
340
    };
341
    
342
    /**
305
     * Read keymap from one folder Map (GlobalAction > Set (String (shortcut))).
343
     * Read keymap from one folder Map (GlobalAction > Set (String (shortcut))).
306
     */
344
     */
307
    private Map<ShortcutAction, Set<String>> readKeymap (DataFolder root) {
345
    private Map<ShortcutAction, Set<String>> readKeymap (DataFolder root) {
346
        LOG.log(Level.FINEST, "Reading keymap from: {0}", root);
308
        Map<ShortcutAction, Set<String>> keymap = 
347
        Map<ShortcutAction, Set<String>> keymap = 
309
                new HashMap<ShortcutAction, Set<String>> ();
348
                new HashMap<ShortcutAction, Set<String>> ();
310
        if (root == null) return keymap;
349
        if (root == null) return keymap;
Lines 314-320 Link Here
314
            if (dataObject instanceof DataFolder) continue;
353
            if (dataObject instanceof DataFolder) continue;
315
            GlobalAction action = createAction (dataObject, null);
354
            GlobalAction action = createAction (dataObject, null);
316
            if (action == null) continue;
355
            if (action == null) continue;
317
            String shortcut = dataObject.getName ();
356
            String shortcut = dataObject.getPrimaryFile().getName();
318
            
357
            
319
            LOG.log(Level.FINEST, "Action {0}: {1}, by {2}", new Object[] {
358
            LOG.log(Level.FINEST, "Action {0}: {1}, by {2}", new Object[] {
320
                action.getDisplayName(),
359
                action.getDisplayName(),
Lines 331-336 Link Here
331
        return keymap;
370
        return keymap;
332
    }
371
    }
333
372
373
    @Override
334
    public void deleteProfile (String profile) {
374
    public void deleteProfile (String profile) {
335
        FileObject root = FileUtil.getConfigFile(KEYMAPS_FOLDER);
375
        FileObject root = FileUtil.getConfigFile(KEYMAPS_FOLDER);
336
        if (root == null) return;
376
        if (root == null) return;
Lines 344-354 Link Here
344
    }
384
    }
345
    
385
    
346
    // actionToShortcuts Map (GlobalAction > Set (String (shortcut))
386
    // actionToShortcuts Map (GlobalAction > Set (String (shortcut))
387
    @Override
347
    public void saveKeymap (String profile, Map<ShortcutAction, Set<String>> actionToShortcuts) {
388
    public void saveKeymap (String profile, Map<ShortcutAction, Set<String>> actionToShortcuts) {
348
        // discard our cached copy first
389
        // discard our cached copy first
349
        keymaps.remove(profile);
390
        keymaps.remove(profile);
350
        
391
        keymapDefaults.remove(profile);
351
        // 1) get / create Keymaps/Profile folder
392
        // 1) get / create Keymaps/Profile folder
393
        DataFolder defaultFolder = getRootFolder(SHORTCUTS_FOLDER, null);
352
        DataFolder folder = getRootFolder (KEYMAPS_FOLDER, profile);
394
        DataFolder folder = getRootFolder (KEYMAPS_FOLDER, profile);
353
        if (folder == null) {
395
        if (folder == null) {
354
            folder = getRootFolder (KEYMAPS_FOLDER, null);
396
            folder = getRootFolder (KEYMAPS_FOLDER, null);
Lines 359-394 Link Here
359
                return;
401
                return;
360
            }
402
            }
361
        }
403
        }
362
        saveKeymap (folder, actionToShortcuts, true);
404
        saveKeymap (defaultFolder, folder, actionToShortcuts);
363
        
364
        // FIXME: this rewrites the shared Shortcuts (default) folder; should be
365
        // done only after switching the profile
366
        folder = getRootFolder (SHORTCUTS_FOLDER, null);
367
        saveKeymap (folder, actionToShortcuts, false);
368
    }
405
    }
369
    
406
    
370
    private void saveKeymap (DataFolder folder, Map<ShortcutAction, Set<String>> actionToShortcuts, boolean add) {
407
    private void saveKeymap (DataFolder defaultMap, DataFolder folder, Map<ShortcutAction, Set<String>> actionToShortcuts) {
408
        LOG.log(Level.FINEST, "Saving keymap to: {0}", folder.getPrimaryFile().getPath());
371
        // hack: initialize the actions map first
409
        // hack: initialize the actions map first
372
  	getActions();
410
  	getActions();
373
        // 2) convert to: Map (String (shortcut AC-C X) > GlobalAction)
411
        // 2) convert to: Map (String (shortcut AC-C X) > GlobalAction)
374
        Map<String, ShortcutAction> shortcutToAction = shortcutToAction (actionToShortcuts);
412
        Map<String, ShortcutAction> shortcutToAction = shortcutToAction (actionToShortcuts);
375
        
413
        
414
        Set<String> definedShortcuts = new HashSet<String>(shortcutToAction.keySet());
415
        
376
        // 3) delete obsolete DataObjects
416
        // 3) delete obsolete DataObjects
417
        FileObject targetDir = folder.getPrimaryFile();
418
377
        Enumeration en = folder.children ();
419
        Enumeration en = folder.children ();
378
        while (en.hasMoreElements ()) {
420
        while (en.hasMoreElements ()) {
379
            DataObject dataObject = (DataObject) en.nextElement ();
421
            DataObject dataObject = (DataObject) en.nextElement ();
422
            if (dataObject.getPrimaryFile().getExt().equals(EXT_REMOVED)) {
423
                continue;
424
            }
380
            GlobalAction a1 = (GlobalAction) shortcutToAction.get (dataObject.getName ());
425
            GlobalAction a1 = (GlobalAction) shortcutToAction.get (dataObject.getName ());
381
            if (a1 != null) {
426
            if (a1 != null) {
382
                GlobalAction action = createAction (dataObject, null);
427
                GlobalAction action = createAction (dataObject, null);
383
                if (action == null) continue;
428
                if (action == null) continue;
384
                if (action.equals (a1)) {
429
                if (action.equals (a1)) {
385
                    // shortcut already saved
430
                    // shortcut already saved
431
                    LOG.log(Level.FINEST, "Found same binding: {0} -> {1}", new Object[] { dataObject.getName(), action.getId()});
386
                    shortcutToAction.remove (dataObject.getName ());
432
                    shortcutToAction.remove (dataObject.getName ());
387
                    continue;
433
                    continue;
388
                }
434
                }
389
            }
435
            }
390
            // obsolete shortcut
436
            // obsolete shortcut. 
391
            try {
437
            try {
438
                LOG.log(Level.FINEST, "Removing obsolete binding: {0}", dataObject.getName());
392
                dataObject.delete ();
439
                dataObject.delete ();
393
            } catch (IOException ex) {
440
            } catch (IOException ex) {
394
                ex.printStackTrace ();
441
                ex.printStackTrace ();
Lines 396-405 Link Here
396
        }
443
        }
397
        
444
        
398
        // 4) add new shortcuts
445
        // 4) add new shortcuts
399
        if (!add) return;
446
        en = defaultMap.children();
447
        while (en.hasMoreElements()) {
448
            DataObject dataObject = (DataObject)en.nextElement();
449
            GlobalAction ga = (GlobalAction)shortcutToAction.get(dataObject.getName());
450
            if (ga == null) {
451
                continue;
452
            }
453
            GlobalAction action = createAction(dataObject, null);
454
            if (ga.equals(action)) {
455
                LOG.log(Level.FINEST, "Leaving default shortcut: {0}", dataObject.getName());
456
                shortcutToAction.remove(dataObject.getName());
457
            }
458
        }
459
        
400
        Iterator it = shortcutToAction.keySet ().iterator ();
460
        Iterator it = shortcutToAction.keySet ().iterator ();
401
        while (it.hasNext ()) {
461
        while (it.hasNext ()) {
402
            String shortcut = (String) it.next ();
462
            String shortcut = (String) it.next ();
463
            // check whether the DO does not already exist:
403
            GlobalAction action = (GlobalAction) shortcutToAction.get (shortcut);
464
            GlobalAction action = (GlobalAction) shortcutToAction.get (shortcut);
404
            DataObject dataObject = actionToDataObject.get (action);
465
            DataObject dataObject = actionToDataObject.get (action);
405
            if (dataObject == null) {
466
            if (dataObject == null) {
Lines 409-419 Link Here
409
            }
470
            }
410
            try {
471
            try {
411
                DataShadow.create (folder, shortcut, dataObject);
472
                DataShadow.create (folder, shortcut, dataObject);
473
                // remove the '.remove' file, if it exists:
474
                FileObject f = targetDir.getFileObject(shortcut, EXT_REMOVED);
475
                if (f != null) {
476
                    f.delete();
477
                }
412
            } catch (IOException ex) {
478
            } catch (IOException ex) {
413
                ex.printStackTrace ();
479
                ex.printStackTrace ();
414
                continue;
480
                continue;
415
            }
481
            }
416
        }
482
        }
483
484
        // 5, mask out DataObjects from the global keymap, which are NOT present in this profile:
485
        if (defaultMap != null) {
486
            en = defaultMap.children();
487
            while (en.hasMoreElements()) {
488
                DataObject dataObject = (DataObject) en.nextElement ();
489
                if (definedShortcuts.contains(dataObject.getName())) {
490
                    continue;
491
                }
492
                try {
493
                    FileObject pf = dataObject.getPrimaryFile();
494
                    // If the shortcut is ALSO defined in 'parent' folder,
495
                    // we cannot just 'delete' it, but also mask the parent by adding 'removed' file.
496
                    if (targetDir.getFileObject(pf.getName(), EXT_REMOVED) == null) {
497
                        LOG.log(Level.FINEST, "Masking out binding: {0}", pf.getName());
498
                        folder.getPrimaryFile().createData(pf.getName(), EXT_REMOVED);
499
                    }
500
                } catch (IOException ex) {
501
                    ex.printStackTrace ();
502
                }
503
            }
504
        }
505
        
417
    }    
506
    }    
418
507
419
    private static DataFolder getRootFolder (String name1, String name2) {
508
    private static DataFolder getRootFolder (String name1, String name2) {
Lines 438-454 Link Here
438
     */
527
     */
439
    private GlobalAction createAction (DataObject dataObject, String prefix) {
528
    private GlobalAction createAction (DataObject dataObject, String prefix) {
440
        InstanceCookie ic = dataObject.getCookie(InstanceCookie.class);
529
        InstanceCookie ic = dataObject.getCookie(InstanceCookie.class);
441
        if (ic == null) return null;
530
        // handle any non-IC file as instruction to remove the action
531
        if (ic == null) {
532
            FileObject pf = dataObject.getPrimaryFile();
533
            if (!EXT_REMOVED.equals(pf.getExt())) {
534
                LOG.log(Level.WARNING, "Invalid shortcut: {0}", dataObject);
535
            }
536
            // ignore the 'remove' file, if there's a shadow (= real action) present
537
            if (FileUtil.findBrother(pf, "shadow") != null) {
538
                // handle redefinition + removal: ignore the removal.
539
                return null;
540
            }
541
            return REMOVED;
542
        }
442
        try {
543
        try {
443
            Object action = ic.instanceCreate ();
544
            Object action = ic.instanceCreate ();
444
            if (action == null) return null;
545
            if (action == null) return null;
445
            if (!(action instanceof Action)) return null;
546
            if (!(action instanceof Action)) return null;
446
            return new GlobalAction ((Action) action, prefix, dataObject.getPrimaryFile().getName());
547
            return createAction((Action) action, prefix, dataObject.getPrimaryFile().getName());
447
        } catch (Exception ex) {
548
        } catch (Exception ex) {
448
            ex.printStackTrace ();
549
            ex.printStackTrace ();
449
            return null;
550
            return null;
450
        }
551
        }
451
    }
552
    }
553
554
    // hack: hardcoded OpenIDE impl class name + field
555
    private static final String OPENIDE_DELEGATE_ACTION = "org.openide.awt.GeneralAction$DelegateAction"; // NOI18N
556
    private static Field KEY_FIELD;
557
558
    /**
559
     * Hack, which allows to somehow extract actionId from OpenIDE actions. Public API
560
     * does not exist for this.
561
     * 
562
     * @param a
563
     * @param prefix
564
     * @param name
565
     * @return 
566
     */
567
    private static GlobalAction createAction(Action a, String prefix, String name) {
568
        String id = name;
569
        
570
        try {
571
            if (a.getClass().getName().equals(OPENIDE_DELEGATE_ACTION)) {
572
                if (KEY_FIELD == null) {
573
                    Class c = a.getClass();
574
                    KEY_FIELD = c.getSuperclass().getDeclaredField("key"); // NOI18N
575
                    KEY_FIELD.setAccessible(true);
576
                }
577
                String key = (String)KEY_FIELD.get(a);
578
                if (key != null) {
579
                    id = key;
580
                }
581
            }
582
        } catch (NoSuchFieldException ex) {
583
            Exceptions.printStackTrace(ex);
584
        } catch (IllegalAccessException ex) {
585
            Exceptions.printStackTrace(ex);
586
        }
587
        
588
        return new GlobalAction(a, prefix, id);
589
    }
452
    
590
    
453
    /**
591
    /**
454
     * converts: actionToShortcuts: Map (ShortcutAction > Set (String (shortcut AC-C X)))
592
     * converts: actionToShortcuts: Map (ShortcutAction > Set (String (shortcut AC-C X)))
Lines 494-500 Link Here
494
    
632
    
495
    private static class GlobalAction implements ShortcutAction {
633
    private static class GlobalAction implements ShortcutAction {
496
        private Action action;
634
        private Action action;
497
        private String name;
635
        String name;
498
        private String id;
636
        private String id;
499
        
637
        
500
        /**
638
        /**
Lines 539-550 Link Here
539
        @Override
677
        @Override
540
        public boolean equals (Object o) {
678
        public boolean equals (Object o) {
541
            if (!(o instanceof GlobalAction)) return false;
679
            if (!(o instanceof GlobalAction)) return false;
542
            return ((GlobalAction) o).action.equals (action);
680
            return ((GlobalAction) o).action == action || ((GlobalAction) o).action.equals (action);
543
        }
681
        }
544
        
682
        
545
        @Override
683
        @Override
546
        public int hashCode () {
684
        public int hashCode () {
547
            return action.hashCode ();
685
            return action == null ? 111 : action.hashCode ();
548
        }
686
        }
549
        
687
        
550
        @Override
688
        @Override

Return to bug 203203