package org.pegadi.artis;
import org.pegadi.artis.text.PersonInText;
import org.pegadi.artis.text.PersonInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
import org.w3c.dom.Element;
import javax.swing.text.*;
public abstract class AbstractTextDocument extends javax.swing.text.DefaultStyledDocument {
/** The document type definition for this document. */
DocumentType dt;
/** The schema element representing the root node. */
Element schemaRoot;
/** Next five int's are needed to make automatic uppercase-letters when writing documents*/
protected static final int SPACE_CASE_INIT=1;
protected static final int SPACE_CASE_DOT=2;
protected static final int SPACE_CASE_SPACE=3;
protected int spaceCaseState;
private static final int EASTER_INIT=1;
private static final int EASTER_NEXT=2;
private int easterState = EASTER_INIT;
protected int lastOffset;
protected boolean autoCorrection=false;
public Logger log = LoggerFactory.getLogger(getClass());
public AbstractTextDocument(Node root, StyleContext styles, DocumentType docType) {
super(styles);
log.info("root: " + root);
NodeList elements = root.getChildNodes();
for (int i = 0; i < elements.getLength(); i++) {
addNode(elements.item(i));
}
// XMLUtil.debugDump(docType, " ");
dt = docType;
//schemaRoot = XMLUtil.getSchemaElement(root.getNodeName(),
// root.getOwnerDocument());
schemaRoot = null;
// Remove the extra element that was added when the document was created.
// But only if we have some content
if (getLength() > 0) {
try {
this.remove(this.getLength()-1, 1);
} catch (BadLocationException ble) {
log.error("Failed to remove extra element at the end.", ble);
}
}
spaceCaseState = SPACE_CASE_INIT;
}
public void toggleAutoCorrection(){
autoCorrection = !autoCorrection;
}
public boolean getAutoCorrectionState(){
return autoCorrection;
}
public void setAutoCorrectState(boolean state){
autoCorrection = state;
}
/**
* Inserts a new paragraph at the given position. This method will return
* <code>false</code> if the schema used does not allow a new paragraph at this
* point.<br>
* If multiple paragraph types is available at the given point this method will
* first attempt to insert a paragraph of the same type as the previous paragrap,
* or the first encoutered paragrap if the same as the previous is not allowed.
*
* @param offset Position to insert at.
* @param str The text to insert.
* @param a Attributes that will be applied to the text.
* @return <code>true</code> if the paragraph was created.
* @throws BadLocationException If the offset given is not available.
* https://pegadi.underdusken.no/ticket/83
*/
protected boolean createParagraph(int offset, String str, AttributeSet a) {
// Walk through the document to find the current paragraph
int pos = 0;
javax.swing.text.Element curPara;
Element schemaNode = (Element)schemaRoot.getFirstChild();
log.info("First schema node is " + schemaNode.getAttribute("name"));
while (offset > pos) {
curPara = super.getParagraphElement(pos);
pos = curPara.getEndOffset();
}
return false;
}
public void checkEaster(String str) {
if(easterState==EASTER_INIT && str.equalsIgnoreCase("l")) { //\u00f8
easterState=EASTER_NEXT;
}
else if (easterState == EASTER_NEXT && str.equalsIgnoreCase("s")) {
easterState=EASTER_INIT;
// Try to confuse the user a bit :-)
if(System.currentTimeMillis() % 10 == 0) {
fireEaster();
}
}
else {
easterState=EASTER_INIT;
}
}
/**
* Checks if this node is a ignorable whitespace node
*/
public boolean isIgnorable(Node node) {
if (node instanceof Text) {
String data = ((Text)node).getData();
for(int i = 0;i<data.length();i++) {
if(!Character.isWhitespace(data.charAt(i))) {
return false;
}
}
}
else
return false;
return true;
}
/**
* Adds a new XML node to the document. This method does not check if this is
* legal according to the DTD.
*
* @param node The node to add.
*/
protected void addNode(Node node) {
if (node instanceof Text && !isIgnorable(node)) {
try {
// Styles are applied on the above level
String data = ((Text)node).getData();
String txt = data.replace('\n', ' ');
// Remove all consecutive spaces.
// FIXME: The XML parser should do this instead, but we don't
// know how to make it do it.
int i;
while ((i = txt.indexOf(" ")) != -1) {
int j = i+2;
while (j > txt.length() && txt.charAt(j) == ' ') {
j++;
}
txt = txt.substring(0, i+1) + txt.substring(j);
}
super.insertString(getLength(), txt, null);
return;
} catch (BadLocationException ble) {
log.error("ERROR inserting text", ble);
}
}
int before = getLength();
if(node.getNodeName().equals("person")) {
StringBuilder name = new StringBuilder();
for(int i = 0; i < node.getChildNodes().getLength(); i++) {
if(node.getChildNodes().item(i) instanceof Text) {
name.append(((Text)node.getChildNodes().item(i)).getData());
}
}
Style personStyle = addStyle("person", StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE));
PersonInText box = new PersonInText(new PersonInfo("0", name.toString(), "Default"));
StyleConstants.setComponent(personStyle, box);
try {
super.insertString(getLength(), " ", personStyle);
return;
} catch (BadLocationException e) {
log.error("Badlocation", e);
}
}
NodeList children = node.getChildNodes();
if (children != null && children.getLength() > 0) {
for (int i = 0; i < children.getLength(); i++) {
addNode(children.item(i));
}
}
else {
return;
}
Style s = getStyle(node.getNodeName());
if (s == null) {
log.info("Style {} is unknown, adding style.", node.getNodeName());
Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
s = addStyle(node.getNodeName(), def);
}
Object display = s.getAttribute("display");
if (display != null && display.equals("block")) {
try {
super.insertString(getLength(), "\n", null);
} catch (BadLocationException e) {
log.error("Unable to add terminating '\\n' at block element.", e);
}
super.setParagraphAttributes(before, getLength() - before, s, false);
}
else {
super.setCharacterAttributes(before, getLength() - before, s, false);
}
}
public void addEasterListener(EasterListener l) {
listenerList.add(EasterListener.class, l);
}
public void fireEaster() {
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i] == EasterListener.class) {
((EasterListener)listeners[i+1]).easterEventHappened();
}
}
}
public void fireUpdatePerformed(int offset) {
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i] == UpdatePerformedListener.class) {
((UpdatePerformedListener)listeners[i+1]).updatePerformed(offset);
}
}
}
public void addUpdatePerformedListener(UpdatePerformedListener upl) {
listenerList.add(UpdatePerformedListener.class, upl);
}
}