htmlediting_impl.cpp

来自「手机浏览器源码程序,功能强大」· C++ 代码 · 共 1,886 行 · 第 1/5 页

CPP
1,886
字号
            return;
        if (next.node()->isTextNode()) {
            TextImpl *nextTextNode = static_cast<TextImpl *>(next.node());
            if (textNodesAreJoinable(textNode, nextTextNode)) {
                joinTextNodes(textNode, nextTextNode);
                setEndingSelection(Position(nextTextNode, pos.offset()));
             //   LOG(Editing, "joinTextNodesWithSameStyle [2]\n");
            }
        }
    }
}

bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const Position &start, const Position &end)
{
    // Returns whether the range contains only whitespace characters.
    // This is inclusive of the start, but not of the end.
    PositionIterator it(start);
    while (!it.atEnd()) {
        if (!it.current().node()->isTextNode())
            return false;
        const DOMString &text = static_cast<TextImpl *>(it.current().node())->data();
        // EDIT FIXME: signed/unsigned mismatch
        if (text.length() > INT_MAX)
            return false;
        if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()]))
            return false;
        it.next();
        if (it.current() == end)
            break;
    }
    return true;
}

void DeleteSelectionCommandImpl::doApply()
{
    // If selection has not been set to a custom selection when the command was created,
    // use the current ending selection.
    if (!m_hasSelectionToDelete)
        m_selectionToDelete = endingSelection();
        
    if (m_selectionToDelete.state() != Selection::RANGE)
        return;

    deleteCollapsibleWhitespace(m_selectionToDelete);
    Selection selection = endingSelection();

    Position upstreamStart(selection.start().equivalentUpstreamPosition());
    Position downstreamStart(selection.start().equivalentDownstreamPosition());
    Position upstreamEnd(selection.end().equivalentUpstreamPosition());
    Position downstreamEnd(selection.end().equivalentDownstreamPosition());

    if (upstreamStart == downstreamEnd)
        // after collapsing whitespace, selection is empty...no work to do
        return;

    Position endingPosition;
    bool adjustEndingPositionDownstream = false;

    bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd);
 
    bool startCompletelySelected = !onlyWhitespace &&
        (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() &&
        ((downstreamStart.node() != upstreamEnd.node()) ||
         (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset())));

    bool endCompletelySelected = !onlyWhitespace &&
        (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() &&
        ((downstreamStart.node() != upstreamEnd.node()) ||
         (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset())));

    unsigned long startRenderedOffset = downstreamStart.renderedOffset();
    
    bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement();
    bool startAtStartOfBlock = startAtStartOfRootEditableElement || 
        (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock());
    bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock();

    NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement();
    NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement();
    bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode();

    debugPosition("upstreamStart:       ", upstreamStart);
    debugPosition("downstreamStart:     ", downstreamStart);
    debugPosition("upstreamEnd:         ", upstreamEnd);
    debugPosition("downstreamEnd:       ", downstreamEnd);
    LOG(Editing,  "start selected:      %s", startCompletelySelected ? "YES" : "NO");
    LOG(Editing,  "at start block:      %s", startAtStartOfBlock ? "YES" : "NO");
    LOG(Editing,  "at start root block: %s", startAtStartOfRootEditableElement ? "YES" : "NO");
    LOG(Editing,  "at end block:        %s", endAtEndOfBlock ? "YES" : "NO");
    LOG(Editing,  "only whitespace:     %s", onlyWhitespace ? "YES" : "NO");

    // Determine where to put the caret after the deletion
    if (startAtStartOfBlock) {
       // LOG(Editing,  "ending position case 1");
        endingPosition = Position(startBlock, 0);
        adjustEndingPositionDownstream = true;
    }
    else if (!startCompletelySelected) {
       // LOG(Editing,  "ending position case 2");
        endingPosition = upstreamStart;
        if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
            adjustEndingPositionDownstream = true;
    }
    else if (upstreamStart != downstreamStart) {
       // LOG(Editing,  "ending position case 3");
        endingPosition = upstreamStart;
        if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1)
            adjustEndingPositionDownstream = true;
    }
   
    //
    // Figure out the whitespace conversions to do
    //
    if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) {
        // convert trailing whitespace
        Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
        if (trailing.notEmpty()) {
            debugPosition("convertTrailingWhitespace: ", trailing);
            Position collapse = trailing.nextCharacterPosition();
            if (collapse != trailing)
                deleteCollapsibleWhitespace(collapse);
            TextImpl *textNode = static_cast<TextImpl *>(trailing.node());
            replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString());
        }
    }
    else if (!startAtStartOfBlock && endAtEndOfBlock) {
        // convert leading whitespace
        Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
        if (leading.notEmpty()) {
            debugPosition("convertLeadingWhitespace:  ", leading);
            TextImpl *textNode = static_cast<TextImpl *>(leading.node());
            replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
        }
    }
    else if (!startAtStartOfBlock && !endAtEndOfBlock) {
        // convert contiguous whitespace
        Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition());
        Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition());
        if (leading.notEmpty() && trailing.notEmpty()) {
            debugPosition("convertLeadingWhitespace [contiguous]:  ", leading);
            TextImpl *textNode = static_cast<TextImpl *>(leading.node());
            replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString());
        }
    }
        
    //
    // Do the delete
    //
    NodeImpl *n = downstreamStart.node()->traverseNextNode();

    // work on start node
    if (startCompletelySelected) {
//        LOG(Editing,  "start node delete case 1");
        removeNodeAndPrune(downstreamStart.node(), startBlock);
    }
    else if (onlyWhitespace) {
        // Selection only contains whitespace. This is really a special-case to 
        // handle significant whitespace that is collapsed at the end of a line,
        // but also handles deleting a space in mid-line.
  //      LOG(Editing,  "start node delete case 2");
        ASSERT(upstreamStart.node()->isTextNode());
        TextImpl *text = static_cast<TextImpl *>(upstreamStart.node());
        int offset = upstreamStart.offset();
        // EDIT FIXME: Signed/unsigned mismatch
        int length = text->length();
        if (length == upstreamStart.offset())
            offset--;
        deleteText(text, offset, 1);
    }
    else if (downstreamStart.node()->isTextNode()) {
    //    LOG(Editing,  "start node delete case 3");
        TextImpl *text = static_cast<TextImpl *>(downstreamStart.node());
        int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length();
        if (endOffset > downstreamStart.offset()) {
            deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset());
        }
    }
    else {
        // we have clipped the end of a non-text element
        // the offset must be 1 here. if it is, do nothing and move on.
      //  LOG(Editing,  "start node delete case 4");
        ASSERT(downstreamStart.offset() == 1);
    }

    if (!onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) {
        // work on intermediate nodes
        while (n != upstreamEnd.node()) {
            NodeImpl *d = n;
            n = n->traverseNextNode();
            if (d->renderer() && d->renderer()->isEditable())
                removeNodeAndPrune(d, startBlock);
        }
        
        // work on end node
        ASSERT(n == upstreamEnd.node());
        if (endCompletelySelected) {
            removeNodeAndPrune(upstreamEnd.node(), startBlock);
        }
        else if (upstreamEnd.node()->isTextNode()) {
            if (upstreamEnd.offset() > 0) {
                TextImpl *text = static_cast<TextImpl *>(upstreamEnd.node());
                deleteText(text, 0, upstreamEnd.offset());
            }
        }
        else {
            // we have clipped the beginning of a non-text element
            // the offset must be 0 here. if it is, do nothing and move on.
            ASSERT(downstreamStart.offset() == 0);
        }
    }

    // Do block merge if start and end of selection are in different blocks
    // and the blocks are siblings. This is a first cut at this rule arrived
    // at by doing a bunch of edits and settling on the behavior that made
    // the most sense. This could change in the future as we get more
    // experience with how this should behave.
    if (startBlock != endBlock && startBlockEndBlockAreSiblings) {
//        LOG(Editing,  "merging content to start block");
        NodeImpl *node = endBlock->firstChild();
        while (node) {
            NodeImpl *moveNode = node;
            node = node->nextSibling();
            removeNode(moveNode);
            appendNode(startBlock, moveNode);
        }
    }

    if (adjustEndingPositionDownstream) {
  //      LOG(Editing,  "adjust ending position downstream");
        endingPosition = endingPosition.equivalentDownstreamPosition();
    }

    debugPosition("ending position:     ", endingPosition);
    setEndingSelection(endingPosition);

   // LOG(Editing, "-----------------------------------------------------\n");
}

//------------------------------------------------------------------------------------------
// DeleteTextCommandImpl

DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count)
    : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count)
{
    ASSERT(m_node);
    ASSERT(m_offset >= 0);
    ASSERT(m_count >= 0);
    
    m_node->ref();
}

DeleteTextCommandImpl::~DeleteTextCommandImpl()
{
    if (m_node)
        m_node->deref();
}

int DeleteTextCommandImpl::commandID() const
{
    return DeleteTextCommandID;
}

void DeleteTextCommandImpl::doApply()
{
    ASSERT(m_node);

    int exceptionCode = 0;
    m_text = m_node->substringData(m_offset, m_count, exceptionCode);
    ASSERT(exceptionCode == 0);
    
    m_node->deleteData(m_offset, m_count, exceptionCode);
    ASSERT(exceptionCode == 0);
}

void DeleteTextCommandImpl::doUnapply()
{
    ASSERT(m_node);
    ASSERT(!m_text.isEmpty());

    int exceptionCode = 0;
    m_node->insertData(m_offset, m_text, exceptionCode);
    ASSERT(exceptionCode == 0);
}

//------------------------------------------------------------------------------------------
// InputNewlineCommandImpl

InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document) 
    : CompositeEditCommandImpl(document)
{
}

InputNewlineCommandImpl::~InputNewlineCommandImpl() 
{
}

int InputNewlineCommandImpl::commandID() const
{
    return InputNewlineCommandID;
}

void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
{
    // Insert the BR after the caret position. In the case the
    // position is a block, do an append. We don't want to insert
    // the BR *after* the block.
    Position upstream(pos.equivalentUpstreamPosition());
    NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
    if (cb == pos.node())
        appendNode(cb, node);
    else
        insertNodeAfter(node, pos.node());
}

void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
{
    // Insert the BR after the caret position. In the case the
    // position is a block, do an append. We don't want to insert
    // the BR *before* the block.
    Position upstream(pos.equivalentUpstreamPosition());
    NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
    if (cb == pos.node())
        appendNode(cb, node);
    else
        insertNodeBefore(node, pos.node());
}

void InputNewlineCommandImpl::doApply()
{
    deleteSelection();
    Selection selection = endingSelection();

    int exceptionCode = 0;
    ElementImpl *breakNode = document()->createHTMLElement("BR", exceptionCode);
    ASSERT(exceptionCode == 0);

    NodeImpl *nodeToInsert = breakNode;
    
    // Handle the case where there is a typing style.
    if (document()->part()->typingStyle()) {
        int exceptionCode = 0;
        ElementImpl *styleElement = createTypingStyleElement();
        styleElement->appendChild(breakNode, exceptionCode);
        ASSERT(exceptionCode == 0);
        nodeToInsert = styleElement;
    }
    
    Position pos(selection.start().equivalentDownstreamPosition());
    bool atStart = pos.offset() <= pos.node()->caretMinOffset();
    bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock();
    
    if (atEndOfBlock) {
     //   LOG(Editing, "input newline case 1");
        // Insert an "extra" BR at the end of the block. This makes the "real" BR we want
        // to insert appear in the rendering without any significant side effects (and no
        // real worries either since you can't arrow past this extra one.
        insertNodeAfterPosition(nodeToInsert, pos);
        exceptionCode = 0;
        ElementImpl *extraBreakNode = document()->createHTMLElement("BR", exceptionCode);
        ASSERT(exceptionCode == 0);
        insertNodeAfter(extraBreakNode, nodeToInsert);
        setEndingSelection(Position(extraBreakNode, 0));
    }
    else if (atStart) {
       // LOG(Editing, "input newline case 2");
        // Insert node, but place the caret into index 0 of the downstream
        // position. This will make the caret appear after the break, and as we know
        // there is content at that location, this is OK.
        insertNodeBeforePosition(nodeToInsert, pos);
        setEndingSelection(Position(pos.node(), 0));
    }
    else {
        // Split a text node
       // LOG(Editing, "input newline case 3");
        ASSERT(pos.node()->isTextNode());
        TextImpl *textNode = static_cast<TextImpl *>(pos.node());
        TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
        deleteText(textNode, 0, selection.start().offset());

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?