KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > link > LinkedModeUI


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 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  *******************************************************************************/

11 package org.eclipse.jface.text.link;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.HashSet JavaDoc;
16 import java.util.Iterator JavaDoc;
17 import java.util.List JavaDoc;
18 import java.util.Set JavaDoc;
19
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.custom.StyledText;
22 import org.eclipse.swt.custom.VerifyKeyListener;
23 import org.eclipse.swt.events.ShellEvent;
24 import org.eclipse.swt.events.ShellListener;
25 import org.eclipse.swt.events.VerifyEvent;
26 import org.eclipse.swt.graphics.Point;
27 import org.eclipse.swt.widgets.Display;
28 import org.eclipse.swt.widgets.Shell;
29
30 import org.eclipse.core.runtime.Assert;
31
32 import org.eclipse.jface.internal.text.link.contentassist.ContentAssistant2;
33 import org.eclipse.jface.internal.text.link.contentassist.IProposalListener;
34 import org.eclipse.jface.viewers.IPostSelectionProvider;
35 import org.eclipse.jface.viewers.ISelection;
36 import org.eclipse.jface.viewers.ISelectionChangedListener;
37 import org.eclipse.jface.viewers.SelectionChangedEvent;
38
39 import org.eclipse.jface.text.BadLocationException;
40 import org.eclipse.jface.text.BadPartitioningException;
41 import org.eclipse.jface.text.BadPositionCategoryException;
42 import org.eclipse.jface.text.DefaultPositionUpdater;
43 import org.eclipse.jface.text.DocumentCommand;
44 import org.eclipse.jface.text.DocumentEvent;
45 import org.eclipse.jface.text.IAutoEditStrategy;
46 import org.eclipse.jface.text.IDocument;
47 import org.eclipse.jface.text.IDocumentExtension3;
48 import org.eclipse.jface.text.IDocumentListener;
49 import org.eclipse.jface.text.IEditingSupport;
50 import org.eclipse.jface.text.IEditingSupportRegistry;
51 import org.eclipse.jface.text.IPositionUpdater;
52 import org.eclipse.jface.text.IRegion;
53 import org.eclipse.jface.text.IRewriteTarget;
54 import org.eclipse.jface.text.ITextInputListener;
55 import org.eclipse.jface.text.ITextOperationTarget;
56 import org.eclipse.jface.text.ITextSelection;
57 import org.eclipse.jface.text.ITextViewer;
58 import org.eclipse.jface.text.ITextViewerExtension;
59 import org.eclipse.jface.text.ITextViewerExtension2;
60 import org.eclipse.jface.text.ITextViewerExtension5;
61 import org.eclipse.jface.text.Position;
62 import org.eclipse.jface.text.Region;
63 import org.eclipse.jface.text.contentassist.ICompletionProposal;
64 import org.eclipse.jface.text.source.IAnnotationModel;
65 import org.eclipse.jface.text.source.IAnnotationModelExtension;
66 import org.eclipse.jface.text.source.ISourceViewer;
67
68 /**
69  * The UI for linked mode. Detects events that influence behavior of the linked mode
70  * UI and acts upon them.
71  * <p>
72  * <code>LinkedModeUI</code> relies on all added
73  * <code>LinkedModeUITarget</code>s to provide implementations of
74  * <code>ITextViewer</code> that implement <code>ITextViewerExtension</code>,
75  * and the documents being edited to implement <code>IDocumentExtension3</code>.
76  * </p>
77  * <p>
78  * Clients may instantiate and extend this class.
79  * </p>
80  *
81  * @since 3.0
82  */

83 public class LinkedModeUI {
84
85     /* cycle constants */
86     /**
87      * Constant indicating that this UI should never cycle from the last
88      * position to the first and vice versa.
89      */

90     public static final Object JavaDoc CYCLE_NEVER= new Object JavaDoc();
91     /**
92      * Constant indicating that this UI should always cycle from the last
93      * position to the first and vice versa.
94      */

95     public static final Object JavaDoc CYCLE_ALWAYS= new Object JavaDoc();
96     /**
97      * Constant indicating that this UI should cycle from the last position to
98      * the first and vice versa if its model is not nested.
99      */

100     public static final Object JavaDoc CYCLE_WHEN_NO_PARENT= new Object JavaDoc();
101
102     /**
103      * Listener that gets notified when the linked mode UI switches its focus position.
104      * <p>
105      * Clients may implement this interface.
106      * </p>
107      */

108     public interface ILinkedModeUIFocusListener {
109         /**
110          * Called when the UI for the linked mode leaves a linked position.
111          *
112          * @param position the position being left
113          * @param target the target where <code>position</code> resides in
114          */

115         void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target);
116         /**
117          * Called when the UI for the linked mode gives focus to a linked position.
118          *
119          * @param position the position being entered
120          * @param target the target where <code>position</code> resides in
121          */

122         void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target);
123     }
124
125     /**
126      * Null object implementation of focus listener.
127      */

128     private static final class EmtpyFocusListener implements ILinkedModeUIFocusListener {
129
130         public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) {
131             // ignore
132
}
133
134         public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) {
135             // ignore
136
}
137     }
138
139     /**
140      * A link target consists of a viewer and gets notified if the linked mode UI on
141      * it is being shown.
142      * <p>
143      * Clients may extend this class.
144      * </p>
145      * @since 3.0
146      */

147     public static abstract class LinkedModeUITarget implements ILinkedModeUIFocusListener {
148         /**
149          * Returns the viewer represented by this target, never <code>null</code>.
150          *
151          * @return the viewer associated with this target.
152          */

153         public abstract ITextViewer getViewer();
154
155         /**
156          * The viewer's text widget is initialized when the UI first connects
157          * to the viewer and never changed thereafter. This is to keep the
158          * reference of the widget that we have registered our listeners with,
159          * as the viewer, when it gets disposed, does not remember it, resulting
160          * in a situation where we cannot uninstall the listeners and a memory leak.
161          */

162         StyledText fWidget;
163
164         /** The cached shell - same reason as fWidget. */
165         Shell fShell;
166
167         /** The registered listener, or <code>null</code>. */
168         KeyListener fKeyListener;
169
170         /** The cached custom annotation model. */
171         LinkedPositionAnnotations fAnnotationModel;
172     }
173
174     private static final class EmptyTarget extends LinkedModeUITarget {
175
176         private ITextViewer fTextViewer;
177
178         /**
179          * @param viewer the viewer
180          */

181         public EmptyTarget(ITextViewer viewer) {
182             Assert.isNotNull(viewer);
183             fTextViewer= viewer;
184         }
185
186         /*
187          * @see org.eclipse.jdt.internal.ui.text.link2.LinkedModeUI.ILinkedUITarget#getViewer()
188          */

189         public ITextViewer getViewer() {
190             return fTextViewer;
191         }
192
193         /**
194          * {@inheritDoc}
195          */

196         public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) {
197         }
198
199         /**
200          * {@inheritDoc}
201          */

202         public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) {
203         }
204
205     }
206
207     /**
208      * Listens for state changes in the model.
209      */

210     private final class ExitListener implements ILinkedModeListener {
211         public void left(LinkedModeModel model, int flags) {
212             leave(ILinkedModeListener.EXIT_ALL | flags);
213         }
214
215         public void suspend(LinkedModeModel model) {
216             disconnect();
217             redraw();
218         }
219
220         public void resume(LinkedModeModel model, int flags) {
221             if ((flags & ILinkedModeListener.EXIT_ALL) != 0) {
222                 leave(flags);
223             } else {
224                 connect();
225                 if ((flags & ILinkedModeListener.SELECT) != 0)
226                     select();
227                 ensureAnnotationModelInstalled();
228                 redraw();
229             }
230         }
231     }
232
233     /**
234      * Exit flags returned if a custom exit policy wants to exit linked mode.
235      * <p>
236      * Clients may instantiate this class.
237      * </p>
238      */

239     public static class ExitFlags {
240         /** The flags to return in the <code>leave</code> method. */
241         public int flags;
242         /** The doit flag of the checked <code>VerifyKeyEvent</code>. */
243         public boolean doit;
244         /**
245          * Creates a new instance.
246          *
247          * @param flags the exit flags
248          * @param doit the doit flag for the verify event
249          */

250         public ExitFlags(int flags, boolean doit) {
251             this.flags= flags;
252             this.doit= doit;
253         }
254     }
255
256     /**
257      * An exit policy can be registered by a caller to get custom exit
258      * behavior.
259      * <p>
260      * Clients may implement this interface.
261      * </p>
262      */

263     public interface IExitPolicy {
264         /**
265          * Checks whether the linked mode should be left after receiving the
266          * given <code>VerifyEvent</code> and selection. Note that the event
267          * carries widget coordinates as opposed to <code>offset</code> and
268          * <code>length</code> which are document coordinates.
269          *
270          * @param model the linked mode model
271          * @param event the verify event
272          * @param offset the offset of the current selection
273          * @param length the length of the current selection
274          * @return valid exit flags or <code>null</code> if no special action
275          * should be taken
276          */

277         ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length);
278     }
279
280     /**
281      * A NullObject implementation of <code>IExitPolicy</code>.
282      */

283     private static class NullExitPolicy implements IExitPolicy {
284         /*
285          * @see org.eclipse.jdt.internal.ui.text.link2.LinkedModeUI.IExitPolicy#doExit(org.eclipse.swt.events.VerifyEvent, int, int)
286          */

287         public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) {
288             return null;
289         }
290     }
291
292     /**
293      * Listens for shell events and acts upon them.
294      */

295     private class Closer implements ShellListener, ITextInputListener {
296
297         public void shellActivated(ShellEvent e) {
298         }
299
300         public void shellClosed(ShellEvent e) {
301             leave(ILinkedModeListener.EXIT_ALL);
302         }
303
304         public void shellDeactivated(ShellEvent e) {
305 // TODO re-enable after debugging
306
// if (true) return;
307

308             // from LinkedPositionUI:
309

310             // don't deactivate on focus lost, since the proposal popups may take focus
311
// plus: it doesn't hurt if you can check with another window without losing linked mode
312
// since there is no intrusive popup sticking out.
313

314             // need to check first what happens on reentering based on an open action
315
// Seems to be no problem
316

317             // Better:
318
// Check with content assistant and only leave if its not the proposal shell that took the
319
// focus away.
320

321             StyledText text;
322             final ITextViewer viewer;
323             Display display;
324
325             if (fCurrentTarget == null || (text= fCurrentTarget.fWidget) == null
326                     || text.isDisposed() || (display= text.getDisplay()) == null
327                     || display.isDisposed()
328                     || (viewer= fCurrentTarget.getViewer()) == null)
329             {
330                 leave(ILinkedModeListener.EXIT_ALL);
331             }
332             else
333             {
334                 // Post in UI thread since the assistant popup will only get the focus after we lose it.
335
display.asyncExec(new Runnable JavaDoc() {
336                     public void run() {
337                         if (fIsActive && viewer instanceof IEditingSupportRegistry) {
338                             IEditingSupport[] helpers= ((IEditingSupportRegistry) viewer).getRegisteredSupports();
339                             for (int i= 0; i < helpers.length; i++) {
340                                 if (helpers[i].ownsFocusShell())
341                                     return;
342                             }
343                         }
344
345                         // else
346
leave(ILinkedModeListener.EXIT_ALL);
347
348                     }
349                 });
350             }
351         }
352
353         public void shellDeiconified(ShellEvent e) {
354         }
355
356         public void shellIconified(ShellEvent e) {
357             leave(ILinkedModeListener.EXIT_ALL);
358         }
359
360         /*
361          * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
362          */

363         public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
364             leave(ILinkedModeListener.EXIT_ALL);
365         }
366
367         /*
368          * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
369          */

370         public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
371         }
372
373     }
374
375     /**
376      * @since 3.1
377      */

378     private class DocumentListener implements IDocumentListener {
379         /*
380          * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
381          */

382         public void documentAboutToBeChanged(DocumentEvent event) {
383
384             // default behavior: any document change outside a linked position
385
// causes us to exit
386
int end= event.getOffset() + event.getLength();
387             for (int offset= event.getOffset(); offset <= end; offset++) {
388                 if (!fModel.anyPositionContains(offset)) {
389                     ITextViewer viewer= fCurrentTarget.getViewer();
390                     if (fFramePosition != null && viewer instanceof IEditingSupportRegistry) {
391                         IEditingSupport[] helpers= ((IEditingSupportRegistry) viewer).getRegisteredSupports();
392                         for (int i= 0; i < helpers.length; i++) {
393                             if (helpers[i].isOriginator(null, new Region(fFramePosition.getOffset(), fFramePosition.getLength())))
394                                 return;
395                         }
396                     }
397
398                     leave(ILinkedModeListener.EXTERNAL_MODIFICATION);
399                     return;
400                 }
401             }
402         }
403
404         /*
405          * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
406          */

407         public void documentChanged(DocumentEvent event) {
408         }
409     }
410
411     /**
412      * Listens for key events, checks the exit policy for custom exit
413      * strategies but defaults to handling Tab, Enter, and Escape.
414      */

415     private class KeyListener implements VerifyKeyListener {
416
417         private boolean fIsEnabled= true;
418
419         public void verifyKey(VerifyEvent event) {
420
421             if (!event.doit || !fIsEnabled)
422                 return;
423
424             Point selection= fCurrentTarget.getViewer().getSelectedRange();
425             int offset= selection.x;
426             int length= selection.y;
427
428             // if the custom exit policy returns anything, use that
429
ExitFlags exitFlags= fExitPolicy.doExit(fModel, event, offset, length);
430             if (exitFlags != null) {
431                 leave(exitFlags.flags);
432                 event.doit= exitFlags.doit;
433                 return;
434             }
435
436             // standard behavior:
437
// (Shift+)Tab: jumps from position to position, depending on cycle mode
438
// Enter: accepts all entries and leaves all (possibly stacked) environments, the last sets the caret
439
// Esc: accepts all entries and leaves all (possibly stacked) environments, the caret stays
440
// ? what do we do to leave one level of a cycling model that is stacked?
441
// -> This is only the case if the level was set up with forced cycling (CYCLE_ALWAYS), in which case
442
// the caller is sure that one does not need by-level exiting.
443
switch (event.character) {
444                 // [SHIFT-]TAB = hop between edit boxes
445
case 0x09:
446                     if (!(fExitPosition != null && fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) {
447                         // outside any edit box -> leave (all? TODO should only leave the affected, level and forward to the next upper)
448
leave(ILinkedModeListener.EXIT_ALL);
449                         break;
450                     }
451
452                     if (event.stateMask == SWT.SHIFT)
453                         previous();
454                     else
455                         next();
456
457                     event.doit= false;
458                     break;
459
460                 // ENTER
461
case 0x0A:
462                 // Ctrl+Enter on WinXP
463
case 0x0D:
464 // if ((fExitPosition != null && fExitPosition.includes(offset)) || !fModel.anyPositionContains(offset)) {
465
if (!fModel.anyPositionContains(offset)) {
466 // if ((fExitPosition == null || !fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) {
467
// outside any edit box or on exit position -> leave (all? TODO should only leave the affected, level and forward to the next upper)
468
leave(ILinkedModeListener.EXIT_ALL);
469                         break;
470                     }
471
472                     // normal case: exit entire stack and put caret to final position
473
leave(ILinkedModeListener.EXIT_ALL | ILinkedModeListener.UPDATE_CARET);
474                     event.doit= false;
475                     break;
476
477                 // ESC
478
case 0x1B:
479                     // exit entire stack and leave caret
480
leave(ILinkedModeListener.EXIT_ALL);
481                     event.doit= false;
482                     break;
483
484                 default:
485                     if (event.character != 0) {
486                         if (!controlUndoBehavior(offset, length)) {
487                             leave(ILinkedModeListener.EXIT_ALL);
488                             break;
489                         }
490                     }
491             }
492         }
493
494         private boolean controlUndoBehavior(int offset, int length) {
495             LinkedPosition position= fModel.findPosition(new LinkedPosition(fCurrentTarget.getViewer().getDocument(), offset, length, LinkedPositionGroup.NO_STOP));
496             if (position != null) {
497
498                 // if the last position is not the same and there is an open change: close it.
499
if (!position.equals(fPreviousPosition))
500                     endCompoundChange();
501
502                 beginCompoundChange();
503             }
504
505             fPreviousPosition= position;
506             return fPreviousPosition != null;
507         }
508
509         /**
510          * @param enabled the new enabled state
511          */

512         public void setEnabled(boolean enabled) {
513             fIsEnabled= enabled;
514         }
515
516     }
517
518     /**
519      * Installed as post selection listener on the watched viewer. Updates the
520      * linked position after cursor movement, even to positions not in the
521      * iteration list.
522      */

523     private class MySelectionListener implements ISelectionChangedListener {
524
525         /*
526          * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
527          */

528         public void selectionChanged(SelectionChangedEvent event) {
529             ISelection selection= event.getSelection();
530             if (selection instanceof ITextSelection) {
531                 ITextSelection textsel= (ITextSelection) selection;
532                 if (event.getSelectionProvider() instanceof ITextViewer) {
533                     IDocument doc= ((ITextViewer) event.getSelectionProvider()).getDocument();
534                     if (doc != null) {
535                         int offset= textsel.getOffset();
536                         int length= textsel.getLength();
537                         if (offset >= 0 && length >= 0) {
538                             LinkedPosition find= new LinkedPosition(doc, offset, length, LinkedPositionGroup.NO_STOP);
539                             LinkedPosition pos= fModel.findPosition(find);
540                             if (pos == null && fExitPosition != null && fExitPosition.includes(find))
541                                 pos= fExitPosition;
542
543                             if (pos != null)
544                                 switchPosition(pos, false, false);
545                         }
546                     }
547                 }
548             }
549         }
550
551     }
552
553     private class ProposalListener implements IProposalListener {
554
555         /*
556          * @see org.eclipse.jface.internal.text.link.contentassist.IProposalListener#proposalChosen(org.eclipse.jface.text.contentassist.ICompletionProposal)
557          */

558         public void proposalChosen(ICompletionProposal proposal) {
559             next();
560         }
561     }
562
563     /** The current viewer. */
564     private LinkedModeUITarget fCurrentTarget;
565     /**
566      * The manager of the linked positions we provide a UI for.
567      * @since 3.1
568      */

569     private LinkedModeModel fModel;
570     /** The set of viewers we manage. */
571     private LinkedModeUITarget[] fTargets;
572     /** The iterator over the tab stop positions. */
573     private TabStopIterator fIterator;
574
575     /* Our team of event listeners */
576     /** The shell listener. */
577     private Closer fCloser= new Closer();
578     /** The linked mode listener. */
579     private ILinkedModeListener fLinkedListener= new ExitListener();
580     /** The selection listener. */
581     private MySelectionListener fSelectionListener= new MySelectionListener();
582     /** The content assist listener. */
583     private ProposalListener fProposalListener= new ProposalListener();
584     /**
585      * The document listener.
586      * @since 3.1
587      */

588     private IDocumentListener fDocumentListener= new DocumentListener();
589
590     /** The last caret position, used by fCaretListener. */
591     private final Position fCaretPosition= new Position(0, 0);
592     /** The exit policy to control custom exit behavior */
593     private IExitPolicy fExitPolicy= new NullExitPolicy();
594     /** The current frame position shown in the UI, or <code>null</code>. */
595     private LinkedPosition fFramePosition;
596     /** The last visited position, used for undo / redo. */
597     private LinkedPosition fPreviousPosition;
598     /** The content assistant used to show proposals. */
599     private ContentAssistant2 fAssistant;
600     /** The exit position. */
601     private LinkedPosition fExitPosition;
602     /** State indicator to prevent multiple invocation of leave. */
603     private boolean fIsActive= false;
604     /** The position updater for the exit position. */
605     private IPositionUpdater fPositionUpdater= new DefaultPositionUpdater(getCategory());
606     /** Whether to show context info. */
607     private boolean fDoContextInfo= false;
608     /** Whether we have begun a compound change, but not yet closed. */
609     private boolean fHasOpenCompoundChange= false;
610     /** The position listener. */
611     private ILinkedModeUIFocusListener fPositionListener= new EmtpyFocusListener();
612     private IAutoEditStrategy fAutoEditVetoer= new IAutoEditStrategy() {
613
614         /*
615          * @see org.eclipse.jface.text.IAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
616          */

617         public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
618             // invalidate the change to ensure that the change is performed on the document only.
619
if (fModel.anyPositionContains(command.offset)) {
620                 command.doit= false;
621                 command.caretOffset= command.offset + command.length;
622             }
623
624         }
625     };
626     /** Whether this UI is in simple highlighting mode or not. */
627     private boolean fSimple;
628
629     /**
630      * Creates a new UI on the given model and the set of viewers. The model
631      * must provide a tab stop sequence with a non-empty list of tab stops.
632      *
633      * @param model the linked mode model
634      * @param targets the non-empty list of targets upon which the linked mode
635      * UI should act
636      */

637     public LinkedModeUI(LinkedModeModel model, LinkedModeUITarget[] targets) {
638         constructor(model, targets);
639     }
640
641     /**
642      * Convenience constructor for just one viewer.
643      *
644      * @param model the linked mode model
645      * @param viewer the viewer upon which the linked mode UI should act
646      */

647     public LinkedModeUI(LinkedModeModel model, ITextViewer viewer) {
648         constructor(model, new LinkedModeUITarget[]{new EmptyTarget(viewer)});
649     }
650
651     /**
652      * Convenience constructor for multiple viewers.
653      *
654      * @param model the linked mode model
655      * @param viewers the non-empty list of viewers upon which the linked mode
656      * UI should act
657      */

658     public LinkedModeUI(LinkedModeModel model, ITextViewer[] viewers) {
659         LinkedModeUITarget[] array= new LinkedModeUITarget[viewers.length];
660         for (int i= 0; i < array.length; i++) {
661             array[i]= new EmptyTarget(viewers[i]);
662         }
663         constructor(model, array);
664     }
665
666     /**
667      * Convenience constructor for one target.
668      *
669      * @param model the linked mode model
670      * @param target the target upon which the linked mode UI should act
671      */

672     public LinkedModeUI(LinkedModeModel model, LinkedModeUITarget target) {
673         constructor(model, new LinkedModeUITarget[]{target});
674     }
675
676     /**
677      * This does the actual constructor work.
678      *
679      * @param model the linked mode model
680      * @param targets the non-empty array of targets upon which the linked mode UI
681      * should act
682      */

683     private void constructor(LinkedModeModel model, LinkedModeUITarget[] targets) {
684         Assert.isNotNull(model);
685         Assert.isNotNull(targets);
686         Assert.isTrue(targets.length > 0);
687         Assert.isTrue(model.getTabStopSequence().size() > 0);
688
689         fModel= model;
690         fTargets= targets;
691         fCurrentTarget= targets[0];
692         fIterator= new TabStopIterator(fModel.getTabStopSequence());
693         fIterator.setCycling(!fModel.isNested());
694         fModel.addLinkingListener(fLinkedListener);
695
696         fAssistant= new ContentAssistant2();
697         fAssistant.addProposalListener(fProposalListener);
698         // TODO find a way to set up content assistant.
699
// fAssistant.setDocumentPartitioning(IJavaPartitions.JAVA_PARTITIONING);
700

701         fCaretPosition.delete();
702     }
703
704     /**
705      * Starts this UI on the first position.
706      */

707     public void enter() {
708         fIsActive= true;
709         connect();
710         next();
711     }
712
713     /**
714      * Sets an <code>IExitPolicy</code> to customize the exit behavior of
715      * this linked mode UI.
716      *
717      * @param policy the exit policy to use.
718      */

719     public void setExitPolicy(IExitPolicy policy) {
720         fExitPolicy= policy;
721     }
722
723     /**
724      * Sets the exit position to move the caret to when linked mode mode is
725      * exited.
726      *
727      * @param target the target where the exit position is located
728      * @param offset the offset of the exit position
729      * @param length the length of the exit position (in case there should be a
730      * selection)
731      * @param sequence set to the tab stop position of the exit position, or
732      * <code>LinkedPositionGroup.NO_STOP</code> if there should be no
733      * tab stop.
734      * @throws BadLocationException if the position is not valid in the viewer's
735      * document
736      */

737     public void setExitPosition(LinkedModeUITarget target, int offset, int length, int sequence) throws BadLocationException {
738         // remove any existing exit position
739
if (fExitPosition != null) {
740             fExitPosition.getDocument().removePosition(fExitPosition);
741             fIterator.removePosition(fExitPosition);
742             fExitPosition= null;
743         }
744
745         IDocument doc= target.getViewer().getDocument();
746         if (doc == null)
747             return;
748
749         fExitPosition= new LinkedPosition(doc, offset, length, sequence);
750         doc.addPosition(fExitPosition); // gets removed in leave()
751
if (sequence != LinkedPositionGroup.NO_STOP)
752             fIterator.addPosition(fExitPosition);
753
754     }
755
756     /**
757      * Sets the exit position to move the caret to when linked mode is exited.
758      *
759      * @param viewer the viewer where the exit position is located
760      * @param offset the offset of the exit position
761      * @param length the length of the exit position (in case there should be a
762      * selection)
763      * @param sequence set to the tab stop position of the exit position, or
764      * <code>LinkedPositionGroup.NO_STOP</code> if there should be no tab stop.
765      * @throws BadLocationException if the position is not valid in the
766      * viewer's document
767      */

768     public void setExitPosition(ITextViewer viewer, int offset, int length, int sequence) throws BadLocationException {
769         setExitPosition(new EmptyTarget(viewer), offset, length, sequence);
770     }
771
772     /**
773      * Sets the cycling mode to either of <code>CYCLING_ALWAYS</code>,
774      * <code>CYCLING_NEVER</code>, or <code>CYCLING_WHEN_NO_PARENT</code>,
775      * which is the default.
776      *
777      * @param mode the new cycling mode.
778      */

779     public void setCyclingMode(Object JavaDoc mode) {
780         if (mode != CYCLE_ALWAYS && mode != CYCLE_NEVER && mode != CYCLE_WHEN_NO_PARENT)
781             throw new IllegalArgumentException JavaDoc();
782
783         if (mode == CYCLE_ALWAYS || mode == CYCLE_WHEN_NO_PARENT && !fModel.isNested())
784             fIterator.setCycling(true);
785         else
786             fIterator.setCycling(false);
787     }
788
789     void next() {
790         if (fIterator.hasNext(fFramePosition)) {
791             switchPosition(fIterator.next(fFramePosition), true, true);
792             return;
793         }
794         leave(ILinkedModeListener.UPDATE_CARET);
795     }
796
797     void previous() {
798         if (fIterator.hasPrevious(fFramePosition)) {
799             switchPosition(fIterator.previous(fFramePosition), true, true);
800         } else
801             // dont't update caret, but rather select the current frame
802
leave(ILinkedModeListener.SELECT);
803     }
804
805     private void triggerContextInfo() {
806         ITextOperationTarget target= fCurrentTarget.getViewer().getTextOperationTarget();
807         if (target != null) {
808             if (target.canDoOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION))
809                 target.doOperation(ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION);
810         }
811     }
812
813     /** Trigger content assist on choice positions */
814     private void triggerContentAssist() {
815         if (fFramePosition instanceof ProposalPosition) {
816             ProposalPosition pp= (ProposalPosition) fFramePosition;
817             ICompletionProposal[] choices= pp.getChoices();
818             if (choices != null && choices.length > 0) {
819                 fAssistant.setCompletions(choices);
820                 fAssistant.showPossibleCompletions();
821                 return;
822             }
823         }
824
825         fAssistant.setCompletions(new ICompletionProposal[0]);
826         fAssistant.hidePossibleCompletions();
827     }
828
829     private void switchPosition(LinkedPosition pos, boolean select, boolean showProposals) {
830         Assert.isNotNull(pos);
831         if (pos.equals(fFramePosition))
832             return;
833
834         if (fFramePosition != null && fCurrentTarget != null)
835             fPositionListener.linkingFocusLost(fFramePosition, fCurrentTarget);
836
837         // undo
838
endCompoundChange();
839
840         redraw(); // redraw current position being left - usually not needed
841
IDocument oldDoc= fFramePosition == null ? null : fFramePosition.getDocument();
842         IDocument newDoc= pos.getDocument();
843
844         switchViewer(oldDoc, newDoc, pos);
845         fFramePosition= pos;
846
847         if (select)
848             select();
849         if (fFramePosition == fExitPosition && !fIterator.isCycling())
850             leave(ILinkedModeListener.NONE);
851         else {
852             redraw(); // redraw new position
853
ensureAnnotationModelInstalled();
854         }
855         if (showProposals)
856             triggerContentAssist();
857         if (fFramePosition != fExitPosition && fDoContextInfo)
858             triggerContextInfo();
859
860         if (fFramePosition != null && fCurrentTarget != null)
861             fPositionListener.linkingFocusGained(fFramePosition, fCurrentTarget);
862
863     }
864
865     private void ensureAnnotationModelInstalled() {
866         LinkedPositionAnnotations lpa= fCurrentTarget.fAnnotationModel;
867         if (lpa != null) {
868             ITextViewer viewer= fCurrentTarget.getViewer();
869             if (viewer instanceof ISourceViewer) {
870                 ISourceViewer sv= (ISourceViewer) viewer;
871                 IAnnotationModel model= sv.getAnnotationModel();
872                 if (model instanceof IAnnotationModelExtension) {
873                     IAnnotationModelExtension ext= (IAnnotationModelExtension) model;
874                     IAnnotationModel ourModel= ext.getAnnotationModel(getUniqueKey());
875                     if (ourModel == null) {
876                         ext.addAnnotationModel(getUniqueKey(), lpa);
877                     }
878                 }
879             }
880         }
881     }
882
883     private void uninstallAnnotationModel(LinkedModeUITarget target) {
884         ITextViewer viewer= target.getViewer();
885         if (viewer instanceof ISourceViewer) {
886             ISourceViewer sv= (ISourceViewer) viewer;
887             IAnnotationModel model= sv.getAnnotationModel();
888             if (model instanceof IAnnotationModelExtension) {
889                 IAnnotationModelExtension ext= (IAnnotationModelExtension) model;
890                 ext.removeAnnotationModel(getUniqueKey());
891             }
892         }
893     }
894
895     private void switchViewer(IDocument oldDoc, IDocument newDoc, LinkedPosition pos) {
896         if (oldDoc != newDoc) {
897
898             // redraw current document with new position before switching viewer
899
if (fCurrentTarget.fAnnotationModel != null)
900                 fCurrentTarget.fAnnotationModel.switchToPosition(fModel, pos);
901
902             LinkedModeUITarget target= null;
903             for (int i= 0; i < fTargets.length; i++) {
904                 if (fTargets[i].getViewer().getDocument() == newDoc) {
905                     target= fTargets[i];
906                     break;
907                 }
908             }
909             if (target != fCurrentTarget) {
910                 disconnect();
911                 fCurrentTarget= target;
912                 target.linkingFocusLost(fFramePosition, target);
913                 connect();
914                 ensureAnnotationModelInstalled();
915                 if (fCurrentTarget != null)
916                     fCurrentTarget.linkingFocusGained(pos, fCurrentTarget);
917             }
918         }
919     }
920
921     private void select() {
922         ITextViewer viewer= fCurrentTarget.getViewer();
923         if (viewer instanceof ITextViewerExtension5) {
924             ITextViewerExtension5 extension5= (ITextViewerExtension5) viewer;
925             extension5.exposeModelRange(new Region(fFramePosition.offset, fFramePosition.length));
926         } else if (!viewer.overlapsWithVisibleRegion(fFramePosition.offset, fFramePosition.length)) {
927             viewer.resetVisibleRegion();
928         }
929         viewer.revealRange(fFramePosition.offset, fFramePosition.length);
930         viewer.setSelectedRange(fFramePosition.offset, fFramePosition.length);
931     }
932
933     private void redraw() {
934         if (fCurrentTarget.fAnnotationModel != null)
935             fCurrentTarget.fAnnotationModel.switchToPosition(fModel, fFramePosition);
936     }
937
938     private void connect() {
939         Assert.isNotNull(fCurrentTarget);
940         ITextViewer viewer= fCurrentTarget.getViewer();
941         Assert.isNotNull(viewer);
942         fCurrentTarget.fWidget= viewer.getTextWidget();
943         if (fCurrentTarget.fWidget == null)
944             leave(ILinkedModeListener.EXIT_ALL);
945
946         if (fCurrentTarget.fKeyListener == null) {
947             fCurrentTarget.fKeyListener= new KeyListener();
948             ((ITextViewerExtension) viewer).prependVerifyKeyListener(fCurrentTarget.fKeyListener);
949         } else
950             fCurrentTarget.fKeyListener.setEnabled(true);
951
952         registerAutoEditVetoer(viewer);
953
954         ((IPostSelectionProvider) viewer).addPostSelectionChangedListener(fSelectionListener);
955
956         createAnnotationModel();
957
958         showSelection();
959
960         fCurrentTarget.fShell= fCurrentTarget.fWidget.getShell();
961         if (fCurrentTarget.fShell == null)
962             leave(ILinkedModeListener.EXIT_ALL);
963         fCurrentTarget.fShell.addShellListener(fCloser);
964
965         fAssistant.install(viewer);
966
967         viewer.addTextInputListener(fCloser);
968
969         viewer.getDocument().addDocumentListener(fDocumentListener);
970     }
971
972     /**
973      * Reveals the selection on the current target's widget, if it is valid.
974      */

975     private void showSelection() {
976         final StyledText widget= fCurrentTarget.fWidget;
977         if (widget == null || widget.isDisposed())
978             return;
979         
980         // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132263
981
widget.getDisplay().asyncExec(new Runnable JavaDoc() {
982             public void run() {
983                 if (!widget.isDisposed())
984                     try {
985                     widget.showSelection();
986                     } catch (IllegalArgumentException JavaDoc e) {
987                         /*
988                          * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=66914
989                          * if the StyledText is in setRedraw(false) mode, its
990                          * selection may not be up2date and calling showSelection
991                          * will throw an IAE.
992                          * We don't have means to find out whether the selection is valid
993                          * or whether the widget is redrawing or not therefore we try
994                          * and ignore an IAE.
995                          */

996                     }
997             }
998         });
999     }
1000
1001    /**
1002     * Registers our auto edit vetoer with the viewer.
1003     *
1004     * @param viewer the viewer we want to veto ui-triggered changes within
1005     * linked positions
1006     */

1007    private void registerAutoEditVetoer(ITextViewer viewer) {
1008        try {
1009            String JavaDoc[] contentTypes= getContentTypes(viewer.getDocument());
1010            if (viewer instanceof ITextViewerExtension2) {
1011                ITextViewerExtension2 vExtension= ((ITextViewerExtension2) viewer);
1012                for (int i= 0; i < contentTypes.length; i++) {
1013                    vExtension.prependAutoEditStrategy(fAutoEditVetoer, contentTypes[i]);
1014                }
1015            } else {
1016                Assert.isTrue(false);
1017            }
1018
1019        } catch (BadPartitioningException e) {
1020            leave(ILinkedModeListener.EXIT_ALL);
1021        }
1022    }
1023
1024    private void unregisterAutoEditVetoer(ITextViewer viewer) {
1025        try {
1026            String JavaDoc[] contentTypes= getContentTypes(viewer.getDocument());
1027            if (viewer instanceof ITextViewerExtension2) {
1028                ITextViewerExtension2 vExtension= ((ITextViewerExtension2) viewer);
1029                for (int i= 0; i < contentTypes.length; i++) {
1030                    vExtension.removeAutoEditStrategy(fAutoEditVetoer, contentTypes[i]);
1031                }
1032            } else {
1033                Assert.isTrue(false);
1034            }
1035        } catch (BadPartitioningException e) {
1036            leave(ILinkedModeListener.EXIT_ALL);
1037        }
1038    }
1039
1040    /**
1041     * Returns all possible content types of <code>document</code>.
1042     *
1043     * @param document the document
1044     * @return all possible content types of <code>document</code>
1045     * @throws BadPartitioningException
1046     * @since 3.1
1047     */

1048    private String JavaDoc[] getContentTypes(IDocument document) throws BadPartitioningException {
1049        if (document instanceof IDocumentExtension3) {
1050            IDocumentExtension3 ext= (IDocumentExtension3) document;
1051            String JavaDoc[] partitionings= ext.getPartitionings();
1052            Set JavaDoc contentTypes= new HashSet JavaDoc(20);
1053            for (int i= 0; i < partitionings.length; i++) {
1054                contentTypes.addAll(Arrays.asList(ext.getLegalContentTypes(partitionings[i])));
1055            }
1056            contentTypes.add(IDocument.DEFAULT_CONTENT_TYPE);
1057            return (String JavaDoc[]) contentTypes.toArray(new String JavaDoc[contentTypes.size()]);
1058        }
1059        return document.getLegalContentTypes();
1060    }
1061    
1062    private void createAnnotationModel() {
1063        if (fCurrentTarget.fAnnotationModel == null) {
1064            LinkedPositionAnnotations lpa= new LinkedPositionAnnotations();
1065            if (fSimple) {
1066                lpa.markExitTarget(true);
1067                lpa.markFocus(false);
1068                lpa.markSlaves(false);
1069                lpa.markTargets(false);
1070            }
1071            lpa.setTargets(fIterator.getPositions());
1072            lpa.setExitTarget(fExitPosition);
1073            lpa.connect(fCurrentTarget.getViewer().getDocument());
1074            fCurrentTarget.fAnnotationModel= lpa;
1075        }
1076    }
1077
1078    private String JavaDoc getUniqueKey() {
1079        return "linked.annotationmodelkey."+toString(); //$NON-NLS-1$
1080
}
1081
1082    private void disconnect() {
1083        Assert.isNotNull(fCurrentTarget);
1084        ITextViewer viewer= fCurrentTarget.getViewer();
1085        Assert.isNotNull(viewer);
1086
1087        viewer.getDocument().removeDocumentListener(fDocumentListener);
1088
1089        fAssistant.uninstall();
1090        fAssistant.removeProposalListener(fProposalListener);
1091
1092        fCurrentTarget.fWidget= null;
1093
1094        Shell shell= fCurrentTarget.fShell;
1095        fCurrentTarget.fShell= null;
1096
1097        if (shell != null && !shell.isDisposed())
1098            shell.removeShellListener(fCloser);
1099
1100        // this one is asymmetric: we don't install the model in
1101
// connect, but leave it to its callers to ensure they
1102
// have the model installed if they need it
1103
uninstallAnnotationModel(fCurrentTarget);
1104
1105        unregisterAutoEditVetoer(viewer);
1106
1107        // don't remove the verify key listener to let it keep its position
1108
// in the listener queue
1109
if (fCurrentTarget.fKeyListener != null)
1110            fCurrentTarget.fKeyListener.setEnabled(false);
1111
1112        ((IPostSelectionProvider) viewer).removePostSelectionChangedListener(fSelectionListener);
1113
1114        redraw();
1115    }
1116
1117    void leave(final int flags) {
1118        if (!fIsActive)
1119            return;
1120        fIsActive= false;
1121
1122        endCompoundChange();
1123
1124        Display display= null;
1125        if (fCurrentTarget.fWidget != null && !fCurrentTarget.fWidget.isDisposed())
1126            display= fCurrentTarget.fWidget.getDisplay();
1127
1128        if (fCurrentTarget.fAnnotationModel != null)
1129            fCurrentTarget.fAnnotationModel.removeAllAnnotations();
1130        disconnect();
1131
1132        for (int i= 0; i < fTargets.length; i++) {
1133            LinkedModeUITarget target= fTargets[i];
1134            ITextViewer viewer= target.getViewer();
1135            if (target.fKeyListener != null) {
1136                ((ITextViewerExtension) viewer).removeVerifyKeyListener(target.fKeyListener);
1137                target.fKeyListener= null;
1138            }
1139
1140            viewer.removeTextInputListener(fCloser);
1141        }
1142
1143        for (int i= 0; i < fTargets.length; i++) {
1144
1145            if (fTargets[i].fAnnotationModel != null) {
1146                fTargets[i].fAnnotationModel.removeAllAnnotations();
1147                fTargets[i].fAnnotationModel.disconnect(fTargets[i].getViewer().getDocument());
1148                fTargets[i].fAnnotationModel= null;
1149            }
1150
1151            uninstallAnnotationModel(fTargets[i]);
1152        }
1153
1154
1155        if ((flags & ILinkedModeListener.UPDATE_CARET) != 0 && fExitPosition != null && fFramePosition != fExitPosition && !fExitPosition.isDeleted())
1156            switchPosition(fExitPosition, true, false);
1157
1158        final List JavaDoc docs= new ArrayList JavaDoc();
1159        for (int i= 0; i < fTargets.length; i++) {
1160            IDocument doc= fTargets[i].getViewer().getDocument();
1161            if (doc != null)
1162                docs.add(doc);
1163        }
1164
1165        fModel.stopForwarding(flags);
1166
1167        Runnable JavaDoc runnable= new Runnable JavaDoc() {
1168            public void run() {
1169                if (fExitPosition != null)
1170                    fExitPosition.getDocument().removePosition(fExitPosition);
1171
1172                for (Iterator JavaDoc iter = docs.iterator(); iter.hasNext(); ) {
1173                    IDocument doc= (IDocument) iter.next();
1174                    doc.removePositionUpdater(fPositionUpdater);
1175                    boolean uninstallCat= false;
1176                    String JavaDoc[] cats= doc.getPositionCategories();
1177                    for (int j= 0; j < cats.length; j++) {
1178                        if (getCategory().equals(cats[j])) {
1179                            uninstallCat= true;
1180                            break;
1181                        }
1182                    }
1183                    if (uninstallCat)
1184                        try {
1185                            doc.removePositionCategory(getCategory());
1186                        } catch (BadPositionCategoryException e) {
1187                            // ignore
1188
}
1189                }
1190                fModel.exit(flags);
1191            }
1192        };
1193
1194        // remove positions (both exit positions AND linked positions in the
1195
// model) asynchronously to make sure that the annotation painter
1196
// gets correct document offsets.
1197
if (display != null)
1198            display.asyncExec(runnable);
1199        else
1200            runnable.run();
1201    }
1202
1203    private void endCompoundChange() {
1204        if (fHasOpenCompoundChange) {
1205            ITextViewerExtension extension= (ITextViewerExtension) fCurrentTarget.getViewer();
1206            IRewriteTarget target= extension.getRewriteTarget();
1207            target.endCompoundChange();
1208            fHasOpenCompoundChange= false;
1209        }
1210    }
1211
1212    private void beginCompoundChange() {
1213        if (!fHasOpenCompoundChange) {
1214            ITextViewerExtension extension= (ITextViewerExtension) fCurrentTarget.getViewer();
1215            IRewriteTarget target= extension.getRewriteTarget();
1216            target.beginCompoundChange();
1217            fHasOpenCompoundChange= true;
1218        }
1219    }
1220
1221    /**
1222     * Returns the currently selected region or <code>null</code>.
1223     *
1224     * @return the currently selected region or <code>null</code>
1225     */

1226    public IRegion getSelectedRegion() {
1227        if (fFramePosition != null)
1228            return new Region(fFramePosition.getOffset(), fFramePosition.getLength());
1229        if (fExitPosition != null)
1230            return new Region(fExitPosition.getOffset(), fExitPosition.getLength());
1231        return null;
1232    }
1233
1234    private String JavaDoc getCategory() {
1235        return toString();
1236    }
1237
1238    /**
1239     * Sets the context info property. If set to <code>true</code>, context
1240     * info will be invoked on the current target's viewer whenever a position
1241     * is switched.
1242     *
1243     * @param doContextInfo <code>true</code> if context information should be
1244     * displayed
1245     */

1246    public void setDoContextInfo(boolean doContextInfo) {
1247        fDoContextInfo= doContextInfo;
1248    }
1249
1250    /**
1251     * Sets the focus callback which will get informed when the focus of the
1252     * linked mode UI changes.
1253     * <p>
1254     * If there is a listener installed already, it will be replaced.
1255     * </p>
1256     *
1257     * @param listener the new listener, never <code>null</code>.
1258     */

1259    protected void setPositionListener(ILinkedModeUIFocusListener listener) {
1260        Assert.isNotNull(listener);
1261        fPositionListener= listener;
1262    }
1263
1264    /**
1265     * Sets the "simple" mode of the receiver. A linked mode UI in simple mode
1266     * merely draws the exit position, but not the target, focus, and slave
1267     * positions. Default is <code>false</code>. This method must be called
1268     * before it is entered.
1269     *
1270     * @param simple <code>true</code> if the UI should be in simple mode.
1271     */

1272    public void setSimpleMode(boolean simple) {
1273        fSimple= simple;
1274    }
1275
1276}
1277
Popular Tags