KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > presentation > PresentationReconciler


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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
12 package org.eclipse.jface.text.presentation;
13
14 import java.util.HashMap JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.Map JavaDoc;
17
18 import org.eclipse.swt.custom.StyleRange;
19
20 import org.eclipse.core.runtime.Assert;
21
22 import org.eclipse.jface.text.BadLocationException;
23 import org.eclipse.jface.text.BadPositionCategoryException;
24 import org.eclipse.jface.text.DefaultPositionUpdater;
25 import org.eclipse.jface.text.DocumentEvent;
26 import org.eclipse.jface.text.DocumentPartitioningChangedEvent;
27 import org.eclipse.jface.text.IDocument;
28 import org.eclipse.jface.text.IDocumentExtension3;
29 import org.eclipse.jface.text.IDocumentListener;
30 import org.eclipse.jface.text.IDocumentPartitioningListener;
31 import org.eclipse.jface.text.IDocumentPartitioningListenerExtension;
32 import org.eclipse.jface.text.IDocumentPartitioningListenerExtension2;
33 import org.eclipse.jface.text.IPositionUpdater;
34 import org.eclipse.jface.text.IRegion;
35 import org.eclipse.jface.text.ITextInputListener;
36 import org.eclipse.jface.text.ITextListener;
37 import org.eclipse.jface.text.ITextViewer;
38 import org.eclipse.jface.text.ITextViewerExtension5;
39 import org.eclipse.jface.text.ITypedRegion;
40 import org.eclipse.jface.text.Region;
41 import org.eclipse.jface.text.TextEvent;
42 import org.eclipse.jface.text.TextPresentation;
43 import org.eclipse.jface.text.TextUtilities;
44 import org.eclipse.jface.text.TypedPosition;
45
46
47
48 /**
49  * Standard implementation of <code>IPresentationReconciler</code>. This
50  * implementation assumes that the tasks performed by its presentation damagers
51  * and repairers are lightweight and of low cost. This presentation reconciler
52  * runs in the UI thread and always repairs the complete damage caused by a
53  * document change rather than just the portion overlapping with the viewer's
54  * viewport.
55  * <p>
56  * Usually, clients instantiate this class and configure it before using it.
57  * </p>
58  */

59 public class PresentationReconciler implements IPresentationReconciler, IPresentationReconcilerExtension {
60
61     /** Prefix of the name of the position category for tracking damage regions. */
62     protected final static String JavaDoc TRACKED_PARTITION= "__reconciler_tracked_partition"; //$NON-NLS-1$
63

64
65     /**
66      * Internal listener class.
67      */

68     class InternalListener implements
69             ITextInputListener, IDocumentListener, ITextListener,
70             IDocumentPartitioningListener, IDocumentPartitioningListenerExtension, IDocumentPartitioningListenerExtension2 {
71
72         /** Set to <code>true</code> if between a document about to be changed and a changed event. */
73         private boolean fDocumentChanging= false;
74         /**
75          * The cached redraw state of the text viewer.
76          * @since 3.0
77          */

78         private boolean fCachedRedrawState= true;
79
80         /*
81          * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument)
82          */

83         public void inputDocumentAboutToBeChanged(IDocument oldDocument, IDocument newDocument) {
84             if (oldDocument != null) {
85                 try {
86
87                     fViewer.removeTextListener(this);
88                     oldDocument.removeDocumentListener(this);
89                     oldDocument.removeDocumentPartitioningListener(this);
90
91                     oldDocument.removePositionUpdater(fPositionUpdater);
92                     oldDocument.removePositionCategory(fPositionCategory);
93
94                 } catch (BadPositionCategoryException x) {
95                     // should not happened for former input documents;
96
}
97             }
98         }
99
100         /*
101          * @see ITextInputListener#inputDocumenChanged(IDocument, IDocument)
102          */

103         public void inputDocumentChanged(IDocument oldDocument, IDocument newDocument) {
104
105             fDocumentChanging= false;
106             fCachedRedrawState= true;
107
108             if (newDocument != null) {
109
110                 newDocument.addPositionCategory(fPositionCategory);
111                 newDocument.addPositionUpdater(fPositionUpdater);
112
113                 newDocument.addDocumentPartitioningListener(this);
114                 newDocument.addDocumentListener(this);
115                 fViewer.addTextListener(this);
116
117                 setDocumentToDamagers(newDocument);
118                 setDocumentToRepairers(newDocument);
119                 processDamage(new Region(0, newDocument.getLength()), newDocument);
120             }
121         }
122
123         /*
124          * @see IDocumentPartitioningListener#documentPartitioningChanged(IDocument)
125          */

126         public void documentPartitioningChanged(IDocument document) {
127             if (!fDocumentChanging && fCachedRedrawState)
128                 processDamage(new Region(0, document.getLength()), document);
129             else
130                 fDocumentPartitioningChanged= true;
131         }
132
133         /*
134          * @see IDocumentPartitioningListenerExtension#documentPartitioningChanged(IDocument, IRegion)
135          * @since 2.0
136          */

137         public void documentPartitioningChanged(IDocument document, IRegion changedRegion) {
138             if (!fDocumentChanging && fCachedRedrawState) {
139                 processDamage(new Region(changedRegion.getOffset(), changedRegion.getLength()), document);
140             } else {
141                 fDocumentPartitioningChanged= true;
142                 fChangedDocumentPartitions= changedRegion;
143             }
144         }
145
146         /*
147          * @see org.eclipse.jface.text.IDocumentPartitioningListenerExtension2#documentPartitioningChanged(org.eclipse.jface.text.DocumentPartitioningChangedEvent)
148          * @since 3.0
149          */

150         public void documentPartitioningChanged(DocumentPartitioningChangedEvent event) {
151             IRegion changedRegion= event.getChangedRegion(getDocumentPartitioning());
152             if (changedRegion != null)
153                 documentPartitioningChanged(event.getDocument(), changedRegion);
154         }
155
156         /*
157          * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
158          */

159         public void documentAboutToBeChanged(DocumentEvent e) {
160
161             fDocumentChanging= true;
162             if (fCachedRedrawState) {
163                 try {
164                     int offset= e.getOffset() + e.getLength();
165                     ITypedRegion region= getPartition(e.getDocument(), offset);
166                     fRememberedPosition= new TypedPosition(region);
167                     e.getDocument().addPosition(fPositionCategory, fRememberedPosition);
168                 } catch (BadLocationException x) {
169                     // can not happen
170
} catch (BadPositionCategoryException x) {
171                     // should not happen on input elements
172
}
173             }
174         }
175
176         /*
177          * @see IDocumentListener#documentChanged(DocumentEvent)
178          */

179         public void documentChanged(DocumentEvent e) {
180             if (fCachedRedrawState) {
181                 try {
182                     e.getDocument().removePosition(fPositionCategory, fRememberedPosition);
183                 } catch (BadPositionCategoryException x) {
184                     // can not happen on input documents
185
}
186             }
187             fDocumentChanging= false;
188         }
189
190         /*
191          * @see ITextListener#textChanged(TextEvent)
192          */

193         public void textChanged(TextEvent e) {
194
195             fCachedRedrawState= e.getViewerRedrawState();
196             if (!fCachedRedrawState)
197                 return;
198
199             IRegion damage= null;
200             IDocument document= null;
201
202             if (e.getDocumentEvent() == null) {
203                 document= fViewer.getDocument();
204                 if (document != null) {
205                     if (e.getOffset() == 0 && e.getLength() == 0 && e.getText() == null) {
206                         // redraw state change, damage the whole document
207
damage= new Region(0, document.getLength());
208                     } else {
209                         IRegion region= widgetRegion2ModelRegion(e);
210                         try {
211                             String JavaDoc text= document.get(region.getOffset(), region.getLength());
212                             DocumentEvent de= new DocumentEvent(document, region.getOffset(), region.getLength(), text);
213                             damage= getDamage(de, false);
214                         } catch (BadLocationException x) {
215                         }
216                     }
217                 }
218             } else {
219                 DocumentEvent de= e.getDocumentEvent();
220                 document= de.getDocument();
221                 damage= getDamage(de, true);
222             }
223
224             if (damage != null && document != null)
225                 processDamage(damage, document);
226
227             fDocumentPartitioningChanged= false;
228             fChangedDocumentPartitions= null;
229         }
230
231         /**
232          * Translates the given text event into the corresponding range of the viewer's document.
233          *
234          * @param e the text event
235          * @return the widget region corresponding the region of the given event
236          * @since 2.1
237          */

238         protected IRegion widgetRegion2ModelRegion(TextEvent e) {
239
240             String JavaDoc text= e.getText();
241             int length= text == null ? 0 : text.length();
242
243             if (fViewer instanceof ITextViewerExtension5) {
244                 ITextViewerExtension5 extension= (ITextViewerExtension5) fViewer;
245                 return extension.widgetRange2ModelRange(new Region(e.getOffset(), length));
246             }
247
248             IRegion visible= fViewer.getVisibleRegion();
249             IRegion region= new Region(e.getOffset() + visible.getOffset(), length);
250             return region;
251         }
252     }
253
254     /** The map of presentation damagers. */
255     private Map JavaDoc fDamagers;
256     /** The map of presentation repairers. */
257     private Map JavaDoc fRepairers;
258     /** The target viewer. */
259     private ITextViewer fViewer;
260     /** The internal listener. */
261     private InternalListener fInternalListener= new InternalListener();
262     /** The name of the position category to track damage regions. */
263     private String JavaDoc fPositionCategory;
264     /** The position updated for the damage regions' position category. */
265     private IPositionUpdater fPositionUpdater;
266     /** The positions representing the damage regions. */
267     private TypedPosition fRememberedPosition;
268     /** Flag indicating the receipt of a partitioning changed notification. */
269     private boolean fDocumentPartitioningChanged= false;
270     /** The range covering the changed partitioning. */
271     private IRegion fChangedDocumentPartitions= null;
272     /**
273      * The partitioning used by this presentation reconciler.
274      * @since 3.0
275      */

276     private String JavaDoc fPartitioning;
277
278     /**
279      * Creates a new presentation reconciler. There are no damagers or repairers
280      * registered with this reconciler by default. The default partitioning
281      * <code>IDocumentExtension3.DEFAULT_PARTITIONING</code> is used.
282      */

283     public PresentationReconciler() {
284         super();
285         fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING;
286         fPositionCategory= TRACKED_PARTITION + hashCode();
287         fPositionUpdater= new DefaultPositionUpdater(fPositionCategory);
288     }
289
290     /**
291      * Sets the document partitioning for this presentation reconciler.
292      *
293      * @param partitioning the document partitioning for this presentation reconciler.
294      * @since 3.0
295      */

296     public void setDocumentPartitioning(String JavaDoc partitioning) {
297         Assert.isNotNull(partitioning);
298         fPartitioning= partitioning;
299     }
300
301     /*
302      * @see org.eclipse.jface.text.presentation.IPresentationReconcilerExtension#geDocumenttPartitioning()
303      * @since 3.0
304      */

305     public String JavaDoc getDocumentPartitioning() {
306         return fPartitioning;
307     }
308
309     /**
310      * Registers the given presentation damager for a particular content type.
311      * If there is already a damager registered for this type, the old damager
312      * is removed first.
313      *
314      * @param damager the presentation damager to register, or <code>null</code> to remove an existing one
315      * @param contentType the content type under which to register
316      */

317     public void setDamager(IPresentationDamager damager, String JavaDoc contentType) {
318
319         Assert.isNotNull(contentType);
320
321         if (fDamagers == null)
322             fDamagers= new HashMap JavaDoc();
323
324         if (damager == null)
325             fDamagers.remove(contentType);
326         else
327             fDamagers.put(contentType, damager);
328     }
329
330     /**
331      * Registers the given presentation repairer for a particular content type.
332      * If there is already a repairer registered for this type, the old repairer
333      * is removed first.
334      *
335      * @param repairer the presentation repairer to register, or <code>null</code> to remove an existing one
336      * @param contentType the content type under which to register
337      */

338     public void setRepairer(IPresentationRepairer repairer, String JavaDoc contentType) {
339
340         Assert.isNotNull(contentType);
341
342         if (fRepairers == null)
343             fRepairers= new HashMap JavaDoc();
344
345         if (repairer == null)
346             fRepairers.remove(contentType);
347         else
348             fRepairers.put(contentType, repairer);
349     }
350
351     /*
352      * @see IPresentationReconciler#install(ITextViewer)
353      */

354     public void install(ITextViewer viewer) {
355         Assert.isNotNull(viewer);
356
357         fViewer= viewer;
358         fViewer.addTextInputListener(fInternalListener);
359         
360         IDocument document= viewer.getDocument();
361         if (document != null)
362             fInternalListener.inputDocumentChanged(null, document);
363     }
364
365     /*
366      * @see IPresentationReconciler#uninstall()
367      */

368     public void uninstall() {
369         fViewer.removeTextInputListener(fInternalListener);
370
371         // Ensure we uninstall all listeners
372
fInternalListener.inputDocumentAboutToBeChanged(fViewer.getDocument(), null);
373     }
374
375     /*
376      * @see IPresentationReconciler#getDamager(String)
377      */

378     public IPresentationDamager getDamager(String JavaDoc contentType) {
379
380         if (fDamagers == null)
381             return null;
382
383         return (IPresentationDamager) fDamagers.get(contentType);
384     }
385
386     /*
387      * @see IPresentationReconciler#getRepairer(String)
388      */

389     public IPresentationRepairer getRepairer(String JavaDoc contentType) {
390
391         if (fRepairers == null)
392             return null;
393
394         return (IPresentationRepairer) fRepairers.get(contentType);
395     }
396
397     /**
398      * Informs all registered damagers about the document on which they will work.
399      *
400      * @param document the document on which to work
401      */

402     protected void setDocumentToDamagers(IDocument document) {
403         if (fDamagers != null) {
404             Iterator JavaDoc e= fDamagers.values().iterator();
405             while (e.hasNext()) {
406                 IPresentationDamager damager= (IPresentationDamager) e.next();
407                 damager.setDocument(document);
408             }
409         }
410     }
411
412     /**
413      * Informs all registered repairers about the document on which they will work.
414      *
415      * @param document the document on which to work
416      */

417     protected void setDocumentToRepairers(IDocument document) {
418         if (fRepairers != null) {
419             Iterator JavaDoc e= fRepairers.values().iterator();
420             while (e.hasNext()) {
421                 IPresentationRepairer repairer= (IPresentationRepairer) e.next();
422                 repairer.setDocument(document);
423             }
424         }
425     }
426
427     /**
428      * Constructs a "repair description" for the given damage and returns this
429      * description as a text presentation. For this, it queries the partitioning
430      * of the damage region and asks the appropriate presentation repairer for
431      * each partition to construct the "repair description" for this partition.
432      *
433      * @param damage the damage to be repaired
434      * @param document the document whose presentation must be repaired
435      * @return the presentation repair description as text presentation or
436      * <code>null</code> if the partitioning could not be computed
437      */

438     protected TextPresentation createPresentation(IRegion damage, IDocument document) {
439         try {
440             if (fRepairers == null || fRepairers.isEmpty()) {
441                 TextPresentation presentation= new TextPresentation(damage, 1);
442                 presentation.setDefaultStyleRange(new StyleRange(damage.getOffset(), damage.getLength(), null, null));
443                 return presentation;
444             }
445
446             TextPresentation presentation= new TextPresentation(damage, 1000);
447
448             ITypedRegion[] partitioning= TextUtilities.computePartitioning(document, getDocumentPartitioning(), damage.getOffset(), damage.getLength(), false);
449             for (int i= 0; i < partitioning.length; i++) {
450                 ITypedRegion r= partitioning[i];
451                 IPresentationRepairer repairer= getRepairer(r.getType());
452                 if (repairer != null)
453                     repairer.createPresentation(presentation, r);
454             }
455
456             return presentation;
457
458         } catch (BadLocationException x) {
459         }
460
461         return null;
462     }
463
464
465     /**
466      * Checks for the first and the last affected partition affected by a
467      * document event and calls their damagers. Invalidates everything from the
468      * start of the damage for the first partition until the end of the damage
469      * for the last partition.
470      *
471      * @param e the event describing the document change
472      * @param optimize <code>true</code> if partition changes should be
473      * considered for optimization
474      * @return the damaged caused by the change or <code>null</code> if
475      * computing the partitioning failed
476      * @since 3.0
477      */

478     private IRegion getDamage(DocumentEvent e, boolean optimize) {
479         int length= e.getText() == null ? 0 : e.getText().length();
480         
481         if (fDamagers == null || fDamagers.isEmpty()) {
482             length= Math.max(e.getLength(), length);
483             length= Math.min(e.getDocument().getLength() - e.getOffset(), length);
484             return new Region(e.getOffset(), length);
485         }
486
487         boolean isDeletion= length == 0;
488         IRegion damage= null;
489         try {
490             int offset= e.getOffset();
491             if (isDeletion)
492                 offset= Math.max(0, offset - 1);
493             ITypedRegion partition= getPartition(e.getDocument(), offset);
494             IPresentationDamager damager= getDamager(partition.getType());
495             if (damager == null)
496                 return null;
497
498             IRegion r= damager.getDamageRegion(partition, e, fDocumentPartitioningChanged);
499
500             if (!fDocumentPartitioningChanged && optimize && !isDeletion) {
501                 damage= r;
502             } else {
503
504                 int damageEnd= getDamageEndOffset(e);
505
506                 int parititionDamageEnd= -1;
507                 if (fChangedDocumentPartitions != null)
508                     parititionDamageEnd= fChangedDocumentPartitions.getOffset() + fChangedDocumentPartitions.getLength();
509
510                 int end= Math.max(damageEnd, parititionDamageEnd);
511
512                 damage= end == -1 ? r : new Region(r.getOffset(), end - r.getOffset());
513             }
514
515         } catch (BadLocationException x) {
516         }
517
518         return damage;
519     }
520
521     /**
522      * Returns the end offset of the damage. If a partition has been split by
523      * the given document event also the second half of the original
524      * partition must be considered. This is achieved by using the remembered
525      * partition range.
526      *
527      * @param e the event describing the change
528      * @return the damage end offset (excluding)
529      * @exception BadLocationException if method accesses invalid offset
530      */

531     private int getDamageEndOffset(DocumentEvent e) throws BadLocationException {
532
533         IDocument d= e.getDocument();
534
535         int length= 0;
536         if (e.getText() != null) {
537             length= e.getText().length();
538             if (length > 0)
539                 -- length;
540         }
541
542         ITypedRegion partition= getPartition(d, e.getOffset() + length);
543         int endOffset= partition.getOffset() + partition.getLength();
544         if (endOffset == e.getOffset())
545             return -1;
546
547         int end= fRememberedPosition == null ? -1 : fRememberedPosition.getOffset() + fRememberedPosition.getLength();
548         if (endOffset < end && end < d.getLength())
549             partition= getPartition(d, end);
550
551         IPresentationDamager damager= getDamager(partition.getType());
552         if (damager == null)
553             return -1;
554
555         IRegion r= damager.getDamageRegion(partition, e, fDocumentPartitioningChanged);
556
557         return r.getOffset() + r.getLength();
558     }
559
560     /**
561      * Processes the given damage.
562      * @param damage the damage to be repaired
563      * @param document the document whose presentation must be repaired
564      */

565     private void processDamage(IRegion damage, IDocument document) {
566         if (damage != null && damage.getLength() > 0) {
567             TextPresentation p= createPresentation(damage, document);
568             if (p != null)
569                 applyTextRegionCollection(p);
570         }
571     }
572
573     /**
574      * Applies the given text presentation to the text viewer the presentation
575      * reconciler is installed on.
576      *
577      * @param presentation the text presentation to be applied to the text viewer
578      */

579     private void applyTextRegionCollection(TextPresentation presentation) {
580         fViewer.changeTextPresentation(presentation, false);
581     }
582
583     /**
584      * Returns the partition for the given offset in the given document.
585      *
586      * @param document the document
587      * @param offset the offset
588      * @return the partition
589      * @throws BadLocationException if offset is invalid in the given document
590      * @since 3.0
591      */

592     private ITypedRegion getPartition(IDocument document, int offset) throws BadLocationException {
593         return TextUtilities.getPartition(document, getDocumentPartitioning(), offset, false);
594     }
595 }
596
Popular Tags