/**
* Copyright 2009 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.extract;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Display;
import org.waveprotocol.wave.client.common.util.DomHelper;
import org.waveprotocol.wave.client.common.util.QuirksConstants;
import org.waveprotocol.wave.client.editor.ElementHandlerRegistry;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.content.ContentNode;
import org.waveprotocol.wave.client.editor.content.ContentTextNode;
import org.waveprotocol.wave.client.editor.content.Renderer;
import org.waveprotocol.wave.client.editor.content.paragraph.ParagraphHelper;
import org.waveprotocol.wave.client.editor.impl.NodeManager;
import org.waveprotocol.wave.client.editor.selection.html.NativeSelectionUtil;
import org.waveprotocol.wave.model.document.util.DocHelper;
import org.waveprotocol.wave.model.document.util.DocumentContext;
import org.waveprotocol.wave.model.document.util.FilteredView.Skip;
import org.waveprotocol.wave.model.document.util.LocalDocument;
import org.waveprotocol.wave.model.document.util.Point;
import java.util.Collections;
/**
* Controller for a little DOM object to contain the cursor during IME
* composition, to protect it from concurrent mutations to the surrounding DOM.
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class ImeExtractor {
private final SpanElement imeContainer = Document.get().createSpanElement();
private final SpanElement imeInput;
private Point.El<Node> inContainer;
private ContentElement wrapper = null;
private static final String WRAPPER_TAGNAME = "l:ime";
public static void register(ElementHandlerRegistry registry) {
registry.registerRenderer(WRAPPER_TAGNAME, new Renderer() {
@Override
public Element createDomImpl(Renderable element) {
return element.setAutoAppendContainer(Document.get().createSpanElement());
}
});
}
/***/
public ImeExtractor() {
NodeManager.setTransparency(imeContainer, Skip.DEEP);
NodeManager.setMayContainSelectionEvenWhenDeep(imeContainer, true);
if (QuirksConstants.SUPPORTS_CARET_IN_EMPTY_SPAN) {
// For browsers that support putting the caret in an empty span,
// we do just that (it's simpler).
imeInput = imeContainer;
} else {
// For other browsers, we use inline block so we can reuse the
// paragraph logic to keep the ime extractor span open (i.e. to
// allow the cursor to live inside it when it contains no text).
// see #clearContainer()
imeContainer.getStyle().setDisplay(Display.INLINE_BLOCK);
DomHelper.setContentEditable(imeContainer, false, false);
imeInput = Document.get().createSpanElement();
imeInput.getStyle().setDisplay(Display.INLINE_BLOCK);
imeInput.getStyle().setProperty("outline", "0");
DomHelper.setContentEditable(imeInput, true, false);
NodeManager.setTransparency(imeInput, Skip.DEEP);
NodeManager.setMayContainSelectionEvenWhenDeep(imeInput, true);
imeContainer.appendChild(imeInput);
}
clearContainer();
}
/**
* @return the current composition text if isActive(), null otherwise.
*/
public String getContent() {
return isActive() ? imeContainer.getInnerText() : null;
}
/**
* Activates the IME extractor at the given location.
*
* The extraction node will be put in place, and selection moved to it.
*
* @param cxt
* @param location
*/
public void activate(
DocumentContext<ContentNode, ContentElement, ContentTextNode> cxt,
Point<ContentNode> location) {
clearWrapper(cxt.annotatableContent());
Point<ContentNode> point = DocHelper.ensureNodeBoundary(
DocHelper.transparentSlice(location, cxt),
cxt.annotatableContent(), cxt.textNodeOrganiser());
// NOTE(danilatos): Needed as a workaround to bug 2152316
ContentElement container = point.getContainer().asElement();
ContentNode nodeAfter = point.getNodeAfter();
if (nodeAfter != null) {
container = nodeAfter.getParentElement();
}
////
wrapper = cxt.annotatableContent().transparentCreate(
WRAPPER_TAGNAME, Collections.<String, String>emptyMap(),
container, nodeAfter);
wrapper.getContainerNodelet().appendChild(imeContainer);
NativeSelectionUtil.setCaret(inContainer);
}
/**
* Removes the IME extractor node.
* @param doc
* @return the location where the node resided
*/
public Point<ContentNode> deactivate(
LocalDocument<ContentNode, ContentElement, ContentTextNode> doc) {
Point.El<ContentNode> ret = Point.<ContentNode>inElement(
doc.getParentElement(wrapper), doc.getNextSibling(wrapper));
clearWrapper(doc);
return ret;
}
/**
* @return whether the extractor is actively containing composition events
*/
public boolean isActive() {
return wrapper != null;
}
private void clearWrapper(LocalDocument<ContentNode, ContentElement, ContentTextNode> doc) {
if (wrapper != null && wrapper.getParentElement() != null) {
doc.transparentDeepRemove(wrapper);
}
wrapper = null;
clearContainer();
}
private void clearContainer() {
imeInput.setInnerHTML("");
if (!QuirksConstants.SUPPORTS_CARET_IN_EMPTY_SPAN) {
ParagraphHelper.INSTANCE.onEmpty(imeInput);
}
inContainer = Point.<Node>inElement(imeInput, imeInput.getFirstChild());
}
}