| /******************************************************************************* |
| * Copyright (c) 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.contexts.ws; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IPageListener; |
| import org.eclipse.ui.IPartListener; |
| import org.eclipse.ui.IPerspectiveDescriptor; |
| import org.eclipse.ui.IPerspectiveListener; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchSite; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.contexts.EnabledSubmission; |
| import org.eclipse.ui.contexts.IContext; |
| import org.eclipse.ui.contexts.IContextManager; |
| import org.eclipse.ui.contexts.IWorkbenchContextSupport; |
| import org.eclipse.ui.contexts.NotDefinedException; |
| import org.eclipse.ui.internal.Workbench; |
| import org.eclipse.ui.internal.contexts.ContextManagerFactory; |
| import org.eclipse.ui.internal.contexts.IMutableContextManager; |
| import org.eclipse.ui.internal.contexts.ProxyContextManager; |
| import org.eclipse.ui.internal.keys.WorkbenchKeyboard; |
| import org.eclipse.ui.internal.misc.Policy; |
| import org.eclipse.ui.internal.util.Util; |
| |
| /** |
| * Provides support for contexts within the workbench -- including key bindings, |
| * and some default contexts for shell types. |
| * |
| * @since 3.0 |
| */ |
| public class WorkbenchContextSupport implements IWorkbenchContextSupport { |
| |
| /** |
| * Whether the workbench context support should kick into debugging mode. |
| * This causes the list of context identifier to the be reported before |
| * every call to change the context identifiers. |
| */ |
| private static final boolean DEBUG = Policy.DEBUG_CONTEXTS; |
| |
| /** |
| * The number of stack trace elements to show when the contexts have |
| * changed. This is used for debugging purposes to show what caused a |
| * context switch to occur. |
| */ |
| private static final int DEBUG_STACK_LENGTH_TO_SHOW = 5; |
| |
| /** |
| * Whether the workbench context support should kick into verbose debugging |
| * mode. This causes each context change to print out a bit of the current |
| * stack -- letting you know what caused the context change to occur. |
| */ |
| private static final boolean DEBUG_VERBOSE = Policy.DEBUG_CONTEXTS_VERBOSE; |
| |
| /** |
| * Creates a tree of context identifiers, representing the hierarchical |
| * structure of the given contexts. The tree is structured as a mapping from |
| * child to parent. |
| * |
| * @param contextIds |
| * The set of context identifiers to be converted into a tree; |
| * must not be <code>null</code>. |
| * @return The tree of contexts to use; may be empty, but never |
| * <code>null</code>. The keys and values are both strings. |
| */ |
| public final Map createContextTreeFor(final Set contextIds) { |
| final Map contextTree = new HashMap(); |
| final IContextManager contextManager = getContextManager(); |
| |
| final Iterator contextIdItr = contextIds.iterator(); |
| while (contextIdItr.hasNext()) { |
| String childContextId = (String) contextIdItr.next(); |
| while (childContextId != null) { |
| final IContext childContext = contextManager |
| .getContext(childContextId); |
| |
| try { |
| final String parentContextId = childContext.getParentId(); |
| contextTree.put(childContextId, parentContextId); |
| childContextId = parentContextId; |
| } catch (final NotDefinedException e) { |
| break; // stop ascending |
| } |
| } |
| } |
| |
| return contextTree; |
| } |
| |
| /** |
| * Listens for shell activation events, and updates the list of enabled |
| * contexts appropriately. This is used to keep the enabled contexts |
| * synchronized with respect to the <code>activeShell</code> condition. |
| */ |
| private Listener activationListener = new Listener() { |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) |
| */ |
| public void handleEvent(Event event) { |
| checkWindowType(event.display.getActiveShell()); |
| } |
| }; |
| |
| /** |
| * The currently active shell. This value is never <code>null</code>. |
| */ |
| private Shell activeShell; |
| |
| private IWorkbenchSite activeWorkbenchSite; |
| |
| /** |
| * The workbench window on which the listeners are currently attached. |
| */ |
| private IWorkbenchWindow activeWorkbenchWindow; |
| |
| private Map enabledSubmissionsByContextId = new HashMap(); |
| |
| /** |
| * The key binding support for the contexts. In the workbench, key bindings |
| * are intimately tied to the context mechanism. |
| */ |
| private final WorkbenchKeyboard keyboard; |
| |
| /** |
| * Whether the key binding service is currently active. That is, whether it |
| * is currently listening for (and possibly eating) key events on the |
| * display. |
| */ |
| private volatile boolean keyFilterEnabled; |
| |
| private IMutableContextManager mutableContextManager; |
| |
| private IPageListener pageListener = new IPageListener() { |
| |
| public void pageActivated(IWorkbenchPage workbenchPage) { |
| processEnabledSubmissions(false); |
| } |
| |
| public void pageClosed(IWorkbenchPage workbenchPage) { |
| processEnabledSubmissions(false); |
| } |
| |
| public void pageOpened(IWorkbenchPage workbenchPage) { |
| processEnabledSubmissions(false); |
| } |
| }; |
| |
| private IPartListener partListener = new IPartListener() { |
| |
| public void partActivated(IWorkbenchPart workbenchPart) { |
| processEnabledSubmissions(false); |
| } |
| |
| public void partBroughtToTop(IWorkbenchPart workbenchPart) { |
| processEnabledSubmissions(false); |
| } |
| |
| public void partClosed(IWorkbenchPart workbenchPart) { |
| processEnabledSubmissions(false); |
| } |
| |
| public void partDeactivated(IWorkbenchPart workbenchPart) { |
| processEnabledSubmissions(false); |
| } |
| |
| public void partOpened(IWorkbenchPart workbenchPart) { |
| processEnabledSubmissions(false); |
| } |
| }; |
| |
| private IPerspectiveListener perspectiveListener = new IPerspectiveListener() { |
| |
| public void perspectiveActivated(IWorkbenchPage workbenchPage, |
| IPerspectiveDescriptor perspectiveDescriptor) { |
| processEnabledSubmissions(false); |
| } |
| |
| public void perspectiveChanged(IWorkbenchPage workbenchPage, |
| IPerspectiveDescriptor perspectiveDescriptor, String changeId) { |
| processEnabledSubmissions(false); |
| } |
| }; |
| |
| private ProxyContextManager proxyContextManager; |
| |
| /** |
| * This is a map of shell to a list of submissions. When a shell is |
| * registered, it is added to this map with the list of submissions that |
| * should be submitted when the shell is active. When the shell is |
| * deactivated, this same list should be withdrawn. A shell is removed from |
| * this map using the {@link #unregisterShell(Shell)}method. This value may |
| * be empty, but is never <code>null</code>. The <code>null</code> key |
| * is reserved for active shells that have not been registered but have a |
| * parent (i.e., default dialog service). |
| */ |
| private final Map registeredWindows = new WeakHashMap(); |
| |
| private Workbench workbench; |
| |
| /** |
| * Constructs a new instance of <code>WorkbenchCommandSupport</code>. |
| * This attaches the key binding support, and adds a global shell activation |
| * filter. |
| * |
| * @param workbenchToSupport |
| * The workbench that needs to be supported by this instance; |
| * must not be <code>null</code>. |
| */ |
| public WorkbenchContextSupport(final Workbench workbenchToSupport) { |
| workbench = workbenchToSupport; |
| mutableContextManager = ContextManagerFactory |
| .getMutableContextManager(); |
| proxyContextManager = new ProxyContextManager(mutableContextManager); |
| |
| // Hook up the key binding support. |
| keyboard = new WorkbenchKeyboard(workbench, workbench |
| .getActivitySupport().getActivityManager(), workbench |
| .getCommandSupport().getCommandManager()); |
| setKeyFilterEnabled(true); |
| |
| // And hook up a shell activation filter. |
| workbenchToSupport.getDisplay().addFilter(SWT.Activate, |
| activationListener); |
| } |
| |
| public void addEnabledSubmission(EnabledSubmission enabledSubmission) { |
| addEnabledSubmissions(Collections.singleton(enabledSubmission)); |
| } |
| |
| public void addEnabledSubmissions(Collection enabledSubmissions) { |
| enabledSubmissions = Util.safeCopy(enabledSubmissions, |
| EnabledSubmission.class); |
| |
| for (Iterator iterator = enabledSubmissions.iterator(); iterator |
| .hasNext();) { |
| EnabledSubmission enabledSubmission = (EnabledSubmission) iterator |
| .next(); |
| String contextId = enabledSubmission.getContextId(); |
| List enabledSubmissions2 = (List) enabledSubmissionsByContextId |
| .get(contextId); |
| |
| if (enabledSubmissions2 == null) { |
| enabledSubmissions2 = new ArrayList(); |
| enabledSubmissionsByContextId.put(contextId, |
| enabledSubmissions2); |
| } |
| |
| enabledSubmissions2.add(enabledSubmission); |
| } |
| |
| processEnabledSubmissions(true); |
| } |
| |
| /** |
| * Checks whether the new active shell is registered. If it is already |
| * registered, then it does no work. If it is not registered, then it checks |
| * what type of contexts the shell should have by default. This is |
| * determined by parenting. A shell with no parent receives no contexts. A |
| * shell with a parent, receives the dialog contexts. |
| * |
| * @param newShell |
| * The newly active shell; may be <code>null</code> or |
| * disposed. |
| */ |
| private final void checkWindowType(final Shell newShell) { |
| boolean submissionsProcessed = false; |
| final Shell oldShell = activeShell; |
| |
| /* |
| * If the previous active shell was recognized as a dialog by default, |
| * then remove its submissions. |
| */ |
| List oldSubmissions = (List) registeredWindows.get(oldShell); |
| if (oldSubmissions == null) { |
| /* |
| * The old shell wasn't registered. So, we need to check if it was |
| * considered a dialog by default. |
| */ |
| oldSubmissions = (List) registeredWindows.get(null); |
| if (oldSubmissions != null) { |
| removeEnabledSubmissions(oldSubmissions); |
| submissionsProcessed = true; |
| } |
| } |
| |
| /* |
| * If the new active shell is recognized as a dialog by default, then |
| * create some submissions, remember them, and submit them for |
| * processing. |
| */ |
| if ((newShell != null) && (!newShell.isDisposed()) |
| && (newShell.getParent() != null) |
| && (registeredWindows.get(newShell) == null)) { |
| final List newSubmissions = new ArrayList(); |
| newSubmissions.add(new EnabledSubmission(null, newShell, null, |
| CONTEXT_ID_DIALOG_AND_WINDOW)); |
| newSubmissions.add(new EnabledSubmission(null, newShell, null, |
| CONTEXT_ID_DIALOG)); |
| addEnabledSubmissions(newSubmissions); |
| registeredWindows.put(null, newSubmissions); |
| submissionsProcessed = true; |
| |
| /* |
| * Make sure the submissions will be removed in event of disposal. |
| * This is really just a paranoid check. The "oldSubmissions" code |
| * above should take care of this. |
| */ |
| newShell.addDisposeListener(new DisposeListener() { |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) |
| */ |
| public void widgetDisposed(DisposeEvent e) { |
| registeredWindows.remove(null); |
| removeEnabledSubmissions(newSubmissions); |
| } |
| }); |
| } |
| |
| // If we still haven't reprocessed the submissions, then do it now. |
| if (!submissionsProcessed) { |
| processEnabledSubmissions(false, newShell); |
| } |
| } |
| |
| public IContextManager getContextManager() { |
| return proxyContextManager; |
| } |
| |
| /** |
| * An accessor for the underlying key binding support. This method is |
| * internal, and is not intended to be used by clients. It is currently only |
| * used for testing purposes. |
| * |
| * @return A reference to the key binding support; never <code>null</code>. |
| */ |
| public final WorkbenchKeyboard getKeyboard() { |
| return keyboard; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.contexts.IWorkbenchContextSupport#isKeyFilterEnabled() |
| */ |
| public boolean isKeyFilterEnabled() { |
| synchronized (keyboard) { |
| return keyFilterEnabled; |
| } |
| } |
| |
| private void processEnabledSubmissions(boolean force) { |
| processEnabledSubmissions(force, workbench.getDisplay() |
| .getActiveShell()); |
| } |
| |
| /** |
| * If you use this method, I will break your legs. |
| * |
| * TODO See WorkbenchKeyboard. Switch to private when Bug 56231 is resolved. |
| */ |
| public void processEnabledSubmissions(boolean force, |
| final Shell newActiveShell) { |
| IWorkbenchSite newActiveWorkbenchSite = null; |
| final IWorkbenchWindow newActiveWorkbenchWindow = workbench |
| .getActiveWorkbenchWindow(); |
| boolean update = false; |
| |
| // Update the active shell, and swap the listener. |
| if (activeShell != newActiveShell) { |
| activeShell = newActiveShell; |
| update = true; |
| } |
| |
| // Update the active workbench window, and swap the listeners. |
| if (activeWorkbenchWindow != newActiveWorkbenchWindow) { |
| if (activeWorkbenchWindow != null) { |
| activeWorkbenchWindow.removePageListener(pageListener); |
| activeWorkbenchWindow |
| .removePerspectiveListener(perspectiveListener); |
| activeWorkbenchWindow.getPartService().removePartListener( |
| partListener); |
| } |
| |
| if (newActiveWorkbenchWindow != null) { |
| newActiveWorkbenchWindow.addPageListener(pageListener); |
| newActiveWorkbenchWindow |
| .addPerspectiveListener(perspectiveListener); |
| newActiveWorkbenchWindow.getPartService().addPartListener( |
| partListener); |
| } |
| |
| activeWorkbenchWindow = newActiveWorkbenchWindow; |
| update = true; |
| } |
| |
| /* |
| * Get a reference to the active workbench site on the new active |
| * workbench window. |
| */ |
| if (newActiveWorkbenchWindow != null) { |
| IWorkbenchPage activeWorkbenchPage = newActiveWorkbenchWindow |
| .getActivePage(); |
| |
| if (activeWorkbenchPage != null) { |
| IWorkbenchPart activeWorkbenchPart = activeWorkbenchPage |
| .getActivePart(); |
| |
| if (activeWorkbenchPart != null) |
| newActiveWorkbenchSite = activeWorkbenchPart.getSite(); |
| } |
| } |
| |
| if (force || update |
| || !Util.equals(activeWorkbenchSite, newActiveWorkbenchSite)) { |
| activeWorkbenchSite = newActiveWorkbenchSite; |
| final Set enabledContextIds = new HashSet(); |
| |
| for (Iterator iterator = enabledSubmissionsByContextId.entrySet() |
| .iterator(); iterator.hasNext();) { |
| final Map.Entry entry = (Map.Entry) iterator.next(); |
| final String contextId = (String) entry.getKey(); |
| final List enabledSubmissions = (List) entry.getValue(); |
| |
| for (int i = 0; i < enabledSubmissions.size(); i++) { |
| EnabledSubmission enabledSubmission = (EnabledSubmission) enabledSubmissions |
| .get(i); |
| |
| Shell activeShell2 = enabledSubmission.getActiveShell(); |
| |
| if (activeShell2 != null && activeShell2 != newActiveShell) |
| continue; |
| |
| IWorkbenchSite activeWorkbenchSite2 = enabledSubmission |
| .getActiveWorkbenchPartSite(); |
| |
| if (activeWorkbenchSite2 != null |
| && activeWorkbenchSite2 != newActiveWorkbenchSite) |
| continue; |
| |
| enabledContextIds.add(contextId); |
| break; |
| } |
| } |
| |
| // Check to see whether a dialog or window is active. |
| Iterator contextIdItr = enabledContextIds.iterator(); |
| boolean dialog = false; |
| boolean window = false; |
| while (contextIdItr.hasNext()) { |
| final String contextId = (String) contextIdItr.next(); |
| if (CONTEXT_ID_DIALOG.equals(contextId)) { |
| dialog = true; |
| continue; |
| } |
| if (CONTEXT_ID_WINDOW.equals(contextId)) { |
| window = true; |
| continue; |
| } |
| } |
| |
| /* |
| * Remove all context identifiers for contexts whose parents are |
| * dialog or window, and the corresponding dialog or window context |
| * is not active. |
| */ |
| try { |
| contextIdItr = enabledContextIds.iterator(); |
| while (contextIdItr.hasNext()) { |
| String contextId = (String) contextIdItr.next(); |
| IContext context = mutableContextManager |
| .getContext(contextId); |
| String parentId = context.getParentId(); |
| while (parentId != null) { |
| if (CONTEXT_ID_DIALOG.equals(parentId)) { |
| if (!dialog) { |
| contextIdItr.remove(); |
| } |
| break; |
| } |
| if (CONTEXT_ID_WINDOW.equals(parentId)) { |
| if (!window) { |
| contextIdItr.remove(); |
| } |
| break; |
| } |
| if (CONTEXT_ID_DIALOG_AND_WINDOW.equals(parentId)) { |
| if ((!window) && (!dialog)) { |
| contextIdItr.remove(); |
| } |
| break; |
| } |
| |
| context = mutableContextManager.getContext(parentId); |
| parentId = context.getParentId(); |
| } |
| } |
| } catch (NotDefinedException e) { |
| if (DEBUG) { |
| System.out.println("CONTEXTS >>> NotDefinedException('" //$NON-NLS-1$ |
| + e.getMessage() |
| + "') while filtering dialog/window contexts"); //$NON-NLS-1$ |
| } |
| } |
| |
| if ((DEBUG) |
| && (!mutableContextManager.getEnabledContextIds().equals( |
| enabledContextIds))) { |
| System.out.println("CONTEXTS >>> " + enabledContextIds); //$NON-NLS-1$ |
| if (DEBUG_VERBOSE) { |
| final Exception exception = new Exception(); |
| exception.fillInStackTrace(); |
| final StackTraceElement[] stackTrace = exception |
| .getStackTrace(); |
| final int elementsToShow = (stackTrace.length < DEBUG_STACK_LENGTH_TO_SHOW) ? stackTrace.length |
| : DEBUG_STACK_LENGTH_TO_SHOW; |
| for (int i = 0; i < elementsToShow; i++) { |
| final StackTraceElement element = stackTrace[i]; |
| System.out.println("CONTEXTS >>> " //$NON-NLS-1$ |
| + element.toString()); |
| } |
| } |
| } |
| |
| // Set the list of enabled identifiers to be the stripped list. |
| mutableContextManager.setEnabledContextIds(enabledContextIds); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.contexts.IWorkbenchContextSupport#registerShell(org.eclipse.swt.widgets.Shell, |
| * int) |
| */ |
| public boolean registerShell(final Shell shell, final int type) { |
| // We do not allow null shell registration. It is reserved. |
| if (shell == null) { throw new NullPointerException( |
| "The shell was null"); //$NON-NLS-1$ |
| } |
| |
| // Build the list of submissions. |
| final List submissions = new ArrayList(); |
| switch (type) { |
| case TYPE_DIALOG: |
| submissions.add(new EnabledSubmission(null, shell, null, |
| CONTEXT_ID_DIALOG_AND_WINDOW)); |
| submissions.add(new EnabledSubmission(null, shell, null, |
| CONTEXT_ID_DIALOG)); |
| break; |
| case TYPE_NONE: |
| break; |
| case TYPE_WINDOW: |
| submissions.add(new EnabledSubmission(null, shell, null, |
| CONTEXT_ID_DIALOG_AND_WINDOW)); |
| submissions.add(new EnabledSubmission(null, shell, null, |
| CONTEXT_ID_WINDOW)); |
| break; |
| default: |
| throw new IllegalArgumentException("The type is not recognized: " //$NON-NLS-1$ |
| + type); |
| } |
| |
| // Check to see if the submissions are already present. |
| boolean returnValue = false; |
| List previousSubmissions = (List) registeredWindows.get(shell); |
| if (previousSubmissions != null) { |
| returnValue = true; |
| removeEnabledSubmissions(previousSubmissions); |
| } |
| |
| // Add the new submissions, and force some reprocessing to occur. |
| registeredWindows.put(shell, submissions); |
| addEnabledSubmissions(submissions); |
| |
| // Make sure the submissions will be removed in event of disposal. |
| shell.addDisposeListener(new DisposeListener() { |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) |
| */ |
| public void widgetDisposed(DisposeEvent e) { |
| registeredWindows.remove(shell); |
| removeEnabledSubmissions(submissions); |
| } |
| }); |
| |
| return returnValue; |
| } |
| |
| public void removeEnabledSubmission(EnabledSubmission enabledSubmission) { |
| removeEnabledSubmissions(Collections.singleton(enabledSubmission)); |
| } |
| |
| public void removeEnabledSubmissions(Collection enabledSubmissions) { |
| enabledSubmissions = Util.safeCopy(enabledSubmissions, |
| EnabledSubmission.class); |
| |
| for (Iterator iterator = enabledSubmissions.iterator(); iterator |
| .hasNext();) { |
| EnabledSubmission enabledSubmission = (EnabledSubmission) iterator |
| .next(); |
| String contextId = enabledSubmission.getContextId(); |
| List enabledSubmissions2 = (List) enabledSubmissionsByContextId |
| .get(contextId); |
| |
| if (enabledSubmissions2 != null) { |
| enabledSubmissions2.remove(enabledSubmission); |
| |
| if (enabledSubmissions2.isEmpty()) |
| enabledSubmissionsByContextId.remove(contextId); |
| } |
| } |
| |
| processEnabledSubmissions(true); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.contexts.IWorkbenchContextSupport#setKeyFilterEnabled(boolean) |
| */ |
| public void setKeyFilterEnabled(boolean enabled) { |
| synchronized (keyboard) { |
| Display currentDisplay = Display.getCurrent(); |
| Listener keyFilter = keyboard.getKeyDownFilter(); |
| |
| if (enabled) { |
| currentDisplay.addFilter(SWT.KeyDown, keyFilter); |
| currentDisplay.addFilter(SWT.Traverse, keyFilter); |
| } else { |
| currentDisplay.removeFilter(SWT.KeyDown, keyFilter); |
| currentDisplay.removeFilter(SWT.Traverse, keyFilter); |
| } |
| |
| keyFilterEnabled = enabled; |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.contexts.IWorkbenchContextSupport#unregisterShell(org.eclipse.swt.widgets.Shell) |
| */ |
| public boolean unregisterShell(Shell shell) { |
| // Don't allow this method to play with the special null slot. |
| if (shell == null) { return false; } |
| |
| List previousSubmissions = (List) registeredWindows.get(shell); |
| if (previousSubmissions != null) { |
| registeredWindows.remove(shell); |
| removeEnabledSubmissions(previousSubmissions); |
| return true; |
| } |
| |
| return false; |
| } |
| } |