/** * @see RawDocument#deleteData(Object, int, int) */ void deleteData(int offset, int count, boolean affectImpl) { String data = getData(); setContentData(data.substring(0, offset) + data.substring(offset + count, data.length())); if (affectImpl) { if (isImplAttached()) { findNodeletWithOffset(offset, nodeletOffsetOutput, getRepairer()); Text nodelet = nodeletOffsetOutput.getNode().cast(); int subOffset = nodeletOffsetOutput.getOffset(); if (nodelet.getLength() - subOffset >= count) { // Handle the special case where the delete is in a single text nodelet // carefully, to avoid splitting it nodelet.deleteData(subOffset, count); getExtendedContext().editing().textNodeletAffected( nodelet, subOffset, -count, TextNodeChangeType.DATA); } else { // General case Node toExcl = implSplitText(offset + count); Node fromIncl = implSplitText(offset); HtmlView filteredHtml = getFilteredHtmlView(); for (Node node = fromIncl; node != toExcl && node != null;) { Node next = filteredHtml.getNextSibling(node); node.removeFromParent(); node = next; } } } else { // TODO(user): have these assertion failure fixed (b/2129931) // assert getImplNodelet().getLength() == getLength() : // "text node's html impl not normalised while not attached to html dom"; getImplNodelet().deleteData(offset, count); } } }
private boolean matchesSelectionTextNodes(Text nodelet, int affectedAfterOffset) { if (savedSelection == null) { return false; } if (savedSelection.isOrdered()) { if (nodelet == savedSelectionAnchorTextNodelet) { return savedSelectionAnchorOffset > affectedAfterOffset; } else if (nodelet == savedSelectionFocusTextNodelet) { return true; } } else { // The inverse of the above if (nodelet == savedSelectionFocusTextNodelet) { return savedSelectionFocusOffset > affectedAfterOffset; } else if (nodelet == savedSelectionAnchorTextNodelet) { return true; } } return false; }
/** * @see RawDocument#insertData(Object, int, String) */ void insertData(int offset, String arg, boolean affectImpl) { String data = getData(); setContentData( data.substring(0, offset) + arg + data.substring(offset, data.length())); if (affectImpl) { // NOTE(user): There is an issue here. When findNodeletWihOffset causes a // repair, the data may get inserted twice. The repairer may set the DOM // node to reflect the updated content data (which already has the data // inseretd). Then, when insertData is called, the data is inserted again. findNodeletWithOffset(offset, nodeletOffsetOutput, getRepairer()); Text nodelet = nodeletOffsetOutput.getNode().<Text>cast(); int nodeletOffset = nodeletOffsetOutput.getOffset(); nodelet.insertData(nodeletOffset, arg); getExtendedContext().editing().textNodeletAffected( nodelet, nodeletOffset, arg.length(), TextNodeChangeType.DATA); } }
/** * Splits and returns the second. * If split point at a node boundary, doesn't split, but returns the next nodelet. */ private Text implSplitText(int offset) { findNodeletWithOffset(offset, nodeletOffsetOutput, getRepairer()); Text text = nodeletOffsetOutput.getNode().<Text>cast(); if (text.getLength() == nodeletOffsetOutput.getOffset()) { return text.getNextSibling().cast(); } else if (nodeletOffsetOutput.getOffset() == 0) { return text; } else { int nodeletOffset = nodeletOffsetOutput.getOffset(); Text ret = text.splitText(nodeletOffset); // -10000 because the number should be ignored in the splitText case, // so some large number to trigger an error if it is not ignored. getExtendedContext().editing().textNodeletAffected( text, nodeletOffset, -10000, TextNodeChangeType.SPLIT); return ret; } }
/** * Implementation of {@link #getOffset(Text, Node)} that is not bound to any * specific ContentTextNode */ public static int getOffset(Text textNodelet, Text startNode, Node nextImpl, HtmlView view) { int offset = 0; for (Text nodelet = startNode; nodelet != nextImpl; nodelet = view.getNextSibling(nodelet).cast()) { if (nodelet == textNodelet) { return offset; } offset += nodelet.getLength(); } // Programming error, this method assumes the input state was valid, // unlike other methods which don't throw new EditorRuntimeException("Didn't find text nodelet to get offset for"); }
/** * Same as {@link #findNodeletWithOffset(int, HtmlPoint)}, but does not do * a consistency check. Instead it accepts the impl nodelet of the next * wrapper (or null) and assumes everthing is consistent. */ public void findNodeletWithOffset(int offset, HtmlPoint output, Node nextImpl) { HtmlView filteredHtml = getFilteredHtmlView(); int sum = 0; int prevSum; for (Text nodelet = getImplNodelet(); nodelet != nextImpl; nodelet = filteredHtml.getNextSibling(nodelet).cast()) { prevSum = sum; sum += nodelet.getLength(); if (sum >= offset) { output.setNode(nodelet); output.setOffset(offset - prevSum); return; } } output.setNode(null); }
/** * Deals with a dom mutation event, deciding if it is damaging or not. * * WARNING: This method should NOT be called for mutation events that are a * result of programmatic changes - only for changes that the browser did by * itself and we need to investigate. Doing otherwise will result in * programmatic changes being reverted! * * @param event a dom mutation event */ public void handleMutationEvent(SignalEvent event) { // TODO(user): Do we care about other types of events? if (event.getType().equals("DOMNodeRemoved")) { Node target = event.getTarget(); boolean ignorableWhenEmpty = DomHelper.isTextNode(target) || !NodeManager.hasBackReference(target.<Element>cast()); if (ignorableWhenEmpty && entries.isEmpty()) { // If it's a text node, or a non-backreferenced element, // and we don't already have entries, then we just ignore it as regular typing. Ok. } else { EditorStaticDeps.logger.trace().log("REVERT REMOVAL: " + (DomHelper.isTextNode(target) ? target.<Text>cast().getData() : target.<Element>cast().getTagName())); addEntry(new RemovalEntry(target, target.getParentElement(), target.getNextSibling(), ignorableWhenEmpty)); } } }
private boolean isPartOfThisState(Point<Node> point) { checkRangeIsValid(); Text node = point.isInTextNode() ? point.getContainer().<Text>cast() : null; if (node == null) { // If we're not in a text node - i.e. we just started typing // either in an empty element, or between elements. if (htmlRange.getNodeAfter() == point.getNodeAfter() && htmlRange.getContainer() == point.getContainer()) { return true; } else if (point.getNodeAfter() == null) { return false; } else { return partOfMutatingRange(point.getNodeAfter()); } } // The first check is redundant but speeds up the general case return node == lastTextNode || partOfMutatingRange(node); }
void checkForWebkitEndOfLinkHack(SignalEvent signal) { // If it's inserting text if (DomHelper.isTextNode(signal.getTarget()) && (signal.getType().equals(JsEvents.DOM_CHARACTER_DATA_MODIFIED) || signal.getType().equals(JsEvents.DOM_NODE_INSERTED))) { Text textNode = signal.getTarget().cast(); if (textNode.getLength() > 0) { Node e = textNode.getPreviousSibling(); if (e != null && !DomHelper.isTextNode(e) && e.<Element>cast().getTagName().toLowerCase().equals("a")) { FocusedPointRange<Node> selection = editorInteractor.getHtmlSelection(); if (selection.isCollapsed() && selection.getFocus().getTextOffset() == 0) { editorInteractor.noteWebkitEndOfLinkHackOccurred(textNode); } } } } }
/** * Converts the given point to a parent-nodeAfter point, splitting a * text node if necessary. * * @param point * @return a point at the same location, between node boundaries */ public static Point.El<Node> forceElementPoint(Point<Node> point) { Point.El<Node> elementPoint = point.asElementPoint(); if (elementPoint != null) { return elementPoint; } Element parent; Node nodeAfter; Text text = point.getContainer().cast(); parent = text.getParentElement(); int offset = point.getTextOffset(); if (offset == 0) { nodeAfter = text; } else if (offset == text.getLength()) { nodeAfter = text.getNextSibling(); } else { nodeAfter = text.splitText(offset); } return Point.inElement(parent, nodeAfter); }
public void testImplDataSumsTextNodes() throws HtmlMissing { ContentDocument dom = TestEditors.createTestDocument(); c = dom.debugGetRawDocument(); ContentElement root = c.getDocumentElement(); ContentTextNode t1 = c.createTextNode("hello", root, null); Text txt = t1.getImplNodelet(); assertEquals("hello", t1.getImplData()); Text txt3 = Document.get().createTextNode(" there"); txt.getParentNode().insertAfter(txt3, txt); assertEquals("hello there", t1.getImplData()); Text txt2 = txt.splitText(2); assertEquals("hello there", t1.getImplData()); assertSame(txt3, txt.getNextSibling().getNextSibling()); assertTrue(t1.owns(txt) && t1.owns(txt2) && t1.owns(txt3)); ContentTextNode t2 = c.createTextNode("before", root, t1); assertEquals("before", t2.getImplData()); Text t2_2 = t2.getImplNodelet().splitText(3); assertEquals("before", t2.getImplData()); assertTrue(t2.owns(t2_2)); }
public void testDeleteWithRangeSelectedDeletesRange() { FakeEditorEvent fakeEvent = FakeEditorEvent.create(KeySignalType.DELETE, KeyCodes.KEY_LEFT); //Event event = Document.get().createKeyPressEvent( // false, false, false, false, KeyCodes.KEY_BACKSPACE, 0).cast(); Text input = Document.get().createTextNode("ABCDE"); ContentNode node = new ContentTextNode(input, null); final Point<ContentNode> start = Point.inText(node, 1); final Point<ContentNode> end = Point.inText(node, 4); FakeEditorInteractor interactor = setupFakeEditorInteractor( new FocusedContentRange(start, end)); EditorEventsSubHandler subHandler = new FakeEditorEventsSubHandler(); EditorEventHandler handler = createEditorEventHandler(interactor, subHandler); interactor.call(FakeEditorInteractor.DELETE_RANGE).nOf(1).withArgs( start, end, false).returns(start); handler.handleEvent(fakeEvent); interactor.checkExpectations(); }
private void deleteText(int amount) { Point<Node> sel = getSelectionStart(); Text txt = sel == null ? null : sel.getContainer().<Text>cast(); int startIndex = sel.getTextOffset(), len; while (amount > 0) { if (txt == null || !DomHelper.isTextNode(txt)) { throw new RuntimeException("Action ran off end of text node"); } String data = txt.getData(); int remainingInNode = data.length() - startIndex; if (remainingInNode >= amount) { len = amount; } else { len = remainingInNode; } txt.setData(data.substring(0, startIndex) + data.substring(startIndex + len)); amount -= len; startIndex = 0; txt = htmlView.getNextSibling(txt).cast(); } moveCaret(0); }
public void testGenerateOutputMultipleContentNodes() { Element container = Document.get().createDivElement(); mBody.appendChild(container); Element content1 = Document.get().createPElement(); content1.appendChild(Document.get().createTextNode("Some text content 1.")); container.appendChild(content1); Element content2 = Document.get().createPElement(); content2.appendChild(Document.get().createTextNode("Some text content 2.")); container.appendChild(content2); WebTextBuilder builder = new WebTextBuilder(); builder.textNode(Text.as(content1.getChild(0)), 0); builder.textNode(Text.as(content2.getChild(0)), 0); WebText text = builder.build(0); String got = text.generateOutput(false); String want = "<div><p>Some text content 1.</p><p>Some text content 2.</p></div>"; assertEquals(want, TestUtil.removeAllDirAttributes(got)); }
public void testGenerateOutputSingleContentNode() { Element container = Document.get().createDivElement(); mBody.appendChild(container); Element content1 = Document.get().createPElement(); content1.appendChild(Document.get().createTextNode("Some text content 1.")); container.appendChild(content1); Element content2 = Document.get().createPElement(); content2.appendChild(Document.get().createTextNode("Some non-content.")); container.appendChild(content2); WebTextBuilder builder = new WebTextBuilder(); builder.textNode(Text.as(content1.getChild(0)), 0); WebText text = builder.build(0); String got = text.generateOutput(false); String want = "<p>Some text content 1.</p>"; assertEquals(want, TestUtil.removeAllDirAttributes(got)); }
private void resetInner() { this.anchor.getElement().removeAllChildren(); if (this.iconType != null) { Icon icon = new Icon(); icon.setType(this.iconType); this.anchor.getElement().appendChild(icon.getElement()); } if (this.label != null) { Text textElem = Document.get().createTextNode(this.label); this.anchor.getElement().appendChild(textElem); } Text spaceElem = Document.get().createTextNode(" "); this.anchor.getElement().appendChild(spaceElem); this.anchor.getElement().appendChild(this.caret); }
private void resetInner() { if (this.elementExists()) { Element element = this.getElement(); element.removeAllChildren(); boolean rendervalue = Boolean.TRUE.equals(this.getValue()); if (this.outputType != RenderType.TEXT) { Icon icon = rendervalue ? this.trueIcon : this.falseIcon; if (icon != null) { element.appendChild(icon.getElement()); } } if (this.outputType != RenderType.ICON) { Text textElem = Document.get().createTextNode(rendervalue ? this.trueLabel : this.falseLabel); element.appendChild(textElem); } } }
@Override public void redraw() { this.getElement().removeAllChildren(); for (Token<?> token : this.tokenList) { if (token.getContent() != null && token.getContent() instanceof CssRendererTokenContent && ((CssRendererTokenContent) token.getContent()).getCssStyle() != null) { SpanElement spanElement = Document.get().createSpanElement(); spanElement.addClassName(((CssRendererTokenContent) token.getContent()).getCssStyle()); spanElement.setInnerText(token.getText()); this.getElement().appendChild(spanElement); } else { Text textElement = Document.get().createTextNode(token.getText()); this.getElement().appendChild(textElement); } } }
void checkForWebkitEndOfLinkHack(SignalEvent signal) { // If it's inserting text if (DomHelper.isTextNode(signal.getTarget()) && (signal.getType().equals(BrowserEvents.DOMCharacterDataModified) || signal.getType().equals(BrowserEvents.DOMNodeInserted))) { Text textNode = signal.getTarget().cast(); if (textNode.getLength() > 0) { Node e = textNode.getPreviousSibling(); if (e != null && !DomHelper.isTextNode(e) && e.<Element>cast().getTagName().toLowerCase().equals("a")) { FocusedPointRange<Node> selection = editorInteractor.getHtmlSelection(); if (selection.isCollapsed() && selection.getFocus().getTextOffset() == 0) { editorInteractor.noteWebkitEndOfLinkHackOccurred(textNode); } } } } }
private void drawFromTemplate(VisualCoords center, String templateId, String text, final String id) { SVGElement target = (SVGElement) svg.getElementById(id); if (target == null) { target = (SVGElement) svg.getElementById(templateId); target = (SVGElement) target.cloneNode(true); target.setId(id); svg.getElementById("markers").appendChild(target); } target.getStyle().setProperty("pointerEvents", "none"); target.getStyle().setVisibility(Visibility.VISIBLE); if(text != null) { SVGTSpanElement tspan = (SVGTSpanElement) target.getElementsByTagName("tspan").getItem(0); Text item = (Text) tspan.getChildNodes().getItem(0); item.setNodeValue(text); } target.setAttribute("transform", "translate(" + center.x + ", " + center.y + ")"); bringToTop(target); }
/** * @see LowLevelEditingConcerns#textNodeletAffected(Text, int, int, TextNodeChangeType) */ void textNodeletAffected(Text nodelet, int affectedAfterOffset, int insertionAmount, TextNodeChangeType changeType) { if (needToRestoreSelection == true) { return; } switch (changeType) { case DATA: if (!QuirksConstants.OK_SELECTION_ACROSS_TEXT_NODE_DATA_CHANGES && matchesSelectionTextNodes(nodelet, affectedAfterOffset)) { needToRestoreSelection = true; } else { maybeUpdateNodeOffsets(nodelet, affectedAfterOffset, nodelet, insertionAmount); } return; case SPLIT: if (matchesSelectionTextNodes(nodelet, affectedAfterOffset)) { if (!QuirksConstants.OK_SELECTION_ACROSS_TEXT_NODE_SPLITS) { needToRestoreSelection = true; } else { maybeUpdateNodeOffsets(nodelet, affectedAfterOffset, nodelet.getNextSibling().<Text>cast(), -affectedAfterOffset); } } return; case REMOVE: if (!QuirksConstants.OK_SELECTION_ACROSS_NODE_REMOVALS && matchesSelectionTextNodes(nodelet)) { needToRestoreSelection = true; } return; case MOVE: case REPLACE_DATA: if (matchesSelectionTextNodes(nodelet)) { needToRestoreSelection = true; } return; } }
private void maybeUpdateNodeOffsets(Text nodelet, int affectedAfterOffset, Text newNodelet, int offsetDifference) { if (nodelet == savedSelectionAnchorTextNodelet && savedSelectionAnchorOffset > affectedAfterOffset) { savedSelectionAnchorOffset += offsetDifference; savedSelectionAnchorTextNodelet = newNodelet; } if (nodelet == savedSelectionFocusTextNodelet && savedSelectionFocusOffset > affectedAfterOffset) { savedSelectionFocusOffset += offsetDifference; savedSelectionFocusTextNodelet = newNodelet; } }
/** * Splits this text node at the given offset. * If the offset is zero, no split occurs, and the current node is returned. * If the offset is equal to or greater than the length of the text node, no split * occurs, and null is returned. * * @see RawDocument#splitText(Object, int) */ ContentTextNode splitText(int offset, boolean affectImpl) { if (offset == 0) { return this; } else if (offset >= getLength()) { return null; } Text nodelet = null; if (affectImpl) { nodelet = implSplitText(offset); } else { nodelet = Document.get().createTextNode(""); } String first = getData().substring(0, offset); String second = getData().substring(offset); ContentTextNode sibling = new ContentTextNode(second, nodelet, getExtendedContext()); setContentData(first); // Always false for affecting the impl, as it's already been done getParentElement().insertBefore(sibling, getNextSibling(), false); return sibling; }
/** * Compacts the multiple impl text nodelets into one * @throws HtmlMissing */ public void normaliseImplThrow() throws HtmlMissing { // TODO(danilatos): Some code in line container depends on the isImplAttached() check, // but sometimes it might not be attached but should, and so should throw an exception. if (!isContentAttached() || !isImplAttached()) { simpleNormaliseImpl(); } Text first = getImplNodelet(); if (first.getLength() == getLength()) { return; } ContentNode next = checkNodeAndNeighbour(this); HtmlView filteredHtml = getFilteredHtmlView(); //String sum = ""; Node nextImpl = (next == null) ? null : next.getImplNodelet(); for (Text nodelet = first; nodelet != nextImpl && nodelet != null; nodelet = filteredHtml.getNextSibling(first).cast()) { //sum += nodelet.getData(); if (nodelet != first) { getExtendedContext().editing().textNodeletAffected( nodelet, -1000, -1000, TextNodeChangeType.REMOVE); nodelet.removeFromParent(); } } getExtendedContext().editing().textNodeletAffected( first, -1000, -1000, TextNodeChangeType.REPLACE_DATA); first.setData(getData()); }
/** * Helper function to concatenate the character data of a group of * adjacent text nodes. * Important: Does not do any consistency checking. It assumes this has * already been done. * * @param fromIncl Start from this node, inclusive * @param toExcl Go until this node, exclusive * @param filteredHtml Html view to use * @return the summed impl data */ public static String sumTextNodes(Text fromIncl, Node toExcl, HtmlView filteredHtml) { // TODO(danilatos): This could potentially be slow if there are many nodelets. In // practice this shouldn't be an issue, as we should be normalising them when // they get too many. String data = ""; // TODO(danilatos): Some assumptions about validity here. Could they fail? for (Text n = fromIncl; n != toExcl && n != null; n = filteredHtml.getNextSibling(n).cast()) { data += n.getData(); } return getNodeValueFromHtmlString(data); }
private static int sumTextNodesLength(Text fromIncl, Node toExcl, HtmlView filteredHtml) { int length = 0; for (Text n = fromIncl; n != toExcl && n != null; n = filteredHtml.getNextSibling(n).cast()) { length += n.getLength(); } return length; }
/** * Check whether the given text nodelet is one of the nodelets owned by this * wrapper. * @param textNodelet * @return true if textNodelet is owned by this wrapper * @throws HtmlMissing */ public boolean owns(Text textNodelet) throws HtmlMissing { ContentNode next = checkNodeAndNeighbour(this); HtmlView filteredHtml = getFilteredHtmlView(); Node nextImpl = (next == null) ? null : next.getImplNodelet(); for (Text nodelet = getImplNodelet(); nodelet != nextImpl; nodelet = filteredHtml.getNextSibling(nodelet).cast()) { if (nodelet == textNodelet) { return true; } } return false; }
@Override public void cleanup() { if (removeAfter) { EditorStaticDeps.logger.trace().log("REVERT CLEANUP: " + (DomHelper.isTextNode(removedNode) ? removedNode.<Text>cast().getData() : removedNode.<Element>cast().getTagName())); removedNode.removeFromParent(); } }
/** Utility for creating tokenizer based on UA. */ private RichTextTokenizer createTokenizer(Element container) { if (UserAgent.isFirefox()) { return new RichTextTokenizerImplFirefox<Node, Element, Text>(new HtmlViewImpl(container)); } else { return new RichTextTokenizerImpl<Node, Element, Text>(new HtmlViewImpl(container)); } }
/** * @return The current value of the text in the html, within our tracked range */ private String calculateNewValue() { HtmlView filteredHtml = filteredHtmlView; Text fromIncl = htmlRange.getStartNode(filteredHtml).cast(); Node toExcl = htmlRange.getPointAfter().getNodeAfter(); return ContentTextNode.sumTextNodes(fromIncl, toExcl, filteredHtml); }