/* * 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; } } }