+2004-09-13 Ken Kocienda <kocienda@apple.com>
+
+ Reviewed by Darin
+
+ Various editing improvements, many focused on the improvements made possible by the
+ new CaretPosition class.
+
+ Includes fixes for these bugs:
+
+ <rdar://problem/3748164> REGRESSION (Mail): Arrow navigation in typical mail message can result in stuck caret
+ <rdar://problem/3782062> REGRESSION (Mail): option-delete can delete almost all of a message when it has trouble finding a word
+ <rdar://problem/3790456> triple click does not select entire paragraph (folklore.org)
+
+ * WebCore.pbproj/project.pbxproj: Added CaretPosition class files.
+ * khtml/dom/dom2_range.h:
+ (DOM::offsetInCharacters): Moved this helper here from khtml_text_operations.cpp. This
+ function helps to determine how to interpret the offsets used in DOM Ranges.
+ * khtml/editing/htmlediting_impl.cpp:
+ (khtml::InputNewlineCommandImpl::doApply): Use CaretPosition class to make "end-of-block" determination.
+ Also, fix a caret placement glitch in "case 1" in the code: Place the caret in the node after the inserted
+ BR. This makes it show up in the right place.
+ * khtml/khtml_part.cpp:
+ (KHTMLPart::handleMousePressEventDoubleClick): Don't limit double-click and triple-click handling only to text nodes.
+ (KHTMLPart::handleMousePressEventTripleClick): Ditto.
+ (KHTMLPart::selectAll): Use CaretPosition class to implement improved selectAll.
+ * khtml/misc/khtml_text_operations.cpp:
+ (khtml::SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator): Make a fix to the initial state setting of
+ m_handledChildren. This is true if the offset into the end node is 0, meaning that we do not want to descend
+ into its children at all.
+ * khtml/xml/dom_caretposition.cpp: Added.
+ * khtml/xml/dom_caretposition.h: Added.
+ * khtml/xml/dom_nodeimpl.cpp:
+ (NodeImpl::traverseNextNode): Fix bugs with the stayWithin implementation. We could miss nodes we want to test
+ due to an erroneous check of stayWithin when no such check is needed.
+ (NodeImpl::traverseNextSibling): Ditto.
+ (NodeImpl::traversePreviousNodePostOrder): Ditto.
+ * khtml/xml/dom_position.cpp:
+ (DOM::Position::Position): Remove spurious semi-colon.
+ (DOM::Position::upstream): Move incoming Position to its equivalentDeepPosition. This is part of the transition
+ that will make this code work better with increased use of CaretPosition.
+ (DOM::Position::downstream): Ditto.
+ (DOM::Position::atStartOfContainingEditableBlock): Removed. Dead code; was not being called.
+ (DOM::Position::atStartOfRootEditableElement): Removed. Dead code; was not being called.
+ (DOM::Position::isLastRenderedPositionInEditableBlock): Removed. Replaced with calls to CaretPosition class.
+ (DOM::Position::inLastEditableInRootEditableElement): Removed. Dead code; was not being called.
+ (DOM::Position::inFirstEditableInRootEditableElement): Removed. Was only being called by other code that has been removed.
+ * khtml/xml/dom_position.h:
+ * khtml/xml/dom_selection.cpp:
+ (DOM::Selection::modifyExtendingRightForward): Moved implementation of CHARACTER case to use CaretPosition class instead
+ of Position class helpers.
+ (DOM::Selection::modifyMovingRightForward): Ditto.
+ (DOM::Selection::modifyExtendingLeftBackward): Ditto.
+ (DOM::Selection::modifyMovingLeftBackward): Ditto.
+ (DOM::Selection::validate): Made simplifications in code that used to call a concoction of Position class helpers to
+ do the right thing. Now calls CaretPosition equivalents.
+
2004-09-13 Ken Kocienda <kocienda@apple.com>
Reviewed by me
--- /dev/null
+#include <dom_caretposition.h>
BC06F25006D18A7E004A6FA3,
93ABCE6006E1A42E0085925B,
93B641F406E28C5C0055F610,
+ BEF3442706F1123C000A65D3,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
BC06F24F06D18A7E004A6FA3,
93ABCE5F06E1A42E0085925B,
93B641F306E28C5C0055F610,
+ BEF3442606F1123C000A65D3,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
refType = 4;
sourceTree = "<group>";
};
+ BEF3442406F1123C000A65D3 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ path = dom_caretposition.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ BEF3442506F1123C000A65D3 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ path = dom_caretposition.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ BEF3442606F1123C000A65D3 = {
+ fileRef = BEF3442406F1123C000A65D3;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ BEF3442706F1123C000A65D3 = {
+ fileRef = BEF3442506F1123C000A65D3;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
BEF7EEA005FF8F0D009717EE = {
fileEncoding = 30;
isa = PBXFileReference;
BC3B364505C9D5E200E42902,
BC3B364605C9D5E200E42902,
BC3B364705C9D5E200E42902,
+ BEF3442406F1123C000A65D3,
+ BEF3442506F1123C000A65D3,
F523D2F402DE4476018635CA,
F523D2F502DE4476018635CA,
F523D2F702DE4476018635CA,
bool operator==(const Range &, const Range &);
inline bool operator!=(const Range &a, const Range &b) { return !(a == b); }
+// Used determine how to interpret the offsets used in DOM Ranges.
+inline bool offsetInCharacters(unsigned short type)
+{
+ switch (type) {
+ case Node::ATTRIBUTE_NODE:
+ case Node::DOCUMENT_FRAGMENT_NODE:
+ case Node::DOCUMENT_NODE:
+ case Node::ELEMENT_NODE:
+ case Node::ENTITY_REFERENCE_NODE:
+ return false;
+
+ case Node::CDATA_SECTION_NODE:
+ case Node::COMMENT_NODE:
+ case Node::PROCESSING_INSTRUCTION_NODE:
+ case Node::TEXT_NODE:
+ return true;
+
+ case Node::DOCUMENT_TYPE_NODE:
+ case Node::ENTITY_NODE:
+ case Node::NOTATION_NODE:
+ assert(false); // should never be reached
+ return false;
+ }
+
+ assert(false); // should never be reached
+ return false;
+}
+
} // namespace
#endif
#include "dom_selection.h"
+#include "dom_caretposition.h"
+#include "dom_docimpl.h"
+#include "dom_elementimpl.h"
+#include "dom_node.h"
+#include "dom_nodeimpl.h"
+#include "dom_positioniterator.h"
+#include "dom_string.h"
+#include "dom_textimpl.h"
+#include "dom2_range.h"
#include "htmltags.h"
#include "khtml_part.h"
#include "khtmlview.h"
#include "qevent.h"
#include "qpainter.h"
#include "qrect.h"
-#include "dom/dom2_range.h"
-#include "dom/dom_node.h"
-#include "dom/dom_string.h"
-#include "rendering/render_object.h"
-#include "rendering/render_style.h"
-#include "rendering/render_text.h"
-#include "xml/dom_docimpl.h"
-#include "xml/dom_positioniterator.h"
-#include "xml/dom_elementimpl.h"
-#include "xml/dom_nodeimpl.h"
-#include "xml/dom_textimpl.h"
+#include "render_object.h"
+#include "render_style.h"
+#include "render_text.h"
#if APPLE_CHANGES
#include "KWQAssertions.h"
pos = pos.nextCharacterPosition();
break;
case WORD:
- pos = pos.nextWordPosition();
+ pos = CaretPosition(pos).next().deepEquivalent();
break;
case PARAGRAPH:
// "Next paragraph" not implemented yet. Fall through to LINE.
if (state() == RANGE)
pos = end();
else
- pos = extent().nextCharacterPosition();
+ pos = CaretPosition(extent()).next().deepEquivalent();
break;
case WORD:
pos = extent().nextWordPosition();
Position pos = extent();
switch (granularity) {
case CHARACTER:
- pos = pos.previousCharacterPosition();
+ pos = CaretPosition(pos).previous().deepEquivalent();
break;
case WORD:
pos = pos.previousWordPosition();
if (state() == RANGE)
pos = start();
else
- pos = extent().previousCharacterPosition();
+ pos = CaretPosition(extent()).previous().deepEquivalent();
break;
case WORD:
pos = extent().previousWordPosition();
}
case PARAGRAPH:
if (m_baseIsStart) {
- assignStart(base().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(extent().endParagraphBoundary(IncludeLineBreak).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(base().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(extent().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
} else {
- assignStart(extent().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(base().endParagraphBoundary(IncludeLineBreak).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(extent().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(base().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
}
break;
case DOCUMENT: {
NodeImpl *de = start().node()->getDocument()->documentElement();
- assignStart(Position(de, 0).equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(Position(de, de->childNodeCount()).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(de, 0).deepEquivalent());
+ assignEnd(CaretPosition(de, de->childNodeCount()).deepEquivalent());
break;
}
case PARAGRAPH_BOUNDARY:
if (m_baseIsStart) {
- assignStart(base().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(extent().endParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(base().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(extent().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
} else {
- assignStart(extent().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(base().endParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(extent().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(base().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
}
break;
}
#include "rendering/render_object.h"
#include "rendering/render_style.h"
#include "rendering/render_text.h"
+#include "xml/dom_caretposition.h"
#include "xml/dom_docimpl.h"
#include "xml/dom_elementimpl.h"
#include "xml/dom_positioniterator.h"
#endif
using DOM::AttrImpl;
+using DOM::CaretPosition;
using DOM::CSSComputedStyleDeclarationImpl;
using DOM::CSSPrimitiveValue;
using DOM::CSSPrimitiveValueImpl;
Position pos(selection.start().upstream(StayInBlock));
bool atStart = pos.offset() <= pos.node()->caretMinOffset();
bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
- bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock();
+ bool atEndOfBlock = CaretPosition(pos).isLastInBlock();
if (atEndOfBlock) {
LOG(Editing, "input newline case 1");
bool hasTrailingBR = next && next->id() == ID_BR;
insertNodeAfterPosition(nodeToInsert, pos);
if (hasTrailingBR) {
- setEndingSelection(Position(nodeToInsert, 0));
+ setEndingSelection(Position(next, 0));
}
else {
// Insert an "extra" BR at the end of the block.
#include "dom_selection.h"
+#include "dom_caretposition.h"
+#include "dom_docimpl.h"
+#include "dom_elementimpl.h"
+#include "dom_node.h"
+#include "dom_nodeimpl.h"
+#include "dom_positioniterator.h"
+#include "dom_string.h"
+#include "dom_textimpl.h"
+#include "dom2_range.h"
#include "htmltags.h"
#include "khtml_part.h"
#include "khtmlview.h"
#include "qevent.h"
#include "qpainter.h"
#include "qrect.h"
-#include "dom/dom2_range.h"
-#include "dom/dom_node.h"
-#include "dom/dom_string.h"
-#include "rendering/render_object.h"
-#include "rendering/render_style.h"
-#include "rendering/render_text.h"
-#include "xml/dom_docimpl.h"
-#include "xml/dom_positioniterator.h"
-#include "xml/dom_elementimpl.h"
-#include "xml/dom_nodeimpl.h"
-#include "xml/dom_textimpl.h"
+#include "render_object.h"
+#include "render_style.h"
+#include "render_text.h"
#if APPLE_CHANGES
#include "KWQAssertions.h"
pos = pos.nextCharacterPosition();
break;
case WORD:
- pos = pos.nextWordPosition();
+ pos = CaretPosition(pos).next().deepEquivalent();
break;
case PARAGRAPH:
// "Next paragraph" not implemented yet. Fall through to LINE.
if (state() == RANGE)
pos = end();
else
- pos = extent().nextCharacterPosition();
+ pos = CaretPosition(extent()).next().deepEquivalent();
break;
case WORD:
pos = extent().nextWordPosition();
Position pos = extent();
switch (granularity) {
case CHARACTER:
- pos = pos.previousCharacterPosition();
+ pos = CaretPosition(pos).previous().deepEquivalent();
break;
case WORD:
pos = pos.previousWordPosition();
if (state() == RANGE)
pos = start();
else
- pos = extent().previousCharacterPosition();
+ pos = CaretPosition(extent()).previous().deepEquivalent();
break;
case WORD:
pos = extent().previousWordPosition();
}
case PARAGRAPH:
if (m_baseIsStart) {
- assignStart(base().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(extent().endParagraphBoundary(IncludeLineBreak).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(base().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(extent().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
} else {
- assignStart(extent().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(base().endParagraphBoundary(IncludeLineBreak).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(extent().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(base().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
}
break;
case DOCUMENT: {
NodeImpl *de = start().node()->getDocument()->documentElement();
- assignStart(Position(de, 0).equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(Position(de, de->childNodeCount()).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(de, 0).deepEquivalent());
+ assignEnd(CaretPosition(de, de->childNodeCount()).deepEquivalent());
break;
}
case PARAGRAPH_BOUNDARY:
if (m_baseIsStart) {
- assignStart(base().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(extent().endParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(base().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(extent().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
} else {
- assignStart(extent().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(base().endParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(extent().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(base().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
}
break;
}
--- /dev/null
+/*
+ * Copyright (C) 2004 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 "dom_caretposition.h"
+
+#include "dom2_range.h"
+#include "dom_elementimpl.h"
+#include "dom_nodeimpl.h"
+#include "dom_string.h"
+#include "htmltags.h"
+#include "render_object.h"
+#include "render_text.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#include "KWQLogging.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#define LOG(channel, formatAndArgs...) ((void)0)
+#endif
+
+using khtml::InlineTextBox;
+using khtml::RenderObject;
+using khtml::RenderText;
+using khtml::VISIBLE;
+
+namespace DOM {
+
+CaretPosition::CaretPosition(NodeImpl *node, long offset)
+ : m_node(0)
+{
+ init(Position(node, offset));
+}
+
+CaretPosition::CaretPosition(const Position &pos)
+ : m_node(0)
+{
+ init(pos);
+}
+
+CaretPosition::CaretPosition(const CaretPosition &pos)
+ : m_node(0)
+{
+ setPosition(pos);
+}
+
+CaretPosition::~CaretPosition()
+{
+ if (m_node)
+ m_node->deref();
+}
+
+void CaretPosition::init(const Position &pos)
+{
+ Position result;
+ Position deepPos = deepEquivalent(pos);
+
+ if (isCandidate(deepPos)) {
+ result = deepPos;
+ Position previous = previousCaretPosition(deepPos);
+ if (previous.notEmpty()) {
+ Position next = nextCaretPosition(previous);
+ if (next.notEmpty())
+ result = next;
+ }
+ }
+ else {
+ Position next = nextCaretPosition(deepPos);
+ if (next.notEmpty()) {
+ result = next;
+ }
+ else {
+ Position previous = previousCaretPosition(deepPos);
+ if (previous.notEmpty())
+ result = previous;
+ }
+ }
+
+ setPosition(result);
+}
+
+CaretPosition &CaretPosition::operator=(const CaretPosition &pos)
+{
+ setPosition(pos);
+ return *this;
+}
+
+bool CaretPosition::isLastInBlock() const
+{
+ if (isEmpty())
+ return false;
+
+ CaretPosition n = next();
+ return n.isEmpty() || (n.deepEquivalent().node()->enclosingBlockFlowElement() != node()->enclosingBlockFlowElement());
+}
+
+CaretPosition CaretPosition::next() const
+{
+ CaretPosition result;
+ result.setPosition(nextCaretPosition(*this));
+ return result;
+}
+
+CaretPosition CaretPosition::previous() const
+{
+ CaretPosition result;
+ result.setPosition(previousCaretPosition(*this));
+ return result;
+}
+
+Position CaretPosition::previousCaretPosition(const Position &pos)
+{
+ if (pos.isEmpty() || atStart(pos))
+ return Position();
+
+ Position test = deepEquivalent(pos);
+ bool acceptAnyCaretPosition = !isCandidate(test) || pos.isFirstRenderedPositionOnLine();
+
+ Position current = test;
+ while (!atStart(current)) {
+ current = previousPosition(current);
+ if (isCandidate(current) && (acceptAnyCaretPosition || current.rendersInDifferentPosition(test))) {
+ return current;
+ }
+ }
+
+ return Position();
+}
+
+Position CaretPosition::nextCaretPosition(const Position &pos)
+{
+ if (pos.isEmpty() || atEnd(pos))
+ return Position();
+
+ Position test = deepEquivalent(pos);
+ bool acceptAnyCaretPosition = !isCandidate(test) || pos.isLastRenderedPositionOnLine();
+
+ Position current = test;
+ while (!atEnd(current)) {
+ current = nextPosition(current);
+ if (isCandidate(current) && (acceptAnyCaretPosition || current.rendersInDifferentPosition(test))) {
+ return current;
+ }
+ }
+
+ return Position();
+}
+
+Position CaretPosition::previousPosition(const Position &pos)
+{
+ if (pos.isEmpty())
+ return pos;
+
+ Position result;
+
+ if (pos.offset() <= 0) {
+ NodeImpl *prevNode = pos.node()->previousLeafNode();
+ if (prevNode)
+ result = Position(prevNode, prevNode->maxOffset());
+ }
+ else {
+ result = Position(pos.node(), pos.offset() - 1);
+ }
+
+ return result;
+}
+
+Position CaretPosition::nextPosition(const Position &pos)
+{
+ if (pos.isEmpty())
+ return pos;
+
+ Position result;
+
+ if (pos.offset() >= pos.node()->maxOffset()) {
+ NodeImpl *nextNode = pos.node()->nextLeafNode();
+ if (nextNode)
+ result = Position(nextNode, 0);
+ }
+ else {
+ result = Position(pos.node(), pos.offset() + 1);
+ }
+
+ return result;
+}
+
+bool CaretPosition::atStart(const Position &pos)
+{
+ if (pos.isEmpty())
+ return true;
+
+ return pos.offset() <= 0 && pos.node()->previousLeafNode() == 0;
+}
+
+bool CaretPosition::atEnd(const Position &pos)
+{
+ if (pos.isEmpty())
+ return true;
+
+ return pos.offset() >= pos.node()->maxOffset() && pos.node()->nextLeafNode() == 0;
+}
+
+bool CaretPosition::isCandidate(const Position &pos)
+{
+ if (pos.isEmpty())
+ return false;
+
+ RenderObject *renderer = pos.node()->renderer();
+ if (!renderer)
+ return false;
+
+ if (renderer->style()->visibility() != VISIBLE)
+ return false;
+
+ if (renderer->isBR() && static_cast<RenderText *>(renderer)->firstTextBox()) {
+ return pos.offset() == 0;
+ }
+ else if (renderer->isText()) {
+ RenderText *textRenderer = static_cast<RenderText *>(renderer);
+ for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+ if (pos.offset() >= box->m_start && pos.offset() <= box->m_start + box->m_len) {
+ return true;
+ }
+ else if (pos.offset() < box->m_start) {
+ // The offset we're looking for is before this node
+ // this means the offset must be in content that is
+ // not rendered. Return false.
+ return false;
+ }
+ }
+ }
+ else if (renderer->isReplaced() || (pos.node()->isBlockFlow() && !pos.node()->firstChild() && pos.offset() == 0)) {
+ // return true for replaced elements, for inline flows if they have a line box
+ // and for blocks if they are empty
+ return true;
+ }
+
+ return false;
+}
+
+Position CaretPosition::deepEquivalent(const Position &pos)
+{
+ if (pos.isEmpty())
+ return pos;
+
+ if (isAtomicNode(pos.node())) {
+ // This is part of the strategy to wean the code off Positions with BRs and replaced elements
+ // as the nodes and offsets > 0.
+ if (pos.offset() > 0 && (pos.node()->id() == ID_BR || pos.node()->renderer() && pos.node()->renderer()->isReplaced())) {
+ NodeImpl *next = pos.node()->traverseNextNode();
+ if (next && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement())
+ return deepEquivalent(Position(next, 0));
+ }
+ return pos;
+ }
+
+ NodeImpl *child = 0;
+ Position result = pos;
+ if (pos.offset() >= (long)pos.node()->childNodeCount()) {
+ child = pos.node()->lastChild();
+ result = Position(child, maxOffset(child));
+ ASSERT(child);
+ while (!isAtomicNode(child) && result.node()->hasChildNodes()) {
+ child = result.node()->lastChild();
+ ASSERT(child);
+ result = Position(child, maxOffset(child));
+ }
+ }
+ else {
+ child = pos.node()->childNode(pos.offset());
+ ASSERT(child);
+ result = Position(child, 0);
+ while (!isAtomicNode(child) && result.node()->hasChildNodes()) {
+ child = result.node()->firstChild();
+ ASSERT(child);
+ result = Position(child, 0);
+ }
+ }
+ return result;
+}
+
+Position CaretPosition::rangeCompliantEquivalent(const Position &pos)
+{
+ if (pos.isEmpty())
+ return Position();
+
+ if (isAtomicNode(pos.node()))
+ return Position(pos.node()->parentNode(), pos.offset() > 0 ? pos.node()->nodeIndex() + 1 : pos.node()->nodeIndex());
+ else
+ return Position(pos.node(), kMin(pos.offset(), maxOffset(pos.node())));
+}
+
+long CaretPosition::maxOffset(const NodeImpl *node)
+{
+ return offsetInCharacters(node->nodeType()) ? (long)static_cast<const CharacterDataImpl *>(node)->length() : (long)node->childNodeCount();
+}
+
+bool CaretPosition::isAtomicNode(const NodeImpl *node)
+{
+ return node && (!node->hasChildNodes() || (node->id() == ID_OBJECT && node->renderer() && node->renderer()->isReplaced()));
+}
+
+// CaretPosition objects are immutable.
+// This helper is only used during construction and assignment.
+void CaretPosition::setPosition(const Position &pos)
+{
+ if (m_node)
+ m_node->deref();
+ m_node = pos.node();
+ if (m_node)
+ m_node->ref();
+
+ m_offset = pos.offset();
+}
+
+void CaretPosition::debugPosition(const char *msg) const
+{
+ if (isEmpty())
+ fprintf(stderr, "Position [%s]: empty\n", msg);
+ else
+ fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, getTagName(node()->id()).string().latin1(), node(), offset());
+}
+
+#ifndef NDEBUG
+#define FormatBufferSize 1024
+void CaretPosition::formatForDebugger(char *buffer, unsigned length) const
+{
+ DOMString result;
+ DOMString s;
+
+ if (isEmpty()) {
+ result = "<empty>";
+ }
+ else {
+ char s[FormatBufferSize];
+ result += "offset ";
+ result += QString::number(m_offset);
+ result += " of ";
+ m_node->formatForDebugger(s, FormatBufferSize);
+ result += s;
+ }
+
+ strncpy(buffer, result.string().latin1(), length - 1);
+}
+#undef FormatBufferSize
+#endif
+
+} // namespace DOM
--- /dev/null
+/*
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef __dom_caretposition_h__
+#define __dom_caretposition_h__
+
+#include "dom_position.h"
+
+namespace DOM {
+
+class NodeImpl;
+class Position;
+
+class CaretPosition
+{
+public:
+ CaretPosition() : m_node(0), m_offset(0) {}
+ CaretPosition(NodeImpl *, long offset);
+ explicit CaretPosition(const Position &);
+ CaretPosition(const CaretPosition &);
+ ~CaretPosition();
+
+ CaretPosition &operator=(const CaretPosition &o);
+
+ bool isLastInBlock() const;
+
+ CaretPosition next() const;
+ CaretPosition previous() const;
+
+ bool isEmpty() const { return m_node == 0; }
+ bool notEmpty() const { return m_node != 0; }
+
+ Position position() const { return rangeCompliantEquivalent(*this); }
+ Position deepEquivalent() const { return Position(node(), offset()); }
+
+ friend inline bool operator==(const CaretPosition &a, const CaretPosition &b);
+ friend inline bool operator!=(const CaretPosition &a, const CaretPosition &b);
+
+ void debugPosition(const char *msg="") const;
+
+#ifndef NDEBUG
+ void formatForDebugger(char *buffer, unsigned length) const;
+#endif
+
+private:
+ operator Position() const { return Position(node(), offset()); }
+
+ void init(const Position &);
+ void setPosition(const Position &);
+
+ NodeImpl *node() const { return m_node; }
+ long offset() const { return m_offset; }
+
+ static Position deepEquivalent(const Position &);
+ static Position rangeCompliantEquivalent(const Position &);
+
+ static long maxOffset(const NodeImpl *);
+ static bool isAtomicNode(const NodeImpl *);
+
+ static Position previousCaretPosition(const Position &);
+ static Position nextCaretPosition(const Position &);
+
+ static Position previousPosition(const Position &);
+ static Position nextPosition(const Position &);
+
+ static bool atStart(const Position &);
+ static bool atEnd(const Position &);
+
+ static bool isCandidate(const Position &);
+
+ NodeImpl *m_node;
+ long m_offset;
+};
+
+inline bool operator==(const CaretPosition &a, const CaretPosition &b)
+{
+ return a.node() == b.node() && a.offset() == b.offset();
+}
+
+inline bool operator!=(const CaretPosition &a, const CaretPosition &b)
+{
+ return !(a == b);
+}
+
+} // namespace DOM
+
+#endif // __dom_caretposition_h__
\ No newline at end of file
#include "khtml_text_operations.h"
+#include <dom/dom2_range.h>
#include <misc/htmltags.h>
#include <rendering/render_text.h>
#include <xml/dom_nodeimpl.h>
using DOM::DOMString;
using DOM::Node;
using DOM::NodeImpl;
+using DOM::offsetInCharacters;
using DOM::Range;
namespace khtml {
{
}
-inline bool offsetInCharacters(unsigned short type)
-{
- switch (type) {
- case Node::ATTRIBUTE_NODE:
- case Node::DOCUMENT_FRAGMENT_NODE:
- case Node::DOCUMENT_NODE:
- case Node::ELEMENT_NODE:
- case Node::ENTITY_REFERENCE_NODE:
- return false;
-
- case Node::CDATA_SECTION_NODE:
- case Node::COMMENT_NODE:
- case Node::PROCESSING_INSTRUCTION_NODE:
- case Node::TEXT_NODE:
- return true;
-
- case Node::DOCUMENT_TYPE_NODE:
- case Node::ENTITY_NODE:
- case Node::NOTATION_NODE:
- assert(false); // should never be reached
- return false;
- }
-
- assert(false); // should never be reached
- return false;
-}
-
TextIterator::TextIterator(const Range &r)
{
if (r.isNull()) {
m_node = endNode;
m_offset = endOffset;
m_handledNode = false;
- m_handledChildren = false;
+ m_handledChildren = endOffset == 0;
m_startNode = startNode;
m_startOffset = startOffset;
#include "rendering/render_frames.h"
#include "misc/htmlhashes.h"
#include "misc/loader.h"
+#include "xml/dom_caretposition.h"
#include "xml/dom_selection.h"
#include "xml/dom2_eventsimpl.h"
#include "xml/xml_tokenizer.h"
if (mouse->button() == LeftButton && !innerNode.isNull() && innerNode.handle()->renderer() &&
innerNode.handle()->renderer()->shouldSelect()) {
Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()));
- if (pos.node() && (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE)) {
+ if (pos.notEmpty()) {
selection.moveTo(pos);
selection.expandUsingGranularity(Selection::WORD);
}
if (mouse->button() == LeftButton && !innerNode.isNull() && innerNode.handle()->renderer() &&
innerNode.handle()->renderer()->shouldSelect()) {
Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()));
- if (pos.node() && (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE)) {
+ if (pos.notEmpty()) {
selection.moveTo(pos);
selection.expandUsingGranularity(Selection::PARAGRAPH);
}
{
if (!d->m_doc)
return;
- Selection selection(Position(d->m_doc->documentElement(), 0));
- selection.validate(Selection::DOCUMENT);
+ CaretPosition start(d->m_doc->documentElement(), 0);
+ CaretPosition end(d->m_doc->documentElement(), d->m_doc->documentElement()->childNodeCount());
+ Selection selection(start.deepEquivalent(), end.deepEquivalent());
setSelection(selection);
}
#include "khtml_text_operations.h"
+#include <dom/dom2_range.h>
#include <misc/htmltags.h>
#include <rendering/render_text.h>
#include <xml/dom_nodeimpl.h>
using DOM::DOMString;
using DOM::Node;
using DOM::NodeImpl;
+using DOM::offsetInCharacters;
using DOM::Range;
namespace khtml {
{
}
-inline bool offsetInCharacters(unsigned short type)
-{
- switch (type) {
- case Node::ATTRIBUTE_NODE:
- case Node::DOCUMENT_FRAGMENT_NODE:
- case Node::DOCUMENT_NODE:
- case Node::ELEMENT_NODE:
- case Node::ENTITY_REFERENCE_NODE:
- return false;
-
- case Node::CDATA_SECTION_NODE:
- case Node::COMMENT_NODE:
- case Node::PROCESSING_INSTRUCTION_NODE:
- case Node::TEXT_NODE:
- return true;
-
- case Node::DOCUMENT_TYPE_NODE:
- case Node::ENTITY_NODE:
- case Node::NOTATION_NODE:
- assert(false); // should never be reached
- return false;
- }
-
- assert(false); // should never be reached
- return false;
-}
-
TextIterator::TextIterator(const Range &r)
{
if (r.isNull()) {
m_node = endNode;
m_offset = endOffset;
m_handledNode = false;
- m_handledChildren = false;
+ m_handledChildren = endOffset == 0;
m_startNode = startNode;
m_startOffset = startOffset;
--- /dev/null
+/*
+ * Copyright (C) 2004 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 "dom_caretposition.h"
+
+#include "dom2_range.h"
+#include "dom_elementimpl.h"
+#include "dom_nodeimpl.h"
+#include "dom_string.h"
+#include "htmltags.h"
+#include "render_object.h"
+#include "render_text.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#include "KWQLogging.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#define LOG(channel, formatAndArgs...) ((void)0)
+#endif
+
+using khtml::InlineTextBox;
+using khtml::RenderObject;
+using khtml::RenderText;
+using khtml::VISIBLE;
+
+namespace DOM {
+
+CaretPosition::CaretPosition(NodeImpl *node, long offset)
+ : m_node(0)
+{
+ init(Position(node, offset));
+}
+
+CaretPosition::CaretPosition(const Position &pos)
+ : m_node(0)
+{
+ init(pos);
+}
+
+CaretPosition::CaretPosition(const CaretPosition &pos)
+ : m_node(0)
+{
+ setPosition(pos);
+}
+
+CaretPosition::~CaretPosition()
+{
+ if (m_node)
+ m_node->deref();
+}
+
+void CaretPosition::init(const Position &pos)
+{
+ Position result;
+ Position deepPos = deepEquivalent(pos);
+
+ if (isCandidate(deepPos)) {
+ result = deepPos;
+ Position previous = previousCaretPosition(deepPos);
+ if (previous.notEmpty()) {
+ Position next = nextCaretPosition(previous);
+ if (next.notEmpty())
+ result = next;
+ }
+ }
+ else {
+ Position next = nextCaretPosition(deepPos);
+ if (next.notEmpty()) {
+ result = next;
+ }
+ else {
+ Position previous = previousCaretPosition(deepPos);
+ if (previous.notEmpty())
+ result = previous;
+ }
+ }
+
+ setPosition(result);
+}
+
+CaretPosition &CaretPosition::operator=(const CaretPosition &pos)
+{
+ setPosition(pos);
+ return *this;
+}
+
+bool CaretPosition::isLastInBlock() const
+{
+ if (isEmpty())
+ return false;
+
+ CaretPosition n = next();
+ return n.isEmpty() || (n.deepEquivalent().node()->enclosingBlockFlowElement() != node()->enclosingBlockFlowElement());
+}
+
+CaretPosition CaretPosition::next() const
+{
+ CaretPosition result;
+ result.setPosition(nextCaretPosition(*this));
+ return result;
+}
+
+CaretPosition CaretPosition::previous() const
+{
+ CaretPosition result;
+ result.setPosition(previousCaretPosition(*this));
+ return result;
+}
+
+Position CaretPosition::previousCaretPosition(const Position &pos)
+{
+ if (pos.isEmpty() || atStart(pos))
+ return Position();
+
+ Position test = deepEquivalent(pos);
+ bool acceptAnyCaretPosition = !isCandidate(test) || pos.isFirstRenderedPositionOnLine();
+
+ Position current = test;
+ while (!atStart(current)) {
+ current = previousPosition(current);
+ if (isCandidate(current) && (acceptAnyCaretPosition || current.rendersInDifferentPosition(test))) {
+ return current;
+ }
+ }
+
+ return Position();
+}
+
+Position CaretPosition::nextCaretPosition(const Position &pos)
+{
+ if (pos.isEmpty() || atEnd(pos))
+ return Position();
+
+ Position test = deepEquivalent(pos);
+ bool acceptAnyCaretPosition = !isCandidate(test) || pos.isLastRenderedPositionOnLine();
+
+ Position current = test;
+ while (!atEnd(current)) {
+ current = nextPosition(current);
+ if (isCandidate(current) && (acceptAnyCaretPosition || current.rendersInDifferentPosition(test))) {
+ return current;
+ }
+ }
+
+ return Position();
+}
+
+Position CaretPosition::previousPosition(const Position &pos)
+{
+ if (pos.isEmpty())
+ return pos;
+
+ Position result;
+
+ if (pos.offset() <= 0) {
+ NodeImpl *prevNode = pos.node()->previousLeafNode();
+ if (prevNode)
+ result = Position(prevNode, prevNode->maxOffset());
+ }
+ else {
+ result = Position(pos.node(), pos.offset() - 1);
+ }
+
+ return result;
+}
+
+Position CaretPosition::nextPosition(const Position &pos)
+{
+ if (pos.isEmpty())
+ return pos;
+
+ Position result;
+
+ if (pos.offset() >= pos.node()->maxOffset()) {
+ NodeImpl *nextNode = pos.node()->nextLeafNode();
+ if (nextNode)
+ result = Position(nextNode, 0);
+ }
+ else {
+ result = Position(pos.node(), pos.offset() + 1);
+ }
+
+ return result;
+}
+
+bool CaretPosition::atStart(const Position &pos)
+{
+ if (pos.isEmpty())
+ return true;
+
+ return pos.offset() <= 0 && pos.node()->previousLeafNode() == 0;
+}
+
+bool CaretPosition::atEnd(const Position &pos)
+{
+ if (pos.isEmpty())
+ return true;
+
+ return pos.offset() >= pos.node()->maxOffset() && pos.node()->nextLeafNode() == 0;
+}
+
+bool CaretPosition::isCandidate(const Position &pos)
+{
+ if (pos.isEmpty())
+ return false;
+
+ RenderObject *renderer = pos.node()->renderer();
+ if (!renderer)
+ return false;
+
+ if (renderer->style()->visibility() != VISIBLE)
+ return false;
+
+ if (renderer->isBR() && static_cast<RenderText *>(renderer)->firstTextBox()) {
+ return pos.offset() == 0;
+ }
+ else if (renderer->isText()) {
+ RenderText *textRenderer = static_cast<RenderText *>(renderer);
+ for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+ if (pos.offset() >= box->m_start && pos.offset() <= box->m_start + box->m_len) {
+ return true;
+ }
+ else if (pos.offset() < box->m_start) {
+ // The offset we're looking for is before this node
+ // this means the offset must be in content that is
+ // not rendered. Return false.
+ return false;
+ }
+ }
+ }
+ else if (renderer->isReplaced() || (pos.node()->isBlockFlow() && !pos.node()->firstChild() && pos.offset() == 0)) {
+ // return true for replaced elements, for inline flows if they have a line box
+ // and for blocks if they are empty
+ return true;
+ }
+
+ return false;
+}
+
+Position CaretPosition::deepEquivalent(const Position &pos)
+{
+ if (pos.isEmpty())
+ return pos;
+
+ if (isAtomicNode(pos.node())) {
+ // This is part of the strategy to wean the code off Positions with BRs and replaced elements
+ // as the nodes and offsets > 0.
+ if (pos.offset() > 0 && (pos.node()->id() == ID_BR || pos.node()->renderer() && pos.node()->renderer()->isReplaced())) {
+ NodeImpl *next = pos.node()->traverseNextNode();
+ if (next && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement())
+ return deepEquivalent(Position(next, 0));
+ }
+ return pos;
+ }
+
+ NodeImpl *child = 0;
+ Position result = pos;
+ if (pos.offset() >= (long)pos.node()->childNodeCount()) {
+ child = pos.node()->lastChild();
+ result = Position(child, maxOffset(child));
+ ASSERT(child);
+ while (!isAtomicNode(child) && result.node()->hasChildNodes()) {
+ child = result.node()->lastChild();
+ ASSERT(child);
+ result = Position(child, maxOffset(child));
+ }
+ }
+ else {
+ child = pos.node()->childNode(pos.offset());
+ ASSERT(child);
+ result = Position(child, 0);
+ while (!isAtomicNode(child) && result.node()->hasChildNodes()) {
+ child = result.node()->firstChild();
+ ASSERT(child);
+ result = Position(child, 0);
+ }
+ }
+ return result;
+}
+
+Position CaretPosition::rangeCompliantEquivalent(const Position &pos)
+{
+ if (pos.isEmpty())
+ return Position();
+
+ if (isAtomicNode(pos.node()))
+ return Position(pos.node()->parentNode(), pos.offset() > 0 ? pos.node()->nodeIndex() + 1 : pos.node()->nodeIndex());
+ else
+ return Position(pos.node(), kMin(pos.offset(), maxOffset(pos.node())));
+}
+
+long CaretPosition::maxOffset(const NodeImpl *node)
+{
+ return offsetInCharacters(node->nodeType()) ? (long)static_cast<const CharacterDataImpl *>(node)->length() : (long)node->childNodeCount();
+}
+
+bool CaretPosition::isAtomicNode(const NodeImpl *node)
+{
+ return node && (!node->hasChildNodes() || (node->id() == ID_OBJECT && node->renderer() && node->renderer()->isReplaced()));
+}
+
+// CaretPosition objects are immutable.
+// This helper is only used during construction and assignment.
+void CaretPosition::setPosition(const Position &pos)
+{
+ if (m_node)
+ m_node->deref();
+ m_node = pos.node();
+ if (m_node)
+ m_node->ref();
+
+ m_offset = pos.offset();
+}
+
+void CaretPosition::debugPosition(const char *msg) const
+{
+ if (isEmpty())
+ fprintf(stderr, "Position [%s]: empty\n", msg);
+ else
+ fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, getTagName(node()->id()).string().latin1(), node(), offset());
+}
+
+#ifndef NDEBUG
+#define FormatBufferSize 1024
+void CaretPosition::formatForDebugger(char *buffer, unsigned length) const
+{
+ DOMString result;
+ DOMString s;
+
+ if (isEmpty()) {
+ result = "<empty>";
+ }
+ else {
+ char s[FormatBufferSize];
+ result += "offset ";
+ result += QString::number(m_offset);
+ result += " of ";
+ m_node->formatForDebugger(s, FormatBufferSize);
+ result += s;
+ }
+
+ strncpy(buffer, result.string().latin1(), length - 1);
+}
+#undef FormatBufferSize
+#endif
+
+} // namespace DOM
--- /dev/null
+/*
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef __dom_caretposition_h__
+#define __dom_caretposition_h__
+
+#include "dom_position.h"
+
+namespace DOM {
+
+class NodeImpl;
+class Position;
+
+class CaretPosition
+{
+public:
+ CaretPosition() : m_node(0), m_offset(0) {}
+ CaretPosition(NodeImpl *, long offset);
+ explicit CaretPosition(const Position &);
+ CaretPosition(const CaretPosition &);
+ ~CaretPosition();
+
+ CaretPosition &operator=(const CaretPosition &o);
+
+ bool isLastInBlock() const;
+
+ CaretPosition next() const;
+ CaretPosition previous() const;
+
+ bool isEmpty() const { return m_node == 0; }
+ bool notEmpty() const { return m_node != 0; }
+
+ Position position() const { return rangeCompliantEquivalent(*this); }
+ Position deepEquivalent() const { return Position(node(), offset()); }
+
+ friend inline bool operator==(const CaretPosition &a, const CaretPosition &b);
+ friend inline bool operator!=(const CaretPosition &a, const CaretPosition &b);
+
+ void debugPosition(const char *msg="") const;
+
+#ifndef NDEBUG
+ void formatForDebugger(char *buffer, unsigned length) const;
+#endif
+
+private:
+ operator Position() const { return Position(node(), offset()); }
+
+ void init(const Position &);
+ void setPosition(const Position &);
+
+ NodeImpl *node() const { return m_node; }
+ long offset() const { return m_offset; }
+
+ static Position deepEquivalent(const Position &);
+ static Position rangeCompliantEquivalent(const Position &);
+
+ static long maxOffset(const NodeImpl *);
+ static bool isAtomicNode(const NodeImpl *);
+
+ static Position previousCaretPosition(const Position &);
+ static Position nextCaretPosition(const Position &);
+
+ static Position previousPosition(const Position &);
+ static Position nextPosition(const Position &);
+
+ static bool atStart(const Position &);
+ static bool atEnd(const Position &);
+
+ static bool isCandidate(const Position &);
+
+ NodeImpl *m_node;
+ long m_offset;
+};
+
+inline bool operator==(const CaretPosition &a, const CaretPosition &b)
+{
+ return a.node() == b.node() && a.offset() == b.offset();
+}
+
+inline bool operator!=(const CaretPosition &a, const CaretPosition &b)
+{
+ return !(a == b);
+}
+
+} // namespace DOM
+
+#endif // __dom_caretposition_h__
\ No newline at end of file
const NodeImpl *n = this;
while (n && !n->nextSibling() && (!stayWithin || n->parentNode() != stayWithin))
n = n->parentNode();
- if (n && (!stayWithin || n->parentNode() != stayWithin))
+ if (n)
return n->nextSibling();
return 0;
}
const NodeImpl *n = this;
while (n && !n->nextSibling() && (!stayWithin || n->parentNode() != stayWithin))
n = n->parentNode();
- if (n && (!stayWithin || n->parentNode() != stayWithin))
+ if (n)
return n->nextSibling();
return 0;
}
const NodeImpl *n = this;
while (n && !n->previousSibling() && (!stayWithin || n->parentNode() != stayWithin))
n = n->parentNode();
- if (n && (!stayWithin || n->parentNode() != stayWithin))
+ if (n)
return n->previousSibling();
return 0;
}
if (node) {
node->ref();
}
-};
+}
Position::Position(const Position &o)
: m_node(o.m_node), m_offset(o.m_offset)
NodeImpl *block = node()->isBlockFlow() ? node() : node()->enclosingBlockFlowElement();
- PositionIterator it(*this);
+ PositionIterator it(equivalentDeepPosition());
for (; !it.atStart(); it.previous()) {
if (stayInBlock) {
NodeImpl *currentBlock = it.current().node()->isBlockFlow() ? it.current().node() : it.current().node()->enclosingBlockFlowElement();
NodeImpl *block = node()->isBlockFlow() ? node() : node()->enclosingBlockFlowElement();
- PositionIterator it(*this);
+ PositionIterator it(equivalentDeepPosition());
for (; !it.atEnd(); it.next()) {
if (stayInBlock) {
NodeImpl *currentBlock = it.current().node()->isBlockFlow() ? it.current().node() : it.current().node()->enclosingBlockFlowElement();
return Position();
}
-bool Position::atStartOfContainingEditableBlock() const
-{
- return renderedOffset() == 0 && inFirstEditableInContainingEditableBlock();
-}
-
-bool Position::atStartOfRootEditableElement() const
-{
- return renderedOffset() == 0 && inFirstEditableInRootEditableElement();
-}
-
bool Position::inRenderedContent() const
{
if (isEmpty())
return true;
}
-bool Position::isLastRenderedPositionInEditableBlock() const
-{
- if (isEmpty())
- return false;
-
- RenderObject *renderer = node()->renderer();
- if (!renderer)
- return false;
-
- if (renderer->style()->visibility() != VISIBLE)
- return false;
-
- if (renderedOffset() != (long)node()->caretMaxRenderedOffset())
- return false;
-
- PositionIterator it(*this);
- while (!it.atEnd()) {
- it.next();
- if (!it.current().node()->inSameContainingBlockFlowElement(node()))
- return true;
- RenderObject *currentRenderer = it.current().node()->renderer();
- if (!currentRenderer || currentRenderer->firstChild())
- // we want a leaf for this check
- continue;
- if (it.current().inRenderedContent())
- return false;
- }
- return true;
-}
-
bool Position::inFirstEditableInRootEditableElement() const
{
if (isEmpty() || !inRenderedContent())
return true;
}
-bool Position::inLastEditableInRootEditableElement() const
-{
- if (isEmpty() || !inRenderedContent())
- return false;
-
- PositionIterator it(*this);
- while (!it.atEnd()) {
- it.next();
- RenderObject *currentRenderer = it.current().node()->renderer();
- if (!currentRenderer || currentRenderer->firstChild())
- // we want a leaf for this check
- continue;
- if (it.current().inRenderedContent())
- return false;
- }
-
- return true;
-}
-
-bool Position::inFirstEditableInContainingEditableBlock() const
-{
- if (isEmpty() || !inRenderedContent())
- return false;
-
- NodeImpl *block = node()->enclosingBlockFlowElement();
-
- PositionIterator it(*this);
- while (!it.atStart()) {
- it.previous();
- RenderObject *currentRenderer = it.current().node()->renderer();
- if (!currentRenderer || currentRenderer->firstChild())
- // we want a leaf for this check
- continue;
- if (!it.current().inRenderedContent())
- continue;
- return block != it.current().node()->enclosingBlockFlowElement();
- }
-
- return true;
-}
-
-bool Position::inLastEditableInContainingEditableBlock() const
-{
- if (isEmpty() || !inRenderedContent())
- return false;
-
- NodeImpl *block = node()->enclosingBlockFlowElement();
-
- PositionIterator it(*this);
- while (!it.atEnd()) {
- it.next();
- RenderObject *currentRenderer = it.current().node()->renderer();
- if (!currentRenderer || currentRenderer->firstChild())
- // we want a leaf for this check
- continue;
- if (!it.current().inRenderedContent())
- continue;
- return block != it.current().node()->enclosingBlockFlowElement();
- }
-
- return true;
-}
-
static inline bool isWS(const QChar &c)
{
const char nonBreakingSpace = 0xA0;
Position equivalentShallowPosition() const;
Position equivalentDeepPosition() const;
Position closestRenderedPosition(EAffinity) const;
- bool atStartOfContainingEditableBlock() const;
- bool atStartOfRootEditableElement() const;
bool inRenderedContent() const;
bool inRenderedText() const;
bool isRenderedCharacter() const;
bool rendersInDifferentPosition(const Position &pos) const;
bool isFirstRenderedPositionOnLine() const;
bool isLastRenderedPositionOnLine() const;
- bool isLastRenderedPositionInEditableBlock() const;
bool inFirstEditableInRootEditableElement() const;
- bool inLastEditableInRootEditableElement() const;
- bool inFirstEditableInContainingEditableBlock() const;
- bool inLastEditableInContainingEditableBlock() const;
Position &operator=(const Position &o);
#include "dom_selection.h"
+#include "dom_caretposition.h"
+#include "dom_docimpl.h"
+#include "dom_elementimpl.h"
+#include "dom_node.h"
+#include "dom_nodeimpl.h"
+#include "dom_positioniterator.h"
+#include "dom_string.h"
+#include "dom_textimpl.h"
+#include "dom2_range.h"
#include "htmltags.h"
#include "khtml_part.h"
#include "khtmlview.h"
#include "qevent.h"
#include "qpainter.h"
#include "qrect.h"
-#include "dom/dom2_range.h"
-#include "dom/dom_node.h"
-#include "dom/dom_string.h"
-#include "rendering/render_object.h"
-#include "rendering/render_style.h"
-#include "rendering/render_text.h"
-#include "xml/dom_docimpl.h"
-#include "xml/dom_positioniterator.h"
-#include "xml/dom_elementimpl.h"
-#include "xml/dom_nodeimpl.h"
-#include "xml/dom_textimpl.h"
+#include "render_object.h"
+#include "render_style.h"
+#include "render_text.h"
#if APPLE_CHANGES
#include "KWQAssertions.h"
pos = pos.nextCharacterPosition();
break;
case WORD:
- pos = pos.nextWordPosition();
+ pos = CaretPosition(pos).next().deepEquivalent();
break;
case PARAGRAPH:
// "Next paragraph" not implemented yet. Fall through to LINE.
if (state() == RANGE)
pos = end();
else
- pos = extent().nextCharacterPosition();
+ pos = CaretPosition(extent()).next().deepEquivalent();
break;
case WORD:
pos = extent().nextWordPosition();
Position pos = extent();
switch (granularity) {
case CHARACTER:
- pos = pos.previousCharacterPosition();
+ pos = CaretPosition(pos).previous().deepEquivalent();
break;
case WORD:
pos = pos.previousWordPosition();
if (state() == RANGE)
pos = start();
else
- pos = extent().previousCharacterPosition();
+ pos = CaretPosition(extent()).previous().deepEquivalent();
break;
case WORD:
pos = extent().previousWordPosition();
}
case PARAGRAPH:
if (m_baseIsStart) {
- assignStart(base().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(extent().endParagraphBoundary(IncludeLineBreak).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(base().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(extent().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
} else {
- assignStart(extent().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(base().endParagraphBoundary(IncludeLineBreak).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(extent().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(base().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
}
break;
case DOCUMENT: {
NodeImpl *de = start().node()->getDocument()->documentElement();
- assignStart(Position(de, 0).equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(Position(de, de->childNodeCount()).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(de, 0).deepEquivalent());
+ assignEnd(CaretPosition(de, de->childNodeCount()).deepEquivalent());
break;
}
case PARAGRAPH_BOUNDARY:
if (m_baseIsStart) {
- assignStart(base().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(extent().endParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(base().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(extent().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
} else {
- assignStart(extent().startParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
- assignEnd(base().endParagraphBoundary().equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+ assignStart(CaretPosition(extent().startParagraphBoundary()).deepEquivalent());
+ assignEnd(CaretPosition(base().endParagraphBoundary(IncludeLineBreak)).deepEquivalent());
}
break;
}