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