/*
* Copyright (c) 2011, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.ui.internal.text.editor;
import com.google.dart.tools.ui.DartToolsPlugin;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IExecutionListener;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
/**
* Exit strategy for commands that want to fold repeated execution into one compound edit. See
* {@link org.eclipse.jface.text.IRewriteTarget#endCompoundChange()
* IRewriteTarget.endCompoundChange}. As long as a strategy is installed on an {@link ITextViewer},
* it will detect the end of a compound operation when any of the following conditions becomes true:
* <ul>
* <li>the viewer's text widget loses the keyboard focus</li>
* <li>the mouse is clicked or double clicked inside the viewer's widget</li>
* <li>a command other than the ones specified is executed</li>
* <li>the viewer receives any key events that are not modifier combinations</li>
* </ul>
* <p>
* If the end of a compound edit is detected, any registered {@link ICompoundEditListener}s are
* notified and the strategy is disarmed (spring-loaded).
* </p>
*/
public final class CompoundEditExitStrategy {
/**
* Listens for events that may trigger the end of a compound edit.
*/
private final class EventListener implements MouseListener, FocusListener, VerifyKeyListener,
IExecutionListener {
@Override
public void focusGained(FocusEvent e) {
}
/*
* @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events .FocusEvent)
*/
@Override
public void focusLost(FocusEvent e) {
// losing focus ends the change
fireEndCompoundEdit();
}
/*
* @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt
* .events.MouseEvent)
*/
@Override
public void mouseDoubleClick(MouseEvent e) {
// mouse actions end the compound change
fireEndCompoundEdit();
}
/*
* @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events .MouseEvent)
*/
@Override
public void mouseDown(MouseEvent e) {
// mouse actions end the compound change
fireEndCompoundEdit();
}
@Override
public void mouseUp(MouseEvent e) {
}
@Override
public void notHandled(String commandId, NotHandledException exception) {
}
@Override
public void postExecuteFailure(String commandId, ExecutionException exception) {
}
@Override
public void postExecuteSuccess(String commandId, Object returnValue) {
}
/*
* @see org.eclipse.core.commands.IExecutionListener#preExecute(java.lang.String,
* org.eclipse.core.commands.ExecutionEvent)
*/
@Override
public void preExecute(String commandId, ExecutionEvent event) {
// any command other than the known ones end the compound change
for (int i = 0; i < fCommandIds.length; i++) {
if (commandId.equals(fCommandIds[i])) {
return;
}
}
fireEndCompoundEdit();
}
/*
* @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey(org.eclipse.swt.events .VerifyEvent)
*/
@Override
public void verifyKey(VerifyEvent event) {
// any key press that is not a modifier combo ends the compound change
final int maskWithoutShift = SWT.MODIFIER_MASK & ~SWT.SHIFT;
if ((event.keyCode & SWT.MODIFIER_MASK) == 0 && (event.stateMask & maskWithoutShift) == 0) {
fireEndCompoundEdit();
}
}
}
private final String[] fCommandIds;
private final EventListener fEventListener = new EventListener();
private final ListenerList fListenerList = new ListenerList(ListenerList.IDENTITY);
private ITextViewer fViewer;
private StyledText fWidgetEventSource;
/**
* Creates a new strategy, equivalent to calling {@linkplain #CompoundEditExitStrategy(String[])
* CompoundEditExitStrategy(new String[] { commandId })}.
*
* @param commandId the command id of the repeatable command
*/
public CompoundEditExitStrategy(String commandId) {
if (commandId == null) {
throw new NullPointerException("commandId"); //$NON-NLS-1$
}
fCommandIds = new String[] {commandId};
}
/**
* Creates a new strategy, ending upon execution of any command other than the ones specified.
*
* @param commandIds the ids of the repeatable commands
*/
public CompoundEditExitStrategy(String[] commandIds) {
for (int i = 0; i < commandIds.length; i++) {
if (commandIds[i] == null) {
throw new NullPointerException("commandIds[" + i + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
fCommandIds = new String[commandIds.length];
System.arraycopy(commandIds, 0, fCommandIds, 0, commandIds.length);
}
/**
* Adds a compound edit listener. Multiple registration is possible. Note that the receiver is
* automatically disarmed before the listeners are notified.
*
* @param listener the new listener
*/
public void addCompoundListener(ICompoundEditListener listener) {
fListenerList.add(listener);
}
/**
* Installs the receiver on <code>viewer</code> and arms it. After this call returns, any
* registered listeners will be notified if a compound edit ends.
*
* @param viewer the viewer to install on
*/
public void arm(ITextViewer viewer) {
disarm();
if (viewer == null) {
throw new NullPointerException("editor"); //$NON-NLS-1$
}
fViewer = viewer;
addListeners(fViewer);
}
/**
* Disarms the receiver. After this call returns, any registered listeners will be not be notified
* any more until <code>install</code> is called again. Note that the listeners are not removed.
* <p>
* Note that the receiver is automatically disarmed when the end of a compound edit has been
* detected and before the listeners are notified.
* </p>
*/
public void disarm() {
if (isInstalled()) {
removeListeners(fViewer);
fViewer = null;
}
}
/**
* Removes a compound edit listener. If <code>listener</code> is registered multiple times, an
* arbitrary instance is removed. If <code>listener</code> is not currently registered, nothing
* happens.
*
* @param listener the listener to be removed.
*/
public void removeCompoundListener(ICompoundEditListener listener) {
fListenerList.remove(listener);
}
private void addListeners(ITextViewer viewer) {
fWidgetEventSource = viewer.getTextWidget();
if (fWidgetEventSource != null) {
fWidgetEventSource.addVerifyKeyListener(fEventListener);
fWidgetEventSource.addMouseListener(fEventListener);
fWidgetEventSource.addFocusListener(fEventListener);
}
ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getAdapter(
ICommandService.class);
if (commandService != null) {
commandService.addExecutionListener(fEventListener);
}
}
private void fireEndCompoundEdit() {
disarm();
Object[] listeners = fListenerList.getListeners();
for (int i = 0; i < listeners.length; i++) {
ICompoundEditListener listener = (ICompoundEditListener) listeners[i];
try {
listener.endCompoundEdit();
} catch (Exception e) {
DartToolsPlugin.log(e);
}
}
}
private boolean isInstalled() {
return fViewer != null;
}
private void removeListeners(ITextViewer editor) {
ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getAdapter(
ICommandService.class);
if (commandService != null) {
commandService.removeExecutionListener(fEventListener);
}
if (fWidgetEventSource != null) {
fWidgetEventSource.removeFocusListener(fEventListener);
fWidgetEventSource.removeMouseListener(fEventListener);
fWidgetEventSource.removeVerifyKeyListener(fEventListener);
fWidgetEventSource = null;
}
}
}