com.adobe.xfa.text.TextStream Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
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;
}
}