Mercurial > projects > dwt-addons
annotate dwtx/jface/text/link/LinkedModeUI.d @ 143:53b889547456
instanceof after &&
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sun, 24 Aug 2008 21:32:37 +0200 |
parents | 26688fec6d23 |
children | 75302ef3f92f |
rev | line source |
---|---|
129 | 1 /******************************************************************************* |
2 * Copyright (c) 2000, 2008 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 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.jface.text.link.LinkedModeUI; | |
14 | |
131 | 15 import dwtx.jface.text.link.LinkedModeModel; // packageimport |
16 import dwtx.jface.text.link.LinkedPosition; // packageimport | |
17 import dwtx.jface.text.link.ILinkedModeListener; // packageimport | |
18 import dwtx.jface.text.link.TabStopIterator; // packageimport | |
19 import dwtx.jface.text.link.InclusivePositionUpdater; // packageimport | |
20 import dwtx.jface.text.link.LinkedPositionGroup; // packageimport | |
21 import dwtx.jface.text.link.LinkedModeManager; // packageimport | |
22 import dwtx.jface.text.link.LinkedPositionAnnotations; // packageimport | |
23 import dwtx.jface.text.link.ProposalPosition; // packageimport | |
24 | |
25 | |
129 | 26 import dwt.dwthelper.utils; |
27 | |
28 import java.util.ArrayList; | |
29 import java.util.Arrays; | |
30 import java.util.HashSet; | |
31 import java.util.Iterator; | |
32 import java.util.List; | |
33 import java.util.Set; | |
34 | |
35 import dwt.DWT; | |
36 import dwt.custom.StyledText; | |
37 import dwt.custom.VerifyKeyListener; | |
38 import dwt.events.ShellEvent; | |
39 import dwt.events.ShellListener; | |
40 import dwt.events.VerifyEvent; | |
41 import dwt.graphics.Point; | |
42 import dwt.widgets.Display; | |
43 import dwt.widgets.Shell; | |
44 import dwtx.core.runtime.Assert; | |
45 import dwtx.jface.internal.text.link.contentassist.ContentAssistant2; | |
46 import dwtx.jface.internal.text.link.contentassist.IProposalListener; | |
47 import dwtx.jface.text.BadLocationException; | |
48 import dwtx.jface.text.BadPartitioningException; | |
49 import dwtx.jface.text.BadPositionCategoryException; | |
50 import dwtx.jface.text.DefaultPositionUpdater; | |
51 import dwtx.jface.text.DocumentCommand; | |
52 import dwtx.jface.text.DocumentEvent; | |
53 import dwtx.jface.text.IAutoEditStrategy; | |
54 import dwtx.jface.text.IDocument; | |
55 import dwtx.jface.text.IDocumentExtension3; | |
56 import dwtx.jface.text.IDocumentListener; | |
57 import dwtx.jface.text.IEditingSupport; | |
58 import dwtx.jface.text.IEditingSupportRegistry; | |
59 import dwtx.jface.text.IPositionUpdater; | |
60 import dwtx.jface.text.IRegion; | |
61 import dwtx.jface.text.IRewriteTarget; | |
62 import dwtx.jface.text.ITextInputListener; | |
63 import dwtx.jface.text.ITextOperationTarget; | |
64 import dwtx.jface.text.ITextSelection; | |
65 import dwtx.jface.text.ITextViewer; | |
66 import dwtx.jface.text.ITextViewerExtension; | |
67 import dwtx.jface.text.ITextViewerExtension2; | |
68 import dwtx.jface.text.ITextViewerExtension5; | |
69 import dwtx.jface.text.Position; | |
70 import dwtx.jface.text.Region; | |
71 import dwtx.jface.text.contentassist.ICompletionProposal; | |
72 import dwtx.jface.text.contentassist.ICompletionProposalExtension6; | |
73 import dwtx.jface.text.source.IAnnotationModel; | |
74 import dwtx.jface.text.source.IAnnotationModelExtension; | |
75 import dwtx.jface.text.source.ISourceViewer; | |
76 import dwtx.jface.viewers.IPostSelectionProvider; | |
77 import dwtx.jface.viewers.ISelection; | |
78 import dwtx.jface.viewers.ISelectionChangedListener; | |
79 import dwtx.jface.viewers.SelectionChangedEvent; | |
80 | |
81 /** | |
82 * The UI for linked mode. Detects events that influence behavior of the linked mode | |
83 * UI and acts upon them. | |
84 * <p> | |
85 * <code>LinkedModeUI</code> relies on all added | |
86 * <code>LinkedModeUITarget</code>s to provide implementations of | |
87 * <code>ITextViewer</code> that implement <code>ITextViewerExtension</code>, | |
88 * and the documents being edited to implement <code>IDocumentExtension3</code>. | |
89 * </p> | |
90 * <p> | |
91 * Clients may instantiate and extend this class. | |
92 * </p> | |
93 * | |
94 * @since 3.0 | |
95 */ | |
96 public class LinkedModeUI { | |
97 | |
98 /* cycle constants */ | |
99 /** | |
100 * Constant indicating that this UI should never cycle from the last | |
101 * position to the first and vice versa. | |
102 */ | |
103 public static final Object CYCLE_NEVER= new Object(); | |
104 /** | |
105 * Constant indicating that this UI should always cycle from the last | |
106 * position to the first and vice versa. | |
107 */ | |
108 public static final Object CYCLE_ALWAYS= new Object(); | |
109 /** | |
110 * Constant indicating that this UI should cycle from the last position to | |
111 * the first and vice versa if its model is not nested. | |
112 */ | |
113 public static final Object CYCLE_WHEN_NO_PARENT= new Object(); | |
114 | |
115 /** | |
116 * Listener that gets notified when the linked mode UI switches its focus position. | |
117 * <p> | |
118 * Clients may implement this interface. | |
119 * </p> | |
120 */ | |
121 public interface ILinkedModeUIFocusListener { | |
122 /** | |
123 * Called when the UI for the linked mode leaves a linked position. | |
124 * | |
125 * @param position the position being left | |
126 * @param target the target where <code>position</code> resides in | |
127 */ | |
128 void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target); | |
129 /** | |
130 * Called when the UI for the linked mode gives focus to a linked position. | |
131 * | |
132 * @param position the position being entered | |
133 * @param target the target where <code>position</code> resides in | |
134 */ | |
135 void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target); | |
136 } | |
137 | |
138 /** | |
139 * Null object implementation of focus listener. | |
140 */ | |
141 private static final class EmtpyFocusListener : ILinkedModeUIFocusListener { | |
142 | |
143 public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) { | |
144 // ignore | |
145 } | |
146 | |
147 public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) { | |
148 // ignore | |
149 } | |
150 } | |
151 | |
152 /** | |
153 * A link target consists of a viewer and gets notified if the linked mode UI on | |
154 * it is being shown. | |
155 * <p> | |
156 * Clients may extend this class. | |
157 * </p> | |
158 * @since 3.0 | |
159 */ | |
160 public static abstract class LinkedModeUITarget : ILinkedModeUIFocusListener { | |
161 /** | |
162 * Returns the viewer represented by this target, never <code>null</code>. | |
163 * | |
164 * @return the viewer associated with this target. | |
165 */ | |
166 public abstract ITextViewer getViewer(); | |
167 | |
168 /** | |
169 * The viewer's text widget is initialized when the UI first connects | |
170 * to the viewer and never changed thereafter. This is to keep the | |
171 * reference of the widget that we have registered our listeners with, | |
172 * as the viewer, when it gets disposed, does not remember it, resulting | |
173 * in a situation where we cannot uninstall the listeners and a memory leak. | |
174 */ | |
175 StyledText fWidget; | |
176 | |
177 /** The cached shell - same reason as fWidget. */ | |
178 Shell fShell; | |
179 | |
180 /** The registered listener, or <code>null</code>. */ | |
181 KeyListener fKeyListener; | |
182 | |
183 /** The cached custom annotation model. */ | |
184 LinkedPositionAnnotations fAnnotationModel; | |
185 } | |
186 | |
187 private static final class EmptyTarget : LinkedModeUITarget { | |
188 | |
189 private ITextViewer fTextViewer; | |
190 | |
191 /** | |
192 * @param viewer the viewer | |
193 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
194 public this(ITextViewer viewer) { |
129 | 195 Assert.isNotNull(viewer); |
196 fTextViewer= viewer; | |
197 } | |
198 | |
199 /* | |
200 * @see dwtx.jdt.internal.ui.text.link2.LinkedModeUI.ILinkedUITarget#getViewer() | |
201 */ | |
202 public ITextViewer getViewer() { | |
203 return fTextViewer; | |
204 } | |
205 | |
206 /** | |
207 * {@inheritDoc} | |
208 */ | |
209 public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) { | |
210 } | |
211 | |
212 /** | |
213 * {@inheritDoc} | |
214 */ | |
215 public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) { | |
216 } | |
217 | |
218 } | |
219 | |
220 /** | |
221 * Listens for state changes in the model. | |
222 */ | |
223 private final class ExitListener : ILinkedModeListener { | |
224 public void left(LinkedModeModel model, int flags) { | |
225 leave(ILinkedModeListener.EXIT_ALL | flags); | |
226 } | |
227 | |
228 public void suspend(LinkedModeModel model) { | |
229 disconnect(); | |
230 redraw(); | |
231 } | |
232 | |
233 public void resume(LinkedModeModel model, int flags) { | |
234 if ((flags & ILinkedModeListener.EXIT_ALL) !is 0) { | |
235 leave(flags); | |
236 } else { | |
237 connect(); | |
238 if ((flags & ILinkedModeListener.SELECT) !is 0) | |
239 select(); | |
240 ensureAnnotationModelInstalled(); | |
241 redraw(); | |
242 } | |
243 } | |
244 } | |
245 | |
246 /** | |
247 * Exit flags returned if a custom exit policy wants to exit linked mode. | |
248 * <p> | |
249 * Clients may instantiate this class. | |
250 * </p> | |
251 */ | |
252 public static class ExitFlags { | |
253 /** The flags to return in the <code>leave</code> method. */ | |
254 public int flags; | |
255 /** The doit flag of the checked <code>VerifyKeyEvent</code>. */ | |
256 public bool doit; | |
257 /** | |
258 * Creates a new instance. | |
259 * | |
260 * @param flags the exit flags | |
261 * @param doit the doit flag for the verify event | |
262 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
263 public this(int flags, bool doit) { |
129 | 264 this.flags= flags; |
265 this.doit= doit; | |
266 } | |
267 } | |
268 | |
269 /** | |
270 * An exit policy can be registered by a caller to get custom exit | |
271 * behavior. | |
272 * <p> | |
273 * Clients may implement this interface. | |
274 * </p> | |
275 */ | |
276 public interface IExitPolicy { | |
277 /** | |
278 * Checks whether the linked mode should be left after receiving the | |
279 * given <code>VerifyEvent</code> and selection. Note that the event | |
280 * carries widget coordinates as opposed to <code>offset</code> and | |
281 * <code>length</code> which are document coordinates. | |
282 * | |
283 * @param model the linked mode model | |
284 * @param event the verify event | |
285 * @param offset the offset of the current selection | |
286 * @param length the length of the current selection | |
287 * @return valid exit flags or <code>null</code> if no special action | |
288 * should be taken | |
289 */ | |
290 ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length); | |
291 } | |
292 | |
293 /** | |
294 * A NullObject implementation of <code>IExitPolicy</code>. | |
295 */ | |
296 private static class NullExitPolicy : IExitPolicy { | |
297 /* | |
298 * @see dwtx.jdt.internal.ui.text.link2.LinkedModeUI.IExitPolicy#doExit(dwt.events.VerifyEvent, int, int) | |
299 */ | |
300 public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) { | |
301 return null; | |
302 } | |
303 } | |
304 | |
305 /** | |
306 * Listens for shell events and acts upon them. | |
307 */ | |
308 private class Closer : ShellListener, ITextInputListener { | |
309 | |
310 public void shellActivated(ShellEvent e) { | |
311 } | |
312 | |
313 public void shellClosed(ShellEvent e) { | |
314 leave(ILinkedModeListener.EXIT_ALL); | |
315 } | |
316 | |
317 public void shellDeactivated(ShellEvent e) { | |
318 // TODO re-enable after debugging | |
319 // if (true) return; | |
320 | |
321 // from LinkedPositionUI: | |
322 | |
323 // don't deactivate on focus lost, since the proposal popups may take focus | |
324 // plus: it doesn't hurt if you can check with another window without losing linked mode | |
325 // since there is no intrusive popup sticking out. | |
326 | |
327 // need to check first what happens on reentering based on an open action | |
328 // Seems to be no problem | |
329 | |
330 // Better: | |
331 // Check with content assistant and only leave if its not the proposal shell that took the | |
332 // focus away. | |
333 | |
334 StyledText text; | |
335 final ITextViewer viewer; | |
336 Display display; | |
337 | |
338 if (fCurrentTarget is null || (text= fCurrentTarget.fWidget) is null | |
339 || text.isDisposed() || (display= text.getDisplay()) is null | |
340 || display.isDisposed() | |
341 || (viewer= fCurrentTarget.getViewer()) is null) | |
342 { | |
343 leave(ILinkedModeListener.EXIT_ALL); | |
344 } | |
345 else | |
346 { | |
347 // Post in UI thread since the assistant popup will only get the focus after we lose it. | |
135 | 348 display.asyncExec(new class() Runnable { |
129 | 349 public void run() { |
143 | 350 if (fIsActive && cast(IEditingSupportRegistry)viewer ) { |
134 | 351 IEditingSupport[] helpers= (cast(IEditingSupportRegistry) viewer).getRegisteredSupports(); |
129 | 352 for (int i= 0; i < helpers.length; i++) { |
353 if (helpers[i].ownsFocusShell()) | |
354 return; | |
355 } | |
356 } | |
357 | |
358 // else | |
359 leave(ILinkedModeListener.EXIT_ALL); | |
360 | |
361 } | |
362 }); | |
363 } | |
364 } | |
365 | |
366 public void shellDeiconified(ShellEvent e) { | |
367 } | |
368 | |
369 public void shellIconified(ShellEvent e) { | |
370 leave(ILinkedModeListener.EXIT_ALL); | |
371 } | |
372 | |
373 /* | |
374 * @see dwtx.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument) | |
375 */ | |
376 public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { | |
377 leave(ILinkedModeListener.EXIT_ALL); | |
378 } | |
379 | |
380 /* | |
381 * @see dwtx.jface.text.ITextInputListener#inputDocumentChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument) | |
382 */ | |
383 public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { | |
384 } | |
385 | |
386 } | |
387 | |
388 /** | |
389 * @since 3.1 | |
390 */ | |
391 private class DocumentListener : IDocumentListener { | |
392 /* | |
393 * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.jface.text.DocumentEvent) | |
394 */ | |
395 public void documentAboutToBeChanged(DocumentEvent event) { | |
396 | |
397 // default behavior: any document change outside a linked position | |
398 // causes us to exit | |
399 int end= event.getOffset() + event.getLength(); | |
400 for (int offset= event.getOffset(); offset <= end; offset++) { | |
401 if (!fModel.anyPositionContains(offset)) { | |
402 ITextViewer viewer= fCurrentTarget.getViewer(); | |
143 | 403 if (fFramePosition !is null && cast(IEditingSupportRegistry)viewer ) { |
134 | 404 IEditingSupport[] helpers= (cast(IEditingSupportRegistry) viewer).getRegisteredSupports(); |
129 | 405 for (int i= 0; i < helpers.length; i++) { |
406 if (helpers[i].isOriginator(null, new Region(fFramePosition.getOffset(), fFramePosition.getLength()))) | |
407 return; | |
408 } | |
409 } | |
410 | |
411 leave(ILinkedModeListener.EXTERNAL_MODIFICATION); | |
412 return; | |
413 } | |
414 } | |
415 } | |
416 | |
417 /* | |
418 * @see dwtx.jface.text.IDocumentListener#documentChanged(dwtx.jface.text.DocumentEvent) | |
419 */ | |
420 public void documentChanged(DocumentEvent event) { | |
421 } | |
422 } | |
423 | |
424 /** | |
425 * Listens for key events, checks the exit policy for custom exit | |
426 * strategies but defaults to handling Tab, Enter, and Escape. | |
427 */ | |
428 private class KeyListener : VerifyKeyListener { | |
429 | |
430 private bool fIsEnabled= true; | |
431 | |
432 public void verifyKey(VerifyEvent event) { | |
433 | |
434 if (!event.doit || !fIsEnabled) | |
435 return; | |
436 | |
437 Point selection= fCurrentTarget.getViewer().getSelectedRange(); | |
438 int offset= selection.x; | |
439 int length= selection.y; | |
440 | |
441 // if the custom exit policy returns anything, use that | |
442 ExitFlags exitFlags= fExitPolicy.doExit(fModel, event, offset, length); | |
443 if (exitFlags !is null) { | |
444 leave(exitFlags.flags); | |
445 event.doit= exitFlags.doit; | |
446 return; | |
447 } | |
448 | |
449 // standard behavior: | |
450 // (Shift+)Tab: jumps from position to position, depending on cycle mode | |
451 // Enter: accepts all entries and leaves all (possibly stacked) environments, the last sets the caret | |
452 // Esc: accepts all entries and leaves all (possibly stacked) environments, the caret stays | |
453 // ? what do we do to leave one level of a cycling model that is stacked? | |
134 | 454 // -> This is only the case if the level was set up with forced cycling cast(CYCLE_ALWAYS), in which case |
129 | 455 // the caller is sure that one does not need by-level exiting. |
456 switch (event.character) { | |
457 // [SHIFT-]TAB = hop between edit boxes | |
458 case 0x09: | |
459 if (!(fExitPosition !is null && fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) { | |
460 // outside any edit box -> leave (all? TODO should only leave the affected, level and forward to the next upper) | |
461 leave(ILinkedModeListener.EXIT_ALL); | |
462 break; | |
463 } | |
464 | |
465 if (event.stateMask is DWT.SHIFT) | |
466 previous(); | |
467 else | |
468 next(); | |
469 | |
470 event.doit= false; | |
471 break; | |
472 | |
473 // ENTER | |
474 case 0x0A: | |
475 // Ctrl+Enter on WinXP | |
476 case 0x0D: | |
477 // if ((fExitPosition !is null && fExitPosition.includes(offset)) || !fModel.anyPositionContains(offset)) { | |
478 if (!fModel.anyPositionContains(offset)) { | |
479 // if ((fExitPosition is null || !fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) { | |
480 // outside any edit box or on exit position -> leave (all? TODO should only leave the affected, level and forward to the next upper) | |
481 leave(ILinkedModeListener.EXIT_ALL); | |
482 break; | |
483 } | |
484 | |
485 // normal case: exit entire stack and put caret to final position | |
486 leave(ILinkedModeListener.EXIT_ALL | ILinkedModeListener.UPDATE_CARET); | |
487 event.doit= false; | |
488 break; | |
489 | |
490 // ESC | |
491 case 0x1B: | |
492 // exit entire stack and leave caret | |
493 leave(ILinkedModeListener.EXIT_ALL); | |
494 event.doit= false; | |
495 break; | |
496 | |
497 default: | |
498 if (event.character !is 0) { | |
499 if (!controlUndoBehavior(offset, length)) { | |
500 leave(ILinkedModeListener.EXIT_ALL); | |
501 break; | |
502 } | |
503 } | |
504 } | |
505 } | |
506 | |
507 private bool controlUndoBehavior(int offset, int length) { | |
508 LinkedPosition position= fModel.findPosition(new LinkedPosition(fCurrentTarget.getViewer().getDocument(), offset, length, LinkedPositionGroup.NO_STOP)); | |
509 if (position !is null) { | |
510 | |
511 // if the last position is not the same and there is an open change: close it. | |
512 if (!position.equals(fPreviousPosition)) | |
513 endCompoundChange(); | |
514 | |
515 beginCompoundChange(); | |
516 } | |
517 | |
518 fPreviousPosition= position; | |
519 return fPreviousPosition !is null; | |
520 } | |
521 | |
522 /** | |
523 * @param enabled the new enabled state | |
524 */ | |
525 public void setEnabled(bool enabled) { | |
526 fIsEnabled= enabled; | |
527 } | |
528 | |
529 } | |
530 | |
531 /** | |
532 * Installed as post selection listener on the watched viewer. Updates the | |
533 * linked position after cursor movement, even to positions not in the | |
534 * iteration list. | |
535 */ | |
536 private class MySelectionListener : ISelectionChangedListener { | |
537 | |
538 /* | |
539 * @see dwtx.jface.viewers.ISelectionChangedListener#selectionChanged(dwtx.jface.viewers.SelectionChangedEvent) | |
540 */ | |
541 public void selectionChanged(SelectionChangedEvent event) { | |
542 ISelection selection= event.getSelection(); | |
138 | 543 if ( cast(ITextSelection)selection ) { |
134 | 544 ITextSelection textsel= cast(ITextSelection) selection; |
129 | 545 if (event.getSelectionProvider() instanceof ITextViewer) { |
134 | 546 IDocument doc= (cast(ITextViewer) event.getSelectionProvider()).getDocument(); |
129 | 547 if (doc !is null) { |
548 int offset= textsel.getOffset(); | |
549 int length= textsel.getLength(); | |
550 if (offset >= 0 && length >= 0) { | |
551 LinkedPosition find= new LinkedPosition(doc, offset, length, LinkedPositionGroup.NO_STOP); | |
552 LinkedPosition pos= fModel.findPosition(find); | |
553 if (pos is null && fExitPosition !is null && fExitPosition.includes(find)) | |
554 pos= fExitPosition; | |
555 | |
556 if (pos !is null) | |
557 switchPosition(pos, false, false); | |
558 } | |
559 } | |
560 } | |
561 } | |
562 } | |
563 | |
564 } | |
565 | |
566 private class ProposalListener : IProposalListener { | |
567 | |
568 /* | |
569 * @see dwtx.jface.internal.text.link.contentassist.IProposalListener#proposalChosen(dwtx.jface.text.contentassist.ICompletionProposal) | |
570 */ | |
571 public void proposalChosen(ICompletionProposal proposal) { | |
572 next(); | |
573 } | |
574 } | |
575 | |
576 /** The current viewer. */ | |
577 private LinkedModeUITarget fCurrentTarget; | |
578 /** | |
579 * The manager of the linked positions we provide a UI for. | |
580 * @since 3.1 | |
581 */ | |
582 private LinkedModeModel fModel; | |
583 /** The set of viewers we manage. */ | |
584 private LinkedModeUITarget[] fTargets; | |
585 /** The iterator over the tab stop positions. */ | |
586 private TabStopIterator fIterator; | |
587 | |
588 /* Our team of event listeners */ | |
589 /** The shell listener. */ | |
590 private Closer fCloser= new Closer(); | |
591 /** The linked mode listener. */ | |
592 private ILinkedModeListener fLinkedListener= new ExitListener(); | |
593 /** The selection listener. */ | |
594 private MySelectionListener fSelectionListener= new MySelectionListener(); | |
595 /** The content assist listener. */ | |
596 private ProposalListener fProposalListener= new ProposalListener(); | |
597 /** | |
598 * The document listener. | |
599 * @since 3.1 | |
600 */ | |
601 private IDocumentListener fDocumentListener= new DocumentListener(); | |
602 | |
603 /** The last caret position, used by fCaretListener. */ | |
604 private final Position fCaretPosition= new Position(0, 0); | |
605 /** The exit policy to control custom exit behavior */ | |
606 private IExitPolicy fExitPolicy= new NullExitPolicy(); | |
607 /** The current frame position shown in the UI, or <code>null</code>. */ | |
608 private LinkedPosition fFramePosition; | |
609 /** The last visited position, used for undo / redo. */ | |
610 private LinkedPosition fPreviousPosition; | |
611 /** The content assistant used to show proposals. */ | |
612 private ContentAssistant2 fAssistant; | |
613 /** The exit position. */ | |
614 private LinkedPosition fExitPosition; | |
615 /** State indicator to prevent multiple invocation of leave. */ | |
616 private bool fIsActive= false; | |
617 /** The position updater for the exit position. */ | |
618 private IPositionUpdater fPositionUpdater= new DefaultPositionUpdater(getCategory()); | |
619 /** Whether to show context info. */ | |
620 private bool fDoContextInfo= false; | |
621 /** Whether we have begun a compound change, but not yet closed. */ | |
622 private bool fHasOpenCompoundChange= false; | |
623 /** The position listener. */ | |
624 private ILinkedModeUIFocusListener fPositionListener= new EmtpyFocusListener(); | |
135 | 625 private IAutoEditStrategy fAutoEditVetoer= new class() IAutoEditStrategy { |
129 | 626 |
627 /* | |
628 * @see dwtx.jface.text.IAutoEditStrategy#customizeDocumentCommand(dwtx.jface.text.IDocument, dwtx.jface.text.DocumentCommand) | |
629 */ | |
630 public void customizeDocumentCommand(IDocument document, DocumentCommand command) { | |
631 // invalidate the change to ensure that the change is performed on the document only. | |
632 if (fModel.anyPositionContains(command.offset)) { | |
633 command.doit= false; | |
634 command.caretOffset= command.offset + command.length; | |
635 } | |
636 | |
637 } | |
638 }; | |
639 | |
640 | |
641 /** Whether this UI is in simple highlighting mode or not. */ | |
642 private bool fSimple; | |
643 | |
644 /** | |
645 * Tells whether colored labels support is enabled. | |
646 * @since 3.4 | |
647 */ | |
648 private bool fIsColoredLabelsSupportEnabled= false; | |
649 | |
650 /** | |
651 * Creates a new UI on the given model and the set of viewers. The model | |
652 * must provide a tab stop sequence with a non-empty list of tab stops. | |
653 * | |
654 * @param model the linked mode model | |
655 * @param targets the non-empty list of targets upon which the linked mode | |
656 * UI should act | |
657 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
658 public this(LinkedModeModel model, LinkedModeUITarget[] targets) { |
129 | 659 constructor(model, targets); |
660 } | |
661 | |
662 /** | |
663 * Convenience constructor for just one viewer. | |
664 * | |
665 * @param model the linked mode model | |
666 * @param viewer the viewer upon which the linked mode UI should act | |
667 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
668 public this(LinkedModeModel model, ITextViewer viewer) { |
129 | 669 constructor(model, new LinkedModeUITarget[]{new EmptyTarget(viewer)}); |
670 } | |
671 | |
672 /** | |
673 * Convenience constructor for multiple viewers. | |
674 * | |
675 * @param model the linked mode model | |
676 * @param viewers the non-empty list of viewers upon which the linked mode | |
677 * UI should act | |
678 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
679 public this(LinkedModeModel model, ITextViewer[] viewers) { |
129 | 680 LinkedModeUITarget[] array= new LinkedModeUITarget[viewers.length]; |
681 for (int i= 0; i < array.length; i++) { | |
682 array[i]= new EmptyTarget(viewers[i]); | |
683 } | |
684 constructor(model, array); | |
685 } | |
686 | |
687 /** | |
688 * Convenience constructor for one target. | |
689 * | |
690 * @param model the linked mode model | |
691 * @param target the target upon which the linked mode UI should act | |
692 */ | |
133
7d818bd32d63
Fix ctors to this with gvim regexp
Frank Benoit <benoit@tionex.de>
parents:
131
diff
changeset
|
693 public this(LinkedModeModel model, LinkedModeUITarget target) { |
129 | 694 constructor(model, new LinkedModeUITarget[]{target}); |
695 } | |
696 | |
697 /** | |
698 * This does the actual constructor work. | |
699 * | |
700 * @param model the linked mode model | |
701 * @param targets the non-empty array of targets upon which the linked mode UI | |
702 * should act | |
703 */ | |
704 private void constructor(LinkedModeModel model, LinkedModeUITarget[] targets) { | |
705 Assert.isNotNull(model); | |
706 Assert.isNotNull(targets); | |
707 Assert.isTrue(targets.length > 0); | |
708 Assert.isTrue(model.getTabStopSequence().size() > 0); | |
709 | |
710 fModel= model; | |
711 fTargets= targets; | |
712 fCurrentTarget= targets[0]; | |
713 fIterator= new TabStopIterator(fModel.getTabStopSequence()); | |
714 fIterator.setCycling(!fModel.isNested()); | |
715 fModel.addLinkingListener(fLinkedListener); | |
716 | |
717 fAssistant= new ContentAssistant2(); | |
718 fAssistant.addProposalListener(fProposalListener); | |
719 // TODO find a way to set up content assistant. | |
720 // fAssistant.setDocumentPartitioning(IJavaPartitions.JAVA_PARTITIONING); | |
721 fAssistant.enableColoredLabels(fIsColoredLabelsSupportEnabled); | |
140
26688fec6d23
Following dsss compile errors
Frank Benoit <benoit@tionex.de>
parents:
138
diff
changeset
|
722 fCaretPosition.delete_(); |
129 | 723 } |
724 | |
725 /** | |
726 * Starts this UI on the first position. | |
727 */ | |
728 public void enter() { | |
729 fIsActive= true; | |
730 connect(); | |
731 next(); | |
732 } | |
733 | |
734 /** | |
735 * Sets an <code>IExitPolicy</code> to customize the exit behavior of | |
736 * this linked mode UI. | |
737 * | |
738 * @param policy the exit policy to use. | |
739 */ | |
740 public void setExitPolicy(IExitPolicy policy) { | |
741 fExitPolicy= policy; | |
742 } | |
743 | |
744 /** | |
745 * Sets the exit position to move the caret to when linked mode mode is | |
746 * exited. | |
747 * | |
748 * @param target the target where the exit position is located | |
749 * @param offset the offset of the exit position | |
750 * @param length the length of the exit position (in case there should be a | |
751 * selection) | |
752 * @param sequence set to the tab stop position of the exit position, or | |
753 * <code>LinkedPositionGroup.NO_STOP</code> if there should be no | |
754 * tab stop. | |
755 * @throws BadLocationException if the position is not valid in the viewer's | |
756 * document | |
757 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
135
diff
changeset
|
758 public void setExitPosition(LinkedModeUITarget target, int offset, int length, int sequence) { |
129 | 759 // remove any existing exit position |
760 if (fExitPosition !is null) { | |
761 fExitPosition.getDocument().removePosition(fExitPosition); | |
762 fIterator.removePosition(fExitPosition); | |
763 fExitPosition= null; | |
764 } | |
765 | |
766 IDocument doc= target.getViewer().getDocument(); | |
767 if (doc is null) | |
768 return; | |
769 | |
770 fExitPosition= new LinkedPosition(doc, offset, length, sequence); | |
771 doc.addPosition(fExitPosition); // gets removed in leave() | |
772 if (sequence !is LinkedPositionGroup.NO_STOP) | |
773 fIterator.addPosition(fExitPosition); | |
774 | |
775 } | |
776 | |
777 /** | |
778 * Sets the exit position to move the caret to when linked mode is exited. | |
779 * | |
780 * @param viewer the viewer where the exit position is located | |
781 * @param offset the offset of the exit position | |
782 * @param length the length of the exit position (in case there should be a | |
783 * selection) | |
784 * @param sequence set to the tab stop position of the exit position, or | |
785 * <code>LinkedPositionGroup.NO_STOP</code> if there should be no tab stop. | |
786 * @throws BadLocationException if the position is not valid in the | |
787 * viewer's document | |
788 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
135
diff
changeset
|
789 public void setExitPosition(ITextViewer viewer, int offset, int length, int sequence) { |
129 | 790 setExitPosition(new EmptyTarget(viewer), offset, length, sequence); |
791 } | |
792 | |
793 /** | |
794 * Sets the cycling mode to either of <code>CYCLING_ALWAYS</code>, | |
795 * <code>CYCLING_NEVER</code>, or <code>CYCLING_WHEN_NO_PARENT</code>, | |
796 * which is the default. | |
797 * | |
798 * @param mode the new cycling mode. | |
799 */ | |
800 public void setCyclingMode(Object mode) { | |
801 if (mode !is CYCLE_ALWAYS && mode !is CYCLE_NEVER && mode !is CYCLE_WHEN_NO_PARENT) | |
802 throw new IllegalArgumentException(); | |
803 | |
804 if (mode is CYCLE_ALWAYS || mode is CYCLE_WHEN_NO_PARENT && !fModel.isNested()) | |
805 fIterator.setCycling(true); | |
806 else | |
807 fIterator.setCycling(false); | |
808 } | |
809 | |
810 void next() { | |
811 if (fIterator.hasNext(fFramePosition)) { | |
812 switchPosition(fIterator.next(fFramePosition), true, true); | |
813 return; | |
814 } | |
815 leave(ILinkedModeListener.UPDATE_CARET); | |
816 } | |
817 | |
818 void previous() { | |
819 if (fIterator.hasPrevious(fFramePosition)) { | |
820 switchPosition(fIterator.previous(fFramePosition), true, true); | |
821 } else | |
822 // dont't update caret, but rather select the current frame | |
823 leave(ILinkedModeListener.SELECT); | |
824 } | |
825 | |
826 private void triggerContextInfo() { | |
827 ITextOperationTarget target= fCurrentTarget.getViewer().getTextOperationTarget(); | |
828 if (target !is null) { | |
829 if (target.canDoOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION)) | |
830 target.doOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION); | |
831 } | |
832 } | |
833 | |
834 /** Trigger content assist on choice positions */ | |
835 private void triggerContentAssist() { | |
138 | 836 if ( cast(ProposalPosition)fFramePosition ) { |
134 | 837 ProposalPosition pp= cast(ProposalPosition) fFramePosition; |
129 | 838 ICompletionProposal[] choices= pp.getChoices(); |
839 if (choices !is null && choices.length > 0) { | |
840 fAssistant.setCompletions(choices); | |
841 fAssistant.showPossibleCompletions(); | |
842 return; | |
843 } | |
844 } | |
845 | |
846 fAssistant.setCompletions(new ICompletionProposal[0]); | |
847 fAssistant.hidePossibleCompletions(); | |
848 } | |
849 | |
850 private void switchPosition(LinkedPosition pos, bool select, bool showProposals) { | |
851 Assert.isNotNull(pos); | |
852 if (pos.equals(fFramePosition)) | |
853 return; | |
854 | |
855 if (fFramePosition !is null && fCurrentTarget !is null) | |
856 fPositionListener.linkingFocusLost(fFramePosition, fCurrentTarget); | |
857 | |
858 // undo | |
859 endCompoundChange(); | |
860 | |
861 redraw(); // redraw current position being left - usually not needed | |
862 IDocument oldDoc= fFramePosition is null ? null : fFramePosition.getDocument(); | |
863 IDocument newDoc= pos.getDocument(); | |
864 | |
865 switchViewer(oldDoc, newDoc, pos); | |
866 fFramePosition= pos; | |
867 | |
868 if (select) | |
869 select(); | |
870 if (fFramePosition is fExitPosition && !fIterator.isCycling()) | |
871 leave(ILinkedModeListener.NONE); | |
872 else { | |
873 redraw(); // redraw new position | |
874 ensureAnnotationModelInstalled(); | |
875 } | |
876 if (showProposals) | |
877 triggerContentAssist(); | |
878 if (fFramePosition !is fExitPosition && fDoContextInfo) | |
879 triggerContextInfo(); | |
880 | |
881 if (fFramePosition !is null && fCurrentTarget !is null) | |
882 fPositionListener.linkingFocusGained(fFramePosition, fCurrentTarget); | |
883 | |
884 } | |
885 | |
886 private void ensureAnnotationModelInstalled() { | |
887 LinkedPositionAnnotations lpa= fCurrentTarget.fAnnotationModel; | |
888 if (lpa !is null) { | |
889 ITextViewer viewer= fCurrentTarget.getViewer(); | |
138 | 890 if ( cast(ISourceViewer)viewer ) { |
134 | 891 ISourceViewer sv= cast(ISourceViewer) viewer; |
129 | 892 IAnnotationModel model= sv.getAnnotationModel(); |
138 | 893 if ( cast(IAnnotationModelExtension)model ) { |
134 | 894 IAnnotationModelExtension ext= cast(IAnnotationModelExtension) model; |
129 | 895 IAnnotationModel ourModel= ext.getAnnotationModel(getUniqueKey()); |
896 if (ourModel is null) { | |
897 ext.addAnnotationModel(getUniqueKey(), lpa); | |
898 } | |
899 } | |
900 } | |
901 } | |
902 } | |
903 | |
904 private void uninstallAnnotationModel(LinkedModeUITarget target) { | |
905 ITextViewer viewer= target.getViewer(); | |
138 | 906 if ( cast(ISourceViewer)viewer ) { |
134 | 907 ISourceViewer sv= cast(ISourceViewer) viewer; |
129 | 908 IAnnotationModel model= sv.getAnnotationModel(); |
138 | 909 if ( cast(IAnnotationModelExtension)model ) { |
134 | 910 IAnnotationModelExtension ext= cast(IAnnotationModelExtension) model; |
129 | 911 ext.removeAnnotationModel(getUniqueKey()); |
912 } | |
913 } | |
914 } | |
915 | |
916 private void switchViewer(IDocument oldDoc, IDocument newDoc, LinkedPosition pos) { | |
917 if (oldDoc !is newDoc) { | |
918 | |
919 // redraw current document with new position before switching viewer | |
920 if (fCurrentTarget.fAnnotationModel !is null) | |
921 fCurrentTarget.fAnnotationModel.switchToPosition(fModel, pos); | |
922 | |
923 LinkedModeUITarget target= null; | |
924 for (int i= 0; i < fTargets.length; i++) { | |
925 if (fTargets[i].getViewer().getDocument() is newDoc) { | |
926 target= fTargets[i]; | |
927 break; | |
928 } | |
929 } | |
930 if (target !is fCurrentTarget) { | |
931 disconnect(); | |
932 fCurrentTarget= target; | |
933 target.linkingFocusLost(fFramePosition, target); | |
934 connect(); | |
935 ensureAnnotationModelInstalled(); | |
936 if (fCurrentTarget !is null) | |
937 fCurrentTarget.linkingFocusGained(pos, fCurrentTarget); | |
938 } | |
939 } | |
940 } | |
941 | |
942 private void select() { | |
943 ITextViewer viewer= fCurrentTarget.getViewer(); | |
138 | 944 if ( cast(ITextViewerExtension5)viewer ) { |
134 | 945 ITextViewerExtension5 extension5= cast(ITextViewerExtension5) viewer; |
129 | 946 extension5.exposeModelRange(new Region(fFramePosition.offset, fFramePosition.length)); |
947 } else if (!viewer.overlapsWithVisibleRegion(fFramePosition.offset, fFramePosition.length)) { | |
948 viewer.resetVisibleRegion(); | |
949 } | |
950 viewer.revealRange(fFramePosition.offset, fFramePosition.length); | |
951 viewer.setSelectedRange(fFramePosition.offset, fFramePosition.length); | |
952 } | |
953 | |
954 private void redraw() { | |
955 if (fCurrentTarget.fAnnotationModel !is null) | |
956 fCurrentTarget.fAnnotationModel.switchToPosition(fModel, fFramePosition); | |
957 } | |
958 | |
959 private void connect() { | |
960 Assert.isNotNull(fCurrentTarget); | |
961 ITextViewer viewer= fCurrentTarget.getViewer(); | |
962 Assert.isNotNull(viewer); | |
963 fCurrentTarget.fWidget= viewer.getTextWidget(); | |
964 if (fCurrentTarget.fWidget is null) | |
965 leave(ILinkedModeListener.EXIT_ALL); | |
966 | |
967 if (fCurrentTarget.fKeyListener is null) { | |
968 fCurrentTarget.fKeyListener= new KeyListener(); | |
134 | 969 (cast(ITextViewerExtension) viewer).prependVerifyKeyListener(fCurrentTarget.fKeyListener); |
129 | 970 } else |
971 fCurrentTarget.fKeyListener.setEnabled(true); | |
972 | |
973 registerAutoEditVetoer(viewer); | |
974 | |
134 | 975 (cast(IPostSelectionProvider) viewer).addPostSelectionChangedListener(fSelectionListener); |
129 | 976 |
977 createAnnotationModel(); | |
978 | |
979 showSelection(); | |
980 | |
981 fCurrentTarget.fShell= fCurrentTarget.fWidget.getShell(); | |
982 if (fCurrentTarget.fShell is null) | |
983 leave(ILinkedModeListener.EXIT_ALL); | |
984 fCurrentTarget.fShell.addShellListener(fCloser); | |
985 | |
986 fAssistant.install(viewer); | |
987 | |
988 viewer.addTextInputListener(fCloser); | |
989 | |
990 viewer.getDocument().addDocumentListener(fDocumentListener); | |
991 } | |
992 | |
993 /** | |
994 * Reveals the selection on the current target's widget, if it is valid. | |
995 */ | |
996 private void showSelection() { | |
997 final StyledText widget= fCurrentTarget.fWidget; | |
998 if (widget is null || widget.isDisposed()) | |
999 return; | |
1000 | |
1001 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132263 | |
135 | 1002 widget.getDisplay().asyncExec(new class() Runnable { |
129 | 1003 public void run() { |
1004 if (!widget.isDisposed()) | |
1005 try { | |
1006 widget.showSelection(); | |
1007 } catch (IllegalArgumentException e) { | |
1008 /* | |
1009 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=66914 | |
1010 * if the StyledText is in setRedraw(false) mode, its | |
1011 * selection may not be up2date and calling showSelection | |
1012 * will throw an IAE. | |
1013 * We don't have means to find out whether the selection is valid | |
1014 * or whether the widget is redrawing or not therefore we try | |
1015 * and ignore an IAE. | |
1016 */ | |
1017 } | |
1018 } | |
1019 }); | |
1020 } | |
1021 | |
1022 /** | |
1023 * Registers our auto edit vetoer with the viewer. | |
1024 * | |
1025 * @param viewer the viewer we want to veto ui-triggered changes within | |
1026 * linked positions | |
1027 */ | |
1028 private void registerAutoEditVetoer(ITextViewer viewer) { | |
1029 try { | |
1030 String[] contentTypes= getContentTypes(viewer.getDocument()); | |
138 | 1031 if ( cast(ITextViewerExtension2)viewer ) { |
134 | 1032 ITextViewerExtension2 vExtension= (cast(ITextViewerExtension2) viewer); |
129 | 1033 for (int i= 0; i < contentTypes.length; i++) { |
1034 vExtension.prependAutoEditStrategy(fAutoEditVetoer, contentTypes[i]); | |
1035 } | |
1036 } else { | |
1037 Assert.isTrue(false); | |
1038 } | |
1039 | |
1040 } catch (BadPartitioningException e) { | |
1041 leave(ILinkedModeListener.EXIT_ALL); | |
1042 } | |
1043 } | |
1044 | |
1045 private void unregisterAutoEditVetoer(ITextViewer viewer) { | |
1046 try { | |
1047 String[] contentTypes= getContentTypes(viewer.getDocument()); | |
138 | 1048 if ( cast(ITextViewerExtension2)viewer ) { |
134 | 1049 ITextViewerExtension2 vExtension= (cast(ITextViewerExtension2) viewer); |
129 | 1050 for (int i= 0; i < contentTypes.length; i++) { |
1051 vExtension.removeAutoEditStrategy(fAutoEditVetoer, contentTypes[i]); | |
1052 } | |
1053 } else { | |
1054 Assert.isTrue(false); | |
1055 } | |
1056 } catch (BadPartitioningException e) { | |
1057 leave(ILinkedModeListener.EXIT_ALL); | |
1058 } | |
1059 } | |
1060 | |
1061 /** | |
1062 * Returns all possible content types of <code>document</code>. | |
1063 * | |
1064 * @param document the document | |
1065 * @return all possible content types of <code>document</code> | |
1066 * @throws BadPartitioningException | |
1067 * @since 3.1 | |
1068 */ | |
136
6dcb0baaa031
Regex removal of throws decls, some instanceof
Frank Benoit <benoit@tionex.de>
parents:
135
diff
changeset
|
1069 private String[] getContentTypes(IDocument document) { |
138 | 1070 if ( cast(IDocumentExtension3)document ) { |
134 | 1071 IDocumentExtension3 ext= cast(IDocumentExtension3) document; |
129 | 1072 String[] partitionings= ext.getPartitionings(); |
1073 Set contentTypes= new HashSet(20); | |
1074 for (int i= 0; i < partitionings.length; i++) { | |
1075 contentTypes.addAll(Arrays.asList(ext.getLegalContentTypes(partitionings[i]))); | |
1076 } | |
1077 contentTypes.add(IDocument.DEFAULT_CONTENT_TYPE); | |
1078 return (String[]) contentTypes.toArray(new String[contentTypes.size()]); | |
1079 } | |
1080 return document.getLegalContentTypes(); | |
1081 } | |
1082 | |
1083 private void createAnnotationModel() { | |
1084 if (fCurrentTarget.fAnnotationModel is null) { | |
1085 LinkedPositionAnnotations lpa= new LinkedPositionAnnotations(); | |
1086 if (fSimple) { | |
1087 lpa.markExitTarget(true); | |
1088 lpa.markFocus(false); | |
1089 lpa.markSlaves(false); | |
1090 lpa.markTargets(false); | |
1091 } | |
1092 lpa.setTargets(fIterator.getPositions()); | |
1093 lpa.setExitTarget(fExitPosition); | |
1094 lpa.connect(fCurrentTarget.getViewer().getDocument()); | |
1095 fCurrentTarget.fAnnotationModel= lpa; | |
1096 } | |
1097 } | |
1098 | |
1099 private String getUniqueKey() { | |
1100 return "linked.annotationmodelkey."+toString(); //$NON-NLS-1$ | |
1101 } | |
1102 | |
1103 private void disconnect() { | |
1104 Assert.isNotNull(fCurrentTarget); | |
1105 ITextViewer viewer= fCurrentTarget.getViewer(); | |
1106 Assert.isNotNull(viewer); | |
1107 | |
1108 viewer.getDocument().removeDocumentListener(fDocumentListener); | |
1109 | |
1110 fAssistant.uninstall(); | |
1111 fAssistant.removeProposalListener(fProposalListener); | |
1112 | |
1113 fCurrentTarget.fWidget= null; | |
1114 | |
1115 Shell shell= fCurrentTarget.fShell; | |
1116 fCurrentTarget.fShell= null; | |
1117 | |
1118 if (shell !is null && !shell.isDisposed()) | |
1119 shell.removeShellListener(fCloser); | |
1120 | |
1121 // this one is asymmetric: we don't install the model in | |
1122 // connect, but leave it to its callers to ensure they | |
1123 // have the model installed if they need it | |
1124 uninstallAnnotationModel(fCurrentTarget); | |
1125 | |
1126 unregisterAutoEditVetoer(viewer); | |
1127 | |
1128 // don't remove the verify key listener to let it keep its position | |
1129 // in the listener queue | |
1130 if (fCurrentTarget.fKeyListener !is null) | |
1131 fCurrentTarget.fKeyListener.setEnabled(false); | |
1132 | |
134 | 1133 (cast(IPostSelectionProvider) viewer).removePostSelectionChangedListener(fSelectionListener); |
129 | 1134 |
1135 redraw(); | |
1136 } | |
1137 | |
1138 void leave(final int flags) { | |
1139 if (!fIsActive) | |
1140 return; | |
1141 fIsActive= false; | |
1142 | |
1143 endCompoundChange(); | |
1144 | |
1145 Display display= null; | |
1146 if (fCurrentTarget.fWidget !is null && !fCurrentTarget.fWidget.isDisposed()) | |
1147 display= fCurrentTarget.fWidget.getDisplay(); | |
1148 | |
1149 if (fCurrentTarget.fAnnotationModel !is null) | |
1150 fCurrentTarget.fAnnotationModel.removeAllAnnotations(); | |
1151 disconnect(); | |
1152 | |
1153 for (int i= 0; i < fTargets.length; i++) { | |
1154 LinkedModeUITarget target= fTargets[i]; | |
1155 ITextViewer viewer= target.getViewer(); | |
1156 if (target.fKeyListener !is null) { | |
134 | 1157 (cast(ITextViewerExtension) viewer).removeVerifyKeyListener(target.fKeyListener); |
129 | 1158 target.fKeyListener= null; |
1159 } | |
1160 | |
1161 viewer.removeTextInputListener(fCloser); | |
1162 } | |
1163 | |
1164 for (int i= 0; i < fTargets.length; i++) { | |
1165 | |
1166 if (fTargets[i].fAnnotationModel !is null) { | |
1167 fTargets[i].fAnnotationModel.removeAllAnnotations(); | |
1168 fTargets[i].fAnnotationModel.disconnect(fTargets[i].getViewer().getDocument()); | |
1169 fTargets[i].fAnnotationModel= null; | |
1170 } | |
1171 | |
1172 uninstallAnnotationModel(fTargets[i]); | |
1173 } | |
1174 | |
1175 | |
1176 if ((flags & ILinkedModeListener.UPDATE_CARET) !is 0 && fExitPosition !is null && fFramePosition !is fExitPosition && !fExitPosition.isDeleted()) | |
1177 switchPosition(fExitPosition, true, false); | |
1178 | |
1179 final List docs= new ArrayList(); | |
1180 for (int i= 0; i < fTargets.length; i++) { | |
1181 IDocument doc= fTargets[i].getViewer().getDocument(); | |
1182 if (doc !is null) | |
1183 docs.add(doc); | |
1184 } | |
1185 | |
1186 fModel.stopForwarding(flags); | |
1187 | |
135 | 1188 Runnable runnable= new class() Runnable { |
129 | 1189 public void run() { |
1190 if (fExitPosition !is null) | |
1191 fExitPosition.getDocument().removePosition(fExitPosition); | |
1192 | |
1193 for (Iterator iter = docs.iterator(); iter.hasNext(); ) { | |
134 | 1194 IDocument doc= cast(IDocument) iter.next(); |
129 | 1195 doc.removePositionUpdater(fPositionUpdater); |
1196 bool uninstallCat= false; | |
1197 String[] cats= doc.getPositionCategories(); | |
1198 for (int j= 0; j < cats.length; j++) { | |
1199 if (getCategory().equals(cats[j])) { | |
1200 uninstallCat= true; | |
1201 break; | |
1202 } | |
1203 } | |
1204 if (uninstallCat) | |
1205 try { | |
1206 doc.removePositionCategory(getCategory()); | |
1207 } catch (BadPositionCategoryException e) { | |
1208 // ignore | |
1209 } | |
1210 } | |
1211 fModel.exit(flags); | |
1212 } | |
1213 }; | |
1214 | |
1215 // remove positions (both exit positions AND linked positions in the | |
1216 // model) asynchronously to make sure that the annotation painter | |
1217 // gets correct document offsets. | |
1218 if (display !is null) | |
1219 display.asyncExec(runnable); | |
1220 else | |
1221 runnable.run(); | |
1222 } | |
1223 | |
1224 private void endCompoundChange() { | |
1225 if (fHasOpenCompoundChange) { | |
134 | 1226 ITextViewerExtension extension= cast(ITextViewerExtension) fCurrentTarget.getViewer(); |
129 | 1227 IRewriteTarget target= extension.getRewriteTarget(); |
1228 target.endCompoundChange(); | |
1229 fHasOpenCompoundChange= false; | |
1230 } | |
1231 } | |
1232 | |
1233 private void beginCompoundChange() { | |
1234 if (!fHasOpenCompoundChange) { | |
134 | 1235 ITextViewerExtension extension= cast(ITextViewerExtension) fCurrentTarget.getViewer(); |
129 | 1236 IRewriteTarget target= extension.getRewriteTarget(); |
1237 target.beginCompoundChange(); | |
1238 fHasOpenCompoundChange= true; | |
1239 } | |
1240 } | |
1241 | |
1242 /** | |
1243 * Returns the currently selected region or <code>null</code>. | |
1244 * | |
1245 * @return the currently selected region or <code>null</code> | |
1246 */ | |
1247 public IRegion getSelectedRegion() { | |
1248 if (fFramePosition !is null) | |
1249 return new Region(fFramePosition.getOffset(), fFramePosition.getLength()); | |
1250 if (fExitPosition !is null) | |
1251 return new Region(fExitPosition.getOffset(), fExitPosition.getLength()); | |
1252 return null; | |
1253 } | |
1254 | |
1255 private String getCategory() { | |
1256 return toString(); | |
1257 } | |
1258 | |
1259 /** | |
1260 * Sets the context info property. If set to <code>true</code>, context | |
1261 * info will be invoked on the current target's viewer whenever a position | |
1262 * is switched. | |
1263 * | |
1264 * @param doContextInfo <code>true</code> if context information should be | |
1265 * displayed | |
1266 */ | |
1267 public void setDoContextInfo(bool doContextInfo) { | |
1268 fDoContextInfo= doContextInfo; | |
1269 } | |
1270 | |
1271 /** | |
1272 * Sets the focus callback which will get informed when the focus of the | |
1273 * linked mode UI changes. | |
1274 * <p> | |
1275 * If there is a listener installed already, it will be replaced. | |
1276 * </p> | |
1277 * | |
1278 * @param listener the new listener, never <code>null</code>. | |
1279 */ | |
1280 protected void setPositionListener(ILinkedModeUIFocusListener listener) { | |
1281 Assert.isNotNull(listener); | |
1282 fPositionListener= listener; | |
1283 } | |
1284 | |
1285 /** | |
1286 * Sets the "simple" mode of the receiver. A linked mode UI in simple mode | |
1287 * merely draws the exit position, but not the target, focus, and slave | |
1288 * positions. Default is <code>false</code>. This method must be called | |
1289 * before it is entered. | |
1290 * | |
1291 * @param simple <code>true</code> if the UI should be in simple mode. | |
1292 */ | |
1293 public void setSimpleMode(bool simple) { | |
1294 fSimple= simple; | |
1295 } | |
1296 | |
1297 /** | |
1298 * Enables the support for colored labels in the proposal popup. | |
1299 * <p>Completion proposals can implement {@link ICompletionProposalExtension6} | |
1300 * to provide colored proposal labels.</p> | |
1301 * | |
1302 * @param isEnabled if <code>true</code> the support for colored labels is enabled in the proposal popup | |
1303 * @since 3.4 | |
1304 */ | |
1305 public void enableColoredLabels(bool isEnabled) { | |
1306 fIsColoredLabelsSupportEnabled= isEnabled; | |
1307 } | |
1308 | |
1309 } |