001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.Collections;
015: import java.util.HashMap;
016: import java.util.HashSet;
017: import java.util.Iterator;
018: import java.util.List;
019: import java.util.Map;
020: import java.util.Set;
021:
022: import org.eclipse.jface.action.IAction;
023: import org.eclipse.ui.IKeyBindingService;
024: import org.eclipse.ui.INestableKeyBindingService;
025: import org.eclipse.ui.IWorkbenchPartSite;
026: import org.eclipse.ui.IWorkbenchSite;
027: import org.eclipse.ui.commands.ActionHandler;
028: import org.eclipse.ui.commands.HandlerSubmission;
029: import org.eclipse.ui.commands.IHandler;
030: import org.eclipse.ui.commands.Priority;
031: import org.eclipse.ui.contexts.EnabledSubmission;
032: import org.eclipse.ui.internal.actions.CommandAction;
033: import org.eclipse.ui.internal.handlers.CommandLegacyActionWrapper;
034:
035: /**
036: * This service provides a nestable implementation of a key binding service.
037: * This class is provided for backwards compatibility only, and might be removed
038: * in the future. All of the functionality is the class can be duplicated by
039: * using the commands and contexts API.
040: *
041: * @since 2.0
042: */
043: public final class KeyBindingService implements
044: INestableKeyBindingService {
045:
046: /**
047: * The currently active nested service, if any. If there are no nested
048: * services or none of them are active, then this value is <code>null</code>.
049: */
050: private IKeyBindingService activeService = null;
051:
052: /**
053: * Whether this key binding service has been disposed. A disposed key
054: * binding service should not be used again.
055: */
056: private boolean disposed;
057:
058: /**
059: * The set of context identifiers enabled in this key binding service (not
060: * counting any nested services). This set may be empty, but it is never
061: * <code>null</code>.
062: */
063: private Set enabledContextIds = Collections.EMPTY_SET;
064:
065: /**
066: * The list of context submissions indicating the enabled state of the
067: * context. This does not include those from nested services. This list may
068: * be empty, but it is never <code>null</code>.
069: */
070: private List enabledSubmissions = new ArrayList();
071:
072: /**
073: * The map of handler submissions, sorted by command identifiers. This does
074: * not include those from nested services. This map may be empty, but it is
075: * never <code>null</code>.
076: */
077: private Map handlerSubmissionsByCommandId = new HashMap();
078:
079: /**
080: * The context submissions from the currently active nested service. This
081: * value is <code>null</code> if there is no currently active nested
082: * service.
083: */
084: private List nestedEnabledSubmissions = null;
085:
086: /**
087: * The handler submissions from the currently active nested service. This
088: * value is <code>null</code> if there is no currently active handler
089: * service.
090: */
091: private List nestedHandlerSubmissions = null;
092:
093: /**
094: * The map of workbench part sites to nested key binding services. This map
095: * may be empty, but is never <code>null</code>.
096: */
097: private final Map nestedServices = new HashMap();
098:
099: /**
100: * The parent for this key binding service; <code>null</code> if there is
101: * no parent. If there is a parent, then this means that it should not do a
102: * "live" update of its contexts or handlers, but should make a call to the
103: * parent instead.
104: */
105: private final KeyBindingService parent;
106:
107: /**
108: * The site within the workbench at which this service is provided. This
109: * value should not be <code>null</code>.
110: */
111: private IWorkbenchPartSite workbenchPartSite;
112:
113: /**
114: * Constructs a new instance of <code>KeyBindingService</code> on a given
115: * workbench site. This instance is not nested.
116: *
117: * @param workbenchPartSite
118: * The site for which this service will be responsible; should
119: * not be <code>null</code>.
120: */
121: public KeyBindingService(IWorkbenchPartSite workbenchPartSite) {
122: this (workbenchPartSite, null);
123: }
124:
125: /**
126: * Constructs a new instance of <code>KeyBindingService</code> on a given
127: * workbench site.
128: *
129: * @param workbenchPartSite
130: * The site for which this service will be responsible; should
131: * not be <code>null</code>.
132: * @param parent
133: * The parent key binding service, if any; <code>null</code> if
134: * none.
135: */
136: KeyBindingService(IWorkbenchPartSite workbenchPartSite,
137: KeyBindingService parent) {
138: this .workbenchPartSite = workbenchPartSite;
139: this .parent = parent;
140: }
141:
142: /*
143: * (non-Javadoc)
144: *
145: * @see org.eclipse.ui.INestableKeyBindingService#activateKeyBindingService(org.eclipse.ui.IWorkbenchSite)
146: */
147: public boolean activateKeyBindingService(IWorkbenchSite nestedSite) {
148: if (disposed) {
149: return false;
150: }
151:
152: // Check if we should do a deactivation.
153: if (nestedSite == null) {
154: // We should do a deactivation, if there is one active.
155: if (activeService == null) {
156: // There is no active service. Do no work.
157: return false;
158: } else {
159: // Deactivate the currently active nested service.
160: deactivateNestedService();
161: return true;
162: }
163: }
164:
165: // Attempt to activate a service.
166: final IKeyBindingService service = (IKeyBindingService) nestedServices
167: .get(nestedSite);
168: if (service == null) {
169: return false;
170: }
171:
172: if (service == activeService) {
173: // The service is already active.
174: return false;
175: }
176:
177: deactivateNestedService();
178: activateNestedService(service);
179: return true;
180: }
181:
182: /**
183: * Activates the given service without worrying about the currently active
184: * service. This goes through the work of adding all of the nested context
185: * ids as enabled submissions.
186: *
187: * @param service
188: * The service to become active; if <code>null</code>, then
189: * the reference to the active service is set to
190: * <code>null</code> but nothing else happens.
191: */
192: private final void activateNestedService(
193: final IKeyBindingService service) {
194: if (disposed) {
195: return;
196: }
197:
198: /*
199: * If I have a parent, and I'm the active service, then deactivate so
200: * that I can make changes.
201: */
202: boolean active = false;
203: boolean haveParent = (parent != null);
204: if (haveParent) {
205: active = (parent.activeService == this );
206: if (active) {
207: parent.deactivateNestedService();
208: }
209: }
210:
211: // Update the active service.
212: activeService = service;
213:
214: // Check to see that the service isn't null.
215: if (service == null) {
216: return;
217: }
218:
219: if (haveParent) {
220: if (active) {
221: parent.activateNestedService(this );
222: }
223:
224: } else if (activeService instanceof KeyBindingService) {
225: // I have no parent, so I can make the changes myself.
226: final KeyBindingService nestedService = (KeyBindingService) activeService;
227:
228: // Update the contexts.
229: nestedEnabledSubmissions = nestedService
230: .getEnabledSubmissions();
231: normalizeSites(nestedEnabledSubmissions);
232: Workbench.getInstance().getContextSupport()
233: .addEnabledSubmissions(nestedEnabledSubmissions);
234:
235: // Update the handlers.
236: nestedHandlerSubmissions = nestedService
237: .getHandlerSubmissions();
238: normalizeSites(nestedHandlerSubmissions);
239: Workbench.getInstance().getCommandSupport()
240: .addHandlerSubmissions(nestedHandlerSubmissions);
241: }
242: }
243:
244: /**
245: * Deactives the currently active service. This nulls out the reference, and
246: * removes all the enabled submissions for the nested service.
247: */
248: private final void deactivateNestedService() {
249: if (disposed) {
250: return;
251: }
252:
253: // Don't do anything if there is no active service.
254: if (activeService == null) {
255: return;
256: }
257:
258: // Check to see if there is a parent.
259: boolean active = false;
260: if (parent != null) {
261: // Check if I'm the active service.
262: if (parent.activeService == this ) {
263: active = true;
264: // Deactivate myself so I can make changes.
265: parent.deactivateNestedService();
266: }
267:
268: } else if (activeService instanceof KeyBindingService) {
269: // Remove all the nested context ids.
270: Workbench.getInstance().getContextSupport()
271: .removeEnabledSubmissions(nestedEnabledSubmissions);
272:
273: /*
274: * Remove all of the nested handler submissions. The handlers here
275: * weren't created by this instance (but by the nest instance), and
276: * hence can't be disposed here.
277: */
278: Workbench.getInstance().getCommandSupport()
279: .removeHandlerSubmissions(nestedHandlerSubmissions);
280:
281: }
282:
283: // Clear our reference to the active service.
284: activeService = null;
285:
286: // If necessary, let my parent know that changes have occurred.
287: if (active) {
288: parent.activateNestedService(this );
289: }
290: }
291:
292: /**
293: * Disposes this key binding service. This clears out all of the submissions
294: * held by this service, and its nested services.
295: */
296: public void dispose() {
297: if (!disposed) {
298: deactivateNestedService();
299: disposed = true;
300:
301: Workbench.getInstance().getContextSupport()
302: .removeEnabledSubmissions(
303: new ArrayList(enabledSubmissions));
304: enabledSubmissions.clear();
305:
306: /*
307: * Each removed handler submission, must dispose its corresponding
308: * handler -- as these handlers only exist inside of this class.
309: */
310: final List submissions = new ArrayList(
311: handlerSubmissionsByCommandId.values());
312: final Iterator submissionItr = submissions.iterator();
313: while (submissionItr.hasNext()) {
314: ((HandlerSubmission) submissionItr.next()).getHandler()
315: .dispose();
316: }
317: Workbench.getInstance().getCommandSupport()
318: .removeHandlerSubmissions(submissions);
319: handlerSubmissionsByCommandId.clear();
320:
321: for (Iterator iterator = nestedServices.values().iterator(); iterator
322: .hasNext();) {
323: KeyBindingService keyBindingService = (KeyBindingService) iterator
324: .next();
325: keyBindingService.dispose();
326: }
327:
328: nestedEnabledSubmissions = null;
329: nestedHandlerSubmissions = null;
330: nestedServices.clear();
331: }
332: }
333:
334: /**
335: * Gets a copy of all the enabled submissions in the nesting chain.
336: *
337: * @return All of the nested enabled submissions -- including the ones from
338: * this service. This list may be empty, but is never
339: * <code>null</code>.
340: */
341: private final List getEnabledSubmissions() {
342: if (disposed) {
343: return null;
344: }
345:
346: final List submissions = new ArrayList(enabledSubmissions);
347: if (activeService instanceof KeyBindingService) {
348: final KeyBindingService nestedService = (KeyBindingService) activeService;
349: submissions.addAll(nestedService.getEnabledSubmissions());
350: }
351: return submissions;
352: }
353:
354: /**
355: * Gets a copy of all the handler submissions in the nesting chain.
356: *
357: * @return All of the nested handler submissions -- including the ones from
358: * this service. This list may be empty, but is never
359: * <code>null</code>.
360: */
361: private final List getHandlerSubmissions() {
362: if (disposed) {
363: return null;
364: }
365:
366: final List submissions = new ArrayList(
367: handlerSubmissionsByCommandId.values());
368: if (activeService instanceof KeyBindingService) {
369: final KeyBindingService nestedService = (KeyBindingService) activeService;
370: submissions.addAll(nestedService.getHandlerSubmissions());
371: }
372: return submissions;
373: }
374:
375: /*
376: * (non-Javadoc)
377: *
378: * @see org.eclipse.ui.INestableKeyBindingService#getKeyBindingService(org.eclipse.ui.IWorkbenchSite)
379: */
380: public IKeyBindingService getKeyBindingService(
381: IWorkbenchSite nestedSite) {
382: if (disposed) {
383: return null;
384: }
385:
386: if (nestedSite == null) {
387: return null;
388: }
389:
390: IKeyBindingService service = (IKeyBindingService) nestedServices
391: .get(nestedSite);
392: if (service == null) {
393: // TODO the INestedKeyBindingService API should be based on
394: // IWorkbenchPartSite..
395: if (nestedSite instanceof IWorkbenchPartSite) {
396: service = new KeyBindingService(
397: (IWorkbenchPartSite) nestedSite, this );
398: } else {
399: service = new KeyBindingService(null, this );
400: }
401:
402: nestedServices.put(nestedSite, service);
403: }
404:
405: return service;
406: }
407:
408: public String[] getScopes() {
409: if (disposed) {
410: return null;
411: }
412:
413: // Get the nested scopes, if any.
414: final String[] nestedScopes;
415: if (activeService == null) {
416: nestedScopes = null;
417: } else {
418: nestedScopes = activeService.getScopes();
419: }
420:
421: // Build the list of active scopes
422: final Set activeScopes = new HashSet();
423: activeScopes.addAll(enabledContextIds);
424: if (nestedScopes != null) {
425: for (int i = 0; i < nestedScopes.length; i++) {
426: activeScopes.add(nestedScopes[i]);
427: }
428: }
429:
430: return (String[]) activeScopes.toArray(new String[activeScopes
431: .size()]);
432: }
433:
434: /**
435: * Replaces the active workbench site with this service's active workbench
436: * site. This ensures that the context manager will recognize the context as
437: * active. Note: this method modifies the list in place; it is
438: * <em>destructive</em>.
439: *
440: * @param submissionsToModify
441: * The submissions list to modify; must not be <code>null</code>,
442: * but may be empty.
443: */
444: private final void normalizeSites(final List submissionsToModify) {
445: if (disposed) {
446: return;
447: }
448:
449: final int size = submissionsToModify.size();
450: for (int i = 0; i < size; i++) {
451: final Object submission = submissionsToModify.get(i);
452: final Object replacementSubmission;
453:
454: if (submission instanceof EnabledSubmission) {
455: final EnabledSubmission enabledSubmission = (EnabledSubmission) submission;
456: if (!workbenchPartSite.equals(enabledSubmission
457: .getActiveWorkbenchPartSite())) {
458: replacementSubmission = new EnabledSubmission(null,
459: enabledSubmission.getActiveShell(),
460: workbenchPartSite, enabledSubmission
461: .getContextId());
462: } else {
463: replacementSubmission = enabledSubmission;
464: }
465:
466: } else if (submission instanceof HandlerSubmission) {
467: final HandlerSubmission handlerSubmission = (HandlerSubmission) submission;
468: if (!workbenchPartSite.equals(handlerSubmission
469: .getActiveWorkbenchPartSite())) {
470: replacementSubmission = new HandlerSubmission(null,
471: handlerSubmission.getActiveShell(),
472: workbenchPartSite, handlerSubmission
473: .getCommandId(), handlerSubmission
474: .getHandler(), handlerSubmission
475: .getPriority());
476: } else {
477: replacementSubmission = handlerSubmission;
478: }
479:
480: } else {
481: replacementSubmission = submission;
482: }
483:
484: submissionsToModify.set(i, replacementSubmission);
485: }
486:
487: }
488:
489: public void registerAction(IAction action) {
490: if (disposed) {
491: return;
492: }
493:
494: if (action instanceof CommandLegacyActionWrapper) {
495: // this is a registration of a fake action for an already
496: // registered handler
497: WorkbenchPlugin
498: .log("Cannot register a CommandLegacyActionWrapper back into the system"); //$NON-NLS-1$
499: return;
500: }
501:
502: if (action instanceof CommandAction) {
503: // we unfortunately had to allow these out into the wild, but they
504: // still must not feed back into the system
505: return;
506: }
507:
508: unregisterAction(action);
509: String commandId = action.getActionDefinitionId();
510: if (commandId != null) {
511: /*
512: * If I have a parent and I'm active, de-activate myself while
513: * making changes.
514: */
515: boolean active = false;
516: if ((parent != null) && (parent.activeService == this )) {
517: active = true;
518: parent.deactivateNestedService();
519: }
520:
521: // Create the new submission
522: IHandler handler = new ActionHandler(action);
523: HandlerSubmission handlerSubmission = new HandlerSubmission(
524: null, workbenchPartSite.getShell(),
525: workbenchPartSite, commandId, handler,
526: Priority.MEDIUM);
527: handlerSubmissionsByCommandId.put(commandId,
528: handlerSubmission);
529:
530: // Either submit the new handler myself, or simply re-activate.
531: if (parent != null) {
532: if (active) {
533: parent.activateNestedService(this );
534: }
535: } else {
536: Workbench.getInstance().getCommandSupport()
537: .addHandlerSubmission(handlerSubmission);
538: }
539: }
540: }
541:
542: /*
543: * (non-Javadoc)
544: *
545: * @see org.eclipse.ui.INestableKeyBindingService#removeKeyBindingService(org.eclipse.ui.IWorkbenchSite)
546: */
547: public boolean removeKeyBindingService(IWorkbenchSite nestedSite) {
548: if (disposed) {
549: return false;
550: }
551:
552: final IKeyBindingService service = (IKeyBindingService) nestedServices
553: .remove(nestedSite);
554: if (service == null) {
555: return false;
556: }
557:
558: if (service.equals(activeService)) {
559: deactivateNestedService();
560: }
561:
562: return true;
563: }
564:
565: public void setScopes(String[] scopes) {
566: if (disposed) {
567: return;
568: }
569:
570: // Either deactivate myself, or remove the previous submissions myself.
571: boolean active = false;
572: if ((parent != null) && (parent.activeService == this )) {
573: active = true;
574: parent.deactivateNestedService();
575: } else {
576: Workbench.getInstance().getContextSupport()
577: .removeEnabledSubmissions(enabledSubmissions);
578: }
579: enabledSubmissions.clear();
580:
581: // Determine the new list of submissions.
582: enabledContextIds = new HashSet(Arrays.asList(scopes));
583: for (Iterator iterator = enabledContextIds.iterator(); iterator
584: .hasNext();) {
585: String contextId = (String) iterator.next();
586: enabledSubmissions.add(new EnabledSubmission(null, null,
587: workbenchPartSite, contextId));
588: }
589:
590: // Submit the new contexts myself, or simply re-active myself.
591: if (parent != null) {
592: if (active) {
593: parent.activateNestedService(this );
594: }
595: } else {
596: Workbench.getInstance().getContextSupport()
597: .addEnabledSubmissions(enabledSubmissions);
598: }
599: }
600:
601: public void unregisterAction(IAction action) {
602: if (disposed) {
603: return;
604: }
605:
606: if (action instanceof CommandLegacyActionWrapper) {
607: // this is a registration of a fake action for an already
608: // registered handler
609: WorkbenchPlugin
610: .log("Cannot unregister a CommandLegacyActionWrapper out of the system"); //$NON-NLS-1$
611: return;
612: }
613:
614: String commandId = action.getActionDefinitionId();
615:
616: if (commandId != null) {
617: // Deactivate this service while making changes.
618: boolean active = false;
619: if ((parent != null) && (parent.activeService == this )) {
620: active = true;
621: parent.deactivateNestedService();
622: }
623:
624: // Remove the current submission, if any.
625: HandlerSubmission handlerSubmission = (HandlerSubmission) handlerSubmissionsByCommandId
626: .remove(commandId);
627:
628: /*
629: * Either activate this service again, or remove the submission
630: * myself.
631: */
632: if (parent != null) {
633: if (active) {
634: parent.activateNestedService(this);
635: }
636: } else {
637: if (handlerSubmission != null) {
638: Workbench.getInstance().getCommandSupport()
639: .removeHandlerSubmission(handlerSubmission);
640: handlerSubmission.getHandler().dispose();
641: }
642: }
643: }
644: }
645: }
|