Lines 44-51
Link Here
|
44 |
|
44 |
|
45 |
package org.netbeans.modules.options.keymap; |
45 |
package org.netbeans.modules.options.keymap; |
46 |
|
46 |
|
|
|
47 |
import java.awt.event.ActionListener; |
47 |
import java.io.IOException; |
48 |
import java.io.IOException; |
|
|
49 |
import java.lang.reflect.Field; |
48 |
import java.util.ArrayList; |
50 |
import java.util.ArrayList; |
|
|
51 |
import java.util.Arrays; |
49 |
import java.util.Collections; |
52 |
import java.util.Collections; |
50 |
import java.util.Enumeration; |
53 |
import java.util.Enumeration; |
51 |
import java.util.HashMap; |
54 |
import java.util.HashMap; |
Lines 69-74
Link Here
|
69 |
import org.openide.loaders.DataFolder; |
72 |
import org.openide.loaders.DataFolder; |
70 |
import org.openide.loaders.DataObject; |
73 |
import org.openide.loaders.DataObject; |
71 |
import org.openide.loaders.DataShadow; |
74 |
import org.openide.loaders.DataShadow; |
|
|
75 |
import org.openide.util.Exceptions; |
76 |
import org.openide.util.Lookup; |
72 |
import org.openide.util.NbBundle; |
77 |
import org.openide.util.NbBundle; |
73 |
|
78 |
|
74 |
/** |
79 |
/** |
Lines 79-84
Link Here
|
79 |
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.core.options.keymap.spi.KeymapManager.class) |
84 |
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.core.options.keymap.spi.KeymapManager.class) |
80 |
public class LayersBridge extends KeymapManager { |
85 |
public class LayersBridge extends KeymapManager { |
81 |
|
86 |
|
|
|
87 |
/** |
88 |
* Extension for DataObjects, which cause an action to be removed from the parent (general) keymap. |
89 |
*/ |
90 |
private static final String EXT_REMOVED = "removed"; // NOI18N |
91 |
|
82 |
private static final Logger LOG = Logger.getLogger(LayersBridge.class.getName()); |
92 |
private static final Logger LOG = Logger.getLogger(LayersBridge.class.getName()); |
83 |
|
93 |
|
84 |
static final String KEYMAPS_FOLDER = "Keymaps"; |
94 |
static final String KEYMAPS_FOLDER = "Keymaps"; |
Lines 221-226
Link Here
|
221 |
new HashMap<String, Map<ShortcutAction, Set<String>>> (); |
231 |
new HashMap<String, Map<ShortcutAction, Set<String>>> (); |
222 |
|
232 |
|
223 |
/** |
233 |
/** |
|
|
234 |
* The base keymap, shared for all profiles. Used as a baseline when generating |
235 |
* 'removed' instructions for a profile. |
236 |
*/ |
237 |
private volatile Map<ShortcutAction, Set<String>> baseKeyMap; |
238 |
|
239 |
/** |
224 |
* Returns Map (GlobalAction > Set (String (shortcut))). |
240 |
* Returns Map (GlobalAction > Set (String (shortcut))). |
225 |
*/ |
241 |
*/ |
226 |
public Map<ShortcutAction, Set<String>> getKeymap (String profile) { |
242 |
public Map<ShortcutAction, Set<String>> getKeymap (String profile) { |
Lines 229-238
Link Here
|
229 |
Map<ShortcutAction, Set<String>> m = readKeymap (root); |
245 |
Map<ShortcutAction, Set<String>> m = readKeymap (root); |
230 |
root = getRootFolder (KEYMAPS_FOLDER, profile); |
246 |
root = getRootFolder (KEYMAPS_FOLDER, profile); |
231 |
overrideWithKeyMap(m, readKeymap(root), profile); |
247 |
overrideWithKeyMap(m, readKeymap(root), profile); |
|
|
248 |
m.remove(REMOVED); |
232 |
keymaps.put (profile, m); |
249 |
keymaps.put (profile, m); |
233 |
} |
250 |
} |
234 |
return Collections.unmodifiableMap (keymaps.get (profile)); |
251 |
return Collections.unmodifiableMap (keymaps.get (profile)); |
235 |
} |
252 |
} |
|
|
253 |
|
254 |
private Map<ShortcutAction, Set<String>> getBaseKeyMap() { |
255 |
if (baseKeyMap == null) { |
256 |
DataFolder root = getRootFolder (SHORTCUTS_FOLDER, null); |
257 |
Map<ShortcutAction, Set<String>> m = readKeymap (root); |
258 |
baseKeyMap = m; |
259 |
} |
260 |
return baseKeyMap; |
261 |
} |
236 |
|
262 |
|
237 |
/** |
263 |
/** |
238 |
* Overrides the base shortcut map with contents of the Keymap. If keymap specifies |
264 |
* Overrides the base shortcut map with contents of the Keymap. If keymap specifies |
Lines 286-297
Link Here
|
286 |
/** |
312 |
/** |
287 |
* Returns Map (GlobalAction > Set (String (shortcut))). |
313 |
* Returns Map (GlobalAction > Set (String (shortcut))). |
288 |
*/ |
314 |
*/ |
289 |
public Map<ShortcutAction, Set<String>> getDefaultKeymap (String profile) { |
315 |
public synchronized Map<ShortcutAction, Set<String>> getDefaultKeymap (String profile) { |
290 |
if (!keymapDefaults.containsKey (profile)) { |
316 |
if (!keymapDefaults.containsKey (profile)) { |
291 |
DataFolder root = getRootFolder (SHORTCUTS_FOLDER, null); |
317 |
DataFolder root = getRootFolder (SHORTCUTS_FOLDER, null); |
|
|
318 |
System.err.println(Arrays.asList(root.getChildren())); |
292 |
Map<ShortcutAction, Set<String>> m = readKeymap (root); |
319 |
Map<ShortcutAction, Set<String>> m = readKeymap (root); |
293 |
root = getRootFolder (KEYMAPS_FOLDER, profile); |
320 |
root = getRootFolder (KEYMAPS_FOLDER, profile); |
294 |
m.putAll (readKeymap (root)); |
321 |
overrideWithKeyMap(m, readKeymap(root), profile); |
|
|
322 |
m.remove(REMOVED); |
295 |
keymapDefaults.put (profile, m); |
323 |
keymapDefaults.put (profile, m); |
296 |
} |
324 |
} |
297 |
return Collections.unmodifiableMap (keymapDefaults.get (profile)); |
325 |
return Collections.unmodifiableMap (keymapDefaults.get (profile)); |
Lines 302-310
Link Here
|
302 |
} |
330 |
} |
303 |
|
331 |
|
304 |
/** |
332 |
/** |
|
|
333 |
* Placeholder, which indicates shortcut(s) that should be removed. Must be used |
334 |
* only internally ! |
335 |
*/ |
336 |
private static final GlobalAction REMOVED = new GlobalAction(null, null, "<removed>") { |
337 |
{ |
338 |
name = ""; // NOI18N |
339 |
} |
340 |
}; |
341 |
|
342 |
/** |
305 |
* Read keymap from one folder Map (GlobalAction > Set (String (shortcut))). |
343 |
* Read keymap from one folder Map (GlobalAction > Set (String (shortcut))). |
306 |
*/ |
344 |
*/ |
307 |
private Map<ShortcutAction, Set<String>> readKeymap (DataFolder root) { |
345 |
private Map<ShortcutAction, Set<String>> readKeymap (DataFolder root) { |
|
|
346 |
LOG.log(Level.FINEST, "Reading keymap from: {0}", root); |
308 |
Map<ShortcutAction, Set<String>> keymap = |
347 |
Map<ShortcutAction, Set<String>> keymap = |
309 |
new HashMap<ShortcutAction, Set<String>> (); |
348 |
new HashMap<ShortcutAction, Set<String>> (); |
310 |
if (root == null) return keymap; |
349 |
if (root == null) return keymap; |
Lines 314-320
Link Here
|
314 |
if (dataObject instanceof DataFolder) continue; |
353 |
if (dataObject instanceof DataFolder) continue; |
315 |
GlobalAction action = createAction (dataObject, null); |
354 |
GlobalAction action = createAction (dataObject, null); |
316 |
if (action == null) continue; |
355 |
if (action == null) continue; |
317 |
String shortcut = dataObject.getName (); |
356 |
String shortcut = dataObject.getPrimaryFile().getName(); |
318 |
|
357 |
|
319 |
LOG.log(Level.FINEST, "Action {0}: {1}, by {2}", new Object[] { |
358 |
LOG.log(Level.FINEST, "Action {0}: {1}, by {2}", new Object[] { |
320 |
action.getDisplayName(), |
359 |
action.getDisplayName(), |
Lines 331-336
Link Here
|
331 |
return keymap; |
370 |
return keymap; |
332 |
} |
371 |
} |
333 |
|
372 |
|
|
|
373 |
@Override |
334 |
public void deleteProfile (String profile) { |
374 |
public void deleteProfile (String profile) { |
335 |
FileObject root = FileUtil.getConfigFile(KEYMAPS_FOLDER); |
375 |
FileObject root = FileUtil.getConfigFile(KEYMAPS_FOLDER); |
336 |
if (root == null) return; |
376 |
if (root == null) return; |
Lines 344-354
Link Here
|
344 |
} |
384 |
} |
345 |
|
385 |
|
346 |
// actionToShortcuts Map (GlobalAction > Set (String (shortcut)) |
386 |
// actionToShortcuts Map (GlobalAction > Set (String (shortcut)) |
|
|
387 |
@Override |
347 |
public void saveKeymap (String profile, Map<ShortcutAction, Set<String>> actionToShortcuts) { |
388 |
public void saveKeymap (String profile, Map<ShortcutAction, Set<String>> actionToShortcuts) { |
348 |
// discard our cached copy first |
389 |
// discard our cached copy first |
349 |
keymaps.remove(profile); |
390 |
keymaps.remove(profile); |
350 |
|
391 |
keymapDefaults.remove(profile); |
351 |
// 1) get / create Keymaps/Profile folder |
392 |
// 1) get / create Keymaps/Profile folder |
|
|
393 |
DataFolder defaultFolder = getRootFolder(SHORTCUTS_FOLDER, null); |
352 |
DataFolder folder = getRootFolder (KEYMAPS_FOLDER, profile); |
394 |
DataFolder folder = getRootFolder (KEYMAPS_FOLDER, profile); |
353 |
if (folder == null) { |
395 |
if (folder == null) { |
354 |
folder = getRootFolder (KEYMAPS_FOLDER, null); |
396 |
folder = getRootFolder (KEYMAPS_FOLDER, null); |
Lines 359-394
Link Here
|
359 |
return; |
401 |
return; |
360 |
} |
402 |
} |
361 |
} |
403 |
} |
362 |
saveKeymap (folder, actionToShortcuts, true); |
404 |
saveKeymap (defaultFolder, folder, actionToShortcuts); |
363 |
|
|
|
364 |
// FIXME: this rewrites the shared Shortcuts (default) folder; should be |
365 |
// done only after switching the profile |
366 |
folder = getRootFolder (SHORTCUTS_FOLDER, null); |
367 |
saveKeymap (folder, actionToShortcuts, false); |
368 |
} |
405 |
} |
369 |
|
406 |
|
370 |
private void saveKeymap (DataFolder folder, Map<ShortcutAction, Set<String>> actionToShortcuts, boolean add) { |
407 |
private void saveKeymap (DataFolder defaultMap, DataFolder folder, Map<ShortcutAction, Set<String>> actionToShortcuts) { |
|
|
408 |
LOG.log(Level.FINEST, "Saving keymap to: {0}", folder.getPrimaryFile().getPath()); |
371 |
// hack: initialize the actions map first |
409 |
// hack: initialize the actions map first |
372 |
getActions(); |
410 |
getActions(); |
373 |
// 2) convert to: Map (String (shortcut AC-C X) > GlobalAction) |
411 |
// 2) convert to: Map (String (shortcut AC-C X) > GlobalAction) |
374 |
Map<String, ShortcutAction> shortcutToAction = shortcutToAction (actionToShortcuts); |
412 |
Map<String, ShortcutAction> shortcutToAction = shortcutToAction (actionToShortcuts); |
375 |
|
413 |
|
|
|
414 |
Set<String> definedShortcuts = new HashSet<String>(shortcutToAction.keySet()); |
415 |
|
376 |
// 3) delete obsolete DataObjects |
416 |
// 3) delete obsolete DataObjects |
|
|
417 |
FileObject targetDir = folder.getPrimaryFile(); |
418 |
|
377 |
Enumeration en = folder.children (); |
419 |
Enumeration en = folder.children (); |
378 |
while (en.hasMoreElements ()) { |
420 |
while (en.hasMoreElements ()) { |
379 |
DataObject dataObject = (DataObject) en.nextElement (); |
421 |
DataObject dataObject = (DataObject) en.nextElement (); |
|
|
422 |
if (dataObject.getPrimaryFile().getExt().equals(EXT_REMOVED)) { |
423 |
continue; |
424 |
} |
380 |
GlobalAction a1 = (GlobalAction) shortcutToAction.get (dataObject.getName ()); |
425 |
GlobalAction a1 = (GlobalAction) shortcutToAction.get (dataObject.getName ()); |
381 |
if (a1 != null) { |
426 |
if (a1 != null) { |
382 |
GlobalAction action = createAction (dataObject, null); |
427 |
GlobalAction action = createAction (dataObject, null); |
383 |
if (action == null) continue; |
428 |
if (action == null) continue; |
384 |
if (action.equals (a1)) { |
429 |
if (action.equals (a1)) { |
385 |
// shortcut already saved |
430 |
// shortcut already saved |
|
|
431 |
LOG.log(Level.FINEST, "Found same binding: {0} -> {1}", new Object[] { dataObject.getName(), action.getId()}); |
386 |
shortcutToAction.remove (dataObject.getName ()); |
432 |
shortcutToAction.remove (dataObject.getName ()); |
387 |
continue; |
433 |
continue; |
388 |
} |
434 |
} |
389 |
} |
435 |
} |
390 |
// obsolete shortcut |
436 |
// obsolete shortcut. |
391 |
try { |
437 |
try { |
|
|
438 |
LOG.log(Level.FINEST, "Removing obsolete binding: {0}", dataObject.getName()); |
392 |
dataObject.delete (); |
439 |
dataObject.delete (); |
393 |
} catch (IOException ex) { |
440 |
} catch (IOException ex) { |
394 |
ex.printStackTrace (); |
441 |
ex.printStackTrace (); |
Lines 396-405
Link Here
|
396 |
} |
443 |
} |
397 |
|
444 |
|
398 |
// 4) add new shortcuts |
445 |
// 4) add new shortcuts |
399 |
if (!add) return; |
446 |
en = defaultMap.children(); |
|
|
447 |
while (en.hasMoreElements()) { |
448 |
DataObject dataObject = (DataObject)en.nextElement(); |
449 |
GlobalAction ga = (GlobalAction)shortcutToAction.get(dataObject.getName()); |
450 |
if (ga == null) { |
451 |
continue; |
452 |
} |
453 |
GlobalAction action = createAction(dataObject, null); |
454 |
if (ga.equals(action)) { |
455 |
LOG.log(Level.FINEST, "Leaving default shortcut: {0}", dataObject.getName()); |
456 |
shortcutToAction.remove(dataObject.getName()); |
457 |
} |
458 |
} |
459 |
|
400 |
Iterator it = shortcutToAction.keySet ().iterator (); |
460 |
Iterator it = shortcutToAction.keySet ().iterator (); |
401 |
while (it.hasNext ()) { |
461 |
while (it.hasNext ()) { |
402 |
String shortcut = (String) it.next (); |
462 |
String shortcut = (String) it.next (); |
|
|
463 |
// check whether the DO does not already exist: |
403 |
GlobalAction action = (GlobalAction) shortcutToAction.get (shortcut); |
464 |
GlobalAction action = (GlobalAction) shortcutToAction.get (shortcut); |
404 |
DataObject dataObject = actionToDataObject.get (action); |
465 |
DataObject dataObject = actionToDataObject.get (action); |
405 |
if (dataObject == null) { |
466 |
if (dataObject == null) { |
Lines 409-419
Link Here
|
409 |
} |
470 |
} |
410 |
try { |
471 |
try { |
411 |
DataShadow.create (folder, shortcut, dataObject); |
472 |
DataShadow.create (folder, shortcut, dataObject); |
|
|
473 |
// remove the '.remove' file, if it exists: |
474 |
FileObject f = targetDir.getFileObject(shortcut, EXT_REMOVED); |
475 |
if (f != null) { |
476 |
f.delete(); |
477 |
} |
412 |
} catch (IOException ex) { |
478 |
} catch (IOException ex) { |
413 |
ex.printStackTrace (); |
479 |
ex.printStackTrace (); |
414 |
continue; |
480 |
continue; |
415 |
} |
481 |
} |
416 |
} |
482 |
} |
|
|
483 |
|
484 |
// 5, mask out DataObjects from the global keymap, which are NOT present in this profile: |
485 |
if (defaultMap != null) { |
486 |
en = defaultMap.children(); |
487 |
while (en.hasMoreElements()) { |
488 |
DataObject dataObject = (DataObject) en.nextElement (); |
489 |
if (definedShortcuts.contains(dataObject.getName())) { |
490 |
continue; |
491 |
} |
492 |
try { |
493 |
FileObject pf = dataObject.getPrimaryFile(); |
494 |
// If the shortcut is ALSO defined in 'parent' folder, |
495 |
// we cannot just 'delete' it, but also mask the parent by adding 'removed' file. |
496 |
if (targetDir.getFileObject(pf.getName(), EXT_REMOVED) == null) { |
497 |
LOG.log(Level.FINEST, "Masking out binding: {0}", pf.getName()); |
498 |
folder.getPrimaryFile().createData(pf.getName(), EXT_REMOVED); |
499 |
} |
500 |
} catch (IOException ex) { |
501 |
ex.printStackTrace (); |
502 |
} |
503 |
} |
504 |
} |
505 |
|
417 |
} |
506 |
} |
418 |
|
507 |
|
419 |
private static DataFolder getRootFolder (String name1, String name2) { |
508 |
private static DataFolder getRootFolder (String name1, String name2) { |
Lines 438-454
Link Here
|
438 |
*/ |
527 |
*/ |
439 |
private GlobalAction createAction (DataObject dataObject, String prefix) { |
528 |
private GlobalAction createAction (DataObject dataObject, String prefix) { |
440 |
InstanceCookie ic = dataObject.getCookie(InstanceCookie.class); |
529 |
InstanceCookie ic = dataObject.getCookie(InstanceCookie.class); |
441 |
if (ic == null) return null; |
530 |
// handle any non-IC file as instruction to remove the action |
|
|
531 |
if (ic == null) { |
532 |
FileObject pf = dataObject.getPrimaryFile(); |
533 |
if (!EXT_REMOVED.equals(pf.getExt())) { |
534 |
LOG.log(Level.WARNING, "Invalid shortcut: {0}", dataObject); |
535 |
} |
536 |
// ignore the 'remove' file, if there's a shadow (= real action) present |
537 |
if (FileUtil.findBrother(pf, "shadow") != null) { |
538 |
// handle redefinition + removal: ignore the removal. |
539 |
return null; |
540 |
} |
541 |
return REMOVED; |
542 |
} |
442 |
try { |
543 |
try { |
443 |
Object action = ic.instanceCreate (); |
544 |
Object action = ic.instanceCreate (); |
444 |
if (action == null) return null; |
545 |
if (action == null) return null; |
445 |
if (!(action instanceof Action)) return null; |
546 |
if (!(action instanceof Action)) return null; |
446 |
return new GlobalAction ((Action) action, prefix, dataObject.getPrimaryFile().getName()); |
547 |
return createAction((Action) action, prefix, dataObject.getPrimaryFile().getName()); |
447 |
} catch (Exception ex) { |
548 |
} catch (Exception ex) { |
448 |
ex.printStackTrace (); |
549 |
ex.printStackTrace (); |
449 |
return null; |
550 |
return null; |
450 |
} |
551 |
} |
451 |
} |
552 |
} |
|
|
553 |
|
554 |
// hack: hardcoded OpenIDE impl class name + field |
555 |
private static final String OPENIDE_DELEGATE_ACTION = "org.openide.awt.GeneralAction$DelegateAction"; // NOI18N |
556 |
private static Field KEY_FIELD; |
557 |
|
558 |
/** |
559 |
* Hack, which allows to somehow extract actionId from OpenIDE actions. Public API |
560 |
* does not exist for this. |
561 |
* |
562 |
* @param a |
563 |
* @param prefix |
564 |
* @param name |
565 |
* @return |
566 |
*/ |
567 |
private static GlobalAction createAction(Action a, String prefix, String name) { |
568 |
String id = name; |
569 |
|
570 |
try { |
571 |
if (a.getClass().getName().equals(OPENIDE_DELEGATE_ACTION)) { |
572 |
if (KEY_FIELD == null) { |
573 |
Class c = a.getClass(); |
574 |
KEY_FIELD = c.getSuperclass().getDeclaredField("key"); // NOI18N |
575 |
KEY_FIELD.setAccessible(true); |
576 |
} |
577 |
String key = (String)KEY_FIELD.get(a); |
578 |
if (key != null) { |
579 |
id = key; |
580 |
} |
581 |
} |
582 |
} catch (NoSuchFieldException ex) { |
583 |
Exceptions.printStackTrace(ex); |
584 |
} catch (IllegalAccessException ex) { |
585 |
Exceptions.printStackTrace(ex); |
586 |
} |
587 |
|
588 |
return new GlobalAction(a, prefix, id); |
589 |
} |
452 |
|
590 |
|
453 |
/** |
591 |
/** |
454 |
* converts: actionToShortcuts: Map (ShortcutAction > Set (String (shortcut AC-C X))) |
592 |
* converts: actionToShortcuts: Map (ShortcutAction > Set (String (shortcut AC-C X))) |
Lines 494-500
Link Here
|
494 |
|
632 |
|
495 |
private static class GlobalAction implements ShortcutAction { |
633 |
private static class GlobalAction implements ShortcutAction { |
496 |
private Action action; |
634 |
private Action action; |
497 |
private String name; |
635 |
String name; |
498 |
private String id; |
636 |
private String id; |
499 |
|
637 |
|
500 |
/** |
638 |
/** |
Lines 539-550
Link Here
|
539 |
@Override |
677 |
@Override |
540 |
public boolean equals (Object o) { |
678 |
public boolean equals (Object o) { |
541 |
if (!(o instanceof GlobalAction)) return false; |
679 |
if (!(o instanceof GlobalAction)) return false; |
542 |
return ((GlobalAction) o).action.equals (action); |
680 |
return ((GlobalAction) o).action == action || ((GlobalAction) o).action.equals (action); |
543 |
} |
681 |
} |
544 |
|
682 |
|
545 |
@Override |
683 |
@Override |
546 |
public int hashCode () { |
684 |
public int hashCode () { |
547 |
return action.hashCode (); |
685 |
return action == null ? 111 : action.hashCode (); |
548 |
} |
686 |
} |
549 |
|
687 |
|
550 |
@Override |
688 |
@Override |