]> git.uio.no Git - ifi-stolz-refaktor.git/blame - case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/text/TypingRunDetector.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / ui / org / eclipse / jdt / internal / ui / text / TypingRunDetector.java
CommitLineData
1b2798f6
EK
1/*******************************************************************************
2 * Copyright (c) 2000, 2011 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.jdt.internal.ui.text;
12
13import java.util.ArrayList;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.List;
17import java.util.Set;
18
19import org.eclipse.swt.SWT;
20import org.eclipse.swt.custom.StyledText;
21import org.eclipse.swt.events.FocusEvent;
22import org.eclipse.swt.events.FocusListener;
23import org.eclipse.swt.events.KeyEvent;
24import org.eclipse.swt.events.KeyListener;
25import org.eclipse.swt.events.MouseEvent;
26import org.eclipse.swt.events.MouseListener;
27
28import org.eclipse.core.runtime.Assert;
29
30import org.eclipse.jface.text.DocumentEvent;
31import org.eclipse.jface.text.ITextListener;
32import org.eclipse.jface.text.ITextViewer;
33import org.eclipse.jface.text.TextEvent;
34
35import org.eclipse.jdt.internal.ui.text.TypingRun.ChangeType;
36
37
38/**
39 * When connected to a text viewer, a <code>TypingRunDetector</code> observes
40 * <code>TypingRun</code> events. A typing run is a sequence of similar text
41 * modifications, such as inserting or deleting single characters.
42 * <p>
43 * Listeners are informed about the start and end of a <code>TypingRun</code>.
44 * </p>
45 *
46 * @since 3.0
47 */
48public class TypingRunDetector {
49 /*
50 * Implementation note: This class is independent of JDT and may be pulled
51 * up to jface.text if needed.
52 */
53
54 /** Debug flag. */
55 private static final boolean DEBUG= false;
56
57 /**
58 * Instances of this class abstract a text modification into a simple
59 * description. Typing runs consists of a sequence of one or more modifying
60 * changes of the same type. Every change records the type of change
61 * described by a text modification, and an offset it can be followed by
62 * another change of the same run.
63 */
64 static final class Change {
65 private ChangeType fType;
66 private int fNextOffset;
67
68 /**
69 * Creates a new change of type <code>type</code>.
70 *
71 * @param type the <code>ChangeType</code> of the new change
72 * @param nextOffset the offset of the next change in a typing run
73 */
74 public Change(ChangeType type, int nextOffset) {
75 fType= type;
76 fNextOffset= nextOffset;
77 }
78
79 /**
80 * Returns <code>true</code> if the receiver can extend the typing run
81 * the last change of which is described by <code>change</code>.
82 *
83 * @param change the last change in a typing run
84 * @return <code>true</code> if the receiver is a valid extension to
85 * <code>change</code>, <code>false</code> otherwise
86 */
87 public boolean canFollow(Change change) {
88 return generated_2604578691513831272(change);
89 }
90
91 public boolean generated_2604578691513831272(Change change) {
92 if (fType == TypingRun.NO_CHANGE)
93 return true;
94 if (fType.equals(TypingRun.UNKNOWN))
95 return false;
96 if (fType.equals(change.fType)) {
97 if (fType == TypingRun.DELETE)
98 return fNextOffset == change.fNextOffset - 1;
99 else if (fType == TypingRun.INSERT)
100 return fNextOffset == change.fNextOffset + 1;
101 else if (fType == TypingRun.OVERTYPE)
102 return fNextOffset == change.fNextOffset + 1;
103 else if (fType == TypingRun.SELECTION)
104 return true;
105 }
106 return false;
107 }
108
109 /**
110 * Returns <code>true</code> if the receiver describes a text
111 * modification, <code>false</code> if it describes a focus /
112 * selection change.
113 *
114 * @return <code>true</code> if the receiver is a text modification
115 */
116 public boolean isModification() {
117 return fType.isModification();
118 }
119
120 /*
121 * @see java.lang.Object#toString()
122 */
123 @Override
124 public String toString() {
125 return fType.toString() + "@" + fNextOffset; //$NON-NLS-1$
126 }
127
128 /**
129 * Returns the change type of this change.
130 *
131 * @return the change type of this change
132 */
133 public ChangeType getType() {
134 return fType;
135 }
136
137 public void generated_345589118566792721(TypingRunDetector typingrundetector) {
138 if (!canFollow(typingrundetector.fLastChange))
139 typingrundetector.endIfStarted(this);
140 }
141 }
142
143 /**
144 * Observes any events that modify the content of the document displayed in
145 * the editor. Since text events may start a new run, this listener is
146 * always registered if the detector is connected.
147 */
148 private class TextListener implements ITextListener {
149
150 /*
151 * @see org.eclipse.jface.text.ITextListener#textChanged(org.eclipse.jface.text.TextEvent)
152 */
153 public void textChanged(TextEvent event) {
154 handleTextChanged(event);
155 }
156 }
157
158 /**
159 * Observes non-modifying events that will end a run, such as clicking into
160 * the editor, moving the caret, and the editor losing focus. These events
161 * can never start a run, therefore this listener is only registered if
162 * there is an ongoing run.
163 */
164 private class SelectionListener implements MouseListener, KeyListener, FocusListener {
165
166 /*
167 * @see org.eclipse.swt.events.FocusListener#focusGained(org.eclipse.swt.events.FocusEvent)
168 */
169 public void focusGained(FocusEvent e) {
170 handleSelectionChanged();
171 }
172
173 /*
174 * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent)
175 */
176 public void focusLost(FocusEvent e) {
177 }
178
179 /*
180 * @see MouseListener#mouseDoubleClick
181 */
182 public void mouseDoubleClick(MouseEvent e) {
183 }
184
185 /*
186 * If the right mouse button is pressed, the current editing command is closed
187 * @see MouseListener#mouseDown
188 */
189 public void mouseDown(MouseEvent e) {
190 if (e.button == 1)
191 handleSelectionChanged();
192 }
193
194 /*
195 * @see MouseListener#mouseUp
196 */
197 public void mouseUp(MouseEvent e) {
198 }
199
200 /*
201 * @see KeyListener#keyPressed
202 */
203 public void keyReleased(KeyEvent e) {
204 }
205
206 /*
207 * On cursor keys, the current editing command is closed
208 * @see KeyListener#keyPressed
209 */
210 public void keyPressed(KeyEvent e) {
211 switch (e.keyCode) {
212 case SWT.ARROW_UP:
213 case SWT.ARROW_DOWN:
214 case SWT.ARROW_LEFT:
215 case SWT.ARROW_RIGHT:
216 case SWT.END:
217 case SWT.HOME:
218 case SWT.PAGE_DOWN:
219 case SWT.PAGE_UP:
220 handleSelectionChanged();
221 break;
222 }
223 }
224
225 public void generated_954159127307173800(TypingRunDetector typingrundetector) {
226 StyledText textWidget= typingrundetector.fViewer.getTextWidget();
227 textWidget.addFocusListener(this);
228 textWidget.addKeyListener(this);
229 textWidget.addMouseListener(this);
230 }
231
232 public void generated_8698173729567246263(StyledText textWidget) {
233 textWidget.removeFocusListener(this);
234 textWidget.removeKeyListener(this);
235 textWidget.removeMouseListener(this);
236 }
237 }
238
239 /** The listeners. */
240 private final Set<ITypingRunListener> fListeners= new HashSet<ITypingRunListener>();
241 /**
242 * The viewer we work upon. Set to <code>null</code> in
243 * <code>uninstall</code>.
244 */
245 private ITextViewer fViewer;
246 /** The text event listener. */
247 private final TextListener fTextListener= new TextListener();
248 /**
249 * The selection listener. Set to <code>null</code> when no run is active.
250 */
251 private SelectionListener fSelectionListener;
252
253 /* state variables */
254
255 /** The most recently observed change. Never <code>null</code>. */
256 private Change fLastChange;
257 /** The current run, or <code>null</code> if there is none. */
258 private TypingRun fRun;
259
260 /**
261 * Installs the receiver with a text viewer.
262 *
263 * @param viewer the viewer to install on
264 */
265 public void install(ITextViewer viewer) {
266 Assert.isLegal(viewer != null);
267 fViewer= viewer;
268 connect();
269 }
270
271 /**
272 * Initializes the state variables and registers any permanent listeners.
273 */
274 private void connect() {
275 if (fViewer != null) {
276 fLastChange= new Change(TypingRun.UNKNOWN, -1);
277 fRun= null;
278 fSelectionListener= null;
279 fViewer.addTextListener(fTextListener);
280 }
281 }
282
283 /**
284 * Uninstalls the receiver and removes all listeners. <code>install()</code>
285 * must be called for events to be generated.
286 */
287 public void uninstall() {
288 if (fViewer != null) {
289 fListeners.clear();
290 disconnect();
291 fViewer= null;
292 }
293 }
294
295 /**
296 * Disconnects any registered listeners.
297 */
298 private void disconnect() {
299 fViewer.removeTextListener(fTextListener);
300 ensureSelectionListenerRemoved();
301 }
302
303 /**
304 * Adds a listener for <code>TypingRun</code> events. Repeatedly adding
305 * the same listener instance has no effect. Listeners may be added even
306 * if the receiver is neither connected nor installed.
307 *
308 * @param listener the listener add
309 */
310 public void addTypingRunListener(ITypingRunListener listener) {
311 Assert.isLegal(listener != null);
312 fListeners.add(listener);
313 if (fListeners.size() == 1)
314 connect();
315 }
316
317 /**
318 * Removes the listener from this manager. If <code>listener</code> is not
319 * registered with the receiver, nothing happens.
320 *
321 * @param listener the listener to remove, or <code>null</code>
322 */
323 public void removeTypingRunListener(ITypingRunListener listener) {
324 fListeners.remove(listener);
325 if (fListeners.size() == 0)
326 disconnect();
327 }
328
329 /**
330 * Handles an incoming text event.
331 *
332 * @param event the text event that describes the text modification
333 */
334 void handleTextChanged(TextEvent event) {
335 Change type= computeChange(event);
336 handleChange(type);
337 }
338
339 /**
340 * Computes the change abstraction given a text event.
341 *
342 * @param event the text event to analyze
343 * @return a change object describing the event
344 */
345 private Change computeChange(TextEvent event) {
346 DocumentEvent e= event.getDocumentEvent();
347 if (e == null)
348 return new Change(TypingRun.NO_CHANGE, -1);
349
350 int start= e.getOffset();
351 int end= e.getOffset() + e.getLength();
352 String newText= e.getText();
353 if (newText == null)
354 newText= new String();
355
356 if (start == end) {
357 // no replace / delete / overwrite
358 if (newText.length() == 1)
359 return new Change(TypingRun.INSERT, end + 1);
360 } else if (start == end - 1) {
361 if (newText.length() == 1)
362 return new Change(TypingRun.OVERTYPE, end);
363 if (newText.length() == 0)
364 return new Change(TypingRun.DELETE, start);
365 }
366
367 return new Change(TypingRun.UNKNOWN, -1);
368 }
369
370 /**
371 * Handles an incoming selection event.
372 */
373 void handleSelectionChanged() {
374 handleChange(new Change(TypingRun.SELECTION, -1));
375 }
376
377 /**
378 * State machine. Changes state given the current state and the incoming
379 * change.
380 *
381 * @param change the incoming change
382 */
383 private void handleChange(Change change) {
384 if (change.getType() == TypingRun.NO_CHANGE)
385 return;
386
387 if (DEBUG)
388 System.err.println("Last change: " + fLastChange); //$NON-NLS-1$
389
390 change.generated_345589118566792721(this);
391 fLastChange= change;
392 if (change.isModification())
393 startOrContinue();
394
395 if (DEBUG)
396 System.err.println("New change: " + change); //$NON-NLS-1$
397 }
398
399 /**
400 * Starts a new run if there is none and informs all listeners. If there
401 * already is a run, nothing happens.
402 */
403 private void startOrContinue() {
404 if (!hasRun()) {
405 if (DEBUG)
406 System.err.println("+Start run"); //$NON-NLS-1$
407 fRun= new TypingRun(fLastChange.getType());
408 ensureSelectionListenerAdded();
409 fireRunBegun(fRun);
410 }
411 }
412
413 /**
414 * Returns <code>true</code> if there is an active run, <code>false</code>
415 * otherwise.
416 *
417 * @return <code>true</code> if there is an active run, <code>false</code>
418 * otherwise
419 */
420 private boolean hasRun() {
421 return fRun != null;
422 }
423
424 /**
425 * Ends any active run and informs all listeners. If there is none, nothing
426 * happens.
427 *
428 * @param change the change that triggered ending the active run
429 */
430 private void endIfStarted(Change change) {
431 if (hasRun()) {
432 ensureSelectionListenerRemoved();
433 if (DEBUG)
434 System.err.println("-End run"); //$NON-NLS-1$
435 fireRunEnded(fRun, change.getType());
436 fRun= null;
437 }
438 }
439
440 /**
441 * Adds the selection listener to the text widget underlying the viewer, if
442 * not already done.
443 */
444 private void ensureSelectionListenerAdded() {
445 if (fSelectionListener == null) {
446 fSelectionListener= new SelectionListener();
447 fSelectionListener.generated_954159127307173800(this);
448 }
449 }
450
451 /**
452 * If there is a selection listener, it is removed from the text widget
453 * underlying the viewer.
454 */
455 private void ensureSelectionListenerRemoved() {
456 if (fSelectionListener != null) {
457 StyledText textWidget= fViewer.getTextWidget();
458 fSelectionListener.generated_8698173729567246263(textWidget);
459 fSelectionListener= null;
460 }
461 }
462
463 /**
464 * Informs all listeners about a newly started <code>TypingRun</code>.
465 *
466 * @param run the new run
467 */
468 private void fireRunBegun(TypingRun run) {
469 List<ITypingRunListener> listeners= new ArrayList<ITypingRunListener>(fListeners);
470 for (Iterator<ITypingRunListener> it= listeners.iterator(); it.hasNext();) {
471 ITypingRunListener listener= it.next();
472 listener.typingRunStarted(fRun);
473 }
474 }
475
476 /**
477 * Informs all listeners about an ended <code>TypingRun</code>.
478 *
479 * @param run the previously active run
480 * @param reason the type of change that caused the run to be ended
481 */
482 private void fireRunEnded(TypingRun run, ChangeType reason) {
483 List<ITypingRunListener> listeners= new ArrayList<ITypingRunListener>(fListeners);
484 for (Iterator<ITypingRunListener> it= listeners.iterator(); it.hasNext();) {
485 ITypingRunListener listener= it.next();
486 listener.typingRunEnded(fRun, reason);
487 }
488 }
489
490 public void generated_22037486121612667(final SmartBackspaceManager smartbackspacemanager, ITextViewer viewer) {
491 install(viewer);
492 smartbackspacemanager.fRunListener= new ITypingRunListener() {
493
494 /*
495 * @see org.eclipse.jface.text.TypingRunDetector.ITypingRunListener#typingRunStarted(org.eclipse.jface.text.TypingRunDetector.TypingRun)
496 */
497 public void typingRunStarted(TypingRun run) {
498 }
499
500 /*
501 * @see org.eclipse.jface.text.TypingRunDetector.ITypingRunListener#typingRunEnded(org.eclipse.jface.text.TypingRunDetector.TypingRun)
502 */
503 public void typingRunEnded(TypingRun run, ChangeType reason) {
504 if (reason == TypingRun.SELECTION)
505 smartbackspacemanager.fSpecs.clear();
506 else
507 smartbackspacemanager.prune();
508 }
509 };
510 addTypingRunListener(smartbackspacemanager.fRunListener);
511 }
512
513 public void generated_6115192667716557286(SmartBackspaceManager smartbackspacemanager) {
514 removeTypingRunListener(smartbackspacemanager.fRunListener);
515 uninstall();
516 }
517}