All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.text.TextStream Maven / Gradle / Ivy

There is a newer version: 2024.11.18598.20241113T125352Z-241000
Show newest version
package com.adobe.xfa.text;

import java.util.List;

import com.adobe.xfa.font.FontService;
import com.adobe.xfa.text.markup.MarkupIn;
import com.adobe.xfa.text.markup.MarkupOut;
import com.adobe.xfa.text.markup.MarkupText;
import com.adobe.xfa.ut.Storage;
import com.adobe.xfa.ut.UniCharIterator;

/**
 * A text stream is a container of rich text, embedded fields and other
 * objects.  Class TextStream embodies this functionality, but does
 * not support rendering directly.	There are two branches from
 * TextStream in the inheritance tree: one for streams that can be
 * rendered (TextDispStr), and one for fields (TextField).
 *
 * @exclude from published api -- Mike Tardif, May 2006.
 */
public class TextStream extends TextLegacy {
	public final static TextStream DEFAULT_STREAM = new TextStream();

	private final static int UPDATE_NORMAL = 0x00;
	private final static int UPDATE_SUPPRESS_RECONCILE = 0x01;
	private final static int UPDATE_KEEP_TEMP_ATTR = 0x02;
	private final static int UPDATE_FULL_SUPPRESS = 0x03; // "or" of above two
	private final static int UPDATE_SUPPRESS_DISPLAY = 0x04;

	private final static int UPDATE_POSN_FORCE = 0;
	private final static int UPDATE_POSN_NORMAL = 1;
	private final static int UPDATE_POSN_TIGHTEN = 2;

// Private data.
	private final Storage moItems = new Storage();
	private TextPosn mpoAssociated;		// Associated position list
	private TextGfxSource moGfxSource;	// Source of Gfx attributes
	private int mnCount;				// Total item count
	private TextDisplay mpoDisplay; 	// Display connection
	private TextRange mpoTempAttr;		// Temporary attr start/end
	private int mnMaxSize;				// Maximum number of chars/embeds
	private boolean mbAllowNewLines;	// Allow new-lines?
	private int mnSuppressAutoLoad;
// TODO: Consider carrying a TextPosnBase obj, rather than newing all over the place

/**
 * Default constructor.
 * 

* The text stream contains no content and has no pool/mapping * assocotiation. */ public TextStream () { initialize (null); } /** * Copy constructor with graphics source information. *

* Copy all stream content from the source stream, using optional * graphic attribute pool. * @param oSource Source text stream to copy content from. * @param poPool Graphic attribute pool to use. */ public TextStream (TextStream oSource, TextGfxSource poPool) { moGfxSource = poPool; initialize (null); LoadText oLoader = new LoadText (oSource, moGfxSource); loadText (oLoader); } public TextStream (TextStream oSource) { moGfxSource = null; initialize (null); LoadText oLoader = new LoadText (oSource, moGfxSource); loadText (oLoader); } /** * Constructor with source text string. *

* Create a text stream whose initial content is copied from the given * string. The text stream initially has no attribute pool association. * @param sSource String whose contents are to be copied to the text * stream. */ public TextStream (String sSource) { initialize (sSource); } /** * Destructor. *

* Deletes all objects owned by the stream, including internal * representation of text, as well as embedded fields and objects. If * the stream holds references on pooled objects, those reference counts * are decremented. */ // public void finalize () { // TODO: implement proper detach mechanism // if (Display() != null) { // Display().Detach (this); // } // // TextPosn poPosn = mpoAssociated; // while (poPosn != null) { // TextPosn poCleanup = poPosn; // poPosn = poPosn.mpoNext; // poCleanup.Cleanup(); // poCleanup.mpoNext = null; // } // ClearItemArray (moItems); // } static TextStream defaultStream () { return DEFAULT_STREAM; } /** * Enumerate embeded objects in the stream. *

* Populates an array with pointers to all embedded objects in the * stream. This is a flat enumeration (i.e., it doesn't descend into * embedded fields looking for embedded objects). These embedded * objects remain property of the text stream and must not be deleted by * the caller. * @param oEmbeds Array to contain the resultant set of pointers. The * array size is set to zero before repopulating. */ public void enumEmbed (List oEmbeds) { oEmbeds.clear(); for (StrItem strItem : moItems) { strItem.addEmbed (oEmbeds); } } /** * Enumerate embeded fields in the stream. *

* Populates an array with pointers to all embedded fields in the * stream. This is a flat enumeration (i.e., it doesn't descend into * those embedded fields looking for further embedded fields). These * embedded fields remain property of the text stream and must not be * deleted by the caller. The array size is set to zero before * repopulating. * @param oFields Array to contain the resultant set of pointers. The * array size is set to zero before repopulating. */ public void enumField (List oFields) { oFields.clear(); for (StrItem strItem : moItems) { strItem.addField (oFields); } } /** * Enumerate all markers on the stream. *

* Populates an array with pointers to all markers on the stream. This * is a flat enumeration (i.e., it doesn't descend into embedded fields * looking for additional markers). These markers remain property of * the text stream and must not be deleted by the caller, though the * client can cache pointers to these markers provide that it * participates in the marker reference counting mechanism. The array * size is set to zero before repopulating. *

* @param oMarkers - Array to contain pointers to the enumerated * markers. Any previous contents are removed by the call. Even though * the returned array contains non-const pointers to markers, those * markers are owned by AXTE and must not be deleted by the client. * @param bPositionMarkers - True (default) if all position markers are * to be included in the array. False if position markers are to be * excluded. * @param bRangeMarkers - True (default) if all range markers are to be * included in the array. False if range markers are to be excluded. */ public void enumMarker (List oMarkers, boolean bPositionMarkers, boolean bRangeMarkers) { TextRange oRange = new TextRange (this); oRange.enumerateMarkers (oMarkers, bPositionMarkers, bRangeMarkers, false); } /** * Obtain the raw (unformatted) text of the stream. *

* Return a string containing all the text in the stream. Optionally * descend into embedded fields when constructing the string. * @param bIncludeFields (default TRUE) Indicates that the text * retrieval is to recurse into embedded fields, if TRUE. Otherwise, * only the text of the specified stream is returned. */ public String text (boolean bIncludeFields) { MarkupText oMarkup = new MarkupText(); markup (oMarkup, null, false, bIncludeFields); return oMarkup.getText(); } public String text () { return text (false); } /** * Return a set of ranges corresponding to the chunks of raw text in the * stream. *

* This method returns an array of text ranges. Each such range * corresponds to one chunk of contiguous raw text (i.e., between * attributes, embedded objects and fields) in the stream. * @param oRanges Resultant array of text ranges. For some reason, * this is not emptied before repopulating. */ public void contiguousText (List oRanges) { TextPosn oStart = new TextPosn (this); TextPosn oEnd = new TextPosn (oStart); int eItem; for (eItem = oEnd.next(); ; eItem = oEnd.next()) { if ((eItem != TextItem.CHAR) && (eItem != TextItem.ATTR)) { int nEnd = oEnd.index(); if (eItem != TextItem.UNKNOWN) { nEnd--; // we've already skipped over the offender } if (nEnd > oStart.index()) { oRanges.add (new TextRange (this, oStart.index(), nEnd)); } if (eItem == TextItem.UNKNOWN) { break; } oStart = oEnd; } } } /** * Replace the stream's text with the given text. *

* Replace all text in the stream with the contents of the given string. * This also has the effect of removing any embedded objects and fields * from the stream. If the stream contains embedded attribute changes, * only the attributes in effect at the start remain in effect after the * call. * @param sText New text to place in the stream. */ public void setText (String sText) { TextRange oRange = new TextRange (this); oRange.replace (sText); } /** * Add text to the end of the stream. *

* The various Append() methods allow the caller to build up a text * stream sequentially, without having to resort to using text position * objects. This overload adds text to the end of the stream. Whatever * attributes were in effect at the end of the stream before the call * apply to the newly added text. * @param sText Text string to add to the end of the stream. */ public void append (String sText) { TextPosnBase oPosn = new TextPosnBase (this, Integer.MAX_VALUE); oPosn.insert (sText); } /** * Add an attribute to the end of the stream. *

* The various Append() methods allow the caller to build up a text * stream sequentially, without having to resort to using text position * objects. This overload adds an attribute change to the end of the * stream. Note that attributes are somewhat transient. If the next * call on the stream appends text, that text will inherit the new * attributes. However most other manipulations will cause the newly * added attributes to be deemed redundant and removed from the stream. * @param oAttr Attribute object to add to the end of the stream. */ public void append (TextAttr oAttr) { TextPosnBase oPosn = new TextPosnBase (this, Integer.MAX_VALUE); oPosn.attribute (oAttr); } /** * Add an embedded field to the end of the stream. *

* The various Append() methods allow the caller to build up a text * stream sequentially, without having to resort to using text position * objects. This overload adds an embedded field to the end of the * stream. Whatever attributes were in effect at the end of the stream * before the call apply to the field (unless it starts with its own * attributes). * @param poField Field to add to the end of the stream. The method * clones a copy, so ownership of the object passed in remains with the * caller. */ public void append (TextField poField) { TextPosnBase oPosn = new TextPosnBase (this, Integer.MAX_VALUE); oPosn.insert (poField); } /** * Add an embedded object to the end of the stream. *

* The various Append() methods allow the caller to build up a text * stream sequentially, without having to resort to using text position * objects. This overload adds an embedded object to the end of the * stream. * @param poEmbed Object to embed at the end of the stream. The * method clones a copy, so ownership of the object passed in remains * with the caller. */ public void append (TextEmbed poEmbed) { TextPosnBase oPosn = new TextPosnBase (this, Integer.MAX_VALUE); oPosn.insert (poEmbed); } /** * Add a position marker at the end of the stream. *

* Note that there is no way to append a range marker through the text * stream API. Generally markers are inserted through operations on * text position and range objects. *

* @param oMarker - Pointer to marker to add. Note that markers are * always cloned on insertion, so a copy actually gets inserted. The * caller continues to retain ownership of the instance referred to by * this parameter, and can delete it any time after the call. */ public void append (TextMarker oMarker) { // TextPosnBase oPosn = new TextPosnBase (this, Integer.MAX_VALUE); // oPosn.Insert (oMarker); // TODO: } /** * Add rich text to the end of the stream. *

* The various Append() methods allow the caller to build up a text * stream sequentially, without having to resort to using text position * objects. This overload adds rich text to the end of the stream. If * the given rich text does not start with an attribute, then whatever * attributes were in effect at the end of this stream before the call * apply to the start of the newly added rich text. * @param oText Rich text to add to the end of the stream. */ public void append (TextStream oText) { TextPosnBase oPosn = new TextPosnBase (this, Integer.MAX_VALUE); oPosn.insert (oText); } /** * Obtain a pointer to the stream's display object. *

* Return a pointer to the stream's associated text display object. * Note that this method makes sense for embedded fields, as well as the * root displayable stream. All streams in a hiearchy will refer to the * same text display object. Client code that manages the editing of a * field doesn't need to track down the root displayable stream in order * to find the field's display. * @return A pointer to the associated display, or NULL if there is no * display currently associated. Note that ownership of the display * belongs with the root displayable stream. The caller must not delete * the display. */ public TextDisplay display () { return mpoDisplay; } /** * Suppress reformatting of the display for faster multiple updates. *

* If an application plans to make multiple changes to the same stream, * it doesn't want to incur the overhead of reformatting on each call. * This method allows it to turn off formatting and then turn it on * later when all the changes are done. It is only when turned on again * that the formatting occurs. * @param bSuppress TRUE is suppressing formatting, FALSE if restoring * it. Note that calls are actually "stacked", so that two consecutive * calls with TRUE will require two calls with FALSE to resume * formatting. This allows code to attempt to turn formatting off and * back on without having to worry about subverting the caller's use of * this method. */ public void suppressFormat (boolean bSuppress) { if (display() != null) { if (bSuppress) { display().pushSuppressFormat(); } else { display().popSuppressFormat(); } } } public FontService fontService () { return (moGfxSource == null) ? null : moGfxSource.getFontService(); } public void fontService (FontService poNewFontService) { if ((moGfxSource != null) && (poNewFontService == moGfxSource.getFontService())) { return; } moGfxSource = new TextGfxSource (poNewFontService); updateGfxSource(); } /** * Get a pointer to the Gfx attribute pool used by the stream. *

* The stream may carry a pointer to a Gfx attribute pool object in * order to reduce memory consumption for common attributes. This * method returns a pointer to the Gfx attribute pool currently in use. * @return Pointer to the attribute pool currently in use. May be * NULL. */ // public jfGfxAttrPool AttrPool () { // return moGfxSource.Pool(); // } /** * Set the Gfx attribute pool to be used by the stream. *

* The stream may carry a pointer to a Gfx attribute pool object in * order to reduce memory consumption for common attributes. This * method sets the Gfx attribute pool to be used by the stream. * @param poNewPool Pointer to the Gfx attribute pool to use. May be * NULL. */ // public void AttrPool (jfGfxAttrPool poNewPool) { // moGfxSource.Pool (poNewPool); // UpdateGfxSource(); // } /** * Obtain graphic source information used by the stream. *

* AXTE collects the various sources and pools of graphic information * (Gfx attribute pools, and font service) into a text graphic source * object (TextGfxSource). This method returns the current graphic * source of the stream. * @return Constant reference to the text stream's graphic source */ public TextGfxSource gfxSource () { return moGfxSource; } public void gfxSource (TextGfxSource newSource) { if (newSource == moGfxSource) { return; } if ((newSource != null) && (moGfxSource != null)) { if (newSource.getFontService() == moGfxSource.getFontService()) { return; // TODO: also check attr pool once implemented } } moGfxSource = newSource; updateGfxSource(); } /** * Query the stream's current maximum content size. *

* Any text stream can have an optional maximum number of characters. * This number actually is the maximum number of characters, paragraph * marks and embedded objects. This method returns the current maximum * size of the stream. * @return Maximum content size for the stream. A value of zero * indicates the size is unlimited. */ public int maxSize () { return mnMaxSize; } /** * Change the stream's maximum content size. *

* Any text stream can have an optional maximum number of characters. * This number actually is the maximum number of characters, paragraph * marks and embedded objects. This method sets a new maximum size for * the stream. * @param nNewSize New maximum content size for the stream. A value * of zero indicates the size is to be unlimited. * @param bAllowExistingOverflow Controls behaviour when the current * content of the stream exceeds its new maximum size. If TRUE, the * size is changed and the existing overflow is retained, but it is not * treated as an error. If FALSE, the content is retained, the maximum * size is not changed and an exception is thrown. */ public void maxSize (int nNewSize, boolean bAllowExistingOverflow) { if (! bAllowExistingOverflow) { if ((nNewSize != 0) && (nNewSize < currentSize())) { // throw jfExFull (TEXT.ERR_ALREADY_TOO_LARGE); // TODO: error handling } } mnMaxSize = nNewSize; } /** * Return the current content size of the stream * @return The current number of characters, paragraph marks and * embedded objects in the stream. */ public int currentSize () { int nCount = 0; int nSize = moItems.size(); for (int i = 0; i < nSize; i++) { StrItem poItem = getItem (i); int nType = poItem.strType(); if ((nType == TextItem.CHAR) || (nType == TextItem.PARA) || (nType == TextItem.OBJECT)) { nCount += poItem.count(); } } return nCount; } /** * Return the amount of content size left in the stream. *

* Any text stream can have an optional maximum number of characters. * This number actually is the maximum number of characters, paragraph * marks and embedded objects. This method returns the number of * characters that can still be added to the stream before its maximum * content size is exceeded * param return Number of characters that can still be added. If the * maximum content size is unlimited, UINT_MAX is returned. If the * current content already exceeds the maximum size, zero is returned. */ public int spaceLeft () { if (mnMaxSize == 0) { return Integer.MAX_VALUE; } int nCurrentSize = currentSize(); return (nCurrentSize > mnMaxSize) ? 0 : mnMaxSize - nCurrentSize; } /** * Does the text stream allow new line characters. *

* In form-filling applications, it often doesn't make sense to allow * new-line characters in fields. A stream can be told not to allow * new-lines. Note that paragraph marks are not considered new-lines. * This method returns a flag that indicates whether the stream allows * new-lines. * @return TRUE if the stream allows new-lines; FALSE if it doesn't. */ public boolean allowNewLines () { return mbAllowNewLines; } /** * Does the text stream allow new line characters. *

* In form-filling applications, it often doesn't make sense to allow * new-line characters in fields. Note that paragraph marks are not * considered new-lines. This method tells the stream whether or not to * allow new-lines. * @param bNewAllow TRUE if the stream is to allow new-lines; FALSE if * it not. If the stream already contains one or more new-lines when * this value is set to FALSE, an exception is thrown. */ public void allowNewLines (boolean bNewAllow) { if ((! bNewAllow) && anyNewLines()) { // throw jfExFull (TEXT.ERR_NEWLINE_ALREADY); // TODO: error handling } mbAllowNewLines = bNewAllow; } /** * Does the text stream contain any new line characters * @return TRUE if the stream contains any new-lines; FALSE if it * doesn't. */ public boolean anyNewLines () { return find ("\n", null); } /** * Search the stream for a given string. *

* This method scans the string for the exact Unicode character match to * a given string. It optionally returns a text position that points to * the start of the match. Embedded attribute changes are ignored * during the search, but embedded objects and fields are treated as * non-matching characters. * @param sSearch String to search for. Note that an empty search * string always fails to match. * @param poFoundPosn Optional pointer to position object to receive * the location of the start of the match. A NULL pointer (default * value) may be passed when the caller isn't interested in the * position. * @return TRUE if the string was found; FALSE otherwise. */ public boolean find (String sSearch, TextPosnBase poFoundPosn) { if (sSearch.length() == 0) { // Treat empty string as never found. return false; } // See if the stream contains enough characters to find the string. TextRange oCount = new TextRange (this); int nCount = oCount.countText(); if (nCount > sSearch.length()) { return false; } nCount -= (sSearch.length() - 1); // e.g., 1 iteration if just fits UniCharIterator iter = new UniCharIterator (sSearch); // Set up the base position. Position just beyond the first character, // to make sure that we are "close" to it. TextPosnBase oBase = new TextPosnBase (this); oBase.nextChar(); while (nCount > 0) { // Iterate until the search string no longer fits // Set up a position to scane at this base. Note: skip back to // immediately before the character the base has already skipped. TextPosnBase oScan = new TextPosnBase (oBase); oScan.prev(); int i; for (i = 0; i < sSearch.length(); ) { // Match search string characters to text stream characters. Embedded // objects stop the search. Embedded attributes are ignored. int eItem = oScan.next (true); if (eItem == TextItem.CHAR) { int c = oScan.nextChar(); iter.setIndex (i); if (c != iter.next()) { break; } i = iter.getIndex(); } else if (eItem == TextItem.ATTR) { oScan.next(); } else { break; } } if (i >= sSearch.length()) { // If we got to the end of the search string, we found it. Optionally // return the (corrected) base position. if (poFoundPosn != null) { oBase.prev(); poFoundPosn.copyFrom (oBase); } return true; } // Still not found: move the base just past the next starting character // and decrement the count. oBase.nextChar(); nCount--; } return false; } public boolean find (String sSearch) { return find (sSearch, null); } /** * Convert the string contents to markup. *

* Convert the entire stream contents to a markup language. The * particular language is determined by which derived class of * TextMkOut is passed. The caller may request that fields are * flattened into stream content in the markup, or retained as * references. * @param oMarkup Output markup engine to generate the markup * language. * @param poInitAttr Optional ambient/initial attributes. This * parameter, if not NULL, works in concert with the bDefaultInitAttr * parameter. If it indicates ambient attributes, it represents default * attributes that need not be written to the markup if they apply to * the stream. If it indicates initial attributes, all enabled values * are written to the markup. If NULL (default value) it is ignored. * @param bDefaultInitAttr Optional. If FALSE (default), parameter * poInitAttr represents initial attributes to write. If TRUE, * poInitAttr represents ambient attributes as described above. * @param bFlattenFields Optional. If TRUE, embedded field content is * written to the markup as if it was part of (this) root stream. If * FALSE (default) field references are written to the markup. */ public void markup (MarkupOut oMarkup, TextAttr poInitAttr, boolean bDefaultInitAttr, boolean bFlattenFields) { TextRange oRange = new TextRange (this, 0, Integer.MAX_VALUE); // include initial attrs oRange.markup (oMarkup, poInitAttr, bDefaultInitAttr, bFlattenFields); } public void markup (MarkupOut oMarkup, TextAttr poInitAttr, boolean bDefaultInitAttr) { markup (oMarkup, poInitAttr, bDefaultInitAttr, false); } public void markup (MarkupOut oMarkup, TextAttr poInitAttr) { markup (oMarkup, poInitAttr, false, false); } public void markup (MarkupOut oMarkup) { markup (oMarkup, null, false, false); } /** * Replace this streams content by processing the given markup. *

* Process source in a given markup language and replace the stream's * content with content described by the markup source. The particular * language is determined by which derived class of TextMkOut is * passed. * @param oMarkup Markup engine to process the markup. Note that this * pre-populated with the markup source text. */ public void markup (MarkupIn oMarkup) { TextRange oRange = new TextRange (this); oRange.markup (oMarkup); } /** * Invalidate the area occupied by the text stream in an interactive * environment. *

* In conjunction with a graphic environment, this method invalidates * the (visible) graphic area occupied by the text stream. This will * lead to a future paint event through the graphic framework. * @param oGfxEnv Graphic environment to perform the invalidation in. * @param bEraseBkgnd TRUE if the background is to be erased as well; * FALSE (default) if not. */ // public void Invalidate (jfGfxEnv oGfxEnv, boolean bEraseBkgnd) { // TextRange oRange = new TextRange (this); // cast away const // oRange.Invalidate (oGfxEnv, bEraseBkgnd); // } /** * Determine if this stream is contained under a given stream. *

* @param poPosn Optional position object to describe the position of * this stream (or its penultimate ancestor) in the ancestor stream. If * the ancestor is this stream or is not an ancestor, the position * object is not modified. * @return TRUE if this stream is a descendent of the given stream; * FALSE if not. */ public boolean isDescendentOf (TextStream poAncestor, TextPosnBase poPosn) { if (this == poAncestor) { return true; } TextPosn poParentPosn = position(); if (poParentPosn == null) { return false; } TextStream poParentStream = poParentPosn.stream(); if (poParentStream == null) { return false; } if (poParentStream == poAncestor) { if (poPosn != null) { poPosn.copyFrom (poParentPosn); } return true; } return poParentStream.isDescendentOf (poAncestor, poPosn); } public boolean isDescendentOf (TextStream poAncestor) { return isDescendentOf (poAncestor, null); } /** * Set the value of the legacy positioning flag. *

* For compatibility with version 6 layout idiosyncracies, legacy * positioning mode can be enabled for a text stream (actually, it * really makes sense only for displayable streams; see class * TextDispStr). This method sets the flag for the stream and * cascades it to all embedded fields and objects. It currently does * not cause a relayout; therefore legacy positioning must be set before * the display is created. *

* @param bLegacyPositioning - TRUE if legacy positioning enabled for * this stream; FALSE (default) if not. */ public void legacyPositioning (boolean bLegacyPositioning) { cascadeLegacyLevel (bLegacyPositioning ? LEVEL_V6 : LEVEL_NORMAL); } /** * Set legacy positioning by level enumeration and cascade to descendent * streams and objects. * @param eLevel - New legacy level to set. */ public void cascadeLegacyLevel (int eLevel) { setLegacyLevel (eLevel); int nSize = moItems.size(); for (int i = 0; i < nSize; i++) { getItem(i).cascadeLegacyLevel (eLevel); } } /** * Assign this stream's content from the given stream. *

* Replace this stream's content with a copy of the content of the given * stream. The graphic source information is not copied. In * other words, fonts will be re-mapped in this stream's font service * and attributes will be re-pooled in any attribute pool associated * with this stream. * @param oSource Stream containing source content to copy. */ public void copyFrom (TextStream oSource) { LoadText oLoader = new LoadText (oSource, moGfxSource); loadText (oLoader); } /** * Compare text streams for content equality. *

* Compare this stream against the one passed on the parameter oCompare * for content equality. The graphics sources of the streams are not * compared. To be equal, the streams' content must match in all * aspects: raw text, attributes, embedded field content, and so on. * @param object Text Stream object to compare against * @return TRUE if the streams are equal; FALSE otherwise. */ public boolean equals (Object object) { if (this == object) return true; // This overrides Object.equals(boolean) directly, so... if (object == null) return false; if (object.getClass() != getClass()) return false; TextStream oCompare = (TextStream) object; if (moItems.size() != oCompare.moItems.size()) { return false; } int nSize = moItems.size(); for (int i = 0; i < nSize; i++) { StrItem poSource = getItem (i); StrItem poCompare = oCompare.getItem (i); if (poSource.strType() != poCompare.strType()) { return false; } if (! poSource.isEqual (poCompare)) { return false; } } return true; } public int hashCode() { int hash = 47; int nSize = moItems.size(); for (int i = 0; i < nSize; i++) { StrItem oSource = getItem(i); hash = (hash * 31) ^ oSource.hashCode(); break; } return hash; } /** * Compare text streams for content inequality. *

* Compare this stream against the one passed on the parameter oCompare * for content inequality. The graphics sources of the streams are not * compared. This is the exact opposite of the equality comparison. * @param oCompare Stream to compare against * @return TRUE if the streams are unequal; FALSE otherwise. */ public boolean notEqual (TextStream oCompare) { return ! equals (oCompare); } // Overridable by derived classes. /** * Get the stream's text context object. *

* The text context allows multiple streams to share common objects that * are used at layout time, thereby reducing resource usage. This * method returns a pointer to the text context in effect for this * stream. Only displayable streams (see class TextDispStr) carry * contexts. The default implementation of this virtual method looks * back up through the stream containment hierarchy for a displayable * stream and returns its context. * @return Pointer to the text context; NULL if none is in effect or no * displatable stream found. */ public TextContext getContext () { TextPosn poPosn = position(); if (poPosn != null) { TextStream poParentStream = poPosn.stream(); if (poParentStream != null) { return poParentStream.getContext(); } } return null; } /** * Get this stream's position in its parent stream. *

* This virtual method is overridden by class TextField, the embedded * field class. It returns a text position object associated with the * parent stream that represent's the field's position in its parent. * With this method, the caller can traverse back up the ancestry * hierarchy toward the root. The default implementation returns a NULL * pointer, indicating that stream is not embedded in another stream. * @return Pointer to a const position object representing this stream's * position in its parent; NULL if the stream is not embedded in another * stream. */ public TextPosn position () { return null; } /** * Notify a stream that it has been picked. *

* Note: It is not clear whether this method is still used. This method * allows a stream to record the fact that it has been picked. The * default implementation simply sets a Boolean flag indicating that the * stream has been picked. It can be overridden by a derived class for * more advanced pick processing. * @param oPickPosition Position where pick occurred. * @param poGfxEnv Graphic environment in which pick occurred. * @return TRUE if the stream successfully handled the pick request; * FALSE otherwise. Default implementation always returns TRUE. */ // public boolean Pick (TextPosn oPickPosn, jfGfxEnv poGfxEnv) { // mbPicked = true; // return true; // } /** * Notify derived class of content change. *

* When an API call changes the content of the stream, it calls this * method to notify any derived class that changes have occurred. The * default implementation does nothing. Currently, none of the derived * classes implemented in Text Services use this feature. */ public void updateNotification () { } /** * Override the stream's colours in a graphic environment. *

* Note: It is not clear whether this method is still used. It appears * that the application's derived class could provide foreground and * background colour overrides at draw time (for example, to display the * current field in a different colour from the rest). The default * implementation does not provide colour overrides. * @param oGfxEnv Graphic environment in which the stream is being * drawn. * @param oColour Foreground colour override to use for drawing. * @param oColourBb Background colour override to use for drawing. * @return TRUE if the derived class provided colour overrides in the * second and third parameters; FALSE if not. The defalt implementation * returns FALSE. */ // public boolean OverrideColours (jfGfxEnv oGfxEnv, jfGfxColour oColour, jfGfxColour oColourBg) { /* oGfxEnv */ /* oColour */ /* oColourBg */ // return false; // } void posnAttach (TextPosn poPosn) { poPosn.mpoNext = mpoAssociated; mpoAssociated = poPosn; poPosn.mpoStream = this; } void posnDetach (TextPosn poPosn) { TextPosn poPrev = null; TextPosn poSearch = mpoAssociated; while ((poSearch != poPosn) && (poSearch != null)) { poPrev = poSearch; poSearch = poSearch.mpoNext; } if (poSearch != null) { if (poPrev == null) { mpoAssociated = poSearch.mpoNext; } else { poPrev.mpoNext = poSearch.mpoNext; } poSearch.mpoNext = null; poSearch.cleanup(); } } public int posnCount () { return mnCount; } void posnFirst (TextPosnBase oPosn) { oPosn.index (0); int eItem; for (eItem = oPosn.next(); eItem == TextItem.ATTR; eItem = oPosn.next()) { ; } if (eItem != TextItem.UNKNOWN) { oPosn.prev(); // not all attributes } } public int[] posnNext (TextPosnBase oPosn, int eNullFrameMode, boolean bTestOnly) { return posnMove (oPosn, true, eNullFrameMode, bTestOnly); } public int posnNextType (TextPosnBase oPosn, int eNullFrameMode, boolean bTestOnly) { int[] result = posnNext (oPosn, eNullFrameMode, bTestOnly); return (result == null) ? TextItem.UNKNOWN : result[0]; } public int posnNextType (TextPosnBase oPosn, int eNullFrameMode) { int[] result = posnNext (oPosn, eNullFrameMode, false); return (result == null) ? TextItem.UNKNOWN : result[0]; } public int[] posnPrev (TextPosnBase oPosn, int eNullFrameMode, boolean bTestOnly) { return posnMove (oPosn, false, eNullFrameMode, bTestOnly); } public int posnPrevType (TextPosnBase oPosn, int eNullFrameMode, boolean bTestOnly) { int[] result = posnPrev (oPosn, eNullFrameMode, bTestOnly); return (result == null) ? TextItem.UNKNOWN : result[0]; } public int posnPrevType (TextPosnBase oPosn, int eNullFrameMode) { int[] result = posnPrev (oPosn, eNullFrameMode, false); return (result == null) ? TextItem.UNKNOWN : result[0]; } public int posnNextChar (TextPosnBase oPosn, boolean bTestOnly) { // Performance optimization: Chances are that we're looking at the next // character already. If so, just obtain it to be returned, rather than // the more general case. StrItem poItem = getItem (oPosn.major()); int c = poItem.charAt (oPosn.minor()); if ((c != '\0') && ((c != '\n') || (poItem.strType() != TextItem.PARA))) { // para fakes '\n' char if (! bTestOnly) { oPosn.next(); } } // General case: may have to scan forward for the next character. else { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchForward (oTempPosn, TextItem.CHAR); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { c = '\0'; // might have been '\n' from para } else { oTempPosn.prev(); c = getItem(oTempPosn.major()).charAt (oTempPosn.minor()); } } return c; } public int posnPrevChar (TextPosnBase oPosn, boolean bTestOnly) { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchBackward (oTempPosn, TextItem.CHAR); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { return '\0'; } return getItem(oTempPosn.major()).charAt (oTempPosn.minor()); } public TextField posnNextField (TextPosnBase oPosn, boolean bTestOnly) { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchForward (oTempPosn, TextItem.FIELD, TextNullFrame.MODE_IGNORE); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { return null; } oTempPosn.prev(); return getItem(oTempPosn.major()).fieldAt (oTempPosn.minor()); } public TextField posnPrevField (TextPosnBase oPosn, boolean bTestOnly) { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchBackward (oTempPosn, TextItem.FIELD, TextNullFrame.MODE_IGNORE); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { return null; } return getItem(oTempPosn.major()).fieldAt (oTempPosn.minor()); } public TextEmbed posnNextEmbed (TextPosnBase oPosn, boolean bTestOnly) { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchForward (oTempPosn, TextItem.OBJECT, TextNullFrame.MODE_IGNORE); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { return null; } oTempPosn.prev(); return getItem(oTempPosn.major()).embedAt (oTempPosn.minor()); } public TextEmbed posnPrevEmbed (TextPosnBase oPosn, boolean bTestOnly) { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchBackward (oTempPosn, TextItem.OBJECT, TextNullFrame.MODE_IGNORE); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { return null; } return getItem(oTempPosn.major()).embedAt (oTempPosn.minor()); } public TextAttr posnNextAttr (TextPosnBase oPosn, boolean bTestOnly) { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchForward (oTempPosn, TextItem.ATTR, TextNullFrame.MODE_IGNORE); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { return null; } oTempPosn.prev(); return getItem(oTempPosn.major()).attrAt (oTempPosn.minor()); } public TextAttr posnPrevAttr (TextPosnBase oPosn, boolean bTestOnly) { TextPosnBase oTempPosn = new TextPosnBase (oPosn); boolean bMore = posnSearchBackward (oTempPosn, TextItem.ATTR, TextNullFrame.MODE_IGNORE); if (! bTestOnly) { oPosn.copyFrom (oTempPosn); } if (! bMore) { return null; } return getItem(oTempPosn.major()).attrAt (oTempPosn.minor()); } void posnInsert (TextPosnBase oPosn, char cInsert) { // Insert single character String sInsert = ""; sInsert += cInsert; posnInsert (oPosn, sInsert); } void posnInsert (TextPosnBase oPosn, String sInsert) { // Insert text string if (sInsert.length() == 0) { return; // Don't insert an empty string item } if ((! mbAllowNewLines) && (sInsert.indexOf ('\n') >= 0)) { // throw jfExFull (TEXT.ERR_NEWLINE_NOT_ALLOWED); // TODO: error handling } insert (oPosn, new StrText (sInsert), true, TextMarker.SPLIT_REASON_INSERT_TEXT_PLAIN); } public void posnInsert (TextPosnBase oPosn, TextStream oInsert) { // Insert text *stream* insertTest (oInsert.currentSize()); if ((! mbAllowNewLines) && oInsert.find ("")) { // throw ExFull (JF_TEXT_ERR_NEWLINE_NOT_ALLOWED); // TODO: } TextRange oMarkerRange = new TextRange (this, oPosn.index(), oPosn.index()); //int nOldCount = mnCount; // This insertion may change attributes or it may have incomplete // attributes. Initialize for attribute handling of the insertion. TextAttr poFirstAttr = posnAttrPtr (oPosn); TextAttr poPrevAttr = poFirstAttr; boolean bAttrChange = false; boolean bSplitItem = oPosn.minor() != 0; // Compute the old and new array sizes. Increment the new size if we // have to split an existing item. int nIndex = oPosn.mnIndex; int nCount = oInsert.mnCount; int nInsertSize = oInsert.moItems.size() - 1; // Exclude end marker int nCopySize = nInsertSize; if (bSplitItem) { nCopySize += 2; // allow for both halves of split item } int nNewIndex = 0; int i; // Initialize a separate array to contain the copy. This is so that we // don't delete our old data until we're sure we can copy all of it // without exception. Storage oNewItems = new Storage(nCopySize); for (i = 0; i < nCopySize; i++) { oNewItems.add (null); } // This block handles the creation of all new data: first the split (if // required), and then the copy of the source data. // CleanupItemArray oClearItemArray (oNewItems); if (bSplitItem) { // If we have to split the an item in the original array, do so now and // update the positions. StrItem[] split = getItem(oPosn.mnMajor).split (oPosn.mnMinor); StrItem splitFirst = split[0]; StrItem splitLast = split[1]; oNewItems.set (0, splitFirst); oNewItems.set (nCopySize-1, splitLast); nCount += splitFirst.count() + splitLast.count(); nCount -= getItem(oPosn.major()).count(); nNewIndex++; // Where we start putting oInsert } for (i = 0; i < nInsertSize; i++) { // Copy (clone) items from the inserted stream. Note that source // attribute objects may be incomplete. In such a case, they inherit // attributes in effect at the start of the insertion. We'll also need // to remember to restore those attributes after the insertion. StrItem poItem = oInsert.getItem (i); TextAttr poItemAttr = poItem.attrAt (0); StrItem poNewItem = null; if ((poPrevAttr != null) && (poItemAttr != null)) { poNewItem = new StrAttr (poPrevAttr); // start with "ambient" attrs poNewItem.overrideAttr (poItemAttr); // override from insertion poPrevAttr = poNewItem.attrAt (0); // for next incomplete attr bAttrChange = true; } else { poNewItem = oInsert.getItem(i).cloneItem (moGfxSource); } oNewItems.set (nNewIndex, poNewItem); nNewIndex++; } if (bAttrChange) { // If we have changed attributes along the way, we need to create a new // item at the end of the insertion to restore attributes to their state // before the insertion. Note that redundant attributes will be // reconciled later. int lastIndex = nCopySize; nCopySize++; oNewItems.setSize (nCopySize); StrAttr restoreAttr = new StrAttr (poFirstAttr); if (bSplitItem) { int penultimate = lastIndex - 1; oNewItems.set (lastIndex, oNewItems.get (penultimate)); oNewItems.set (penultimate, restoreAttr); } else { oNewItems.set (lastIndex, restoreAttr); } nCount++; } // Prepare our array for the new items. If an item was split, we can now // safely get rid of the original (the two halves are in oNewItems). int nNewSize = moItems.size() + nCopySize; if (bSplitItem) { nNewSize--; moItems.remove (oPosn.major()); } int nOldSize = moItems.size(); moItems.setSize (nNewSize); // Move existing items after the insert to their new positions in the // array. int nSrc = nOldSize; int nDst = nNewSize; while (nSrc > oPosn.major()) { moItems.set (--nDst, moItems.get (--nSrc)); } // Copy the new items into the opened space in our items array. for (i = 0; i < nCopySize; i++) { moItems.set (oPosn.major() + i, oNewItems.get (i)); } // Only if we get this far are the new item pointers valid (they're // copied in moItems now) and cease to be relevant on their own because // CompleteInsert() will muck with our items. So we can detach the // deleter. // oClearItemArray.Detach(); // Before completing the change, create a range to track the insert in // case it has to be backed out. Note that the range is created empty // because CompleteInsert() will update it appropriately. TextRange oInsertRange = new TextRange (this, nIndex, nIndex); if (! completeInsert (nIndex, nCount, true, TextMarker.SPLIT_REASON_INSERT_TEXT_RICH, bAttrChange, UPDATE_NORMAL)) { // Reconcile data and update positions. (treat as "other" because may // containg attr changes and all sorts of stuff). If the call fails, the // text doesn't fit and needs to be backed out. oInsertRange.delete(); // throw ExFull (JF_TEXT_ERR_BLOCK_FULL); return; // TODO: } setEmbedPositions(); // Finally, look for markers that must be split across paragraphs and // reconcile those against any new paragraphs inserted. splitParaMarkers (oMarkerRange, null); } void posnInsert (TextPosnBase oPosn, TextField poField) { // Insert field object insert (oPosn, new StrField (poField, moGfxSource, getLegacyLevel()), false, TextMarker.SPLIT_REASON_INSERT_FIELD); setEmbedPositions(); } public void posnInsert (TextPosnBase oPosn, TextEmbed poEmbed) { // Insert embedded object insertTest (1); insert (oPosn, new StrEmbed (poEmbed, getLegacyLevel()), true, TextMarker.SPLIT_REASON_INSERT_EMBED); setEmbedPositions(); } public void posnInsertPara (TextPosnBase oPosn) { TextRange oMarkerRange = new TextRange (this, oPosn.index(), oPosn.index()); insert (oPosn, new StrPara(), true, TextMarker.SPLIT_REASON_INSERT_PARA_BREAK); splitParaMarkers (oMarkerRange, null); } TextAttr posnAttrPtr (TextPosnBase oPosn) { TextPosnBase oSearch = new TextPosnBase (oPosn); if (posnSearchBackward (oSearch, TextItem.ATTR, TextNullFrame.MODE_IGNORE)) { return getItem (oSearch.mnMajor).attrAt (0); } TextPosn poPosn = position(); if (poPosn != null) { return poPosn.attributePtr(); } return null; } TextAttr posnAttr (TextPosnBase oPosn) { TextAttr poAttr = posnAttrPtr (oPosn); if (poAttr == null) { poAttr = TextAttr.defaultAttr (true); } return new TextAttr (poAttr); // a copy for editing } //---------------------------------------------------------------------- // // PosnAttr: change attributes at a particular position. // // Note: this method inserts a (redundant) pair of attribute // changes at the given position, and doesn't reconcile them. // The position is left between the pair and can be used to // insert characters with the new attributes. // //---------------------------------------------------------------------- // Use the text range method to insert the attribute pair. void posnAttr (TextPosnBase oPosn, TextAttr oNewAttr, boolean bRaw) { TextRange oRange = new TextRange (this, oPosn.mnIndex, oPosn.mnIndex); oRange.attribute (oNewAttr); // Set this position inside the inserted pair, preserving its POSN_BEFORE // or POSN_AFTER code. oPosn.index (oRange.position (TextRange.POSN_ANCHOR).mnIndex); } TextMarker posnMarker (TextPosnBase oPosn, TextMarker poMarker) { TextMarker poCopy = poMarker.cloneMarker(); PosnMarker poLocation = new PosnMarker (oPosn, poCopy); poCopy.setLocation (poLocation); return poCopy; } TextMarker posnMarkerStart (TextPosnBase oPosn, TextMarker poMarker) { return new MarkerTemp (oPosn, poMarker); } void posnMarkerEnd (TextPosnBase oPosn, TextMarker poMarker) { MarkerTemp poStartInfo = (MarkerTemp) poMarker; TextMarker poCopy = poStartInfo.getMarkerCopy(); rangeMarkerInternal (poStartInfo.getStart(), oPosn, poCopy); } void posnDelete (TextPosnBase oPosn, int nDelete, boolean bRaw) { delete (oPosn, nDelete, bRaw, UPDATE_NORMAL); } public void posnUpdateStreamLoc (TextPosnBase oPosn, int nIndex) { // Note: this class manipulates the private members mnMajor and mnMinor // of class TextPosnBase. It must retrieve values from those members // directly, because calling the Major() or Minor() method will result in // an unexpected recursive call to this method, // Set up the true end index number truncated to the text stream size int nEnd = nIndex; if (nEnd > posnCount()) { nEnd = posnCount(); } if ((oPosn.mnIndex > nEnd) || oPosn.mbIsInvalid) { // If our index is before the new end, count from the current position. // If our index is after the new end, count from the start oPosn.mnIndex = 0; // Reposition at start oPosn.mnMajor = 0; oPosn.mnMinor = 0; } // Determine how many positions left to count over int nLeft = nEnd - oPosn.mnIndex; int nCurrent; while (nLeft > 0) { // Iterate until there are no more positions left if (oPosn.mnMajor >= moItems.size()) { break; } // Determine positions left in current item group nCurrent = getItem(oPosn.mnMajor).count() - oPosn.mnMinor; if (nCurrent <= nLeft) { // Current item group too small or just right size: go to next item group oPosn.mnMajor++; oPosn.mnMinor = 0; } else { // Current group larger than positions left: stop here nCurrent = nLeft; oPosn.mnMinor += nCurrent; } // Decrement by number of positions skipped over in current item group nLeft -= nCurrent; } oPosn.mnIndex = nEnd; oPosn.cleanupTarget(); oPosn.mbIsInvalid = false; } void posnInsertNullFrame (TextPosnBase oPosn, TextNullFrame poNullFrame) { insert (oPosn, new StrNullFrame (poNullFrame), UPDATE_SUPPRESS_DISPLAY, TextMarker.SPLIT_REASON_UNKNOWN); } void posnRemoveNullFrame (TextPosnBase oPosn) { TextPosnBase oTighten = new TextPosnBase (oPosn); oTighten.tighten (true); delete (oTighten, 1, true, UPDATE_SUPPRESS_DISPLAY); } String rangeText (TextPosn oStart, TextPosn oEnd, boolean bIncludeFields) { MarkupText oMarkup = new MarkupText(); rangeMarkup (oStart, oEnd, oMarkup, null, false, bIncludeFields); return oMarkup.getText(); } void rangeText (TextPosn oStart, TextPosn oEnd, TextStream oText) { LoadText oLoader = new LoadText (oStart, oEnd, oText.moGfxSource); oText.loadText (oLoader); } void rangeReplace (TextPosnBase oPosn, int nDelete, String sReplace, boolean bRaw) { delete (oPosn, nDelete, bRaw, UPDATE_FULL_SUPPRESS); posnInsert (oPosn, sReplace); } void rangeReplace (TextPosnBase oPosn, int nDelete, TextStream sReplace, boolean bRaw) { delete (oPosn, nDelete, bRaw, UPDATE_FULL_SUPPRESS); posnInsert (oPosn, sReplace); } //---------------------------------------------------------------------- // // RangeGetAttr: return attributes over a range of // characters. // // Note: the returned attribute object may be hooked into the // text stream's attribute pool (if there is one). You must // delete the returned object before deleting the pool // // Also note: this method fills in a caller's attribute // object, rather than returning a reference. An earlier // implementation retained a static object and returned a // reference. Unfortunately, the earlier implementation had // problems when the static object was deleted and still // contained a pointer to a long gone attribute pool. // //---------------------------------------------------------------------- TextAttr rangeGetAttr (TextPosn oStart, TextPosn oEnd) { TextAttr oAttr = posnAttr (oStart); int nSearch = oStart.major(); int nSize = moItems.size(); while ((nSearch < oEnd.major()) && (nSearch < nSize)) { StrItem poItem = getItem (nSearch); if (poItem.strType() == TextItem.ATTR) { oAttr.dropDiff ((poItem.attrAt (0))); } nSearch++; } return oAttr; } //---------------------------------------------------------------------- // // RangeSetAttr: change attributes over a range of // characters. // // If the given attribute object contains paragraph // attributes and we are not doing a "raw" setting, we may // have to do two passes. The first applies only the // non-paragraph attributes to the exact range, while the // second applies only the paragraph attributes to the // paragraph(s) containing the range. // //---------------------------------------------------------------------- void rangeSetAttr (TextPosn oStart, TextPosn oEnd, TextAttr oNewAttr, boolean bRaw) { // Potentially multiple display changes, suppress redundant ones. suppressFormat (true); try { // Marker attribute change notification: If this is a truly non-empty // range, run through the markers and notify them of the attribute // change. Notifications go out to range markers only and only if there // is a non-empty overlap. if (oEnd.index() > oStart.index()) { Storage oMarkers = new Storage(); int nStart = oStart.index(); int nEnd = oEnd.index(); enumerateOverlappingRangeMarkers (nStart, nEnd, oMarkers); for (int i = 0; i < oMarkers.size(); i++) { TextMarker poMarker = (TextMarker) oMarkers.get (i); int[] markers = new int[] {nStart, nEnd}; intersectMarkerRange (poMarker, markers); poMarker.onAttributeChange (nStart, nEnd, oNewAttr); } } // Raw change or no paragraph attributes: simply apply the attrs on the // given range. TextPosn oParaStart = null; TextPosn oParaEnd = null; boolean bTreatAsRaw = bRaw; if (! bTreatAsRaw) { if (! oNewAttr.hasParaAttrs()) { bTreatAsRaw = true; } else { TextRange oParaRange = new TextRange (this, oStart.index(), oEnd.index()); oParaRange.grabPara(); oParaStart = oParaRange.start(); oParaEnd = oParaRange.end(); if (oStart.equals (oParaStart) && oEnd.equals (oParaEnd)) { bTreatAsRaw = true; } } } if (bTreatAsRaw) { rangeAttr (oStart, oEnd, oNewAttr, UPDATE_NORMAL); } // Otherwise, do two separate changes, first to the paragraph and then to // the range (if required). Watson 1251494: To work around a // non-standard calling sequence from the RTF translator, don't collapse // redundant attributes (especially at the end of the stream) on // insertion of the paragraph attributes). Unconditionally set the // non-paragraph attributes, even if empty. This allows for the creation // of a new temporary attribute pair if the start and end are the same. else { TextAttr oTempAttr = new TextAttr (oNewAttr); oTempAttr.isolatePara (true, hasLegacyPositioning()); rangeAttr (oParaStart, oParaEnd, oTempAttr, UPDATE_SUPPRESS_RECONCILE); oTempAttr.copyFrom (oNewAttr); oTempAttr.isolatePara (false, hasLegacyPositioning()); rangeAttr (oStart, oEnd, oTempAttr, UPDATE_NORMAL); } } finally { suppressFormat (false); } } void rangeMarkup (TextPosn oStart, TextPosn oEnd, MarkupOut oMarkup, TextAttr poInitAttr, boolean bDefaultInitAttr, boolean bFlattenFields) { oMarkup.reset(); // Reconcile the first attribute to write. TextPosnBase oBase = new TextPosnBase (oStart); TextAttr poPrevAttr = null; if (! oMarkup.suppressAttributes()) { if (! bDefaultInitAttr) { if (poInitAttr != null) { oMarkup.attr (poInitAttr); } else { poPrevAttr = posnAttrPtr (oBase); if (poPrevAttr != null) { oMarkup.attr (poPrevAttr); } } } else { if (oBase.next (true) == TextItem.ATTR) { oBase.next(); } poPrevAttr = posnAttrPtr (oBase); if (poPrevAttr != null) { TextAttr oAttr = new TextAttr (poPrevAttr); if (poInitAttr != null) { oAttr.dropSame (poInitAttr); } if (! oAttr.isEmpty()) { oMarkup.attr (oAttr); } } } } // Iterate once for each item wholly or partly in the range. int nMajor = oBase.major(); int nMinor = oBase.minor(); while ((nMajor < oEnd.major()) || ((nMajor == oEnd.major()) && (nMinor < oEnd.minor()))) { // If this is the last item in the range, use the minor end position from // the range to end the copy. Otherwise, use the full item size. int nMax; if (nMajor == oEnd.major()) { nMax = oEnd.minor(); } else { nMax = getItem(nMajor).count(); } getItem(nMajor).markup (oMarkup, nMinor, nMax - nMinor, bFlattenFields, poPrevAttr); nMinor = 0; nMajor++; } } void rangeMarkup (TextPosn oStart, MarkupIn oMarkup) { TextPosn oPosn = new TextPosn (oStart); oMarkup.setup (oPosn, moGfxSource); oMarkup.translate(); } public TextMarker rangeMarker (TextPosn oStart, TextPosn oEnd, TextMarker poMarker) { TextMarker poCopy = poMarker.cloneMarker(); rangeMarkerInternal (oStart, oEnd, poCopy); return poCopy; } public void rangeMarkerInternal (TextPosnBase oStart, TextPosnBase oEnd, TextMarker poMarker) { // Adjust to include entire paragraph if necessary TextRange oRange = new TextRange (this, oStart.index(), oEnd.index()); if (poMarker.isParaMarker()) { oRange.grabPara(); } oRange.tighten(); // Create the start and end marker position objects. This automatically // attaches them to the stream. PosnMarker poLocation = new PosnMarker (oRange.start(), poMarker, TextPosn.POSN_BEFORE); PosnMarker poEndLoc = new PosnMarker (oRange.end(), poMarker, TextPosn.POSN_AFTER); // Connect start/end positions to each-other and with the marker poLocation.setMate (poEndLoc); poEndLoc.setMate (poLocation); poMarker.setLocation (poLocation); if (poMarker.getSplitState() == TextMarker.SPLIT_PARA) { // In case the marker is to be split at paragraphs and there are // paragraph breaks within the given range. splitParaMarkers (oRange, poMarker); } } public void rangeEnumMarker (TextPosnBase oStart, TextPosnBase oEnd, List oMarkers, boolean bPositionMarkers, boolean bRangeMarkers, boolean bSubsetOnly) { oMarkers.clear(); int nStart = oStart.index(); int nEnd = oEnd.index(); // Run through all associated positions, looking for markers in the range TextPosn poPosn = mpoAssociated; while (poPosn != null) { int nIndex = poPosn.index(); // Ignore non-marker associated positions. if (poPosn.isMarkerPosition()) { PosnMarker poLocation = (PosnMarker) poPosn; PosnMarker poMate = poLocation.getMate(); // Check for overlapping position markers if (bPositionMarkers && (poMate == null) && (nIndex >= nStart) && (nIndex <= nEnd)) { oMarkers.add (poLocation.getMarker()); } // Check for overlapping range markers if (bRangeMarkers && (poMate != null)) { int nMateIndex = poMate.index(); boolean bProcess = false; if (nIndex < nMateIndex) { bProcess = true; } else if (nIndex == nMateIndex) { bProcess = poLocation.position() == TextPosn.POSN_BEFORE; } if (bProcess) { if (bSubsetOnly) { bProcess = (nIndex >= nStart) && (nMateIndex <= nEnd); } else { bProcess = (nIndex <= nEnd) && (nMateIndex >= nStart); } if (bProcess) { oMarkers.add (poLocation.getMarker()); } } } } poPosn = poPosn.mpoNext; } } public TextMarker splitMarker (TextMarker poSource, int nSplitStart, int nSplitEnd, int eMarkerSplitReason) { TextMarker poNewMarker = null; // Determine the marker's indexes PosnMarker poMarkerStart = poSource.getLocation(); int nMarkerStart = poMarkerStart.index(); PosnMarker poMarkerEnd = poMarkerStart.getMate(); int nMarkerEnd = poMarkerEnd.index(); // Simple case: no overlap or the two just abut each other: nothing to // do. Do this check before expanding the split span to include // attribute changes. if ((nMarkerStart >= nSplitEnd) || (nMarkerEnd <= nSplitStart)) { return null; } // Reassign the split span indexes after tightening the span. This is to // spread out the span to include attribute changes at both ends (thereby // eliminating them from the split markers. TextPosnBase oStart = new TextPosnBase (this, nSplitStart); oStart.tighten (false); nSplitStart = oStart.index(); TextPosnBase oEnd = new TextPosnBase (this, nSplitEnd); oEnd.tighten (false); nSplitEnd = oEnd.index(); // The split span starts before or at the start of the marker. if (nSplitStart <= nMarkerStart) { // If it also ends at or beyond the end of the marker, remove the marker // from the stream if it supports automatic deletion. if (nSplitEnd >= nMarkerEnd) { if (poSource.getAutoRemove()) { removeMarker (poSource, true); } } // Otherwise, its end is within the marker's range, but not right at the // end. Truncate the marker's start to exclude the split span. else { int[] markers = new int[] {nSplitStart, nSplitEnd}; intersectMarkerRange (poSource, markers); nSplitStart = markers[0]; nSplitEnd = markers[1]; if (poSource.onTruncate (nSplitStart, nSplitEnd, false, eMarkerSplitReason)) { poMarkerStart.setMarkerIndex (nSplitEnd); } } } // The split span starts within the marker's range, but not at the // marker's start. else { if (nSplitEnd >= nMarkerEnd) { // If it ends at or beyond the end of the marker, truncate the marker's // end to exclude the split span. int[] markers = new int[] {nSplitStart, nSplitEnd}; intersectMarkerRange (poSource, markers); nSplitStart = markers[0]; nSplitEnd = markers[1]; if (poSource.onTruncate (nSplitStart, nSplitEnd, true, eMarkerSplitReason)) { poMarkerEnd.setMarkerIndex (nSplitStart); } } else { // Otherwise, the span is completely contained within the marker, and // does not abut either end of the marker's range. Perform a true split. poNewMarker = poSource.onSplit (nSplitStart, nSplitEnd, eMarkerSplitReason); if (poNewMarker != null) { TextPosnBase oNewStart = new TextPosnBase (this, nSplitEnd); oNewStart.tighten (true); rangeMarkerInternal (oNewStart, poMarkerEnd, poNewMarker); poMarkerEnd.setMarkerIndex (nSplitStart); } } } return poNewMarker; } public boolean coalesceMarker (TextMarker poFirst, TextMarker poOther) { // Coalesce only if the marker allows it. if (! poFirst.onCoalesce (poOther)) { return false; } // Determine the start and end of each marker's range. Fake empty ranges // for position markers. PosnMarker poFirstStart = poFirst.getLocation(); int nFirstStart = poFirstStart.index(); PosnMarker poFirstEnd = poFirstStart.getMate(); int nFirstEnd = (poFirstEnd == null) ? nFirstStart : poFirstEnd.index(); PosnMarker poOtherStart = poOther.getLocation(); int nOtherStart = poOtherStart.index(); PosnMarker poOtherEnd = poOtherStart.getMate(); int nOtherEnd = (poOtherEnd == null) ? nOtherStart : poOtherEnd.index(); // Compute the full range extent. int nStart = (nFirstStart < nOtherStart) ? nFirstStart : nOtherStart; int nEnd = (nFirstEnd > nOtherEnd) ? nFirstEnd : nOtherEnd; // Adjust the marker's start only if absolutely necessary if (nStart != nFirstStart) { poFirstStart.setMarkerIndex (nFirstStart); } // If the marker is not a range marker, make it into one by creating a // mate position and linking the two. if (poFirstEnd == null) { poFirstEnd = new PosnMarker (poFirst); poFirstEnd.position (TextPosn.POSN_AFTER); poFirstEnd.index (nFirstEnd); poFirstStart.setMate (poFirstEnd); poFirstEnd.setMate (poFirstStart); } // If it is a range marker, simply adjust its end. else if (nEnd != nFirstEnd) { poFirstEnd.setMarkerIndex (nFirstEnd); } // Dispatch the other marker by removing it or moving it to the end of // the range and giving it a zero length. if (poOther.getAutoRemove()) { removeMarker (poOther, true); } else { poOtherStart.setMarkerIndex (nEnd); if (poOtherEnd != null) { poOtherEnd.setMarkerIndex (nEnd); } } return true; } public void removeMarker (TextMarker poRemove, boolean bEditOnly) { PosnMarker poLocation = poRemove.getLocation(); if (poLocation != null) { //PosnMarker poMate = poLocation.getMate(); poRemove.setLocation (null); } poRemove.onRemove (bEditOnly); } public TextMarker findRangeMarkerOver (int nIndex, int nSpan /*, ObjType nType */) { int nEnd = nIndex + nSpan; TextPosn poPosn = mpoAssociated; while (poPosn != null) { if (poPosn.isMarkerPosition()) { PosnMarker poLocation = (PosnMarker) poPosn; PosnMarker poMate = poLocation.getMate(); if (poMate != null) { if ((poLocation.index() <= nIndex) && (poMate.index() >= nEnd)) { TextMarker poMarker = poLocation.getMarker(); // if ((nType == 0) || poMarker.IsDerivedFrom (nType)) { return poMarker; // } } } } poPosn = poPosn.mpoNext; } return null; } // proprietary: (for use by class TextDisplay) void textDisplaySet (TextDisplay poTextDisplay) { mpoDisplay = poTextDisplay; } // proprietary: (for use by class TextLoadText) List items () { // TODO: obsolete? return moItems; } // proprietary: void suppressAutoLoad (boolean bSuppress) { if (bSuppress) { mnSuppressAutoLoad++; } else { mnSuppressAutoLoad--; } } boolean isAutoLoadSuppressed () { return mnSuppressAutoLoad != 0; } public void debug () { debug (0); } void debug (int indent) { indent++; System.out.println (Pkg.doIndent (indent) + "Text stream content:"); for (int i = 0; i < moItems.size(); i++) { getItem(i).debug (indent); } } //---------------------------------------------------------------------- // // UpdateDisplay: Convenience functions to check for // existence of a display pointer and update the display. // //---------------------------------------------------------------------- protected boolean updateDisplay (boolean bJustifyOnly) { boolean bFit = true; if (mpoDisplay != null) { if (bJustifyOnly) { mpoDisplay.updateJustify(); } else { bFit = mpoDisplay.update(); } } return bFit; } protected boolean updateDisplay () { return updateDisplay (false); } //---------------------------------------------------------------------- // // CleanupContent: Allows derived class to invoke premature // cleanup, in case content is somehow dependent on something // in the derived class. Must be used very carefully (only // at destruction time), as it leaves the text stream in an // uninitialized state. // //---------------------------------------------------------------------- protected void cleanupContent (boolean bReinitialize) { if (mpoDisplay != null) { mpoDisplay.detach (this); mpoDisplay = null; } TextPosn poPosn = mpoAssociated; while (poPosn != null) { TextPosn poCleanup = poPosn; poPosn = poPosn.mpoNext; if (poCleanup.isMarkerPosition()) { // Marker positions are a bit tricky to clean up. One or two such // positions may refer to the same marker object, which must be deleted // (but not deleted twice). In addition, no-one owns the marker position // objects, so they must be deleted as well. On the first position of a // pair, we detach the second position and delete the marker. Later, // we'll recognize the dangling position and delete it. PosnMarker poLocation = (PosnMarker) poCleanup; TextMarker poMarker = poLocation.getMarker(); if (poMarker != null) { // first position for this marker poMarker.setLocation (null); // prevent deletion of both posn's in RemoveMarker() removeMarker (poMarker, false); // remove marker (but keep both positions) PosnMarker poMate = poLocation.getMate(); if (poMate != null) { poMate.setMarker (null); // prevent mate from trying to delete marker again } } poCleanup.mpoStream = null; // so it doesn't try to remove itself from our list } else { // explicitly delete this position poCleanup.cleanup(); poCleanup.mpoNext = null; } } mpoAssociated = null; // ClearItemArray (moItems); moItems.setSize (0); mpoTempAttr = null; if (bReinitialize) { initialize (null); } } //---------------------------------------------------------------------- // // Initialize: Initialize the object to a clean (empty) // state. Note the creation of the end marker in the list. // //---------------------------------------------------------------------- private void initialize (String psSourceText) { mpoAssociated = null; mnCount = 0; mpoDisplay = null; mpoTempAttr = null; mnMaxSize = 0; mnSuppressAutoLoad = 0; mbAllowNewLines = true; // The item list always contains an end marker. This saves us a lot of // empty array and bounds checking. It also always starts with a set of // default attributes. StrText poText = null; StrEnd poEnd = null; int nSize = 1; // assume only end // Try to create the 1 or 2 items and size the array. poEnd = StrEnd.newObject(); // always create end marker // If there is text, create an item for it as well. if ((psSourceText != null) && (psSourceText.length() > 0)) { poText = new StrText (psSourceText); nSize++; } moItems.setSize (nSize); // Fill in the 1 or 2 items. mnCount = 0; // don't count end marker if (poText != null) { moItems.set(nSize - 2, poText); mnCount = poText.count(); } moItems.set(nSize - 1, poEnd); } //---------------------------------------------------------------------- // // InsertTest: Confirm there is enough space for the // insertion. Throw an exception if not. If this function // returns, everything is OK. // //---------------------------------------------------------------------- private void insertTest (int nSize) { if ((mnMaxSize > 0) && (nSize > spaceLeft())) { // throw jfExFull (TEXT.ERR_INSERT_TOO_LARGE); // TODO: error handling } } //---------------------------------------------------------------------- // // Insert: Insert an object into the stream at the given // position. The caller creates the object with operator // new. // //---------------------------------------------------------------------- private void insert (TextPosnBase oPosn, StrItem poInsert, boolean bTestSpace, int eMarkerSplitReason) { // Must be created via new int nIndex = oPosn.index(); int nCount = poInsert.count(); boolean bFit; insertTest (nCount); bFit = insert (oPosn, poInsert, UPDATE_NORMAL, eMarkerSplitReason); if (bTestSpace && (! bFit)) { TextPosnBase oRemovePosn = new TextPosnBase (this, nIndex); oRemovePosn.deleteAhead (nCount); // throw ExFull (JF_TEXT_ERR_BLOCK_FULL); // TODO: return; } } //---------------------------------------------------------------------- // // Insert: Like above, but gives caller control over update options. // Also cleans up the given item if there is an exception. // //---------------------------------------------------------------------- private boolean insert (TextPosnBase oPosn, StrItem poInsert, int eUpdate, int eMarkerSplitReason) { int nCount = poInsert.count(); // If we are positioned at the beginning of the item, simply insert a new // entry in the list before the current item. if (oPosn.minor() == 0) { moItems.add (oPosn.major(), poInsert); } // Otherwise, we'll be forced to split the item. This will add two new // entries to the list (and replace the one in question). else { StrItem poItem = getItem(oPosn.major()); // Split the current item, creating two new ones (old one still exists). StrItem[] split = poItem.split (oPosn.minor()); StrItem poBefore = split[0]; StrItem poAfter = split[1]; nCount -= poItem.count() - (poBefore.count() + poAfter.count()); // Before anything else, see if we can update the array size. moItems.setSize (moItems.size() + 2); // Shift all entries after the current one two positions down to make // room for the new items. for (int i = moItems.size() - 3; i > oPosn.major(); i--) { moItems.set (i+2, moItems.get (i)); } // Put in the new items, in order. moItems.set (oPosn.major(), poBefore); moItems.set (oPosn.major()+1, poInsert); moItems.set (oPosn.major()+2, poAfter); } // Insertions of text and objects are the only true logical insertions. // All others don't insert a user positionable object. int eItem = poInsert.strType(); boolean bOther = (eItem != TextItem.CHAR) && (eItem != TextItem.OBJECT); return completeInsert (oPosn.mnIndex, nCount, bOther, eMarkerSplitReason, false, eUpdate); } //---------------------------------------------------------------------- // // Delete: Internal deletion at a given position, by count // with optional reconciliation. // //---------------------------------------------------------------------- private void delete (TextPosnBase oPosn, int nDelete, boolean bRaw, int eUpdate) { if (nDelete == 0) { return; } TextPosnBase oAttrPosn = new TextPosnBase(); boolean bPara = false; if (! bRaw) { TextPosnBase oSearch = new TextPosnBase (this, oPosn.index() + nDelete); while (oSearch.gt (oPosn)) { switch (oSearch.prev()) { case TextItem.ATTR: if (oAttrPosn.stream() == null) { oAttrPosn = oSearch; } break; case TextItem.PARA: bPara = true; break; } } } // Suppress formatting (layout) if it looks like we'll be doing multiple // operations on this stream. //TextStream poSuppressStream = null; if ((oAttrPosn != null) || bPara) { //poSuppressStream = this; } // LayoutSuppressor oSuppressLayout (poSuppressStream); // Delete any auto-delete markers in the deletion range. TBD: may need // to investigate the performance of this for interactive character // deletions. int i; int nDelStart = oPosn.index(); // TBD: could be invalid if frame loaded above int nDelEnd = nDelStart + nDelete; TextRange oMarkerRange = new TextRange (this, nDelStart, nDelEnd); // TBD: adjustment by 1 below? Storage oMarkers = new Storage(); oMarkerRange.enumerateMarkers (oMarkers, true, true, false); // TODO: oMarkers should be returned/null for (i = 0; i < oMarkers.size(); i++) { TextMarker poMarker = (TextMarker) oMarkers.get (i); if (poMarker.getAutoRemove()) { PosnMarker poLocation = poMarker.getLocation(); PosnMarker poMate = poLocation.getMate(); int nMarkerStart = poLocation.index(); int nMarkerEnd = (poMate == null) ? nMarkerStart : poMate.index(); if (nMarkerStart > nMarkerEnd) { int nSwap = nMarkerStart; nMarkerStart = nMarkerEnd; nMarkerEnd = nSwap; } if ((nMarkerStart >= nDelStart) && (nMarkerEnd <= nDelEnd)) { removeMarker (poMarker, true); oMarkers.set (i, null); } } } TextAttr poParaAttr = null; TextRange oRange = null; // No raw or no attrs found: simply delete. if (oAttrPosn.stream() == null) { delete2 (oPosn, nDelete, eUpdate); } // "Normal" delete: Delete around the last attribute change. else { int nBefore = oAttrPosn.index() - oPosn.index(); int nAfter = nDelete - nBefore - 1; if ((nBefore == 0) && (nAfter == 0)) { nAfter = 1; // delete at least 1 char } oAttrPosn.next(); delete2 (oAttrPosn, nAfter, UPDATE_NORMAL); delete2 (oPosn, nBefore, UPDATE_NORMAL); } // We may delete a paragraph mark, in which case we could end up with a // paragraph attribute change in the middle of the collapsed paragraph. // This will be reconciled as we go. // Set up a range for dealing with paragraph attributes later. if (bPara) { oRange = new TextRange(); oRange.associate (this, oPosn.index(), oPosn.index() + nDelete); oRange.grabPara(); // Now, if there is text between the given position and our range (some // part of a prev paragraph left), use the paragraph attributes in effect // at the start of that paragraph for the entire range, to avoid a // paragraph attribute change in the middle of a paragraph. oRange.tighten(); if (oRange.start().index() < oPosn.index()) { TextAttr oBefore = oRange.start().attribute(); poParaAttr = new TextAttr (oBefore); poParaAttr.isolatePara (true, hasLegacyPositioning()); } // Run through markers overlapping the deletion range and adjust any // paragraph markers. for (i = 0; i < oMarkers.size(); i++) { TextMarker poMarker = (TextMarker) oMarkers.get (i); if ((poMarker != null) && poMarker.isParaMarker()) { PosnMarker poLocation = poMarker.getLocation(); PosnMarker poMate = poLocation.getMate(); if (poMate != null) { oMarkerRange.associate (this, poLocation.index(), poMate.index()); oMarkerRange.grabPara(); poLocation = (PosnMarker) oMarkerRange.start(); poMate = (PosnMarker) oMarkerRange.end(); } } } } // Apply attributes if required. if (poParaAttr != null) { if (oRange != null) { oRange.attribute (poParaAttr); } } else { if (! bRaw) { // Watson 1089827: Deleting all the content of the last paragraph leaves // no text to retain that paragraph's attribute and the caret inherits // the attributes of the previous one. This could lead to the cursor // changing position if the previous paragraph had a different horizontal // alignment. So now we set up a temporary attribute if the last text is // deleted. Note that an earlier implementation of this fix would insert // temporary attributes any time all the text within any attribute range // was deleted. However, that led to unexpected results in the middle of // text--new typing could get attributes of deleted text. TextPosnBase oTest = new TextPosnBase (oPosn); if (oTest.prev (true) == TextItem.ATTR) { int eNext = oTest.next (true); if (eNext == TextItem.UNKNOWN) { updateTempAttr (new TextRange (this, oTest.mnIndex, oTest.mnIndex)); eUpdate |= UPDATE_KEEP_TEMP_ATTR; } } } completeUpdate (eUpdate); } } //---------------------------------------------------------------------- // // Delete2: delete by count at a given position. // //---------------------------------------------------------------------- private void delete2 (TextPosnBase oPosn, int nDelete, int eUpdate) { // Make our own local copy of the item count (truncated by the maximum // available). int nActual = mnCount - oPosn.mnIndex; if (nActual > nDelete) { nActual = nDelete; } if (nActual == 0) { return; } Storage oMarkers = new Storage(); int nStart = oPosn.index(); int nEnd = nStart + nDelete; enumerateOverlappingRangeMarkers (nStart, nEnd, oMarkers); for (int i = 0; i < oMarkers.size(); i++) { TextMarker poMarker = (TextMarker) oMarkers.get (i); int[] markers = new int[] {nStart, nEnd}; if (intersectMarkerRange (poMarker, markers)) { poMarker.onDeleteContent (markers[0], markers[1]); } } int nMajor = oPosn.major(); int nMinor = oPosn.minor(); int nThisTime = 0; // number deleted in one iteration Storage oDeleteItems = new Storage(); // defer RemoveRef() until the end // TODO: needed? // Iterate through items until there's nothing left to delete. for (int nLeft = nActual; nLeft > 0; nLeft -= nThisTime) { StrItem poItem = getItem (nMajor); int nCount = poItem.count(); if ((nMinor > 0) || (nLeft < nCount)) { // If the deletion starts or ends in this item, do a partial deletion. nThisTime = nCount - nMinor; if (nThisTime > nLeft) { nThisTime = nLeft; } poItem.delete (nMinor, nThisTime); nMinor = 0; // for next iteration nMajor++; } // Otherwise, this item is completely included in the deletion. else { moItems.remove (nMajor); // Hang on to a ref to each deleted item because it may be referenced in // CompleteDelete(). TODO: can this happen in Java? oDeleteItems.add (poItem); nThisTime = nCount; // removed the whole thing } } completeDelete (oPosn.mnIndex, nActual, eUpdate | UPDATE_FULL_SUPPRESS); } //---------------------------------------------------------------------- // // LoadText: Replace this string's contents with text from // the given loader object. // //---------------------------------------------------------------------- private void loadText (LoadText oLoader) { // Note: code to maintain consistency in the event of an exception // removed in the interest of performance. RD 31-Oct-96. int i; // Delete the old items int nOldSize = moItems.size() - 1; // exclude end marker StrItem poEndMarker = getItem (nOldSize); // save end marker moItems.setSize (nOldSize); // don't clear it // ClearItemArray (moItems); // Now copy the items from the loader int nNewSize = oLoader.size(); int nNewCount = 0; moItems.setSize (nNewSize + 1); // allow for end marker for (i = 0; i < nNewSize; i++) { StrItem poItem = oLoader.nextItem(); moItems.set (i, poItem); nNewCount += poItem.count(); } moItems.set (i, poEndMarker); mnCount = nNewCount; mnMaxSize = oLoader.maxSize(); mbAllowNewLines = oLoader.allowNewLines(); // Update the positions to refer to similar locations in the new stream. // TBD: does this really update the positions or leave them dangling? completeUpdate (UPDATE_NORMAL, false, 0, 0, false); setEmbedPositions(); } //---------------------------------------------------------------------- // // PosnMove: Common code for moving forward or back one // position, loading null frames if required. // //---------------------------------------------------------------------- private int[] posnMove (TextPosnBase oPosn, boolean bNext, int eNullFrameMode, boolean bTestOnly) { int eReturn = TextItem.UNKNOWN; int cSpanned = '\0'; int nMajor = oPosn.major(); int nMinor = oPosn.minor(); int nIndex = oPosn.mnIndex; boolean bFirst = true; for (; ; ) { // Typically this loop iterates once per call, breaking part way through. // It will iterate a second time if a null frame is encountered. The // first iteration loads the frame and the second interation steps into // it. If the frame has no content or we are skipping null frames, it // may iterate more times. int nOldIndex = nIndex; int nCharMinor = nMinor; StrItem poItem = null; // If advancing, the item type is that of the item we're currently // pointing at. Advance the indexes after obtaining the type. if (bNext) { if (nIndex >= posnCount()) { return null; } poItem = getItem (nMajor); eReturn = poItem.strType(); nIndex++; nMinor++; if (nMinor >= poItem.count()) { nMajor++; nMinor = 0; } } // If backing up the item type may be in the current item (if the minor // index is greater than zero) or in the previous item (if it is zero). // Adjust the indexes and then determine the item type. else { if (nIndex == 0) { return null; } nIndex--; if (nMinor > 0) { nMinor--; poItem = getItem (nMajor); } else { nMajor--; poItem = getItem (nMajor); nMinor = poItem.count() - 1; } nCharMinor = nMinor; eReturn = poItem.strType(); } // Most common case: this is not a null frame (or if null frames are // allowed) can break the loop now. if ((eReturn != TextItem.NULL_FRAME) || (eNullFrameMode == TextNullFrame.MODE_ALLOW)) { cSpanned = poItem.charAt (nCharMinor); break; } if ((eNullFrameMode == TextNullFrame.MODE_STOP) || ((mnSuppressAutoLoad > 0) && (eNullFrameMode != TextNullFrame.MODE_IGNORE))) { // If we got here it is a null frame. If stopping on null frames or // we've suppressed null frame loading, fake end of stream. return null; } // Implicit: if null frames are being skipped, just let the loop iterate. if (eNullFrameMode == TextNullFrame.MODE_LOAD) { // Loading null frames... // First create a safe position (TextPosn, not TextPosnBase) on the // correct side of the null frame. This will maintain its relative // position in the stream after the null frame is loaded. If the // position is between an attribute and the null frame, ensure that it is // on the far side of the attribute, with call to Tighten(). That // attribute may be replaced when the frame gets loaded, or it may be // removed altogether if it turns out to be redundant. TextPosn oSafeIter = new TextPosn(); if (bNext) { oSafeIter.associate (oPosn.stream(), nOldIndex, TextPosn.POSN_BEFORE); oSafeIter.tighten (false); } else { oSafeIter.associate (oPosn.stream(), nOldIndex, TextPosn.POSN_AFTER); oSafeIter.tighten (true); } // Remember whether the safe position changed to account for an attribute. boolean bTightened = oSafeIter.index() != nOldIndex; // If positioning backwards, each inserted null frame will push out the // original position. Record the original position's index in another // safe position, to survive the null frame insertion. TextPosn oSafePosn = null; if (! bNext) { oSafePosn = new TextPosn (oPosn.stream(), oPosn.index(), TextPosn.POSN_AFTER); } // Get a pointer to the null frame and load it. TextNullFrame poNullFrame = poItem.nullFrameAt (nMinor); assert (poNullFrame != null); // poNullFrame.Load(); // TODO: // If the safe position moved over an attribute, restore its position. // Note that if the attribute was removed, this Tighten() call does // nothing. if (bTightened) { oSafeIter.tighten (bNext); } // If positioning forward and this is the first iteration, re-associate // the given position to its original index (loading the null frame may // have invalidated its contents). if (bNext) { if (bFirst) { bFirst = false; oPosn.associate (oPosn.stream(), oSafeIter.index()); } } // If positioning ahead, reset the original position's index to account // for the loaded frame's content. else { oPosn.index (oSafePosn.index()); } // Update the indexes and try again. nMajor = oSafeIter.major(); nMinor = oSafeIter.minor(); nIndex = oSafeIter.mnIndex; } } if (! bTestOnly) { // If not just testing the item type, update the position's content to // reflect the new location. oPosn.mnMajor = nMajor; oPosn.mnMinor = nMinor; oPosn.mnIndex = nIndex; if (eReturn != TextItem.ATTR) { oPosn.cleanupTarget(); } } int[] result = new int[2]; result[0] = eReturn; result[1] = (int) cSpanned; return result; } //---------------------------------------------------------------------- // // PosnSearchForward: Search forward in the stream for an // item of a specified type. Update the given position // object. // //---------------------------------------------------------------------- private boolean posnSearchForward (TextPosnBase oPosn, int eSearch, int eNullFrameMode) { // Optimized loop to reduce needless iteration. // First, try a single item move from the current position. for (;;) { int eType = posnNextType (oPosn, eNullFrameMode); if (eType == TextItem.UNKNOWN) { return false; } if (eType == eSearch) { return true; } // Was wrong type: May now be positioned at end; eliminate this case // before optimization. StrItem poItem = getItem (oPosn.major()); if (poItem.strType() == TextItem.UNKNOWN) { return false; } if (oPosn.minor() > 0) { // Optimization: Rather than iterate through all the remaining individual // elements (characters) of this item, skip to the next major item before // trying again. oPosn.mnIndex = oPosn.index() - oPosn.minor() + poItem.count(); oPosn.mnMajor++; oPosn.mnMinor = 0; } } } private boolean posnSearchForward (TextPosnBase oPosn, int eSearch) { return posnSearchForward (oPosn, eSearch, TextNullFrame.MODE_LOAD); } //---------------------------------------------------------------------- // // PosnSearchBackward: Search backward in the stream for an // item of a specified type. Update the given position // object. // //---------------------------------------------------------------------- private boolean posnSearchBackward (TextPosnBase oPosn, int eSearch, int eNullFrameMode) { for (;;) { // Optimized loop to reduce needless iteration. // First, try a single item move from the current position. int eType = posnPrevType (oPosn, eNullFrameMode); if (eType == TextItem.UNKNOWN) { return false; } if (eType == eSearch) { return true; } if (oPosn.minor() > 0) { // Optimization: Rather than iterate through all the individual elements // (characters) of this item, skip to its start before trying again. oPosn.mnIndex -= oPosn.minor(); oPosn.mnMinor = 0; } } } private boolean posnSearchBackward (TextPosnBase oPosn, int eSearch) { return posnSearchBackward (oPosn, eSearch, TextNullFrame.MODE_LOAD); } //---------------------------------------------------------------------- // // RangeAttr: change attributes over a range of characters. // // Note: this method assumes that the start position is a // BEFORE position and the end position is an AFTER position. // It will update the positions so that they are nested // within the inserted attribute change objects. In this // way, there aren't artificial attribute changes at the // start and end of the range. // //---------------------------------------------------------------------- private void rangeAttr (TextPosn oStart, TextPosn oEnd, TextAttr oNewAttr, int eUpdate) { // Start by determining the attributes to restore after this change and // the full attributes to go into effect at the start position. TextAttr poFullAttr = new TextAttr (oNewAttr, moGfxSource); TextAttr poRestoreAttr = null; TextAttr poPrevAttr = posnAttrPtr (oStart); if (poPrevAttr != null) { poFullAttr.addDisabled (poPrevAttr); if (oEnd.mnIndex < mnCount) { poRestoreAttr = posnAttrPtr (oEnd); } } StrAttr poItemChange = null; StrAttr poItemRestore = null; boolean bRestoreInserted = false; // Try to insert a new attribute change at the start of the range and an // attribute restore item at the end of the range. Suppress the change // at the end of the range if it is the end of the stream (to avoid a lot // of redundant changes there). updateTempAttr (null); // clear old attr range regardless poItemChange = new StrAttr (poFullAttr); insert (oStart, poItemChange, UPDATE_SUPPRESS_RECONCILE, TextMarker.SPLIT_REASON_UNKNOWN); // CleanupDeleteAhead oStartDeleteAhead (((TextPosnBase) (oStart))); if (poRestoreAttr != null) { poItemRestore = new StrAttr (poRestoreAttr, moGfxSource); insert (oEnd, poItemRestore, UPDATE_SUPPRESS_RECONCILE, TextMarker.SPLIT_REASON_UNKNOWN); bRestoreInserted = true; } // Truncate the range to exclude the inserted attribute objects. Because // we disabled reconciliation, we know the inserted items still exist. oStart.next(); if (bRestoreInserted) { oEnd.prev(); } // Now, we have to look at the internal attribute changes. We'll want to // go through and override any with the (partial) contents of oNewAttr. int nSearch = oStart.major(); int nIndex = oStart.mnIndex; int nSize = moItems.size(); while ((nSearch < oEnd.major()) && (nSearch < nSize)) { StrItem poItem = getItem (nSearch); if (poItem.strType() == TextItem.ATTR) { poItem.overrideAttr (oNewAttr); } nSearch++; nIndex += poItem.count(); } // If the range is empty, we actually insert a temporary redundant // attribute change. Determine this now. if (oStart.mnIndex == oEnd.mnIndex) { updateTempAttr (new TextRange (this, oStart.mnIndex, oEnd.mnIndex)); } // Reconcile any attributes made redundant by the above loop. But, if // the range was empty, leave the pair in place so that text may be // inserted with the new attributes. if (display() != null) { display().updateOther (this, oStart.index(), oEnd.index() - oStart.index()); } completeUpdate (eUpdate | UPDATE_KEEP_TEMP_ATTR); } //---------------------------------------------------------------------- // // Remove: Remove one item from the list. Delete the item. // Update positions, given the index of the item. // //---------------------------------------------------------------------- private void remove (int nItemIndex, int nPosnIndex, int nRemoveCount) { //StrItem poItem = getItem (nItemIndex); moItems.remove (nItemIndex); mnCount -= nRemoveCount; updatePositions (nPosnIndex, nRemoveCount, false, UPDATE_POSN_FORCE); } //---------------------------------------------------------------------- // // SetEmbedPositions: Run through the stream looking for // fields and embedded objects. Set the positions on any // encountered. This function need be called only when new // embedded objects and fields are added to the stream. Once // the positions are established, they will track updates. // //---------------------------------------------------------------------- private void setEmbedPositions () { int nIndex = 0; int nSize = moItems.size(); for (int i = 0; i < nSize; i++) { StrItem poItem = getItem (i); TextField poField = poItem.fieldAt (0); TextEmbed poEmbed = poItem.embedAt (0); if (poField != null) { poField.positionSet (this, nIndex); } else if (poEmbed != null) { poEmbed.position (new TextPosn (this, nIndex, TextPosn.POSN_AFTER)); } nIndex += poItem.count(); } } //---------------------------------------------------------------------- // // CompleteInsert: Complete an insert on the stream. Standard // update completion, plus record the insert with the display // (which might be able to optimize it). // //---------------------------------------------------------------------- private boolean completeInsert (int nIndex, int nChange, boolean bOther, int eMarkerSplitReason, boolean bTighten, int eUpdate) { completeUpdate (eUpdate, true, nIndex, nChange, bTighten); // After updating positions, split any SPLIT_INSERT markers. if ((nChange > 0) && (eMarkerSplitReason != TextMarker.SPLIT_REASON_UNKNOWN)) { TextRange oRange = new TextRange (this, nIndex, nIndex + nChange); oRange.tighten(); if (! oRange.isEmpty()) { int nSplitStart = oRange.start().index(); int nSplitEnd = oRange.end().index(); Storage oMarkers = new Storage(); oRange.enumerateMarkers (oMarkers); // TODO: for (int i = 0; i < oMarkers.size(); i++) { TextMarker poMarker = (TextMarker) oMarkers.get (i); if (poMarker.isRangeMarker() && (poMarker.getSplitState() == TextMarker.SPLIT_INSERT)) { poMarker.forceSplit (nSplitStart, nSplitEnd, eMarkerSplitReason); } } } } // Now, update the display associated with this stream (if there is one). // Note that this stream may be subordinate to the stream that created // the display. Update handles text block overflow. boolean bFit = true; if ((mpoDisplay != null) && ((eUpdate & UPDATE_SUPPRESS_DISPLAY) == 0)) { if (bOther) { bFit = mpoDisplay.updateOther (this, nIndex, 0); } else { bFit = mpoDisplay.updateInsert (this, nIndex, nChange); } } // The derived class may need to be notified of the update. updateNotification(); return bFit; } //---------------------------------------------------------------------- // // CompleteDelete: Complete a deletion on the stream. // Standard update completion, plus record the delete with // the display (which might be able to optimize it). // //---------------------------------------------------------------------- private boolean completeDelete (int nIndex, int nChange, int eUpdate) { completeUpdate (eUpdate, false, nIndex, nChange, false); // Now, update the display associated with this stream (if there is one). // Note that even a deletion (of spaces) could cause a word to move down // and no longer fit. boolean bFit = true; if ((mpoDisplay != null) && ((eUpdate & UPDATE_SUPPRESS_DISPLAY) == 0)) { bFit = mpoDisplay.updateDelete (this, nIndex, nChange); } // Coalesce any markers at this position coalesceAdjacentMarkers (nIndex); // The derived class may need to be notified of the update. updateNotification(); return bFit; } //---------------------------------------------------------------------- // // CompleteUpdate: Complete an update of the stream. Handle // recinciliation and update all position objects. // //---------------------------------------------------------------------- private void completeUpdate (int eUpdate) { completeUpdate (eUpdate, false, 0, 0, false); } private void completeUpdate (int eUpdate, boolean bInsert, int nIndex, int nChange, boolean bTighten) { // Update the total count of items. if (bInsert) { mnCount += nChange; } else { mnCount -= nChange; } // Determine how to update the positions. If the insertion has created // an extra attribute item at its end, to restore attributes, we mustn't // include that attribute change in the position movement. int nPosnChange = nChange; int eUpdatePosn = UPDATE_POSN_NORMAL; if (bTighten) { eUpdatePosn = UPDATE_POSN_TIGHTEN; if (bInsert && (nPosnChange > 0)) { nPosnChange--; } } // Update the position objects first, to reflect any change (insert or // delete) that might have been made. updatePositions (nIndex, nPosnChange, bInsert, eUpdatePosn); // Delete the tempoary attributes after positions have moved. if ((eUpdate & UPDATE_KEEP_TEMP_ATTR) == 0) { updateTempAttr (null); } // If reconciliation is requested, run through the text stream and // reconcile anything that is redundant. Two consecutive string items // are considered redundant--they can be merged into a single item. // Attributes are a little more complicated. If there are two // consecutive attribute changes, the first one is redundant and can be // discarded. In addition, an attribute change may duplicate the // contents of its predecessor somewhere earlier in the stream. Such a // change can also be removed. if ((eUpdate & UPDATE_SUPPRESS_RECONCILE) == 0) { int nReconcileIndex = 0; // Attribute bookkeeping. Keep track of the previous attribute structure // in the stream. TextAttr poPrevAttr = null; int nLastAttr = 0; int nLastAttrIndex = 0; // Look at the items in pairs, reconciling the second against the first. for (int i = 1; i < moItems.size(); ) { boolean bAdvance = true; StrItem poPrev = getItem (i-1); StrItem poNext = getItem (i); // We can reconcile if they have the same types and support // reconciliation. if ((poPrev.strType() == poNext.strType()) && (poPrev.canCoalesce (poNext))) { int nPrev = poPrev.count(); int nNext = poNext.count(); int nOldCount = nPrev + nNext; // Coalesce type I: items cancel each other out completely and both need // to be removed. This actually causes poPrev to back up 1 for the next // iteration of the loop and poNext will be the item after the removed // pair. if (poPrev.coalesce (poNext, nReconcileIndex)) { // The actual reconciliation remove (i, nReconcileIndex + nPrev, nNext); remove (i - 1, nReconcileIndex, nPrev); if (i > 1) { i--; // keep prev/next pair legit } } // Coalesce type II (more common): contents of poNext are somehow added // to poPrev. Can remove poNext from the list and retest poPrev against // the new next item in the next iteration. else { nPrev = poPrev.count(); // may have changed int nDiff = nOldCount - nPrev; remove (i, nReconcileIndex + nPrev, nDiff); } bAdvance = false; // retest reconciled item } // If we can't reconcile, check attributes. Note: we don't test for // duplicate attributes if we did reconcile, because we may still be able // to reconcile again (e.g., three consecutive attribute changes, where // the third duplicates an attribute setting earlier in the stream). else { TextAttr poAttr = poPrev.attrAt (0); // Not an attribute change: clear out the last attribute index if this is // not the end marker. if (poAttr == null) { if (poPrev.strType() != TextItem.UNKNOWN) { nLastAttr = 0; } } // Attribute change: else { if ((poPrevAttr != null) && (poPrevAttr != poAttr) && (poPrevAttr == poAttr)) { // If there is a previous attribute change AND we have not already backed // up to it AND it matches this one, remove this one (poPrev). This may // now allow for adjacent markers to be coalesced. Note that we actually // back up in such a case, because the item before the (redundant) // attribute change might now reconcile with the next item. This means // rechecking the item before this one (poPrev at index i-1 after i // decremented). i--; // back up remove (i, nReconcileIndex, poPrev.count()); coalesceAdjacentMarkers (nReconcileIndex); nReconcileIndex -= getItem(i-1).count(); if (i == 1) { poPrevAttr = null; // new poPrev at start ... // ... no previous attr } bAdvance = false; } // True attr change: record this as the previous attribute change and // record this for possible deletion later. else { poPrevAttr = poAttr; if (nLastAttr == 0) { // handle several consecutive nLastAttr = i - 1; // ... attrs at the end nLastAttrIndex = nReconcileIndex; } } } } // If we reconciled or deleted an attribute item, stay at same position // and compare against the new next item. Otherwise, move on to the // next item. if (bAdvance) { i++; nReconcileIndex += poPrev.count(); } } // If the last item(s) in the stream (before the end marker) was an // attribute change, and it wasn't the only stream item, and it isn't // flagged as a temporary attribute pair start, remove it. if (nLastAttr > 0) { int nSize = moItems.size(); while (nLastAttr < nSize - 1) { // leave end marker StrItem poItem = getItem (nLastAttr); int nCount = poItem.count(); if (poItem.attrState() == Pkg.ATTR_STATE_OPEN) { nLastAttrIndex += nCount; nLastAttr++; // skip } else { remove (nLastAttr, nLastAttrIndex, nCount); nSize--; } } } } } //---------------------------------------------------------------------- // // UpdatePositions: Update all position objects after a // change in the stream. // //---------------------------------------------------------------------- private void updatePositions (int nIndex, int nChange, boolean bInsert, int eUpdate) { // If this is not a "real" update, don't waste the time. if ((nChange == 0) && (eUpdate != UPDATE_POSN_FORCE)) { return; } if (nIndex > posnCount()) { nIndex = posnCount(); } // Determine the safe change start. Some operations may cause items to // coalesce, so we need to update all positions before the item which // spans the given index. int nChangeStartIndex = 0; for (int i = 0; i < moItems.size(); i++) { int nItemEnd = nChangeStartIndex + getItem(i).count(); if (nIndex <= nItemEnd) { break; // TBD: probably could be simply < } nChangeStartIndex = nItemEnd; } // Update each position object associated with this stream. for (TextPosn poPosn = mpoAssociated; poPosn != null; poPosn = poPosn.mpoNext) { if (poPosn.mnIndex >= nChangeStartIndex) { // Insert: we will alter the index any object that is physically after // the current position, or is at the current position, and flagged with // POSN_AFTER. if (bInsert) { if ((poPosn.mnIndex > nIndex) || ((poPosn.mnIndex == nIndex) && (poPosn.position() == TextPosn.POSN_AFTER))) { poPosn.mnIndex += nChange; } } // Delete: Any object that comes after will be brought back by the number // of items deleted (truncated at the change point). else { if (poPosn.mnIndex > nIndex) { int nMove = poPosn.mnIndex - nIndex; if (nMove > nChange) { nMove = nChange; } poPosn.mnIndex -= nMove; } } // Force a recalculation from the start of the stream. This is to // prevent an optimization in PosnUpdateStreamLoc() that assumes the // position's members are still self-consistent. poPosn.invalidate (eUpdate == UPDATE_POSN_TIGHTEN); } } } //---------------------------------------------------------------------- // // UpdateTempAttr: Remove the special state codes from any // existing temporary attribute items and install the // optional new temporary attribute pointer. // //---------------------------------------------------------------------- private void updateTempAttr (TextRange poNewTempAttr) { if (poNewTempAttr == mpoTempAttr) { return; } if (mpoTempAttr != null) { updateAttrRange (mpoTempAttr, Pkg.ATTR_STATE_NORMAL, Pkg.ATTR_STATE_NORMAL); mpoTempAttr = null; } if (poNewTempAttr != null) { mpoTempAttr = poNewTempAttr; updateAttrRange (mpoTempAttr, Pkg.ATTR_STATE_OPEN, Pkg.ATTR_STATE_CLOSE); } } //---------------------------------------------------------------------- // // UpdateAttrRange: set the states of attribute items just // outside a given range. // //---------------------------------------------------------------------- private void updateAttrRange (TextRange oRange, int eStart, int eEnd) { int nStart = oRange.position (TextRange.POSN_START).index(); int nEnd = oRange.position (TextRange.POSN_END).index(); if (nStart > 0) { nStart--; updateAttrState (nStart, eStart); } if (nEnd < mnCount) { updateAttrState (nEnd, eEnd); } } //---------------------------------------------------------------------- // // UpdateAttrState: set the states of a given attribute // items. // //---------------------------------------------------------------------- private void updateAttrState (int nIndex, int eState) { TextPosnBase oPosn = new TextPosnBase (this, nIndex); StrItem poItem = getItem (oPosn.mnMajor); poItem.attrState (eState); } //---------------------------------------------------------------------- // // UpdateGfxSource: update the text graphic source for all // items in the stream. // //---------------------------------------------------------------------- private void updateGfxSource () { int nSize = moItems.size(); for (int i = 0; i < nSize; i++) { getItem(i).gfxSource (moGfxSource); } } final StrItem getItem (int index) { return moItems.get (index); } //---------------------------------------------------------------------- // // SplitParaMarkers - Run through a given range of text // looking for paragraph breaks. For each one of these, // split any PARA_STATE_SPLIT markers. // //---------------------------------------------------------------------- private void splitParaMarkers (TextRange oRange, TextMarker poCandidate) { TextRange oMarkerRange = new TextRange (oRange); oMarkerRange.tighten(); TextPosnBase oParaPosn = new TextPosnBase (oMarkerRange.start()); // If there is a single candidate to split, prepopulate the marker array // with it. Otherwise, the array gets regenerated on each paragraph // break. Storage oMarkers = new Storage(); if (poCandidate != null) { oMarkers.add (poCandidate); } // The outer loop iterates by paragraph break. At each break, we'll // split all PARA_STATE_SPLIT markers that span it. // Determine the position after the paragraph break and any subsequent // attribute objects. This becomes the start of the seconf half of each // split. while (oParaPosn.nextPara (TextNullFrame.MODE_IGNORE)) { int nSplitEnd = oParaPosn.index(); // If this paragraph break is outside the range, get out. oParaPosn.tighten (false); if (oParaPosn.index() > oMarkerRange.end().index()) { break; } // Determine the position before the paragraph break and any preceding // attribute objects. This becomes the end of the first half of each // split. TextPosnBase oPrev = new TextPosnBase (oParaPosn); oPrev.prev(); oPrev.tighten (false); int nSplitStart = oPrev.index(); // Find all range markers that overlap the paragraph break; TextRange oParaRange = new TextRange (this, oPrev.index(), nSplitEnd); if (poCandidate == null) { oParaRange.enumerateMarkers (oMarkers, false, true, false); // TODO: } // The inner loop iterates once for each such marker. for (int i = 0; i < oMarkers.size(); i++) { TextMarker poMarker = (TextMarker) oMarkers.get (i); if (poMarker.getSplitState() == TextMarker.SPLIT_PARA) { poMarker.forceSplit (nSplitStart, nSplitEnd, TextMarker.SPLIT_REASON_PARA_MARKER); } } } } // Coalesce any markers that allow it, given an index into this stream. private void coalesceAdjacentMarkers (int nIndex) { Storage oBefore = new Storage(); Storage oAfter = new Storage(); // First build two arrays of marker pointers: one for markers that come // immediately before the given position and one for those that come // immediately after. In order to be included, the marker must be a // range marker, require splitting and not have a zero length. This // should keep the arrays very small, often empty. TextPosn poPosn = mpoAssociated; while (poPosn != null) { if (poPosn.isMarkerPosition()) { PosnMarker poLocation = (PosnMarker) poPosn; TextMarker poMarker = poLocation.getMarker(); if ((poMarker != null) && (poLocation == poMarker.getLocation()) && (poMarker.getAutoCoalesce())) { PosnMarker poMate = poLocation.getMate(); if ((poMate != null) && (poLocation.index() != poMate.index())) { if (poMate.index() == nIndex) { oBefore.add (poMarker); } else if (poLocation.index() == nIndex) { oAfter.add (poMarker); } } } } poPosn = poPosn.mpoNext; } // No after markers, no point in iterating through the before markers. if (oAfter.size() == 0) { return; } // Iterate through the before markers, looking for a match for each in // the after markers. for (int i = 0; i < oBefore.size(); i++) { TextMarker poBefore = (TextMarker) oBefore.get (i); for (int j = 0; j < oAfter.size(); j++) { // If this is an eligible pair, try to coalesce. TextMarker poAfter = (TextMarker) oAfter.get (j); if (poBefore.canCoalesce (poAfter)) { if (coalesceMarker (poBefore, poAfter)) { oAfter.set (j, null); // no longer a valid after candidate break; // done with this before marker } } } } } private void enumerateOverlappingRangeMarkers (int nStart, int nEnd, Storage oMarkers) { oMarkers.setSize (0); TextRange oRange = new TextRange (this, nStart, nEnd); oRange.tighten(); if (oRange.isEmpty()) { return; } nStart = oRange.start().index(); nEnd = oRange.end().index(); oRange.enumerateMarkers (oMarkers); // TODO: for (int i = 0; i < oMarkers.size(); ) { TextMarker poMarker = (TextMarker) oMarkers.get (i); boolean bKeep = false; if (poMarker.isRangeMarker()) { PosnMarker poLocation = poMarker.getLocation(); if ((poLocation.index() < nEnd) && (poLocation.getMate().index() > nStart)) { bKeep = true; } } if (bKeep) { i++; } else { oMarkers.remove (i); } } } private static boolean intersectMarkerRange (TextMarker poMarker, int[] markers) { PosnMarker poLocation = poMarker.getLocation(); if (poLocation == null) { return false; } int nMarkerStart = poLocation.index(); PosnMarker poMate = poLocation.getMate(); int nMarkerEnd = (poMate == null) ? nMarkerStart : poMate.index(); int nStart = markers[0]; int nEnd = markers[1]; if ((nStart > nMarkerEnd) || (nEnd < nMarkerStart)) { return false; } if (nStart < nMarkerStart) { nStart = nMarkerStart; } if (nEnd > nMarkerEnd) { nEnd = nMarkerEnd; } markers[0] = nStart; markers[1] = nEnd; return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy