comparison dwtx/jface/text/source/projection/ProjectionViewer.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
comparison
equal deleted inserted replaced
128:8df1d4193877 129:eb30df5ca28b
1 /*******************************************************************************
2 * Copyright (c) 2000, 2008 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.jface.text.source.projection.ProjectionViewer;
14
15 import dwt.dwthelper.utils;
16
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 import java.util.List;
20
21 import dwt.DWTError;
22 import dwt.custom.ST;
23 import dwt.custom.StyledText;
24 import dwt.dnd.Clipboard;
25 import dwt.dnd.DND;
26 import dwt.dnd.TextTransfer;
27 import dwt.dnd.Transfer;
28 import dwt.events.VerifyEvent;
29 import dwt.graphics.Point;
30 import dwt.widgets.Composite;
31 import dwt.widgets.Display;
32 import dwtx.core.runtime.Assert;
33 import dwtx.jface.text.BadLocationException;
34 import dwtx.jface.text.DocumentEvent;
35 import dwtx.jface.text.FindReplaceDocumentAdapter;
36 import dwtx.jface.text.IDocument;
37 import dwtx.jface.text.IDocumentInformationMappingExtension;
38 import dwtx.jface.text.IDocumentListener;
39 import dwtx.jface.text.IRegion;
40 import dwtx.jface.text.ISlaveDocumentManager;
41 import dwtx.jface.text.ITextViewerExtension5;
42 import dwtx.jface.text.Position;
43 import dwtx.jface.text.Region;
44 import dwtx.jface.text.TextUtilities;
45 import dwtx.jface.text.projection.ProjectionDocument;
46 import dwtx.jface.text.projection.ProjectionDocumentEvent;
47 import dwtx.jface.text.projection.ProjectionDocumentManager;
48 import dwtx.jface.text.source.Annotation;
49 import dwtx.jface.text.source.AnnotationModelEvent;
50 import dwtx.jface.text.source.CompositeRuler;
51 import dwtx.jface.text.source.IAnnotationModel;
52 import dwtx.jface.text.source.IAnnotationModelExtension;
53 import dwtx.jface.text.source.IAnnotationModelListener;
54 import dwtx.jface.text.source.IAnnotationModelListenerExtension;
55 import dwtx.jface.text.source.IOverviewRuler;
56 import dwtx.jface.text.source.IVerticalRuler;
57 import dwtx.jface.text.source.IVerticalRulerColumn;
58 import dwtx.jface.text.source.SourceViewer;
59
60
61 /**
62 * A projection source viewer is a source viewer which supports multiple visible
63 * regions which can dynamically be changed.
64 * <p>
65 * A projection source viewer uses a <code>ProjectionDocumentManager</code>
66 * for the management of the visible document.</p>
67 * <p>
68 * NOTE: The <code>ProjectionViewer</code> only supports projections that cover full lines.
69 * </p>
70 * <p>
71 * This class should not be subclassed.</p>
72 *
73 * @since 3.0
74 * @noextend This class is not intended to be subclassed by clients.
75 */
76 public class ProjectionViewer : SourceViewer , ITextViewerExtension5 {
77
78 private static final int BASE= INFORMATION; // see ISourceViewer.INFORMATION
79
80 /** Operation constant for the expand operation. */
81 public static final int EXPAND= BASE + 1;
82 /** Operation constant for the collapse operation. */
83 public static final int COLLAPSE= BASE + 2;
84 /** Operation constant for the toggle projection operation. */
85 public static final int TOGGLE= BASE + 3;
86 /** Operation constant for the expand all operation. */
87 public static final int EXPAND_ALL= BASE + 4;
88 /**
89 * Operation constant for the collapse all operation.
90 *
91 * @since 3.2
92 */
93 public static final int COLLAPSE_ALL= BASE + 5;
94
95 /**
96 * Internal listener to changes of the annotation model.
97 */
98 private class AnnotationModelListener : IAnnotationModelListener, IAnnotationModelListenerExtension {
99
100 /*
101 * @see dwtx.jface.text.source.IAnnotationModelListener#modelChanged(dwtx.jface.text.source.IAnnotationModel)
102 */
103 public void modelChanged(IAnnotationModel model) {
104 processModelChanged(model, null);
105 }
106
107 /*
108 * @see dwtx.jface.text.source.IAnnotationModelListenerExtension#modelChanged(dwtx.jface.text.source.AnnotationModelEvent)
109 */
110 public void modelChanged(AnnotationModelEvent event) {
111 processModelChanged(event.getAnnotationModel(), event);
112 }
113
114 private void processModelChanged(IAnnotationModel model, AnnotationModelEvent event) {
115 if (model is fProjectionAnnotationModel) {
116
117 if (fProjectionSummary !is null)
118 fProjectionSummary.updateSummaries();
119 processCatchupRequest(event);
120
121 } else if (model is getAnnotationModel() && fProjectionSummary !is null)
122 fProjectionSummary.updateSummaries();
123 }
124 }
125
126 /**
127 * Executes the 'replaceVisibleDocument' operation when called the first time. Self-destructs afterwards.
128 */
129 private class ReplaceVisibleDocumentExecutor : IDocumentListener {
130
131 private IDocument fSlaveDocument;
132 private IDocument fExecutionTrigger;
133
134 /**
135 * Creates a new executor in order to free the given slave document.
136 *
137 * @param slaveDocument the slave document to free
138 */
139 public ReplaceVisibleDocumentExecutor(IDocument slaveDocument) {
140 fSlaveDocument= slaveDocument;
141 }
142
143 /**
144 * Installs this executor on the given trigger document.
145 *
146 * @param executionTrigger the trigger document
147 */
148 public void install(IDocument executionTrigger) {
149 if (executionTrigger !is null && fSlaveDocument !is null) {
150 fExecutionTrigger= executionTrigger;
151 fExecutionTrigger.addDocumentListener(this);
152 }
153 }
154
155 /*
156 * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.jface.text.DocumentEvent)
157 */
158 public void documentAboutToBeChanged(DocumentEvent event) {
159 }
160
161 /*
162 * @see dwtx.jface.text.IDocumentListener#documentChanged(dwtx.jface.text.DocumentEvent)
163 */
164 public void documentChanged(DocumentEvent event) {
165 fExecutionTrigger.removeDocumentListener(this);
166 executeReplaceVisibleDocument(fSlaveDocument);
167 }
168 }
169
170 /**
171 * A command representing a change of the projection document. This can be either
172 * adding a master document range, removing a master document change, or invalidating
173 * the viewer text presentation.
174 */
175 private static class ProjectionCommand {
176
177 final static int ADD= 0;
178 final static int REMOVE= 1;
179 final static int INVALIDATE_PRESENTATION= 2;
180
181 ProjectionDocument fProjection;
182 int fType;
183 int fOffset;
184 int fLength;
185
186 ProjectionCommand(ProjectionDocument projection, int type, int offset, int length) {
187 fProjection= projection;
188 fType= type;
189 fOffset= offset;
190 fLength= length;
191 }
192
193 ProjectionCommand(int offset, int length) {
194 fType= INVALIDATE_PRESENTATION;
195 fOffset= offset;
196 fLength= length;
197 }
198
199 int computeExpectedCosts() {
200
201 switch(fType) {
202 case ADD: {
203 try {
204 IRegion[] gaps= fProjection.computeUnprojectedMasterRegions(fOffset, fLength);
205 return gaps is null ? 0 : gaps.length;
206 } catch (BadLocationException x) {
207 }
208 break;
209 }
210 case REMOVE: {
211 try {
212 IRegion[] fragments= fProjection.computeProjectedMasterRegions(fOffset, fLength);
213 return fragments is null ? 0 : fragments.length;
214 } catch (BadLocationException x) {
215 }
216 break;
217 }
218 }
219 return 0;
220 }
221 }
222
223 /**
224 * The queue of projection command objects.
225 */
226 private static class ProjectionCommandQueue {
227
228 final static int REDRAW_COSTS= 15;
229 final static int INVALIDATION_COSTS= 10;
230
231 List fList= new ArrayList(15);
232 int fExpectedExecutionCosts= -1;
233
234
235 void add(ProjectionCommand command) {
236 fList.add(command);
237 }
238
239 Iterator iterator() {
240 return fList.iterator();
241 }
242
243 void clear() {
244 fList.clear();
245 fExpectedExecutionCosts= -1;
246 }
247
248 bool passedRedrawCostsThreshold() {
249 if (fExpectedExecutionCosts is -1)
250 computeExpectedExecutionCosts();
251 return fExpectedExecutionCosts > REDRAW_COSTS;
252 }
253
254 bool passedInvalidationCostsThreshold() {
255 if (fExpectedExecutionCosts is -1)
256 computeExpectedExecutionCosts();
257 return fExpectedExecutionCosts > INVALIDATION_COSTS;
258 }
259
260 private void computeExpectedExecutionCosts() {
261 int max_costs= Math.max(REDRAW_COSTS, INVALIDATION_COSTS);
262 fExpectedExecutionCosts= fList.size();
263 if (fExpectedExecutionCosts <= max_costs) {
264 ProjectionCommand command;
265 Iterator e= fList.iterator();
266 while (e.hasNext()) {
267 command= (ProjectionCommand) e.next();
268 fExpectedExecutionCosts += command.computeExpectedCosts();
269 if (fExpectedExecutionCosts > max_costs)
270 break;
271 }
272 }
273 }
274 }
275
276 /** The projection annotation model used by this viewer. */
277 private ProjectionAnnotationModel fProjectionAnnotationModel;
278 /** The annotation model listener */
279 private IAnnotationModelListener fAnnotationModelListener= new AnnotationModelListener();
280 /** The projection summary. */
281 private ProjectionSummary fProjectionSummary;
282 /** Indication that an annotation world change has not yet been processed. */
283 private bool fPendingAnnotationWorldChange= false;
284 /** Indication whether projection changes in the visible document should be considered. */
285 private bool fHandleProjectionChanges= true;
286 /** The list of projection listeners. */
287 private List fProjectionListeners;
288 /** Internal lock for protecting the list of pending requests */
289 private Object fLock= new Object();
290 /** The list of pending requests */
291 private List fPendingRequests= new ArrayList();
292 /** The replace-visible-document execution trigger */
293 private IDocument fReplaceVisibleDocumentExecutionTrigger;
294 /** <code>true</code> if projection was on the last time we switched to segmented mode. */
295 private bool fWasProjectionEnabled;
296 /** The queue of projection commands used to assess the costs of projection changes. */
297 private ProjectionCommandQueue fCommandQueue;
298 /**
299 * The amount of lines deleted by the last document event issued by the
300 * visible document event.
301 * @since 3.1
302 */
303 private int fDeletedLines;
304
305
306 /**
307 * Creates a new projection source viewer.
308 *
309 * @param parent the DWT parent control
310 * @param ruler the vertical ruler
311 * @param overviewRuler the overview ruler
312 * @param showsAnnotationOverview <code>true</code> if the overview ruler should be shown
313 * @param styles the DWT style bits
314 */
315 public ProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, bool showsAnnotationOverview, int styles) {
316 super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
317 }
318
319 /**
320 * Sets the projection summary for this viewer.
321 *
322 * @param projectionSummary the projection summary.
323 */
324 public void setProjectionSummary(ProjectionSummary projectionSummary) {
325 fProjectionSummary= projectionSummary;
326 }
327
328 /**
329 * Adds the projection annotation model to the given annotation model.
330 *
331 * @param model the model to which the projection annotation model is added
332 */
333 private void addProjectionAnnotationModel(IAnnotationModel model) {
334 if (model instanceof IAnnotationModelExtension) {
335 IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
336 extension.addAnnotationModel(ProjectionSupport.PROJECTION, fProjectionAnnotationModel);
337 model.addAnnotationModelListener(fAnnotationModelListener);
338 }
339 }
340
341 /**
342 * Removes the projection annotation model from the given annotation model.
343 *
344 * @param model the mode from which the projection annotation model is removed
345 * @return the removed projection annotation model or <code>null</code> if there was none
346 */
347 private IAnnotationModel removeProjectionAnnotationModel(IAnnotationModel model) {
348 if (model instanceof IAnnotationModelExtension) {
349 model.removeAnnotationModelListener(fAnnotationModelListener);
350 IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
351 return extension.removeAnnotationModel(ProjectionSupport.PROJECTION);
352 }
353 return null;
354 }
355
356 /*
357 * @see dwtx.jface.text.source.SourceViewer#setDocument(dwtx.jface.text.IDocument, dwtx.jface.text.source.IAnnotationModel, int, int)
358 */
359 public void setDocument(IDocument document, IAnnotationModel annotationModel, int modelRangeOffset, int modelRangeLength) {
360 bool wasProjectionEnabled= false;
361
362 synchronized (fLock) {
363 fPendingRequests.clear();
364 }
365
366 if (fProjectionAnnotationModel !is null) {
367 wasProjectionEnabled= removeProjectionAnnotationModel(getVisualAnnotationModel()) !is null;
368 fProjectionAnnotationModel= null;
369 }
370
371 super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength);
372
373 if (wasProjectionEnabled && document !is null)
374 enableProjection();
375 }
376
377 /*
378 * @see dwtx.jface.text.source.SourceViewer#createVisualAnnotationModel(dwtx.jface.text.source.IAnnotationModel)
379 */
380 protected IAnnotationModel createVisualAnnotationModel(IAnnotationModel annotationModel) {
381 IAnnotationModel model= super.createVisualAnnotationModel(annotationModel);
382 fProjectionAnnotationModel= new ProjectionAnnotationModel();
383 return model;
384 }
385
386 /**
387 * Returns the projection annotation model.
388 *
389 * @return the projection annotation model
390 */
391 public ProjectionAnnotationModel getProjectionAnnotationModel() {
392 IAnnotationModel model= getVisualAnnotationModel();
393 if (model instanceof IAnnotationModelExtension) {
394 IAnnotationModelExtension extension= (IAnnotationModelExtension) model;
395 return (ProjectionAnnotationModel) extension.getAnnotationModel(ProjectionSupport.PROJECTION);
396 }
397 return null;
398 }
399
400 /*
401 * @see dwtx.jface.text.TextViewer#createSlaveDocumentManager()
402 */
403 protected ISlaveDocumentManager createSlaveDocumentManager() {
404 return new ProjectionDocumentManager();
405 }
406
407 /*
408 * @see dwtx.jface.text.TextViewer#updateSlaveDocument(dwtx.jface.text.IDocument, int, int)
409 */
410 protected bool updateSlaveDocument(IDocument slaveDocument, int modelRangeOffset, int modelRangeLength) throws BadLocationException {
411 if (slaveDocument instanceof ProjectionDocument) {
412 ProjectionDocument projection= (ProjectionDocument) slaveDocument;
413
414 int offset= modelRangeOffset;
415 int length= modelRangeLength;
416
417 if (!isProjectionMode()) {
418 // mimic original TextViewer behavior
419 IDocument master= projection.getMasterDocument();
420 int line= master.getLineOfOffset(modelRangeOffset);
421 offset= master.getLineOffset(line);
422 length= (modelRangeOffset - offset) + modelRangeLength;
423
424 }
425
426 try {
427 fHandleProjectionChanges= false;
428 projection.replaceMasterDocumentRanges(offset, length);
429 } finally {
430 fHandleProjectionChanges= true;
431 }
432 return true;
433 }
434 return false;
435 }
436
437 /**
438 * Adds a projection annotation listener to this viewer. The listener may
439 * not be <code>null</code>. If the listener is already registered, this method
440 * does not have any effect.
441 *
442 * @param listener the listener to add
443 */
444 public void addProjectionListener(IProjectionListener listener) {
445
446 Assert.isNotNull(listener);
447
448 if (fProjectionListeners is null)
449 fProjectionListeners= new ArrayList();
450
451 if (!fProjectionListeners.contains(listener))
452 fProjectionListeners.add(listener);
453 }
454
455 /**
456 * Removes the given listener from this viewer. The listener may not be
457 * <code>null</code>. If the listener is not registered with this viewer,
458 * this method is without effect.
459 *
460 * @param listener the listener to remove
461 */
462 public void removeProjectionListener(IProjectionListener listener) {
463
464 Assert.isNotNull(listener);
465
466 if (fProjectionListeners !is null) {
467 fProjectionListeners.remove(listener);
468 if (fProjectionListeners.size() is 0)
469 fProjectionListeners= null;
470 }
471 }
472
473 /**
474 * Notifies all registered projection listeners
475 * that projection mode has been enabled.
476 */
477 protected void fireProjectionEnabled() {
478 if (fProjectionListeners !is null) {
479 Iterator e= new ArrayList(fProjectionListeners).iterator();
480 while (e.hasNext()) {
481 IProjectionListener l= (IProjectionListener) e.next();
482 l.projectionEnabled();
483 }
484 }
485 }
486
487 /**
488 * Notifies all registered projection listeners
489 * that projection mode has been disabled.
490 */
491 protected void fireProjectionDisabled() {
492 if (fProjectionListeners !is null) {
493 Iterator e= new ArrayList(fProjectionListeners).iterator();
494 while (e.hasNext()) {
495 IProjectionListener l= (IProjectionListener) e.next();
496 l.projectionDisabled();
497 }
498 }
499 }
500
501 /**
502 * Returns whether this viewer is in projection mode.
503 *
504 * @return <code>true</code> if this viewer is in projection mode,
505 * <code>false</code> otherwise
506 */
507 public final bool isProjectionMode() {
508 return getProjectionAnnotationModel() !is null;
509 }
510
511 /**
512 * Disables the projection mode.
513 */
514 public final void disableProjection() {
515 if (isProjectionMode()) {
516 removeProjectionAnnotationModel(getVisualAnnotationModel());
517 fProjectionAnnotationModel.removeAllAnnotations();
518 fFindReplaceDocumentAdapter= null;
519 fireProjectionDisabled();
520 }
521 }
522
523 /**
524 * Enables the projection mode.
525 */
526 public final void enableProjection() {
527 if (!isProjectionMode()) {
528 addProjectionAnnotationModel(getVisualAnnotationModel());
529 fFindReplaceDocumentAdapter= null;
530 fireProjectionEnabled();
531 }
532 }
533
534 private void expandAll() {
535 int offset= 0;
536 IDocument doc= getDocument();
537 int length= doc is null ? 0 : doc.getLength();
538 if (isProjectionMode()) {
539 fProjectionAnnotationModel.expandAll(offset, length);
540 }
541 }
542
543 private void expand() {
544 if (isProjectionMode()) {
545 Position found= null;
546 Annotation bestMatch= null;
547 Point selection= getSelectedRange();
548 for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
549 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
550 if (annotation.isCollapsed()) {
551 Position position= fProjectionAnnotationModel.getPosition(annotation);
552 // take the first most fine grained match
553 if (position !is null && touches(selection, position))
554 if (found is null || position.includes(found.offset) && position.includes(found.offset + found.length)) {
555 found= position;
556 bestMatch= annotation;
557 }
558 }
559 }
560
561 if (bestMatch !is null) {
562 fProjectionAnnotationModel.expand(bestMatch);
563 revealRange(selection.x, selection.y);
564 }
565 }
566 }
567
568 private bool touches(Point selection, Position position) {
569 return position.overlapsWith(selection.x, selection.y) || selection.y is 0 && position.offset + position.length is selection.x + selection.y;
570 }
571
572 private void collapse() {
573 if (isProjectionMode()) {
574 Position found= null;
575 Annotation bestMatch= null;
576 Point selection= getSelectedRange();
577 for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
578 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
579 if (!annotation.isCollapsed()) {
580 Position position= fProjectionAnnotationModel.getPosition(annotation);
581 // take the first most fine grained match
582 if (position !is null && touches(selection, position))
583 if (found is null || found.includes(position.offset) && found.includes(position.offset + position.length)) {
584 found= position;
585 bestMatch= annotation;
586 }
587 }
588 }
589
590 if (bestMatch !is null) {
591 fProjectionAnnotationModel.collapse(bestMatch);
592 revealRange(selection.x, selection.y);
593 }
594 }
595 }
596
597 /*
598 * @since 3.2
599 */
600 private void collapseAll() {
601 int offset= 0;
602 IDocument doc= getDocument();
603 int length= doc is null ? 0 : doc.getLength();
604 if (isProjectionMode()) {
605 fProjectionAnnotationModel.collapseAll(offset, length);
606 }
607 }
608
609 /**
610 * Adds the given master range to the given projection document. While the
611 * modification is processed, the viewer no longer handles projection
612 * changes, as it is causing them.
613 *
614 * @param projection the projection document
615 * @param offset the offset in the master document
616 * @param length the length in the master document
617 * @throws BadLocationException in case the specified range is invalid
618 *
619 * @see ProjectionDocument#addMasterDocumentRange(int, int)
620 */
621 private void addMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException {
622
623 if (fCommandQueue !is null) {
624 fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.ADD, offset, length));
625 } else {
626 try {
627 fHandleProjectionChanges= false;
628 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258
629 // make sure the document range is strictly line based
630 int end= offset + length;
631 offset= toLineStart(projection.getMasterDocument(), offset, false);
632 length= toLineStart(projection.getMasterDocument(), end, true) - offset;
633 projection.addMasterDocumentRange(offset, length);
634 } finally {
635 fHandleProjectionChanges= true;
636 }
637 }
638 }
639
640 /**
641 * Removes the given master range from the given projection document. While the
642 * modification is processed, the viewer no longer handles projection
643 * changes, as it is causing them.
644 *
645 * @param projection the projection document
646 * @param offset the offset in the master document
647 * @param length the length in the master document
648 * @throws BadLocationException in case the specified range is invalid
649 *
650 * @see ProjectionDocument#removeMasterDocumentRange(int, int)
651 */
652 private void removeMasterDocumentRange(ProjectionDocument projection, int offset, int length) throws BadLocationException {
653 if (fCommandQueue !is null) {
654 fCommandQueue.add(new ProjectionCommand(projection, ProjectionCommand.REMOVE, offset, length));
655 } else {
656 try {
657 fHandleProjectionChanges= false;
658 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=108258
659 // make sure the document range is strictly line based
660 int end= offset + length;
661 offset= toLineStart(projection.getMasterDocument(), offset, false);
662 length= toLineStart(projection.getMasterDocument(), end, true) - offset;
663 projection.removeMasterDocumentRange(offset, length);
664 } finally {
665 fHandleProjectionChanges= true;
666 }
667 }
668 }
669
670 /**
671 * Returns the first line offset &lt;= <code>offset</code>. If <code>testLastLine</code>
672 * is <code>true</code> and the offset is on last line then <code>offset</code> is returned.
673 *
674 * @param document the document
675 * @param offset the master document offset
676 * @param testLastLine <code>true</code> if the test for the last line should be performed
677 * @return the closest line offset &gt;= <code>offset</code>
678 * @throws BadLocationException if the offset is invalid
679 * @since 3.2
680 */
681 private int toLineStart(IDocument document, int offset, bool testLastLine) throws BadLocationException {
682 if (document is null)
683 return offset;
684
685 if (testLastLine && offset >= document.getLineInformationOfOffset(document.getLength() - 1).getOffset())
686 return offset;
687
688 return document.getLineInformationOfOffset(offset).getOffset();
689 }
690
691 /*
692 * @see dwtx.jface.text.TextViewer#setVisibleRegion(int, int)
693 */
694 public void setVisibleRegion(int start, int length) {
695 fWasProjectionEnabled= isProjectionMode();
696 disableProjection();
697 super.setVisibleRegion(start, length);
698 }
699
700 /*
701 * @see dwtx.jface.text.TextViewer#setVisibleDocument(dwtx.jface.text.IDocument)
702 */
703 protected void setVisibleDocument(IDocument document) {
704 if (!isProjectionMode()) {
705 super.setVisibleDocument(document);
706 return;
707 }
708
709 // In projection mode we don't want to throw away the find/replace document adapter
710 FindReplaceDocumentAdapter adapter= fFindReplaceDocumentAdapter;
711 super.setVisibleDocument(document);
712 fFindReplaceDocumentAdapter= adapter;
713 }
714
715 /*
716 * @see dwtx.jface.text.TextViewer#resetVisibleRegion()
717 */
718 public void resetVisibleRegion() {
719 super.resetVisibleRegion();
720 if (fWasProjectionEnabled)
721 enableProjection();
722 }
723
724 /*
725 * @see dwtx.jface.text.ITextViewer#getVisibleRegion()
726 */
727 public IRegion getVisibleRegion() {
728 disableProjection();
729 IRegion visibleRegion= getModelCoverage();
730 if (visibleRegion is null)
731 visibleRegion= new Region(0, 0);
732
733 return visibleRegion;
734 }
735
736 /*
737 * @see dwtx.jface.text.ITextViewer#overlapsWithVisibleRegion(int,int)
738 */
739 public bool overlapsWithVisibleRegion(int offset, int length) {
740 disableProjection();
741 IRegion coverage= getModelCoverage();
742 if (coverage is null)
743 return false;
744
745 bool appending= (offset is coverage.getOffset() + coverage.getLength()) && length is 0;
746 return appending || TextUtilities.overlaps(coverage, new Region(offset, length));
747 }
748
749 /**
750 * Replace the visible document with the given document. Maintains the
751 * scroll offset and the selection.
752 *
753 * @param slave the visible document
754 */
755 private void replaceVisibleDocument(IDocument slave) {
756 if (fReplaceVisibleDocumentExecutionTrigger !is null) {
757 ReplaceVisibleDocumentExecutor executor= new ReplaceVisibleDocumentExecutor(slave);
758 executor.install(fReplaceVisibleDocumentExecutionTrigger);
759 } else
760 executeReplaceVisibleDocument(slave);
761 }
762
763
764 private void executeReplaceVisibleDocument(IDocument visibleDocument) {
765 StyledText textWidget= getTextWidget();
766 try {
767 if (textWidget !is null && !textWidget.isDisposed())
768 textWidget.setRedraw(false);
769
770 int topIndex= getTopIndex();
771 Point selection= getSelectedRange();
772 setVisibleDocument(visibleDocument);
773 Point currentSelection= getSelectedRange();
774 if (currentSelection.x !is selection.x || currentSelection.y !is selection.y)
775 setSelectedRange(selection.x, selection.y);
776 setTopIndex(topIndex);
777
778 } finally {
779 if (textWidget !is null && !textWidget.isDisposed())
780 textWidget.setRedraw(true);
781 }
782 }
783
784 /**
785 * Hides the given range by collapsing it. If requested, a redraw request is issued.
786 *
787 * @param offset the offset of the range to hide
788 * @param length the length of the range to hide
789 * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> otherwise
790 * @throws BadLocationException in case the range is invalid
791 */
792 private void collapse(int offset, int length, bool fireRedraw) throws BadLocationException {
793 ProjectionDocument projection= null;
794
795 IDocument visibleDocument= getVisibleDocument();
796 if (visibleDocument instanceof ProjectionDocument)
797 projection= (ProjectionDocument) visibleDocument;
798 else {
799 IDocument master= getDocument();
800 IDocument slave= createSlaveDocument(getDocument());
801 if (slave instanceof ProjectionDocument) {
802 projection= (ProjectionDocument) slave;
803 addMasterDocumentRange(projection, 0, master.getLength());
804 replaceVisibleDocument(projection);
805 }
806 }
807
808 if (projection !is null)
809 removeMasterDocumentRange(projection, offset, length);
810
811 if (projection !is null && fireRedraw) {
812 // repaint line above to get the folding box
813 IDocument document= getDocument();
814 int line= document.getLineOfOffset(offset);
815 if (line > 0) {
816 IRegion info= document.getLineInformation(line - 1);
817 internalInvalidateTextPresentation(info.getOffset(), info.getLength());
818 }
819 }
820 }
821
822 /**
823 * Makes the given range visible again while not changing the folding state of any contained
824 * ranges. If requested, a redraw request is issued.
825 *
826 * @param offset the offset of the range to be expanded
827 * @param length the length of the range to be expanded
828 * @param fireRedraw <code>true</code> if a redraw request should be issued,
829 * <code>false</code> otherwise
830 * @throws BadLocationException in case the range is invalid
831 */
832 private void expand(int offset, int length, bool fireRedraw) throws BadLocationException {
833 IDocument slave= getVisibleDocument();
834 if (slave instanceof ProjectionDocument) {
835 ProjectionDocument projection= (ProjectionDocument) slave;
836
837 // expand
838 addMasterDocumentRange(projection, offset, length);
839
840 // collapse contained regions
841 ProjectionAnnotation[] collapsed= computeCollapsedNestedAnnotations(offset, length);
842 if (collapsed !is null) {
843 for (int i= 0; i < collapsed.length; i++) {
844 IRegion[] regions= computeCollapsedRegions(fProjectionAnnotationModel.getPosition(collapsed[i]));
845 if (regions !is null)
846 for (int j= 0; j < regions.length; j++)
847 removeMasterDocumentRange(projection, regions[j].getOffset(), regions[j].getLength());
848 }
849 }
850
851 // redraw if requested
852 if (fireRedraw)
853 internalInvalidateTextPresentation(offset, length);
854 }
855 }
856
857 /**
858 * Processes the request for catch up with the annotation model in the UI thread. If the current
859 * thread is not the UI thread or there are pending catch up requests, a new request is posted.
860 *
861 * @param event the annotation model event
862 */
863 protected final void processCatchupRequest(AnnotationModelEvent event) {
864 if (Display.getCurrent() !is null) {
865 bool run= false;
866 synchronized (fLock) {
867 run= fPendingRequests.isEmpty();
868 }
869 if (run) {
870
871 try {
872 catchupWithProjectionAnnotationModel(event);
873 } catch (BadLocationException x) {
874 throw new IllegalArgumentException();
875 }
876
877 } else
878 postCatchupRequest(event);
879 } else {
880 postCatchupRequest(event);
881 }
882 }
883
884 /**
885 * Posts the request for catch up with the annotation model into the UI thread.
886 *
887 * @param event the annotation model event
888 */
889 protected final void postCatchupRequest(final AnnotationModelEvent event) {
890 synchronized (fLock) {
891 fPendingRequests.add(event);
892 if (fPendingRequests.size() is 1) {
893 StyledText widget= getTextWidget();
894 if (widget !is null) {
895 Display display= widget.getDisplay();
896 if (display !is null) {
897 display.asyncExec(new Runnable() {
898 public void run() {
899 try {
900 while (true) {
901 AnnotationModelEvent ame= null;
902 synchronized (fLock) {
903 if (fPendingRequests.size() is 0)
904 return;
905 ame= (AnnotationModelEvent) fPendingRequests.remove(0);
906 }
907 catchupWithProjectionAnnotationModel(ame);
908 }
909 } catch (BadLocationException x) {
910 try {
911 catchupWithProjectionAnnotationModel(null);
912 } catch (BadLocationException x1) {
913 throw new IllegalArgumentException();
914 } finally {
915 synchronized (fLock) {
916 fPendingRequests.clear();
917 }
918 }
919 }
920 }
921 });
922 }
923 }
924 }
925 }
926 }
927
928 /**
929 * Tests whether the visible document's master document
930 * is identical to this viewer's document.
931 *
932 * @return <code>true</code> if the visible document's master is
933 * identical to this viewer's document
934 * @since 3.1
935 */
936 private bool isVisibleMasterDocumentSameAsDocument() {
937 IDocument visibleDocument= getVisibleDocument();
938 return (visibleDocument instanceof ProjectionDocument) && ((ProjectionDocument)visibleDocument).getMasterDocument() is getDocument();
939 }
940
941 /**
942 * Adapts the slave visual document of this viewer to the changes described
943 * in the annotation model event. When the event is <code>null</code>,
944 * this is identical to a world change event.
945 *
946 * @param event the annotation model event or <code>null</code>
947 * @exception BadLocationException in case the annotation model event is no longer in synchronization with the document
948 */
949 private void catchupWithProjectionAnnotationModel(AnnotationModelEvent event) throws BadLocationException {
950
951 if (event is null || !isVisibleMasterDocumentSameAsDocument()) {
952
953 fPendingAnnotationWorldChange= false;
954 reinitializeProjection();
955
956 } else if (event.isWorldChange()) {
957
958 if (event.isValid()) {
959 fPendingAnnotationWorldChange= false;
960 reinitializeProjection();
961 } else
962 fPendingAnnotationWorldChange= true;
963
964 } else if (fPendingAnnotationWorldChange) {
965 if (event.isValid()) {
966 fPendingAnnotationWorldChange= false;
967 reinitializeProjection();
968 }
969 } else {
970
971 Annotation[] addedAnnotations= event.getAddedAnnotations();
972 Annotation[] changedAnnotation= event.getChangedAnnotations();
973 Annotation[] removedAnnotations= event.getRemovedAnnotations();
974
975 fCommandQueue= new ProjectionCommandQueue();
976
977 bool isRedrawing= redraws();
978 int topIndex= isRedrawing ? getTopIndex() : -1;
979
980 processDeletions(event, removedAnnotations, true);
981 List coverage= new ArrayList();
982 processChanges(addedAnnotations, true, coverage);
983 processChanges(changedAnnotation, true, coverage);
984
985 ProjectionCommandQueue commandQueue= fCommandQueue;
986 fCommandQueue= null;
987
988 if (commandQueue.passedRedrawCostsThreshold()) {
989 setRedraw(false);
990 try {
991 executeProjectionCommands(commandQueue, false);
992 } catch (IllegalArgumentException x) {
993 reinitializeProjection();
994 } finally {
995 setRedraw(true, topIndex);
996 }
997 } else {
998 try {
999 bool fireRedraw= !commandQueue.passedInvalidationCostsThreshold();
1000 executeProjectionCommands(commandQueue, fireRedraw);
1001 if (!fireRedraw)
1002 invalidateTextPresentation();
1003 } catch (IllegalArgumentException x) {
1004 reinitializeProjection();
1005 }
1006 }
1007 }
1008 }
1009
1010 private void executeProjectionCommands(ProjectionCommandQueue commandQueue, bool fireRedraw) throws BadLocationException {
1011
1012 ProjectionCommand command;
1013 Iterator e= commandQueue.iterator();
1014 while (e.hasNext()) {
1015 command= (ProjectionCommand) e.next();
1016 switch (command.fType) {
1017 case ProjectionCommand.ADD:
1018 addMasterDocumentRange(command.fProjection, command.fOffset, command.fLength);
1019 break;
1020 case ProjectionCommand.REMOVE:
1021 removeMasterDocumentRange(command.fProjection, command.fOffset, command.fLength);
1022 break;
1023 case ProjectionCommand.INVALIDATE_PRESENTATION:
1024 if (fireRedraw)
1025 invalidateTextPresentation(command.fOffset, command.fLength);
1026 break;
1027 }
1028 }
1029
1030 commandQueue.clear();
1031 }
1032
1033 private bool covers(int offset, int length, Position position) {
1034 if (!(position.offset is offset && position.length is length) && !position.isDeleted())
1035 return offset <= position.getOffset() && position.getOffset() + position.getLength() <= offset + length;
1036 return false;
1037 }
1038
1039 private ProjectionAnnotation[] computeCollapsedNestedAnnotations(int offset, int length) {
1040 List annotations= new ArrayList(5);
1041 Iterator e= fProjectionAnnotationModel.getAnnotationIterator();
1042 while (e.hasNext()) {
1043 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
1044 if (annotation.isCollapsed()) {
1045 Position position= fProjectionAnnotationModel.getPosition(annotation);
1046 if (position is null) {
1047 // annotation might already be deleted, we will be informed later on about this deletion
1048 continue;
1049 }
1050 if (covers(offset, length, position))
1051 annotations.add(annotation);
1052 }
1053 }
1054
1055 if (annotations.size() > 0) {
1056 ProjectionAnnotation[] result= new ProjectionAnnotation[annotations.size()];
1057 annotations.toArray(result);
1058 return result;
1059 }
1060
1061 return null;
1062 }
1063
1064 private void internalInvalidateTextPresentation(int offset, int length) {
1065 if (fCommandQueue !is null) {
1066 fCommandQueue.add(new ProjectionCommand(offset, length));
1067 } else {
1068 invalidateTextPresentation(offset, length);
1069 }
1070 }
1071
1072 /*
1073 * We pass the removed annotation into this method for performance reasons only. Otherwise, they could be fetch from the event.
1074 */
1075 private void processDeletions(AnnotationModelEvent event, Annotation[] removedAnnotations, bool fireRedraw) throws BadLocationException {
1076 for (int i= 0; i < removedAnnotations.length; i++) {
1077 ProjectionAnnotation annotation= (ProjectionAnnotation) removedAnnotations[i];
1078 if (annotation.isCollapsed()) {
1079 Position expanded= event.getPositionOfRemovedAnnotation(annotation);
1080 expand(expanded.getOffset(), expanded.getLength(), fireRedraw);
1081 }
1082 }
1083 }
1084
1085 /**
1086 * Computes the region that must be collapsed when the given position is the
1087 * position of an expanded projection annotation.
1088 *
1089 * @param position the position
1090 * @return the range that must be collapsed
1091 */
1092 public IRegion computeCollapsedRegion(Position position) {
1093 try {
1094 IDocument document= getDocument();
1095 if (document is null)
1096 return null;
1097
1098 int line= document.getLineOfOffset(position.getOffset());
1099 int offset= document.getLineOffset(line + 1);
1100
1101 int length= position.getLength() - (offset - position.getOffset());
1102 if (length > 0)
1103 return new Region(offset, length);
1104 } catch (BadLocationException x) {
1105 }
1106
1107 return null;
1108 }
1109
1110 /**
1111 * Computes the regions that must be collapsed when the given position is
1112 * the position of an expanded projection annotation.
1113 *
1114 * @param position the position
1115 * @return the ranges that must be collapsed, or <code>null</code> if
1116 * there are none
1117 * @since 3.1
1118 */
1119 IRegion[] computeCollapsedRegions(Position position) {
1120 try {
1121 IDocument document= getDocument();
1122 if (document is null)
1123 return null;
1124
1125 if (position instanceof IProjectionPosition) {
1126 IProjectionPosition projPosition= (IProjectionPosition) position;
1127 return projPosition.computeProjectionRegions(document);
1128 }
1129
1130 int line= document.getLineOfOffset(position.getOffset());
1131 int offset= document.getLineOffset(line + 1);
1132
1133 int length= position.getLength() - (offset - position.getOffset());
1134 if (length > 0)
1135 return new IRegion[] {new Region(offset, length)};
1136
1137 return null;
1138 } catch (BadLocationException x) {
1139 return null;
1140 }
1141 }
1142
1143 /**
1144 * Computes the collapsed region anchor for the given position. Assuming
1145 * that the position is the position of an expanded projection annotation,
1146 * the anchor is the region that is still visible after the projection
1147 * annotation has been collapsed.
1148 *
1149 * @param position the position
1150 * @return the collapsed region anchor
1151 */
1152 public Position computeCollapsedRegionAnchor(Position position) {
1153 try {
1154 IDocument document= getDocument();
1155 if (document is null)
1156 return null;
1157
1158 int captionOffset= position.getOffset();
1159 if (position instanceof IProjectionPosition)
1160 captionOffset+= ((IProjectionPosition) position).computeCaptionOffset(document);
1161
1162 IRegion lineInfo= document.getLineInformationOfOffset(captionOffset);
1163 return new Position(lineInfo.getOffset() + lineInfo.getLength(), 0);
1164 } catch (BadLocationException x) {
1165 }
1166 return null;
1167 }
1168
1169 private void processChanges(Annotation[] annotations, bool fireRedraw, List coverage) throws BadLocationException {
1170 for (int i= 0; i < annotations.length; i++) {
1171 ProjectionAnnotation annotation= (ProjectionAnnotation) annotations[i];
1172 Position position= fProjectionAnnotationModel.getPosition(annotation);
1173
1174 if (position is null)
1175 continue;
1176
1177 if (!covers(coverage, position)) {
1178 if (annotation.isCollapsed()) {
1179 coverage.add(position);
1180 IRegion[] regions= computeCollapsedRegions(position);
1181 if (regions !is null)
1182 for (int j= 0; j < regions.length; j++)
1183 collapse(regions[j].getOffset(), regions[j].getLength(), fireRedraw);
1184 } else {
1185 expand(position.getOffset(), position.getLength(), fireRedraw);
1186 }
1187 }
1188 }
1189 }
1190
1191 private bool covers(List coverage, Position position) {
1192 Iterator e= coverage.iterator();
1193 while (e.hasNext()) {
1194 Position p= (Position) e.next();
1195 if (p.getOffset() <= position.getOffset() && position.getOffset() + position.getLength() <= p.getOffset() + p.getLength())
1196 return true;
1197 }
1198 return false;
1199 }
1200
1201 /**
1202 * Forces this viewer to throw away any old state and to initialize its content
1203 * from its projection annotation model.
1204 *
1205 * @throws BadLocationException in case something goes wrong during initialization
1206 */
1207 public final void reinitializeProjection() throws BadLocationException {
1208
1209 ProjectionDocument projection= null;
1210
1211 ISlaveDocumentManager manager= getSlaveDocumentManager();
1212 if (manager !is null) {
1213 IDocument master= getDocument();
1214 if (master !is null) {
1215 IDocument slave= manager.createSlaveDocument(master);
1216 if (slave instanceof ProjectionDocument) {
1217 projection= (ProjectionDocument) slave;
1218 addMasterDocumentRange(projection, 0, master.getLength());
1219 }
1220 }
1221 }
1222
1223 if (projection !is null) {
1224 Iterator e= fProjectionAnnotationModel.getAnnotationIterator();
1225 while (e.hasNext()) {
1226 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
1227 if (annotation.isCollapsed()) {
1228 Position position= fProjectionAnnotationModel.getPosition(annotation);
1229 if (position !is null) {
1230 IRegion[] regions= computeCollapsedRegions(position);
1231 if (regions !is null)
1232 for (int i= 0; i < regions.length; i++)
1233 removeMasterDocumentRange(projection, regions[i].getOffset(), regions[i].getLength());
1234 }
1235 }
1236 }
1237
1238 }
1239
1240 replaceVisibleDocument(projection);
1241 }
1242
1243 /*
1244 * @see dwtx.jface.text.TextViewer#handleVerifyEvent(dwt.events.VerifyEvent)
1245 */
1246 protected void handleVerifyEvent(VerifyEvent e) {
1247 IRegion modelRange= event2ModelRange(e);
1248 if (exposeModelRange(modelRange))
1249 e.doit= false;
1250 else
1251 super.handleVerifyEvent(e);
1252 }
1253
1254 /**
1255 * Adds the give column as last column to this viewer's vertical ruler.
1256 *
1257 * @param column the column to be added
1258 */
1259 public void addVerticalRulerColumn(IVerticalRulerColumn column) {
1260 IVerticalRuler ruler= getVerticalRuler();
1261 if (ruler instanceof CompositeRuler) {
1262 CompositeRuler compositeRuler= (CompositeRuler) ruler;
1263 compositeRuler.addDecorator(99, column);
1264 }
1265 }
1266
1267 /**
1268 * Removes the give column from this viewer's vertical ruler.
1269 *
1270 * @param column the column to be removed
1271 */
1272 public void removeVerticalRulerColumn(IVerticalRulerColumn column) {
1273 IVerticalRuler ruler= getVerticalRuler();
1274 if (ruler instanceof CompositeRuler) {
1275 CompositeRuler compositeRuler= (CompositeRuler) ruler;
1276 compositeRuler.removeDecorator(column);
1277 }
1278 }
1279
1280 /*
1281 * @see dwtx.jface.text.ITextViewerExtension5#exposeModelRange(dwtx.jface.text.IRegion)
1282 */
1283 public bool exposeModelRange(IRegion modelRange) {
1284 if (isProjectionMode())
1285 return fProjectionAnnotationModel.expandAll(modelRange.getOffset(), modelRange.getLength());
1286
1287 if (!overlapsWithVisibleRegion(modelRange.getOffset(), modelRange.getLength())) {
1288 resetVisibleRegion();
1289 return true;
1290 }
1291
1292 return false;
1293 }
1294
1295 /*
1296 * @see dwtx.jface.text.source.SourceViewer#setRangeIndication(int, int, bool)
1297 */
1298 public void setRangeIndication(int offset, int length, bool moveCursor) {
1299 IRegion rangeIndication= getRangeIndication();
1300 if (moveCursor && fProjectionAnnotationModel !is null && (rangeIndication is null || offset !is rangeIndication.getOffset() || length !is rangeIndication.getLength())) {
1301 List expand= new ArrayList(2);
1302 // expand the immediate effected collapsed regions
1303 Iterator iterator= fProjectionAnnotationModel.getAnnotationIterator();
1304 while (iterator.hasNext()) {
1305 ProjectionAnnotation annotation= (ProjectionAnnotation)iterator.next();
1306 if (annotation.isCollapsed() && willAutoExpand(fProjectionAnnotationModel.getPosition(annotation), offset, length))
1307 expand.add(annotation);
1308 }
1309
1310 if (!expand.isEmpty()) {
1311 Iterator e= expand.iterator();
1312 while (e.hasNext())
1313 fProjectionAnnotationModel.expand((Annotation)e.next());
1314 }
1315 }
1316 super.setRangeIndication(offset, length, moveCursor);
1317 }
1318
1319 private bool willAutoExpand(Position position, int offset, int length) {
1320 if (position is null || position.isDeleted())
1321 return false;
1322 // right or left boundary
1323 if (position.getOffset() is offset || position.getOffset() + position.getLength() is offset + length)
1324 return true;
1325 // completely embedded in given position
1326 if (position.getOffset() < offset && offset + length < position.getOffset() + position.getLength())
1327 return true;
1328 return false;
1329 }
1330
1331 /*
1332 * @see dwtx.jface.text.source.SourceViewer#handleDispose()
1333 * @since 3.0
1334 */
1335 protected void handleDispose() {
1336 fWasProjectionEnabled= false;
1337 super.handleDispose();
1338 }
1339
1340 /*
1341 * @see dwtx.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(dwtx.jface.text.DocumentEvent)
1342 */
1343 protected void handleVisibleDocumentChanged(DocumentEvent event) {
1344 if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) {
1345 ProjectionDocumentEvent e= (ProjectionDocumentEvent) event;
1346
1347 DocumentEvent master= e.getMasterEvent();
1348 if (master !is null)
1349 fReplaceVisibleDocumentExecutionTrigger= master.getDocument();
1350
1351 try {
1352
1353 int replaceLength= e.getText() is null ? 0 : e.getText().length();
1354 if (ProjectionDocumentEvent.PROJECTION_CHANGE is e.getChangeType()) {
1355 if (e.getLength() is 0 && replaceLength !is 0)
1356 fProjectionAnnotationModel.expandAll(e.getMasterOffset(), e.getMasterLength());
1357 } else if (master !is null && (replaceLength > 0 || fDeletedLines > 1)) {
1358 try {
1359 int numberOfLines= e.getDocument().getNumberOfLines(e.getOffset(), replaceLength);
1360 if (numberOfLines > 1 || fDeletedLines > 1)
1361 fProjectionAnnotationModel.expandAll(master.getOffset(), master.getLength());
1362 } catch (BadLocationException x) {
1363 }
1364 }
1365
1366 } finally {
1367 fReplaceVisibleDocumentExecutionTrigger= null;
1368 }
1369
1370 }
1371 }
1372
1373 /*
1374 * @see dwtx.jface.text.TextViewer#handleVisibleDocumentAboutToBeChanged(dwtx.jface.text.DocumentEvent)
1375 * @since 3.1
1376 */
1377 protected void handleVisibleDocumentAboutToBeChanged(DocumentEvent event) {
1378 if (fHandleProjectionChanges && event instanceof ProjectionDocumentEvent && isProjectionMode()) {
1379 int deletedLines;
1380 try {
1381 deletedLines= event.getDocument().getNumberOfLines(event.getOffset(), event.getLength());
1382 } catch (BadLocationException e1) {
1383 deletedLines= 0;
1384 }
1385 fDeletedLines= deletedLines;
1386 }
1387 }
1388
1389 /*
1390 * @see dwtx.jface.text.ITextViewerExtension5#getCoveredModelRanges(dwtx.jface.text.IRegion)
1391 */
1392 public IRegion[] getCoveredModelRanges(IRegion modelRange) {
1393 if (fInformationMapping is null)
1394 return new IRegion[] { new Region(modelRange.getOffset(), modelRange.getLength()) };
1395
1396 if (fInformationMapping instanceof IDocumentInformationMappingExtension) {
1397 IDocumentInformationMappingExtension extension= (IDocumentInformationMappingExtension) fInformationMapping;
1398 try {
1399 return extension.getExactCoverage(modelRange);
1400 } catch (BadLocationException x) {
1401 }
1402 }
1403
1404 return null;
1405 }
1406
1407 /*
1408 * @see dwtx.jface.text.ITextOperationTarget#doOperation(int)
1409 */
1410 public void doOperation(int operation) {
1411 switch (operation) {
1412 case TOGGLE:
1413 if (canDoOperation(TOGGLE)) {
1414 if (!isProjectionMode()) {
1415 enableProjection();
1416 } else {
1417 expandAll();
1418 disableProjection();
1419 }
1420 return;
1421 }
1422 }
1423
1424 if (!isProjectionMode()) {
1425 super.doOperation(operation);
1426 return;
1427 }
1428
1429 StyledText textWidget= getTextWidget();
1430 if (textWidget is null)
1431 return;
1432
1433 Point selection= null;
1434 switch (operation) {
1435
1436 case CUT:
1437
1438 if (redraws()) {
1439 selection= getSelectedRange();
1440 if (exposeModelRange(new Region(selection.x, selection.y)))
1441 return;
1442
1443 if (selection.y is 0)
1444 copyMarkedRegion(true);
1445 else
1446 copyToClipboard(selection.x, selection.y, true, textWidget);
1447
1448 selection= textWidget.getSelectionRange();
1449 fireSelectionChanged(selection.x, selection.y);
1450 }
1451 break;
1452
1453 case COPY:
1454
1455 if (redraws()) {
1456 selection= getSelectedRange();
1457 if (selection.y is 0)
1458 copyMarkedRegion(false);
1459 else
1460 copyToClipboard(selection.x, selection.y, false, textWidget);
1461 }
1462 break;
1463
1464 case DELETE:
1465
1466 if (redraws()) {
1467 try {
1468 selection= getSelectedRange();
1469 Point widgetSelection= textWidget.getSelectionRange();
1470 if (selection.y is 0 || selection.y is widgetSelection.y)
1471 getTextWidget().invokeAction(ST.DELETE_NEXT);
1472 else
1473 deleteTextRange(selection.x, selection.y, textWidget);
1474
1475 selection= textWidget.getSelectionRange();
1476 fireSelectionChanged(selection.x, selection.y);
1477
1478 } catch (BadLocationException x) {
1479 // ignore
1480 }
1481 }
1482 break;
1483
1484
1485 case EXPAND_ALL:
1486 if (redraws())
1487 expandAll();
1488 break;
1489
1490 case EXPAND:
1491 if (redraws()) {
1492 expand();
1493 }
1494 break;
1495
1496 case COLLAPSE_ALL:
1497 if (redraws())
1498 collapseAll();
1499 break;
1500
1501 case COLLAPSE:
1502 if (redraws()) {
1503 collapse();
1504 }
1505 break;
1506
1507 default:
1508 super.doOperation(operation);
1509 }
1510 }
1511
1512 /*
1513 * @see dwtx.jface.text.source.SourceViewer#canDoOperation(int)
1514 */
1515 public bool canDoOperation(int operation) {
1516
1517 switch (operation) {
1518 case COLLAPSE:
1519 case COLLAPSE_ALL:
1520 case EXPAND:
1521 case EXPAND_ALL:
1522 return isProjectionMode();
1523 case TOGGLE:
1524 return isProjectionMode() || !isSegmented();
1525 }
1526
1527 return super.canDoOperation(operation);
1528 }
1529
1530 private bool isSegmented() {
1531 IDocument document= getDocument();
1532 int length= document is null ? 0 : document.getLength();
1533 IRegion visible= getModelCoverage();
1534 bool isSegmented= visible !is null && !visible.equals(new Region(0, length));
1535 return isSegmented;
1536 }
1537
1538 private IRegion getMarkedRegion() {
1539 if (getTextWidget() is null)
1540 return null;
1541
1542 if (fMarkPosition is null || fMarkPosition.isDeleted())
1543 return null;
1544
1545 int start= fMarkPosition.getOffset();
1546 int end= getSelectedRange().x;
1547
1548 return start > end ? new Region (end, start - end) : new Region(start, end - start);
1549 }
1550
1551 /*
1552 * @see dwtx.jface.text.TextViewer#copyMarkedRegion(bool)
1553 */
1554 protected void copyMarkedRegion(bool delete) {
1555 IRegion markedRegion= getMarkedRegion();
1556 if (markedRegion !is null)
1557 copyToClipboard(markedRegion.getOffset(), markedRegion.getLength(), delete, getTextWidget());
1558 }
1559
1560 private void copyToClipboard(int offset, int length, bool delete, StyledText textWidget) {
1561
1562 String copyText= null;
1563
1564 try {
1565 IDocument document= getDocument();
1566 copyText= document.get(offset, length);
1567 } catch (BadLocationException ex) {
1568 // XXX: should log here, but JFace Text has no Log
1569 // As a fallback solution let the widget handle this
1570 textWidget.copy();
1571 }
1572
1573 if (copyText !is null && copyText.equals(textWidget.getSelectionText())) {
1574 /*
1575 * XXX: Reduce pain of https://bugs.eclipse.org/bugs/show_bug.cgi?id=64498
1576 * by letting the widget handle the copy operation in this special case.
1577 */
1578 textWidget.copy();
1579 } else if (copyText !is null) {
1580
1581 Clipboard clipboard= new Clipboard(textWidget.getDisplay());
1582
1583 try {
1584 Transfer[] dataTypes= new Transfer[] { TextTransfer.getInstance() };
1585 Object[] data= new Object[] { copyText };
1586 try {
1587 clipboard.setContents(data, dataTypes);
1588 } catch (DWTError e) {
1589 if (e.code !is DND.ERROR_CANNOT_SET_CLIPBOARD)
1590 throw e;
1591 /*
1592 * TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59459
1593 * we should either log and/or inform the user
1594 * silently fail for now.
1595 */
1596 return;
1597 }
1598
1599 } finally {
1600 clipboard.dispose();
1601 }
1602 }
1603
1604 if (delete) {
1605 try {
1606 deleteTextRange(offset, length, textWidget);
1607 } catch (BadLocationException x) {
1608 // XXX: should log here, but JFace Text has no Log
1609 }
1610 }
1611 }
1612
1613 private void deleteTextRange(int offset, int length, StyledText textWidget) throws BadLocationException {
1614 getDocument().replace(offset, length, ""); //$NON-NLS-1$
1615 int widgetCaret= modelOffset2WidgetOffset(offset);
1616 if (widgetCaret > -1)
1617 textWidget.setSelection(widgetCaret);
1618 }
1619
1620 /**
1621 * Adapts the behavior of the super class to respect line based folding.
1622 *
1623 * @param widgetSelection the widget selection
1624 * @return the model selection while respecting line based folding
1625 */
1626 protected Point widgetSelection2ModelSelection(Point widgetSelection) {
1627
1628 if (!isProjectionMode())
1629 return super.widgetSelection2ModelSelection(widgetSelection);
1630
1631 /*
1632 * There is one requirement that governs preservation of logical
1633 * positions:
1634 *
1635 * 1) a selection with widget_length is 0 should never expand to have
1636 * model_length > 0.
1637 *
1638 * There are a number of ambiguities to resolve with projection regions.
1639 * A projected region P has a widget-length of zero. Its widget offset
1640 * may interact with the selection S in various ways:
1641 *
1642 * A) P.widget_offset lies at the caret, S.widget_length is zero.
1643 * Requirement 1 applies. S is *behind* P (done so by widgetRange2ModelRange).
1644 *
1645 * B) P.widget_offset lies inside the widget selection. This case is
1646 * easy: P is included in S, which is automatically done so by
1647 * widgetRange2ModelRange.
1648 *
1649 * C) P.widget_offset lies at S.widget_end: This is
1650 * arguable - our policy is to include P if it belongs to a projection
1651 * annotation that overlaps with the widget selection.
1652 *
1653 * D) P.widget_offset lies at S.widget_offset: Arguable - our policy
1654 * is to include P if it belongs to a projection annotation that
1655 * overlaps with the widget selection
1656 */
1657 IRegion modelSelection= widgetRange2ModelRange(new Region(widgetSelection.x, widgetSelection.y));
1658 if (modelSelection is null)
1659 return null;
1660
1661 int modelOffset= modelSelection.getOffset();
1662 int modelEndOffset= modelOffset + modelSelection.getLength();
1663
1664 /* Case A: never expand a zero-length selection. S is *behind* P. */
1665 if (widgetSelection.y is 0)
1666 return new Point(modelEndOffset, 0);
1667
1668 int widgetSelectionExclusiveEnd= widgetSelection.x + widgetSelection.y;
1669 Position[] annotationPositions= computeOverlappingAnnotationPositions(modelSelection);
1670 for (int i= 0; i < annotationPositions.length; i++) {
1671 IRegion[] regions= computeCollapsedRegions(annotationPositions[i]);
1672 if (regions is null)
1673 continue;
1674 for (int j= 0; j < regions.length; j++) {
1675 IRegion modelRange= regions[j];
1676 IRegion widgetRange= modelRange2ClosestWidgetRange(modelRange);
1677 // only take collapsed ranges, i.e. widget length is 0
1678 if (widgetRange !is null && widgetRange.getLength() is 0) {
1679 int widgetOffset= widgetRange.getOffset();
1680 // D) region is collapsed at S.widget_offset
1681 if (widgetOffset is widgetSelection.x)
1682 modelOffset= Math.min(modelOffset, modelRange.getOffset());
1683 // C) region is collapsed at S.widget_end
1684 else if (widgetOffset is widgetSelectionExclusiveEnd)
1685 modelEndOffset= Math.max(modelEndOffset, modelRange.getOffset() + modelRange.getLength());
1686 }
1687 }
1688 }
1689 return new Point(modelOffset, modelEndOffset - modelOffset);
1690 }
1691
1692 /**
1693 * Returns the positions of all annotations that intersect with
1694 * <code>modelSelection</code> and that are at least partly visible.
1695 * @param modelSelection a model range
1696 * @return the positions of all annotations that intersect with
1697 * <code>modelSelection</code>
1698 * @since 3.1
1699 */
1700 private Position[] computeOverlappingAnnotationPositions(IRegion modelSelection) {
1701 List positions= new ArrayList();
1702 for (Iterator e= fProjectionAnnotationModel.getAnnotationIterator(); e.hasNext();) {
1703 ProjectionAnnotation annotation= (ProjectionAnnotation) e.next();
1704 Position position= fProjectionAnnotationModel.getPosition(annotation);
1705 if (position !is null && position.overlapsWith(modelSelection.getOffset(), modelSelection.getLength()) && modelRange2WidgetRange(position) !is null)
1706 positions.add(position);
1707 }
1708 return (Position[]) positions.toArray(new Position[positions.size()]);
1709 }
1710
1711 /*
1712 * @see dwtx.jface.text.TextViewer#getFindReplaceDocumentAdapter()
1713 */
1714 protected FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() {
1715 if (fFindReplaceDocumentAdapter is null) {
1716 IDocument document= isProjectionMode() ? getDocument() : getVisibleDocument();
1717 fFindReplaceDocumentAdapter= new FindReplaceDocumentAdapter(document);
1718 }
1719 return fFindReplaceDocumentAdapter;
1720 }
1721
1722 /*
1723 * @see dwtx.jface.text.TextViewer#findAndSelect(int, java.lang.String, bool, bool, bool, bool)
1724 */
1725 protected int findAndSelect(int startPosition, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, bool regExSearch) {
1726
1727 if (!isProjectionMode())
1728 return super.findAndSelect(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
1729
1730 StyledText textWidget= getTextWidget();
1731 if (textWidget is null)
1732 return -1;
1733
1734 try {
1735
1736 IRegion matchRegion= getFindReplaceDocumentAdapter().find(startPosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
1737 if (matchRegion !is null) {
1738 exposeModelRange(matchRegion);
1739 revealRange(matchRegion.getOffset(), matchRegion.getLength());
1740 setSelectedRange(matchRegion.getOffset(), matchRegion.getLength());
1741 return matchRegion.getOffset();
1742 }
1743
1744 } catch (BadLocationException x) {
1745 }
1746
1747 return -1;
1748 }
1749
1750 /*
1751 * @see dwtx.jface.text.TextViewer#findAndSelectInRange(int, java.lang.String, bool, bool, bool, int, int, bool)
1752 */
1753 protected int findAndSelectInRange(int startPosition, String findString, bool forwardSearch, bool caseSensitive, bool wholeWord, int rangeOffset, int rangeLength, bool regExSearch) {
1754
1755 if (!isProjectionMode())
1756 return super.findAndSelectInRange(startPosition, findString, forwardSearch, caseSensitive, wholeWord, rangeOffset, rangeLength, regExSearch);
1757
1758 StyledText textWidget= getTextWidget();
1759 if (textWidget is null)
1760 return -1;
1761
1762 try {
1763
1764 int modelOffset= startPosition;
1765 if (forwardSearch && (startPosition is -1 || startPosition < rangeOffset)) {
1766 modelOffset= rangeOffset;
1767 } else if (!forwardSearch && (startPosition is -1 || startPosition > rangeOffset + rangeLength)) {
1768 modelOffset= rangeOffset + rangeLength;
1769 }
1770
1771 IRegion matchRegion= getFindReplaceDocumentAdapter().find(modelOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch);
1772 if (matchRegion !is null) {
1773 int offset= matchRegion.getOffset();
1774 int length= matchRegion.getLength();
1775 if (rangeOffset <= offset && offset + length <= rangeOffset + rangeLength) {
1776 exposeModelRange(matchRegion);
1777 revealRange(offset, length);
1778 setSelectedRange(offset, length);
1779 return offset;
1780 }
1781 }
1782
1783 } catch (BadLocationException x) {
1784 }
1785
1786 return -1;
1787 }
1788 }