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 + -
显示快捷键?