001: /*******************************************************************************
002: * Copyright (c) 2005, 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.contexts;
011:
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.HashMap;
015: import java.util.HashSet;
016: import java.util.Iterator;
017: import java.util.List;
018: import java.util.Map;
019: import java.util.Set;
020: import java.util.WeakHashMap;
021:
022: import org.eclipse.core.commands.contexts.ContextManager;
023: import org.eclipse.core.commands.util.Tracing;
024: import org.eclipse.core.expressions.Expression;
025: import org.eclipse.core.runtime.Assert;
026: import org.eclipse.swt.events.DisposeEvent;
027: import org.eclipse.swt.events.DisposeListener;
028: import org.eclipse.swt.widgets.Shell;
029: import org.eclipse.ui.ActiveShellExpression;
030: import org.eclipse.ui.ISources;
031: import org.eclipse.ui.contexts.IContextActivation;
032: import org.eclipse.ui.contexts.IContextService;
033: import org.eclipse.ui.internal.misc.Policy;
034: import org.eclipse.ui.internal.services.ExpressionAuthority;
035:
036: /**
037: * <p>
038: * A central authority for deciding activation of contexts. This authority
039: * listens to a variety of incoming sources, and updates the underlying context
040: * manager if changes occur.
041: * </p>
042: *
043: * @since 3.1
044: */
045: public final class ContextAuthority extends ExpressionAuthority {
046: public static final String DEFER_EVENTS = "org.eclipse.ui.internal.contexts.deferEvents"; //$NON-NLS-1$
047: public static final String SEND_EVENTS = "org.eclipse.ui.internal.contexts.sendEvents"; //$NON-NLS-1$
048:
049: /**
050: * The default size of the set containing the activations to recompute. This
051: * is more than enough to cover the average case.
052: */
053: private static final int ACTIVATIONS_TO_RECOMPUTE_SIZE = 4;
054:
055: /**
056: * Whether the context authority should kick into debugging mode. This
057: * causes the unresolvable handler conflicts to be printed to the console.
058: */
059: private static final boolean DEBUG = Policy.DEBUG_CONTEXTS;
060:
061: /**
062: * Whether the performance information should be printed about the
063: * performance of the context authority.
064: */
065: private static final boolean DEBUG_PERFORMANCE = Policy.DEBUG_CONTEXTS_PERFORMANCE;
066:
067: /**
068: * The name of the data tag containing the dispose listener information.
069: */
070: private static final String DISPOSE_LISTENER = "org.eclipse.ui.internal.contexts.ContextAuthority"; //$NON-NLS-1$
071:
072: /**
073: * The component name to print when displaying tracing information.
074: */
075: private static final String TRACING_COMPONENT = "CONTEXTS"; //$NON-NLS-1$
076:
077: /**
078: * A bucket sort of the context activations based on source priority. Each
079: * activation will appear only once per set, but may appear in multiple
080: * sets. If no activations are defined for a particular priority level, then
081: * the array at that index will only contain <code>null</code>.
082: */
083: private final Set[] activationsBySourcePriority = new Set[33];
084:
085: /**
086: * This is a map of context activations (<code>Collection</code> of
087: * <code>IContextActivation</code>) sorted by context identifier (<code>String</code>).
088: * If there is only one context activation for a context, then the
089: * <code>Collection</code> is replaced by a
090: * <code>IContextActivation</code>. If there is no activation, the entry
091: * should be removed entirely.
092: */
093: private final Map contextActivationsByContextId = new HashMap();
094:
095: /**
096: * The context manager that should be updated when the contexts are
097: * changing.
098: */
099: private final ContextManager contextManager;
100:
101: /**
102: * The context service that should be used for authority-managed
103: * shell-related contexts. This value is never <code>null</code>.
104: */
105: private final IContextService contextService;
106:
107: /**
108: * This is a map of shell to a list of activations. When a shell is
109: * registered, it is added to this map with the list of activation that
110: * should be submitted when the shell is active. When the shell is
111: * deactivated, this same list should be withdrawn. A shell is removed from
112: * this map using the {@link #unregisterShell(Shell)}method. This value may
113: * be empty, but is never <code>null</code>. The <code>null</code> key
114: * is reserved for active shells that have not been registered but have a
115: * parent (i.e., default dialog service).
116: */
117: private final Map registeredWindows = new WeakHashMap();
118:
119: /**
120: * Constructs a new instance of <code>ContextAuthority</code>.
121: *
122: * @param contextManager
123: * The context manager from which contexts can be retrieved (to
124: * update their active state); must not be <code>null</code>.
125: * @param contextService
126: * The workbench context service for which this authority is
127: * acting. This allows the authority to manage shell-specific
128: * contexts. This value must not be <code>null</code>.
129: */
130: ContextAuthority(final ContextManager contextManager,
131: final IContextService contextService) {
132: if (contextManager == null) {
133: throw new NullPointerException(
134: "The context authority needs a context manager"); //$NON-NLS-1$
135: }
136: if (contextService == null) {
137: throw new NullPointerException(
138: "The context authority needs an evaluation context"); //$NON-NLS-1$
139: }
140:
141: this .contextManager = contextManager;
142: this .contextService = contextService;
143: }
144:
145: /**
146: * Activates a context on the workbench. This will add it to a master list.
147: *
148: * @param activation
149: * The activation; must not be <code>null</code>.
150: */
151: final void activateContext(final IContextActivation activation) {
152: // First we update the contextActivationsByContextId map.
153: final String contextId = activation.getContextId();
154: if (DEFER_EVENTS.equals(contextId)
155: || SEND_EVENTS.equals(contextId)) {
156: contextManager.addActiveContext(contextId);
157: return;
158: }
159: final Object value = contextActivationsByContextId
160: .get(contextId);
161: if (value instanceof Collection) {
162: final Collection contextActivations = (Collection) value;
163: if (!contextActivations.contains(activation)) {
164: contextActivations.add(activation);
165: updateContext(contextId,
166: containsActive(contextActivations));
167: }
168: } else if (value instanceof IContextActivation) {
169: if (value != activation) {
170: final Collection contextActivations = new ArrayList(2);
171: contextActivations.add(value);
172: contextActivations.add(activation);
173: contextActivationsByContextId.put(contextId,
174: contextActivations);
175: updateContext(contextId,
176: containsActive(contextActivations));
177: }
178: } else {
179: contextActivationsByContextId.put(contextId, activation);
180: updateContext(contextId, evaluate(activation));
181: }
182:
183: // Next we update the source priority bucket sort of activations.
184: final int sourcePriority = activation.getSourcePriority();
185: for (int i = 1; i <= 32; i++) {
186: if ((sourcePriority & (1 << i)) != 0) {
187: Set activations = activationsBySourcePriority[i];
188: if (activations == null) {
189: activations = new HashSet(1);
190: activationsBySourcePriority[i] = activations;
191: }
192: activations.add(activation);
193: }
194: }
195: }
196:
197: /**
198: * Checks whether the new active shell is registered. If it is already
199: * registered, then it does no work. If it is not registered, then it checks
200: * what type of contexts the shell should have by default. This is
201: * determined by parenting. A shell with no parent receives no contexts. A
202: * shell with a parent, receives the dialog contexts.
203: *
204: * @param newShell
205: * The newly active shell; may be <code>null</code> or
206: * disposed.
207: * @param oldShell
208: * The previously active shell; may be <code>null</code> or
209: * disposed.
210: */
211: private final void checkWindowType(final Shell newShell,
212: final Shell oldShell) {
213: /*
214: * If the previous active shell was recognized as a dialog by default,
215: * then remove its submissions.
216: */
217: Collection oldActivations = (Collection) registeredWindows
218: .get(oldShell);
219: if (oldActivations == null) {
220: /*
221: * The old shell wasn't registered. So, we need to check if it was
222: * considered a dialog by default.
223: */
224: oldActivations = (Collection) registeredWindows.get(null);
225: if (oldActivations != null) {
226: final Iterator oldActivationItr = oldActivations
227: .iterator();
228: while (oldActivationItr.hasNext()) {
229: final IContextActivation activation = (IContextActivation) oldActivationItr
230: .next();
231: deactivateContext(activation);
232: }
233: }
234: }
235:
236: /*
237: * If the new active shell is recognized as a dialog by default, then
238: * create some submissions, remember them, and submit them for
239: * processing.
240: */
241: if ((newShell != null) && (!newShell.isDisposed())) {
242: final Collection newActivations;
243:
244: if ((newShell.getParent() != null)
245: && (registeredWindows.get(newShell) == null)) {
246: // This is a dialog by default.
247: newActivations = new ArrayList();
248: final Expression expression = new ActiveShellExpression(
249: newShell);
250: final IContextActivation dialogWindowActivation = new ContextActivation(
251: IContextService.CONTEXT_ID_DIALOG_AND_WINDOW,
252: expression, contextService);
253: activateContext(dialogWindowActivation);
254: newActivations.add(dialogWindowActivation);
255: final IContextActivation dialogActivation = new ContextActivation(
256: IContextService.CONTEXT_ID_DIALOG, expression,
257: contextService);
258: activateContext(dialogActivation);
259: newActivations.add(dialogActivation);
260: registeredWindows.put(null, newActivations);
261:
262: /*
263: * Make sure the submissions will be removed in event of
264: * disposal. This is really just a paranoid check. The
265: * "oldSubmissions" code above should take care of this.
266: */
267: newShell.addDisposeListener(new DisposeListener() {
268:
269: /*
270: * (non-Javadoc)
271: *
272: * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
273: */
274: public void widgetDisposed(DisposeEvent e) {
275: registeredWindows.remove(null);
276: if (!newShell.isDisposed()) {
277: newShell.removeDisposeListener(this );
278: }
279:
280: /*
281: * In the case where a dispose has happened, we are
282: * expecting an activation event to arrive at some point
283: * in the future. If we process the submissions now,
284: * then we will update the activeShell before
285: * checkWindowType is called. This means that dialogs
286: * won't be recognized as dialogs.
287: */
288: final Iterator newActivationItr = newActivations
289: .iterator();
290: while (newActivationItr.hasNext()) {
291: deactivateContext((IContextActivation) newActivationItr
292: .next());
293: }
294: }
295: });
296:
297: } else {
298: // Shells that are not dialogs by default must register.
299: newActivations = null;
300:
301: }
302: }
303: }
304:
305: /**
306: * Returns a subset of the given <code>activations</code> containing only
307: * those that are active
308: *
309: * @param activations
310: * The activations to trim; must not be <code>null</code>, but
311: * may be empty.
312: * @return <code>true</code> if there is at least one active context;
313: * <code>false</code> otherwise.
314: */
315: private final boolean containsActive(final Collection activations) {
316: final Iterator activationItr = activations.iterator();
317: while (activationItr.hasNext()) {
318: final IContextActivation activation = (IContextActivation) activationItr
319: .next();
320: if (evaluate(activation)) {
321: return true;
322: }
323: }
324:
325: return false;
326: }
327:
328: /**
329: * Removes an activation for a context on the workbench. This will remove it
330: * from the master list, and update the appropriate context, if necessary.
331: *
332: * @param activation
333: * The activation; must not be <code>null</code>.
334: */
335: final void deactivateContext(final IContextActivation activation) {
336: // First we update the handlerActivationsByCommandId map.
337: final String contextId = activation.getContextId();
338: if (DEFER_EVENTS.equals(contextId)
339: || SEND_EVENTS.equals(contextId)) {
340: return;
341: }
342: final Object value = contextActivationsByContextId
343: .get(contextId);
344: if (value instanceof Collection) {
345: final Collection contextActivations = (Collection) value;
346: if (contextActivations.contains(activation)) {
347: contextActivations.remove(activation);
348: if (contextActivations.isEmpty()) {
349: contextActivationsByContextId.remove(contextId);
350: updateContext(contextId, false);
351:
352: } else if (contextActivations.size() == 1) {
353: final IContextActivation remainingActivation = (IContextActivation) contextActivations
354: .iterator().next();
355: contextActivationsByContextId.put(contextId,
356: remainingActivation);
357: updateContext(contextId,
358: evaluate(remainingActivation));
359:
360: } else {
361: updateContext(contextId,
362: containsActive(contextActivations));
363: }
364: }
365: } else if (value instanceof IContextActivation) {
366: if (value == activation) {
367: contextActivationsByContextId.remove(contextId);
368: updateContext(contextId, false);
369: }
370: }
371:
372: // Next we update the source priority bucket sort of activations.
373: final int sourcePriority = activation.getSourcePriority();
374: for (int i = 1; i <= 32; i++) {
375: if ((sourcePriority & (1 << i)) != 0) {
376: final Set activations = activationsBySourcePriority[i];
377: if (activations == null) {
378: continue;
379: }
380: activations.remove(activation);
381: if (activations.isEmpty()) {
382: activationsBySourcePriority[i] = null;
383: }
384: }
385: }
386: }
387:
388: /**
389: * Returns the currently active shell.
390: *
391: * @return The currently active shell; may be <code>null</code>.
392: */
393: final Shell getActiveShell() {
394: return (Shell) getVariable(ISources.ACTIVE_SHELL_NAME);
395: }
396:
397: /**
398: * Returns the shell type for the given shell.
399: *
400: * @param shell
401: * The shell for which the type should be determined. If this
402: * value is <code>null</code>, then
403: * <code>IWorkbenchContextSupport.TYPE_NONE</code> is returned.
404: * @return <code>IWorkbenchContextSupport.TYPE_WINDOW</code>,
405: * <code>IWorkbenchContextSupport.TYPE_DIALOG</code>, or
406: * <code>IWorkbenchContextSupport.TYPE_NONE</code>.
407: */
408: public final int getShellType(final Shell shell) {
409: // If the shell is null, then return none.
410: if (shell == null) {
411: return IContextService.TYPE_NONE;
412: }
413:
414: final Collection activations = (Collection) registeredWindows
415: .get(shell);
416: if (activations != null) {
417: // The shell is registered, so check what type it was registered as.
418: if (activations.isEmpty()) {
419: // It was registered as none.
420: return IContextService.TYPE_NONE;
421: }
422:
423: // Look for the right type of context id.
424: final Iterator activationItr = activations.iterator();
425: while (activationItr.hasNext()) {
426: final IContextActivation activation = (IContextActivation) activationItr
427: .next();
428: final String contextId = activation.getContextId();
429: if (contextId == IContextService.CONTEXT_ID_DIALOG) {
430: return IContextService.TYPE_DIALOG;
431: } else if (contextId == IContextService.CONTEXT_ID_WINDOW) {
432: return IContextService.TYPE_WINDOW;
433: }
434: }
435:
436: // This shouldn't be possible.
437: Assert
438: .isTrue(
439: false,
440: "A registered shell should have at least one submission matching TYPE_WINDOW or TYPE_DIALOG"); //$NON-NLS-1$
441: return IContextService.TYPE_NONE; // not reachable
442:
443: } else if (shell.getParent() != null) {
444: /*
445: * The shell is not registered, but it has a parent. It is therefore
446: * considered a dialog by default.
447: */
448: return IContextService.TYPE_DIALOG;
449:
450: } else {
451: /*
452: * The shell is not registered, but has no parent. It gets no key
453: * bindings.
454: */
455: return IContextService.TYPE_NONE;
456: }
457: }
458:
459: /**
460: * <p>
461: * Registers a shell to automatically promote or demote some basic types of
462: * contexts. The "In Dialogs" and "In Windows" contexts are provided by the
463: * system. This a convenience method to ensure that these contexts are
464: * promoted when the given is shell is active.
465: * </p>
466: * <p>
467: * If a shell is registered as a window, then the "In Windows" context is
468: * enabled when that shell is active. If a shell is registered as a dialog --
469: * or is not registered, but has a parent shell -- then the "In Dialogs"
470: * context is enabled when that shell is active. If the shell is registered
471: * as none -- or is not registered, but has no parent shell -- then the
472: * neither of the contexts will be enabled (by us -- someone else can always
473: * enabled them).
474: * </p>
475: * <p>
476: * If the provided shell has already been registered, then this method will
477: * change the registration.
478: * </p>
479: *
480: * @param shell
481: * The shell to register for key bindings; must not be
482: * <code>null</code>.
483: * @param type
484: * The type of shell being registered. This value must be one of
485: * the constants given in this interface.
486: *
487: * @return <code>true</code> if the shell had already been registered
488: * (i.e., the registration has changed); <code>false</code>
489: * otherwise.
490: */
491: public final boolean registerShell(final Shell shell, final int type) {
492: // We do not allow null shell registration. It is reserved.
493: if (shell == null) {
494: throw new NullPointerException("The shell was null"); //$NON-NLS-1$
495: }
496:
497: // Debugging output
498: if (DEBUG) {
499: final StringBuffer buffer = new StringBuffer(
500: "register shell '"); //$NON-NLS-1$
501: buffer.append(shell);
502: buffer.append("' as "); //$NON-NLS-1$
503: switch (type) {
504: case IContextService.TYPE_DIALOG:
505: buffer.append("dialog"); //$NON-NLS-1$
506: break;
507: case IContextService.TYPE_WINDOW:
508: buffer.append("window"); //$NON-NLS-1$
509: break;
510: case IContextService.TYPE_NONE:
511: buffer.append("none"); //$NON-NLS-1$
512: break;
513: default:
514: buffer.append("unknown"); //$NON-NLS-1$
515: break;
516: }
517: Tracing.printTrace(TRACING_COMPONENT, buffer.toString());
518: }
519:
520: // Build the list of submissions.
521: final List activations = new ArrayList();
522: Expression expression;
523: IContextActivation dialogWindowActivation;
524: switch (type) {
525: case IContextService.TYPE_DIALOG:
526: expression = new ActiveShellExpression(shell);
527: dialogWindowActivation = new ContextActivation(
528: IContextService.CONTEXT_ID_DIALOG_AND_WINDOW,
529: expression, contextService);
530: activateContext(dialogWindowActivation);
531: activations.add(dialogWindowActivation);
532: final IContextActivation dialogActivation = new ContextActivation(
533: IContextService.CONTEXT_ID_DIALOG, expression,
534: contextService);
535: activateContext(dialogActivation);
536: activations.add(dialogActivation);
537: break;
538: case IContextService.TYPE_NONE:
539: break;
540: case IContextService.TYPE_WINDOW:
541: expression = new ActiveShellExpression(shell);
542: dialogWindowActivation = new ContextActivation(
543: IContextService.CONTEXT_ID_DIALOG_AND_WINDOW,
544: expression, contextService);
545: activateContext(dialogWindowActivation);
546: activations.add(dialogWindowActivation);
547: final IContextActivation windowActivation = new ContextActivation(
548: IContextService.CONTEXT_ID_WINDOW, expression,
549: contextService);
550: activateContext(windowActivation);
551: activations.add(windowActivation);
552: break;
553: default:
554: throw new IllegalArgumentException(
555: "The type is not recognized: " //$NON-NLS-1$
556: + type);
557: }
558:
559: // Check to see if the activations are already present.
560: boolean returnValue = false;
561: final Collection previousActivations = (Collection) registeredWindows
562: .get(shell);
563: if (previousActivations != null) {
564: returnValue = true;
565: final Iterator previousActivationItr = previousActivations
566: .iterator();
567: while (previousActivationItr.hasNext()) {
568: final IContextActivation activation = (IContextActivation) previousActivationItr
569: .next();
570: deactivateContext(activation);
571: }
572: }
573:
574: // Add the new submissions, and force some reprocessing to occur.
575: registeredWindows.put(shell, activations);
576:
577: /*
578: * Remember the dispose listener so that we can remove it later if we
579: * unregister the shell.
580: */
581: final DisposeListener shellDisposeListener = new DisposeListener() {
582:
583: /*
584: * (non-Javadoc)
585: *
586: * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
587: */
588: public void widgetDisposed(DisposeEvent e) {
589: registeredWindows.remove(shell);
590: if (!shell.isDisposed()) {
591: shell.removeDisposeListener(this );
592: }
593:
594: /*
595: * In the case where a dispose has happened, we are expecting an
596: * activation event to arrive at some point in the future. If we
597: * process the submissions now, then we will update the
598: * activeShell before checkWindowType is called. This means that
599: * dialogs won't be recognized as dialogs.
600: */
601: final Iterator activationItr = activations.iterator();
602: while (activationItr.hasNext()) {
603: deactivateContext((IContextActivation) activationItr
604: .next());
605: }
606: }
607: };
608:
609: // Make sure the submissions will be removed in event of disposal.
610: shell.addDisposeListener(shellDisposeListener);
611: shell.setData(DISPOSE_LISTENER, shellDisposeListener);
612:
613: return returnValue;
614: }
615:
616: /**
617: * Carries out the actual source change notification. It assumed that by the
618: * time this method is called, <code>context</code> is up-to-date with the
619: * current state of the application.
620: *
621: * @param sourcePriority
622: * A bit mask of all the source priorities that have changed.
623: */
624: protected final void sourceChanged(final int sourcePriority) {
625: // If tracing, then track how long it takes to process the activations.
626: long startTime = 0L;
627: if (DEBUG_PERFORMANCE) {
628: startTime = System.currentTimeMillis();
629: }
630:
631: /*
632: * In this first phase, we cycle through all of the activations that
633: * could have potentially changed. Each such activation is added to a
634: * set for future processing. We add it to a set so that we avoid
635: * handling any individual activation more than once.
636: */
637: final Set activationsToRecompute = new HashSet(
638: ACTIVATIONS_TO_RECOMPUTE_SIZE);
639: for (int i = 1; i <= 32; i++) {
640: if ((sourcePriority & (1 << i)) != 0) {
641: final Collection activations = activationsBySourcePriority[i];
642: if (activations != null) {
643: final Iterator activationItr = activations
644: .iterator();
645: while (activationItr.hasNext()) {
646: activationsToRecompute
647: .add(activationItr.next());
648: }
649: }
650: }
651: }
652:
653: /*
654: * For every activation, we recompute its active state, and check
655: * whether it has changed. If it has changed, then we take note of the
656: * context identifier so we can update the context later.
657: */
658: final Collection changedContextIds = new ArrayList(
659: activationsToRecompute.size());
660: final Iterator activationItr = activationsToRecompute
661: .iterator();
662: while (activationItr.hasNext()) {
663: final IContextActivation activation = (IContextActivation) activationItr
664: .next();
665: final boolean currentActive = evaluate(activation);
666: activation.clearResult();
667: final boolean newActive = evaluate(activation);
668: if (newActive != currentActive) {
669: changedContextIds.add(activation.getContextId());
670: }
671: }
672:
673: try {
674: contextManager.addActiveContext(DEFER_EVENTS);
675: /*
676: * For every context identifier with a changed activation, we
677: * resolve conflicts and trigger an update.
678: */
679: final Iterator changedContextIdItr = changedContextIds
680: .iterator();
681: while (changedContextIdItr.hasNext()) {
682: final String contextId = (String) changedContextIdItr
683: .next();
684: final Object value = contextActivationsByContextId
685: .get(contextId);
686: if (value instanceof IContextActivation) {
687: final IContextActivation activation = (IContextActivation) value;
688: updateContext(contextId, evaluate(activation));
689: } else if (value instanceof Collection) {
690: updateContext(contextId,
691: containsActive((Collection) value));
692: } else {
693: updateContext(contextId, false);
694: }
695: }
696: } finally {
697: contextManager.addActiveContext(SEND_EVENTS);
698: }
699:
700: // If tracing performance, then print the results.
701: if (DEBUG_PERFORMANCE) {
702: final long elapsedTime = System.currentTimeMillis()
703: - startTime;
704: final int size = activationsToRecompute.size();
705: if (size > 0) {
706: Tracing
707: .printTrace(
708: TRACING_COMPONENT,
709: size
710: + " activations recomputed in " + elapsedTime + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
711: }
712: }
713: }
714:
715: /**
716: * <p>
717: * Unregisters a shell that was previously registered. After this method
718: * completes, the shell will be treated as if it had never been registered
719: * at all. If you have registered a shell, you should ensure that this
720: * method is called when the shell is disposed. Otherwise, a potential
721: * memory leak will exist.
722: * </p>
723: * <p>
724: * If the shell was never registered, or if the shell is <code>null</code>,
725: * then this method returns <code>false</code> and does nothing.
726: *
727: * @param shell
728: * The shell to be unregistered; does nothing if this value is
729: * <code>null</code>.
730: *
731: * @return <code>true</code> if the shell had been registered;
732: * <code>false</code> otherwise.
733: */
734: public final boolean unregisterShell(final Shell shell) {
735: // Don't allow this method to play with the special null slot.
736: if (shell == null) {
737: return false;
738: }
739:
740: /*
741: * If we're unregistering the shell but we're not about to dispose it,
742: * then we'll end up leaking the DisposeListener unless we remove it
743: * here.
744: */
745: if (!shell.isDisposed()) {
746: final DisposeListener oldListener = (DisposeListener) shell
747: .getData(DISPOSE_LISTENER);
748: if (oldListener != null) {
749: shell.removeDisposeListener(oldListener);
750: }
751: }
752:
753: Collection previousActivations = (Collection) registeredWindows
754: .get(shell);
755: if (previousActivations != null) {
756: registeredWindows.remove(shell);
757:
758: final Iterator previousActivationItr = previousActivations
759: .iterator();
760: while (previousActivationItr.hasNext()) {
761: final IContextActivation activation = (IContextActivation) previousActivationItr
762: .next();
763: deactivateContext(activation);
764: }
765: return true;
766: }
767:
768: return false;
769: }
770:
771: /**
772: * Updates the context with the given context activation.
773: *
774: * @param contextId
775: * The identifier of the context which should be updated; must
776: * not be <code>null</code>.
777: * @param active
778: * Whether the context should be active; <code>false</code>
779: * otherwise.
780: */
781: private final void updateContext(final String contextId,
782: final boolean active) {
783: if (active) {
784: contextManager.addActiveContext(contextId);
785: } else {
786: contextManager.removeActiveContext(contextId);
787: }
788: }
789:
790: /**
791: * Updates this authority's evaluation context. If the changed variable is
792: * the <code>ISources.ACTIVE_SHELL_NAME</code> variable, then this also
793: * triggers an update of the shell-specific contexts. For example, if a
794: * dialog becomes active, then the dialog context will be activated by this
795: * method.
796: *
797: * @param name
798: * The name of the variable to update; must not be
799: * <code>null</code>.
800: * @param value
801: * The new value of the variable. If this value is
802: * <code>null</code>, then the variable is removed.
803: */
804: protected final void updateEvaluationContext(final String name,
805: final Object value) {
806: /*
807: * Bug 84056. If we update the active workbench window, then we risk
808: * falling back to that shell when the active shell has registered as
809: * "none".
810: */
811: if ((name != null)
812: && (!ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME
813: .equals(name))) {
814: /*
815: * We need to track shell activation ourselves, as some special
816: * contexts are automatically activated in response to different
817: * types of shells becoming active.
818: */
819: if (ISources.ACTIVE_SHELL_NAME.equals(name)) {
820: checkWindowType((Shell) value,
821: (Shell) getVariable(ISources.ACTIVE_SHELL_NAME));
822: }
823:
824: // Update the evaluation context itself.
825: changeVariable(name, value);
826: }
827: }
828:
829: /**
830: * <p>
831: * Bug 95792. A mechanism by which the key binding architecture can force an
832: * update of the contexts (based on the active shell) before trying to
833: * execute a command. This mechanism is required for GTK+ only.
834: * </p>
835: * <p>
836: * DO NOT CALL THIS METHOD.
837: * </p>
838: */
839: final void updateShellKludge() {
840: updateCurrentState();
841: sourceChanged(ISources.ACTIVE_SHELL);
842: }
843: }
|