001: /*******************************************************************************
002: * Copyright (c) 2005, 2006 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.texteditor;
011:
012: import org.eclipse.swt.SWT;
013: import org.eclipse.swt.custom.StyledText;
014: import org.eclipse.swt.custom.VerifyKeyListener;
015: import org.eclipse.swt.events.FocusEvent;
016: import org.eclipse.swt.events.FocusListener;
017: import org.eclipse.swt.events.MouseEvent;
018: import org.eclipse.swt.events.MouseListener;
019: import org.eclipse.swt.events.VerifyEvent;
020:
021: import org.eclipse.core.commands.ExecutionEvent;
022: import org.eclipse.core.commands.ExecutionException;
023: import org.eclipse.core.commands.IExecutionListener;
024: import org.eclipse.core.commands.NotHandledException;
025:
026: import org.eclipse.core.runtime.IStatus;
027: import org.eclipse.core.runtime.ListenerList;
028: import org.eclipse.core.runtime.Status;
029:
030: import org.eclipse.jface.text.ITextViewer;
031:
032: import org.eclipse.ui.PlatformUI;
033: import org.eclipse.ui.commands.ICommandService;
034:
035: /**
036: * Exit strategy for commands that want to fold repeated execution into one compound edit. See
037: * {@link org.eclipse.jface.text.IRewriteTarget#endCompoundChange() IRewriteTarget.endCompoundChange}.
038: * As long as a strategy is installed on an {@link ITextViewer}, it will detect the end of a
039: * compound operation when any of the following conditions becomes true:
040: * <ul>
041: * <li>the viewer's text widget loses the keyboard focus</li>
042: * <li>the mouse is clicked or double clicked inside the viewer's widget</li>
043: * <li>a command other than the ones specified is executed</li>
044: * <li>the viewer receives any key events that are not modifier combinations</li>
045: * </ul>
046: * <p>
047: * If the end of a compound edit is detected, any registered {@link ICompoundEditListener}s are
048: * notified and the strategy is disarmed (spring-loaded).
049: * </p>
050: *
051: * @since 3.1
052: */
053: public final class CompoundEditExitStrategy {
054: /**
055: * Listens for events that may trigger the end of a compound edit.
056: */
057: private final class EventListener implements MouseListener,
058: FocusListener, VerifyKeyListener, IExecutionListener {
059:
060: /*
061: * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
062: */
063: public void mouseDoubleClick(MouseEvent e) {
064: // mouse actions end the compound change
065: fireEndCompoundEdit();
066: }
067:
068: /*
069: * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
070: */
071: public void mouseDown(MouseEvent e) {
072: // mouse actions end the compound change
073: fireEndCompoundEdit();
074: }
075:
076: public void mouseUp(MouseEvent e) {
077: }
078:
079: public void focusGained(FocusEvent e) {
080: }
081:
082: /*
083: * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent)
084: */
085: public void focusLost(FocusEvent e) {
086: // losing focus ends the change
087: fireEndCompoundEdit();
088: }
089:
090: public void notHandled(String commandId,
091: NotHandledException exception) {
092: }
093:
094: public void postExecuteFailure(String commandId,
095: ExecutionException exception) {
096: }
097:
098: public void postExecuteSuccess(String commandId,
099: Object returnValue) {
100: }
101:
102: /*
103: * @see org.eclipse.core.commands.IExecutionListener#preExecute(java.lang.String, org.eclipse.core.commands.ExecutionEvent)
104: */
105: public void preExecute(String commandId, ExecutionEvent event) {
106: // any command other than the known ones end the compound change
107: for (int i = 0; i < fCommandIds.length; i++) {
108: if (commandId.equals(fCommandIds[i]))
109: return;
110: }
111: fireEndCompoundEdit();
112: }
113:
114: /*
115: * @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey(org.eclipse.swt.events.VerifyEvent)
116: */
117: public void verifyKey(VerifyEvent event) {
118: // any key press that is not a modifier combo ends the compound change
119: final int maskWithoutShift = SWT.MODIFIER_MASK & ~SWT.SHIFT;
120: if ((event.keyCode & SWT.MODIFIER_MASK) == 0
121: && (event.stateMask & maskWithoutShift) == 0)
122: fireEndCompoundEdit();
123: }
124:
125: }
126:
127: private final String[] fCommandIds;
128: private final EventListener fEventListener = new EventListener();
129: private final ListenerList fListenerList = new ListenerList(
130: ListenerList.IDENTITY);
131:
132: private ITextViewer fViewer;
133: private StyledText fWidgetEventSource;
134:
135: /**
136: * Creates a new strategy, equivalent to calling
137: * {@linkplain #CompoundEditExitStrategy(String[]) CompoundEditExitStrategy(new String[] { commandId })}.
138: *
139: * @param commandId the command id of the repeatable command
140: */
141: public CompoundEditExitStrategy(String commandId) {
142: if (commandId == null)
143: throw new NullPointerException("commandId"); //$NON-NLS-1$
144: fCommandIds = new String[] { commandId };
145: }
146:
147: /**
148: * Creates a new strategy, ending upon execution of any command other than the ones
149: * specified.
150: *
151: * @param commandIds the ids of the repeatable commands
152: */
153: public CompoundEditExitStrategy(String[] commandIds) {
154: for (int i = 0; i < commandIds.length; i++) {
155: if (commandIds[i] == null)
156: throw new NullPointerException("commandIds[" + i + "]"); //$NON-NLS-1$ //$NON-NLS-2$
157: }
158: fCommandIds = new String[commandIds.length];
159: System.arraycopy(commandIds, 0, fCommandIds, 0,
160: commandIds.length);
161: }
162:
163: /**
164: * Installs the receiver on <code>viewer</code> and arms it. After this call returns, any
165: * registered listeners will be notified if a compound edit ends.
166: *
167: * @param viewer the viewer to install on
168: */
169: public void arm(ITextViewer viewer) {
170: disarm();
171: if (viewer == null)
172: throw new NullPointerException("editor"); //$NON-NLS-1$
173: fViewer = viewer;
174: addListeners(fViewer);
175: }
176:
177: /**
178: * Disarms the receiver. After this call returns, any registered listeners will be not be
179: * notified any more until <code>install</code> is called again. Note that the listeners are
180: * not removed.
181: * <p>
182: * Note that the receiver is automatically disarmed when the end of a compound edit has
183: * been detected and before the listeners are notified.
184: * </p>
185: */
186: public void disarm() {
187: if (isInstalled()) {
188: removeListeners(fViewer);
189: fViewer = null;
190: }
191: }
192:
193: private void addListeners(ITextViewer viewer) {
194: fWidgetEventSource = viewer.getTextWidget();
195: if (fWidgetEventSource != null) {
196: fWidgetEventSource.addVerifyKeyListener(fEventListener);
197: fWidgetEventSource.addMouseListener(fEventListener);
198: fWidgetEventSource.addFocusListener(fEventListener);
199: }
200:
201: ICommandService commandService = (ICommandService) PlatformUI
202: .getWorkbench().getAdapter(ICommandService.class);
203: if (commandService != null)
204: commandService.addExecutionListener(fEventListener);
205: }
206:
207: private void removeListeners(ITextViewer editor) {
208: ICommandService commandService = (ICommandService) PlatformUI
209: .getWorkbench().getAdapter(ICommandService.class);
210: if (commandService != null)
211: commandService.removeExecutionListener(fEventListener);
212:
213: if (fWidgetEventSource != null) {
214: fWidgetEventSource.removeFocusListener(fEventListener);
215: fWidgetEventSource.removeMouseListener(fEventListener);
216: fWidgetEventSource.removeVerifyKeyListener(fEventListener);
217: fWidgetEventSource = null;
218: }
219: }
220:
221: private boolean isInstalled() {
222: return fViewer != null;
223: }
224:
225: private void fireEndCompoundEdit() {
226: disarm();
227: Object[] listeners = fListenerList.getListeners();
228: for (int i = 0; i < listeners.length; i++) {
229: ICompoundEditListener listener = (ICompoundEditListener) listeners[i];
230: try {
231: listener.endCompoundEdit();
232: } catch (Exception e) {
233: IStatus status = new Status(IStatus.ERROR,
234: TextEditorPlugin.PLUGIN_ID, IStatus.OK,
235: "listener notification failed", e); //$NON-NLS-1$
236: TextEditorPlugin.getDefault().getLog().log(status);
237: }
238: }
239: }
240:
241: /**
242: * Adds a compound edit listener. Multiple registration is possible. Note that the receiver is
243: * automatically disarmed before the listeners are notified.
244: *
245: * @param listener the new listener
246: */
247: public void addCompoundListener(ICompoundEditListener listener) {
248: fListenerList.add(listener);
249: }
250:
251: /**
252: * Removes a compound edit listener. If <code>listener</code> is registered multiple times, an
253: * arbitrary instance is removed. If <code>listener</code> is not currently registered,
254: * nothing happens.
255: *
256: * @param listener the listener to be removed.
257: */
258: public void removeCompoundListener(ICompoundEditListener listener) {
259: fListenerList.remove(listener);
260: }
261:
262: }
|