📄 visibleselection.cpp
字号:
/* * Copyright (C) 2004, 2005, 2006 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 "VisibleSelection.h"#include "CharacterNames.h"#include "CString.h"#include "Document.h"#include "Element.h"#include "htmlediting.h"#include "TextIterator.h"#include "VisiblePosition.h"#include "visible_units.h"#include "Range.h"#include <wtf/Assertions.h>#include <stdio.h>namespace WebCore {VisibleSelection::VisibleSelection() : m_affinity(DOWNSTREAM) , m_granularity(CharacterGranularity) , m_selectionType(NoSelection) , m_baseIsFirst(true){}VisibleSelection::VisibleSelection(const Position& pos, EAffinity affinity) : m_base(pos) , m_extent(pos) , m_affinity(affinity) , m_granularity(CharacterGranularity){ validate();}VisibleSelection::VisibleSelection(const Position& base, const Position& extent, EAffinity affinity) : m_base(base) , m_extent(extent) , m_affinity(affinity) , m_granularity(CharacterGranularity){ validate();}VisibleSelection::VisibleSelection(const VisiblePosition& pos) : m_base(pos.deepEquivalent()) , m_extent(pos.deepEquivalent()) , m_affinity(pos.affinity()) , m_granularity(CharacterGranularity){ validate();}VisibleSelection::VisibleSelection(const VisiblePosition& base, const VisiblePosition& extent) : m_base(base.deepEquivalent()) , m_extent(extent.deepEquivalent()) , m_affinity(base.affinity()) , m_granularity(CharacterGranularity){ validate();}VisibleSelection::VisibleSelection(const Range* range, EAffinity affinity) : m_base(range->startPosition()) , m_extent(range->endPosition()) , m_affinity(affinity) , m_granularity(CharacterGranularity){ validate();}VisibleSelection VisibleSelection::selectionFromContentsOfNode(Node* node){ return VisibleSelection(Position(node, 0), Position(node, maxDeepOffset(node)), DOWNSTREAM);}void VisibleSelection::setBase(const Position& position){ m_base = position; validate();}void VisibleSelection::setBase(const VisiblePosition& visiblePosition){ m_base = visiblePosition.deepEquivalent(); validate();}void VisibleSelection::setExtent(const Position& position){ m_extent = position; validate();}void VisibleSelection::setExtent(const VisiblePosition& visiblePosition){ m_extent = visiblePosition.deepEquivalent(); validate();}PassRefPtr<Range> VisibleSelection::firstRange() const{ if (isNone()) return 0; Position start = rangeCompliantEquivalent(m_start); Position end = rangeCompliantEquivalent(m_end); return Range::create(start.node()->document(), start, end);}PassRefPtr<Range> VisibleSelection::toNormalizedRange() const{ if (isNone()) return 0; // Make sure we have an updated layout since this function is called // in the course of running edit commands which modify the DOM. // Failing to call this can result in equivalentXXXPosition calls returning // incorrect results. m_start.node()->document()->updateLayout(); // Check again, because updating layout can clear the selection. if (isNone()) return 0; Position s, e; if (isCaret()) { // If the selection is a caret, move the range start upstream. This helps us match // the conventions of text editors tested, which make style determinations based // on the character before the caret, if any. s = rangeCompliantEquivalent(m_start.upstream()); e = s; } else { // If the selection is a range, select the minimum range that encompasses the selection. // Again, this is to match the conventions of text editors tested, which make style // determinations based on the first character of the selection. // For instance, this operation helps to make sure that the "X" selected below is the // only thing selected. The range should not be allowed to "leak" out to the end of the // previous text node, or to the beginning of the next text node, each of which has a // different style. // // On a treasure map, <b>X</b> marks the spot. // ^ selected // ASSERT(isRange()); s = m_start.downstream(); e = m_end.upstream(); if (Range::compareBoundaryPoints(s.node(), s.offset(), e.node(), e.offset()) > 0) { // Make sure the start is before the end. // The end can wind up before the start if collapsed whitespace is the only thing selected. Position tmp = s; s = e; e = tmp; } s = rangeCompliantEquivalent(s); e = rangeCompliantEquivalent(e); } // VisibleSelections are supposed to always be valid. This constructor will ASSERT // if a valid range could not be created, which is fine for this callsite. return Range::create(s.node()->document(), s, e);}bool VisibleSelection::expandUsingGranularity(TextGranularity granularity){ if (isNone()) return false; m_granularity = granularity; validate(); return true;}static PassRefPtr<Range> makeSearchRange(const Position& pos){ Node* n = pos.node(); if (!n) return 0; Document* d = n->document(); Node* de = d->documentElement(); if (!de) return 0; Node* boundary = n->enclosingBlockFlowElement(); if (!boundary) return 0; RefPtr<Range> searchRange(Range::create(d)); ExceptionCode ec = 0; Position start(rangeCompliantEquivalent(pos)); searchRange->selectNodeContents(boundary, ec); searchRange->setStart(start.node(), start.offset(), ec); ASSERT(!ec); if (ec) return 0; return searchRange.release();}void VisibleSelection::appendTrailingWhitespace(){ RefPtr<Range> searchRange = makeSearchRange(m_end); if (!searchRange) return; CharacterIterator charIt(searchRange.get(), true); for (; charIt.length(); charIt.advance(1)) { UChar c = charIt.characters()[0]; if (!isSpaceOrNewline(c) && c != noBreakSpace) break; m_end = charIt.range()->endPosition(); }}void VisibleSelection::setBaseAndExtentToDeepEquivalents(){ // Move the selection to rendered positions, if possible. bool baseAndExtentEqual = m_base == m_extent; if (m_base.isNotNull()) { m_base = VisiblePosition(m_base, m_affinity).deepEquivalent(); if (baseAndExtentEqual) m_extent = m_base; } if (m_extent.isNotNull() && !baseAndExtentEqual) m_extent = VisiblePosition(m_extent, m_affinity).deepEquivalent(); // Make sure we do not have a dangling base or extent. if (m_base.isNull() && m_extent.isNull()) m_baseIsFirst = true; else if (m_base.isNull()) { m_base = m_extent; m_baseIsFirst = true; } else if (m_extent.isNull()) { m_extent = m_base; m_baseIsFirst = true; } else m_baseIsFirst = comparePositions(m_base, m_extent) <= 0;}void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(){ if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; } else { m_start = m_extent; m_end = m_base; } switch (m_granularity) { case CharacterGranularity: // Don't do any expansion. break; case WordGranularity: { // General case: Select the word the caret is positioned inside of, or at the start of (RightWordIfOnBoundary). // Edge case: If the caret is after the last word in a soft-wrapped line or the last word in // the document, select that last word (LeftWordIfOnBoundary). // Edge case: If the caret is after the last word in a paragraph, select from the the end of the // last word to the line break (also RightWordIfOnBoundary); VisiblePosition start = VisiblePosition(m_start, m_affinity); VisiblePosition originalEnd(m_end, m_affinity); EWordSide side = RightWordIfOnBoundary; if (isEndOfDocument(start) || (isEndOfLine(start) && !isStartOfLine(start) && !isEndOfParagraph(start))) side = LeftWordIfOnBoundary; m_start = startOfWord(start, side).deepEquivalent(); side = RightWordIfOnBoundary; if (isEndOfDocument(originalEnd) || (isEndOfLine(originalEnd) && !isStartOfLine(originalEnd) && !isEndOfParagraph(originalEnd))) side = LeftWordIfOnBoundary; VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); if (isEndOfParagraph(originalEnd)) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); if (Node* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) end = end.next(true); else end = wordEnd; } if (end.isNull()) end = wordEnd; } m_end = end.deepEquivalent(); break; } case SentenceGranularity: { m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; } case LineGranularity: { m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -