/**
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.wave.client.editor.content;
import org.waveprotocol.wave.client.editor.EditorStaticDeps;
import org.waveprotocol.wave.model.document.raw.RawDocument;
import org.waveprotocol.wave.model.util.OffsetList;
import org.waveprotocol.wave.model.util.Preconditions;
import java.util.Collections;
import java.util.Map;
/**
* Implementation of {@link RawDocument} for ContentNodes
*
* Allows modifying of just the content wrapper dom, or both the wrapper and
* html dom. In the latter case, consitency between the two is assumed (However
* if inconsistency is detected, it is silently dealt with internally)
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class ContentRawDocument extends FullContentView implements
RawDocument<ContentNode, ContentElement, ContentTextNode> {
// NOTE(danilatos): Very soon we will only need the setupBehaviour method,
// we can construct the nodes directly.
interface Factory {
ContentTextNode createText(String text);
ContentElement createElement(String tagName, Map<String, String> attributes);
void setupBehaviour(ContentElement element);
}
private final Factory factory;
private final ContentElement rootElement;
private boolean affectImpl = false;
/**
* @param rootElement The root element of this document
*/
public ContentRawDocument(ContentElement rootElement, Factory factory) {
this.rootElement = rootElement;
this.factory = factory;
}
/**
* Set the state to the default, which is to equivalently affect the HTML
* implementation at the same time as the wrapper dom, when mutator methods
* are called.
*/
public void setAffectHtml() {
Preconditions.checkState(!affectImpl, "Already affectImpl is true");
affectImpl = true;
}
/**
* @return The current affect html state
*/
public boolean getAffectHtml() {
return affectImpl;
}
/**
* Set the state to not touch the HTML implementation when mutator methods are
* called.
*/
public void clearAffectHtml() {
Preconditions.checkState(affectImpl, "Already not affectImpl");
affectImpl = false;
}
/**
* Same as {@link #createElement(String, Map, ContentElement, ContentNode)},
* except no starting attributes
*/
public ContentElement createElement(String tagName,
ContentElement parent, ContentNode nodeAfter) {
return createElement(tagName, Collections.<String,String>emptyMap(),
parent, nodeAfter);
}
/** {@inheritDoc} */
public ContentElement createElement(String tagName, Map<String, String> attributes,
ContentElement parent, ContentNode nodeAfter) {
ContentElement newElement = factory.createElement(tagName, attributes);
EditorStaticDeps.startIgnoreMutations();
try {
insertBefore(parent, newElement, null, nodeAfter, factory);
return newElement;
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public ContentTextNode createTextNode(String data,
ContentElement parent, ContentNode nodeAfter) {
ContentTextNode newTextNode = factory.createText(data);
EditorStaticDeps.startIgnoreMutations();
try {
insertBefore(parent, newTextNode, nodeAfter);
return newTextNode;
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public ContentNode insertBefore(ContentElement parent, ContentNode newChild,
ContentNode refChild) {
EditorStaticDeps.startIgnoreMutations();
try {
return parent.insertBefore(newChild, refChild, affectImpl);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public ContentNode insertBefore(ContentElement parent, ContentNode from, ContentNode to,
ContentNode refChild) {
return insertBefore(parent, from, to, refChild, null);
}
/**
* @param factory for activation. Only used during node construction, to make
* sure the nodes get activated. It has to be threaded through because
* we want onActivated to get called at exactly the right time, just
* after the nodes get attached to the DOM but BEFORE onAddedToParent
* gets called. For just moving existing nodes around the DOM, we just
* want to pass null for the factory.
*/
private ContentNode insertBefore(ContentElement parent, ContentNode from, ContentNode to,
ContentNode refChild, Factory factory) {
EditorStaticDeps.startIgnoreMutations();
try {
return parent.insertBefore(from, to, refChild, affectImpl, factory);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public void insertData(ContentTextNode textNode, int offset, String arg) {
EditorStaticDeps.startIgnoreMutations();
try {
textNode.insertData(offset, arg, affectImpl);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public void deleteData(ContentTextNode textNode, int offset, int count) {
EditorStaticDeps.startIgnoreMutations();
try {
textNode.deleteData(offset, count, affectImpl);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public void removeAttribute(ContentElement element, String name) {
EditorStaticDeps.startIgnoreMutations();
try {
element.removeAttribute(name);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public void removeChild(ContentElement parent, ContentNode oldChild) {
EditorStaticDeps.startIgnoreMutations();
try {
parent.removeChild(oldChild, affectImpl);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public void setAttribute(ContentElement element, String name, String value) {
EditorStaticDeps.startIgnoreMutations();
try {
element.setAttribute(name, value);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public ContentTextNode splitText(ContentTextNode textNode, int offset) {
EditorStaticDeps.startIgnoreMutations();
try {
return textNode.splitText(offset, affectImpl);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public ContentTextNode mergeText(ContentTextNode secondSibling) {
EditorStaticDeps.startIgnoreMutations();
try {
// TODO(danilatos): Proper implementation
return null;
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
public void appendData(ContentTextNode textNode, String arg) {
EditorStaticDeps.startIgnoreMutations();
try {
insertData(textNode, getData(textNode).length(), arg);
} finally {
EditorStaticDeps.endIgnoreMutations();
}
}
/** {@inheritDoc} */
@Override
public ContentElement getDocumentElement() {
return rootElement;
}
/** {@inheritDoc} */
public OffsetList.Container<ContentNode> getIndexingContainer(ContentNode domNode) {
return domNode.getIndexingContainer();
}
/** {@inheritDoc} */
public void setIndexingContainer(ContentNode domNode,
OffsetList.Container<ContentNode> indexingContainer) {
domNode.setIndexingContainer(indexingContainer);
}
}