📄 deleteselectioncommand.cpp
字号:
/* * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */#include "config.h"#include "DeleteSelectionCommand.h"#include "Document.h"#include "DocumentFragment.h"#include "Editor.h"#include "EditorClient.h"#include "Element.h"#include "Frame.h"#include "Logging.h"#include "CSSComputedStyleDeclaration.h"#include "htmlediting.h"#include "HTMLInputElement.h"#include "HTMLNames.h"#include "markup.h"#include "RenderTableCell.h"#include "ReplaceSelectionCommand.h"#include "Text.h"#include "TextIterator.h"#include "visible_units.h"namespace WebCore {using namespace HTMLNames;static bool isTableRow(const Node* node){ return node && node->hasTagName(trTag);}static bool isTableCellEmpty(Node* cell){ ASSERT(isTableCell(cell)); VisiblePosition firstInCell(Position(cell, 0)); VisiblePosition lastInCell(Position(cell, maxDeepOffset(cell))); return firstInCell == lastInCell;}static bool isTableRowEmpty(Node* row){ if (!isTableRow(row)) return false; for (Node* child = row->firstChild(); child; child = child->nextSibling()) if (isTableCell(child) && !isTableCellEmpty(child)) return false; return true;}DeleteSelectionCommand::DeleteSelectionCommand(Document *document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) : CompositeEditCommand(document), m_hasSelectionToDelete(false), m_smartDelete(smartDelete), m_mergeBlocksAfterDelete(mergeBlocksAfterDelete), m_replace(replace), m_expandForSpecialElements(expandForSpecialElements), m_pruneStartBlockIfNecessary(false), m_startBlock(0), m_endBlock(0), m_typingStyle(0), m_deleteIntoBlockquoteStyle(0){}DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) : CompositeEditCommand(selection.start().node()->document()), m_hasSelectionToDelete(true), m_smartDelete(smartDelete), m_mergeBlocksAfterDelete(mergeBlocksAfterDelete), m_replace(replace), m_expandForSpecialElements(expandForSpecialElements), m_pruneStartBlockIfNecessary(false), m_selectionToDelete(selection), m_startBlock(0), m_endBlock(0), m_typingStyle(0), m_deleteIntoBlockquoteStyle(0){}void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end){ Node* startSpecialContainer = 0; Node* endSpecialContainer = 0; start = m_selectionToDelete.start(); end = m_selectionToDelete.end(); // For HRs, we'll get a position at (HR,1) when hitting delete from the beginning of the previous line, or (HR,0) when forward deleting, // but in these cases, we want to delete it, so manually expand the selection if (start.node()->hasTagName(hrTag)) start = Position(start.node(), 0); else if (end.node()->hasTagName(hrTag)) end = Position(end.node(), 1); // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expanion. if (!m_expandForSpecialElements) return; while (1) { startSpecialContainer = 0; endSpecialContainer = 0; Position s = positionBeforeContainingSpecialElement(start, &startSpecialContainer); Position e = positionAfterContainingSpecialElement(end, &endSpecialContainer); if (!startSpecialContainer && !endSpecialContainer) break; if (VisiblePosition(start) != m_selectionToDelete.visibleStart() || VisiblePosition(end) != m_selectionToDelete.visibleEnd()) break; // If we're going to expand to include the startSpecialContainer, it must be fully selected. if (startSpecialContainer && !endSpecialContainer && Range::compareBoundaryPoints(positionAfterNode(startSpecialContainer), end) > -1) break; // If we're going to expand to include the endSpecialContainer, it must be fully selected. if (endSpecialContainer && !startSpecialContainer && Range::compareBoundaryPoints(start, positionBeforeNode(endSpecialContainer)) > -1) break; if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSpecialContainer)) // Don't adjust the end yet, it is the end of a special element that contains the start // special element (which may or may not be fully selected). start = s; else if (endSpecialContainer && endSpecialContainer->isDescendantOf(startSpecialContainer)) // Don't adjust the start yet, it is the start of a special element that contains the end // special element (which may or may not be fully selected). end = e; else { start = s; end = e; } }}void DeleteSelectionCommand::initializePositionData(){ Position start, end; initializeStartEnd(start, end); m_upstreamStart = start.upstream(); m_downstreamStart = start.downstream(); m_upstreamEnd = end.upstream(); m_downstreamEnd = end.downstream(); m_startRoot = editableRootForPosition(start); m_endRoot = editableRootForPosition(end); m_startTableRow = enclosingNodeOfType(start, &isTableRow); m_endTableRow = enclosingNodeOfType(end, &isTableRow); // Don't move content out of a table cell. // If the cell is non-editable, enclosingNodeOfType won't return it by default, so // tell that function that we don't care if it returns non-editable nodes. Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, false); Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, false); // FIXME: This isn't right. A borderless table with two rows and a single column would appear as two paragraphs. if (endCell && endCell != startCell) m_mergeBlocksAfterDelete = false; // Usually the start and the end of the selection to delete are pulled together as a result of the deletion. // Sometimes they aren't (like when no merge is requested), so we must choose one position to hold the caret // and receive the placeholder after deletion. VisiblePosition visibleEnd(m_downstreamEnd); if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd)) m_endingPosition = m_downstreamEnd; else m_endingPosition = m_downstreamStart; // We don't want to merge into a block if it will mean changing the quote level of content after deleting // selections that contain a whole number paragraphs plus a line break, since it is unclear to most users // that such a selection actually ends at the start of the next paragraph. This matches TextEdit behavior // for indented paragraphs. if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end) && isStartOfParagraph(visibleEnd) && isStartOfParagraph(VisiblePosition(start))) { m_mergeBlocksAfterDelete = false; m_pruneStartBlockIfNecessary = true; } // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity()); m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY); if (m_smartDelete) { // skip smart delete if the selection to delete already starts or ends with whitespace Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.affinity()).deepEquivalent(); bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull(); if (!skipSmartDelete) skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull(); // extend selection upstream if there is whitespace there bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity(), true).isNotNull(); if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) { VisiblePosition visiblePos = VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY).previous(); pos = visiblePos.deepEquivalent(); // Expand out one character upstream for smart delete and recalculate // positions based on this change. m_upstreamStart = pos.upstream(); m_downstreamStart = pos.downstream(); m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity()); } // trailing whitespace is only considered for smart delete if there is no leading // whitespace, as in the case where you double-click the first word of a paragraph. if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) { // Expand out one character downstream for smart delete and recalculate // positions based on this change. pos = VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY).next().deepEquivalent(); m_upstreamEnd = pos.upstream(); m_downstreamEnd = pos.downstream(); m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY); } } // We must pass the positions through rangeCompliantEquivalent, since some editing positions // that appear inside their nodes aren't really inside them. [hr, 0] is one example. // FIXME: rangeComplaintEquivalent should eventually be moved into enclosing element getters // like the one below, since editing functions should obviously accept editing positions. // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return a non-editable // node. This was done to match existing behavior, but it seems wrong. m_startBlock = enclosingNodeOfType(rangeCompliantEquivalent(m_downstreamStart), &isBlock, false); m_endBlock = enclosingNodeOfType(rangeCompliantEquivalent(m_upstreamEnd), &isBlock, false);}static void removeEnclosingAnchorStyle(CSSMutableStyleDeclaration* style, const Position& position){ Node* enclosingAnchor = enclosingAnchorElement(position); if (!enclosingAnchor || !enclosingAnchor->parentNode()) return; RefPtr<CSSMutableStyleDeclaration> parentStyle = Position(enclosingAnchor->parentNode(), 0).computedStyle()->copyInheritableProperties(); RefPtr<CSSMutableStyleDeclaration> anchorStyle = Position(enclosingAnchor, 0).computedStyle()->copyInheritableProperties(); parentStyle->diff(anchorStyle.get()); anchorStyle->diff(style);}void DeleteSelectionCommand::saveTypingStyleState(){ // A common case is deleting characters that are all from the same text node. In // that case, the style at the start of the selection before deletion will be the // same as the style at the start of the selection after deletion (since those // two positions will be identical). Therefore there is no need to save the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -