001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.options.keymap;
043:
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.Collections;
047: import java.util.Enumeration;
048: import java.util.HashMap;
049: import java.util.HashSet;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.Map;
053: import java.util.MissingResourceException;
054: import java.util.Set;
055: import javax.swing.Action;
056: import org.netbeans.core.options.keymap.api.ShortcutAction;
057: import org.netbeans.core.options.keymap.spi.KeymapManager;
058: import org.openide.ErrorManager;
059:
060: import org.openide.cookies.InstanceCookie;
061: import org.openide.filesystems.FileObject;
062: import org.openide.filesystems.FileStateInvalidException;
063: import org.openide.filesystems.FileSystem;
064: import org.openide.filesystems.Repository;
065: import org.openide.loaders.DataFolder;
066: import org.openide.loaders.DataObject;
067: import org.openide.loaders.DataShadow;
068: import org.openide.util.NbBundle;
069:
070: /**
071: * Bridge to old layers based system.
072: *
073: * @author Jan Jancura
074: */
075: public class LayersBridge extends KeymapManager {
076:
077: static final String KEYMAPS_FOLDER = "Keymaps";
078: private static final String SHORTCUTS_FOLDER = "Shortcuts";
079:
080: private static final String LAYERS_BRIDGE = "LayersBridge";
081:
082: /** Map (GlobalAction > DataObject). */
083: private Map<GlobalAction, DataObject> actionToDataObject = new HashMap<GlobalAction, DataObject>();
084: /** Map (String (folderName) > Set (GlobalAction)). */
085: private Map<String, Set<ShortcutAction>> categoryToActions;
086: /** Set (GlobalAction). */
087: private Set<GlobalAction> actions = new HashSet<GlobalAction>();
088:
089: public LayersBridge() {
090: super (LAYERS_BRIDGE);
091: }
092:
093: /**
094: * Returns Map (String (folderName) > Set (GlobalAction)).
095: */
096: public Map<String, Set<ShortcutAction>> getActions() {
097: if (categoryToActions == null) {
098: categoryToActions = new HashMap<String, Set<ShortcutAction>>();
099: initActions("Actions", null); // NOI18N
100: categoryToActions.remove("Hidden"); // NOI18N
101: categoryToActions = Collections
102: .unmodifiableMap(categoryToActions);
103: }
104: return categoryToActions;
105: }
106:
107: private void initActions(String folder, String category) {
108: FileSystem fs = Repository.getDefault().getDefaultFileSystem();
109: FileObject fo = fs.findResource(folder);
110: if (fo == null)
111: return;
112: DataFolder root = DataFolder.findFolder(fo);
113: Enumeration<DataObject> en = root.children();
114: while (en.hasMoreElements()) {
115: DataObject dataObject = en.nextElement();
116: if (dataObject instanceof DataFolder)
117: initActions((DataFolder) dataObject, null, category);
118: }
119: }
120:
121: private void initActions(DataFolder folder, String folderName,
122: String category) {
123:
124: // 1) reslove name
125: String name = folder.getName();
126: if (category != null)
127: name = category;
128: else {
129: String bundleName = (String) folder.getPrimaryFile()
130: .getAttribute("SystemFileSystem.localizingBundle");
131: if (bundleName != null)
132: try {
133: name = NbBundle.getBundle(bundleName).getString(
134: folder.getPrimaryFile().getPath());
135: } catch (MissingResourceException ex) {
136: ErrorManager.getDefault().notify(ex);
137: }
138: if (folderName != null)
139: name = folderName + '/' + name;
140: }
141:
142: Enumeration en = folder.children();
143: while (en.hasMoreElements()) {
144: DataObject dataObject = (DataObject) en.nextElement();
145: if (dataObject instanceof DataFolder) {
146: initActions((DataFolder) dataObject, name, category);
147: continue;
148: }
149: GlobalAction action = createAction(dataObject);
150: if (actions.contains(action))
151: continue;
152: if (action == null)
153: continue;
154: actions.add(action);
155:
156: // add to actions (Map (String (folderName) > Set (GlobalAction))).
157: Set<ShortcutAction> a = categoryToActions.get(name);
158: if (a == null) {
159: a = new HashSet<ShortcutAction>();
160: categoryToActions.put(name, a);
161: }
162: a.add(action);
163:
164: while (dataObject instanceof DataShadow)
165: dataObject = ((DataShadow) dataObject).getOriginal();
166:
167: actionToDataObject.put(action, dataObject);
168: }
169: }
170:
171: private List<String> keymapNames;
172: private Map<String, String> keymapDisplayNames;
173:
174: public List<String> getProfiles() {
175: if (keymapNames == null) {
176: DataFolder root = getRootFolder(KEYMAPS_FOLDER, null);
177: Enumeration en = root.children(false);
178: keymapNames = new ArrayList<String>();
179: keymapDisplayNames = new HashMap<String, String>();
180: while (en.hasMoreElements()) {
181: FileObject f = ((DataObject) en.nextElement())
182: .getPrimaryFile();
183: if (f.isFolder()) {
184: String name = f.getNameExt();
185: String displayName;
186:
187: try {
188: displayName = f.getFileSystem().getStatus()
189: .annotateName(name,
190: Collections.singleton(f));
191: } catch (FileStateInvalidException fsie) {
192: // ignore
193: displayName = name;
194: }
195: keymapNames.add(name);
196: keymapDisplayNames.put(name, displayName);
197: }
198: }
199: if (keymapNames.isEmpty()) {
200: keymapNames.add("NetBeans"); //NOI18N
201: }
202: }
203: return Collections.unmodifiableList(keymapNames);
204: }
205:
206: public @Override
207: String getProfileDisplayName(String profileName) {
208: String displayName = keymapDisplayNames.get(profileName);
209: return displayName == null ? profileName : displayName;
210: }
211:
212: /** Profile to Map of GlobalAction to set of shortcuts. */
213: private Map<String, Map<ShortcutAction, Set<String>>> keymaps = new HashMap<String, Map<ShortcutAction, Set<String>>>();
214:
215: /**
216: * Returns Map (GlobalAction > Set (String (shortcut))).
217: */
218: public Map<ShortcutAction, Set<String>> getKeymap(String profile) {
219: if (!keymaps.containsKey(profile)) {
220: DataFolder root = getRootFolder(SHORTCUTS_FOLDER, null);
221: Map<ShortcutAction, Set<String>> m = readKeymap(root);
222: root = getRootFolder(KEYMAPS_FOLDER, profile);
223: m.putAll(readKeymap(root));
224: keymaps.put(profile, m);
225: }
226: return Collections.unmodifiableMap(keymaps.get(profile));
227: }
228:
229: /** Map (String (profile) > Map (GlobalAction > Set (String (shortcut)))). */
230: private Map<String, Map<ShortcutAction, Set<String>>> keymapDefaults = new HashMap<String, Map<ShortcutAction, Set<String>>>();
231:
232: /**
233: * Returns Map (GlobalAction > Set (String (shortcut))).
234: */
235: public Map<ShortcutAction, Set<String>> getDefaultKeymap(
236: String profile) {
237: if (!keymapDefaults.containsKey(profile)) {
238: DataFolder root = getRootFolder(SHORTCUTS_FOLDER, null);
239: Map<ShortcutAction, Set<String>> m = readKeymap(root);
240: root = getRootFolder(KEYMAPS_FOLDER, profile);
241: m.putAll(readKeymap(root));
242: keymapDefaults.put(profile, m);
243: }
244: return Collections.unmodifiableMap(keymapDefaults.get(profile));
245: }
246:
247: DataObject getDataObject(Object action) {
248: return actionToDataObject.get(action);
249: }
250:
251: /**
252: * Read keymap from one folder Map (GlobalAction > Set (String (shortcut))).
253: */
254: private Map<ShortcutAction, Set<String>> readKeymap(DataFolder root) {
255: Map<ShortcutAction, Set<String>> keymap = new HashMap<ShortcutAction, Set<String>>();
256: if (root == null)
257: return keymap;
258: Enumeration<DataObject> en = root.children(false);
259: while (en.hasMoreElements()) {
260: DataObject dataObject = en.nextElement();
261: if (dataObject instanceof DataFolder)
262: continue;
263: GlobalAction action = createAction(dataObject);
264: if (action == null)
265: continue;
266: String shortcut = dataObject.getName();
267: Set<String> s = keymap.get(action);
268: if (s == null) {
269: s = new HashSet<String>();
270: keymap.put(action, s);
271: }
272: s.add(shortcut);
273: }
274: return keymap;
275: }
276:
277: public void deleteProfile(String profile) {
278: FileObject root = Repository.getDefault()
279: .getDefaultFileSystem().getRoot();
280: root = root.getFileObject(KEYMAPS_FOLDER);
281: if (root == null)
282: return;
283: root = root.getFileObject(profile);
284: if (root == null)
285: return;
286: try {
287: root.delete();
288: } catch (IOException ex) {
289: ErrorManager.getDefault().notify(ex);
290: }
291: }
292:
293: // actionToShortcuts Map (GlobalAction > Set (String (shortcut))
294: public void saveKeymap(String profile,
295: Map<ShortcutAction, Set<String>> actionToShortcuts) {
296: // discard our cached copy first
297: keymaps.remove(profile);
298:
299: // 1) get / create Keymaps/Profile folder
300: DataFolder folder = getRootFolder(KEYMAPS_FOLDER, profile);
301: if (folder == null) {
302: folder = getRootFolder(KEYMAPS_FOLDER, null);
303: try {
304: folder = DataFolder.create(folder, profile);
305: } catch (IOException ex) {
306: ErrorManager.getDefault().notify(ex);
307: return;
308: }
309: }
310: saveKeymap(folder, actionToShortcuts, true);
311:
312: folder = getRootFolder(SHORTCUTS_FOLDER, null);
313: saveKeymap(folder, actionToShortcuts, false);
314: }
315:
316: private void saveKeymap(DataFolder folder,
317: Map<ShortcutAction, Set<String>> actionToShortcuts,
318: boolean add) {
319: // hack: initialize the actions map first
320: getActions();
321: // 2) convert to: Map (String (shortcut AC-C X) > GlobalAction)
322: Map<String, ShortcutAction> shortcutToAction = shortcutToAction(actionToShortcuts);
323:
324: // 3) delete obsolete DataObjects
325: Enumeration en = folder.children();
326: while (en.hasMoreElements()) {
327: DataObject dataObject = (DataObject) en.nextElement();
328: GlobalAction a1 = (GlobalAction) shortcutToAction
329: .get(dataObject.getName());
330: if (a1 != null) {
331: GlobalAction action = createAction(dataObject);
332: if (action == null)
333: continue;
334: if (action.equals(a1)) {
335: // shortcut already saved
336: shortcutToAction.remove(dataObject.getName());
337: continue;
338: }
339: }
340: // obsolete shortcut
341: try {
342: dataObject.delete();
343: } catch (IOException ex) {
344: ex.printStackTrace();
345: }
346: }
347:
348: // 4) add new shortcuts
349: if (!add)
350: return;
351: Iterator it = shortcutToAction.keySet().iterator();
352: while (it.hasNext()) {
353: String shortcut = (String) it.next();
354: GlobalAction action = (GlobalAction) shortcutToAction
355: .get(shortcut);
356: DataObject dataObject = actionToDataObject.get(action);
357: if (dataObject == null) {
358: if (System.getProperty("org.netbeans.optionsDialog") != null)
359: System.out
360: .println("No original DataObject specified! Not possible to create shadow1. "
361: + action);
362: continue;
363: }
364: try {
365: DataShadow.create(folder, shortcut, dataObject);
366: } catch (IOException ex) {
367: ex.printStackTrace();
368: continue;
369: }
370: }
371: }
372:
373: private static DataFolder getRootFolder(String name1, String name2) {
374: FileObject root = Repository.getDefault()
375: .getDefaultFileSystem().getRoot();
376: FileObject fo1 = root.getFileObject(name1);
377: try {
378: if (fo1 == null)
379: fo1 = root.createFolder(name1);
380: if (fo1 == null)
381: return null;
382: if (name2 == null)
383: return DataFolder.findFolder(fo1);
384: FileObject fo2 = fo1.getFileObject(name2);
385: if (fo2 == null)
386: fo2 = fo1.createFolder(name2);
387: if (fo2 == null)
388: return null;
389: return DataFolder.findFolder(fo2);
390: } catch (IOException ex) {
391: ErrorManager.getDefault().notify(ex);
392: return null;
393: }
394: }
395:
396: /**
397: * Returns instance of GlobalAction encapsulating action, or null.
398: */
399: private GlobalAction createAction(DataObject dataObject) {
400: InstanceCookie ic = dataObject.getCookie(InstanceCookie.class);
401: if (ic == null)
402: return null;
403: try {
404: Object action = ic.instanceCreate();
405: if (action == null)
406: return null;
407: if (!(action instanceof Action))
408: return null;
409: return new GlobalAction((Action) action);
410: } catch (Exception ex) {
411: ex.printStackTrace();
412: return null;
413: }
414: }
415:
416: /**
417: * converts: actionToShortcuts: Map (ShortcutAction > Set (String (shortcut AC-C X)))
418: * to: Map (String (shortcut AC-C X) > GlobalAction).
419: * removes all non GlobalAction actions.
420: */
421: static Map<String, ShortcutAction> shortcutToAction(
422: Map<ShortcutAction, Set<String>> actionToShortcuts) {
423: Map<String, ShortcutAction> shortcutToAction = new HashMap<String, ShortcutAction>();
424: for (Map.Entry<ShortcutAction, Set<String>> entry : actionToShortcuts
425: .entrySet()) {
426: ShortcutAction action = entry.getKey();
427: Set<String> shortcuts = entry.getValue();
428: action = action.getKeymapManagerInstance(LAYERS_BRIDGE);
429: if (!(action instanceof GlobalAction))
430: continue;
431: for (String multiShortcut : shortcuts) {
432: shortcutToAction.put(multiShortcut, action);
433: }
434: }
435: return shortcutToAction;
436: }
437:
438: public void refreshActions() {
439: }
440:
441: public String getCurrentProfile() {
442: return null;
443: }
444:
445: public void setCurrentProfile(String profileName) {
446: }
447:
448: public boolean isCustomProfile(String profileName) {
449: // TODO:
450: return false;
451: }
452:
453: private static class GlobalAction implements ShortcutAction {
454: private Action action;
455: private String name;
456: private String id;
457:
458: private GlobalAction(Action a) {
459: action = a;
460: }
461:
462: public String getDisplayName() {
463: if (name == null) {
464: name = (String) action.getValue(Action.NAME);
465: if (name == null)
466: name = action.toString();
467: name = name.replaceAll("&", "").trim();
468: }
469: return name;
470: }
471:
472: public String getId() {
473: if (id == null)
474: id = action.getClass().getName();
475: return id;
476: }
477:
478: public String getDelegatingActionId() {
479: return null;
480: }
481:
482: @Override
483: public boolean equals(Object o) {
484: if (!(o instanceof GlobalAction))
485: return false;
486: return ((GlobalAction) o).action.equals(action);
487: }
488:
489: @Override
490: public int hashCode() {
491: return action.hashCode();
492: }
493:
494: @Override
495: public String toString() {
496: return "GlobalAction[" + getDisplayName() + ":" + id + "]";
497: }
498:
499: public ShortcutAction getKeymapManagerInstance(
500: String keymapManagerName) {
501: if (LAYERS_BRIDGE.equals(keymapManagerName)) {
502: return this;
503: }
504: return null;
505: }
506: }
507: }
|