2005-07-16 00:30:23 +00:00
|
|
|
/* DefaultStyledDocument.java --
|
2005-11-15 23:20:01 +00:00
|
|
|
Copyright (C) 2004, 2005 Free Software Foundation, Inc.
|
2005-07-16 00:30:23 +00:00
|
|
|
|
|
|
|
This file is part of GNU Classpath.
|
|
|
|
|
|
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
any later version.
|
|
|
|
|
|
|
|
GNU Classpath is distributed in the hope that it will be useful, but
|
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
|
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
02110-1301 USA.
|
|
|
|
|
|
|
|
Linking this library statically or dynamically with other modules is
|
|
|
|
making a combined work based on this library. Thus, the terms and
|
|
|
|
conditions of the GNU General Public License cover the whole
|
|
|
|
combination.
|
|
|
|
|
|
|
|
As a special exception, the copyright holders of this library give you
|
|
|
|
permission to link this library with independent modules to produce an
|
|
|
|
executable, regardless of the license terms of these independent
|
|
|
|
modules, and to copy and distribute the resulting executable under
|
|
|
|
terms of your choice, provided that you also meet, for each linked
|
|
|
|
independent module, the terms and conditions of the license of that
|
|
|
|
module. An independent module is a module which is not derived from
|
|
|
|
or based on this library. If you modify this library, you may extend
|
|
|
|
this exception to your version of the library, but you are not
|
|
|
|
obligated to do so. If you do not wish to do so, delete this
|
|
|
|
exception statement from your version. */
|
|
|
|
|
|
|
|
|
|
|
|
package javax.swing.text;
|
|
|
|
|
|
|
|
import java.awt.Color;
|
|
|
|
import java.awt.Font;
|
|
|
|
import java.io.Serializable;
|
2005-11-15 23:20:01 +00:00
|
|
|
import java.util.Enumeration;
|
2006-01-17 18:09:40 +00:00
|
|
|
import java.util.Stack;
|
2005-11-15 23:20:01 +00:00
|
|
|
import java.util.Vector;
|
2005-07-16 00:30:23 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
import javax.swing.event.ChangeEvent;
|
|
|
|
import javax.swing.event.ChangeListener;
|
2005-09-23 21:31:04 +00:00
|
|
|
import javax.swing.event.DocumentEvent;
|
2006-01-17 18:09:40 +00:00
|
|
|
import javax.swing.event.UndoableEditEvent;
|
2005-11-15 23:20:01 +00:00
|
|
|
import javax.swing.undo.AbstractUndoableEdit;
|
|
|
|
import javax.swing.undo.UndoableEdit;
|
2005-09-23 21:31:04 +00:00
|
|
|
|
2005-07-16 00:30:23 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* The default implementation of {@link StyledDocument}. The document is
|
|
|
|
* modeled as an {@link Element} tree, which has a {@link SectionElement} as
|
|
|
|
* single root, which has one or more {@link AbstractDocument.BranchElement}s
|
|
|
|
* as paragraph nodes and each paragraph node having one or more
|
2005-09-23 21:31:04 +00:00
|
|
|
* {@link AbstractDocument.LeafElement}s as content nodes.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-07-16 00:30:23 +00:00
|
|
|
* @author Michael Koch (konqueror@gmx.de)
|
2005-09-23 21:31:04 +00:00
|
|
|
* @author Roman Kennke (roman@kennke.org)
|
2005-07-16 00:30:23 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
public class DefaultStyledDocument extends AbstractDocument implements
|
|
|
|
StyledDocument
|
2005-07-16 00:30:23 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
|
|
|
* An {@link UndoableEdit} that can undo attribute changes to an element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @author Roman Kennke (kennke@aicas.com)
|
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
public static class AttributeUndoableEdit extends AbstractUndoableEdit
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* A copy of the old attributes.
|
|
|
|
*/
|
|
|
|
protected AttributeSet copy;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The new attributes.
|
|
|
|
*/
|
|
|
|
protected AttributeSet newAttributes;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If the new attributes replaced the old attributes or if they only were
|
|
|
|
* added to them.
|
|
|
|
*/
|
|
|
|
protected boolean isReplacing;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The element that has changed.
|
|
|
|
*/
|
|
|
|
protected Element element;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new <code>AttributeUndoableEdit</code>.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param el
|
|
|
|
* the element that changes attributes
|
|
|
|
* @param newAtts
|
|
|
|
* the new attributes
|
|
|
|
* @param replacing
|
|
|
|
* if the new attributes replace the old or only append to them
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public AttributeUndoableEdit(Element el, AttributeSet newAtts,
|
|
|
|
boolean replacing)
|
|
|
|
{
|
|
|
|
element = el;
|
|
|
|
newAttributes = newAtts;
|
|
|
|
isReplacing = replacing;
|
|
|
|
copy = el.getAttributes().copyAttributes();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Undos the attribute change. The <code>copy</code> field is set as
|
|
|
|
* attributes on <code>element</code>.
|
|
|
|
*/
|
|
|
|
public void undo()
|
|
|
|
{
|
|
|
|
super.undo();
|
|
|
|
AttributeSet atts = element.getAttributes();
|
|
|
|
if (atts instanceof MutableAttributeSet)
|
|
|
|
{
|
|
|
|
MutableAttributeSet mutable = (MutableAttributeSet) atts;
|
|
|
|
mutable.removeAttributes(atts);
|
|
|
|
mutable.addAttributes(copy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Redos an attribute change. This adds <code>newAttributes</code> to the
|
|
|
|
* <code>element</code>'s attribute set, possibly clearing all attributes
|
|
|
|
* if <code>isReplacing</code> is true.
|
|
|
|
*/
|
|
|
|
public void redo()
|
|
|
|
{
|
|
|
|
super.undo();
|
|
|
|
AttributeSet atts = element.getAttributes();
|
|
|
|
if (atts instanceof MutableAttributeSet)
|
|
|
|
{
|
|
|
|
MutableAttributeSet mutable = (MutableAttributeSet) atts;
|
|
|
|
if (isReplacing)
|
|
|
|
mutable.removeAttributes(atts);
|
|
|
|
mutable.addAttributes(newAttributes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Carries specification information for new {@link Element}s that should be
|
|
|
|
* created in {@link ElementBuffer}. This allows the parsing process to be
|
|
|
|
* decoupled from the <code>Element</code> creation process.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public static class ElementSpec
|
|
|
|
{
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* This indicates a start tag. This is a possible value for {@link #getType}.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public static final short StartTagType = 1;
|
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* This indicates an end tag. This is a possible value for {@link #getType}.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public static final short EndTagType = 2;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This indicates a content element. This is a possible value for
|
|
|
|
* {@link #getType}.
|
|
|
|
*/
|
|
|
|
public static final short ContentType = 3;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This indicates that the data associated with this spec should be joined
|
2006-03-10 21:46:48 +00:00
|
|
|
* with what precedes it. This is a possible value for {@link #getDirection}.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public static final short JoinPreviousDirection = 4;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This indicates that the data associated with this spec should be joined
|
2006-03-10 21:46:48 +00:00
|
|
|
* with what follows it. This is a possible value for {@link #getDirection}.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public static final short JoinNextDirection = 5;
|
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* This indicates that the data associated with this spec should be used to
|
|
|
|
* create a new element. This is a possible value for {@link #getDirection}.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public static final short OriginateDirection = 6;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This indicates that the data associated with this spec should be joined
|
|
|
|
* to the fractured element. This is a possible value for
|
|
|
|
* {@link #getDirection}.
|
|
|
|
*/
|
|
|
|
public static final short JoinFractureDirection = 7;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The type of the tag.
|
|
|
|
*/
|
|
|
|
short type;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The direction of the tag.
|
|
|
|
*/
|
|
|
|
short direction;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The offset of the content.
|
|
|
|
*/
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The length of the content.
|
|
|
|
*/
|
|
|
|
int length;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The actual content.
|
|
|
|
*/
|
|
|
|
char[] content;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The attributes for the tag.
|
|
|
|
*/
|
|
|
|
AttributeSet attributes;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new <code>ElementSpec</code> with no content, length or
|
|
|
|
* offset. This is most useful for start and end tags.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param a
|
|
|
|
* the attributes for the element to be created
|
|
|
|
* @param type
|
|
|
|
* the type of the tag
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public ElementSpec(AttributeSet a, short type)
|
|
|
|
{
|
|
|
|
this(a, type, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new <code>ElementSpec</code> that specifies the length but
|
|
|
|
* not the offset of an element. Such <code>ElementSpec</code>s are
|
|
|
|
* processed sequentially from a known starting point.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param a
|
|
|
|
* the attributes for the element to be created
|
|
|
|
* @param type
|
|
|
|
* the type of the tag
|
|
|
|
* @param len
|
|
|
|
* the length of the element
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public ElementSpec(AttributeSet a, short type, int len)
|
|
|
|
{
|
|
|
|
this(a, type, null, 0, len);
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
|
|
|
* Creates a new <code>ElementSpec</code> with document content.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param a
|
|
|
|
* the attributes for the element to be created
|
|
|
|
* @param type
|
|
|
|
* the type of the tag
|
|
|
|
* @param txt
|
|
|
|
* the actual content
|
|
|
|
* @param offs
|
|
|
|
* the offset into the <code>txt</code> array
|
|
|
|
* @param len
|
|
|
|
* the length of the element
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
|
|
|
attributes = a;
|
|
|
|
this.type = type;
|
|
|
|
offset = offs;
|
|
|
|
length = len;
|
|
|
|
content = txt;
|
|
|
|
direction = OriginateDirection;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the type of the element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param type
|
|
|
|
* the type of the element to be set
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public void setType(short type)
|
|
|
|
{
|
|
|
|
this.type = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the type of the element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return the type of the element
|
|
|
|
*/
|
|
|
|
public short getType()
|
|
|
|
{
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the direction of the element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param dir
|
|
|
|
* the direction of the element to be set
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public void setDirection(short dir)
|
|
|
|
{
|
|
|
|
direction = dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the direction of the element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return the direction of the element
|
|
|
|
*/
|
|
|
|
public short getDirection()
|
|
|
|
{
|
|
|
|
return direction;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the attributes of the element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return the attributes of the element
|
|
|
|
*/
|
|
|
|
public AttributeSet getAttributes()
|
|
|
|
{
|
|
|
|
return attributes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the actual content of the element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return the actual content of the element
|
|
|
|
*/
|
|
|
|
public char[] getArray()
|
|
|
|
{
|
|
|
|
return content;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the offset of the content.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return the offset of the content
|
|
|
|
*/
|
|
|
|
public int getOffset()
|
|
|
|
{
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the length of the content.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return the length of the content
|
|
|
|
*/
|
|
|
|
public int getLength()
|
|
|
|
{
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a String representation of this <code>ElementSpec</code>
|
|
|
|
* describing the type, direction and length of this
|
|
|
|
* <code>ElementSpec</code>.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return a String representation of this <code>ElementSpec</code>
|
|
|
|
*/
|
|
|
|
public String toString()
|
|
|
|
{
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case StartTagType:
|
|
|
|
b.append("StartTag");
|
|
|
|
break;
|
|
|
|
case EndTagType:
|
|
|
|
b.append("EndTag");
|
|
|
|
break;
|
|
|
|
case ContentType:
|
|
|
|
b.append("Content");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
b.append("??");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
b.append(':');
|
|
|
|
|
|
|
|
switch (direction)
|
|
|
|
{
|
|
|
|
case JoinPreviousDirection:
|
|
|
|
b.append("JoinPrevious");
|
|
|
|
break;
|
|
|
|
case JoinNextDirection:
|
|
|
|
b.append("JoinNext");
|
|
|
|
break;
|
|
|
|
case OriginateDirection:
|
|
|
|
b.append("Originate");
|
|
|
|
break;
|
|
|
|
case JoinFractureDirection:
|
|
|
|
b.append("Fracture");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
b.append("??");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
b.append(':');
|
|
|
|
b.append(length);
|
|
|
|
|
|
|
|
return b.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Performs all <em>structural</code> changes to the <code>Element</code>
|
2006-03-10 21:46:48 +00:00
|
|
|
* hierarchy. This class was implemented with much help from the document:
|
|
|
|
* http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html.
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-11-15 23:20:01 +00:00
|
|
|
public class ElementBuffer implements Serializable
|
2005-07-16 00:30:23 +00:00
|
|
|
{
|
2005-11-15 23:20:01 +00:00
|
|
|
/** The serialization UID (compatible with JDK1.5). */
|
|
|
|
private static final long serialVersionUID = 1688745877691146623L;
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/** The root element of the hierarchy. */
|
2005-07-16 00:30:23 +00:00
|
|
|
private Element root;
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/** Holds the offset for structural changes. */
|
|
|
|
private int offset;
|
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/** Holds the end offset for structural changes. */
|
|
|
|
private int endOffset;
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/** Holds the length of structural changes. */
|
|
|
|
private int length;
|
2006-01-17 18:09:40 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/** Holds the position of the change. */
|
|
|
|
private int pos;
|
2006-01-17 18:09:40 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/** Holds the element that was last fractured. */
|
|
|
|
private Element lastFractured;
|
|
|
|
|
|
|
|
/** True if a fracture was not created during a insertFracture call. */
|
|
|
|
private boolean fracNotCreated;
|
2006-01-17 18:09:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The current position in the element tree. This is used for bulk inserts
|
|
|
|
* using ElementSpecs.
|
|
|
|
*/
|
|
|
|
private Stack elementStack;
|
2005-09-23 21:31:04 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
|
|
|
* The ElementChange that describes the latest changes.
|
|
|
|
*/
|
|
|
|
DefaultDocumentEvent documentEvent;
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Creates a new <code>ElementBuffer</code> for the specified
|
|
|
|
* <code>root</code> element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param root
|
|
|
|
* the root element for this <code>ElementBuffer</code>
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public ElementBuffer(Element root)
|
|
|
|
{
|
|
|
|
this.root = root;
|
2006-01-17 18:09:40 +00:00
|
|
|
elementStack = new Stack();
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Returns the root element of this <code>ElementBuffer</code>.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the root element of this <code>ElementBuffer</code>
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Element getRootElement()
|
|
|
|
{
|
|
|
|
return root;
|
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Removes the content. This method sets some internal parameters and
|
|
|
|
* delegates the work to {@link #removeUpdate}.
|
|
|
|
*
|
|
|
|
* @param offs
|
|
|
|
* the offset from which content is remove
|
|
|
|
* @param len
|
|
|
|
* the length of the removed content
|
|
|
|
* @param ev
|
|
|
|
* the document event that records the changes
|
2006-01-17 18:09:40 +00:00
|
|
|
*/
|
|
|
|
public void remove(int offs, int len, DefaultDocumentEvent ev)
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
if (len == 0)
|
|
|
|
return;
|
2006-01-17 18:09:40 +00:00
|
|
|
offset = offs;
|
|
|
|
length = len;
|
2006-03-10 21:46:48 +00:00
|
|
|
pos = offset;
|
2006-01-17 18:09:40 +00:00
|
|
|
documentEvent = ev;
|
|
|
|
removeUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the element structure of the document in response to removal of
|
|
|
|
* content. It removes the affected {@link Element}s from the document
|
|
|
|
* structure.
|
|
|
|
*/
|
|
|
|
protected void removeUpdate()
|
|
|
|
{
|
|
|
|
int startParagraph = root.getElementIndex(offset);
|
|
|
|
int endParagraph = root.getElementIndex(offset + length);
|
|
|
|
Element[] empty = new Element[0];
|
|
|
|
int removeStart = -1;
|
|
|
|
int removeEnd = -1;
|
2006-03-10 21:46:48 +00:00
|
|
|
for (int i = startParagraph; i < endParagraph; i++)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
BranchElement paragraph = (BranchElement) root.getElement(i);
|
2006-01-17 18:09:40 +00:00
|
|
|
int contentStart = paragraph.getElementIndex(offset);
|
|
|
|
int contentEnd = paragraph.getElementIndex(offset + length);
|
|
|
|
if (contentStart == paragraph.getStartOffset()
|
|
|
|
&& contentEnd == paragraph.getEndOffset())
|
|
|
|
{
|
|
|
|
// In this case we only need to remove the whole paragraph. We
|
|
|
|
// do this in one go after this loop and only record the indices
|
|
|
|
// here.
|
|
|
|
if (removeStart == -1)
|
|
|
|
{
|
|
|
|
removeStart = i;
|
|
|
|
removeEnd = i;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
removeEnd = i;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// In this case we remove a couple of child elements from this
|
|
|
|
// paragraph.
|
|
|
|
int removeLen = contentEnd - contentStart;
|
|
|
|
Element[] removed = new Element[removeLen];
|
|
|
|
for (int j = contentStart; j < contentEnd; j++)
|
|
|
|
removed[j] = paragraph.getElement(j);
|
2006-03-10 21:46:48 +00:00
|
|
|
Edit edit = getEditForParagraphAndIndex(paragraph, contentStart);
|
|
|
|
edit.addRemovedElements(removed);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now we remove paragraphs from the root that have been tagged for
|
|
|
|
// removal.
|
|
|
|
if (removeStart != -1)
|
|
|
|
{
|
|
|
|
int removeLen = removeEnd - removeStart;
|
|
|
|
Element[] removed = new Element[removeLen];
|
|
|
|
for (int i = removeStart; i < removeEnd; i++)
|
|
|
|
removed[i] = root.getElement(i);
|
2006-03-10 21:46:48 +00:00
|
|
|
Edit edit = getEditForParagraphAndIndex((BranchElement) root,
|
|
|
|
removeStart);
|
|
|
|
edit.addRemovedElements(removed);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Performs the actual work for {@link #change}. The elements at the
|
|
|
|
* interval boundaries are split up (if necessary) so that the interval
|
|
|
|
* boundaries are located at element boundaries.
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
|
|
|
protected void changeUpdate()
|
|
|
|
{
|
|
|
|
// Split up the element at the start offset if necessary.
|
|
|
|
Element el = getCharacterElement(offset);
|
2006-03-10 21:46:48 +00:00
|
|
|
Element[] res = split(el, offset, 0, el.getElementIndex(offset));
|
2006-01-17 18:09:40 +00:00
|
|
|
BranchElement par = (BranchElement) el.getParentElement();
|
2006-03-10 21:46:48 +00:00
|
|
|
int index = par.getElementIndex(offset);
|
|
|
|
Edit edit = getEditForParagraphAndIndex(par, index);
|
2006-01-17 18:09:40 +00:00
|
|
|
if (res[1] != null)
|
|
|
|
{
|
|
|
|
Element[] removed;
|
|
|
|
Element[] added;
|
|
|
|
if (res[0] == null)
|
|
|
|
{
|
|
|
|
removed = new Element[0];
|
2006-03-10 21:46:48 +00:00
|
|
|
added = new Element[] { res[1] };
|
2006-01-17 18:09:40 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
removed = new Element[] { el };
|
|
|
|
added = new Element[] { res[0], res[1] };
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
edit.addRemovedElements(removed);
|
|
|
|
|
|
|
|
edit.addAddedElements(added);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
int endOffset = offset + length;
|
|
|
|
el = getCharacterElement(endOffset);
|
2006-03-10 21:46:48 +00:00
|
|
|
res = split(el, endOffset, 0, el.getElementIndex(endOffset));
|
2006-01-17 18:09:40 +00:00
|
|
|
par = (BranchElement) el.getParentElement();
|
2006-03-10 21:46:48 +00:00
|
|
|
if (res[0] != null)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
|
|
|
Element[] removed;
|
|
|
|
Element[] added;
|
|
|
|
if (res[1] == null)
|
|
|
|
{
|
|
|
|
removed = new Element[0];
|
2006-03-10 21:46:48 +00:00
|
|
|
added = new Element[] { res[1] };
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
removed = new Element[] { el };
|
|
|
|
added = new Element[] { res[0], res[1] };
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
edit.addRemovedElements(removed);
|
|
|
|
edit.addAddedElements(added);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Modifies the element structure so that the specified interval starts and
|
|
|
|
* ends at an element boundary. Content and paragraph elements are split and
|
|
|
|
* created as necessary. This also updates the
|
|
|
|
* <code>DefaultDocumentEvent</code> to reflect the structural changes.
|
|
|
|
* The bulk work is delegated to {@link #changeUpdate()}.
|
|
|
|
*
|
|
|
|
* @param offset
|
|
|
|
* the start index of the interval to be changed
|
|
|
|
* @param length
|
|
|
|
* the length of the interval to be changed
|
|
|
|
* @param ev
|
|
|
|
* the <code>DefaultDocumentEvent</code> describing the change
|
|
|
|
*/
|
|
|
|
public void change(int offset, int length, DefaultDocumentEvent ev)
|
|
|
|
{
|
|
|
|
if (length == 0)
|
|
|
|
return;
|
|
|
|
this.offset = offset;
|
|
|
|
this.pos = offset;
|
|
|
|
this.length = length;
|
|
|
|
documentEvent = ev;
|
|
|
|
changeUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates and returns a deep clone of the specified <code>clonee</code>
|
|
|
|
* with the specified parent as new parent.
|
2005-09-23 21:31:04 +00:00
|
|
|
*
|
2006-03-10 21:46:48 +00:00
|
|
|
* This method can only clone direct instances of {@link BranchElement}
|
|
|
|
* or {@link LeafElement}.
|
2006-01-17 18:09:40 +00:00
|
|
|
*
|
2006-03-10 21:46:48 +00:00
|
|
|
* @param parent the new parent
|
|
|
|
* @param clonee the element to be cloned
|
|
|
|
*
|
|
|
|
* @return the cloned element with the new parent
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
public Element clone(Element parent, Element clonee)
|
2005-09-23 21:31:04 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element clone = clonee;
|
|
|
|
// We can only handle AbstractElements here.
|
|
|
|
if (clonee instanceof BranchElement)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
BranchElement branchEl = (BranchElement) clonee;
|
|
|
|
BranchElement branchClone =
|
|
|
|
new BranchElement(parent, branchEl.getAttributes());
|
|
|
|
// Also clone all of the children.
|
|
|
|
int numChildren = branchClone.getElementCount();
|
|
|
|
Element[] cloneChildren = new Element[numChildren];
|
|
|
|
for (int i = 0; i < numChildren; ++i)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
cloneChildren[i] = clone(branchClone,
|
|
|
|
branchClone.getElement(i));
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
branchClone.replace(0, 0, cloneChildren);
|
|
|
|
clone = branchClone;
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
else if (clonee instanceof LeafElement)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
clone = new LeafElement(parent, clonee.getAttributes(),
|
|
|
|
clonee.getStartOffset(),
|
|
|
|
clonee.getEndOffset());
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
return clone;
|
2005-09-23 21:31:04 +00:00
|
|
|
}
|
2005-11-15 23:20:01 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts new <code>Element</code> in the document at the specified
|
2006-03-10 21:46:48 +00:00
|
|
|
* position. Most of the work is done by {@link #insertUpdate}, after some
|
|
|
|
* fields have been prepared for it.
|
|
|
|
*
|
|
|
|
* @param offset
|
|
|
|
* the location in the document at which the content is inserted
|
|
|
|
* @param length
|
|
|
|
* the length of the inserted content
|
|
|
|
* @param data
|
|
|
|
* the element specifications for the content to be inserted
|
|
|
|
* @param ev
|
|
|
|
* the document event that is updated to reflect the structural
|
|
|
|
* changes
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public void insert(int offset, int length, ElementSpec[] data,
|
|
|
|
DefaultDocumentEvent ev)
|
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
if (length == 0)
|
|
|
|
return;
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
this.offset = offset;
|
2006-03-10 21:46:48 +00:00
|
|
|
this.pos = offset;
|
2006-01-17 18:09:40 +00:00
|
|
|
this.endOffset = offset + length;
|
2006-03-10 21:46:48 +00:00
|
|
|
this.length = length;
|
2005-11-15 23:20:01 +00:00
|
|
|
documentEvent = ev;
|
2006-03-10 21:46:48 +00:00
|
|
|
|
|
|
|
edits.removeAllElements();
|
|
|
|
elementStack.removeAllElements();
|
|
|
|
lastFractured = null;
|
|
|
|
fracNotCreated = false;
|
2005-11-15 23:20:01 +00:00
|
|
|
insertUpdate(data);
|
2006-03-10 21:46:48 +00:00
|
|
|
// This for loop applies all the changes that were made and updates the
|
|
|
|
// DocumentEvent.
|
|
|
|
int size = edits.size();
|
|
|
|
for (int i = 0; i < size; i++)
|
|
|
|
{
|
|
|
|
Edit curr = (Edit) edits.get(i);
|
|
|
|
BranchElement e = (BranchElement) curr.e;
|
|
|
|
Element[] removed = curr.getRemovedElements();
|
|
|
|
Element[] added = curr.getAddedElements();
|
|
|
|
// FIXME: We probably shouldn't create the empty Element[] in the
|
|
|
|
// first place.
|
|
|
|
if (removed.length > 0 || added.length > 0)
|
|
|
|
{
|
|
|
|
if (curr.index + removed.length <= e.getElementCount())
|
|
|
|
{
|
|
|
|
e.replace(curr.index, removed.length, added);
|
|
|
|
ElementEdit ee = new ElementEdit(e, curr.index, removed, added);
|
|
|
|
ev.addEdit(ee);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
System.err.println("WARNING: Tried to replace elements ");
|
|
|
|
System.err.print("beyond boundaries: elementCount: ");
|
|
|
|
System.err.println(e.getElementCount());
|
|
|
|
System.err.print("index: " + curr.index);
|
|
|
|
System.err.println(", removed.length: " + removed.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Inserts new content
|
|
|
|
*
|
|
|
|
* @param data
|
|
|
|
* the element specifications for the elements to be inserted
|
|
|
|
*/
|
2005-11-15 23:20:01 +00:00
|
|
|
protected void insertUpdate(ElementSpec[] data)
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
// Push the root and the paragraph at offset onto the element stack.
|
|
|
|
Element current = root;
|
|
|
|
int index;
|
|
|
|
while (!current.isLeaf())
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
index = current.getElementIndex(offset);
|
|
|
|
elementStack.push(current);
|
|
|
|
current = current.getElement(index);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
int i = 0;
|
|
|
|
int type = data[0].getType();
|
|
|
|
if (type == ElementSpec.ContentType)
|
|
|
|
{
|
|
|
|
// If the first tag is content we must treat it separately to allow
|
|
|
|
// for joining properly to previous Elements and to ensure that
|
|
|
|
// no extra LeafElements are erroneously inserted.
|
|
|
|
insertFirstContentTag(data);
|
|
|
|
pos += data[0].length;
|
|
|
|
i = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
createFracture(data);
|
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle each ElementSpec individually.
|
|
|
|
for (; i < data.length; i++)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
BranchElement paragraph = (BranchElement) elementStack.peek();
|
2005-11-15 23:20:01 +00:00
|
|
|
switch (data[i].getType())
|
|
|
|
{
|
|
|
|
case ElementSpec.StartTagType:
|
2006-01-17 18:09:40 +00:00
|
|
|
switch (data[i].getDirection())
|
|
|
|
{
|
|
|
|
case ElementSpec.JoinFractureDirection:
|
2006-03-10 21:46:48 +00:00
|
|
|
// Fracture the tree and ensure the appropriate element
|
|
|
|
// is on top of the stack.
|
|
|
|
fracNotCreated = false;
|
2006-01-17 18:09:40 +00:00
|
|
|
insertFracture(data[i]);
|
2006-03-10 21:46:48 +00:00
|
|
|
if (fracNotCreated)
|
|
|
|
{
|
|
|
|
if (lastFractured != null)
|
|
|
|
elementStack.push(lastFractured.getParentElement());
|
|
|
|
else
|
|
|
|
elementStack.push(paragraph.getElement(0));
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
break;
|
|
|
|
case ElementSpec.JoinNextDirection:
|
2006-03-10 21:46:48 +00:00
|
|
|
// Push the next paragraph element onto the stack so
|
|
|
|
// future insertions are added to it.
|
|
|
|
int ix = paragraph.getElementIndex(pos) + 1;
|
|
|
|
elementStack.push(paragraph.getElement(ix));
|
2006-01-17 18:09:40 +00:00
|
|
|
break;
|
|
|
|
default:
|
2006-03-10 21:46:48 +00:00
|
|
|
Element br = null;
|
|
|
|
if (data.length > i + 1)
|
|
|
|
{
|
|
|
|
// leaves will be added to paragraph later
|
|
|
|
int x = 0;
|
|
|
|
if (paragraph.getElementCount() > 0)
|
|
|
|
x = paragraph.getElementIndex(pos) + 1;
|
|
|
|
Edit e = getEditForParagraphAndIndex(paragraph, x);
|
|
|
|
br = (BranchElement) createBranchElement(paragraph,
|
|
|
|
data[i].getAttributes());
|
|
|
|
e.added.add(br);
|
|
|
|
elementStack.push(br);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
// need to add leaves to paragraph now
|
|
|
|
br = insertParagraph(paragraph, pos);
|
2006-01-17 18:09:40 +00:00
|
|
|
break;
|
|
|
|
}
|
2005-11-15 23:20:01 +00:00
|
|
|
break;
|
|
|
|
case ElementSpec.EndTagType:
|
2006-01-17 18:09:40 +00:00
|
|
|
elementStack.pop();
|
2005-11-15 23:20:01 +00:00
|
|
|
break;
|
2006-01-17 18:09:40 +00:00
|
|
|
case ElementSpec.ContentType:
|
2005-11-15 23:20:01 +00:00
|
|
|
insertContentTag(data[i]);
|
2006-03-10 21:46:48 +00:00
|
|
|
offset = pos;
|
2005-11-15 23:20:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts a new paragraph.
|
|
|
|
*
|
|
|
|
* @param par -
|
|
|
|
* the parent
|
|
|
|
* @param offset -
|
|
|
|
* the offset
|
|
|
|
* @return the new paragraph
|
|
|
|
*/
|
|
|
|
private Element insertParagraph(BranchElement par, int offset)
|
|
|
|
{
|
|
|
|
int index = par.getElementIndex(offset);
|
|
|
|
Element current = par.getElement(index);
|
|
|
|
Element[] res = split(current, offset, 0, 0);
|
|
|
|
Edit e = getEditForParagraphAndIndex(par, index + 1);
|
|
|
|
Element ret;
|
|
|
|
if (res[1] != null)
|
|
|
|
{
|
|
|
|
Element[] removed;
|
|
|
|
Element[] added;
|
|
|
|
if (res[0] == null)
|
|
|
|
{
|
|
|
|
removed = new Element[0];
|
|
|
|
if (res[1] instanceof BranchElement)
|
|
|
|
{
|
|
|
|
added = new Element[] { res[1] };
|
|
|
|
ret = res[1];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = createBranchElement(par, null);
|
|
|
|
added = new Element[] { ret, res[1] };
|
|
|
|
}
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
removed = new Element[] { current };
|
|
|
|
if (res[1] instanceof BranchElement)
|
|
|
|
{
|
|
|
|
ret = res[1];
|
|
|
|
added = new Element[] { res[0], res[1] };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = createBranchElement(par, null);
|
|
|
|
added = new Element[] { res[0], ret, res[1] };
|
|
|
|
}
|
|
|
|
}
|
2005-11-15 23:20:01 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
e.addAddedElements(added);
|
|
|
|
e.addRemovedElements(removed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = createBranchElement(par, null);
|
|
|
|
e.addAddedElement(ret);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Inserts the first tag into the document.
|
|
|
|
*
|
|
|
|
* @param data -
|
|
|
|
* the data to be inserted.
|
2006-01-17 18:09:40 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
private void insertFirstContentTag(ElementSpec[] data)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
ElementSpec first = data[0];
|
|
|
|
BranchElement paragraph = (BranchElement) elementStack.peek();
|
|
|
|
int index = paragraph.getElementIndex(pos);
|
|
|
|
Element current = paragraph.getElement(index);
|
|
|
|
int newEndOffset = pos + first.length;
|
|
|
|
boolean onlyContent = data.length == 1;
|
|
|
|
Edit edit = getEditForParagraphAndIndex(paragraph, index);
|
|
|
|
switch (first.getDirection())
|
|
|
|
{
|
|
|
|
case ElementSpec.JoinPreviousDirection:
|
|
|
|
if (current.getEndOffset() != newEndOffset && !onlyContent)
|
|
|
|
{
|
|
|
|
Element newEl1 = createLeafElement(paragraph,
|
|
|
|
current.getAttributes(),
|
|
|
|
current.getStartOffset(),
|
|
|
|
newEndOffset);
|
|
|
|
edit.addAddedElement(newEl1);
|
|
|
|
edit.addRemovedElement(current);
|
|
|
|
offset = newEndOffset;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ElementSpec.JoinNextDirection:
|
|
|
|
if (pos != 0)
|
|
|
|
{
|
|
|
|
Element newEl1 = createLeafElement(paragraph,
|
|
|
|
current.getAttributes(),
|
|
|
|
current.getStartOffset(),
|
|
|
|
pos);
|
|
|
|
edit.addAddedElement(newEl1);
|
|
|
|
Element next = paragraph.getElement(index + 1);
|
|
|
|
|
|
|
|
if (onlyContent)
|
|
|
|
newEl1 = createLeafElement(paragraph, next.getAttributes(),
|
|
|
|
pos, next.getEndOffset());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
newEl1 = createLeafElement(paragraph, next.getAttributes(),
|
|
|
|
pos, newEndOffset);
|
|
|
|
pos = newEndOffset;
|
|
|
|
}
|
|
|
|
edit.addAddedElement(newEl1);
|
|
|
|
edit.addRemovedElement(current);
|
|
|
|
edit.addRemovedElement(next);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (current.getStartOffset() != pos)
|
|
|
|
{
|
|
|
|
Element newEl = createLeafElement(paragraph,
|
|
|
|
current.getAttributes(),
|
|
|
|
current.getStartOffset(),
|
|
|
|
pos);
|
|
|
|
edit.addAddedElement(newEl);
|
|
|
|
}
|
|
|
|
edit.addRemovedElement(current);
|
|
|
|
Element newEl1 = createLeafElement(paragraph, first.getAttributes(),
|
|
|
|
pos, newEndOffset);
|
|
|
|
edit.addAddedElement(newEl1);
|
|
|
|
if (current.getEndOffset() != endOffset)
|
|
|
|
recreateLeaves(newEndOffset, paragraph, onlyContent);
|
|
|
|
else
|
|
|
|
offset = newEndOffset;
|
|
|
|
break;
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Inserts a content element into the document structure.
|
|
|
|
*
|
|
|
|
* @param tag -
|
|
|
|
* the element spec
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
private void insertContentTag(ElementSpec tag)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
BranchElement paragraph = (BranchElement) elementStack.peek();
|
|
|
|
int len = tag.getLength();
|
|
|
|
int dir = tag.getDirection();
|
|
|
|
AttributeSet tagAtts = tag.getAttributes();
|
|
|
|
|
|
|
|
if (dir == ElementSpec.JoinNextDirection)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
int index = paragraph.getElementIndex(pos);
|
|
|
|
Element target = paragraph.getElement(index);
|
|
|
|
Edit edit = getEditForParagraphAndIndex(paragraph, index);
|
|
|
|
|
|
|
|
if (paragraph.getStartOffset() > pos)
|
|
|
|
{
|
|
|
|
Element first = paragraph.getElement(0);
|
|
|
|
Element newEl = createLeafElement(paragraph,
|
|
|
|
first.getAttributes(), pos,
|
|
|
|
first.getEndOffset());
|
|
|
|
edit.addAddedElement(newEl);
|
|
|
|
edit.addRemovedElement(first);
|
|
|
|
}
|
|
|
|
else if (paragraph.getElementCount() > (index + 1)
|
|
|
|
&& (pos == target.getStartOffset() && !target.equals(lastFractured)))
|
|
|
|
{
|
|
|
|
Element next = paragraph.getElement(index + 1);
|
|
|
|
Element newEl = createLeafElement(paragraph,
|
|
|
|
next.getAttributes(), pos,
|
|
|
|
next.getEndOffset());
|
|
|
|
edit.addAddedElement(newEl);
|
|
|
|
edit.addRemovedElement(next);
|
|
|
|
edit.addRemovedElement(target);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
BranchElement parent = (BranchElement) paragraph.getParentElement();
|
|
|
|
int i = parent.getElementIndex(pos);
|
|
|
|
BranchElement next = (BranchElement) parent.getElement(i + 1);
|
|
|
|
AttributeSet atts = tag.getAttributes();
|
|
|
|
|
|
|
|
if (next != null)
|
|
|
|
{
|
|
|
|
Element nextLeaf = next.getElement(0);
|
|
|
|
Edit e = getEditForParagraphAndIndex(next, 0);
|
|
|
|
Element newEl2 = createLeafElement(next, atts, pos, nextLeaf.getEndOffset());
|
|
|
|
e.addAddedElement(newEl2);
|
|
|
|
e.addRemovedElement(nextLeaf);
|
|
|
|
}
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
int end = pos + len;
|
|
|
|
Element leaf = createLeafElement(paragraph, tag.getAttributes(), pos, end);
|
|
|
|
|
|
|
|
// Check for overlap with other leaves/branches
|
|
|
|
if (paragraph.getElementCount() > 0)
|
|
|
|
{
|
|
|
|
int index = paragraph.getElementIndex(pos);
|
|
|
|
Element target = paragraph.getElement(index);
|
|
|
|
boolean onlyContent = target.isLeaf();
|
|
|
|
|
|
|
|
BranchElement toRec = paragraph;
|
|
|
|
if (!onlyContent)
|
|
|
|
toRec = (BranchElement) target;
|
|
|
|
|
|
|
|
// Check if we should place the leaf before or after target
|
|
|
|
if (pos > target.getStartOffset())
|
|
|
|
index++;
|
2006-01-17 18:09:40 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
Edit edit = getEditForParagraphAndIndex(paragraph, index);
|
|
|
|
edit.addAddedElement(leaf);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
paragraph.replace(0, 0, new Element[] { leaf });
|
|
|
|
}
|
|
|
|
|
|
|
|
pos += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method fractures the child at offset.
|
|
|
|
*
|
|
|
|
* @param data
|
|
|
|
* the ElementSpecs used for the entire insertion
|
|
|
|
*/
|
|
|
|
private void createFracture(ElementSpec[] data)
|
|
|
|
{
|
|
|
|
BranchElement paragraph = (BranchElement) elementStack.peek();
|
|
|
|
int index = paragraph.getElementIndex(offset);
|
|
|
|
Element child = paragraph.getElement(index);
|
|
|
|
Edit edit = getEditForParagraphAndIndex(paragraph, index);
|
|
|
|
AttributeSet atts = child.getAttributes();
|
|
|
|
|
|
|
|
if (offset != 0)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element newEl1 = createLeafElement(paragraph, atts,
|
|
|
|
child.getStartOffset(), offset);
|
|
|
|
edit.addAddedElement(newEl1);
|
|
|
|
edit.addRemovedElement(child);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/**
|
|
|
|
* Recreates a specified part of a the tree after a new leaf
|
|
|
|
* has been inserted.
|
|
|
|
*
|
|
|
|
* @param start - where to start recreating from
|
|
|
|
* @param paragraph - the paragraph to recreate
|
|
|
|
* @param onlyContent - true if this is the only content
|
|
|
|
*/
|
|
|
|
private void recreateLeaves(int start, BranchElement paragraph, boolean onlyContent)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
int index = paragraph.getElementIndex(start);
|
|
|
|
Element child = paragraph.getElement(index);
|
|
|
|
AttributeSet atts = child.getAttributes();
|
|
|
|
|
|
|
|
if (!onlyContent)
|
|
|
|
{
|
|
|
|
BranchElement newBranch = (BranchElement) createBranchElement(paragraph,
|
|
|
|
atts);
|
|
|
|
Element newLeaf = createLeafElement(newBranch, atts, start,
|
|
|
|
child.getEndOffset());
|
|
|
|
newBranch.replace(0, 0, new Element[] { newLeaf });
|
|
|
|
|
|
|
|
BranchElement parent = (BranchElement) paragraph.getParentElement();
|
|
|
|
int parSize = parent.getElementCount();
|
|
|
|
Edit edit = getEditForParagraphAndIndex(parent, parSize);
|
|
|
|
edit.addAddedElement(newBranch);
|
|
|
|
|
|
|
|
int paragraphSize = paragraph.getElementCount();
|
|
|
|
Element[] removed = new Element[paragraphSize - (index + 1)];
|
|
|
|
int s = 0;
|
|
|
|
for (int j = index + 1; j < paragraphSize; j++)
|
|
|
|
removed[s++] = paragraph.getElement(j);
|
|
|
|
|
|
|
|
edit = getEditForParagraphAndIndex(paragraph, index);
|
|
|
|
edit.addRemovedElements(removed);
|
|
|
|
Element[] added = recreateAfterFracture(removed, newBranch, 0, child.getEndOffset());
|
|
|
|
newBranch.replace(1, 0, added);
|
|
|
|
|
|
|
|
lastFractured = newLeaf;
|
|
|
|
pos = newBranch.getEndOffset();
|
|
|
|
}
|
|
|
|
else
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element newLeaf = createLeafElement(paragraph, atts, start,
|
|
|
|
child.getEndOffset());
|
|
|
|
Edit edit = getEditForParagraphAndIndex(paragraph, index);
|
|
|
|
edit.addAddedElement(newLeaf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Splits an element if <code>offset</code> is not already at its
|
|
|
|
* boundary.
|
|
|
|
*
|
|
|
|
* @param el
|
|
|
|
* the Element to possibly split
|
|
|
|
* @param offset
|
|
|
|
* the offset at which to possibly split
|
|
|
|
* @param space
|
|
|
|
* the amount of space to create between the splitted parts
|
|
|
|
* @param editIndex
|
|
|
|
* the index of the edit to use
|
|
|
|
* @return An array of elements which represent the split result. This array
|
|
|
|
* has two elements, the two parts of the split. The first element
|
|
|
|
* might be null, which means that the element which should be
|
|
|
|
* splitted can remain in place. The second element might also be
|
|
|
|
* null, which means that the offset is already at an element
|
|
|
|
* boundary and the element doesn't need to be splitted.
|
|
|
|
*/
|
|
|
|
private Element[] split(Element el, int offset, int space, int editIndex)
|
|
|
|
{
|
|
|
|
// If we are at an element boundary, then return an empty array.
|
|
|
|
if ((offset == el.getStartOffset() || offset == el.getEndOffset())
|
|
|
|
&& space == 0 && el.isLeaf())
|
|
|
|
return new Element[2];
|
|
|
|
|
|
|
|
// If the element is an instance of BranchElement, then we
|
|
|
|
// recursivly
|
|
|
|
// call this method to perform the split.
|
|
|
|
Element[] res = new Element[2];
|
|
|
|
if (el instanceof BranchElement)
|
|
|
|
{
|
|
|
|
int index = el.getElementIndex(offset);
|
|
|
|
Element child = el.getElement(index);
|
|
|
|
Element[] result = split(child, offset, space, editIndex);
|
2006-01-17 18:09:40 +00:00
|
|
|
Element[] removed;
|
|
|
|
Element[] added;
|
2006-03-10 21:46:48 +00:00
|
|
|
Element[] newAdded;
|
|
|
|
|
|
|
|
int count = el.getElementCount();
|
|
|
|
if (result[1] != null)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
// This is the case when we can keep the first element.
|
|
|
|
if (result[0] == null)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
removed = new Element[count - index - 1];
|
|
|
|
newAdded = new Element[count - index - 1];
|
|
|
|
added = new Element[] {};
|
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
// This is the case when we may not keep the first
|
|
|
|
// element.
|
2006-01-17 18:09:40 +00:00
|
|
|
else
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
removed = new Element[count - index];
|
|
|
|
newAdded = new Element[count - index];
|
|
|
|
added = new Element[] { result[0] };
|
|
|
|
}
|
|
|
|
newAdded[0] = result[1];
|
|
|
|
for (int i = index; i < count; i++)
|
|
|
|
{
|
|
|
|
Element el2 = el.getElement(i);
|
|
|
|
int ind = i - count + removed.length;
|
|
|
|
removed[ind] = el2;
|
|
|
|
if (ind != 0)
|
|
|
|
newAdded[ind] = el2;
|
|
|
|
}
|
|
|
|
|
|
|
|
Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex);
|
|
|
|
edit.addRemovedElements(removed);
|
|
|
|
edit.addAddedElements(added);
|
|
|
|
|
|
|
|
BranchElement newPar =
|
|
|
|
(BranchElement) createBranchElement(el.getParentElement(),
|
|
|
|
el.getAttributes());
|
|
|
|
newPar.replace(0, 0, newAdded);
|
|
|
|
res = new Element[] { null, newPar };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
removed = new Element[count - index];
|
|
|
|
for (int i = index; i < count; ++i)
|
|
|
|
removed[i - index] = el.getElement(i);
|
|
|
|
|
|
|
|
Edit edit = getEditForParagraphAndIndex((BranchElement) el, editIndex);
|
|
|
|
edit.addRemovedElements(removed);
|
|
|
|
|
|
|
|
BranchElement newPar = (BranchElement) createBranchElement(el.getParentElement(),
|
|
|
|
el.getAttributes());
|
|
|
|
newPar.replace(0, 0, removed);
|
|
|
|
res = new Element[] { null, newPar };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (el instanceof LeafElement)
|
|
|
|
{
|
|
|
|
BranchElement par = (BranchElement) el.getParentElement();
|
|
|
|
Element el1 = createLeafElement(par, el.getAttributes(),
|
|
|
|
el.getStartOffset(), offset);
|
|
|
|
|
|
|
|
Element el2 = createLeafElement(par, el.getAttributes(),
|
|
|
|
offset + space,
|
|
|
|
el.getEndOffset());
|
|
|
|
res = new Element[] { el1, el2 };
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts a fracture into the document structure.
|
|
|
|
*
|
|
|
|
* @param tag -
|
|
|
|
* the element spec.
|
|
|
|
*/
|
|
|
|
private void insertFracture(ElementSpec tag)
|
|
|
|
{
|
|
|
|
// insert the fracture at offset.
|
|
|
|
BranchElement parent = (BranchElement) elementStack.peek();
|
|
|
|
int parentIndex = parent.getElementIndex(pos);
|
|
|
|
AttributeSet parentAtts = parent.getAttributes();
|
|
|
|
Element toFracture = parent.getElement(parentIndex);
|
|
|
|
int parSize = parent.getElementCount();
|
|
|
|
Edit edit = getEditForParagraphAndIndex(parent, parentIndex);
|
|
|
|
Element frac = toFracture;
|
|
|
|
int leftIns = 0;
|
|
|
|
int indexOfFrac = toFracture.getElementIndex(pos);
|
|
|
|
int size = toFracture.getElementCount();
|
|
|
|
|
|
|
|
// gets the leaf that falls along the fracture
|
|
|
|
frac = toFracture.getElement(indexOfFrac);
|
|
|
|
while (!frac.isLeaf())
|
|
|
|
frac = frac.getElement(frac.getElementIndex(pos));
|
|
|
|
|
|
|
|
AttributeSet atts = frac.getAttributes();
|
|
|
|
int fracStart = frac.getStartOffset();
|
|
|
|
int fracEnd = frac.getEndOffset();
|
|
|
|
if (pos >= fracStart && pos < fracEnd)
|
|
|
|
{
|
|
|
|
// recreate left-side of branch and all its children before offset
|
|
|
|
// add the fractured leaves to the right branch
|
|
|
|
BranchElement rightBranch =
|
|
|
|
(BranchElement) createBranchElement(parent, parentAtts);
|
|
|
|
|
|
|
|
// Check if left branch has already been edited. If so, we only
|
|
|
|
// need to create the right branch.
|
|
|
|
BranchElement leftBranch = null;
|
|
|
|
Element[] added = null;
|
|
|
|
if (edit.added.size() > 0 || edit.removed.size() > 0)
|
|
|
|
{
|
|
|
|
added = new Element[] { rightBranch };
|
|
|
|
|
|
|
|
// don't try to remove left part of tree
|
|
|
|
parentIndex++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
leftBranch =
|
|
|
|
(BranchElement) createBranchElement(parent, parentAtts);
|
|
|
|
added = new Element[] { leftBranch, rightBranch };
|
|
|
|
|
|
|
|
// add fracture to leftBranch
|
|
|
|
if (fracStart != pos)
|
|
|
|
{
|
|
|
|
Element leftFracturedLeaf =
|
|
|
|
createLeafElement(leftBranch, atts, fracStart, pos);
|
|
|
|
leftBranch.replace(leftIns, 0,
|
|
|
|
new Element[] { leftFracturedLeaf });
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
|
|
|
if (!toFracture.isLeaf())
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
// add all non-fracture elements to the branches
|
|
|
|
if (indexOfFrac > 0 && leftBranch != null)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element[] add = new Element[indexOfFrac];
|
|
|
|
for (int i = 0; i < indexOfFrac; i++)
|
|
|
|
add[i] = toFracture.getElement(i);
|
|
|
|
leftIns = add.length;
|
|
|
|
leftBranch.replace(0, 0, add);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
|
|
|
int count = size - indexOfFrac - 1;
|
|
|
|
if (count > 0)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element[] add = new Element[count];
|
|
|
|
int j = 0;
|
|
|
|
int i = indexOfFrac + 1;
|
|
|
|
while (j < count)
|
|
|
|
add[j++] = toFracture.getElement(i++);
|
|
|
|
rightBranch.replace(0, 0, add);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
|
|
|
// add to fracture to rightBranch
|
|
|
|
// Check if we can join the right frac leaf with the next leaf
|
|
|
|
int rm = 0;
|
|
|
|
int end = fracEnd;
|
|
|
|
Element next = rightBranch.getElement(0);
|
|
|
|
if (next != null && next.isLeaf()
|
|
|
|
&& next.getAttributes().isEqual(atts))
|
|
|
|
{
|
|
|
|
end = next.getEndOffset();
|
|
|
|
rm = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Element rightFracturedLeaf = createLeafElement(rightBranch, atts,
|
|
|
|
pos, end);
|
|
|
|
rightBranch.replace(0, rm, new Element[] { rightFracturedLeaf });
|
|
|
|
|
|
|
|
// recreate those elements after parentIndex and add/remove all
|
|
|
|
// new/old elements to parent
|
|
|
|
int remove = parSize - parentIndex;
|
|
|
|
Element[] removed = new Element[0];
|
|
|
|
Element[] added2 = new Element[0];
|
|
|
|
if (remove > 0)
|
|
|
|
{
|
|
|
|
removed = new Element[remove];
|
|
|
|
int s = 0;
|
|
|
|
for (int j = parentIndex; j < parSize; j++)
|
|
|
|
removed[s++] = parent.getElement(j);
|
|
|
|
edit.addRemovedElements(removed);
|
|
|
|
added2 = recreateAfterFracture(removed, parent, 1,
|
|
|
|
rightBranch.getEndOffset());
|
|
|
|
}
|
|
|
|
|
|
|
|
edit.addAddedElements(added);
|
|
|
|
edit.addAddedElements(added2);
|
|
|
|
elementStack.push(rightBranch);
|
|
|
|
lastFractured = rightFracturedLeaf;
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
else
|
2006-03-10 21:46:48 +00:00
|
|
|
fracNotCreated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recreates all the elements from the parent to the element on the top of
|
|
|
|
* the stack, starting from startFrom with the starting offset of
|
|
|
|
* startOffset.
|
|
|
|
*
|
|
|
|
* @param recreate -
|
|
|
|
* the elements to recreate
|
|
|
|
* @param parent -
|
|
|
|
* the element to add the new elements to
|
|
|
|
* @param startFrom -
|
|
|
|
* where to start recreating from
|
|
|
|
* @param startOffset -
|
|
|
|
* the offset of the first element
|
|
|
|
* @return the array of added elements
|
|
|
|
*/
|
|
|
|
private Element[] recreateAfterFracture(Element[] recreate,
|
|
|
|
BranchElement parent, int startFrom,
|
|
|
|
int startOffset)
|
|
|
|
{
|
|
|
|
Element[] added = new Element[recreate.length - startFrom];
|
|
|
|
int j = 0;
|
|
|
|
for (int i = startFrom; i < recreate.length; i++)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element curr = recreate[i];
|
|
|
|
int len = curr.getEndOffset() - curr.getStartOffset();
|
|
|
|
if (curr instanceof LeafElement)
|
|
|
|
added[j] = createLeafElement(parent, curr.getAttributes(),
|
|
|
|
startOffset, startOffset + len);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
BranchElement br =
|
|
|
|
(BranchElement) createBranchElement(parent,
|
|
|
|
curr.getAttributes());
|
|
|
|
int bSize = curr.getElementCount();
|
|
|
|
for (int k = 0; k < bSize; k++)
|
|
|
|
{
|
|
|
|
Element bCurr = curr.getElement(k);
|
|
|
|
Element[] add = recreateAfterFracture(new Element[] { bCurr }, br, 0,
|
|
|
|
startOffset);
|
|
|
|
br.replace(0, 0, add);
|
|
|
|
|
|
|
|
}
|
|
|
|
added[j] = br;
|
|
|
|
}
|
|
|
|
startOffset += len;
|
|
|
|
j++;
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
|
|
|
return added;
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method looks through the Vector of Edits to see if there is already an
|
|
|
|
* Edit object associated with the given paragraph. If there is, then we
|
|
|
|
* return it. Otherwise we create a new Edit object, add it to the vector, and
|
|
|
|
* return it. Note: this method is package private to avoid accessors.
|
|
|
|
*
|
|
|
|
* @param index
|
|
|
|
* the index associated with the Edit we want to create
|
|
|
|
* @param para
|
|
|
|
* the paragraph associated with the Edit we want
|
|
|
|
* @return the found or created Edit object
|
|
|
|
*/
|
|
|
|
Edit getEditForParagraphAndIndex(BranchElement para, int index)
|
|
|
|
{
|
|
|
|
Edit curr;
|
|
|
|
int size = edits.size();
|
|
|
|
for (int i = 0; i < size; i++)
|
|
|
|
{
|
|
|
|
curr = (Edit) edits.elementAt(i);
|
|
|
|
if (curr.e.equals(para))
|
|
|
|
return curr;
|
|
|
|
}
|
|
|
|
curr = new Edit(para, index, null, null);
|
|
|
|
edits.add(curr);
|
2006-01-17 18:09:40 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
return curr;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Instance of all editing information for an object in the Vector. This class
|
|
|
|
* is used to add information to the DocumentEvent associated with an
|
|
|
|
* insertion/removal/change as well as to store the changes that need to be
|
|
|
|
* made so they can be made all at the same (appropriate) time.
|
|
|
|
*/
|
|
|
|
class Edit
|
|
|
|
{
|
|
|
|
/** The element to edit . */
|
|
|
|
Element e;
|
|
|
|
|
|
|
|
/** The index of the change. */
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/** The removed elements. */
|
|
|
|
Vector removed = new Vector();
|
|
|
|
|
|
|
|
/** The added elements. */
|
|
|
|
Vector added = new Vector();
|
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Return an array containing the Elements that have been removed from the
|
|
|
|
* paragraph associated with this Edit.
|
2006-01-17 18:09:40 +00:00
|
|
|
*
|
2006-03-10 21:46:48 +00:00
|
|
|
* @return an array of removed Elements
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
public Element[] getRemovedElements()
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
int size = removed.size();
|
|
|
|
Element[] removedElements = new Element[size];
|
|
|
|
for (int i = 0; i < size; i++)
|
|
|
|
removedElements[i] = (Element) removed.elementAt(i);
|
|
|
|
return removedElements;
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Return an array containing the Elements that have been added to the
|
|
|
|
* paragraph associated with this Edit.
|
2006-01-17 18:09:40 +00:00
|
|
|
*
|
2006-03-10 21:46:48 +00:00
|
|
|
* @return an array of added Elements
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
public Element[] getAddedElements()
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
int size = added.size();
|
|
|
|
Element[] addedElements = new Element[size];
|
|
|
|
for (int i = 0; i < size; i++)
|
|
|
|
addedElements[i] = (Element) added.elementAt(i);
|
|
|
|
return addedElements;
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/**
|
|
|
|
* Checks if e is already in the vector.
|
|
|
|
*
|
|
|
|
* @param e - the Element to look for
|
|
|
|
* @param v - the vector to search
|
|
|
|
* @return true if e is in v.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
private boolean contains(Vector v, Element e)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
if (e == null)
|
|
|
|
return false;
|
2005-11-15 23:20:01 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
int i = v.size();
|
|
|
|
for (int j = 0; j < i; j++)
|
|
|
|
{
|
|
|
|
Element e1 = (Element) v.get(j);
|
|
|
|
if ((e1 != null) && (e1.getAttributes().isEqual(e.getAttributes()))
|
|
|
|
&& (e1.getName().equals(e.getName()))
|
|
|
|
&& (e1.getStartOffset() == e.getStartOffset())
|
|
|
|
&& (e1.getEndOffset() == e.getEndOffset())
|
|
|
|
&& (e1.getParentElement().equals(e.getParentElement()))
|
|
|
|
&& (e1.getElementCount() == e.getElementCount()))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Adds one Element to the vector of removed Elements.
|
|
|
|
*
|
|
|
|
* @param e
|
|
|
|
* the Element to add
|
2006-01-17 18:09:40 +00:00
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
public void addRemovedElement(Element e)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
if (!contains(removed, e))
|
|
|
|
removed.add(e);
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/**
|
|
|
|
* Adds each Element in the given array to the vector of removed Elements
|
|
|
|
*
|
|
|
|
* @param e
|
|
|
|
* the array containing the Elements to be added
|
|
|
|
*/
|
|
|
|
public void addRemovedElements(Element[] e)
|
|
|
|
{
|
|
|
|
if (e == null || e.length == 0)
|
|
|
|
return;
|
|
|
|
for (int i = 0; i < e.length; i++)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
if (!contains(removed, e[i]))
|
|
|
|
removed.add(e[i]);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds one Element to the vector of added Elements.
|
|
|
|
*
|
|
|
|
* @param e
|
|
|
|
* the Element to add
|
|
|
|
*/
|
|
|
|
public void addAddedElement(Element e)
|
|
|
|
{
|
|
|
|
if (!contains(added, e))
|
|
|
|
added.add(e);
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/**
|
|
|
|
* Adds each Element in the given array to the vector of added Elements.
|
|
|
|
*
|
|
|
|
* @param e
|
|
|
|
* the array containing the Elements to be added
|
|
|
|
*/
|
|
|
|
public void addAddedElements(Element[] e)
|
|
|
|
{
|
|
|
|
if (e == null || e.length == 0)
|
|
|
|
return;
|
|
|
|
for (int i = 0; i < e.length; i++)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
if (!contains(added, e[i]))
|
|
|
|
added.add(e[i]);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/**
|
|
|
|
* Creates a new Edit object with the given parameters
|
|
|
|
*
|
|
|
|
* @param e
|
|
|
|
* the paragraph Element associated with this Edit
|
|
|
|
* @param i
|
|
|
|
* the index within the paragraph where changes are started
|
|
|
|
* @param removed
|
|
|
|
* an array containing Elements that should be removed from the
|
|
|
|
* paragraph Element
|
|
|
|
* @param added
|
|
|
|
* an array containing Elements that should be added to the
|
|
|
|
* paragraph Element
|
|
|
|
*/
|
|
|
|
public Edit(Element e, int i, Element[] removed, Element[] added)
|
|
|
|
{
|
|
|
|
this.e = e;
|
|
|
|
this.index = i;
|
|
|
|
addRemovedElements(removed);
|
|
|
|
addAddedElements(added);
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* An element type for sections. This is a simple BranchElement with a unique
|
|
|
|
* name.
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
protected class SectionElement extends BranchElement
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Creates a new SectionElement.
|
|
|
|
*/
|
|
|
|
public SectionElement()
|
|
|
|
{
|
|
|
|
super(null, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the name of the element. This method always returns
|
|
|
|
* "section".
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return the name of the element
|
|
|
|
*/
|
|
|
|
public String getName()
|
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
return SectionElementName;
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receives notification when any of the document's style changes and calls
|
|
|
|
* {@link DefaultStyledDocument#styleChanged(Style)}.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @author Roman Kennke (kennke@aicas.com)
|
|
|
|
*/
|
2006-03-10 21:46:48 +00:00
|
|
|
private class StyleChangeListener implements ChangeListener
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receives notification when any of the document's style changes and calls
|
|
|
|
* {@link DefaultStyledDocument#styleChanged(Style)}.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
* the change event
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
public void stateChanged(ChangeEvent event)
|
|
|
|
{
|
|
|
|
Style style = (Style) event.getSource();
|
|
|
|
styleChanged(style);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** The serialization UID (compatible with JDK1.5). */
|
|
|
|
private static final long serialVersionUID = 940485415728614849L;
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* The default size to use for new content buffers.
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public static final int BUFFER_SIZE_DEFAULT = 4096;
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* The <code>EditorBuffer</code> that is used to manage to
|
|
|
|
* <code>Element</code> hierarchy.
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
protected DefaultStyledDocument.ElementBuffer buffer;
|
2005-09-23 21:31:04 +00:00
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
|
|
|
* Listens for changes on this document's styles and notifies styleChanged().
|
|
|
|
*/
|
|
|
|
private StyleChangeListener styleChangeListener;
|
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
/**
|
|
|
|
* Vector that contains all the edits. Maybe replace by a HashMap.
|
|
|
|
*/
|
|
|
|
Vector edits = new Vector();
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Creates a new <code>DefaultStyledDocument</code>.
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public DefaultStyledDocument()
|
|
|
|
{
|
|
|
|
this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
|
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Creates a new <code>DefaultStyledDocument</code> that uses the specified
|
|
|
|
* {@link StyleContext}.
|
|
|
|
*
|
|
|
|
* @param context
|
|
|
|
* the <code>StyleContext</code> to use
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public DefaultStyledDocument(StyleContext context)
|
|
|
|
{
|
|
|
|
this(new GapContent(BUFFER_SIZE_DEFAULT), context);
|
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Creates a new <code>DefaultStyledDocument</code> that uses the specified
|
|
|
|
* {@link StyleContext} and {@link Content} buffer.
|
|
|
|
*
|
|
|
|
* @param content
|
|
|
|
* the <code>Content</code> buffer to use
|
|
|
|
* @param context
|
|
|
|
* the <code>StyleContext</code> to use
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public DefaultStyledDocument(AbstractDocument.Content content,
|
2006-03-10 21:46:48 +00:00
|
|
|
StyleContext context)
|
2005-07-16 00:30:23 +00:00
|
|
|
{
|
|
|
|
super(content, context);
|
|
|
|
buffer = new ElementBuffer(createDefaultRoot());
|
|
|
|
setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
|
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Adds a style into the style hierarchy. Unspecified style attributes can be
|
|
|
|
* resolved in the <code>parent</code> style, if one is specified. While it
|
|
|
|
* is legal to add nameless styles (<code>nm == null</code),
|
2005-09-23 21:31:04 +00:00
|
|
|
* you must be aware that the client application is then responsible
|
|
|
|
* for managing the style hierarchy, since unnamed styles cannot be
|
|
|
|
* looked up by their name.
|
|
|
|
*
|
|
|
|
* @param nm the name of the style or <code>null</code> if the style should
|
|
|
|
* be unnamed
|
|
|
|
* @param parent the parent in which unspecified style attributes are
|
|
|
|
* resolved, or <code>null</code> if that is not necessary
|
|
|
|
*
|
|
|
|
* @return the newly created <code>Style</code>
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Style addStyle(String nm, Style parent)
|
|
|
|
{
|
|
|
|
StyleContext context = (StyleContext) getAttributeContext();
|
2005-11-15 23:20:01 +00:00
|
|
|
Style newStyle = context.addStyle(nm, parent);
|
|
|
|
|
|
|
|
// Register change listener.
|
|
|
|
if (styleChangeListener == null)
|
|
|
|
styleChangeListener = new StyleChangeListener();
|
|
|
|
newStyle.addChangeListener(styleChangeListener);
|
|
|
|
|
|
|
|
return newStyle;
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create the default root element for this kind of <code>Document</code>.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the default root element for this kind of <code>Document</code>
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
protected AbstractDocument.AbstractElement createDefaultRoot()
|
|
|
|
{
|
|
|
|
Element[] tmp;
|
2005-11-15 23:20:01 +00:00
|
|
|
SectionElement section = new SectionElement();
|
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
BranchElement paragraph = new BranchElement(section, null);
|
2005-07-16 00:30:23 +00:00
|
|
|
tmp = new Element[1];
|
|
|
|
tmp[0] = paragraph;
|
|
|
|
section.replace(0, 0, tmp);
|
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
Element leaf = new LeafElement(paragraph, null, 0, 1);
|
2005-07-16 00:30:23 +00:00
|
|
|
tmp = new Element[1];
|
|
|
|
tmp[0] = leaf;
|
|
|
|
paragraph.replace(0, 0, tmp);
|
2005-11-15 23:20:01 +00:00
|
|
|
|
2005-07-16 00:30:23 +00:00
|
|
|
return section;
|
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Returns the <code>Element</code> that corresponds to the character at the
|
|
|
|
* specified position.
|
|
|
|
*
|
|
|
|
* @param position
|
|
|
|
* the position of which we query the corresponding
|
|
|
|
* <code>Element</code>
|
|
|
|
* @return the <code>Element</code> that corresponds to the character at the
|
|
|
|
* specified position
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Element getCharacterElement(int position)
|
|
|
|
{
|
|
|
|
Element element = getDefaultRootElement();
|
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
while (!element.isLeaf())
|
2005-07-16 00:30:23 +00:00
|
|
|
{
|
2005-11-15 23:20:01 +00:00
|
|
|
int index = element.getElementIndex(position);
|
|
|
|
element = element.getElement(index);
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2005-07-16 00:30:23 +00:00
|
|
|
return element;
|
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Extracts a background color from a set of attributes.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param attributes
|
|
|
|
* the attributes from which to get a background color
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the background color that correspond to the attributes
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Color getBackground(AttributeSet attributes)
|
|
|
|
{
|
|
|
|
StyleContext context = (StyleContext) getAttributeContext();
|
|
|
|
return context.getBackground(attributes);
|
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the default root element.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the default root element
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Element getDefaultRootElement()
|
|
|
|
{
|
|
|
|
return buffer.getRootElement();
|
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Extracts a font from a set of attributes.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param attributes
|
|
|
|
* the attributes from which to get a font
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the font that correspond to the attributes
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Font getFont(AttributeSet attributes)
|
|
|
|
{
|
|
|
|
StyleContext context = (StyleContext) getAttributeContext();
|
|
|
|
return context.getFont(attributes);
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Extracts a foreground color from a set of attributes.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param attributes
|
|
|
|
* the attributes from which to get a foreground color
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the foreground color that correspond to the attributes
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Color getForeground(AttributeSet attributes)
|
|
|
|
{
|
|
|
|
StyleContext context = (StyleContext) getAttributeContext();
|
|
|
|
return context.getForeground(attributes);
|
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the logical <code>Style</code> for the specified position.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param position
|
|
|
|
* the position from which to query to logical style
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the logical <code>Style</code> for the specified position
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Style getLogicalStyle(int position)
|
|
|
|
{
|
|
|
|
Element paragraph = getParagraphElement(position);
|
|
|
|
AttributeSet attributes = paragraph.getAttributes();
|
2006-01-17 18:09:40 +00:00
|
|
|
AttributeSet a = attributes.getResolveParent();
|
|
|
|
// If the resolve parent is not of type Style, we return null.
|
|
|
|
if (a instanceof Style)
|
|
|
|
return (Style) a;
|
|
|
|
return null;
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
2005-09-23 21:31:04 +00:00
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Returns the paragraph element for the specified position. If the position
|
|
|
|
* is outside the bounds of the document's root element, then the closest
|
|
|
|
* element is returned. That is the last paragraph if
|
2005-11-15 23:20:01 +00:00
|
|
|
* <code>position >= endIndex</code> or the first paragraph if
|
|
|
|
* <code>position < startIndex</code>.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param position
|
|
|
|
* the position for which to query the paragraph element
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the paragraph element for the specified position
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Element getParagraphElement(int position)
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element e = getDefaultRootElement();
|
|
|
|
while (!e.isLeaf())
|
|
|
|
e = e.getElement(e.getElementIndex(position));
|
|
|
|
|
|
|
|
if (e != null)
|
|
|
|
return e.getParentElement();
|
|
|
|
return e;
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Looks up and returns a named <code>Style</code>.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param nm
|
|
|
|
* the name of the <code>Style</code>
|
2005-09-23 21:31:04 +00:00
|
|
|
* @return the found <code>Style</code> of <code>null</code> if no such
|
|
|
|
* <code>Style</code> exists
|
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public Style getStyle(String nm)
|
|
|
|
{
|
|
|
|
StyleContext context = (StyleContext) getAttributeContext();
|
|
|
|
return context.getStyle(nm);
|
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Removes a named <code>Style</code> from the style hierarchy.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param nm
|
|
|
|
* the name of the <code>Style</code> to be removed
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public void removeStyle(String nm)
|
|
|
|
{
|
|
|
|
StyleContext context = (StyleContext) getAttributeContext();
|
|
|
|
context.removeStyle(nm);
|
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Sets text attributes for the fragment specified by <code>offset</code>
|
|
|
|
* and <code>length</code>.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param offset
|
|
|
|
* the start offset of the fragment
|
|
|
|
* @param length
|
|
|
|
* the length of the fragment
|
|
|
|
* @param attributes
|
|
|
|
* the text attributes to set
|
|
|
|
* @param replace
|
|
|
|
* if <code>true</code>, the attributes of the current selection
|
|
|
|
* are overridden, otherwise they are merged
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public void setCharacterAttributes(int offset, int length,
|
2006-03-10 21:46:48 +00:00
|
|
|
AttributeSet attributes, boolean replace)
|
2005-07-16 00:30:23 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
// Exit early if length is 0, so no DocumentEvent is created or fired.
|
|
|
|
if (length == 0)
|
|
|
|
return;
|
|
|
|
try
|
2005-09-23 21:31:04 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
// Must obtain a write lock for this method. writeLock() and
|
2006-01-17 18:09:40 +00:00
|
|
|
// writeUnlock() should always be in try/finally block to make
|
|
|
|
// sure that locking happens in a balanced manner.
|
|
|
|
writeLock();
|
2006-03-10 21:46:48 +00:00
|
|
|
DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
|
|
|
|
length,
|
|
|
|
DocumentEvent.EventType.CHANGE);
|
2006-01-17 18:09:40 +00:00
|
|
|
|
|
|
|
// Modify the element structure so that the interval begins at an
|
|
|
|
// element
|
|
|
|
// start and ends at an element end.
|
|
|
|
buffer.change(offset, length, ev);
|
|
|
|
|
|
|
|
Element root = getDefaultRootElement();
|
|
|
|
// Visit all paragraph elements within the specified interval
|
|
|
|
int end = offset + length;
|
|
|
|
Element curr;
|
2006-03-10 21:46:48 +00:00
|
|
|
for (int pos = offset; pos < end;)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
// Get the CharacterElement at offset pos.
|
|
|
|
curr = getCharacterElement(pos);
|
|
|
|
if (pos == curr.getEndOffset())
|
|
|
|
break;
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes();
|
|
|
|
ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace));
|
|
|
|
// If replace is true, remove all the old attributes.
|
|
|
|
if (replace)
|
|
|
|
a.removeAttributes(a);
|
|
|
|
// Add all the new attributes.
|
|
|
|
a.addAttributes(attributes);
|
|
|
|
// Increment pos so we can check the next CharacterElement.
|
|
|
|
pos = curr.getEndOffset();
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
fireChangedUpdate(ev);
|
|
|
|
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
writeUnlock();
|
2005-09-23 21:31:04 +00:00
|
|
|
}
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Sets the logical style for the paragraph at the specified position.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param position
|
|
|
|
* the position at which the logical style is added
|
|
|
|
* @param style
|
|
|
|
* the style to set for the current paragraph
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public void setLogicalStyle(int position, Style style)
|
|
|
|
{
|
|
|
|
Element el = getParagraphElement(position);
|
2006-01-17 18:09:40 +00:00
|
|
|
// getParagraphElement doesn't return null but subclasses might so
|
|
|
|
// we check for null here.
|
|
|
|
if (el == null)
|
|
|
|
return;
|
|
|
|
try
|
2006-03-10 21:46:48 +00:00
|
|
|
{
|
|
|
|
writeLock();
|
|
|
|
if (el instanceof AbstractElement)
|
|
|
|
{
|
|
|
|
AbstractElement ael = (AbstractElement) el;
|
|
|
|
ael.setResolveParent(style);
|
|
|
|
int start = el.getStartOffset();
|
|
|
|
int end = el.getEndOffset();
|
|
|
|
DefaultDocumentEvent ev = new DefaultDocumentEvent(start,
|
|
|
|
end - start,
|
|
|
|
DocumentEvent.EventType.CHANGE);
|
|
|
|
fireChangedUpdate(ev);
|
|
|
|
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw new AssertionError(
|
|
|
|
"paragraph elements are expected to be"
|
|
|
|
+ "instances of AbstractDocument.AbstractElement");
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
finally
|
2006-03-10 21:46:48 +00:00
|
|
|
{
|
|
|
|
writeUnlock();
|
|
|
|
}
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|
|
|
|
|
2005-09-23 21:31:04 +00:00
|
|
|
/**
|
|
|
|
* Sets text attributes for the paragraph at the specified fragment.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param offset
|
|
|
|
* the beginning of the fragment
|
|
|
|
* @param length
|
|
|
|
* the length of the fragment
|
|
|
|
* @param attributes
|
|
|
|
* the text attributes to set
|
|
|
|
* @param replace
|
|
|
|
* if <code>true</code>, the attributes of the current selection
|
|
|
|
* are overridden, otherwise they are merged
|
2005-09-23 21:31:04 +00:00
|
|
|
*/
|
2005-07-16 00:30:23 +00:00
|
|
|
public void setParagraphAttributes(int offset, int length,
|
2006-03-10 21:46:48 +00:00
|
|
|
AttributeSet attributes, boolean replace)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
try
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
// Must obtain a write lock for this method. writeLock() and
|
2006-01-17 18:09:40 +00:00
|
|
|
// writeUnlock() should always be in try/finally blocks to make
|
|
|
|
// sure that locking occurs in a balanced manner.
|
|
|
|
writeLock();
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
// Create a DocumentEvent to use for changedUpdate().
|
2006-03-10 21:46:48 +00:00
|
|
|
DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
|
|
|
|
length,
|
|
|
|
DocumentEvent.EventType.CHANGE);
|
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
// Have to iterate through all the _paragraph_ elements that are
|
|
|
|
// contained or partially contained in the interval
|
|
|
|
// (offset, offset + length).
|
|
|
|
Element rootElement = getDefaultRootElement();
|
|
|
|
int startElement = rootElement.getElementIndex(offset);
|
|
|
|
int endElement = rootElement.getElementIndex(offset + length - 1);
|
|
|
|
if (endElement < startElement)
|
|
|
|
endElement = startElement;
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
for (int i = startElement; i <= endElement; i++)
|
|
|
|
{
|
|
|
|
Element par = rootElement.getElement(i);
|
|
|
|
MutableAttributeSet a = (MutableAttributeSet) par.getAttributes();
|
|
|
|
// Add the change to the DocumentEvent.
|
|
|
|
ev.addEdit(new AttributeUndoableEdit(par, attributes, replace));
|
|
|
|
// If replace is true remove the old attributes.
|
|
|
|
if (replace)
|
|
|
|
a.removeAttributes(a);
|
|
|
|
// Add the new attributes.
|
|
|
|
a.addAttributes(attributes);
|
|
|
|
}
|
|
|
|
fireChangedUpdate(ev);
|
|
|
|
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
|
|
|
|
}
|
|
|
|
finally
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
writeUnlock();
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* Called in response to content insert actions. This is used to update the
|
|
|
|
* element structure.
|
|
|
|
*
|
|
|
|
* @param ev
|
|
|
|
* the <code>DocumentEvent</code> describing the change
|
|
|
|
* @param attr
|
|
|
|
* the attributes for the change
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
|
|
|
|
{
|
|
|
|
super.insertUpdate(ev, attr);
|
2006-01-17 18:09:40 +00:00
|
|
|
// If the attribute set is null, use an empty attribute set.
|
|
|
|
if (attr == null)
|
|
|
|
attr = SimpleAttributeSet.EMPTY;
|
2005-11-15 23:20:01 +00:00
|
|
|
int offset = ev.getOffset();
|
|
|
|
int length = ev.getLength();
|
|
|
|
int endOffset = offset + length;
|
2006-03-10 21:46:48 +00:00
|
|
|
AttributeSet paragraphAttributes = getParagraphElement(endOffset).getAttributes();
|
2005-11-15 23:20:01 +00:00
|
|
|
Segment txt = new Segment();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
getText(offset, length, txt);
|
|
|
|
}
|
|
|
|
catch (BadLocationException ex)
|
|
|
|
{
|
|
|
|
AssertionError ae = new AssertionError("Unexpected bad location");
|
|
|
|
ae.initCause(ex);
|
|
|
|
throw ae;
|
|
|
|
}
|
|
|
|
|
|
|
|
int len = 0;
|
|
|
|
Vector specs = new Vector();
|
2006-01-17 18:09:40 +00:00
|
|
|
ElementSpec finalStartTag = null;
|
|
|
|
short finalStartDirection = ElementSpec.OriginateDirection;
|
|
|
|
boolean prevCharWasNewline = false;
|
2005-11-15 23:20:01 +00:00
|
|
|
Element prev = getCharacterElement(offset);
|
2006-03-10 21:46:48 +00:00
|
|
|
Element next = getCharacterElement(endOffset);
|
2006-01-17 18:09:40 +00:00
|
|
|
Element prevParagraph = getParagraphElement(offset);
|
|
|
|
Element paragraph = getParagraphElement(endOffset);
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
int segmentEnd = txt.offset + txt.count;
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
// Check to see if we're inserting immediately after a newline.
|
|
|
|
if (offset > 0)
|
|
|
|
{
|
|
|
|
try
|
2006-03-10 21:46:48 +00:00
|
|
|
{
|
|
|
|
String s = getText(offset - 1, 1);
|
|
|
|
if (s.equals("\n"))
|
|
|
|
{
|
|
|
|
finalStartDirection = handleInsertAfterNewline(specs, offset,
|
|
|
|
endOffset,
|
|
|
|
prevParagraph,
|
|
|
|
paragraph,
|
|
|
|
paragraphAttributes);
|
|
|
|
|
|
|
|
prevCharWasNewline = true;
|
|
|
|
// Find the final start tag from the ones just created.
|
|
|
|
for (int i = 0; i < specs.size(); i++)
|
|
|
|
if (((ElementSpec) specs.get(i)).getType() == ElementSpec.StartTagType)
|
|
|
|
finalStartTag = (ElementSpec) specs.get(i);
|
|
|
|
}
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
catch (BadLocationException ble)
|
2006-03-10 21:46:48 +00:00
|
|
|
{
|
|
|
|
// This shouldn't happen.
|
|
|
|
AssertionError ae = new AssertionError();
|
|
|
|
ae.initCause(ble);
|
|
|
|
throw ae;
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2005-11-15 23:20:01 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
for (int i = txt.offset; i < segmentEnd; ++i)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
|
|
|
len++;
|
|
|
|
if (txt.array[i] == '\n')
|
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
// Add the ElementSpec for the content.
|
2006-03-10 21:46:48 +00:00
|
|
|
specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
|
2005-11-15 23:20:01 +00:00
|
|
|
|
|
|
|
// Add ElementSpecs for the newline.
|
2006-01-17 18:09:40 +00:00
|
|
|
specs.add(new ElementSpec(null, ElementSpec.EndTagType));
|
|
|
|
finalStartTag = new ElementSpec(paragraphAttributes,
|
2006-03-10 21:46:48 +00:00
|
|
|
ElementSpec.StartTagType);
|
2006-01-17 18:09:40 +00:00
|
|
|
specs.add(finalStartTag);
|
2005-11-15 23:20:01 +00:00
|
|
|
len = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create last element if last character hasn't been a newline.
|
2006-03-10 21:46:48 +00:00
|
|
|
if (len > 0)
|
2006-01-17 18:09:40 +00:00
|
|
|
specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
|
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
// Set the direction of the last spec of type StartTagType.
|
|
|
|
// If we are inserting after a newline then this value comes from
|
2006-01-17 18:09:40 +00:00
|
|
|
// handleInsertAfterNewline.
|
|
|
|
if (finalStartTag != null)
|
2006-03-10 21:46:48 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
if (prevCharWasNewline)
|
|
|
|
finalStartTag.setDirection(finalStartDirection);
|
|
|
|
else if (prevParagraph.getEndOffset() != endOffset)
|
2006-03-10 21:46:48 +00:00
|
|
|
finalStartTag.setDirection(ElementSpec.JoinFractureDirection);
|
2006-01-17 18:09:40 +00:00
|
|
|
else
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
// If there is an element AFTER this one, then set the
|
2006-01-17 18:09:40 +00:00
|
|
|
// direction to JoinNextDirection.
|
|
|
|
Element parent = prevParagraph.getParentElement();
|
|
|
|
int index = parent.getElementIndex(offset);
|
|
|
|
if (index + 1 < parent.getElementCount()
|
|
|
|
&& !parent.getElement(index + 1).isLeaf())
|
|
|
|
finalStartTag.setDirection(ElementSpec.JoinNextDirection);
|
|
|
|
}
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
// If we are at the last index, then check if we could probably be
|
|
|
|
// joined with the next element.
|
|
|
|
// This means:
|
2006-03-10 21:46:48 +00:00
|
|
|
// - we must be a ContentTag
|
|
|
|
// - if there is a next Element, we must have the same attributes
|
|
|
|
// - if there is no next Element, but one will be created,
|
|
|
|
// we must have the same attributes as the higher-level run.
|
2006-01-17 18:09:40 +00:00
|
|
|
ElementSpec last = (ElementSpec) specs.lastElement();
|
|
|
|
if (last.getType() == ElementSpec.ContentType)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
Element currentRun = prevParagraph.getElement(prevParagraph.getElementIndex(offset));
|
2006-01-17 18:09:40 +00:00
|
|
|
if (currentRun.getEndOffset() == endOffset)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
if (endOffset < getLength() && next.getAttributes().isEqual(attr)
|
|
|
|
&& last.getType() == ElementSpec.ContentType)
|
|
|
|
last.setDirection(ElementSpec.JoinNextDirection);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (finalStartTag != null
|
2006-03-10 21:46:48 +00:00
|
|
|
&& finalStartTag.getDirection() == ElementSpec.JoinFractureDirection
|
2006-01-17 18:09:40 +00:00
|
|
|
&& currentRun.getAttributes().isEqual(attr))
|
|
|
|
{
|
|
|
|
last.setDirection(ElementSpec.JoinNextDirection);
|
|
|
|
}
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
// If we are at the first new element, then check if it could be
|
|
|
|
// joined with the previous element.
|
|
|
|
ElementSpec first = (ElementSpec) specs.firstElement();
|
|
|
|
if (prev.getAttributes().isEqual(attr)
|
|
|
|
&& first.getType() == ElementSpec.ContentType)
|
|
|
|
first.setDirection(ElementSpec.JoinPreviousDirection);
|
2005-11-15 23:20:01 +00:00
|
|
|
|
2006-03-10 21:46:48 +00:00
|
|
|
ElementSpec[] elSpecs = (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]);
|
2005-11-15 23:20:01 +00:00
|
|
|
buffer.insert(offset, length, elSpecs, ev);
|
|
|
|
}
|
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
/**
|
2006-03-10 21:46:48 +00:00
|
|
|
* A helper method to set up the ElementSpec buffer for the special case of an
|
|
|
|
* insertion occurring immediately after a newline.
|
|
|
|
*
|
|
|
|
* @param specs
|
|
|
|
* the ElementSpec buffer to initialize.
|
2006-01-17 18:09:40 +00:00
|
|
|
*/
|
|
|
|
short handleInsertAfterNewline(Vector specs, int offset, int endOffset,
|
2006-03-10 21:46:48 +00:00
|
|
|
Element prevParagraph, Element paragraph,
|
|
|
|
AttributeSet a)
|
2006-01-17 18:09:40 +00:00
|
|
|
{
|
|
|
|
if (prevParagraph.getParentElement() == paragraph.getParentElement())
|
|
|
|
{
|
|
|
|
specs.add(new ElementSpec(a, ElementSpec.EndTagType));
|
|
|
|
specs.add(new ElementSpec(a, ElementSpec.StartTagType));
|
2006-03-10 21:46:48 +00:00
|
|
|
if (paragraph.getStartOffset() != endOffset)
|
2006-01-17 18:09:40 +00:00
|
|
|
return ElementSpec.JoinFractureDirection;
|
|
|
|
// If there is an Element after this one, use JoinNextDirection.
|
|
|
|
Element parent = paragraph.getParentElement();
|
2006-03-10 21:46:48 +00:00
|
|
|
if (parent.getElementCount() > (parent.getElementIndex(offset) + 1))
|
2006-01-17 18:09:40 +00:00
|
|
|
return ElementSpec.JoinNextDirection;
|
|
|
|
}
|
|
|
|
return ElementSpec.OriginateDirection;
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
/**
|
|
|
|
* Updates the document structure in response to text removal. This is
|
2006-03-10 21:46:48 +00:00
|
|
|
* forwarded to the {@link ElementBuffer} of this document. Any changes to the
|
|
|
|
* document structure are added to the specified document event and sent to
|
|
|
|
* registered listeners.
|
|
|
|
*
|
|
|
|
* @param ev
|
|
|
|
* the document event that records the changes to the document
|
2006-01-17 18:09:40 +00:00
|
|
|
*/
|
|
|
|
protected void removeUpdate(DefaultDocumentEvent ev)
|
|
|
|
{
|
|
|
|
super.removeUpdate(ev);
|
|
|
|
buffer.remove(ev.getOffset(), ev.getLength(), ev);
|
|
|
|
}
|
|
|
|
|
2005-11-15 23:20:01 +00:00
|
|
|
/**
|
|
|
|
* Returns an enumeration of all style names.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
2005-11-15 23:20:01 +00:00
|
|
|
* @return an enumeration of all style names
|
|
|
|
*/
|
|
|
|
public Enumeration getStyleNames()
|
|
|
|
{
|
|
|
|
StyleContext context = (StyleContext) getAttributeContext();
|
|
|
|
return context.getStyleNames();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when any of this document's styles changes.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param style
|
|
|
|
* the style that changed
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
protected void styleChanged(Style style)
|
2005-07-16 00:30:23 +00:00
|
|
|
{
|
2005-11-15 23:20:01 +00:00
|
|
|
// Nothing to do here. This is intended to be overridden by subclasses.
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts a bulk of structured content at once.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param offset
|
|
|
|
* the offset at which the content should be inserted
|
|
|
|
* @param data
|
|
|
|
* the actual content spec to be inserted
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
protected void insert(int offset, ElementSpec[] data)
|
2006-03-10 21:46:48 +00:00
|
|
|
throws BadLocationException
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
if (data == null || data.length == 0)
|
|
|
|
return;
|
|
|
|
try
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
// writeLock() and writeUnlock() should always be in a try/finally
|
2006-03-10 21:46:48 +00:00
|
|
|
// block so that locking balance is guaranteed even if some
|
2006-01-17 18:09:40 +00:00
|
|
|
// exception is thrown.
|
|
|
|
writeLock();
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
// First we collect the content to be inserted.
|
|
|
|
StringBuffer contentBuffer = new StringBuffer();
|
|
|
|
for (int i = 0; i < data.length; i++)
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
// Collect all inserts into one so we can get the correct
|
|
|
|
// ElementEdit
|
|
|
|
ElementSpec spec = data[i];
|
|
|
|
if (spec.getArray() != null && spec.getLength() > 0)
|
|
|
|
contentBuffer.append(spec.getArray(), spec.getOffset(),
|
|
|
|
spec.getLength());
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
|
|
|
|
int length = contentBuffer.length();
|
|
|
|
|
|
|
|
// If there was no content inserted then exit early.
|
|
|
|
if (length == 0)
|
|
|
|
return;
|
2006-03-10 21:46:48 +00:00
|
|
|
|
2006-01-17 18:09:40 +00:00
|
|
|
UndoableEdit edit = content.insertString(offset,
|
|
|
|
contentBuffer.toString());
|
|
|
|
|
|
|
|
// Create the DocumentEvent with the ElementEdit added
|
2006-03-10 21:46:48 +00:00
|
|
|
DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
|
|
|
|
length,
|
|
|
|
DocumentEvent.EventType.INSERT);
|
2006-01-17 18:09:40 +00:00
|
|
|
ev.addEdit(edit);
|
|
|
|
|
|
|
|
// Finally we must update the document structure and fire the insert
|
|
|
|
// update event.
|
|
|
|
buffer.insert(offset, length, data, ev);
|
|
|
|
fireInsertUpdate(ev);
|
|
|
|
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
finally
|
2005-11-15 23:20:01 +00:00
|
|
|
{
|
2006-01-17 18:09:40 +00:00
|
|
|
writeUnlock();
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes the <code>DefaultStyledDocument</code> with the specified
|
|
|
|
* data.
|
2006-03-10 21:46:48 +00:00
|
|
|
*
|
|
|
|
* @param data
|
|
|
|
* the specification of the content with which the document is
|
|
|
|
* initialized
|
2005-11-15 23:20:01 +00:00
|
|
|
*/
|
|
|
|
protected void create(ElementSpec[] data)
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
writeLock();
|
2005-11-15 23:20:01 +00:00
|
|
|
try
|
|
|
|
{
|
2006-03-10 21:46:48 +00:00
|
|
|
// Clear content if there is some.
|
|
|
|
int len = getLength();
|
|
|
|
if (len > 0)
|
|
|
|
remove(0, len);
|
|
|
|
|
|
|
|
// Now we insert the content.
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
for (int i = 0; i < data.length; ++i)
|
|
|
|
{
|
|
|
|
ElementSpec el = data[i];
|
|
|
|
if (el.getArray() != null && el.getLength() > 0)
|
|
|
|
b.append(el.getArray(), el.getOffset(), el.getLength());
|
|
|
|
}
|
|
|
|
Content content = getContent();
|
|
|
|
UndoableEdit cEdit = content.insertString(0, b.toString());
|
|
|
|
|
|
|
|
DefaultDocumentEvent ev =
|
|
|
|
new DefaultDocumentEvent(0, b.length(),
|
|
|
|
DocumentEvent.EventType.INSERT);
|
|
|
|
ev.addEdit(cEdit);
|
|
|
|
|
|
|
|
// We do a little trick here to get the new structure: We instantiate
|
|
|
|
// a new ElementBuffer with a new root element, insert into that root
|
|
|
|
// and then reparent the newly created elements to the old root
|
|
|
|
// element.
|
|
|
|
BranchElement createRoot =
|
|
|
|
(BranchElement) createBranchElement(null, null);
|
|
|
|
Element dummyLeaf = createLeafElement(createRoot, null, 0, 1);
|
|
|
|
createRoot.replace(0, 0, new Element[]{ dummyLeaf });
|
|
|
|
ElementBuffer createBuffer = new ElementBuffer(createRoot);
|
|
|
|
createBuffer.insert(0, b.length(), data, new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT));
|
|
|
|
// Now the new root is the first child of the createRoot.
|
|
|
|
Element newRoot = createRoot.getElement(0);
|
|
|
|
BranchElement root = (BranchElement) getDefaultRootElement();
|
|
|
|
Element[] added = new Element[newRoot.getElementCount()];
|
|
|
|
for (int i = 0; i < added.length; ++i)
|
|
|
|
{
|
|
|
|
added[i] = newRoot.getElement(i);
|
|
|
|
((AbstractElement) added[i]).element_parent = root;
|
|
|
|
}
|
|
|
|
Element[] removed = new Element[root.getElementCount()];
|
|
|
|
for (int i = 0; i < removed.length; ++i)
|
|
|
|
removed[i] = root.getElement(i);
|
|
|
|
|
|
|
|
// Replace the old elements in root with the new and update the event.
|
|
|
|
root.replace(0, removed.length, added);
|
|
|
|
ev.addEdit(new ElementEdit(root, 0, removed, added));
|
|
|
|
|
|
|
|
fireInsertUpdate(ev);
|
|
|
|
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
|
2005-11-15 23:20:01 +00:00
|
|
|
}
|
|
|
|
catch (BadLocationException ex)
|
|
|
|
{
|
|
|
|
AssertionError err = new AssertionError("Unexpected bad location");
|
|
|
|
err.initCause(ex);
|
|
|
|
throw err;
|
|
|
|
}
|
2006-03-10 21:46:48 +00:00
|
|
|
finally
|
|
|
|
{
|
|
|
|
writeUnlock();
|
|
|
|
}
|
2006-01-17 18:09:40 +00:00
|
|
|
}
|
2005-07-16 00:30:23 +00:00
|
|
|
}
|