04d120f5816d3f9784709ee8247296eac0ff9467
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpdt / internal / ui / text / link / LinkedPositionUI.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text.link;
12
13 import java.lang.reflect.InvocationTargetException;
14
15 import net.sourceforge.phpdt.internal.ui.util.ExceptionHandler;
16 import net.sourceforge.phpdt.ui.PreferenceConstants;
17 //import net.sourceforge.phpeclipse.PHPeclipsePlugin;
18 import net.sourceforge.phpeclipse.ui.WebUI;
19
20 import org.eclipse.core.runtime.CoreException;
21 import org.eclipse.jface.dialogs.MessageDialog;
22 import org.eclipse.jface.preference.IPreferenceStore;
23 import org.eclipse.jface.preference.PreferenceConverter;
24 import org.eclipse.jface.text.Assert;
25 import org.eclipse.jface.text.BadLocationException;
26 import org.eclipse.jface.text.BadPositionCategoryException;
27 import org.eclipse.jface.text.DefaultPositionUpdater;
28 import org.eclipse.jface.text.IDocument;
29 import org.eclipse.jface.text.IPositionUpdater;
30 import org.eclipse.jface.text.IRegion;
31 import org.eclipse.jface.text.IRewriteTarget;
32 import org.eclipse.jface.text.ITextInputListener;
33 import org.eclipse.jface.text.ITextListener;
34 import org.eclipse.jface.text.ITextViewer;
35 import org.eclipse.jface.text.ITextViewerExtension;
36 import org.eclipse.jface.text.ITextViewerExtension2;
37 import org.eclipse.jface.text.ITextViewerExtension5;
38 import org.eclipse.jface.text.Position;
39 import org.eclipse.jface.text.Region;
40 import org.eclipse.jface.text.TextEvent;
41 import org.eclipse.jface.util.IPropertyChangeListener;
42 import org.eclipse.jface.util.PropertyChangeEvent;
43 import org.eclipse.swt.SWT;
44 import org.eclipse.swt.custom.StyledText;
45 import org.eclipse.swt.custom.VerifyKeyListener;
46 import org.eclipse.swt.events.ModifyEvent;
47 import org.eclipse.swt.events.ModifyListener;
48 import org.eclipse.swt.events.PaintEvent;
49 import org.eclipse.swt.events.PaintListener;
50 import org.eclipse.swt.events.ShellEvent;
51 import org.eclipse.swt.events.ShellListener;
52 import org.eclipse.swt.events.VerifyEvent;
53 import org.eclipse.swt.events.VerifyListener;
54 import org.eclipse.swt.graphics.Color;
55 import org.eclipse.swt.graphics.GC;
56 import org.eclipse.swt.graphics.Point;
57 import org.eclipse.swt.graphics.RGB;
58 import org.eclipse.swt.widgets.Display;
59 import org.eclipse.swt.widgets.Shell;
60
61 /**
62  * A user interface for <code>LinkedPositionManager</code>, using
63  * <code>ITextViewer</code>.
64  */
65 public class LinkedPositionUI implements ILinkedPositionListener,
66                 ITextInputListener, ITextListener, ModifyListener, VerifyListener,
67                 VerifyKeyListener, PaintListener, IPropertyChangeListener,
68                 ShellListener {
69
70         /**
71          * A listener for notification when the user cancelled the edit operation.
72          */
73         public interface ExitListener {
74                 void exit(boolean accept);
75         }
76
77         public static class ExitFlags {
78                 public int flags;
79
80                 public boolean doit;
81
82                 public ExitFlags(int flags, boolean doit) {
83                         this.flags = flags;
84                         this.doit = doit;
85                 }
86         }
87
88         public interface ExitPolicy {
89                 ExitFlags doExit(LinkedPositionManager manager, VerifyEvent event,
90                                 int offset, int length);
91         }
92
93         // leave flags
94         private static final int UNINSTALL = 1; // uninstall linked position manager
95
96         public static final int COMMIT = 2; // commit changes
97
98         private static final int DOCUMENT_CHANGED = 4; // document has changed
99
100         public static final int UPDATE_CARET = 8; // update caret
101
102         private static final IPreferenceStore fgStore = WebUI
103                         .getDefault().getPreferenceStore();
104
105         private static final String CARET_POSITION_PREFIX = "LinkedPositionUI.caret.position"; //$NON-NLS-1$
106
107         private static int fgCounter = 0;
108
109         private final ITextViewer fViewer;
110
111         private final LinkedPositionManager fManager;
112
113         private final IPositionUpdater fUpdater;
114
115         private final String fPositionCategoryName;
116
117         private Color fFrameColor;
118
119         private int fFinalCaretOffset = -1; // no final caret offset
120
121         private Position fFinalCaretPosition;
122
123         private Position fFramePosition;
124
125         private int fInitialOffset = -1;
126
127         private int fCaretOffset;
128
129         private ExitPolicy fExitPolicy;
130
131         private ExitListener fExitListener;
132
133         private boolean fNeedRedraw;
134
135         private String fContentType;
136
137         private Position fPreviousPosition;
138
139         // private ContentAssistant2 fAssistant;
140
141         /**
142          * Flag that records the state of this ui object. As there are many
143          * different entities that may call leave or exit, these cannot always be
144          * sure whether the linked position infrastructure is still active. This is
145          * especially true for multithreaded situations.
146          */
147         private boolean fIsActive = false;
148
149         /**
150          * Creates a user interface for <code>LinkedPositionManager</code>.
151          * 
152          * @param viewer
153          *            the text viewer.
154          * @param manager
155          *            the <code>LinkedPositionManager</code> managing a
156          *            <code>IDocument</code> of the <code>ITextViewer</code>.
157          */
158         public LinkedPositionUI(ITextViewer viewer, LinkedPositionManager manager) {
159                 Assert.isNotNull(viewer);
160                 Assert.isNotNull(manager);
161
162                 fViewer = viewer;
163                 fManager = manager;
164
165                 fPositionCategoryName = CARET_POSITION_PREFIX + (fgCounter++);
166                 fUpdater = new DefaultPositionUpdater(fPositionCategoryName);
167
168                 fManager.setLinkedPositionListener(this);
169
170                 initializeHighlightColor(viewer);
171         }
172
173         /*
174          * @see IPropertyChangeListener#propertyChange(PropertyChangeEvent)
175          */
176         public void propertyChange(PropertyChangeEvent event) {
177                 if (event.getProperty().equals(
178                                 PreferenceConstants.EDITOR_LINKED_POSITION_COLOR)) {
179                         initializeHighlightColor(fViewer);
180                         redrawRegion();
181                 }
182         }
183
184         private void initializeHighlightColor(ITextViewer viewer) {
185
186                 if (fFrameColor != null)
187                         fFrameColor.dispose();
188
189                 StyledText text = viewer.getTextWidget();
190                 if (text != null) {
191                         Display display = text.getDisplay();
192                         fFrameColor = createColor(fgStore,
193                                         PreferenceConstants.EDITOR_LINKED_POSITION_COLOR, display);
194                 }
195         }
196
197         /**
198          * Creates a color from the information stored in the given preference
199          * store. Returns <code>null</code> if there is no such information
200          * available.
201          */
202         private Color createColor(IPreferenceStore store, String key,
203                         Display display) {
204
205                 RGB rgb = null;
206
207                 if (store.contains(key)) {
208
209                         if (store.isDefault(key))
210                                 rgb = PreferenceConverter.getDefaultColor(store, key);
211                         else
212                                 rgb = PreferenceConverter.getColor(store, key);
213
214                         if (rgb != null)
215                                 return new Color(display, rgb);
216                 }
217
218                 return null;
219         }
220
221         /**
222          * Sets the initial offset.
223          * 
224          * @param offset
225          */
226         public void setInitialOffset(int offset) {
227                 fInitialOffset = offset;
228         }
229
230         /**
231          * Sets the final position of the caret when the linked mode is exited
232          * successfully by leaving the last linked position using TAB. The set
233          * position will be a TAB stop as well as the positions configured in the
234          * <code>LinkedPositionManager</code>.
235          */
236         public void setFinalCaretOffset(int offset) {
237                 fFinalCaretOffset = offset;
238         }
239
240         /**
241          * Sets a <code>CancelListener</code> which is notified if the linked mode
242          * is exited unsuccessfully by hitting ESC.
243          */
244         public void setCancelListener(ExitListener listener) {
245                 fExitListener = listener;
246         }
247
248         /**
249          * Sets an <code>ExitPolicy</code> which decides when and how the linked
250          * mode is exited.
251          */
252         public void setExitPolicy(ExitPolicy policy) {
253                 fExitPolicy = policy;
254         }
255
256         /*
257          * @see LinkedPositionManager.LinkedPositionListener#setCurrentPositions(Position,
258          *      int)
259          */
260         public void setCurrentPosition(Position position, int caretOffset) {
261                 if (!fIsActive)
262                         ;// JavaPlugin.log(new Status(IStatus.WARNING,
263                                 // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionUI is
264                                 // not active: "+fPositionCategoryName, new
265                                 // IllegalStateException())); //$NON-NLS-1$
266
267                 if (!fFramePosition.equals(position)) {
268                         fNeedRedraw = true;
269                         fFramePosition = position;
270                 }
271
272                 fCaretOffset = caretOffset;
273         }
274
275         /**
276          * Enters the linked mode. The linked mode can be left by calling
277          * <code>exit</code>.
278          * 
279          * @see #exit(boolean)
280          */
281         public void enter() {
282                 if (fIsActive)
283                         ;// JavaPlugin.log(new Status(IStatus.WARNING,
284                                 // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionUI is
285                                 // already active: "+fPositionCategoryName, new
286                                 // IllegalStateException())); //$NON-NLS-1$
287                 else {
288                         fIsActive = true;
289                         // JavaPlugin.log(new Status(IStatus.INFO, JavaPlugin.getPluginId(),
290                         // IStatus.OK, "LinkedPositionUI activated: "+fPositionCategoryName,
291                         // new Exception())); //$NON-NLS-1$
292                 }
293
294                 // track final caret
295                 IDocument document = fViewer.getDocument();
296                 document.addPositionCategory(fPositionCategoryName);
297                 document.addPositionUpdater(fUpdater);
298
299                 try {
300                         if (fFinalCaretOffset != -1) {
301                                 fFinalCaretPosition = new Position(fFinalCaretOffset);
302                                 document
303                                                 .addPosition(fPositionCategoryName, fFinalCaretPosition);
304                         }
305                 } catch (BadLocationException e) {
306                         handleException(fViewer.getTextWidget().getShell(), e);
307
308                 } catch (BadPositionCategoryException e) {
309                         WebUI.log(e);
310                         Assert.isTrue(false);
311                 }
312
313                 fViewer.addTextInputListener(this);
314                 fViewer.addTextListener(this);
315
316                 ITextViewerExtension extension = (ITextViewerExtension) fViewer;
317                 extension.prependVerifyKeyListener(this);
318
319                 StyledText text = fViewer.getTextWidget();
320                 text.addVerifyListener(this);
321                 text.addModifyListener(this);
322                 text.addPaintListener(this);
323                 text.showSelection();
324
325                 Shell shell = text.getShell();
326                 shell.addShellListener(this);
327
328                 fFramePosition = (fInitialOffset == -1) ? fManager.getFirstPosition()
329                                 : fManager.getPosition(fInitialOffset);
330                 if (fFramePosition == null) {
331                         leave(UNINSTALL | COMMIT | UPDATE_CARET);
332                         return;
333                 }
334
335                 fgStore.addPropertyChangeListener(this);
336
337                 // try {
338                 // fContentType= TextUtilities.getContentType(document,
339                 // IJavaPartitions.JAVA_PARTITIONING, fFramePosition.offset);
340                 // if (fViewer instanceof ITextViewerExtension2) {
341                 // ((ITextViewerExtension2) fViewer).prependAutoEditStrategy(fManager,
342                 // fContentType);
343                 // } else {
344                 // Assert.isTrue(false);
345                 // }
346                 //
347                 // } catch (BadLocationException e) {
348                 // handleException(fViewer.getTextWidget().getShell(), e);
349                 // }
350                 try {
351                         fContentType = document.getContentType(fFramePosition.offset);
352                         if (fViewer instanceof ITextViewerExtension2) {
353                                 ((ITextViewerExtension2) fViewer).prependAutoEditStrategy(
354                                                 fManager, fContentType);
355                         } else {
356                                 Assert.isTrue(false);
357                         }
358
359                 } catch (BadLocationException e) {
360                         handleException(fViewer.getTextWidget().getShell(), e);
361                 }
362                 selectRegion();
363                 // triggerContentAssist();
364         }
365
366         /*
367          * @see net.sourceforge.phpdt.internal.ui.text.link.ILinkedPositionListener#exit(boolean)
368          */
369         public void exit(int flags) {
370                 leave(flags);
371         }
372
373         /**
374          * Returns the cursor selection, after having entered the linked mode.
375          * <code>enter()</code> must be called prior to a call to this method.
376          */
377         public IRegion getSelectedRegion() {
378                 if (!fIsActive)
379                         ;// JavaPlugin.log(new Status(IStatus.WARNING,
380                                 // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionUI is
381                                 // not active: "+fPositionCategoryName, new
382                                 // IllegalStateException())); //$NON-NLS-1$
383
384                 if (fFramePosition == null)
385                         return new Region(fFinalCaretOffset, 0);
386                 else
387                         return new Region(fFramePosition.getOffset(), fFramePosition
388                                         .getLength());
389         }
390
391         private void leave(int flags) {
392                 if (!fIsActive)
393                         ;// JavaPlugin.log(new Status(IStatus.WARNING,
394                                 // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionUI is
395                                 // not active: "+fPositionCategoryName, new
396                                 // IllegalStateException())); //$NON-NLS-1$
397                 else {
398                         fIsActive = false;
399                         // JavaPlugin.log(new Status(IStatus.INFO, JavaPlugin.getPluginId(),
400                         // IStatus.OK, "LinkedPositionUI deactivated:
401                         // "+fPositionCategoryName, new Exception())); //$NON-NLS-1$
402                 }
403
404                 fInitialOffset = -1;
405
406                 if ((flags & UNINSTALL) != 0)
407                         fManager.uninstall((flags & COMMIT) != 0);
408
409                 fgStore.removePropertyChangeListener(this);
410
411                 if (fFrameColor != null) {
412                         fFrameColor.dispose();
413                         fFrameColor = null;
414                 }
415
416                 StyledText text = fViewer.getTextWidget();
417                 // bail out if the styled text is null, meaning the viewer has been
418                 // disposed (-> document is null as well)
419                 // see pr https://bugs.eclipse.org/bugs/show_bug.cgi?id=46821
420                 if (text == null)
421                         return;
422
423                 text.removePaintListener(this);
424                 text.removeModifyListener(this);
425                 text.removeVerifyListener(this);
426
427                 Shell shell = text.getShell();
428                 shell.removeShellListener(this);
429
430                 // if (fAssistant != null) {
431                 // Display display= text.getDisplay();
432                 // if (display != null && !display.isDisposed()) {
433                 // display.asyncExec(new Runnable() {
434                 // public void run() {
435                 // if (fAssistant != null) {
436                 // fAssistant.uninstall();
437                 // fAssistant= null;
438                 // }
439                 // }
440                 // });
441                 // }
442                 // }
443
444                 ITextViewerExtension extension = (ITextViewerExtension) fViewer;
445                 extension.removeVerifyKeyListener(this);
446
447                 IRewriteTarget target = extension.getRewriteTarget();
448                 target.endCompoundChange();
449
450                 if (fViewer instanceof ITextViewerExtension2 && fContentType != null)
451                         ((ITextViewerExtension2) fViewer).removeAutoEditStrategy(fManager,
452                                         fContentType);
453                 fContentType = null;
454
455                 fViewer.removeTextListener(this);
456                 fViewer.removeTextInputListener(this);
457
458                 try {
459                         IDocument document = fViewer.getDocument();
460
461                         if (((flags & COMMIT) != 0) && ((flags & DOCUMENT_CHANGED) == 0)
462                                         && ((flags & UPDATE_CARET) != 0)) {
463                                 Position[] positions = document
464                                                 .getPositions(fPositionCategoryName);
465                                 if ((positions != null) && (positions.length != 0)) {
466
467                                         if (fViewer instanceof ITextViewerExtension5) {
468                                                 ITextViewerExtension5 extension3 = (ITextViewerExtension5) fViewer;
469                                                 int widgetOffset = extension3
470                                                                 .modelOffset2WidgetOffset(positions[0]
471                                                                                 .getOffset());
472                                                 if (widgetOffset >= 0)
473                                                         text.setSelection(widgetOffset, widgetOffset);
474
475                                         } else {
476                                                 IRegion region = fViewer.getVisibleRegion();
477                                                 int offset = positions[0].getOffset()
478                                                                 - region.getOffset();
479                                                 if ((offset >= 0) && (offset <= region.getLength()))
480                                                         text.setSelection(offset, offset);
481                                         }
482                                 }
483                         }
484
485                         document.removePositionUpdater(fUpdater);
486                         document.removePositionCategory(fPositionCategoryName);
487
488                         if (fExitListener != null)
489                                 fExitListener.exit(((flags & COMMIT) != 0)
490                                                 || ((flags & DOCUMENT_CHANGED) != 0));
491
492                 } catch (BadPositionCategoryException e) {
493                         WebUI.log(e);
494                         Assert.isTrue(false);
495                 }
496
497                 if ((flags & DOCUMENT_CHANGED) == 0)
498                         text.redraw();
499         }
500
501         private void next() {
502                 if (!fIsActive)
503                         ;// JavaPlugin.log(new Status(IStatus.WARNING,
504                                 // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionUI is
505                                 // not active: "+fPositionCategoryName, new
506                                 // IllegalStateException())); //$NON-NLS-1$
507
508                 redrawRegion();
509
510                 if (fFramePosition == fFinalCaretPosition)
511                         fFramePosition = fManager.getFirstPosition();
512                 else
513                         fFramePosition = fManager.getNextPosition(fFramePosition
514                                         .getOffset());
515                 if (fFramePosition == null) {
516                         if (fFinalCaretPosition != null)
517                                 fFramePosition = fFinalCaretPosition;
518                         else
519                                 fFramePosition = fManager.getFirstPosition();
520                 }
521                 if (fFramePosition == null) {
522                         leave(UNINSTALL | COMMIT | UPDATE_CARET);
523                 } else {
524                         selectRegion();
525                         // triggerContentAssist();
526                         redrawRegion();
527                 }
528         }
529
530         private void previous() {
531                 if (!fIsActive)
532                         ;// JavaPlugin.log(new Status(IStatus.WARNING,
533                                 // JavaPlugin.getPluginId(), IStatus.OK, "LinkedPositionUI is
534                                 // not active: "+fPositionCategoryName, new
535                                 // IllegalStateException())); //$NON-NLS-1$
536
537                 redrawRegion();
538
539                 fFramePosition = fManager.getPreviousPosition(fFramePosition
540                                 .getOffset());
541                 if (fFramePosition == null) {
542                         if (fFinalCaretPosition != null)
543                                 fFramePosition = fFinalCaretPosition;
544                         else
545                                 fFramePosition = fManager.getLastPosition();
546                 }
547                 if (fFramePosition == null) {
548                         leave(UNINSTALL | COMMIT | UPDATE_CARET);
549                 } else {
550                         selectRegion();
551                         // triggerContentAssist();
552                         redrawRegion();
553                 }
554         }
555
556         /** Trigger content assist on choice positions */
557         // private void triggerContentAssist() {
558         // if (fFramePosition instanceof ProposalPosition) {
559         //
560         // ProposalPosition pp= (ProposalPosition) fFramePosition;
561         // initializeContentAssistant();
562         // if (fAssistant == null)
563         // return;
564         // fAssistant.setCompletions(pp.getChoices());
565         // fAssistant.showPossibleCompletions();
566         // } else {
567         // if (fAssistant != null)
568         // fAssistant.setCompletions(new ICompletionProposal[0]);
569         // }
570         // }
571         /** Lazy initialize content assistant for this linked ui */
572         // private void initializeContentAssistant() {
573         // if (fAssistant != null)
574         // return;
575         // fAssistant= new ContentAssistant2();
576         // fAssistant.setDocumentPartitioning(IJavaPartitions.JAVA_PARTITIONING);
577         // fAssistant.install(fViewer);
578         // }
579         /*
580          * @see VerifyKeyListener#verifyKey(VerifyEvent)
581          */
582         public void verifyKey(VerifyEvent event) {
583
584                 if (!event.doit || !fIsActive)
585                         return;
586
587                 Point selection = fViewer.getSelectedRange();
588                 int offset = selection.x;
589                 int length = selection.y;
590
591                 ExitFlags exitFlags = fExitPolicy == null ? null : fExitPolicy.doExit(
592                                 fManager, event, offset, length);
593                 if (exitFlags != null) {
594                         leave(UNINSTALL | exitFlags.flags);
595                         event.doit = exitFlags.doit;
596                         return;
597                 }
598
599                 switch (event.character) {
600                 // [SHIFT-]TAB = hop between edit boxes
601                 case 0x09: {
602                         // if tab was treated as a document change, would it exceed variable
603                         // range?
604                         if (!LinkedPositionManager.includes(fFramePosition, offset, length)) {
605                                 leave(UNINSTALL | COMMIT);
606                                 return;
607                         }
608                 }
609
610                         if (event.stateMask == SWT.SHIFT)
611                                 previous();
612                         else
613                                 next();
614
615                         event.doit = false;
616                         break;
617
618                 // ENTER
619                 case 0x0A: // Ctrl+Enter
620                 case 0x0D: {
621                         // if (fAssistant != null && fAssistant.wasProposalChosen()) {
622                         // next();
623                         // event.doit= false;
624                         // break;
625                         // }
626
627                         // if enter was treated as a document change, would it exceed
628                         // variable range?
629                         if (!LinkedPositionManager.includes(fFramePosition, offset, length)
630                                         || (fFramePosition == fFinalCaretPosition)) {
631                                 leave(UNINSTALL | COMMIT);
632                                 return;
633                         }
634                 }
635
636                         leave(UNINSTALL | COMMIT | UPDATE_CARET);
637                         event.doit = false;
638                         break;
639
640                 // ESC
641                 case 0x1B:
642                         leave(UNINSTALL | COMMIT);
643                         event.doit = false;
644                         break;
645
646                 case ';':
647                         leave(UNINSTALL | COMMIT);
648                         event.doit = true;
649                         break;
650
651                 default:
652                         if (event.character != 0) {
653                                 if (!controlUndoBehavior(offset, length)
654                                                 || fFramePosition == fFinalCaretPosition) {
655                                         leave(UNINSTALL | COMMIT);
656                                         break;
657                                 }
658                         }
659                 }
660         }
661
662         private boolean controlUndoBehavior(int offset, int length) {
663
664                 Position position = fManager.getEmbracingPosition(offset, length);
665                 if (position != null) {
666
667                         ITextViewerExtension extension = (ITextViewerExtension) fViewer;
668                         IRewriteTarget target = extension.getRewriteTarget();
669
670                         if (fPreviousPosition != null
671                                         && !fPreviousPosition.equals(position))
672                                 target.endCompoundChange();
673                         target.beginCompoundChange();
674                 }
675
676                 fPreviousPosition = position;
677                 return fPreviousPosition != null;
678         }
679
680         /*
681          * @see VerifyListener#verifyText(VerifyEvent)
682          */
683         public void verifyText(VerifyEvent event) {
684                 if (!event.doit)
685                         return;
686
687                 int offset = 0;
688                 int length = 0;
689
690                 if (fViewer instanceof ITextViewerExtension5) {
691                         ITextViewerExtension5 extension = (ITextViewerExtension5) fViewer;
692                         IRegion modelRange = extension.widgetRange2ModelRange(new Region(
693                                         event.start, event.end - event.start));
694                         if (modelRange == null)
695                                 return;
696
697                         offset = modelRange.getOffset();
698                         length = modelRange.getLength();
699
700                 } else {
701                         IRegion visibleRegion = fViewer.getVisibleRegion();
702                         offset = event.start + visibleRegion.getOffset();
703                         length = event.end - event.start;
704                 }
705
706                 // allow changes only within linked positions when coming through UI
707                 if (!fManager.anyPositionIncludes(offset, length))
708                         leave(UNINSTALL | COMMIT);
709         }
710
711         /*
712          * @see PaintListener#paintControl(PaintEvent)
713          */
714         public void paintControl(PaintEvent event) {
715                 if (fFramePosition == null)
716                         return;
717
718                 IRegion widgetRange = asWidgetRange(fFramePosition);
719                 if (widgetRange == null) {
720                         leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
721                         return;
722                 }
723
724                 int offset = widgetRange.getOffset();
725                 int length = widgetRange.getLength();
726
727                 StyledText text = fViewer.getTextWidget();
728
729                 // support for bidi
730                 Point minLocation = getMinimumLocation(text, offset, length);
731                 Point maxLocation = getMaximumLocation(text, offset, length);
732
733                 int x1 = minLocation.x;
734                 int x2 = minLocation.x + maxLocation.x - minLocation.x - 1;
735                 int y = minLocation.y + text.getLineHeight() - 1;
736
737                 GC gc = event.gc;
738                 gc.setForeground(fFrameColor);
739                 gc.drawLine(x1, y, x2, y);
740         }
741
742         protected IRegion asWidgetRange(Position position) {
743                 if (fViewer instanceof ITextViewerExtension5) {
744
745                         ITextViewerExtension5 extension = (ITextViewerExtension5) fViewer;
746                         return extension.modelRange2WidgetRange(new Region(position
747                                         .getOffset(), position.getLength()));
748
749                 } else {
750
751                         IRegion region = fViewer.getVisibleRegion();
752                         if (includes(region, position))
753                                 return new Region(position.getOffset() - region.getOffset(),
754                                                 position.getLength());
755                 }
756
757                 return null;
758         }
759
760         private static Point getMinimumLocation(StyledText text, int offset,
761                         int length) {
762                 Point minLocation = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
763
764                 for (int i = 0; i <= length; i++) {
765                         Point location = text.getLocationAtOffset(offset + i);
766
767                         if (location.x < minLocation.x)
768                                 minLocation.x = location.x;
769                         if (location.y < minLocation.y)
770                                 minLocation.y = location.y;
771                 }
772
773                 return minLocation;
774         }
775
776         private static Point getMaximumLocation(StyledText text, int offset,
777                         int length) {
778                 Point maxLocation = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
779
780                 for (int i = 0; i <= length; i++) {
781                         Point location = text.getLocationAtOffset(offset + i);
782
783                         if (location.x > maxLocation.x)
784                                 maxLocation.x = location.x;
785                         if (location.y > maxLocation.y)
786                                 maxLocation.y = location.y;
787                 }
788
789                 return maxLocation;
790         }
791
792         private void redrawRegion() {
793                 IRegion widgetRange = asWidgetRange(fFramePosition);
794                 if (widgetRange == null) {
795                         leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
796                         return;
797                 }
798
799                 StyledText text = fViewer.getTextWidget();
800                 if (text != null && !text.isDisposed())
801                         text.redrawRange(widgetRange.getOffset(), widgetRange.getLength(),
802                                         true);
803         }
804
805         private void selectRegion() {
806
807                 IRegion widgetRange = asWidgetRange(fFramePosition);
808                 if (widgetRange == null) {
809                         leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
810                         return;
811                 }
812
813                 StyledText text = fViewer.getTextWidget();
814                 if (text != null && !text.isDisposed()) {
815                         int start = widgetRange.getOffset();
816                         int end = widgetRange.getLength() + start;
817                         text.setSelection(start, end);
818                 }
819         }
820
821         private void updateCaret() {
822
823                 IRegion widgetRange = asWidgetRange(fFramePosition);
824                 if (widgetRange == null) {
825                         leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
826                         return;
827                 }
828
829                 int offset = widgetRange.getOffset() + fCaretOffset;
830                 StyledText text = fViewer.getTextWidget();
831                 if (text != null && !text.isDisposed())
832                         text.setCaretOffset(offset);
833         }
834
835         /*
836          * @see ModifyListener#modifyText(ModifyEvent)
837          */
838         public void modifyText(ModifyEvent e) {
839                 // reposition caret after StyledText
840                 redrawRegion();
841                 updateCaret();
842         }
843
844         private static void handleException(Shell shell, Exception e) {
845                 String title = LinkedPositionMessages
846                                 .getString("LinkedPositionUI.error.title"); //$NON-NLS-1$
847                 if (e instanceof CoreException)
848                         ExceptionHandler.handle((CoreException) e, shell, title, null);
849                 else if (e instanceof InvocationTargetException)
850                         ExceptionHandler.handle((InvocationTargetException) e, shell,
851                                         title, null);
852                 else {
853                         MessageDialog.openError(shell, title, e.getMessage());
854                         WebUI.log(e);
855                 }
856         }
857
858         /*
859          * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument,
860          *      IDocument)
861          */
862         public void inputDocumentAboutToBeChanged(IDocument oldInput,
863                         IDocument newInput) {
864                 // 5326: leave linked mode on document change
865                 int flags = UNINSTALL | COMMIT
866                                 | (oldInput.equals(newInput) ? 0 : DOCUMENT_CHANGED);
867                 leave(flags);
868         }
869
870         /*
871          * @see ITextInputListener#inputDocumentChanged(IDocument, IDocument)
872          */
873         public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
874         }
875
876         private static boolean includes(IRegion region, Position position) {
877                 return position.getOffset() >= region.getOffset()
878                                 && position.getOffset() + position.getLength() <= region
879                                                 .getOffset()
880                                                 + region.getLength();
881         }
882
883         /*
884          * @see org.eclipse.jface.text.ITextListener#textChanged(TextEvent)
885          */
886         public void textChanged(TextEvent event) {
887                 if (!fNeedRedraw)
888                         return;
889
890                 redrawRegion();
891                 fNeedRedraw = false;
892         }
893
894         /*
895          * @see org.eclipse.swt.events.ShellListener#shellActivated(org.eclipse.swt.events.ShellEvent)
896          */
897         public void shellActivated(ShellEvent event) {
898         }
899
900         /*
901          * @see org.eclipse.swt.events.ShellListener#shellClosed(org.eclipse.swt.events.ShellEvent)
902          */
903         public void shellClosed(ShellEvent event) {
904                 leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
905         }
906
907         /*
908          * @see org.eclipse.swt.events.ShellListener#shellDeactivated(org.eclipse.swt.events.ShellEvent)
909          */
910         public void shellDeactivated(ShellEvent event) {
911                 // don't deactivate on focus lost, since the proposal popups may take
912                 // focus
913                 // plus: it doesn't hurt if you can check with another window without
914                 // losing linked mode
915                 // since there is no intrusive popup sticking out.
916
917                 // need to check first what happens on reentering based on an open
918                 // action
919                 // Seems to be no problem
920
921                 // TODO check whether we can leave it or uncomment it after debugging
922                 // PS: why DOCUMENT_CHANGED? We want to trigger a redraw! (Shell
923                 // deactivated does not mean
924                 // it is not visible any longer.
925                 // leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
926
927                 // Better:
928                 // Check with content assistant and only leave if its not the proposal
929                 // shell that took the
930                 // focus away.
931
932                 StyledText text;
933                 Display display;
934
935                 // if (fAssistant == null || fViewer == null || (text=
936                 // fViewer.getTextWidget()) == null
937                 // || (display= text.getDisplay()) == null || display.isDisposed()) {
938                 if (fViewer == null || (text = fViewer.getTextWidget()) == null
939                                 || (display = text.getDisplay()) == null
940                                 || display.isDisposed()) {
941                         leave(UNINSTALL | COMMIT);
942                 } else {
943                         // Post in UI thread since the assistant popup will only get the
944                         // focus after we lose it.
945                         display.asyncExec(new Runnable() {
946                                 public void run() {
947                                         // TODO add isDisposed / isUninstalled / hasLeft check? for
948                                         // now: check for content type,
949                                         // since it gets nullified in leave()
950                                         if (fIsActive) {// && (fAssistant == null ||
951                                                                         // !fAssistant.hasFocus())) {
952                                                 leave(UNINSTALL | COMMIT);
953                                         }
954                                 }
955                         });
956                 }
957         }
958
959         /*
960          * @see org.eclipse.swt.events.ShellListener#shellDeiconified(org.eclipse.swt.events.ShellEvent)
961          */
962         public void shellDeiconified(ShellEvent event) {
963         }
964
965         /*
966          * @see org.eclipse.swt.events.ShellListener#shellIconified(org.eclipse.swt.events.ShellEvent)
967          */
968         public void shellIconified(ShellEvent event) {
969                 leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
970         }
971
972 }