blob: 83e3c1e371cb2f9c4381471d9219604073d36ea5 [file] [log] [blame]
/*******************************************************************************
* 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.commands.ws;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.swt.SWT;
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.commands.HandlerSubmission;
import org.eclipse.ui.commands.ICommandManager;
import org.eclipse.ui.commands.IWorkbenchCommandSupport;
import org.eclipse.ui.commands.Priority;
import org.eclipse.ui.internal.Workbench;
import org.eclipse.ui.internal.commands.CommandManagerFactory;
import org.eclipse.ui.internal.commands.IMutableCommandManager;
import org.eclipse.ui.internal.commands.MutableCommandManager;
import org.eclipse.ui.internal.misc.Policy;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.keys.KeyFormatterFactory;
import org.eclipse.ui.keys.SWTKeySupport;
/**
* Provides command support in terms of the workbench.
*
* @since 3.0
*/
public class WorkbenchCommandSupport implements IWorkbenchCommandSupport {
/**
* Whether the workbench command support should kick into debugging mode.
* This causes the unresolvable handler conflicts to be printed to the
* console.
*/
private static final boolean DEBUG = Policy.DEBUG_HANDLERS;
/**
* Whether the workbench command support should kick into verbose debugging
* mode. This causes the resolvable handler conflicts to be printed to the
* console.
*/
private static final boolean DEBUG_VERBOSE = Policy.DEBUG_HANDLERS
&& Policy.DEBUG_HANDLERS_VERBOSE;
/**
* The command identifier to which the verbose output should be restricted.
*/
private static final String DEBUG_VERBOSE_COMMAND_ID = Policy.DEBUG_HANDLERS_VERBOSE_COMMAND_ID;
static {
MutableCommandManager.DEBUG_HANDLERS = Policy.DEBUG_HANDLERS
&& Policy.DEBUG_HANDLERS_VERBOSE;
MutableCommandManager.DEBUG_HANDLERS_COMMAND_ID = Policy.DEBUG_HANDLERS_VERBOSE_COMMAND_ID;
MutableCommandManager.DEBUG_COMMAND_EXECUTION = Policy.DEBUG_KEY_BINDINGS_VERBOSE;
}
/**
* Listens for shell activation events, and updates the list of enabled
* handlers appropriately. This is used to keep the enabled handlers
* 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) {
processHandlerSubmissions(false, event.display.getActiveShell());
}
};
/**
* The currently active shell. This value is never <code>null</code>.
*/
private Shell activeShell;
/**
* The active workbench site when the handler submissions were last
* processed. This value may be <code>null</code> if no workbench site is
* selected.
*/
private IWorkbenchSite activeWorkbenchSite;
/**
* The active workbench window when the handler submissions were last
* processed. This value may be <code>null</code> if Eclipse is not the
* active application.
*/
private IWorkbenchWindow activeWorkbenchWindow;
/**
* The map of the handler submissions indexed by command identifier. This
* value is never <code>null</code>, but may be empty. The command
* identifiers are strings, and the handler submissions are instances of
* <code>HandlerSubmission</code>.
*/
private final Map handlerSubmissionsByCommandId = new HashMap();
/**
* The mutable command manager that should be notified of changes to the
* list of active handlers. This value is never <code>null</code>.
*/
private final IMutableCommandManager mutableCommandManager;
/**
* A listener for changes in the active page. Changes to the active page
* causes the handler submissions to be reprocessed.
*/
private final IPageListener pageListener = new IPageListener() {
public void pageActivated(IWorkbenchPage workbenchPage) {
processHandlerSubmissions(false);
}
public void pageClosed(IWorkbenchPage workbenchPage) {
processHandlerSubmissions(false);
}
public void pageOpened(IWorkbenchPage workbenchPage) {
processHandlerSubmissions(false);
}
};
/**
* A listener for changes in the active part. Changes to the active part
* causes the handler submissions to be reprocessed.
*/
private final IPartListener partListener = new IPartListener() {
public void partActivated(IWorkbenchPart workbenchPart) {
processHandlerSubmissions(false);
}
public void partBroughtToTop(IWorkbenchPart workbenchPart) {
processHandlerSubmissions(false);
}
public void partClosed(IWorkbenchPart workbenchPart) {
processHandlerSubmissions(false);
}
public void partDeactivated(IWorkbenchPart workbenchPart) {
processHandlerSubmissions(false);
}
public void partOpened(IWorkbenchPart workbenchPart) {
processHandlerSubmissions(false);
}
};
/**
* A listener for changes in the active perspective. Changes to the active
* perspective causes the handler submissions to be reprocessed.
*/
private final IPerspectiveListener perspectiveListener = new IPerspectiveListener() {
public void perspectiveActivated(IWorkbenchPage workbenchPage,
IPerspectiveDescriptor perspectiveDescriptor) {
processHandlerSubmissions(false);
}
public void perspectiveChanged(IWorkbenchPage workbenchPage,
IPerspectiveDescriptor perspectiveDescriptor, String changeId) {
processHandlerSubmissions(false);
}
};
/**
* Whether the command support should process handler submissions. If it is
* not processing handler submissions, then it will update the listeners,
* but do no further work. This flag is used to avoid excessive updating
* when the workbench is performing some large change (e.g., opening an
* editor, starting up, shutting down, switching perspectives, etc.)
*/
private boolean processing = true;
/**
* The workbench this class is supporting. This value should never be
* <code>null</code>.
*/
private final Workbench workbench;
/**
* Constructs a new instance of <code>WorkbenchCommandSupport</code>
*
* @param workbenchToSupport
* The workbench for which the support should be created; must
* not be <code>null</code>.
*/
public WorkbenchCommandSupport(final Workbench workbenchToSupport) {
workbench = workbenchToSupport;
mutableCommandManager = CommandManagerFactory
.getMutableCommandManager();
KeyFormatterFactory.setDefault(SWTKeySupport
.getKeyFormatterForPlatform());
// Attach a hook to latch on to the first workbench window to open.
workbenchToSupport.getDisplay().addFilter(SWT.Activate,
activationListener);
final List submissions = new ArrayList();
final MutableCommandManager commandManager = (MutableCommandManager) mutableCommandManager;
final Set handlers = commandManager.getDefinedHandlers();
final Iterator handlerItr = handlers.iterator();
while (handlerItr.hasNext()) {
final HandlerProxy proxy = (HandlerProxy) handlerItr.next();
final String commandId = proxy.getCommandId();
final HandlerSubmission submission = new HandlerSubmission(null,
null, null, commandId, proxy, Priority.LOW);
submissions.add(submission);
}
if (!submissions.isEmpty()) {
addHandlerSubmissions(submissions);
}
// TODO Should these be removed at shutdown? Is life cycle important?
}
public void addHandlerSubmission(HandlerSubmission handlerSubmission) {
addHandlerSubmissions(Collections.singleton(handlerSubmission));
}
public void addHandlerSubmissions(Collection handlerSubmissions) {
handlerSubmissions = Util.safeCopy(handlerSubmissions,
HandlerSubmission.class);
for (Iterator iterator = handlerSubmissions.iterator(); iterator
.hasNext();) {
HandlerSubmission handlerSubmission = (HandlerSubmission) iterator
.next();
String commandId = handlerSubmission.getCommandId();
List handlerSubmissions2 = (List) handlerSubmissionsByCommandId
.get(commandId);
if (handlerSubmissions2 == null) {
handlerSubmissions2 = new ArrayList();
handlerSubmissionsByCommandId.put(commandId,
handlerSubmissions2);
}
handlerSubmissions2.add(handlerSubmission);
}
processHandlerSubmissions(true);
}
/**
* An accessor for the underlying command manager.
*
* @return The command manager used by this support class.
*/
public ICommandManager getCommandManager() {
// TODO need to proxy this to prevent casts to IMutableCommandManager
return mutableCommandManager;
}
/**
* Processes incoming handler submissions, and decides which handlers should
* be active. If <code>force</code> is <code>false</code>, then it will
* only reconsider handlers if the state of the workbench has changed.
*
* @param force
* Whether to force reprocessing of the handlers -- regardless of
* whether the workbench has changed.
*/
private void processHandlerSubmissions(boolean force) {
processHandlerSubmissions(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.
*
* @param force
* Whether to force reprocessing of the handlers -- regardless of
* whether the workbench has changed.
* @param newActiveShell
* The shell that is now active. This could be the same as the
* current active shell, or it could indicate that a new shell
* has become active. This value can be <code>null</code> if
* there is no active shell currently (this can happen during
* shell transitions).
*/
public void processHandlerSubmissions(boolean force,
final Shell newActiveShell) {
IWorkbenchSite newWorkbenchSite = null;
IWorkbenchWindow newWorkbenchWindow = workbench
.getActiveWorkbenchWindow();
boolean update = false;
// Update the active shell, and swap the listener.
if (activeShell != newActiveShell) {
activeShell = newActiveShell;
update = true;
}
if (activeWorkbenchWindow != newWorkbenchWindow) {
if (activeWorkbenchWindow != null) {
activeWorkbenchWindow.removePageListener(pageListener);
activeWorkbenchWindow
.removePerspectiveListener(perspectiveListener);
activeWorkbenchWindow.getPartService().removePartListener(
partListener);
}
if (newWorkbenchWindow != null) {
newWorkbenchWindow.addPageListener(pageListener);
newWorkbenchWindow.addPerspectiveListener(perspectiveListener);
newWorkbenchWindow.getPartService().addPartListener(
partListener);
}
activeWorkbenchWindow = newWorkbenchWindow;
update = true;
}
if ((newWorkbenchWindow != null)
&& (newWorkbenchWindow.getShell() == newActiveShell)) {
IWorkbenchPage activeWorkbenchPage = newWorkbenchWindow
.getActivePage();
if (activeWorkbenchPage != null) {
IWorkbenchPart activeWorkbenchPart = activeWorkbenchPage
.getActivePart();
if (activeWorkbenchPart != null) {
newWorkbenchSite = activeWorkbenchPart.getSite();
}
}
} else {
newWorkbenchSite = null;
}
if (processing
&& (force || update || !Util.equals(activeWorkbenchSite,
newWorkbenchSite))) {
activeWorkbenchSite = newWorkbenchSite;
Map handlersByCommandId = new HashMap();
for (Iterator iterator = handlerSubmissionsByCommandId.entrySet()
.iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String commandId = (String) entry.getKey();
List handlerSubmissions = (List) entry.getValue();
Iterator submissionItr = handlerSubmissions.iterator();
HandlerSubmission bestHandlerSubmission = null;
boolean conflict = false;
while (submissionItr.hasNext()) {
HandlerSubmission handlerSubmission = (HandlerSubmission) submissionItr
.next();
IWorkbenchSite activeWorkbenchSite2 = handlerSubmission
.getActiveWorkbenchPartSite();
if (activeWorkbenchSite2 != null
&& activeWorkbenchSite2 != newWorkbenchSite)
continue;
Shell activeShell2 = handlerSubmission.getActiveShell();
if (activeShell2 != null && activeShell2 != activeShell)
continue;
if (bestHandlerSubmission == null)
bestHandlerSubmission = handlerSubmission;
else {
int compareTo = Util.compareIdentity(activeWorkbenchSite2,
bestHandlerSubmission
.getActiveWorkbenchPartSite());
if (compareTo == 0) {
compareTo = Util.compareIdentity(activeShell2,
bestHandlerSubmission.getActiveShell());
if (compareTo == 0)
compareTo = Util
.compare(handlerSubmission
.getPriority(),
bestHandlerSubmission
.getPriority());
}
if (compareTo > 0) {
if ((DEBUG_VERBOSE)
&& ((DEBUG_VERBOSE_COMMAND_ID == null) || (DEBUG_VERBOSE_COMMAND_ID
.equals(commandId)))) {
System.out
.println("HANDLERS >>> Resolved conflict detected between "); //$NON-NLS-1$
System.out.println("HANDLERS >>> win: " //$NON-NLS-1$
+ handlerSubmission);
System.out.println("HANDLERS >>> lose: " //$NON-NLS-1$
+ bestHandlerSubmission);
}
conflict = false;
bestHandlerSubmission = handlerSubmission;
} else if ((compareTo == 0)
&& (bestHandlerSubmission.getHandler() != handlerSubmission
.getHandler())) {
if (DEBUG) {
System.out
.println("HANDLERS >>> Unresolved conflict detected for " //$NON-NLS-1$
+ commandId);
}
conflict = true;
} else if ((DEBUG_VERBOSE)
&& ((DEBUG_VERBOSE_COMMAND_ID == null) || (DEBUG_VERBOSE_COMMAND_ID
.equals(commandId)))) {
System.out
.println("HANDLERS >>> Resolved conflict detected between "); //$NON-NLS-1$
System.out.println("HANDLERS >>> win: " //$NON-NLS-1$
+ bestHandlerSubmission);
System.out.println("HANDLERS >>> lose: " //$NON-NLS-1$
+ handlerSubmission);
}
}
}
if (bestHandlerSubmission != null && !conflict)
handlersByCommandId.put(commandId,
bestHandlerSubmission.getHandler());
}
mutableCommandManager.setHandlersByCommandId(handlersByCommandId);
}
}
public void removeHandlerSubmission(HandlerSubmission handlerSubmission) {
removeHandlerSubmission(handlerSubmission, true);
}
/**
* Removes a single handler submission -- optionally causing the list of
* handler submissions to be reprocessed. This method is used by the two API
* methods to actually remove a single handler submission.
*
* @param handlerSubmission
* The submission to be removed; must not be <code>null</code>.
* @param reprocess
* Whether to reprocess handler submissions.
*/
private final void removeHandlerSubmission(
final HandlerSubmission handlerSubmission, final boolean reprocess) {
final String commandId = handlerSubmission.getCommandId();
final List handlerSubmissions2 = (List) handlerSubmissionsByCommandId
.get(commandId);
if (handlerSubmissions2 != null) {
handlerSubmissions2.remove(handlerSubmission);
if (handlerSubmissions2.isEmpty()) {
handlerSubmissionsByCommandId.remove(commandId);
}
}
if (reprocess) {
processHandlerSubmissions(true);
}
}
public void removeHandlerSubmissions(Collection handlerSubmissions) {
final Iterator submissionItr = handlerSubmissions.iterator();
while (submissionItr.hasNext()) {
removeHandlerSubmission((HandlerSubmission) submissionItr.next(),
false);
}
processHandlerSubmissions(true);
}
/**
* Sets the active context identifiers on the mutable command manager this
* class interacts with.
*
* @param activeContextIds
* The new map of active context identifiers -- representing the
* hierarchy of active contexts. This should be a map of string
* values. It may be empty, but it should never be
* <code>null</code>.
*/
public void setActiveContextIds(Map activeContextIds) {
mutableCommandManager.setActiveContextIds(activeContextIds);
}
/**
* Sets whether the workbench's command support should process handler
* submissions. The workbench should not allow the event loop to spin unless
* this value is set to <code>true</code>. If the value changes from
* <code>false</code> to <code>true</code>, this automatically triggers
* a re-processing of the handler submissions.
*
* @param processing
* Whether to process handler submissions
*/
public final void setProcessing(final boolean processing) {
final boolean reprocess = !this.processing && processing;
this.processing = processing;
if (reprocess) {
processHandlerSubmissions(true);
}
}
}