summaryrefslogtreecommitdiffstats
path: root/WebCore/editing/SelectionController.cpp
diff options
context:
space:
mode:
authorUpstream <upstream-import@none>1970-01-12 13:46:40 +0000
committerUpstream <upstream-import@none>1970-01-12 13:46:40 +0000
commitd8543bb6618c17b12da906afa77d216f58cf4058 (patch)
treec58dc05ed86825bd0ef8d305d58c8205106b540f /WebCore/editing/SelectionController.cpp
downloadexternal_webkit-d8543bb6618c17b12da906afa77d216f58cf4058.zip
external_webkit-d8543bb6618c17b12da906afa77d216f58cf4058.tar.gz
external_webkit-d8543bb6618c17b12da906afa77d216f58cf4058.tar.bz2
external/webkit r30707
Diffstat (limited to 'WebCore/editing/SelectionController.cpp')
-rw-r--r--WebCore/editing/SelectionController.cpp1117
1 files changed, 1117 insertions, 0 deletions
diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp
new file mode 100644
index 0000000..cd6286a
--- /dev/null
+++ b/WebCore/editing/SelectionController.cpp
@@ -0,0 +1,1117 @@
+/*
+ * Copyright (C) 2004, 2008 Apple 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 "SelectionController.h"
+
+#include "CString.h"
+#include "DeleteSelectionCommand.h"
+#include "Document.h"
+#include "Editor.h"
+#include "Element.h"
+#include "EventHandler.h"
+#include "EventNames.h"
+#include "ExceptionCode.h"
+#include "FocusController.h"
+#include "Frame.h"
+#include "FrameTree.h"
+#include "FrameView.h"
+#include "GraphicsContext.h"
+#include "HTMLInputElement.h"
+#include "HTMLNames.h"
+#include "HitTestRequest.h"
+#include "HitTestResult.h"
+#include "Page.h"
+#include "Range.h"
+#include "RenderTheme.h"
+#include "RenderView.h"
+#include "TextIterator.h"
+#include "TypingCommand.h"
+#include "htmlediting.h"
+#include "visible_units.h"
+#include <stdio.h>
+
+#define EDIT_DEBUG 0
+
+namespace WebCore {
+
+using namespace EventNames;
+using namespace HTMLNames;
+
+const int NoXPosForVerticalArrowNavigation = INT_MIN;
+
+SelectionController::SelectionController(Frame* frame, bool isDragCaretController)
+ : m_needsLayout(true)
+ , m_lastChangeWasHorizontalExtension(false)
+ , m_frame(frame)
+ , m_isDragCaretController(isDragCaretController)
+ , m_isCaretBlinkingSuspended(false)
+ , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation)
+ , m_focused(false)
+{
+}
+
+void SelectionController::moveTo(const VisiblePosition &pos, bool userTriggered)
+{
+ setSelection(Selection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered);
+}
+
+void SelectionController::moveTo(const VisiblePosition &base, const VisiblePosition &extent, bool userTriggered)
+{
+ setSelection(Selection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity()), true, true, userTriggered);
+}
+
+void SelectionController::moveTo(const Position &pos, EAffinity affinity, bool userTriggered)
+{
+ setSelection(Selection(pos, affinity), true, true, userTriggered);
+}
+
+void SelectionController::moveTo(const Range *r, EAffinity affinity, bool userTriggered)
+{
+ setSelection(Selection(startPosition(r), endPosition(r), affinity), true, true, userTriggered);
+}
+
+void SelectionController::moveTo(const Position &base, const Position &extent, EAffinity affinity, bool userTriggered)
+{
+ setSelection(Selection(base, extent, affinity), true, true, userTriggered);
+}
+
+void SelectionController::setSelection(const Selection& s, bool closeTyping, bool clearTypingStyle, bool userTriggered)
+{
+ if (m_isDragCaretController) {
+ invalidateCaretRect();
+ m_sel = s;
+ m_needsLayout = true;
+ invalidateCaretRect();
+ return;
+ }
+ if (!m_frame) {
+ m_sel = s;
+ return;
+ }
+
+ if (s.base().node() && s.base().node()->document() != m_frame->document()) {
+ s.base().node()->document()->frame()->selectionController()->setSelection(s, closeTyping, clearTypingStyle, userTriggered);
+ return;
+ }
+
+ if (closeTyping)
+ TypingCommand::closeTyping(m_frame->editor()->lastEditCommand());
+
+ if (clearTypingStyle) {
+ m_frame->clearTypingStyle();
+ m_frame->editor()->setRemovedAnchor(0);
+ }
+
+ if (m_sel == s)
+ return;
+
+ Selection oldSelection = m_sel;
+
+ m_sel = s;
+
+ m_needsLayout = true;
+
+ if (!s.isNone())
+ m_frame->setFocusedNodeIfNeeded();
+
+ m_frame->selectionLayoutChanged();
+ // Always clear the x position used for vertical arrow navigation.
+ // It will be restored by the vertical arrow navigation code if necessary.
+ m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation;
+ selectFrameElementInParentIfFullySelected();
+ m_frame->notifyRendererOfSelectionChange(userTriggered);
+ m_frame->respondToChangedSelection(oldSelection, closeTyping);
+ if (userTriggered)
+ m_frame->revealCaret(RenderLayer::gAlignToEdgeIfNeeded);
+
+ notifyAccessibilityForSelectionChange();
+}
+
+static bool removingNodeRemovesPosition(Node* node, const Position& position)
+{
+ if (!position.node())
+ return false;
+
+ if (position.node() == node)
+ return true;
+
+ if (!node->isElementNode())
+ return false;
+
+ Element* element = static_cast<Element*>(node);
+ return element->contains(position.node()) || element->contains(position.node()->shadowAncestorNode());
+}
+
+void SelectionController::nodeWillBeRemoved(Node *node)
+{
+ if (isNone())
+ return;
+
+ // There can't be a selection inside a fragment, so if a fragment's node is being removed,
+ // the selection in the document that created the fragment needs no adjustment.
+ if (node && highestAncestor(node)->nodeType() == Node::DOCUMENT_FRAGMENT_NODE)
+ return;
+
+ bool baseRemoved = removingNodeRemovesPosition(node, m_sel.base());
+ bool extentRemoved = removingNodeRemovesPosition(node, m_sel.extent());
+ bool startRemoved = removingNodeRemovesPosition(node, m_sel.start());
+ bool endRemoved = removingNodeRemovesPosition(node, m_sel.end());
+
+ bool clearRenderTreeSelection = false;
+ bool clearDOMTreeSelection = false;
+
+ if (startRemoved || endRemoved) {
+ // FIXME: When endpoints are removed, we should just alter the selection, instead of blowing it away.
+ clearRenderTreeSelection = true;
+ clearDOMTreeSelection = true;
+ } else if (baseRemoved || extentRemoved) {
+ if (m_sel.isBaseFirst()) {
+ m_sel.setBase(m_sel.start());
+ m_sel.setExtent(m_sel.end());
+ } else {
+ m_sel.setBase(m_sel.start());
+ m_sel.setExtent(m_sel.end());
+ }
+ // FIXME: This could be more efficient if we had an isNodeInRange function on Ranges.
+ } else if (Range::compareBoundaryPoints(m_sel.start(), Position(node, 0)) == -1 &&
+ Range::compareBoundaryPoints(m_sel.end(), Position(node, 0)) == 1) {
+ // If we did nothing here, when this node's renderer was destroyed, the rect that it
+ // occupied would be invalidated, but, selection gaps that change as a result of
+ // the removal wouldn't be invalidated.
+ // FIXME: Don't do so much unnecessary invalidation.
+ clearRenderTreeSelection = true;
+ }
+
+ if (clearRenderTreeSelection) {
+ RefPtr<Document> document = m_sel.start().node()->document();
+ document->updateRendering();
+ if (RenderView* view = static_cast<RenderView*>(document->renderer()))
+ view->clearSelection();
+ }
+
+ if (clearDOMTreeSelection)
+ setSelection(Selection(), false, false);
+}
+
+void SelectionController::willBeModified(EAlteration alter, EDirection direction)
+{
+ switch (alter) {
+ case MOVE:
+ m_lastChangeWasHorizontalExtension = false;
+ break;
+ case EXTEND:
+ if (!m_lastChangeWasHorizontalExtension) {
+ m_lastChangeWasHorizontalExtension = true;
+ Position start = m_sel.start();
+ Position end = m_sel.end();
+ switch (direction) {
+ // FIXME: right for bidi?
+ case RIGHT:
+ case FORWARD:
+ m_sel.setBase(start);
+ m_sel.setExtent(end);
+ break;
+ case LEFT:
+ case BACKWARD:
+ m_sel.setBase(end);
+ m_sel.setExtent(start);
+ break;
+ }
+ }
+ break;
+ }
+}
+
+VisiblePosition SelectionController::modifyExtendingRightForward(TextGranularity granularity)
+{
+ VisiblePosition pos(m_sel.extent(), m_sel.affinity());
+ switch (granularity) {
+ case CharacterGranularity:
+ pos = pos.next(true);
+ break;
+ case WordGranularity:
+ pos = nextWordPosition(pos);
+ break;
+ case SentenceGranularity:
+ pos = nextSentencePosition(pos);
+ break;
+ case LineGranularity:
+ pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT));
+ break;
+ case ParagraphGranularity:
+ pos = nextParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT));
+ break;
+ case SentenceBoundary:
+ pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ break;
+ case LineBoundary:
+ pos = endOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ break;
+ case ParagraphBoundary:
+ pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ break;
+ case DocumentBoundary:
+ pos = VisiblePosition(m_sel.end(), m_sel.affinity());
+ if (isEditablePosition(pos.deepEquivalent()))
+ pos = endOfEditableContent(pos);
+ else
+ pos = endOfDocument(pos);
+ break;
+ }
+
+ return pos;
+}
+
+VisiblePosition SelectionController::modifyMovingRightForward(TextGranularity granularity)
+{
+ VisiblePosition pos;
+ // FIXME: Stay in editable content for the less common granularities.
+ switch (granularity) {
+ case CharacterGranularity:
+ if (isRange())
+ pos = VisiblePosition(m_sel.end(), m_sel.affinity());
+ else
+ pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).next(true);
+ break;
+ case WordGranularity:
+ pos = nextWordPosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
+ break;
+ case SentenceGranularity:
+ pos = nextSentencePosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
+ break;
+ case LineGranularity: {
+ // down-arrowing from a range selection that ends at the start of a line needs
+ // to leave the selection at that line start (no need to call nextLinePosition!)
+ pos = VisiblePosition(m_sel.end(), m_sel.affinity());
+ if (!isRange() || !isStartOfLine(pos))
+ pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(START));
+ break;
+ }
+ case ParagraphGranularity:
+ pos = nextParagraphPosition(VisiblePosition(m_sel.end(), m_sel.affinity()), xPosForVerticalArrowNavigation(START));
+ break;
+ case SentenceBoundary:
+ pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ break;
+ case LineBoundary:
+ pos = endOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ break;
+ case ParagraphBoundary:
+ pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ break;
+ case DocumentBoundary:
+ pos = VisiblePosition(m_sel.end(), m_sel.affinity());
+ if (isEditablePosition(pos.deepEquivalent()))
+ pos = endOfEditableContent(pos);
+ else
+ pos = endOfDocument(pos);
+ break;
+
+ }
+ return pos;
+}
+
+VisiblePosition SelectionController::modifyExtendingLeftBackward(TextGranularity granularity)
+{
+ VisiblePosition pos(m_sel.extent(), m_sel.affinity());
+
+ // Extending a selection backward by word or character from just after a table selects
+ // the table. This "makes sense" from the user perspective, esp. when deleting.
+ // It was done here instead of in VisiblePosition because we want VPs to iterate
+ // over everything.
+ switch (granularity) {
+ case CharacterGranularity:
+ pos = pos.previous(true);
+ break;
+ case WordGranularity:
+ pos = previousWordPosition(pos);
+ break;
+ case SentenceGranularity:
+ pos = previousSentencePosition(pos);
+ break;
+ case LineGranularity:
+ pos = previousLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT));
+ break;
+ case ParagraphGranularity:
+ pos = previousParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT));
+ break;
+ case SentenceBoundary:
+ pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ break;
+ case LineBoundary:
+ pos = startOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ break;
+ case ParagraphBoundary:
+ pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ break;
+ case DocumentBoundary:
+ pos = VisiblePosition(m_sel.start(), m_sel.affinity());
+ if (isEditablePosition(pos.deepEquivalent()))
+ pos = startOfEditableContent(pos);
+ else
+ pos = startOfDocument(pos);
+ break;
+ }
+ return pos;
+}
+
+VisiblePosition SelectionController::modifyMovingLeftBackward(TextGranularity granularity)
+{
+ VisiblePosition pos;
+ switch (granularity) {
+ case CharacterGranularity:
+ if (isRange())
+ pos = VisiblePosition(m_sel.start(), m_sel.affinity());
+ else
+ pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).previous(true);
+ break;
+ case WordGranularity:
+ pos = previousWordPosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
+ break;
+ case SentenceGranularity:
+ pos = previousSentencePosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
+ break;
+ case LineGranularity:
+ pos = previousLinePosition(VisiblePosition(m_sel.start(), m_sel.affinity()), xPosForVerticalArrowNavigation(START));
+ break;
+ case ParagraphGranularity:
+ pos = previousParagraphPosition(VisiblePosition(m_sel.start(), m_sel.affinity()), xPosForVerticalArrowNavigation(START));
+ break;
+ case SentenceBoundary:
+ pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ break;
+ case LineBoundary:
+ pos = startOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ break;
+ case ParagraphBoundary:
+ pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ break;
+ case DocumentBoundary:
+ pos = VisiblePosition(m_sel.start(), m_sel.affinity());
+ if (isEditablePosition(pos.deepEquivalent()))
+ pos = startOfEditableContent(pos);
+ else
+ pos = startOfDocument(pos);
+ break;
+ }
+ return pos;
+}
+
+bool SelectionController::modify(EAlteration alter, EDirection dir, TextGranularity granularity, bool userTriggered)
+{
+ if (userTriggered) {
+ SelectionController trialSelectionController;
+ trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension);
+ trialSelectionController.setSelection(m_sel);
+ trialSelectionController.modify(alter, dir, granularity, false);
+
+ bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
+ if (!change)
+ return false;
+ }
+
+ if (m_frame)
+ m_frame->setSelectionGranularity(granularity);
+
+ willBeModified(alter, dir);
+
+ VisiblePosition pos;
+ switch (dir) {
+ // EDIT FIXME: These need to handle bidi
+ case RIGHT:
+ case FORWARD:
+ if (alter == EXTEND)
+ pos = modifyExtendingRightForward(granularity);
+ else
+ pos = modifyMovingRightForward(granularity);
+ break;
+ case LEFT:
+ case BACKWARD:
+ if (alter == EXTEND)
+ pos = modifyExtendingLeftBackward(granularity);
+ else
+ pos = modifyMovingLeftBackward(granularity);
+ break;
+ }
+
+ if (pos.isNull())
+ return false;
+
+ // Some of the above operations set an xPosForVerticalArrowNavigation.
+ // Setting a selection will clear it, so save it to possibly restore later.
+ // Note: the START position type is arbitrary because it is unused, it would be
+ // the requested position type if there were no xPosForVerticalArrowNavigation set.
+ int x = xPosForVerticalArrowNavigation(START);
+
+ switch (alter) {
+ case MOVE:
+ moveTo(pos, userTriggered);
+ break;
+ case EXTEND:
+ setExtent(pos, userTriggered);
+ break;
+ }
+
+ if (granularity == LineGranularity || granularity == ParagraphGranularity)
+ m_xPosForVerticalArrowNavigation = x;
+
+ if (userTriggered) {
+ // User modified selection change also sets the granularity back to character.
+ // NOTE: The one exception is that we need to keep word granularity to
+ // preserve smart delete behavior when extending by word (e.g. double-click),
+ // then shift-option-right arrow, then delete needs to smart delete, per TextEdit.
+ if (!(alter == EXTEND && granularity == WordGranularity && m_frame->selectionGranularity() == WordGranularity))
+ m_frame->setSelectionGranularity(CharacterGranularity);
+ }
+
+ setNeedsLayout();
+
+ return true;
+}
+
+// FIXME: Maybe baseline would be better?
+static bool caretY(const VisiblePosition &c, int &y)
+{
+ Position p = c.deepEquivalent();
+ Node *n = p.node();
+ if (!n)
+ return false;
+ RenderObject *r = p.node()->renderer();
+ if (!r)
+ return false;
+ IntRect rect = r->caretRect(p.offset());
+ if (rect.isEmpty())
+ return false;
+ y = rect.y() + rect.height() / 2;
+ return true;
+}
+
+bool SelectionController::modify(EAlteration alter, int verticalDistance, bool userTriggered)
+{
+ if (verticalDistance == 0)
+ return false;
+
+ if (userTriggered) {
+ SelectionController trialSelectionController;
+ trialSelectionController.setSelection(m_sel);
+ trialSelectionController.modify(alter, verticalDistance, false);
+
+ bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
+ if (!change)
+ return false;
+ }
+
+ bool up = verticalDistance < 0;
+ if (up)
+ verticalDistance = -verticalDistance;
+
+ willBeModified(alter, up ? BACKWARD : FORWARD);
+
+ VisiblePosition pos;
+ int xPos = 0;
+ switch (alter) {
+ case MOVE:
+ pos = VisiblePosition(up ? m_sel.start() : m_sel.end(), m_sel.affinity());
+ xPos = xPosForVerticalArrowNavigation(up ? START : END);
+ m_sel.setAffinity(up ? UPSTREAM : DOWNSTREAM);
+ break;
+ case EXTEND:
+ pos = VisiblePosition(m_sel.extent(), m_sel.affinity());
+ xPos = xPosForVerticalArrowNavigation(EXTENT);
+ m_sel.setAffinity(DOWNSTREAM);
+ break;
+ }
+
+ int startY;
+ if (!caretY(pos, startY))
+ return false;
+ if (up)
+ startY = -startY;
+ int lastY = startY;
+
+ VisiblePosition result;
+ VisiblePosition next;
+ for (VisiblePosition p = pos; ; p = next) {
+ next = (up ? previousLinePosition : nextLinePosition)(p, xPos);
+ if (next.isNull() || next == p)
+ break;
+ int nextY;
+ if (!caretY(next, nextY))
+ break;
+ if (up)
+ nextY = -nextY;
+ if (nextY - startY > verticalDistance)
+ break;
+ if (nextY >= lastY) {
+ lastY = nextY;
+ result = next;
+ }
+ }
+
+ if (result.isNull())
+ return false;
+
+ switch (alter) {
+ case MOVE:
+ moveTo(result, userTriggered);
+ break;
+ case EXTEND:
+ setExtent(result, userTriggered);
+ break;
+ }
+
+ if (userTriggered)
+ m_frame->setSelectionGranularity(CharacterGranularity);
+
+ return true;
+}
+
+bool SelectionController::expandUsingGranularity(TextGranularity granularity)
+{
+ if (isNone())
+ return false;
+
+ m_sel.expandUsingGranularity(granularity);
+ m_needsLayout = true;
+ return true;
+}
+
+int SelectionController::xPosForVerticalArrowNavigation(EPositionType type)
+{
+ int x = 0;
+
+ if (isNone())
+ return x;
+
+ Position pos;
+ switch (type) {
+ case START:
+ pos = m_sel.start();
+ break;
+ case END:
+ pos = m_sel.end();
+ break;
+ case BASE:
+ pos = m_sel.base();
+ break;
+ case EXTENT:
+ pos = m_sel.extent();
+ break;
+ }
+
+ Frame *frame = pos.node()->document()->frame();
+ if (!frame)
+ return x;
+
+ if (m_xPosForVerticalArrowNavigation == NoXPosForVerticalArrowNavigation) {
+ pos = VisiblePosition(pos, m_sel.affinity()).deepEquivalent();
+ // VisiblePosition creation can fail here if a node containing the selection becomes visibility:hidden
+ // after the selection is created and before this function is called.
+ x = pos.isNotNull() ? pos.node()->renderer()->caretRect(pos.offset(), m_sel.affinity()).x() : 0;
+ m_xPosForVerticalArrowNavigation = x;
+ }
+ else
+ x = m_xPosForVerticalArrowNavigation;
+
+ return x;
+}
+
+void SelectionController::clear()
+{
+ setSelection(Selection());
+}
+
+void SelectionController::setBase(const VisiblePosition &pos, bool userTriggered)
+{
+ setSelection(Selection(pos.deepEquivalent(), m_sel.extent(), pos.affinity()), true, true, userTriggered);
+}
+
+void SelectionController::setExtent(const VisiblePosition &pos, bool userTriggered)
+{
+ setSelection(Selection(m_sel.base(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered);
+}
+
+void SelectionController::setBase(const Position &pos, EAffinity affinity, bool userTriggered)
+{
+ setSelection(Selection(pos, m_sel.extent(), affinity), true, true, userTriggered);
+}
+
+void SelectionController::setExtent(const Position &pos, EAffinity affinity, bool userTriggered)
+{
+ setSelection(Selection(m_sel.base(), pos, affinity), true, true, userTriggered);
+}
+
+void SelectionController::setNeedsLayout(bool flag)
+{
+ m_needsLayout = flag;
+}
+
+void SelectionController::layout()
+{
+ if (isNone() || !m_sel.start().node()->inDocument() || !m_sel.end().node()->inDocument()) {
+ m_caretRect = IntRect();
+ m_caretPositionOnLayout = IntPoint();
+ return;
+ }
+
+ m_sel.start().node()->document()->updateRendering();
+
+ m_caretRect = IntRect();
+ m_caretPositionOnLayout = IntPoint();
+
+ if (isCaret()) {
+ Position pos = m_sel.start();
+ pos = VisiblePosition(m_sel.start(), m_sel.affinity()).deepEquivalent();
+ if (pos.isNotNull()) {
+ ASSERT(pos.node()->renderer());
+ m_caretRect = pos.node()->renderer()->caretRect(pos.offset(), m_sel.affinity());
+
+ int x, y;
+ pos.node()->renderer()->absolutePositionForContent(x, y);
+ m_caretPositionOnLayout = IntPoint(x, y);
+ }
+ }
+
+ m_needsLayout = false;
+}
+
+IntRect SelectionController::caretRect() const
+{
+ if (m_needsLayout)
+ const_cast<SelectionController *>(this)->layout();
+
+ IntRect caret = m_caretRect;
+
+ if (m_sel.start().node() && m_sel.start().node()->renderer()) {
+ int x, y;
+ m_sel.start().node()->renderer()->absolutePositionForContent(x, y);
+ caret.move(IntPoint(x, y) - m_caretPositionOnLayout);
+ }
+
+ return caret;
+}
+
+IntRect SelectionController::caretRepaintRect() const
+{
+ return caretRect();
+}
+
+bool SelectionController::recomputeCaretRect()
+{
+ if (!m_frame || !m_frame->document())
+ return false;
+
+ FrameView* v = m_frame->document()->view();
+ if (!v)
+ return false;
+
+ if (!m_needsLayout)
+ return false;
+
+ IntRect oldRect = m_caretRect;
+ m_needsLayout = true;
+ IntRect newRect = caretRect();
+ if (oldRect == newRect)
+ return false;
+
+ v->updateContents(oldRect, false);
+ v->updateContents(newRect, false);
+ return true;
+}
+
+void SelectionController::invalidateCaretRect()
+{
+ if (!isCaret())
+ return;
+
+ FrameView* v = m_sel.start().node()->document()->view();
+ if (!v)
+ return;
+
+ bool caretRectChanged = recomputeCaretRect();
+
+ // EDIT FIXME: This is an unfortunate hack.
+ // Basically, we can't trust this layout position since we
+ // can't guarantee that the check to see if we are in unrendered
+ // content will work at this point. We may have to wait for
+ // a layout and re-render of the document to happen. So, resetting this
+ // flag will cause another caret layout to happen the first time
+ // that we try to paint the caret after this call. That one will work since
+ // it happens after the document has accounted for any editing
+ // changes which may have been done.
+ // And, we need to leave this layout here so the caret moves right
+ // away after clicking.
+ m_needsLayout = true;
+
+ if (!caretRectChanged)
+ v->updateContents(caretRepaintRect(), false);
+}
+
+void SelectionController::paintCaret(GraphicsContext *p, const IntRect &rect)
+{
+ if (! m_sel.isCaret())
+ return;
+
+ if (m_needsLayout)
+ layout();
+
+ IntRect caret = intersection(caretRect(), rect);
+ if (!caret.isEmpty()) {
+ Color caretColor = Color::black;
+ Element* element = rootEditableElement();
+ if (element && element->renderer())
+ caretColor = element->renderer()->style()->color();
+
+ p->fillRect(caret, caretColor);
+ }
+}
+
+void SelectionController::debugRenderer(RenderObject *r, bool selected) const
+{
+ if (r->node()->isElementNode()) {
+ Element *element = static_cast<Element *>(r->node());
+ fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->localName().string().utf8().data());
+ }
+ else if (r->isText()) {
+ RenderText* textRenderer = static_cast<RenderText*>(r);
+ if (textRenderer->textLength() == 0 || !textRenderer->firstTextBox()) {
+ fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " ");
+ return;
+ }
+
+ static const int max = 36;
+ String text = textRenderer->text();
+ int textLength = text.length();
+ if (selected) {
+ int offset = 0;
+ if (r->node() == m_sel.start().node())
+ offset = m_sel.start().offset();
+ else if (r->node() == m_sel.end().node())
+ offset = m_sel.end().offset();
+
+ int pos;
+ InlineTextBox *box = textRenderer->findNextInlineTextBox(offset, pos);
+ text = text.substring(box->m_start, box->m_len);
+
+ String show;
+ int mid = max / 2;
+ int caret = 0;
+
+ // text is shorter than max
+ if (textLength < max) {
+ show = text;
+ caret = pos;
+ }
+
+ // too few characters to left
+ else if (pos - mid < 0) {
+ show = text.left(max - 3) + "...";
+ caret = pos;
+ }
+
+ // enough characters on each side
+ else if (pos - mid >= 0 && pos + mid <= textLength) {
+ show = "..." + text.substring(pos - mid + 3, max - 6) + "...";
+ caret = mid;
+ }
+
+ // too few characters on right
+ else {
+ show = "..." + text.right(max - 3);
+ caret = pos - (textLength - show.length());
+ }
+
+ show.replace('\n', ' ');
+ show.replace('\r', ' ');
+ fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.utf8().data(), pos);
+ fprintf(stderr, " ");
+ for (int i = 0; i < caret; i++)
+ fprintf(stderr, " ");
+ fprintf(stderr, "^\n");
+ }
+ else {
+ if ((int)text.length() > max)
+ text = text.left(max - 3) + "...";
+ else
+ text = text.left(max);
+ fprintf(stderr, " #text : \"%s\"\n", text.utf8().data());
+ }
+ }
+}
+
+bool SelectionController::contains(const IntPoint& point)
+{
+ Document* document = m_frame->document();
+
+ // Treat a collapsed selection like no selection.
+ if (!isRange())
+ return false;
+ if (!document->renderer())
+ return false;
+
+ HitTestRequest request(true, true);
+ HitTestResult result(point);
+ document->renderer()->layer()->hitTest(request, result);
+ Node* innerNode = result.innerNode();
+ if (!innerNode || !innerNode->renderer())
+ return false;
+
+ VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint()));
+ if (visiblePos.isNull())
+ return false;
+
+ if (m_sel.visibleStart().isNull() || m_sel.visibleEnd().isNull())
+ return false;
+
+ Position start(m_sel.visibleStart().deepEquivalent());
+ Position end(m_sel.visibleEnd().deepEquivalent());
+ Position p(visiblePos.deepEquivalent());
+
+ return comparePositions(start, p) <= 0 && comparePositions(p, end) <= 0;
+}
+
+// Workaround for the fact that it's hard to delete a frame.
+// Call this after doing user-triggered selections to make it easy to delete the frame you entirely selected.
+// Can't do this implicitly as part of every setSelection call because in some contexts it might not be good
+// for the focus to move to another frame. So instead we call it from places where we are selecting with the
+// mouse or the keyboard after setting the selection.
+void SelectionController::selectFrameElementInParentIfFullySelected()
+{
+ // Find the parent frame; if there is none, then we have nothing to do.
+ Frame* parent = m_frame->tree()->parent();
+ if (!parent)
+ return;
+ Page* page = m_frame->page();
+ if (!page)
+ return;
+
+ // Check if the selection contains the entire frame contents; if not, then there is nothing to do.
+ if (!isRange())
+ return;
+ if (!isStartOfDocument(selection().visibleStart()))
+ return;
+ if (!isEndOfDocument(selection().visibleEnd()))
+ return;
+
+ // Get to the <iframe> or <frame> (or even <object>) element in the parent frame.
+ Document* doc = m_frame->document();
+ if (!doc)
+ return;
+ Element* ownerElement = doc->ownerElement();
+ if (!ownerElement)
+ return;
+ Node* ownerElementParent = ownerElement->parentNode();
+ if (!ownerElementParent)
+ return;
+
+ // This method's purpose is it to make it easier to select iframes (in order to delete them). Don't do anything if the iframe isn't deletable.
+ if (!ownerElementParent->isContentEditable())
+ return;
+
+ // Create compute positions before and after the element.
+ unsigned ownerElementNodeIndex = ownerElement->nodeIndex();
+ VisiblePosition beforeOwnerElement(VisiblePosition(ownerElementParent, ownerElementNodeIndex, SEL_DEFAULT_AFFINITY));
+ VisiblePosition afterOwnerElement(VisiblePosition(ownerElementParent, ownerElementNodeIndex + 1, VP_UPSTREAM_IF_POSSIBLE));
+
+ // Focus on the parent frame, and then select from before this element to after.
+ Selection newSelection(beforeOwnerElement, afterOwnerElement);
+ if (parent->shouldChangeSelection(newSelection)) {
+ page->focusController()->setFocusedFrame(parent);
+ parent->selectionController()->setSelection(newSelection);
+ }
+}
+
+void SelectionController::selectAll()
+{
+ Document* document = m_frame->document();
+ if (!document)
+ return;
+
+ if (document->focusedNode() && document->focusedNode()->canSelectAll()) {
+ document->focusedNode()->selectAll();
+ return;
+ }
+
+ Node* root = isContentEditable() ? highestEditableRoot(m_sel.start()) : document->documentElement();
+ if (!root)
+ return;
+ Selection newSelection(Selection::selectionFromContentsOfNode(root));
+ if (m_frame->shouldChangeSelection(newSelection))
+ setSelection(newSelection);
+ selectFrameElementInParentIfFullySelected();
+ m_frame->notifyRendererOfSelectionChange(true);
+}
+
+bool SelectionController::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping)
+{
+ if (!range)
+ return false;
+
+ ExceptionCode ec = 0;
+ Node* startContainer = range->startContainer(ec);
+ if (ec)
+ return false;
+
+ Node* endContainer = range->endContainer(ec);
+ if (ec)
+ return false;
+
+ ASSERT(startContainer);
+ ASSERT(endContainer);
+ ASSERT(startContainer->document() == endContainer->document());
+
+ m_frame->document()->updateLayoutIgnorePendingStylesheets();
+
+ // Non-collapsed ranges are not allowed to start at the end of a line that is wrapped,
+ // they start at the beginning of the next line instead
+ bool collapsed = range->collapsed(ec);
+ if (ec)
+ return false;
+
+ int startOffset = range->startOffset(ec);
+ if (ec)
+ return false;
+
+ int endOffset = range->endOffset(ec);
+ if (ec)
+ return false;
+
+ // FIXME: Can we provide extentAffinity?
+ VisiblePosition visibleStart(startContainer, startOffset, collapsed ? affinity : DOWNSTREAM);
+ VisiblePosition visibleEnd(endContainer, endOffset, SEL_DEFAULT_AFFINITY);
+ setSelection(Selection(visibleStart, visibleEnd), closeTyping);
+ return true;
+}
+
+bool SelectionController::isInPasswordField() const
+{
+ Node* startNode = start().node();
+ if (!startNode)
+ return false;
+
+ startNode = startNode->shadowAncestorNode();
+ if (!startNode)
+ return false;
+
+ if (!startNode->hasTagName(inputTag))
+ return false;
+
+ return static_cast<HTMLInputElement*>(startNode)->inputType() == HTMLInputElement::PASSWORD;
+}
+
+bool SelectionController::isInsideNode() const
+{
+ Node* startNode = start().node();
+ if (!startNode)
+ return false;
+ return !isTableElement(startNode) && !editingIgnoresContent(startNode);
+}
+
+void SelectionController::focusedOrActiveStateChanged()
+{
+ bool activeAndFocused = isFocusedAndActive();
+
+ // Because RenderObject::selectionBackgroundColor() and
+ // RenderObject::selectionForegroundColor() check if the frame is active,
+ // we have to update places those colors were painted.
+ if (m_frame->view())
+ m_frame->view()->updateContents(enclosingIntRect(m_frame->selectionRect()));
+
+ // Caret appears in the active frame.
+ if (activeAndFocused)
+ m_frame->setSelectionFromNone();
+ m_frame->setCaretVisible(activeAndFocused);
+
+ // Update for caps lock state
+ m_frame->eventHandler()->capsLockStateMayHaveChanged();
+
+ // Because CSSStyleSelector::checkOneSelector() and
+ // RenderTheme::isFocused() check if the frame is active, we have to
+ // update style and theme state that depended on those.
+ if (Node* node = m_frame->document()->focusedNode()) {
+ node->setChanged();
+ if (RenderObject* renderer = node->renderer())
+ if (renderer && renderer->style()->hasAppearance())
+ theme()->stateChanged(renderer, FocusState);
+ }
+
+ // Secure keyboard entry is set by the active frame.
+ if (m_frame->document()->useSecureKeyboardEntryWhenActive())
+ m_frame->setUseSecureKeyboardEntry(activeAndFocused);
+}
+
+void SelectionController::pageActivationChanged()
+{
+ focusedOrActiveStateChanged();
+}
+
+void SelectionController::setFocused(bool flag)
+{
+ if (m_focused == flag)
+ return;
+ m_focused = flag;
+
+ focusedOrActiveStateChanged();
+
+ if (Document* doc = m_frame->document())
+ doc->dispatchWindowEvent(flag ? focusEvent : blurEvent, false, false);
+}
+
+bool SelectionController::isFocusedAndActive() const
+{
+ return m_focused && m_frame->page() && m_frame->page()->focusController()->isActive();
+}
+
+#ifndef NDEBUG
+
+void SelectionController::formatForDebugger(char* buffer, unsigned length) const
+{
+ m_sel.formatForDebugger(buffer, length);
+}
+
+void SelectionController::showTreeForThis() const
+{
+ m_sel.showTreeForThis();
+}
+
+#endif
+
+}
+
+#ifndef NDEBUG
+
+void showTree(const WebCore::SelectionController& sel)
+{
+ sel.showTreeForThis();
+}
+
+void showTree(const WebCore::SelectionController* sel)
+{
+ if (sel)
+ sel->showTreeForThis();
+}
+
+#endif