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 152576
Collapse All | Expand All

(-)a/o.n.core/src/org/netbeans/core/NbKeymap.java (-332 / +261 lines)
Lines 41-60 Link Here
41
41
42
package org.netbeans.core;
42
package org.netbeans.core;
43
43
44
import java.awt.EventQueue;
45
import java.awt.Toolkit;
44
import java.awt.event.ActionEvent;
46
import java.awt.event.ActionEvent;
45
import java.awt.event.KeyEvent;
47
import java.awt.event.KeyEvent;
46
import java.util.ArrayList;
48
import java.util.ArrayList;
47
import java.util.Arrays;
48
import java.util.Collections;
49
import java.util.Collections;
49
import java.util.Comparator;
50
import java.util.Comparator;
50
import java.util.HashMap;
51
import java.util.HashMap;
51
import java.util.HashSet;
52
import java.util.Iterator;
53
import java.util.List;
52
import java.util.List;
54
import java.util.Map;
53
import java.util.Map;
55
import java.util.Map.Entry;
54
import java.util.TreeMap;
56
import java.util.Observable;
55
import java.util.WeakHashMap;
57
import java.util.Set;
58
import java.util.logging.Level;
56
import java.util.logging.Level;
59
import java.util.logging.Logger;
57
import java.util.logging.Logger;
60
import javax.swing.AbstractAction;
58
import javax.swing.AbstractAction;
Lines 62-100 Link Here
62
import javax.swing.KeyStroke;
60
import javax.swing.KeyStroke;
63
import javax.swing.text.Keymap;
61
import javax.swing.text.Keymap;
64
import org.openide.awt.StatusDisplayer;
62
import org.openide.awt.StatusDisplayer;
65
import org.openide.util.Mutex;
63
import org.openide.cookies.InstanceCookie;
64
import org.openide.filesystems.FileAttributeEvent;
65
import org.openide.filesystems.FileChangeAdapter;
66
import org.openide.filesystems.FileChangeListener;
67
import org.openide.filesystems.FileEvent;
68
import org.openide.filesystems.FileObject;
69
import org.openide.filesystems.FileRenameEvent;
70
import org.openide.filesystems.FileUtil;
71
import org.openide.loaders.DataObject;
72
import org.openide.loaders.DataObjectNotFoundException;
73
import org.openide.loaders.DataShadow;
74
import org.openide.util.Utilities;
66
import org.openide.util.actions.SystemAction;
75
import org.openide.util.actions.SystemAction;
67
import org.openide.util.lookup.ServiceProvider;
76
import org.openide.util.lookup.ServiceProvider;
68
77
69
/** Implementation of standard key - action mappings.
70
*
71
* @author Dafe Simonek
72
*/
73
@ServiceProvider(service=Keymap.class)
78
@ServiceProvider(service=Keymap.class)
74
public final class NbKeymap extends Observable implements Keymap, Comparator<KeyStroke> {
79
public final class NbKeymap implements Keymap, Comparator<KeyStroke> {
75
    /** Name of this keymap */
80
76
    String name;
81
    private static final Action BROKEN = new AbstractAction("<broken>") { // NOI18N
77
    /** Parent keymap */
82
        public void actionPerformed(ActionEvent e) {
78
    Keymap parent;
83
            Toolkit.getDefaultToolkit().beep();
79
    /** Hashtable holding KeyStroke > Action mappings */
84
        }
80
    Map<KeyStroke,Action> bindings;
85
    };
81
    /** Default action */
86
82
    Action defaultAction;
87
    /** Represents a binding of a keystroke. */
83
    /** hash table to map (Action -> ArrayList of KeyStrokes) */
88
    private static class Binding {
84
    Map<Action,List<KeyStroke>> actions;
89
        /** file defining an action; null if nested is not null */
90
        final FileObject actionDefinition;
91
        /** lazily instantiated actual action, in case actionDefinition is not null */
92
        private Action action;
93
        /** nested bindings; null if actionDefinition is not null */
94
        final Map<KeyStroke,Binding> nested;
95
        Binding(FileObject def) {
96
            actionDefinition = def;
97
            nested = null;
98
        }
99
        Binding() {
100
            actionDefinition = null;
101
            nested = new HashMap<KeyStroke,Binding>();
102
        }
103
        synchronized Action loadAction() {
104
            assert actionDefinition != null;
105
            if (action == null) {
106
                try {
107
                    DataObject d = DataObject.find(actionDefinition);
108
                    InstanceCookie ic = d.getLookup().lookup(InstanceCookie.class);
109
                    if (ic == null) {
110
                        return null;
111
                    }
112
                    action = (Action) ic.instanceCreate();
113
                } catch (/*ClassNotFoundException,IOException,ClassCastException*/Exception x) {
114
                    LOG.log(Level.INFO, "could not load action for " + actionDefinition.getPath(), x);
115
                }
116
            }
117
            if (action == null) {
118
                action = BROKEN;
119
            }
120
            return action;
121
        }
122
    }
123
124
    private Map<KeyStroke,Binding> bindings;
125
    private Map<String,KeyStroke> id2Stroke;
126
    private final Map<Action,String> action2Id = new WeakHashMap<Action,String>();
127
    private FileChangeListener keymapListener;
128
    private FileChangeListener bindingsListener = new FileChangeAdapter() {
129
        public @Override void fileDataCreated(FileEvent fe) {
130
            refreshBindings();
131
        }
132
        public @Override void fileAttributeChanged(FileAttributeEvent fe) {
133
            refreshBindings();
134
        }
135
        public @Override void fileChanged(FileEvent fe) {
136
            refreshBindings();
137
        }
138
        public @Override void fileRenamed(FileRenameEvent fe) {
139
            refreshBindings();
140
        }
141
        public @Override void fileDeleted(FileEvent fe) {
142
            refreshBindings();
143
        }
144
    };
145
146
    private synchronized void refreshBindings() {
147
        bindings = null;
148
        bindings();
149
    }
150
151
    private synchronized Map<KeyStroke,Binding> bindings() {
152
        if (bindings == null) {
153
            bindings = new HashMap<KeyStroke,Binding>();
154
            boolean refresh = id2Stroke != null;
155
            id2Stroke = new TreeMap<String,KeyStroke>();
156
            List<FileObject> dirs = new ArrayList<FileObject>(2);
157
            dirs.add(FileUtil.getConfigFile("Shortcuts")); // NOI18N
158
            FileObject keymaps = FileUtil.getConfigFile("Keymaps"); // NOI18N
159
            if (keymaps != null) {
160
                String curr = (String) keymaps.getAttribute("currentKeymap"); // NOI18N
161
                if (curr == null) {
162
                    curr = "NetBeans"; // NOI18N
163
                }
164
                dirs.add(keymaps.getFileObject(curr));
165
                if (keymapListener == null) {
166
                    keymapListener = new FileChangeAdapter() {
167
                        public @Override void fileAttributeChanged(FileAttributeEvent fe) {
168
                            refreshBindings();
169
                        }
170
                    };
171
                    keymaps.addFileChangeListener(keymapListener);
172
                }
173
            }
174
            for (FileObject dir : dirs) {
175
                if (dir != null) {
176
                    for (FileObject def : dir.getChildren()) {
177
                        if (def.isData()) {
178
                            KeyStroke[] strokes = Utilities.stringToKeys(def.getName());
179
                            if (strokes == null || strokes.length == 0) {
180
                                LOG.log(Level.WARNING, "could not load parse name of " + def.getPath());
181
                                continue;
182
                            }
183
                            Map<KeyStroke,Binding> binder = bindings;
184
                            for (int i = 0; i < strokes.length - 1; i++) {
185
                                Binding sub = binder.get(strokes[i]);
186
                                if (sub != null && sub.nested == null) {
187
                                    LOG.log(Level.WARNING, "conflict between " + sub.actionDefinition.getPath() + " and " + def.getPath());
188
                                    sub = null;
189
                                }
190
                                if (sub == null) {
191
                                    binder.put(strokes[i], sub = new Binding());
192
                                }
193
                                binder = sub.nested;
194
                            }
195
                            // XXX warn about conflicts here too:
196
                            binder.put(strokes[strokes.length - 1], new Binding(def));
197
                            if (strokes.length == 1) {
198
                                String id = idForFile(def);
199
                                KeyStroke former = id2Stroke.get(id);
200
                                if (former == null || compare(former, strokes[0]) > 0) {
201
                                    id2Stroke.put(id, strokes[0]);
202
                                }
203
                            }
204
                        }
205
                    }
206
                    dir.removeFileChangeListener(bindingsListener);
207
                    dir.addFileChangeListener(bindingsListener);
208
                }
209
            }
210
            if (refresh) {
211
                // Update accelerators of existing actions after switching keymap.
212
                EventQueue.invokeLater(new Runnable() {
213
                    public void run() {
214
                        for (Map.Entry<Action, String> entry : action2Id.entrySet()) {
215
                            entry.getKey().putValue(Action.ACCELERATOR_KEY, id2Stroke.get(entry.getValue()));
216
                        }
217
                    }
218
                });
219
            }
220
            if (LOG.isLoggable(Level.FINE)) {
221
                for (Map.Entry<String,KeyStroke> entry : id2Stroke.entrySet()) {
222
                    LOG.fine(entry.getValue() + " => " + entry.getKey());
223
                }
224
            }
225
        }
226
        return bindings;
227
    }
85
    
228
    
86
    private static List<KeyStroke> context = new ArrayList<KeyStroke>();
229
    private static List<KeyStroke> context; // accessed reflectively from org.netbeans.editor.MultiKeymap
87
    
230
    
88
    public static void resetContext() {
231
    private static void resetContext() {
89
        context.clear();
232
        context.clear();
90
        StatusDisplayer.getDefault().setStatusText("");
233
        StatusDisplayer.getDefault().setStatusText("");
91
    }
234
    }
92
235
93
    public static KeyStroke[] getContext() {
236
    public static KeyStroke[] getContext() { // called from ShortcutAndMenuKeyEventProcessor
94
        return context.toArray(new KeyStroke[context.size()]);
237
        return context.toArray(new KeyStroke[context.size()]);
95
    }
238
    }
96
    
239
97
    public static void shiftContext(KeyStroke stroke) {
240
    private static void shiftContext(KeyStroke stroke) {
98
        context.add(stroke);
241
        context.add(stroke);
99
242
100
        StringBuilder text = new StringBuilder();
243
        StringBuilder text = new StringBuilder();
Lines 114-304 Link Here
114
            KeyEvent.getKeyText (keyStroke.getKeyCode ()); 
257
            KeyEvent.getKeyText (keyStroke.getKeyCode ()); 
115
    }
258
    }
116
           
259
           
117
    private final Action NO_ACTION = new KeymapAction(null, null);
118
    private static final Logger LOG = Logger.getLogger(NbKeymap.class.getName());
260
    private static final Logger LOG = Logger.getLogger(NbKeymap.class.getName());
119
    
261
    
120
    public Action createMapAction(Keymap k, KeyStroke stroke) {
121
        return new KeymapAction(k, stroke);
122
    }
123
124
    /** Default constructor
125
    */
126
    public NbKeymap() {
262
    public NbKeymap() {
127
        this("Default", null); // NOI18N
263
        context = new ArrayList<KeyStroke>();
128
    }
129
130
    NbKeymap(final String name, final Keymap parent) {
131
        this.name = name;
132
        this.parent = parent;
133
        bindings = new HashMap<KeyStroke,Action>();
134
    }
264
    }
135
265
136
    public Action getDefaultAction() {
266
    public Action getDefaultAction() {
137
        LOG.log(Level.FINE, "getDefaultAction");
267
        return null;
138
        if (defaultAction != null) {
139
            return defaultAction;
140
        }
141
        return (parent != null) ? parent.getDefaultAction() : null;
142
    }
268
    }
143
269
144
    public void setDefaultAction(Action a) {
270
    public void setDefaultAction(Action a) {
145
        LOG.log(Level.FINE, "setDefaultAction {0}", id(a));
271
        throw new UnsupportedOperationException();
146
        defaultAction = a;
147
        setChanged();
148
        notifyObservers();
149
    }
272
    }
150
273
151
    public String getName() {
274
    public String getName() {
152
        return name;
275
        return "Default"; // NOI18N
153
    }
276
    }
154
277
155
    public Action getAction(KeyStroke key) {
278
    public Action getAction(final KeyStroke key) {
279
        switch (key.getKeyCode()) {
280
        case KeyEvent.VK_SHIFT:
281
        case KeyEvent.VK_CONTROL:
282
        case KeyEvent.VK_ALT:
283
        case KeyEvent.VK_ALT_GRAPH:
284
        case KeyEvent.VK_META:
285
        case KeyEvent.VK_UNDEFINED:
286
        case KeyEvent.CHAR_UNDEFINED:
287
                // Not actually a bindable key press.
288
                return null;
289
        }
290
        if (key.isOnKeyRelease()) {
291
            // Again, not really our business here.
292
            return null;
293
        }
156
        LOG.log(Level.FINE, "getAction {0}", key);
294
        LOG.log(Level.FINE, "getAction {0}", key);
157
        
295
        Map<KeyStroke,Binding> binder = bindings();
158
        Action a;
296
        for (KeyStroke ctx : context) {
159
297
            Binding sub = binder.get(ctx);
160
        KeyStroke[] ctx = getContext();
298
            if (sub == null) {
161
        Keymap activ = this;
299
                resetContext();
162
        for (int i=0; i<ctx.length; i++) {
300
                return BROKEN; // no entry found after known prefix
163
            if (activ == this) {
164
                a = bindings.get(ctx[i]);
165
                if ((a == null) && (parent != null)) {
166
                    a = parent.getAction(ctx[i]);
167
                }
168
            } else {
169
                a = activ.getAction(ctx[i]);
170
            }
301
            }
171
            
302
            binder = sub.nested;
172
            if (a instanceof KeymapAction) {
303
            if (binder == null) {
173
                activ = ((KeymapAction)a).keymap;
304
                resetContext();
174
            } else { // unknown ctx
305
                return BROKEN; // anomalous, expected to find submap here
175
                int code = key.getKeyCode();
176
                if (code != KeyEvent.VK_CONTROL &&
177
                        code != KeyEvent.VK_ALT &&
178
                        code != KeyEvent.VK_ALT_GRAPH &&
179
                        code != KeyEvent.VK_SHIFT &&
180
                        code != KeyEvent.VK_META) resetContext();
181
                return null;
182
            }
306
            }
183
        }
307
        }
184
        
308
        Binding b = binder.get(key);
185
        if (activ == this) {
309
        if (b == null) {
186
            a = bindings.get(key);
310
            resetContext();
187
            if ((a == null) && (parent != null)) {
311
            return null; // normal, not found
188
                a = parent.getAction(key);
312
        }
189
            }
313
        if (b.nested == null) {
190
            return a;
314
            resetContext();
315
            return b.loadAction(); // found real action
191
        } else {
316
        } else {
192
            a = activ.getAction(key);
317
            return new AbstractAction() {
193
        }
318
                public void actionPerformed(ActionEvent e) {
194
        
319
                    shiftContext(key); // entering submap
195
        if (a != null) {
320
                }
196
            if (!(a instanceof KeymapAction)) {
321
            };
197
                resetContext();
198
            }
199
            return a;
200
        }
201
            
202
        // no action, should we reset?
203
        if (key.isOnKeyRelease() ||
204
            (key.getKeyChar() != 0 && key.getKeyChar() != KeyEvent.CHAR_UNDEFINED)) {
205
                return null;
206
        }
207
            
208
        switch (key.getKeyCode()) {
209
            case KeyEvent.VK_SHIFT:
210
            case KeyEvent.VK_CONTROL:
211
            case KeyEvent.VK_ALT:
212
            case KeyEvent.VK_META:
213
                return null;
214
            default:
215
                resetContext();
216
                return NO_ACTION;
217
        }
322
        }
218
    }
323
    }
219
324
220
    public KeyStroke[] getBoundKeyStrokes() {
325
    public KeyStroke[] getBoundKeyStrokes() {
221
        LOG.log(Level.FINE, "getBoundKeyStrokes");
326
        assert false;
222
        int i = 0;
327
        return null;
223
        KeyStroke[] keys = null;
224
        synchronized (this) {
225
            keys = new KeyStroke[bindings.size()];
226
            for (KeyStroke ks: bindings.keySet()) {
227
                keys[i++] = ks;
228
            }
229
        }
230
        return keys;
231
    }
328
    }
232
329
233
    public Action[] getBoundActions() {
330
    public Action[] getBoundActions() {
234
        LOG.log(Level.FINE, "getBoundActions");
331
        assert false;
235
        int i = 0;
332
        return null;
236
        Action[] actionsArray = null;
237
        synchronized (this) {
238
            actionsArray = new Action[bindings.size()];
239
            for (Iterator iter = bindings.values().iterator(); iter.hasNext(); ) {
240
                actionsArray[i++] = (Action) iter.next();
241
            }
242
        }
243
        return actionsArray;
244
    }
333
    }
245
334
246
    public KeyStroke[] getKeyStrokesForAction(Action a) {
335
    public KeyStroke[] getKeyStrokesForAction(Action a) {
247
        LOG.log(Level.FINE, "getKeyStrokesForAction {0}", id(a));
336
        FileObject definingFile = (FileObject) a.getValue("definingFile"); // cf. o.o.awt.Toolbar.setAccelerator
248
337
        if (definingFile == null) {
249
        Map<Action,List<KeyStroke>> localActions = actions;
338
            LOG.log(Level.FINE, "no defining file known for {0}", id(a));
250
        if (localActions == null) {
251
            localActions = buildReverseMapping ();
252
        }
253
254
        List<KeyStroke> strokes = localActions.get (a);
255
        if (strokes != null) {
256
            return strokes.toArray(new KeyStroke[strokes.size ()]);
257
        } else {
258
            return new KeyStroke[0];
339
            return new KeyStroke[0];
259
        }
340
        }
341
        String id = idForFile(definingFile);
342
        bindings();
343
        action2Id.put(a, id);
344
        KeyStroke k = id2Stroke.get(id);
345
        LOG.log(Level.FINE, "found keystroke {0} for {1} with ID {2}", new Object[] {k, id(a), id});
346
        return k != null ? new KeyStroke[] {k} : new KeyStroke[0];
260
    }
347
    }
261
348
    /**
262
    private Map<Action,List<KeyStroke>> buildReverseMapping () {
349
     * Traverses shadow files to origin.
263
        Map<Action,List<KeyStroke>> localActions = actions = new HashMap<Action,List<KeyStroke>> ();
350
     * Returns impl class name if that is obvious (common for SystemAction's);
264
351
     * else just returns file path (usual for more modern registrations).
265
        synchronized (this) {
352
     */
266
            for (Map.Entry<KeyStroke,Action> curEntry: bindings.entrySet()) {
353
    private static String idForFile(FileObject f) {
267
                Action curAction = curEntry.getValue();
354
        if (f.hasExt("shadow")) {
268
                KeyStroke curKey = curEntry.getKey();
355
            String path = (String) f.getAttribute("originalFile");
269
356
            if (path != null) {
270
                List<KeyStroke> keysForAction = localActions.get (curAction);
357
                f = FileUtil.getConfigFile(path);
271
                if (keysForAction == null) {
358
            } else {
272
                    keysForAction = Collections.synchronizedList (new ArrayList<KeyStroke> (1));
359
                try {
273
                    localActions.put (curAction, keysForAction);
360
                    DataObject d = DataObject.find(f);
361
                    if (d instanceof DataShadow) {
362
                        f = ((DataShadow) d).getOriginal().getPrimaryFile();
363
                    }
364
                } catch (DataObjectNotFoundException x) {
365
                    LOG.log(Level.INFO, f.getPath(), x);
274
                }
366
                }
275
                keysForAction.add (curKey);
276
            }
367
            }
277
        }
368
        }
278
369
        // Cannot actually load instanceCreate methodvalue=... attribute; just want to see if it is there.
279
        return localActions;
370
        if (f.hasExt("instance") && !Collections.list(f.getAttributes()).contains("instanceCreate")) {
371
            String clazz = (String) f.getAttribute("instanceClass");
372
            if (clazz != null) {
373
                return clazz;
374
            } else {
375
                return f.getName().replace('-', '.');
376
            }
377
        }
378
        return f.getPath();
280
    }
379
    }
281
380
282
    public synchronized boolean isLocallyDefined(KeyStroke key) {
381
    public synchronized boolean isLocallyDefined(KeyStroke key) {
283
        LOG.log(Level.FINE, "isLocallyDefined {0}", key);
382
        assert false;
284
        return bindings.containsKey(key);
383
        return false;
285
    }
384
    }
286
385
287
    /** Updates action accelerator. */
288
    private void updateActionAccelerator(final Action a) {
289
        if(a == null) {
290
            return;
291
        }
292
        
293
        Mutex.EVENT.writeAccess(new Runnable() {
294
            public void run() {
295
                KeyStroke[] keystrokes = getKeyStrokesForAction(a);
296
                Arrays.sort (keystrokes, NbKeymap.this);
297
                a.putValue(Action.ACCELERATOR_KEY, keystrokes.length > 0 ? keystrokes[0] : null);
298
            }
299
        });
300
    }
301
    
302
    public int compare(KeyStroke k1, KeyStroke k2) {
386
    public int compare(KeyStroke k1, KeyStroke k2) {
303
        //#47024 and 32733 - "Find" should not be shown as an accelerator,
387
        //#47024 and 32733 - "Find" should not be shown as an accelerator,
304
        //nor should "Backspace" for Delete.  Solution:  The shorter text wins.
388
        //nor should "Backspace" for Delete.  Solution:  The shorter text wins.
Lines 308-395 Link Here
308
    
392
    
309
    
393
    
310
    public void addActionForKeyStroke(KeyStroke key, Action a) {
394
    public void addActionForKeyStroke(KeyStroke key, Action a) {
311
        LOG.log(Level.FINE, "addActionForKeyStroke {0} => {1}", new Object[] { key, id(a) });
395
        assert false;
312
        // Update reverse binding for old action too (#30455):
313
        Action old;
314
        synchronized (this) {
315
            old = bindings.put(key, a);
316
            actions = null;
317
        }
318
        
319
        updateActionAccelerator(a);
320
        updateActionAccelerator(old);
321
        setChanged();
322
        notifyObservers();
323
    }
324
325
    void addActionForKeyStrokeMap(Map<KeyStroke,Action> map) {
326
        Set<Action> actionsSet = new HashSet<Action>();
327
        synchronized (this) {
328
            for (Entry<KeyStroke,Action> entry: map.entrySet ()) {
329
                KeyStroke key = entry.getKey();
330
                Action value = entry.getValue();
331
                // Add both old and new action:
332
                actionsSet.add(value);
333
                actionsSet.add(bindings.put(key, value));
334
            }
335
            actions = null;
336
        }
337
        
338
        for(Action a: actionsSet) {
339
            updateActionAccelerator(a);
340
        }
341
        
342
        setChanged();
343
        notifyObservers();
344
    }
396
    }
345
397
346
    public void removeKeyStrokeBinding(KeyStroke key) {
398
    public void removeKeyStrokeBinding(KeyStroke key) {
347
        LOG.log(Level.FINE, "removeKeyStrokeBinding {0}", key);
399
        assert false;
348
349
        Action a;
350
        synchronized (this) {
351
            a = bindings.remove(key);
352
            actions = null;
353
        }
354
        updateActionAccelerator(a);
355
        setChanged();
356
        notifyObservers();
357
    }
400
    }
358
401
359
    public void removeBindings() {
402
    public void removeBindings() {
360
        LOG.log(Level.FINE, "removeBindings");
403
        assert false;
361
362
        Set<Action> actionsSet;
363
        synchronized (this) {
364
            actionsSet = new HashSet<Action>(bindings.values());
365
            bindings.clear();
366
            actions = null;
367
        }
368
        
369
        for(Action a: actionsSet) {
370
            updateActionAccelerator(a);
371
        }
372
        
373
        setChanged();
374
        notifyObservers();
375
    }
404
    }
376
405
377
    public Keymap getResolveParent() {
406
    public Keymap getResolveParent() {
378
        return parent;
407
        return null;
379
    }
408
    }
380
409
381
    public void setResolveParent(Keymap parent) {
410
    public void setResolveParent(Keymap parent) {
382
        LOG.log(Level.FINE, "setResolveParent {0}", parent == null ? null : parent.getClass());
411
        throw new UnsupportedOperationException();
383
        this.parent = parent;
384
        setChanged();
385
        notifyObservers();
386
    }
387
388
    /** Returns string representation - can be looong.
389
    */
390
    @Override
391
    public String toString() {
392
        return "Keymap[" + name + "]" + bindings; // NOI18N
393
    }
412
    }
394
413
395
    private static Object id(Action a) {
414
    private static Object id(Action a) {
Lines 399-492 Link Here
399
        return a;
418
        return a;
400
    }
419
    }
401
    
420
    
402
    public static class SubKeymap implements Keymap {
403
        Object hold;
404
        Keymap parent;
405
        Map<KeyStroke, Action> bindings;
406
        Action defaultAction;
407
408
        public SubKeymap(Object hold) {
409
            this.hold = hold;
410
            bindings = new HashMap<KeyStroke, Action>();
411
        }
412
        
413
        public String getName() {
414
            return "name";
415
        }
416
        
417
        public void setResolveParent(Keymap parent) {
418
            this.parent = parent;
419
        }
420
421
        public Keymap getResolveParent() {
422
            return parent;
423
        }
424
425
        public void addActionForKeyStroke(KeyStroke key, Action a) {
426
            bindings.put(key, a);
427
        }
428
429
        public KeyStroke[] getKeyStrokesForAction(Action a) {
430
            return new KeyStroke[0];
431
        }
432
433
        public void setDefaultAction(Action a) {
434
                defaultAction = a;
435
        }
436
437
        public Action getAction(KeyStroke key) {
438
            return bindings.get(key);
439
        }
440
441
        public boolean isLocallyDefined(KeyStroke key) {
442
            return bindings.containsKey(key);
443
        }
444
445
        public void removeKeyStrokeBinding(KeyStroke keys) {
446
            bindings.remove(keys);
447
        }
448
449
        public Action[] getBoundActions() {
450
            synchronized (this) {
451
                return bindings.values().toArray(new Action[0]);
452
            }
453
        }
454
455
        public KeyStroke[] getBoundKeyStrokes() {
456
            synchronized (this) {
457
                return bindings.keySet().toArray(new KeyStroke[0]);
458
            }
459
        }
460
  
461
        public Action getDefaultAction() {
462
            return defaultAction;
463
        }
464
465
        public void removeBindings() {
466
            bindings.clear();
467
        }
468
    
469
    }
470
    
471
    public static class KeymapAction extends AbstractAction {
472
        private Keymap keymap;
473
        private KeyStroke stroke;
474
	
475
        public KeymapAction(Keymap keymap, KeyStroke stroke) {
476
            this.keymap = keymap;
477
            this.stroke = stroke;
478
        }
479
        
480
        public Keymap getSubMap() {
481
            return keymap;
482
        }
483
        
484
        public void actionPerformed(ActionEvent e) {
485
            if (stroke == null) { // NO_ACTION -> reset
486
                resetContext();
487
            } else {
488
                shiftContext(stroke);
489
            }	    
490
        }
491
    }
492
}
421
}
(-)a/o.n.core/src/org/netbeans/core/NonGui.java (-7 lines)
Lines 147-159 Link Here
147
        Splash.getInstance().increment(10);
147
        Splash.getInstance().increment(10);
148
        StartLog.logProgress ("Timer initialized"); // NOI18N
148
        StartLog.logProgress ("Timer initialized"); // NOI18N
149
149
150
        // -----------------------------------------------------------------------------------------------------
151
        // 13. Initialize Shortcuts
152
        ShortcutsFolder.initShortcuts();
153
        Splash.getInstance().increment(1);
154
        StartLog.logProgress ("Shortcuts initialized"); // NOI18N
155
156
157
    // -----------------------------------------------------------------------------------------------------
150
    // -----------------------------------------------------------------------------------------------------
158
    // 14. Open main window
151
    // 14. Open main window
159
        StatusDisplayer.getDefault().setStatusText (NbBundle.getMessage (NonGui.class, "MSG_WindowShowInit"));
152
        StatusDisplayer.getDefault().setStatusText (NbBundle.getMessage (NonGui.class, "MSG_WindowShowInit"));
(-)a/o.n.core/src/org/netbeans/core/ShortcutsFolder.java (-236 lines)
Lines 1-236 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.core;
43
44
import java.io.IOException;
45
import java.util.Collection;
46
import java.util.Enumeration;
47
import java.util.LinkedList;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import javax.swing.Action;
51
import javax.swing.KeyStroke;
52
import javax.swing.text.Keymap;
53
import org.netbeans.core.NbKeymap.KeymapAction;
54
import org.netbeans.core.startup.StartLog;
55
import org.openide.cookies.InstanceCookie;
56
import org.openide.filesystems.FileAttributeEvent;
57
import org.openide.filesystems.FileChangeAdapter;
58
import org.openide.filesystems.FileEvent;
59
import org.openide.filesystems.FileObject;
60
import org.openide.filesystems.FileUtil;
61
import org.openide.loaders.DataFolder;
62
import org.openide.loaders.DataObject;
63
import org.openide.util.Exceptions;
64
import org.openide.util.Lookup;
65
import org.openide.util.RequestProcessor;
66
import org.openide.util.Utilities;
67
68
/**
69
 * Bridge to old layers based system.
70
 *
71
 * @author Jan Jancura
72
 */
73
class ShortcutsFolder {
74
    
75
    private static final String PROFILES_FOLDER = "Keymaps";
76
    private static final String SHORTCUTS_FOLDER = "Shortcuts";
77
    private static final String CURRENT_PROFILE_ATTRIBUTE = "currentKeymap";
78
79
    private static ShortcutsFolder  shortcutsFolder;
80
    private Listener                listener = new Listener ();
81
    private FileObject              profilesFileObject;
82
    private FileObject              shortcutsFileObject;
83
    private FileObject              currentFolder;
84
    private Logger debug = Logger.getLogger(ShortcutsFolder.class.getName ());
85
    private Collection<DataObject> dataObjects;
86
    
87
    
88
    static void initShortcuts () {
89
        StartLog.logStart("initShortcuts");
90
        try {
91
            if (shortcutsFolder != null) return;
92
            shortcutsFolder = new ShortcutsFolder ();
93
        } finally {
94
            StartLog.logEnd("initShortcuts");
95
        }
96
    }
97
    
98
    private ShortcutsFolder () {
99
        try {
100
            FileObject root = FileUtil.getConfigRoot ();
101
            profilesFileObject = root.getFileObject (PROFILES_FOLDER);
102
            if (profilesFileObject == null)
103
                profilesFileObject = root.createFolder (PROFILES_FOLDER);
104
            profilesFileObject.addFileChangeListener (listener);
105
            
106
            shortcutsFileObject = root.getFileObject (SHORTCUTS_FOLDER);
107
            if (shortcutsFileObject == null)
108
                shortcutsFileObject = root.createFolder (SHORTCUTS_FOLDER);
109
            shortcutsFileObject.addFileChangeListener (listener);            
110
        } catch (IOException ex) {
111
            Exceptions.printStackTrace(ex);
112
        }
113
        refresh ();
114
    }
115
    
116
    static void waitFinished () {
117
        shortcutsFolder.listener.task.waitFinished ();
118
    }
119
    
120
    private void refresh () {
121
        
122
        // get keymap and delete old shortcuts
123
        NbKeymap keymap = (NbKeymap) Lookup.getDefault ().lookup (Keymap.class);
124
        keymap.removeBindings ();
125
        dataObjects = new LinkedList<DataObject>();
126
127
        // update main shortcuts
128
        readShortcuts (keymap, shortcutsFileObject);
129
        
130
        // update shortcuts from profile
131
        String keymapName = (String) profilesFileObject.getAttribute
132
            (CURRENT_PROFILE_ATTRIBUTE);
133
        if (keymapName == null || "".equals (keymapName))
134
            keymapName = "NetBeans"; // NOI18N
135
        if (currentFolder != null) 
136
            currentFolder.removeFileChangeListener (listener);
137
        currentFolder = FileUtil.getConfigFile (PROFILES_FOLDER + '/' + keymapName);
138
        if (currentFolder == null) {
139
            try {
140
                currentFolder = profilesFileObject.createFolder(keymapName);
141
            } catch (IOException ioe) {
142
                Exceptions.printStackTrace(ioe);
143
            }
144
        }
145
        if (currentFolder != null) {
146
            readShortcuts (keymap, currentFolder);
147
            // add listener to current profile folder
148
            currentFolder.addFileChangeListener (listener);
149
        }
150
    }
151
    
152
    
153
    private void readShortcuts (NbKeymap keymap, FileObject fileObject) {
154
        debug.fine("\nreadShortcuts " + fileObject);
155
        DataFolder folder = DataFolder.findFolder (fileObject);
156
        Enumeration<DataObject> en = folder.children(false);
157
        while (en.hasMoreElements ()) {
158
            DataObject dataObject = en.nextElement();
159
            if (dataObject instanceof DataFolder) continue;
160
            InstanceCookie ic = dataObject.getCookie(InstanceCookie.class);
161
            if (ic == null) continue;
162
            try {
163
                Action action = (Action) ic.instanceCreate ();
164
                String shortcuts = dataObject.getName ();
165
                debug.fine("  " + shortcuts + " : " + action);
166
                KeyStroke[] keyStrokes = Utilities.stringToKeys (shortcuts);
167
                if (keyStrokes != null) {
168
                    addShortcut(keymap, action, keyStrokes);
169
                } else { // see e.g. secondary exception in #74169
170
                    debug.warning("Unrecognized shortcut name from " + dataObject.getPrimaryFile().getPath()); // NOI18N
171
                }
172
                //remember DataObjects used to create the Actions so that there are
173
                //the same Action instances in the menu
174
                dataObjects.add( dataObject );
175
            } catch (ClassNotFoundException x) {
176
                Logger.getLogger(ShortcutsFolder.class.getName()).log(Level.WARNING,
177
                        "{0} ignored; cannot load class {1}",
178
                        new Object[] {dataObject.getPrimaryFile().getPath(), ic.instanceName()});
179
            } catch (Exception ex) {
180
                Logger.getLogger(ShortcutsFolder.class.getName()).log(Level.WARNING, null, ex);
181
            }
182
        }
183
    }
184
        
185
    private static void addShortcut (
186
        NbKeymap keymap, 
187
        Action action, 
188
        KeyStroke[] keyStrokes
189
    ) {
190
        Keymap currentKeymap = keymap;
191
        int i, k = keyStrokes.length - 1;
192
        for (i = 0; i < k; i++) {
193
            Action a = currentKeymap.getAction (keyStrokes [i]);
194
            if (a == null) {
195
                a = keymap.createMapAction 
196
                    (new NbKeymap.SubKeymap (null), keyStrokes [i]);
197
                currentKeymap.addActionForKeyStroke (keyStrokes [i], a);
198
            }
199
            if (!(a instanceof KeymapAction)) return;
200
            currentKeymap = ((KeymapAction) a).getSubMap ();
201
        }
202
        currentKeymap.addActionForKeyStroke (keyStrokes [k], action);
203
    }
204
    
205
    private class Listener extends FileChangeAdapter implements Runnable {
206
        
207
        private RequestProcessor.Task task = new RequestProcessor ("ShortcutsFolder").create (this);
208
        
209
        public void run () {
210
            refresh ();
211
        }
212
        
213
        @Override
214
        public void fileDataCreated (FileEvent fe) {
215
            task.schedule (500);
216
        }
217
218
        @Override
219
        public void fileChanged (FileEvent fe) {
220
            task.schedule (500);
221
        }
222
223
        @Override
224
        public void fileDeleted (FileEvent fe) {
225
            task.schedule (500);
226
        }
227
        
228
        @Override
229
        public void fileAttributeChanged (FileAttributeEvent fe) {
230
            if (fe.getName () != null &&
231
                !CURRENT_PROFILE_ATTRIBUTE.equals (fe.getName ())
232
            ) return;
233
            task.schedule (500);
234
        }
235
    }
236
}
(-)a/o.n.core/test/unit/src/org/netbeans/core/NbKeymapTest.java (-150 / +213 lines)
Lines 42-224 Link Here
42
package org.netbeans.core;
42
package org.netbeans.core;
43
43
44
import java.awt.event.ActionEvent;
44
import java.awt.event.ActionEvent;
45
import java.beans.PropertyChangeEvent;
45
import java.awt.event.KeyEvent;
46
import java.beans.PropertyChangeListener;
46
import java.io.IOException;
47
import java.io.OutputStream;
47
import java.util.Arrays;
48
import java.util.Arrays;
48
import java.util.Collections;
49
import java.util.Collections;
49
import java.util.HashMap;
50
import java.util.concurrent.atomic.AtomicReference;
50
import java.util.Map;
51
import java.util.Observable;
52
import java.util.Observer;
53
import javax.swing.AbstractAction;
51
import javax.swing.AbstractAction;
54
import javax.swing.Action;
52
import javax.swing.Action;
55
import javax.swing.KeyStroke;
53
import javax.swing.KeyStroke;
56
import javax.swing.text.Keymap;
54
import javax.swing.text.Keymap;
55
import org.netbeans.junit.MockServices;
57
import org.netbeans.junit.NbTestCase;
56
import org.netbeans.junit.NbTestCase;
57
import org.openide.filesystems.FileObject;
58
import org.openide.filesystems.FileUtil;
59
import org.openide.loaders.DataFolder;
60
import org.openide.loaders.DataObject;
61
import org.openide.loaders.DataShadow;
62
import org.openide.loaders.Environment;
63
import org.openide.loaders.InstanceSupport;
64
import org.openide.loaders.XMLDataObject;
65
import org.openide.util.HelpCtx;
66
import org.openide.util.Lookup;
67
import org.openide.util.actions.CallableSystemAction;
68
import org.openide.util.lookup.Lookups;
69
import org.w3c.dom.Document;
58
70
59
/** Test NbKeymap.
60
 * @author Jesse Glick
61
 * @see "#30455" */
62
public class NbKeymapTest extends NbTestCase {
71
public class NbKeymapTest extends NbTestCase {
63
    public NbKeymapTest(String name) {
72
    public NbKeymapTest(String name) {
64
        super(name);
73
        super(name);
65
    }
74
    }
66
    
75
67
    @Override
76
    static {
68
    protected boolean runInEQ () {
77
        System.setProperty("os.name", "Linux"); // just to standardize modifier key binding
69
        return true;
78
    }
79
80
    protected @Override void setUp() throws Exception {
81
        for (FileObject f : FileUtil.getConfigRoot().getChildren()) {
82
            f.delete();
83
        }
70
    }
84
    }
71
    
85
    
72
    public void testBasicFunctionality() throws Exception {
86
    private FileObject make(String path) throws IOException {
73
        Keymap km = new NbKeymap();
87
        return FileUtil.createData(FileUtil.getConfigRoot(), path);
74
        Action a1 = new DummyAction("a1");
75
        Action a2 = new DummyAction("a2");
76
        Action d = new DummyAction("d");
77
        KeyStroke k1 = KeyStroke.getKeyStroke("X");
78
        KeyStroke k2 = KeyStroke.getKeyStroke("Y");
79
        assertFalse(k1.equals(k2));
80
        assertNull(km.getAction(k1));
81
        assertNull(km.getAction(k2));
82
        assertEquals(Collections.EMPTY_LIST, Arrays.asList(km.getBoundActions()));
83
        assertEquals(Collections.EMPTY_LIST, Arrays.asList(km.getBoundKeyStrokes()));
84
        assertNull(km.getDefaultAction());
85
        km.setDefaultAction(d);
86
        assertEquals(d, km.getDefaultAction());
87
        km.addActionForKeyStroke(k1, a1);
88
        assertEquals(a1, km.getAction(k1));
89
        assertTrue(km.isLocallyDefined(k1));
90
        assertEquals(null, km.getAction(k2));
91
        assertEquals(Collections.singletonList(a1), Arrays.asList(km.getBoundActions()));
92
        assertEquals(Collections.singletonList(k1), Arrays.asList(km.getBoundKeyStrokes()));
93
        km.addActionForKeyStroke(k2, a2);
94
        assertEquals(a1, km.getAction(k1));
95
        assertEquals(a2, km.getAction(k2));
96
        assertEquals(2, km.getBoundActions().length);
97
        assertEquals(2, km.getBoundKeyStrokes().length);
98
        km.addActionForKeyStroke(k1, d);
99
        assertEquals(d, km.getAction(k1));
100
        assertEquals(a2, km.getAction(k2));
101
        assertEquals(2, km.getBoundActions().length);
102
        assertEquals(2, km.getBoundKeyStrokes().length);
103
        assertEquals(Collections.EMPTY_LIST, Arrays.asList(km.getKeyStrokesForAction(a1)));
104
        assertEquals(Collections.singletonList(k2), Arrays.asList(km.getKeyStrokesForAction(a2)));
105
        assertEquals(Collections.singletonList(k1), Arrays.asList(km.getKeyStrokesForAction(d)));
106
        km.removeKeyStrokeBinding(k2);
107
        assertEquals(d, km.getAction(k1));
108
        assertNull(km.getAction(k2));
109
        assertEquals(Collections.singletonList(d), Arrays.asList(km.getBoundActions()));
110
        assertEquals(Collections.singletonList(k1), Arrays.asList(km.getBoundKeyStrokes()));
111
        km.removeBindings();
112
        assertNull(km.getAction(k1));
113
        assertNull(km.getAction(k2));
114
        assertEquals(Collections.EMPTY_LIST, Arrays.asList(km.getBoundActions()));
115
        assertEquals(Collections.EMPTY_LIST, Arrays.asList(km.getBoundKeyStrokes()));
116
    }
88
    }
117
    
89
118
    public void testObservability() throws Exception {
90
    private FileObject makeFolder(String path) throws IOException {
119
        NbKeymap km = new NbKeymap();
91
        return FileUtil.createFolder(FileUtil.getConfigRoot(), path);
120
        O o = new O();
92
    }
121
        km.addObserver(o);
93
122
        assertFalse(o.changed);
94
    private void assertMapping(Keymap km, KeyStroke stroke, FileObject presenterDefinition, String actionName) throws Exception {
123
        Action a1 = new DummyAction("a1");
95
        Action a = km.getAction(stroke);
124
        Action a2 = new DummyAction("a2");
96
        assertNotNull("for " + stroke, a);
125
        KeyStroke k1 = KeyStroke.getKeyStroke("X");
97
        assertEquals(actionName, a.getValue(Action.NAME));
126
        km.addActionForKeyStroke(k1, a1);
98
        a.putValue("definingFile", presenterDefinition);
127
        assertTrue(o.changed);
99
        assertEquals("for " + stroke + " from " + presenterDefinition.getPath(),
128
        o.changed = false;
100
                Collections.singletonList(stroke), Arrays.asList(km.getKeyStrokesForAction(a)));
129
        km.addActionForKeyStroke(k1, a2);
130
        assertTrue(o.changed);
131
        o.changed = false;
132
        km.removeKeyStrokeBinding(k1);
133
        assertTrue(o.changed);
134
    }
101
    }
135
    
102
    
136
    public void testAcceleratorMapping() throws Exception {
103
    public void testAcceleratorMapping() throws Exception {
104
        FileObject def1 = make("Actions/DummyAction1.instance");
105
        def1.setAttribute("instanceCreate", new DummyAction("one"));
106
        FileObject def2 = make("Actions/DummyAction2.instance");
107
        def2.setAttribute("instanceCreate", new DummyAction("two"));
108
        FileObject def3 = make("Actions/DummySystemAction1.instance");
109
        def3.setAttribute("instanceClass", DummySystemAction1.class.getName());
110
        FileObject def4 = make("Actions/" + DummySystemAction2.class.getName().replace('.', '-') + ".instance");
111
        DataFolder shortcuts = DataFolder.findFolder(makeFolder("Shortcuts"));
112
        DataShadow.create(shortcuts, "1", DataObject.find(def1)).getPrimaryFile();
113
        DataShadow.create(shortcuts, "2", DataObject.find(def2)).getPrimaryFile();
114
        DataShadow.create(shortcuts, "3", DataObject.find(def3)).getPrimaryFile();
115
        DataShadow.create(shortcuts, "C-4", DataObject.find(def4)).getPrimaryFile();
116
        DataFolder menu = DataFolder.findFolder(makeFolder("Menu/Tools"));
117
        FileObject menuitem1 = DataShadow.create(menu, "whatever1", DataObject.find(def1)).getPrimaryFile();
118
        FileObject menuitem2 = DataShadow.create(menu, "whatever2", DataObject.find(def2)).getPrimaryFile();
119
        FileObject menuitem3 = DataShadow.create(menu, "whatever3", DataObject.find(def3)).getPrimaryFile();
120
        FileObject menuitem4 = DataShadow.create(menu, "whatever4", DataObject.find(def4)).getPrimaryFile();
137
        Keymap km = new NbKeymap();
121
        Keymap km = new NbKeymap();
138
        Action a1 = new DummyAction("a1");
122
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_1, 0), menuitem1, "one");
139
        Action a2 = new DummyAction("a2");
123
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_2, 0), menuitem2, "two");
140
        KeyStroke k1 = KeyStroke.getKeyStroke("X");
124
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_3, 0), menuitem3, "DummySystemAction1");
141
        KeyStroke k2 = KeyStroke.getKeyStroke("Y");
125
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_4, KeyEvent.CTRL_MASK), menuitem4, "DummySystemAction2");
142
        assertNull(a1.getValue(Action.ACCELERATOR_KEY));
126
    }
143
        assertNull(a2.getValue(Action.ACCELERATOR_KEY));
127
144
        AccL l = new AccL();
128
    public void testMultipleAcceleratorMapping() throws Exception {
145
        a1.addPropertyChangeListener(l);
129
        FileObject def = make("Actions/paste.instance");
146
        assertFalse(l.changed);
130
        def.setAttribute("instanceCreate", new DummyAction("paste"));
147
        km.addActionForKeyStroke(k1, a1);
131
        DataFolder shortcuts = DataFolder.findFolder(makeFolder("Shortcuts"));
148
        assertEquals(k1, a1.getValue(Action.ACCELERATOR_KEY));
132
        DataShadow.create(shortcuts, "C-V", DataObject.find(def)).getPrimaryFile();
149
        assertTrue(l.changed);
133
        DataShadow.create(shortcuts, "PASTE", DataObject.find(def)).getPrimaryFile();
150
        l.changed = false;
134
        Keymap km = new NbKeymap();
151
        km.addActionForKeyStroke(k2, a2);
135
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK), def, "paste");
152
        assertEquals(k2, a2.getValue(Action.ACCELERATOR_KEY));
153
        km.addActionForKeyStroke(k2, a1);
154
        Object acc = a1.getValue(Action.ACCELERATOR_KEY);
155
        assertTrue(acc == k1 || acc == k2);
156
        assertNull(a2.getValue(Action.ACCELERATOR_KEY));
157
        km.removeKeyStrokeBinding(k1);
158
        assertEquals(k2, a1.getValue(Action.ACCELERATOR_KEY));
159
        km.removeKeyStrokeBinding(k2);
160
        assertNull(a1.getValue(Action.ACCELERATOR_KEY));
161
        assertTrue(l.changed);
162
    }
136
    }
163
    
137
    
164
    public void testAddActionForKeyStrokeMap() throws Exception {
138
    public void testUnusualInstanceFileExtensions() throws Exception {
165
        NbKeymap km = new NbKeymap();
139
        MockServices.setServices(ENV.class);
166
        O o = new O();
140
        FileObject inst = make("Shortcuts/C-F11.xml");
167
        km.addObserver(o);
141
        OutputStream os = inst.getOutputStream();
168
        Action a1 = new DummyAction("a1");
142
        os.write("<action/>".getBytes());
169
        Action a2 = new DummyAction("a2");
143
        os.close();
170
        Action a3 = new DummyAction("a3");
144
        assertMapping(new NbKeymap(), KeyStroke.getKeyStroke(KeyEvent.VK_F11, KeyEvent.CTRL_MASK), inst, "whatever");
171
        KeyStroke k1 = KeyStroke.getKeyStroke("X");
145
    }
172
        KeyStroke k2 = KeyStroke.getKeyStroke("Y");
146
173
        Map<KeyStroke,Action> m = new HashMap<KeyStroke,Action>();
147
    public void testAbstractModifiers() throws Exception {
174
        m.put(k1, a1);
148
        Keymap km = new NbKeymap();
175
        m.put(k2, a2);
149
        FileObject inst1 = make("Shortcuts/D-1.instance");
176
        km.addActionForKeyStrokeMap(m);
150
        inst1.setAttribute("instanceCreate", new DummyAction("one"));
177
        assertTrue(o.changed);
151
        FileObject inst2 = make("Shortcuts/O-1.instance");
178
        assertEquals(a1, km.getAction(k1));
152
        inst2.setAttribute("instanceCreate", new DummyAction("two"));
179
        assertEquals(a2, km.getAction(k2));
153
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_1, KeyEvent.CTRL_MASK), inst1, "one");
180
        assertEquals(k1, a1.getValue(Action.ACCELERATOR_KEY));
154
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_1, KeyEvent.ALT_MASK), inst2, "two");
181
        assertEquals(k2, a2.getValue(Action.ACCELERATOR_KEY));
155
    }
182
        assertEquals(2, km.getBoundActions().length);
156
183
        assertEquals(2, km.getBoundKeyStrokes().length);
157
    public void testDifferentKeymaps() throws Exception {
184
        km.removeBindings();
158
        make("Shortcuts/C-A.instance").setAttribute("instanceCreate", new DummyAction("one"));
185
        km.addActionForKeyStroke(k1, a3);
159
        make("Keymaps/NetBeans/C-A.instance").setAttribute("instanceCreate", new DummyAction("two"));
186
        km.addActionForKeyStrokeMap(m);
160
        make("Keymaps/Eclipse/C-A.instance").setAttribute("instanceCreate", new DummyAction("three"));
187
        assertEquals(a1, km.getAction(k1));
161
        Keymap km = new NbKeymap();
188
        assertEquals(a2, km.getAction(k2));
162
        KeyStroke controlA = KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK);
189
        assertEquals(k1, a1.getValue(Action.ACCELERATOR_KEY));
163
        assertEquals("two", km.getAction(controlA).getValue(Action.NAME));
190
        assertEquals(k2, a2.getValue(Action.ACCELERATOR_KEY));
164
        FileUtil.getConfigFile("Keymaps").setAttribute("currentKeymap", "Eclipse");
191
        assertNull(a3.getValue(Action.ACCELERATOR_KEY));
165
        assertEquals("three", km.getAction(controlA).getValue(Action.NAME));
192
        assertEquals(2, km.getBoundActions().length);
166
        FileUtil.getConfigFile("Keymaps").setAttribute("currentKeymap", "IDEA");
193
        assertEquals(2, km.getBoundKeyStrokes().length);
167
        assertEquals("one", km.getAction(controlA).getValue(Action.NAME));
168
    }
169
170
    public void testChangeOfAcceleratorFromKeymap() throws Exception {
171
        Action a = new DummyAction("one");
172
        FileObject def = make("Actions/one.instance");
173
        def.setAttribute("instanceCreate", a);
174
        DataShadow.create(DataFolder.findFolder(makeFolder("Keymaps/NetBeans")), "C-A", DataObject.find(def));
175
        DataShadow.create(DataFolder.findFolder(makeFolder("Keymaps/Eclipse")), "C-B", DataObject.find(def));
176
        Keymap km = new NbKeymap();
177
        assertMapping(km, KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK), def, "one");
178
        FileUtil.getConfigFile("Keymaps").setAttribute("currentKeymap", "Eclipse");
179
        // Any actions ever passed to getKeyStrokesForAction should get updated when keymap changes:
180
        assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK), a.getValue(Action.ACCELERATOR_KEY));
181
    }
182
183
    public void testMultiKeyShortcuts() throws Exception {
184
        final AtomicReference<String> ran = new AtomicReference<String>();
185
        class A extends AbstractAction {
186
            final String s;
187
            A(String s) {
188
                this.s = s;
189
            }
190
            public void actionPerformed(ActionEvent e) {
191
                ran.set(s);
192
            }
193
        }
194
        make("Shortcuts/C-X 1.instance").setAttribute("instanceCreate", new A("C-X 1"));
195
        make("Shortcuts/C-X 2.instance").setAttribute("instanceCreate", new A("C-X 2"));
196
        make("Shortcuts/C-U A B.instance").setAttribute("instanceCreate", new A("C-U A B"));
197
        Keymap km = new NbKeymap();
198
        Action a = km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK));
199
        assertNotNull(a);
200
        a.actionPerformed(null);
201
        assertEquals(null, ran.getAndSet(null));
202
        a = km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_1, 0));
203
        assertNotNull(a);
204
        a.actionPerformed(null);
205
        assertEquals("C-X 1", ran.getAndSet(null));
206
        a = km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK));
207
        assertNotNull(a);
208
        a.actionPerformed(null);
209
        assertEquals(null, ran.getAndSet(null));
210
        a = km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_2, 0));
211
        assertNotNull(a);
212
        a.actionPerformed(null);
213
        assertEquals("C-X 2", ran.getAndSet(null));
214
        a = km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_U, KeyEvent.CTRL_MASK));
215
        assertNotNull(a);
216
        a.actionPerformed(null);
217
        assertEquals(null, ran.getAndSet(null));
218
        a = km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0));
219
        assertNotNull(a);
220
        a.actionPerformed(null);
221
        assertEquals(null, ran.getAndSet(null));
222
        a = km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0));
223
        assertNotNull(a);
224
        a.actionPerformed(null);
225
        assertEquals("C-U A B", ran.getAndSet(null));
226
    }
227
228
    public void testChangesInShortcutRegistrations() throws Exception {
229
        make("Shortcuts/C-A.instance").setAttribute("instanceCreate", new DummyAction("one"));
230
        make("Keymaps/NetBeans/C-B.instance").setAttribute("instanceCreate", new DummyAction("two"));
231
        Keymap km = new NbKeymap();
232
        assertEquals("one", km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK)).getValue(Action.NAME));
233
        assertEquals("two", km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK)).getValue(Action.NAME));
234
        assertEquals(null, km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK)));
235
        FileUtil.getConfigFile("Shortcuts/C-A.instance").delete();
236
        assertEquals(null, km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK)));
237
        make("Shortcuts/C-C.instance").setAttribute("instanceCreate", new DummyAction("three"));
238
        assertEquals("three", km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK)).getValue(Action.NAME));
239
        make("Keymaps/NetBeans/C-C.instance").setAttribute("instanceCreate", new DummyAction("four"));
240
        assertEquals("four", km.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK)).getValue(Action.NAME));
241
    }
242
243
    private static final class DummyAction extends AbstractAction {
244
        public DummyAction(String name) {
245
            super(name);
246
        }
247
        public void actionPerformed(ActionEvent e) {}
194
    }
248
    }
195
    
249
    
196
    private static final class DummyAction extends AbstractAction {
250
    public static class ENV extends Object implements Environment.Provider {
197
        private final String name;
251
        public Lookup getEnvironment(DataObject obj) {
198
        public DummyAction(String name) {
252
            if (obj instanceof XMLDataObject) {
199
            this.name = name;
253
                try {
200
        }
254
                    Document doc = ((XMLDataObject) obj).getDocument();
201
        public void actionPerformed(ActionEvent e) {}
255
                    if (doc.getDocumentElement().getNodeName().equals("action")) {
202
        @Override
256
                        return Lookups.singleton(new InstanceSupport.Instance(new DummyAction("whatever")));
203
        public String toString() {
257
                    }
204
            return "DummyAction[" + name + "]";
258
                } catch (Exception ex) {
259
                    ex.printStackTrace();
260
                    fail("No exception: " + ex.getMessage());
261
                }
262
            }
263
            return Lookup.EMPTY;
205
        }
264
        }
206
    }
265
    }
207
    
266
208
    private static final class O implements Observer {
267
    public static final class DummySystemAction1 extends CallableSystemAction {
209
        public boolean changed = false;
268
        public void performAction() {}
210
        public void update(Observable o, Object arg) {
269
        public String getName() {
211
            changed = true;
270
            return "DummySystemAction1";
271
        }
272
        public HelpCtx getHelpCtx() {
273
            return null;
212
        }
274
        }
213
    }
275
    }
214
    
276
215
    private static final class AccL implements PropertyChangeListener {
277
    public static final class DummySystemAction2 extends CallableSystemAction {
216
        public boolean changed = false;
278
        public void performAction() {}
217
        public void propertyChange(PropertyChangeEvent evt) {
279
        public String getName() {
218
            if (Action.ACCELERATOR_KEY.equals(evt.getPropertyName())) {
280
            return "DummySystemAction2";
219
                changed = true;
281
        }
220
            }
282
        public HelpCtx getHelpCtx() {
283
            return null;
221
        }
284
        }
222
    }
285
    }
223
    
286
224
}
287
}
(-)a/o.n.core/test/unit/src/org/netbeans/core/ShortcutsFolder66845Test.java (-79 lines)
Lines 1-79 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
42
package org.netbeans.core;
43
44
import java.util.logging.Level;
45
import javax.swing.text.Keymap;
46
import org.netbeans.core.startup.Main;
47
import org.netbeans.junit.Log;
48
import org.netbeans.junit.NbTestCase;
49
import org.openide.filesystems.FileUtil;
50
import org.openide.util.Lookup;
51
52
public class ShortcutsFolder66845Test extends NbTestCase {
53
54
    public ShortcutsFolder66845Test(String s) {
55
        super(s);
56
    }
57
58
    private Keymap keymap;
59
    private CharSequence logs;
60
61
    @Override
62
    protected void setUp() throws Exception {
63
        Main.initializeURLFactory ();
64
        keymap = Lookup.getDefault().lookup(Keymap.class);
65
        assertNotNull("There is a keymap", keymap);
66
        assertEquals("of correct type", NbKeymap.class, keymap.getClass());
67
        ShortcutsFolder.initShortcuts ();
68
        logs = Log.enable(ShortcutsFolder.class.getName(), Level.WARNING);
69
    }
70
71
    public void testLogging() throws Exception {
72
        FileUtil.createData(FileUtil.getConfigRoot(), "Keymaps/NetBeans/org-nb-Neznam.instance");
73
74
        ShortcutsFolder.waitFinished ();
75
76
        assertTrue("got message in " + logs, logs.toString().contains("Neznam"));
77
    }
78
79
}
(-)a/o.n.core/test/unit/src/org/netbeans/core/ShortcutsFolderTest.java (-206 lines)
Lines 1-206 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
package org.netbeans.core;
42
43
import java.awt.Toolkit;
44
import java.awt.event.ActionEvent;
45
import java.awt.event.KeyEvent;
46
import java.io.IOException;
47
import java.lang.ref.Reference;
48
import java.lang.ref.WeakReference;
49
import java.util.Arrays;
50
import java.util.Collections;
51
import java.util.logging.Level;
52
import javax.swing.AbstractAction;
53
import javax.swing.Action;
54
import javax.swing.KeyStroke;
55
import javax.swing.text.Keymap;
56
import org.netbeans.core.startup.Main;
57
import org.netbeans.junit.*;
58
import org.openide.ErrorManager;
59
import org.openide.cookies.InstanceCookie;
60
import org.openide.filesystems.FileLock;
61
import org.openide.filesystems.FileObject;
62
import org.openide.filesystems.FileSystem;
63
import org.openide.filesystems.FileUtil;
64
import org.openide.loaders.DataObject;
65
import org.openide.loaders.DataShadow;
66
import org.openide.loaders.Environment;
67
import org.openide.loaders.InstanceSupport;
68
import org.openide.loaders.XMLDataObject;
69
import org.openide.util.Lookup;
70
import org.openide.util.lookup.Lookups;
71
import org.w3c.dom.Document;
72
73
public class ShortcutsFolderTest extends NbTestCase {
74
    private ErrorManager err;
75
    private Keymap keymap;
76
    
77
    /** Constructor required by JUnit.
78
     * @param testName method name to be used as testcase
79
     */
80
    public ShortcutsFolderTest(String s) {
81
        super(s);
82
    }
83
    
84
    @Override
85
    protected Level logLevel() {
86
        return Level.ALL;
87
    }
88
    
89
    @Override
90
    protected void setUp() throws Exception {
91
        MockServices.setServices(ENV.class);
92
93
        Main.initializeURLFactory ();
94
        keymap = Lookup.getDefault().lookup(Keymap.class);
95
        
96
        assertNotNull("There is a keymap", keymap);
97
        ShortcutsFolder.initShortcuts ();
98
        
99
        err = ErrorManager.getDefault().getInstance("TEST-" + getName());
100
    }
101
    
102
    public void testApplyChangeToFactoryActionIssue49597 () throws Exception {
103
        final FileObject shortcuts = FileUtil.getConfigFile ("Shortcuts");
104
        FileObject inst = FileUtil.createData (FileUtil.getConfigRoot (), "Actions/Tools/TestAction.instance");
105
        TestAction action = new TestAction ();
106
        inst.setAttribute ("instanceCreate", action);
107
        
108
        Reference<?> ref = new WeakReference<Object>(inst);
109
        inst = null;
110
        assertGC ("File can disappear", ref);
111
112
//        ShortcutsFolder.waitFinished ();
113
114
        assertEquals ("Nothing registered", Collections.EMPTY_LIST, Arrays.asList (keymap.getBoundActions ()));
115
        
116
        final KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_F9, KeyEvent.ALT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
117
118
        class R implements FileSystem.AtomicAction {
119
            FileObject inst2;
120
            
121
            public void run() throws IOException {
122
                inst2 = FileUtil.createData (FileUtil.getConfigRoot (), "/Shortcuts/CA-F9.shadow");
123
                inst2.setAttribute ("originalFile", "/Actions/Tools/TestAction.instance");
124
            }
125
        }
126
        R run = new R();
127
        FileUtil.runAtomicAction(run);
128
129
        ShortcutsFolder.waitFinished ();
130
        err.log("ShortcutsFolder.waitFinished");
131
132
        FileObject[] arr = shortcuts.getChildren ();
133
        err.log("children are here");
134
        
135
        assertEquals ("One element is there", 1, arr.length);
136
        DataObject obj = DataObject.find (arr[0]);
137
        err.log("Object is here" + obj);
138
        
139
        assertEquals("It is DataShadow", DataShadow.class, obj.getClass());
140
141
        Object a = keymap.getAction (stroke);
142
        assertNotNull ("There is an action", a);
143
        assertEquals ("It is test action", TestAction.class, a.getClass ());
144
    }
145
146
    @RandomlyFails
147
    public void testShortcutsForDifferentFilesThanInstanceOrShadows () throws Exception {
148
        FileObject inst = FileUtil.createData (FileUtil.getConfigRoot (), "Shortcuts/C-F11.xml");
149
150
        FileLock lock = inst.lock ();
151
        java.io.PrintStream ps = new java.io.PrintStream (inst.getOutputStream (lock));
152
        ps.println ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
153
        ps.println ("<project default=\"run\" name=\"Build everything.\">");
154
        ps.println ("<target name=\"run\">");
155
        ps.println ("<ant antfile=\"SampleProject.xml\" inheritall=\"false\" target=\"all\"/>");
156
        ps.println ("</target>");
157
        ps.println ("</project>");
158
        ps.close();
159
        lock.releaseLock ();
160
        
161
        DataObject obj = DataObject.find (inst);
162
        assertEquals ("XML Data object", XMLDataObject.class, obj.getClass());
163
        InstanceCookie ic = obj.getCookie(InstanceCookie.class);
164
        assertNotNull ("Has cookie", ic);
165
166
        final KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_F11, KeyEvent.CTRL_MASK);
167
        
168
        ShortcutsFolder.waitFinished ();
169
170
        Action action = keymap.getAction(stroke);
171
        if (action == null) {
172
            fail("There should be some action for " + stroke + " in:\n" + keymap);
173
        }
174
        
175
        inst.delete ();
176
        ShortcutsFolder.waitFinished ();
177
        action = keymap.getAction (stroke);
178
        assertNull ("Action removed", action);
179
    }
180
    
181
    public static class TestAction extends AbstractAction {
182
        public void actionPerformed (ActionEvent ae) {}
183
    }
184
    
185
    public static class ENV extends Object implements Environment.Provider {
186
        public Lookup getEnvironment(DataObject obj) {
187
            if (obj instanceof XMLDataObject) {
188
                try {
189
                    Document doc = ((XMLDataObject) obj).getDocument();
190
                    if (doc.getDocumentElement().getNodeName().equals ("project")) {
191
                        return Lookups.singleton(
192
                            new InstanceSupport.Instance(
193
                                new TestAction ()
194
                            )
195
                        );
196
                    }
197
                } catch (Exception ex) {
198
                    ex.printStackTrace();
199
                    fail ("No exception: " + ex.getMessage());
200
                }
201
            }
202
            return Lookup.EMPTY;
203
        }
204
    }
205
    
206
}
(-)a/openide.awt/src/org/openide/awt/Actions.java (-66 lines)
Lines 63-77 Link Here
63
import org.openide.util.HelpCtx;
63
import org.openide.util.HelpCtx;
64
import org.openide.util.Lookup;
64
import org.openide.util.Lookup;
65
import org.openide.util.NbBundle;
65
import org.openide.util.NbBundle;
66
import java.lang.ref.WeakReference;
67
import java.util.ArrayList;
66
import java.util.ArrayList;
68
import java.util.HashMap;
67
import java.util.HashMap;
69
import java.util.Hashtable;
68
import java.util.Hashtable;
70
import java.util.Iterator;
71
import java.util.Map;
69
import java.util.Map;
72
import java.util.Observable;
73
import java.util.Observer;
74
import java.util.WeakHashMap;
75
import javax.swing.AbstractButton;
70
import javax.swing.AbstractButton;
76
import javax.swing.Action;
71
import javax.swing.Action;
77
import javax.swing.ActionMap;
72
import javax.swing.ActionMap;
Lines 84-90 Link Here
84
import javax.swing.KeyStroke;
79
import javax.swing.KeyStroke;
85
import javax.swing.event.ChangeEvent;
80
import javax.swing.event.ChangeEvent;
86
import javax.swing.event.ChangeListener;
81
import javax.swing.event.ChangeListener;
87
import javax.swing.text.Keymap;
88
import org.netbeans.api.actions.Closable;
82
import org.netbeans.api.actions.Closable;
89
import org.netbeans.api.actions.Editable;
83
import org.netbeans.api.actions.Editable;
90
import org.netbeans.api.actions.Openable;
84
import org.netbeans.api.actions.Openable;
Lines 202-267 Link Here
202
            ((Actions.MenuItem)item).setBridge(b);
196
            ((Actions.MenuItem)item).setBridge(b);
203
        }
197
        }
204
        b.updateState(null);
198
        b.updateState(null);
205
        if (!popup) {
206
            // #39508 fix.
207
            setMenuActionConnection(item, action);
208
        }
209
    }
210
211
    /**
212
     * #39508 fix. the MenuItems have the accelerator set by the Bridge, however that is not removed when the Menu is invisible.
213
     * it's because of the AddNotify/removeNotify method calls and the Bridge.VisL listener.
214
     * that conflicts with the changes done in the global keymap (NbKeymap) that are not propagated to the menu items when such change occurs.
215
     * Fixed by having one global observer on the NbKeyMap (or any other Keymap impl which is Observable) and always synchronizing the menus with the current
216
     * global Keymap
217
     */
218
    private static void setMenuActionConnection(JMenuItem menu, Action action) {
219
        synchronized (menuActionLock) {
220
            if (menuActionCache == null) {
221
                menuActionCache = new WeakHashMap<Action, Reference<JMenuItem>>();
222
223
                Keymap map = Lookup.getDefault().lookup(Keymap.class);
224
225
                if (map instanceof Observable) {
226
                    //HACK MAJOR - assuming we have the NbKeymap which is observable
227
                    ((Observable) map).addObserver(
228
                        new Observer() {
229
                            public void update(Observable o, Object arg) {
230
                                synchronized (menuActionLock) {
231
                                    Iterator<Map.Entry<Action, Reference<JMenuItem>>> it = menuActionCache.entrySet().iterator();
232
233
                                    while (it.hasNext()) {
234
                                        Map.Entry<Action, Reference<JMenuItem>> entry = it.next();
235
                                        Action act = entry.getKey();
236
                                        Reference<JMenuItem> ref = entry.getValue();
237
                                        JMenuItem mn = ref.get();
238
239
                                        if ((act != null) && (mn != null)) {
240
                                            KeyStroke actKey = (KeyStroke) act.getValue(Action.ACCELERATOR_KEY);
241
                                            KeyStroke mnKey = mn.getAccelerator();
242
243
                                            if (
244
                                                ((mnKey == null) && (actKey != null)) ||
245
                                                    ((mnKey != null) && (actKey == null)) ||
246
                                                    ((mnKey != null) && (actKey != null) && !actKey.equals(mnKey))
247
                                            ) {
248
                                                mn.setAccelerator(actKey);
249
                                            }
250
                                        }
251
                                    }
252
                                }
253
                            }
254
                        }
255
                    );
256
                } else {
257
                    Logger.getLogger(Actions.class.getName()).warning(
258
                        "Keymap is not observable, behaviour described in bug #39508 can reappear."
259
                    );
260
                }
261
            }
262
263
            menuActionCache.put(action, new WeakReference<JMenuItem>(menu));
264
        }
265
    }
199
    }
266
200
267
    /** Attaches checkbox menu item to boolean state action.
201
    /** Attaches checkbox menu item to boolean state action.
(-)a/openide.awt/src/org/openide/awt/AlwaysEnabledAction.java (-12 / +7 lines)
Lines 14-21 Link Here
14
import javax.swing.AbstractAction;
14
import javax.swing.AbstractAction;
15
import javax.swing.Action;
15
import javax.swing.Action;
16
import javax.swing.Icon;
16
import javax.swing.Icon;
17
import javax.swing.KeyStroke;
18
import javax.swing.text.Keymap;
19
import org.netbeans.modules.openide.util.ActionsBridge;
17
import org.netbeans.modules.openide.util.ActionsBridge;
20
import org.netbeans.modules.openide.util.ActionsBridge.ActionRunnable;
18
import org.netbeans.modules.openide.util.ActionsBridge.ActionRunnable;
21
import org.openide.util.ContextAwareAction;
19
import org.openide.util.ContextAwareAction;
Lines 138-145 Link Here
138
                return null;
136
                return null;
139
            }
137
            }
140
        }
138
        }
141
139
        Object o = extractCommonAttribute(map, this, name);
142
        return extractCommonAttribute(map, this, name);
140
        // cf. #137709 JG18:
141
        return o != null ? o : super.getValue(name);
143
    }
142
    }
144
143
145
    static final Object extractCommonAttribute(Map fo, Action action, String name) {
144
    static final Object extractCommonAttribute(Map fo, Action action, String name) {
Lines 185-197 Link Here
185
        if ("noIconInMenu".equals(name)) { // NOI18N
184
        if ("noIconInMenu".equals(name)) { // NOI18N
186
            return fo == null ? null : fo.get("noIconInMenu"); // NOI18N
185
            return fo == null ? null : fo.get("noIconInMenu"); // NOI18N
187
        }
186
        }
188
        if (Action.ACCELERATOR_KEY.equals(name)) {
189
            Keymap map = Lookup.getDefault().lookup(Keymap.class);
190
            if (map != null) {
191
                KeyStroke[] arr = map.getKeyStrokesForAction(action);
192
                return arr.length > 0 ? arr[0] : null;
193
            }
194
        }
195
        // Delegate query to other properties to "fo" ignoring special properties
187
        // Delegate query to other properties to "fo" ignoring special properties
196
        if (!"delegate".equals(name) && !"instanceCreate".equals(name)) {
188
        if (!"delegate".equals(name) && !"instanceCreate".equals(name)) {
197
            return fo.get(name);
189
            return fo.get(name);
Lines 227-233 Link Here
227
        return false;
219
        return false;
228
    }
220
    }
229
221
230
222
    @Override
223
    public String toString() {
224
        return "AlwaysEnabledAction[" + getValue(Action.NAME) + "]"; // NOI18N
225
    }
231
226
232
    public void propertyChange(PropertyChangeEvent evt) {
227
    public void propertyChange(PropertyChangeEvent evt) {
233
        if (evt.getSource() == delegate) {
228
        if (evt.getSource() == delegate) {
(-)a/openide.awt/src/org/openide/awt/GeneralAction.java (-1 / +11 lines)
Lines 45-50 Link Here
45
import java.beans.PropertyChangeEvent;
45
import java.beans.PropertyChangeEvent;
46
import java.beans.PropertyChangeListener;
46
import java.beans.PropertyChangeListener;
47
import java.beans.PropertyChangeSupport;
47
import java.beans.PropertyChangeSupport;
48
import java.util.HashMap;
48
import java.util.Map;
49
import java.util.Map;
49
import java.util.logging.Logger;
50
import java.util.logging.Logger;
50
import javax.swing.Action;
51
import javax.swing.Action;
Lines 245-254 Link Here
245
            }
246
            }
246
        }
247
        }
247
248
248
        public void putValue(String key, Object o) {
249
        private Map<String,Object> attrs;
250
251
        public void putValue(String key, Object value) {
252
            if (attrs == null) {
253
                attrs = new HashMap<String,Object>();
254
            }
255
            attrs.put(key, value);
249
        }
256
        }
250
257
251
        public Object getValue(String key) {
258
        public Object getValue(String key) {
259
            if (attrs != null && attrs.containsKey(key)) {
260
                return attrs.get(key);
261
            }
252
            Object ret = GeneralAction.extractCommonAttribute(map, this, key);
262
            Object ret = GeneralAction.extractCommonAttribute(map, this, key);
253
            if (ret != null) {
263
            if (ret != null) {
254
                return ret;
264
                return ret;
(-)a/openide.awt/test/unit/src/org/openide/awt/ActionsTest.java (-106 / +1 lines)
Lines 44-54 Link Here
44
import java.awt.event.ActionEvent;
44
import java.awt.event.ActionEvent;
45
import java.awt.event.KeyEvent;
45
import java.awt.event.KeyEvent;
46
import java.awt.image.BufferedImage;
46
import java.awt.image.BufferedImage;
47
import java.lang.ref.Reference;
48
import java.lang.ref.WeakReference;
49
import java.util.HashMap;
50
import java.util.Map;
51
import java.util.Observable;
52
import javax.swing.AbstractAction;
47
import javax.swing.AbstractAction;
53
import javax.swing.AbstractButton;
48
import javax.swing.AbstractButton;
54
import javax.swing.Action;
49
import javax.swing.Action;
Lines 58-64 Link Here
58
import javax.swing.JMenu;
53
import javax.swing.JMenu;
59
import javax.swing.JMenuItem;
54
import javax.swing.JMenuItem;
60
import javax.swing.KeyStroke;
55
import javax.swing.KeyStroke;
61
import javax.swing.text.Keymap;
62
import org.netbeans.junit.MockServices;
56
import org.netbeans.junit.MockServices;
63
import org.netbeans.junit.NbTestCase;
57
import org.netbeans.junit.NbTestCase;
64
import org.openide.util.HelpCtx;
58
import org.openide.util.HelpCtx;
Lines 115-122 Link Here
115
    }
109
    }
116
    
110
    
117
    protected void setUp() {
111
    protected void setUp() {
118
        MockServices.setServices(new Class[] {TestKeymap.class, TestConnector.class});
112
        MockServices.setServices(TestConnector.class);
119
        assertNotNull("Keymap has to be in lookup", Lookup.getDefault().lookup(Keymap.class));
120
    }
113
    }
121
    
114
    
122
    /**
115
    /**
Lines 238-274 Link Here
238
    }
231
    }
239
    
232
    
240
    /**
233
    /**
241
     * tests if the accelerator for JMenuItem is reset when the global KeyMap changes.
242
     * Has to work even when the menu is not visible (when visible is handled by Actions.Bridge listeners)
243
     * when not visible handled by the tested Actions.setMenuActionConnection() - only for menu items.
244
     * #39508
245
     */
246
    public void testActionRemoval_Issue39508() throws Exception {
247
        // prepare
248
        Keymap map = (Keymap)Lookup.getDefault().lookup(Keymap.class);
249
        map.removeBindings();
250
        Action action = new ActionsTest.TestAction();
251
        KeyStroke stroke = KeyStroke.getKeyStroke("ctrl alt 7");
252
        assertNotNull(stroke);
253
        //test start
254
        JMenuItem menu = new JMenuItem();
255
        assertNull(menu.getAccelerator());
256
        Actions.connect(menu, action, false);
257
        assertEquals(1, ((Observable)map).countObservers());
258
        assertNull(menu.getAccelerator());
259
        map.addActionForKeyStroke(stroke, action);
260
        assertNotNull(action.getValue(Action.ACCELERATOR_KEY));
261
        assertNotNull(menu.getAccelerator());
262
        map.removeKeyStrokeBinding(stroke);
263
        assertNull(action.getValue(Action.ACCELERATOR_KEY));
264
        assertNull(menu.getAccelerator());
265
        Reference ref = new WeakReference(action);
266
        menu = null;
267
        action = null;
268
        assertGC("action can dissappear", ref);
269
    }
270
    
271
    /**
272
     * Tests if changes in accelerator key or name of the action does not change the tooltip
234
     * Tests if changes in accelerator key or name of the action does not change the tooltip
273
     * of the button if the action has a custom tooltip. See first part of #57974.
235
     * of the button if the action has a custom tooltip. See first part of #57974.
274
     */
236
     */
Lines 523-595 Link Here
523
        
485
        
524
    }
486
    }
525
    
487
    
526
    public static final class TestKeymap extends Observable implements Keymap {
527
        
528
        private Map map = new HashMap();
529
        private Action defAct;
530
        
531
        public void addActionForKeyStroke(KeyStroke key, Action act) {
532
            map.put(key, act);
533
            act.putValue(Action.ACCELERATOR_KEY, key);
534
            setChanged();
535
            notifyObservers();
536
        }
537
        
538
        public Action getAction(KeyStroke key) {
539
            return (Action)map.get(key);
540
        }
541
        
542
        public Action[] getBoundActions() {
543
            return new Action[0];
544
        }
545
        
546
        public KeyStroke[] getBoundKeyStrokes() {
547
            return new KeyStroke[0];
548
        }
549
        
550
        public Action getDefaultAction() {
551
            return defAct;
552
        }
553
        
554
        public KeyStroke[] getKeyStrokesForAction(Action a) {
555
            return new KeyStroke[0];
556
        }
557
        
558
        public String getName() {
559
            return "testKeymap";
560
        }
561
        
562
        public Keymap getResolveParent() {
563
            return null;
564
        }
565
        
566
        public boolean isLocallyDefined(KeyStroke key) {
567
            return true;
568
        }
569
        
570
        public void removeBindings() {
571
            map.clear();
572
        }
573
        
574
        public void removeKeyStrokeBinding(KeyStroke keys) {
575
            Action act = (Action)map.remove(keys);
576
            if (act != null) {
577
                act.putValue(Action.ACCELERATOR_KEY, null);
578
            }
579
            setChanged();
580
            notifyObservers();
581
        }
582
        
583
        public void setDefaultAction(Action a) {
584
            defAct = a;
585
        }
586
        
587
        public void setResolveParent(Keymap parent) {
588
            // ignore
589
        }
590
        
591
    }
592
    
593
    public static final class TestConnector implements Actions.ButtonActionConnector {
488
    public static final class TestConnector implements Actions.ButtonActionConnector {
594
        
489
        
595
        private int called = 0;
490
        private int called = 0;
(-)a/openide.loaders/src/org/openide/awt/DynaMenuModel.java (-2 / +5 lines)
Lines 51-62 Link Here
51
import java.util.Map;
51
import java.util.Map;
52
import javax.swing.Action;
52
import javax.swing.Action;
53
import javax.swing.Icon;
53
import javax.swing.Icon;
54
import javax.swing.ImageIcon;
55
import javax.swing.JComponent;
54
import javax.swing.JComponent;
56
import javax.swing.JMenu;
55
import javax.swing.JMenu;
57
import javax.swing.JMenuItem;
56
import javax.swing.JMenuItem;
58
import javax.swing.JPopupMenu;
57
import javax.swing.JPopupMenu;
59
import javax.swing.JSeparator;
58
import javax.swing.JSeparator;
59
import org.openide.filesystems.FileObject;
60
import org.openide.util.ImageUtilities;
60
import org.openide.util.ImageUtilities;
61
import org.openide.util.Utilities;
61
import org.openide.util.Utilities;
62
import org.openide.util.actions.Presenter;
62
import org.openide.util.actions.Presenter;
Lines 76-82 Link Here
76
        actionToMenuMap = new HashMap<DynamicMenuContent, JComponent[]>();
76
        actionToMenuMap = new HashMap<DynamicMenuContent, JComponent[]>();
77
    }
77
    }
78
    
78
    
79
    public void loadSubmenu(List cInstances, JMenu m) {
79
    public void loadSubmenu(List<Object> cInstances, JMenu m, Map<Object,FileObject> cookiesToFiles) {
80
        // clear first - refresh the menu's content
80
        // clear first - refresh the menu's content
81
        boolean addSeparator = false;
81
        boolean addSeparator = false;
82
        Icon curIcon = null;
82
        Icon curIcon = null;
Lines 85-90 Link Here
85
        actionToMenuMap.clear();
85
        actionToMenuMap.clear();
86
        while (it.hasNext()) {
86
        while (it.hasNext()) {
87
            Object obj = it.next();
87
            Object obj = it.next();
88
            if (obj instanceof Action) {
89
                Toolbar.setAccelerator((Action) obj, cookiesToFiles.get(obj));
90
            }
88
            if (obj instanceof Presenter.Menu) {
91
            if (obj instanceof Presenter.Menu) {
89
                // does this still apply??
92
                // does this still apply??
90
                obj = ((Presenter.Menu)obj).getMenuPresenter();
93
                obj = ((Presenter.Menu)obj).getMenuPresenter();
(-)a/openide.loaders/src/org/openide/awt/MenuBar.java (-1 / +11 lines)
Lines 51-58 Link Here
51
import java.io.ObjectOutput;
51
import java.io.ObjectOutput;
52
import java.util.ArrayList;
52
import java.util.ArrayList;
53
import java.util.Arrays;
53
import java.util.Arrays;
54
import java.util.HashMap;
54
import java.util.Iterator;
55
import java.util.Iterator;
55
import java.util.LinkedList;
56
import java.util.LinkedList;
57
import java.util.Map;
56
import javax.swing.Action;
58
import javax.swing.Action;
57
import javax.swing.BorderFactory;
59
import javax.swing.BorderFactory;
58
import javax.swing.ImageIcon;
60
import javax.swing.ImageIcon;
Lines 627-632 Link Here
627
                super.waitFinished();
629
                super.waitFinished();
628
            }
630
            }
629
            
631
            
632
        private Map<Object,FileObject> cookiesToFiles = new HashMap<Object,FileObject>();
633
634
        @Override
635
        protected Object instanceForCookie(DataObject obj, InstanceCookie cookie) throws IOException, ClassNotFoundException {
636
            Object result = super.instanceForCookie(obj, cookie);
637
            cookiesToFiles.put(result, obj.getPrimaryFile());
638
            return result;
639
        }
630
640
631
    	    /**
641
    	    /**
632
             * Accepts only cookies that can provide <code>Menu</code>.
642
             * Accepts only cookies that can provide <code>Menu</code>.
Lines 685-691 Link Here
685
                    m.add(item);
695
                    m.add(item);
686
                }
696
                }
687
697
688
                m.dynaModel.loadSubmenu(cInstances, m);
698
                m.dynaModel.loadSubmenu(cInstances, m, cookiesToFiles);
689
699
690
                return m;
700
                return m;
691
            }
701
            }
(-)a/openide.loaders/src/org/openide/awt/Toolbar.java (-2 / +26 lines)
Lines 58-71 Link Here
58
import javax.swing.JComponent;
58
import javax.swing.JComponent;
59
import javax.swing.JSeparator;
59
import javax.swing.JSeparator;
60
import javax.swing.JToolBar;
60
import javax.swing.JToolBar;
61
import javax.swing.KeyStroke;
61
import javax.swing.UIManager;
62
import javax.swing.UIManager;
62
import javax.swing.plaf.metal.MetalLookAndFeel;
63
import javax.swing.plaf.metal.MetalLookAndFeel;
64
import javax.swing.text.Keymap;
63
import org.netbeans.modules.openide.loaders.DataObjectAccessor;
65
import org.netbeans.modules.openide.loaders.DataObjectAccessor;
64
import org.openide.cookies.InstanceCookie;
66
import org.openide.cookies.InstanceCookie;
67
import org.openide.filesystems.FileObject;
65
import org.openide.loaders.DataFolder;
68
import org.openide.loaders.DataFolder;
66
import org.openide.loaders.DataObject;
69
import org.openide.loaders.DataObject;
67
import org.openide.loaders.FolderInstance;
70
import org.openide.loaders.FolderInstance;
68
import org.openide.util.ImageUtilities;
71
import org.openide.util.ImageUtilities;
72
import org.openide.util.Lookup;
69
import org.openide.util.Task;
73
import org.openide.util.Task;
70
import org.openide.util.actions.Presenter;
74
import org.openide.util.actions.Presenter;
71
75
Lines 352-358 Link Here
352
            return Toolbar.this.getClass();
356
            return Toolbar.this.getClass();
353
        }
357
        }
354
358
355
        private Map<Object, Object> cookiesToObjects = new HashMap<Object, Object>();
359
        private Map<Object,DataObject> cookiesToObjects = new HashMap<Object,DataObject>();
356
    
360
    
357
        @Override
361
        @Override
358
        protected Object instanceForCookie (DataObject obj, InstanceCookie cookie)
362
        protected Object instanceForCookie (DataObject obj, InstanceCookie cookie)
Lines 411-417 Link Here
411
            for (int i = 0; i < cookies.length; i++) {
415
            for (int i = 0; i < cookies.length; i++) {
412
                try {
416
                try {
413
                    Object obj = cookies[i].instanceCreate();
417
                    Object obj = cookies[i].instanceCreate();
414
                    Object file = cookiesToObjects.get(obj);
418
                    DataObject file = cookiesToObjects.get(obj);
415
419
416
                    if (obj instanceof Presenter.Toolbar) {
420
                    if (obj instanceof Presenter.Toolbar) {
417
                        obj = ((Presenter.Toolbar) obj).getToolbarPresenter();
421
                        obj = ((Presenter.Toolbar) obj).getToolbarPresenter();
Lines 447-452 Link Here
447
                        org.openide.awt.Actions.connect(b, a);
451
                        org.openide.awt.Actions.connect(b, a);
448
                        b.putClientProperty("file", file);
452
                        b.putClientProperty("file", file);
449
                        org.openide.awt.Toolbar.this.add(b);
453
                        org.openide.awt.Toolbar.this.add(b);
454
                        setAccelerator(a, file.getPrimaryFile());
450
                        continue;
455
                        continue;
451
                    }
456
                    }
452
                }
457
                }
Lines 479-484 Link Here
479
484
480
    } // end of inner class Folder
485
    } // end of inner class Folder
481
486
487
    static void setAccelerator(Action a, FileObject file) {
488
        if (file == null) {
489
            return;
490
        }
491
        a.putValue("definingFile", file); // cf. o.n.core.NbKeymap.getKeyStrokesForAction
492
        KeyStroke[] keys;
493
        try {
494
            assert a.getValue("definingFile") == file : a.getClass() + " violated Action.putValue contract";
495
            Keymap keymap = Lookup.getDefault().lookup(Keymap.class);
496
            keys = keymap != null ? keymap.getKeyStrokesForAction(a) : new KeyStroke[0];
497
            assert keys != null : keymap;
498
        } finally {
499
            a.putValue("definingFile", null);
500
        }
501
        if (keys.length > 0) {
502
            a.putValue(Action.ACCELERATOR_KEY, keys[0]);
503
        }
504
    }
505
482
    @Override
506
    @Override
483
    public void setUI(javax.swing.plaf.ToolBarUI ui) {
507
    public void setUI(javax.swing.plaf.ToolBarUI ui) {
484
        super.setUI(ui);
508
        super.setUI(ui);
(-)a/openide.loaders/test/unit/src/org/openide/awt/DynaMenuModelTest.java (-4 / +6 lines)
Lines 44-49 Link Here
44
import java.awt.Component;
44
import java.awt.Component;
45
import java.awt.event.ActionEvent;
45
import java.awt.event.ActionEvent;
46
import java.util.ArrayList;
46
import java.util.ArrayList;
47
import java.util.Collections;
47
import java.util.List;
48
import java.util.List;
48
import javax.swing.AbstractAction;
49
import javax.swing.AbstractAction;
49
import javax.swing.JComponent;
50
import javax.swing.JComponent;
Lines 52-57 Link Here
52
import javax.swing.JPopupMenu;
53
import javax.swing.JPopupMenu;
53
import javax.swing.JSeparator;
54
import javax.swing.JSeparator;
54
import junit.framework.TestCase;
55
import junit.framework.TestCase;
56
import org.openide.filesystems.FileObject;
55
import org.openide.util.actions.Presenter;
57
import org.openide.util.actions.Presenter;
56
58
57
/**
59
/**
Lines 70-82 Link Here
70
    public void testLoadSubmenu() {
72
    public void testLoadSubmenu() {
71
        System.out.println("loadSubmenu");
73
        System.out.println("loadSubmenu");
72
        
74
        
73
        List cInstances = new ArrayList();
75
        List<Object> cInstances = new ArrayList<Object>();
74
        cInstances.add(new Act1());
76
        cInstances.add(new Act1());
75
        cInstances.add(new Act2());
77
        cInstances.add(new Act2());
76
        JMenu m = new JMenu();
78
        JMenu m = new JMenu();
77
        DynaMenuModel instance = new DynaMenuModel();
79
        DynaMenuModel instance = new DynaMenuModel();
78
        
80
        
79
        instance.loadSubmenu(cInstances, m);
81
        instance.loadSubmenu(cInstances, m, Collections.<Object,FileObject>emptyMap());
80
        Component[] comps = m.getPopupMenu().getComponents();
82
        Component[] comps = m.getPopupMenu().getComponents();
81
        assertEquals("0", ((JMenuItem)comps[0]).getText());
83
        assertEquals("0", ((JMenuItem)comps[0]).getText());
82
        assertEquals("1", ((JMenuItem)comps[1]).getText());
84
        assertEquals("1", ((JMenuItem)comps[1]).getText());
Lines 88-100 Link Here
88
     * Test of checkSubmenu method, of class org.openide.awt.DynaMenuModel.
90
     * Test of checkSubmenu method, of class org.openide.awt.DynaMenuModel.
89
     */
91
     */
90
    public void testCheckSubmenu() {
92
    public void testCheckSubmenu() {
91
        List cInstances = new ArrayList();
93
        List<Object> cInstances = new ArrayList<Object>();
92
        cInstances.add(new Act1());
94
        cInstances.add(new Act1());
93
        cInstances.add(new Act2());
95
        cInstances.add(new Act2());
94
        JMenu m = new JMenu();
96
        JMenu m = new JMenu();
95
        DynaMenuModel instance = new DynaMenuModel();
97
        DynaMenuModel instance = new DynaMenuModel();
96
        
98
        
97
        instance.loadSubmenu(cInstances, m);
99
        instance.loadSubmenu(cInstances, m, Collections.<Object,FileObject>emptyMap());
98
        instance.checkSubmenu(m);
100
        instance.checkSubmenu(m);
99
        
101
        
100
        Component[] comps = m.getPopupMenu().getComponents();
102
        Component[] comps = m.getPopupMenu().getComponents();

Return to bug 152576