Mercurial > projects > dwt-addons
diff dwtx/jface/text/projection/ProjectionDocument.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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/projection/ProjectionDocument.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,908 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.text.projection.ProjectionDocument; + +import dwt.dwthelper.utils; + +import java.util.ArrayList; +import java.util.List; + +import dwtx.jface.text.AbstractDocument; +import dwtx.jface.text.BadLocationException; +import dwtx.jface.text.BadPositionCategoryException; +import dwtx.jface.text.DefaultLineTracker; +import dwtx.jface.text.DocumentEvent; +import dwtx.jface.text.IDocument; +import dwtx.jface.text.IDocumentExtension; +import dwtx.jface.text.IDocumentInformationMapping; +import dwtx.jface.text.IDocumentListener; +import dwtx.jface.text.ILineTracker; +import dwtx.jface.text.IRegion; +import dwtx.jface.text.ITextStore; +import dwtx.jface.text.Position; +import dwtx.jface.text.Region; +import dwtx.jface.text.TextUtilities; + + +/** + * A <code>ProjectionDocument</code> represents a projection of its master + * document. The contents of a projection document is a sequence of fragments of + * the master document, i.e. the projection document can be thought as being + * constructed from the master document by not copying the whole master document + * but omitting several ranges of the master document. + * <p> + * The projection document indirectly utilizes its master document as + * <code>ITextStore</code> by means of a <code>ProjectionTextStore</code>. + * <p> + * The content of a projection document can be changed in two ways. Either by a + * text replace applied to the master document or the projection document. Or by + * changing the projection between the master document and the projection + * document. For the latter the two methods <code>addMasterDocumentRange</code> + * and <code>removeMasterDocumentRange</code> are provided. For any + * manipulation, the projection document sends out a + * {@link dwtx.jface.text.projection.ProjectionDocumentEvent} describing + * the change. + * <p> + * Clients are not supposed to directly instantiate this class. In order to + * obtain a projection document, a + * {@link dwtx.jface.text.projection.ProjectionDocumentManager}should be + * used. This class is not intended to be subclassed outside of its origin + * package.</p> + * + * @since 3.0 + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + */ +public class ProjectionDocument : AbstractDocument { + + + /** + * Prefix of the name of the position category used to keep track of the master + * document's fragments that correspond to the segments of the projection + * document. + */ + private final static String FRAGMENTS_CATEGORY_PREFIX= "__fragmentsCategory"; //$NON-NLS-1$ + + /** + * Name of the position category used to keep track of the project + * document's segments that correspond to the fragments of the master + * document. + */ + private final static String SEGMENTS_CATEGORY= "__segmentsCategory"; //$NON-NLS-1$ + + + /** The master document */ + private IDocument fMasterDocument; + /** The master document as document extension */ + private IDocumentExtension fMasterDocumentExtension; + /** The fragments' position category */ + private String fFragmentsCategory; + /** The segment's position category */ + private String fSegmentsCategory; + /** The document event issued by the master document */ + private DocumentEvent fMasterEvent; + /** The document event to be issued by the projection document */ + private ProjectionDocumentEvent fSlaveEvent; + /** The original document event generated by a direct manipulation of this projection document */ + private DocumentEvent fOriginalEvent; + /** Indicates whether the projection document initiated a master document update or not */ + private bool fIsUpdating= false; + /** Indicated whether the projection document is in auto expand mode nor not */ + private bool fIsAutoExpanding= false; + /** The position updater for the segments */ + private SegmentUpdater fSegmentUpdater; + /** The position updater for the fragments */ + private FragmentUpdater fFragmentsUpdater; + /** The projection mapping */ + private ProjectionMapping fMapping; + + /** + * Creates a projection document for the given master document. + * + * @param masterDocument the master document + */ + public ProjectionDocument(IDocument masterDocument) { + super(); + + fMasterDocument= masterDocument; + if (fMasterDocument instanceof IDocumentExtension) + fMasterDocumentExtension= (IDocumentExtension) fMasterDocument; + + fSegmentsCategory= SEGMENTS_CATEGORY; + fFragmentsCategory= FRAGMENTS_CATEGORY_PREFIX + hashCode(); + fMasterDocument.addPositionCategory(fFragmentsCategory); + fFragmentsUpdater= new FragmentUpdater(fFragmentsCategory); + fMasterDocument.addPositionUpdater(fFragmentsUpdater); + + fMapping= new ProjectionMapping(masterDocument, fFragmentsCategory, this, fSegmentsCategory); + + ITextStore s= new ProjectionTextStore(masterDocument, fMapping); + ILineTracker tracker= new DefaultLineTracker(); + + setTextStore(s); + setLineTracker(tracker); + + completeInitialization(); + + initializeProjection(); + tracker.set(s.get(0, s.getLength())); + } + + /** + * Disposes this projection document. + */ + public void dispose() { + fMasterDocument.removePositionUpdater(fFragmentsUpdater); + try { + fMasterDocument.removePositionCategory(fFragmentsCategory); + } catch (BadPositionCategoryException x) { + // allow multiple dispose calls + } + } + + private void internalError() { + throw new IllegalStateException(); + } + + /** + * Returns the fragments of the master documents. + * + * @return the fragment of the master document + */ + protected final Position[] getFragments() { + try { + return fMasterDocument.getPositions(fFragmentsCategory); + } catch (BadPositionCategoryException e) { + internalError(); + } + // unreachable + return null; + } + + /** + * Returns the segments of this projection document. + * + * @return the segments of this projection document + */ + protected final Position[] getSegments() { + try { + return getPositions(fSegmentsCategory); + } catch (BadPositionCategoryException e) { + internalError(); + } + // unreachable + return null; + } + + /** + * Returns the projection mapping used by this document. + * + * @return the projection mapping used by this document + * @deprecated As of 3.4, replaced by {@link #getDocumentInformationMapping()} + */ + public ProjectionMapping getProjectionMapping(){ + return fMapping; + } + + /** + * Returns the projection mapping used by this document. + * + * @return the projection mapping used by this document + * @since 3.4 + */ + public IDocumentInformationMapping getDocumentInformationMapping() { + return fMapping; + } + + /** + * Returns the master document of this projection document. + * + * @return the master document of this projection document + */ + public IDocument getMasterDocument() { + return fMasterDocument; + } + + /* + * @see dwtx.jface.text.IDocumentExtension4#getDefaultLineDelimiter() + * @since 3.1 + */ + public String getDefaultLineDelimiter() { + return TextUtilities.getDefaultLineDelimiter(fMasterDocument); + } + + /** + * Initializes the projection document from the master document based on + * the master's fragments. + */ + private void initializeProjection() { + + try { + + addPositionCategory(fSegmentsCategory); + fSegmentUpdater= new SegmentUpdater(fSegmentsCategory); + addPositionUpdater(fSegmentUpdater); + + int offset= 0; + Position[] fragments= getFragments(); + for (int i= 0; i < fragments.length; i++) { + Fragment fragment= (Fragment) fragments[i]; + Segment segment= new Segment(offset, fragment.getLength()); + segment.fragment= fragment; + addPosition(fSegmentsCategory, segment); + offset += fragment.length; + } + + } catch (BadPositionCategoryException x) { + internalError(); + } catch (BadLocationException x) { + internalError(); + } + } + + /** + * Creates a segment for the given fragment at the given position inside the list of segments. + * + * @param fragment the corresponding fragment + * @param index the index in the list of segments + * @return the created segment + * @throws BadLocationException in case the fragment is invalid + * @throws BadPositionCategoryException in case the segment category is invalid + */ + private Segment createSegmentFor(Fragment fragment, int index) throws BadLocationException, BadPositionCategoryException { + + int offset= 0; + if (index > 0) { + Position[] segments= getSegments(); + Segment segment= (Segment) segments[index - 1]; + offset= segment.getOffset() + segment.getLength(); + } + + Segment segment= new Segment(offset, 0); + segment.fragment= fragment; + fragment.segment= segment; + addPosition(fSegmentsCategory, segment); + return segment; + } + + /** + * Adds the given range of the master document to this projection document. + * + * @param offsetInMaster offset of the master document range + * @param lengthInMaster length of the master document range + * @param masterDocumentEvent the master document event that causes this + * projection change or <code>null</code> if none + * @throws BadLocationException if the given range is invalid in the master + * document + */ + private void internalAddMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException { + if (lengthInMaster is 0) + return; + + try { + + Position[] fragments= getFragments(); + int index= fMasterDocument.computeIndexInCategory(fFragmentsCategory, offsetInMaster); + + Fragment left= null; + Fragment right= null; + + if (index < fragments.length) { + Fragment fragment= (Fragment) fragments[index]; + if (offsetInMaster is fragment.offset) + if (fragment.length is 0) // the fragment does not overlap - it is a zero-length fragment at the same offset + left= fragment; + else + throw new IllegalArgumentException("overlaps with existing fragment"); //$NON-NLS-1$ + if (offsetInMaster + lengthInMaster is fragment.offset) + right= fragment; + } + + if (0 < index && index <= fragments.length) { + Fragment fragment= (Fragment) fragments[index - 1]; + if (fragment.includes(offsetInMaster)) + throw new IllegalArgumentException("overlaps with existing fragment"); //$NON-NLS-1$ + if (fragment.getOffset() + fragment.getLength() is offsetInMaster) + left= fragment; + } + + int offsetInSlave= 0; + if (index > 0) { + Fragment fragment= (Fragment) fragments[index - 1]; + Segment segment= fragment.segment; + offsetInSlave= segment.getOffset() + segment.getLength(); + } + + ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, offsetInSlave, 0, fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster, masterDocumentEvent); + super.fireDocumentAboutToBeChanged(event); + + // check for neighboring fragment + if (left !is null && right !is null) { + + int endOffset= right.getOffset() + right.getLength(); + left.setLength(endOffset - left.getOffset()); + left.segment.setLength(left.segment.getLength() + right.segment.getLength()); + + removePosition(fSegmentsCategory, right.segment); + fMasterDocument.removePosition(fFragmentsCategory, right); + + } else if (left !is null) { + int endOffset= offsetInMaster +lengthInMaster; + left.setLength(endOffset - left.getOffset()); + left.segment.markForStretch(); + + } else if (right !is null) { + right.setOffset(right.getOffset() - lengthInMaster); + right.setLength(right.getLength() + lengthInMaster); + right.segment.markForStretch(); + + } else { + // create a new segment + Fragment fragment= new Fragment(offsetInMaster, lengthInMaster); + fMasterDocument.addPosition(fFragmentsCategory, fragment); + Segment segment= createSegmentFor(fragment, index); + segment.markForStretch(); + } + + getTracker().replace(event.getOffset(), event.getLength(), event.getText()); + super.fireDocumentChanged(event); + + } catch (BadPositionCategoryException x) { + internalError(); + } + } + + /** + * Finds the fragment of the master document that represents the given range. + * + * @param offsetInMaster the offset of the range in the master document + * @param lengthInMaster the length of the range in the master document + * @return the fragment representing the given master document range + */ + private Fragment findFragment(int offsetInMaster, int lengthInMaster) { + Position[] fragments= getFragments(); + for (int i= 0; i < fragments.length; i++) { + Fragment f= (Fragment) fragments[i]; + if (f.getOffset() <= offsetInMaster && offsetInMaster + lengthInMaster <= f.getOffset() + f.getLength()) + return f; + } + return null; + } + + /** + * Removes the given range of the master document from this projection + * document. + * + * @param offsetInMaster the offset of the range in the master document + * @param lengthInMaster the length of the range in the master document + * + * @throws BadLocationException if the given range is not valid in the + * master document + * @throws IllegalArgumentException if the given range is not projected in + * this projection document or is not completely comprised by + * an existing fragment + */ + private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException { + try { + + IRegion imageRegion= fMapping.toExactImageRegion(new Region(offsetInMaster, lengthInMaster)); + if (imageRegion is null) + throw new IllegalArgumentException(); + + Fragment fragment= findFragment(offsetInMaster, lengthInMaster); + if (fragment is null) + throw new IllegalArgumentException(); + + ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), "", offsetInMaster, lengthInMaster); //$NON-NLS-1$ + super.fireDocumentAboutToBeChanged(event); + + if (fragment.getOffset() is offsetInMaster) { + fragment.setOffset(offsetInMaster + lengthInMaster); + fragment.setLength(fragment.getLength() - lengthInMaster); + } else if (fragment.getOffset() + fragment.getLength() is offsetInMaster + lengthInMaster) { + fragment.setLength(fragment.getLength() - lengthInMaster); + } else { + // split fragment into three fragments, let position updater remove it + + // add fragment for the region to be removed + Fragment newFragment= new Fragment(offsetInMaster, lengthInMaster); + Segment segment= new Segment(imageRegion.getOffset(), imageRegion.getLength()); + newFragment.segment= segment; + segment.fragment= newFragment; + fMasterDocument.addPosition(fFragmentsCategory, newFragment); + addPosition(fSegmentsCategory, segment); + + // add fragment for the remainder right of the deleted range in the original fragment + int offset= offsetInMaster + lengthInMaster; + newFragment= new Fragment(offset, fragment.getOffset() + fragment.getLength() - offset); + offset= imageRegion.getOffset() + imageRegion.getLength(); + segment= new Segment(offset, fragment.segment.getOffset() + fragment.segment.getLength() - offset); + newFragment.segment= segment; + segment.fragment= newFragment; + fMasterDocument.addPosition(fFragmentsCategory, newFragment); + addPosition(fSegmentsCategory, segment); + + // adjust length of initial fragment (the left one) + fragment.setLength(offsetInMaster - fragment.getOffset()); + fragment.segment.setLength(imageRegion.getOffset() - fragment.segment.getOffset()); + } + + getTracker().replace(event.getOffset(), event.getLength(), event.getText()); + super.fireDocumentChanged(event); + + } catch (BadPositionCategoryException x) { + internalError(); + } + } + + /** + * Returns the sequence of all master document regions which are contained + * in the given master document range and which are not yet part of this + * projection document. + * + * @param offsetInMaster the range offset in the master document + * @param lengthInMaster the range length in the master document + * @return the sequence of regions which are not yet part of the projection + * document + * @throws BadLocationException in case the given range is invalid in the + * master document + */ + public final IRegion[] computeUnprojectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException { + + IRegion[] fragments= null; + IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster)); + if (imageRegion !is null) + fragments= fMapping.toExactOriginRegions(imageRegion); + + if (fragments is null || fragments.length is 0) + return new IRegion[] { new Region(offsetInMaster, lengthInMaster) }; + + List gaps= new ArrayList(); + + IRegion region= fragments[0]; + if (offsetInMaster < region.getOffset()) + gaps.add(new Region(offsetInMaster, region.getOffset() - offsetInMaster)); + + for (int i= 0; i < fragments.length - 1; i++) { + IRegion left= fragments[i]; + IRegion right= fragments[i + 1]; + int leftEnd= left.getOffset() + left.getLength(); + if (leftEnd < right.getOffset()) + gaps.add(new Region(leftEnd, right.getOffset() - leftEnd)); + } + + region= fragments[fragments.length - 1]; + int leftEnd= region.getOffset() + region.getLength(); + int rightEnd= offsetInMaster + lengthInMaster; + if (leftEnd < rightEnd) + gaps.add(new Region(leftEnd, rightEnd - leftEnd)); + + IRegion[] result= new IRegion[gaps.size()]; + gaps.toArray(result); + return result; + } + + /** + * Returns the first master document region which is contained in the given + * master document range and which is not yet part of this projection + * document. + * + * @param offsetInMaster the range offset in the master document + * @param lengthInMaster the range length in the master document + * @return the first region that is not yet part of the projection document + * @throws BadLocationException in case the given range is invalid in the + * master document + * @since 3.1 + */ + private IRegion computeFirstUnprojectedMasterRegion(int offsetInMaster, int lengthInMaster) throws BadLocationException { + + IRegion[] fragments= null; + IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster)); + if (imageRegion !is null) + fragments= fMapping.toExactOriginRegions(imageRegion); + + if (fragments is null || fragments.length is 0) + return new Region(offsetInMaster, lengthInMaster); + + IRegion region= fragments[0]; + if (offsetInMaster < region.getOffset()) + return new Region(offsetInMaster, region.getOffset() - offsetInMaster); + + for (int i= 0; i < fragments.length - 1; i++) { + IRegion left= fragments[i]; + IRegion right= fragments[i + 1]; + int leftEnd= left.getOffset() + left.getLength(); + if (leftEnd < right.getOffset()) + return new Region(leftEnd, right.getOffset() - leftEnd); + } + + region= fragments[fragments.length - 1]; + int leftEnd= region.getOffset() + region.getLength(); + int rightEnd= offsetInMaster + lengthInMaster; + if (leftEnd < rightEnd) + return new Region(leftEnd, rightEnd - leftEnd); + + return null; + } + + /** + * Ensures that the given range of the master document is part of this + * projection document. + * + * @param offsetInMaster the offset of the master document range + * @param lengthInMaster the length of the master document range + * @throws BadLocationException in case the master event is not valid + */ + public void addMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException { + addMasterDocumentRange(offsetInMaster, lengthInMaster, null); + } + + /** + * Ensures that the given range of the master document is part of this + * projection document. + * + * @param offsetInMaster the offset of the master document range + * @param lengthInMaster the length of the master document range + * @param masterDocumentEvent the master document event which causes this + * projection change, or <code>null</code> if none + * @throws BadLocationException in case the master event is not valid + */ + private void addMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException { + /* + * Calling internalAddMasterDocumentRange may cause other master ranges + * to become unfolded, resulting in re-entrant calls to this method. In + * order to not add a region twice, we have to compute the next region + * to add in every iteration. + * + * To place an upper bound on the number of iterations, we use the number + * of fragments * 2 as the limit. + */ + int limit= Math.max(getFragments().length * 2, 20); + while (true) { + if (limit-- < 0) + throw new IllegalArgumentException("safety loop termination"); //$NON-NLS-1$ + + IRegion gap= computeFirstUnprojectedMasterRegion(offsetInMaster, lengthInMaster); + if (gap is null) + return; + + internalAddMasterDocumentRange(gap.getOffset(), gap.getLength(), masterDocumentEvent); + } + } + + /** + * Ensures that the given range of the master document is not part of this + * projection document. + * + * @param offsetInMaster the offset of the master document range + * @param lengthInMaster the length of the master document range + * @throws BadLocationException in case the master event is not valid + */ + public void removeMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException { + IRegion[] fragments= computeProjectedMasterRegions(offsetInMaster, lengthInMaster); + if (fragments is null || fragments.length is 0) + return; + + for (int i= 0; i < fragments.length; i++) { + IRegion fragment= fragments[i]; + internalRemoveMasterDocumentRange(fragment.getOffset(), fragment.getLength()); + } + } + + /** + * Returns the sequence of all master document regions with are contained in the given master document + * range and which are part of this projection document. May return <code>null</code> if no such + * regions exist. + * + * @param offsetInMaster the range offset in the master document + * @param lengthInMaster the range length in the master document + * @return the sequence of regions which are part of the projection document or <code>null</code> + * @throws BadLocationException in case the given range is invalid in the master document + */ + public final IRegion[] computeProjectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException { + IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster)); + return imageRegion !is null ? fMapping.toExactOriginRegions(imageRegion) : null; + } + + /** + * Returns whether this projection is being updated. + * + * @return <code>true</code> if the document is updating + */ + protected bool isUpdating() { + return fIsUpdating; + } + + /* + * @see dwtx.jface.text.IDocument#replace(int, int, java.lang.String) + */ + public void replace(int offset, int length, String text) throws BadLocationException { + try { + fIsUpdating= true; + if (fMasterDocumentExtension !is null) + fMasterDocumentExtension.stopPostNotificationProcessing(); + + super.replace(offset, length, text); + + } finally { + fIsUpdating= false; + if (fMasterDocumentExtension !is null) + fMasterDocumentExtension.resumePostNotificationProcessing(); + } + } + + /* + * @see dwtx.jface.text.IDocument#set(java.lang.String) + */ + public void set(String text) { + try { + fIsUpdating= true; + if (fMasterDocumentExtension !is null) + fMasterDocumentExtension.stopPostNotificationProcessing(); + + super.set(text); + + } finally { + fIsUpdating= false; + if (fMasterDocumentExtension !is null) + fMasterDocumentExtension.resumePostNotificationProcessing(); + } + } + + /** + * Transforms a document event of the master document into a projection + * document based document event. + * + * @param masterEvent the master document event + * @return the slave document event + * @throws BadLocationException in case the master event is not valid + */ + private ProjectionDocumentEvent normalize(DocumentEvent masterEvent) throws BadLocationException { + if (!isUpdating()) { + IRegion imageRegion= fMapping.toExactImageRegion(new Region(masterEvent.getOffset(), masterEvent.getLength())); + if (imageRegion !is null) + return new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), masterEvent.getText(), masterEvent); + return null; + } + + ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, fOriginalEvent.getOffset(), fOriginalEvent.getLength(), fOriginalEvent.getText(), masterEvent); + fOriginalEvent= null; + return event; + } + + /** + * Ensures that when the master event affects this projection document, that the whole region described by the + * event is part of this projection document. + * + * @param masterEvent the master document event + * @return <code>true</code> if masterEvent affects this projection document + * @throws BadLocationException in case the master event is not valid + */ + protected final bool adaptProjectionToMasterChange(DocumentEvent masterEvent) throws BadLocationException { + if (!isUpdating() && fFragmentsUpdater.affectsPositions(masterEvent) || fIsAutoExpanding && masterEvent.getLength() > 0) { + + addMasterDocumentRange(masterEvent.getOffset(), masterEvent.getLength(), masterEvent); + return true; + + } else if (fMapping.getImageLength() is 0 && masterEvent.getLength() is 0) { + + Position[] fragments= getFragments(); + if (fragments.length is 0) { + // there is no segment in this projection document, thus one must be created + // need to bypass the usual infrastructure as the new segment/fragment would be of length 0 and thus the segmentation be not well formed + try { + Fragment fragment= new Fragment(0, 0); + fMasterDocument.addPosition(fFragmentsCategory, fragment); + createSegmentFor(fragment, 0); + } catch (BadPositionCategoryException x) { + internalError(); + } + } + } + + return isUpdating(); + } + + /** + * When called, this projection document is informed about a forthcoming + * change of its master document. This projection document checks whether + * the master document change affects it and if so informs all document + * listeners. + * + * @param masterEvent the master document event + */ + public void masterDocumentAboutToBeChanged(DocumentEvent masterEvent) { + try { + + bool assertNotNull= adaptProjectionToMasterChange(masterEvent); + fSlaveEvent= normalize(masterEvent); + if (assertNotNull && fSlaveEvent is null) + internalError(); + + fMasterEvent= masterEvent; + if (fSlaveEvent !is null) + delayedFireDocumentAboutToBeChanged(); + + } catch (BadLocationException e) { + internalError(); + } + } + + /** + * When called, this projection document is informed about a change of its + * master document. If this projection document is affected it informs all + * of its document listeners. + * + * @param masterEvent the master document event + */ + public void masterDocumentChanged(DocumentEvent masterEvent) { + if ( !isUpdating() && masterEvent is fMasterEvent) { + if (fSlaveEvent !is null) { + try { + getTracker().replace(fSlaveEvent.getOffset(), fSlaveEvent.getLength(), fSlaveEvent.getText()); + fireDocumentChanged(fSlaveEvent); + } catch (BadLocationException e) { + internalError(); + } + } else if (ensureWellFormedSegmentation(masterEvent.getOffset())) + fMapping.projectionChanged(); + } + } + + /* + * @see dwtx.jface.text.AbstractDocument#fireDocumentAboutToBeChanged(dwtx.jface.text.DocumentEvent) + */ + protected void fireDocumentAboutToBeChanged(DocumentEvent event) { + fOriginalEvent= event; + // delay it until there is a notification from the master document + // at this point, it is expensive to construct the master document information + } + + /** + * Fires the slave document event as about-to-be-changed event to all registered listeners. + */ + private void delayedFireDocumentAboutToBeChanged() { + super.fireDocumentAboutToBeChanged(fSlaveEvent); + } + + /** + * Ignores the given event and sends the semantically equal slave document event instead. + * + * @param event the event to be ignored + */ + protected void fireDocumentChanged(DocumentEvent event) { + super.fireDocumentChanged(fSlaveEvent); + } + + /* + * @see dwtx.jface.text.AbstractDocument#updateDocumentStructures(dwtx.jface.text.DocumentEvent) + */ + protected void updateDocumentStructures(DocumentEvent event) { + super.updateDocumentStructures(event); + ensureWellFormedSegmentation(computeAnchor(event)); + fMapping.projectionChanged(); + } + + private int computeAnchor(DocumentEvent event) { + if (event instanceof ProjectionDocumentEvent) { + ProjectionDocumentEvent slave= (ProjectionDocumentEvent) event; + Object changeType= slave.getChangeType(); + if (ProjectionDocumentEvent.CONTENT_CHANGE is changeType) { + DocumentEvent master= slave.getMasterEvent(); + if (master !is null) + return master.getOffset(); + } else if (ProjectionDocumentEvent.PROJECTION_CHANGE is changeType) { + return slave.getMasterOffset(); + } + } + return -1; + } + + private bool ensureWellFormedSegmentation(int anchorOffset) { + bool changed= false; + Position[] segments= getSegments(); + for (int i= 0; i < segments.length; i++) { + Segment segment= (Segment) segments[i]; + if (segment.isDeleted() || segment.getLength() is 0) { + try { + removePosition(fSegmentsCategory, segment); + fMasterDocument.removePosition(fFragmentsCategory, segment.fragment); + changed= true; + } catch (BadPositionCategoryException e) { + internalError(); + } + } else if (i < segments.length - 1) { + Segment next= (Segment) segments[i + 1]; + if (next.isDeleted() || next.getLength() is 0) + continue; + Fragment fragment= segment.fragment; + if (fragment.getOffset() + fragment.getLength() is next.fragment.getOffset()) { + // join fragments and their corresponding segments + segment.setLength(segment.getLength() + next.getLength()); + fragment.setLength(fragment.getLength() + next.fragment.getLength()); + next.delete(); + } + } + } + + if (changed && anchorOffset !is -1) { + Position[] changedSegments= getSegments(); + if (changedSegments is null || changedSegments.length is 0) { + Fragment fragment= new Fragment(anchorOffset, 0); + try { + fMasterDocument.addPosition(fFragmentsCategory, fragment); + createSegmentFor(fragment, 0); + } catch (BadLocationException e) { + internalError(); + } catch (BadPositionCategoryException e) { + internalError(); + } + } + } + + return changed; + } + + /* + * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace) + */ + public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) { + if (!isUpdating()) + throw new UnsupportedOperationException(); + super.registerPostNotificationReplace(owner, replace); + } + + /** + * Sets the auto expand mode for this document. + * + * @param autoExpandMode <code>true</code> if auto-expanding + */ + public void setAutoExpandMode(bool autoExpandMode) { + fIsAutoExpanding= autoExpandMode; + } + + /** + * Replaces all master document ranges with the given master document range. + * + * @param offsetInMaster the offset in the master document + * @param lengthInMaster the length in the master document + * @throws BadLocationException if the given range of the master document is not valid + */ + public void replaceMasterDocumentRanges(int offsetInMaster, int lengthInMaster) throws BadLocationException { + try { + + ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, 0, fMapping.getImageLength(), fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster); + super.fireDocumentAboutToBeChanged(event); + + Position[] fragments= getFragments(); + for (int i= 0; i < fragments.length; i++) { + Fragment fragment= (Fragment) fragments[i]; + fMasterDocument.removePosition(fFragmentsCategory, fragment); + removePosition(fSegmentsCategory, fragment.segment); + } + + Fragment fragment= new Fragment(offsetInMaster, lengthInMaster); + Segment segment= new Segment(0, 0); + segment.fragment= fragment; + fragment.segment= segment; + fMasterDocument.addPosition(fFragmentsCategory, fragment); + addPosition(fSegmentsCategory, segment); + + getTracker().set(fMasterDocument.get(offsetInMaster, lengthInMaster)); + super.fireDocumentChanged(event); + + } catch (BadPositionCategoryException x) { + internalError(); + } + } +}