[SCM] WebKit Debian packaging branch, debian/unstable, updated. debian/1.1.15-1-40151-g37bb677
darin
darin at 268f45cc-cd09-0410-ab3c-d52691b4dbfc
Sat Sep 26 08:38:04 UTC 2009
The following commit has been merged in the debian/unstable branch:
commit a184cc66924812dd150bc5360a9b0f37da0e3560
Author: darin <darin at 268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Date: Mon May 3 22:29:59 2004 +0000
Reviewed by Ken
- fixed <rdar://problem/3091531>: "should format copied tables with tabs between cells, spreadsheet-style, for pasting to Excel"
- fixed <rdar://problem/3110002>: "Find doesn't match across formatting boundaries"
- fixed <rdar://problem/3640340>: "REGRESSION (136-137): nil-deref in KHTMLPart::setFocusNodeIfNeeded inside triple click code after repeatedly click/drag selecting text"
- fixed <rdar://problem/3640422>: "too many blank lines between paragraphs in copied text"
* khtml/dom/dom2_range.h: Added == and != operators to DOMRange.
* khtml/dom/dom2_range.cpp: (DOM::operator==): Added.
* khtml/khtml_part.cpp:
(KHTMLPart::init): Don't call findTextBegin any more; it's now not used at all
in WebCore.
(KHTMLPart::clear): Don't call findTextBegin any more. Also don't set up the
load statistics variables.
(KHTMLPart::findTextNext): Roll out APPLE_CHANGES; this function is no longer
used in WebCore any more and instead the entire thing is compiled out.
(KHTMLPart::text): Reimplement this by calling one of the new text operations. All the interesting
parts were moved into the TextIterator class.
(KHTMLPart::setFocusNodeIfNeeded): Add a check for nil; this is the fix for 364030.
* khtml/khtmlpart_p.h: (KHTMLPartPrivate::KHTMLPartPrivate): Put m_findPos,
m_findNode, m_overURL, m_overURLTarget, m_scrollTimer, m_loadedObjects,
m_totalObjectCount, and m_jobPercent inside !APPLE_CHANGES.
* khtml/xml/dom_selection.h: Replaced uses of 4-character tabs with spaces.
(DOM::Selection::Selection): Added a constructor that takes a DOM range.
(DOM::Selection::operator=): Overloaded operator= for DOM range and position.
This is slightly more efficient than letting a second Selection object be constructed.
* khtml/xml/dom_selection.cpp: Replaced uses of 4-character tabs with spaces.
(DOM::Selection::Selection): Added a constructor that takes a DOM range.
* kwq/KWQKHTMLPart.mm: (KWQKHTMLPart::findString): Reimplement find so it uses
the new text operations function for finding.
* khtml/misc/khtml_text_operations.h:
* khtml/misc/khtml_text_operations.cpp:
* WebCore.pbproj/project.pbxproj:
Added new text iterator classes that do the heavy lifting.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@6532 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/WebCore/ChangeLog-2005-08-23 b/WebCore/ChangeLog-2005-08-23
index ab55223..64bf7d8 100644
--- a/WebCore/ChangeLog-2005-08-23
+++ b/WebCore/ChangeLog-2005-08-23
@@ -1,3 +1,45 @@
+2004-05-03 Darin Adler <darin at apple.com>
+
+ Reviewed by Ken
+
+ - fixed <rdar://problem/3091531>: "should format copied tables with tabs between cells, spreadsheet-style, for pasting to Excel"
+ - fixed <rdar://problem/3110002>: "Find doesn't match across formatting boundaries"
+ - fixed <rdar://problem/3640340>: "REGRESSION (136-137): nil-deref in KHTMLPart::setFocusNodeIfNeeded inside triple click code after repeatedly click/drag selecting text"
+ - fixed <rdar://problem/3640422>: "too many blank lines between paragraphs in copied text"
+
+ * khtml/dom/dom2_range.h: Added == and != operators to DOMRange.
+ * khtml/dom/dom2_range.cpp: (DOM::operator==): Added.
+
+ * khtml/khtml_part.cpp:
+ (KHTMLPart::init): Don't call findTextBegin any more; it's now not used at all
+ in WebCore.
+ (KHTMLPart::clear): Don't call findTextBegin any more. Also don't set up the
+ load statistics variables.
+ (KHTMLPart::findTextNext): Roll out APPLE_CHANGES; this function is no longer
+ used in WebCore any more and instead the entire thing is compiled out.
+ (KHTMLPart::text): Reimplement this by calling one of the new text operations. All the interesting
+ parts were moved into the TextIterator class.
+ (KHTMLPart::setFocusNodeIfNeeded): Add a check for nil; this is the fix for 364030.
+
+ * khtml/khtmlpart_p.h: (KHTMLPartPrivate::KHTMLPartPrivate): Put m_findPos,
+ m_findNode, m_overURL, m_overURLTarget, m_scrollTimer, m_loadedObjects,
+ m_totalObjectCount, and m_jobPercent inside !APPLE_CHANGES.
+
+ * khtml/xml/dom_selection.h: Replaced uses of 4-character tabs with spaces.
+ (DOM::Selection::Selection): Added a constructor that takes a DOM range.
+ (DOM::Selection::operator=): Overloaded operator= for DOM range and position.
+ This is slightly more efficient than letting a second Selection object be constructed.
+ * khtml/xml/dom_selection.cpp: Replaced uses of 4-character tabs with spaces.
+ (DOM::Selection::Selection): Added a constructor that takes a DOM range.
+
+ * kwq/KWQKHTMLPart.mm: (KWQKHTMLPart::findString): Reimplement find so it uses
+ the new text operations function for finding.
+
+ * khtml/misc/khtml_text_operations.h:
+ * khtml/misc/khtml_text_operations.cpp:
+ * WebCore.pbproj/project.pbxproj:
+ Added new text iterator classes that do the heavy lifting.
+
2004-05-03 David Hyatt <hyatt at apple.com>
Make sure that XML processing instructions set themselves as the parent node of the stylesheets they load,
diff --git a/WebCore/WebCore.pbproj/project.pbxproj b/WebCore/WebCore.pbproj/project.pbxproj
index e06d154..7871e3f 100644
--- a/WebCore/WebCore.pbproj/project.pbxproj
+++ b/WebCore/WebCore.pbproj/project.pbxproj
@@ -533,6 +533,7 @@
BC86FB8F061F5C23006BB822,
BE8BD8F506359F6000D3F20B,
BE8BD90B0635CC2F00D3F20B,
+ 9342E3EB0646C8FF00004B05,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@@ -816,6 +817,7 @@
BE8BD8F406359F6000D3F20B,
BE8BD90A0635CC2F00D3F20B,
842F72DD06405FDF00CC271B,
+ 9342E3EA0646C8FF00004B05,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@@ -1780,6 +1782,34 @@
settings = {
};
};
+ 9342E3E80646C8FF00004B05 = {
+ fileEncoding = 4;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ path = khtml_text_operations.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 9342E3E90646C8FF00004B05 = {
+ fileEncoding = 4;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ path = khtml_text_operations.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 9342E3EA0646C8FF00004B05 = {
+ fileRef = 9342E3E80646C8FF00004B05;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 9342E3EB0646C8FF00004B05 = {
+ fileRef = 9342E3E90646C8FF00004B05;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
934E43780414294A008635CE = {
fileEncoding = 4;
isa = PBXFileReference;
@@ -5533,6 +5563,8 @@
F523D28102DE43D7018635CA,
F523D28202DE43D7018635CA,
F523D28302DE43D7018635CA,
+ 9342E3E80646C8FF00004B05,
+ 9342E3E90646C8FF00004B05,
F523D28402DE43D7018635CA,
F523D28502DE43D7018635CA,
F523D28602DE43D7018635CA,
diff --git a/WebCore/khtml/dom/dom2_range.cpp b/WebCore/khtml/dom/dom2_range.cpp
index 271bd0d..356c08c 100644
--- a/WebCore/khtml/dom/dom2_range.cpp
+++ b/WebCore/khtml/dom/dom2_range.cpp
@@ -5,7 +5,7 @@
* (C) 2000 Gunnstein Lye (gunnstein at netcom.no)
* (C) 2000 Frederik Holljen (frederik.holljen at hig.no)
* (C) 2001 Peter Kelly (pmk at post.com)
- * Copyright (C) 2003 Apple Computer, Inc.
+ * Copyright (C) 2004 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -23,11 +23,13 @@
* Boston, MA 02111-1307, USA.
*
*/
+
#include "dom/dom_exception.h"
+
#include "xml/dom_docimpl.h"
#include "xml/dom2_rangeimpl.h"
-using namespace DOM;
+namespace DOM {
Range::Range()
{
@@ -400,5 +402,12 @@ void Range::throwException(int exceptioncode) const
throw DOMException(exceptioncode);
}
+bool operator==(const Range &a, const Range &b)
+{
+ return a.startContainer() == b.startContainer()
+ && a.endContainer() == b.endContainer()
+ && a.startOffset() == b.startOffset()
+ && a.endOffset() == b.endOffset();
+}
-
+}
diff --git a/WebCore/khtml/dom/dom2_range.h b/WebCore/khtml/dom/dom2_range.h
index bb2e526..a5ac6bd 100644
--- a/WebCore/khtml/dom/dom2_range.h
+++ b/WebCore/khtml/dom/dom2_range.h
@@ -5,6 +5,7 @@
* (C) 2000 Gunnstein Lye (gunnstein at netcom.no)
* (C) 2000 Frederik Holljen (frederik.holljen at hig.no)
* (C) 2001 Peter Kelly (pmk at post.com)
+ * Copyright (C) 2004 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -472,6 +473,9 @@ private:
void throwException(int exceptioncode) const;
};
-}; // namespace
+bool operator==(const Range &, const Range &);
+inline bool operator!=(const Range &a, const Range &b) { return !(a == b); }
+
+} // namespace
#endif
diff --git a/WebCore/khtml/editing/SelectionController.cpp b/WebCore/khtml/editing/SelectionController.cpp
index 6f1774f..086487a 100644
--- a/WebCore/khtml/editing/SelectionController.cpp
+++ b/WebCore/khtml/editing/SelectionController.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003 Apple Computer, Inc. All rights reserved.
+ * 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
@@ -77,14 +77,24 @@ Selection::Selection()
Selection::Selection(const Position &pos)
{
init();
- assignBaseAndExtent(pos, pos);
+ assignBaseAndExtent(pos, pos);
+ validate();
+}
+
+Selection::Selection(const Range &r)
+{
+ const Position start(r.startContainer().handle(), r.startOffset());
+ const Position end(r.endContainer().handle(), r.endOffset());
+
+ init();
+ assignBaseAndExtent(start, end);
validate();
}
Selection::Selection(const Position &base, const Position &extent)
{
init();
- assignBaseAndExtent(base, extent);
+ assignBaseAndExtent(base, extent);
validate();
}
@@ -92,8 +102,8 @@ Selection::Selection(const Selection &o)
{
init();
- assignBaseAndExtent(o.base(), o.extent());
- assignStartAndEnd(o.start(), o.end());
+ assignBaseAndExtent(o.base(), o.extent());
+ assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
@@ -129,8 +139,8 @@ void Selection::init()
Selection &Selection::operator=(const Selection &o)
{
- assignBaseAndExtent(o.base(), o.extent());
- assignStartAndEnd(o.start(), o.end());
+ assignBaseAndExtent(o.base(), o.extent());
+ assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
@@ -166,22 +176,22 @@ void Selection::moveTo(const Range &r)
{
Position start(r.startContainer().handle(), r.startOffset());
Position end(r.endContainer().handle(), r.endOffset());
- moveTo(start, end);
+ moveTo(start, end);
}
void Selection::moveTo(const Selection &o)
{
- moveTo(o.start(), o.end());
+ moveTo(o.start(), o.end());
}
void Selection::moveTo(const Position &pos)
{
- moveTo(pos, pos);
+ moveTo(pos, pos);
}
void Selection::moveTo(const Position &base, const Position &extent)
{
- assignBaseAndExtent(base, extent);
+ assignBaseAndExtent(base, extent);
validate();
}
@@ -337,8 +347,8 @@ int Selection::xPosForVerticalArrowNavigation(EPositionType type, bool recalc) c
void Selection::clear()
{
- assignBaseAndExtent(emptyPosition(), emptyPosition());
- validate();
+ assignBaseAndExtent(emptyPosition(), emptyPosition());
+ validate();
}
void Selection::setBase(const Position &pos)
@@ -486,13 +496,13 @@ void Selection::validate(ETextGranularity granularity)
}
// make sure we do not have a dangling start or end
- if (base().isEmpty() && extent().isEmpty()) {
+ if (base().isEmpty() && extent().isEmpty()) {
assignStartAndEnd(emptyPosition(), emptyPosition());
m_baseIsStart = true;
}
- else if (base().isEmpty() || extent().isEmpty()) {
+ else if (base().isEmpty() || extent().isEmpty()) {
m_baseIsStart = true;
- }
+ }
else {
// adjust m_baseIsStart as needed
if (base().node() == extent().node()) {
@@ -572,13 +582,13 @@ void Selection::validate(ETextGranularity granularity)
}
#endif // APPLE_CHANGES
- // adjust the state
- if (start().isEmpty() && end().isEmpty())
- m_state = NONE;
- else if (start() == end())
- m_state = CARET;
- else
- m_state = RANGE;
+ // adjust the state
+ if (start().isEmpty() && end().isEmpty())
+ m_state = NONE;
+ else if (start() == end())
+ m_state = CARET;
+ else
+ m_state = RANGE;
m_needsCaretLayout = true;
@@ -618,13 +628,13 @@ bool Selection::moveToRenderedContent()
bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
{
- if (!n1 || !n2)
- return true;
-
- if (n1 == n2)
- return true;
-
- bool result = false;
+ if (!n1 || !n2)
+ return true;
+
+ if (n1 == n2)
+ return true;
+
+ bool result = false;
int n1Depth = 0;
int n2Depth = 0;
@@ -666,7 +676,7 @@ bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
}
n = n->nextSibling();
}
- return result;
+ return result;
}
#if APPLE_CHANGES
diff --git a/WebCore/khtml/editing/SelectionController.h b/WebCore/khtml/editing/SelectionController.h
index 05434bc..d131d40 100644
--- a/WebCore/khtml/editing/SelectionController.h
+++ b/WebCore/khtml/editing/SelectionController.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003 Apple Computer, Inc. All rights reserved.
+ * 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
@@ -45,10 +45,10 @@ class Range;
class Selection
{
public:
- enum EState { NONE, CARET, RANGE };
- enum EAlter { MOVE, EXTEND };
- enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT, UP, DOWN };
- enum ETextGranularity { CHARACTER, WORD, LINE };
+ enum EState { NONE, CARET, RANGE };
+ enum EAlter { MOVE, EXTEND };
+ enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT, UP, DOWN };
+ enum ETextGranularity { CHARACTER, WORD, LINE };
// These match the AppKit values for these concepts.
// From NSTextView.h:
@@ -57,13 +57,13 @@ public:
enum EAffinity { UPSTREAM = 0, DOWNSTREAM = 1 };
Selection();
+ Selection(const Range &);
Selection(const Position &);
Selection(const Position &, const Position &);
Selection(const Selection &);
- ~Selection() {}
- EState state() const { return m_state; }
- EAffinity affinity() const { return m_affinity; }
+ EState state() const { return m_state; }
+ EAffinity affinity() const { return m_affinity; }
void setAffinity(EAffinity);
void moveTo(const Range &);
@@ -100,6 +100,8 @@ public:
void debugRenderer(khtml::RenderObject *r, bool selected) const;
Selection &operator=(const Selection &o);
+ Selection &operator=(const Range &r) { moveTo(r); return *this; }
+ Selection &operator=(const Position &r) { moveTo(r); return *this; }
friend bool operator==(const Selection &a, const Selection &b);
friend bool operator!=(const Selection &a, const Selection &b);
@@ -107,7 +109,7 @@ public:
friend class KHTMLPart;
private:
- enum EPositionType { START, END, BASE, EXTENT };
+ enum EPositionType { START, END, BASE, EXTENT };
void init();
void validate(ETextGranularity granularity=CHARACTER);
@@ -130,16 +132,16 @@ private:
Position m_start; // start position for the selection
Position m_end; // end position for the selection
- EState m_state; // the state of the selection
- EAffinity m_affinity; // the upstream/downstream affinity of the selection
+ EState m_state; // the state of the selection
+ EAffinity m_affinity; // the upstream/downstream affinity of the selection
- int m_caretX; // caret coordinates and size
- int m_caretY;
- int m_caretSize;
+ int m_caretX; // caret coordinates and size
+ int m_caretY;
+ int m_caretSize;
- bool m_baseIsStart : 1; // true if base node is before the extent node
- bool m_needsCaretLayout : 1; // true if the caret position needs to be calculated
- bool m_modifyBiasSet : 1; // true if the selection has been horizontally
+ bool m_baseIsStart : 1; // true if base node is before the extent node
+ bool m_needsCaretLayout : 1; // true if the caret position needs to be calculated
+ bool m_modifyBiasSet : 1; // true if the selection has been horizontally
// modified with EAlter::EXTEND
};
diff --git a/WebCore/khtml/editing/selection.cpp b/WebCore/khtml/editing/selection.cpp
index 6f1774f..086487a 100644
--- a/WebCore/khtml/editing/selection.cpp
+++ b/WebCore/khtml/editing/selection.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003 Apple Computer, Inc. All rights reserved.
+ * 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
@@ -77,14 +77,24 @@ Selection::Selection()
Selection::Selection(const Position &pos)
{
init();
- assignBaseAndExtent(pos, pos);
+ assignBaseAndExtent(pos, pos);
+ validate();
+}
+
+Selection::Selection(const Range &r)
+{
+ const Position start(r.startContainer().handle(), r.startOffset());
+ const Position end(r.endContainer().handle(), r.endOffset());
+
+ init();
+ assignBaseAndExtent(start, end);
validate();
}
Selection::Selection(const Position &base, const Position &extent)
{
init();
- assignBaseAndExtent(base, extent);
+ assignBaseAndExtent(base, extent);
validate();
}
@@ -92,8 +102,8 @@ Selection::Selection(const Selection &o)
{
init();
- assignBaseAndExtent(o.base(), o.extent());
- assignStartAndEnd(o.start(), o.end());
+ assignBaseAndExtent(o.base(), o.extent());
+ assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
@@ -129,8 +139,8 @@ void Selection::init()
Selection &Selection::operator=(const Selection &o)
{
- assignBaseAndExtent(o.base(), o.extent());
- assignStartAndEnd(o.start(), o.end());
+ assignBaseAndExtent(o.base(), o.extent());
+ assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
@@ -166,22 +176,22 @@ void Selection::moveTo(const Range &r)
{
Position start(r.startContainer().handle(), r.startOffset());
Position end(r.endContainer().handle(), r.endOffset());
- moveTo(start, end);
+ moveTo(start, end);
}
void Selection::moveTo(const Selection &o)
{
- moveTo(o.start(), o.end());
+ moveTo(o.start(), o.end());
}
void Selection::moveTo(const Position &pos)
{
- moveTo(pos, pos);
+ moveTo(pos, pos);
}
void Selection::moveTo(const Position &base, const Position &extent)
{
- assignBaseAndExtent(base, extent);
+ assignBaseAndExtent(base, extent);
validate();
}
@@ -337,8 +347,8 @@ int Selection::xPosForVerticalArrowNavigation(EPositionType type, bool recalc) c
void Selection::clear()
{
- assignBaseAndExtent(emptyPosition(), emptyPosition());
- validate();
+ assignBaseAndExtent(emptyPosition(), emptyPosition());
+ validate();
}
void Selection::setBase(const Position &pos)
@@ -486,13 +496,13 @@ void Selection::validate(ETextGranularity granularity)
}
// make sure we do not have a dangling start or end
- if (base().isEmpty() && extent().isEmpty()) {
+ if (base().isEmpty() && extent().isEmpty()) {
assignStartAndEnd(emptyPosition(), emptyPosition());
m_baseIsStart = true;
}
- else if (base().isEmpty() || extent().isEmpty()) {
+ else if (base().isEmpty() || extent().isEmpty()) {
m_baseIsStart = true;
- }
+ }
else {
// adjust m_baseIsStart as needed
if (base().node() == extent().node()) {
@@ -572,13 +582,13 @@ void Selection::validate(ETextGranularity granularity)
}
#endif // APPLE_CHANGES
- // adjust the state
- if (start().isEmpty() && end().isEmpty())
- m_state = NONE;
- else if (start() == end())
- m_state = CARET;
- else
- m_state = RANGE;
+ // adjust the state
+ if (start().isEmpty() && end().isEmpty())
+ m_state = NONE;
+ else if (start() == end())
+ m_state = CARET;
+ else
+ m_state = RANGE;
m_needsCaretLayout = true;
@@ -618,13 +628,13 @@ bool Selection::moveToRenderedContent()
bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
{
- if (!n1 || !n2)
- return true;
-
- if (n1 == n2)
- return true;
-
- bool result = false;
+ if (!n1 || !n2)
+ return true;
+
+ if (n1 == n2)
+ return true;
+
+ bool result = false;
int n1Depth = 0;
int n2Depth = 0;
@@ -666,7 +676,7 @@ bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
}
n = n->nextSibling();
}
- return result;
+ return result;
}
#if APPLE_CHANGES
diff --git a/WebCore/khtml/editing/selection.h b/WebCore/khtml/editing/selection.h
index 05434bc..d131d40 100644
--- a/WebCore/khtml/editing/selection.h
+++ b/WebCore/khtml/editing/selection.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003 Apple Computer, Inc. All rights reserved.
+ * 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
@@ -45,10 +45,10 @@ class Range;
class Selection
{
public:
- enum EState { NONE, CARET, RANGE };
- enum EAlter { MOVE, EXTEND };
- enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT, UP, DOWN };
- enum ETextGranularity { CHARACTER, WORD, LINE };
+ enum EState { NONE, CARET, RANGE };
+ enum EAlter { MOVE, EXTEND };
+ enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT, UP, DOWN };
+ enum ETextGranularity { CHARACTER, WORD, LINE };
// These match the AppKit values for these concepts.
// From NSTextView.h:
@@ -57,13 +57,13 @@ public:
enum EAffinity { UPSTREAM = 0, DOWNSTREAM = 1 };
Selection();
+ Selection(const Range &);
Selection(const Position &);
Selection(const Position &, const Position &);
Selection(const Selection &);
- ~Selection() {}
- EState state() const { return m_state; }
- EAffinity affinity() const { return m_affinity; }
+ EState state() const { return m_state; }
+ EAffinity affinity() const { return m_affinity; }
void setAffinity(EAffinity);
void moveTo(const Range &);
@@ -100,6 +100,8 @@ public:
void debugRenderer(khtml::RenderObject *r, bool selected) const;
Selection &operator=(const Selection &o);
+ Selection &operator=(const Range &r) { moveTo(r); return *this; }
+ Selection &operator=(const Position &r) { moveTo(r); return *this; }
friend bool operator==(const Selection &a, const Selection &b);
friend bool operator!=(const Selection &a, const Selection &b);
@@ -107,7 +109,7 @@ public:
friend class KHTMLPart;
private:
- enum EPositionType { START, END, BASE, EXTENT };
+ enum EPositionType { START, END, BASE, EXTENT };
void init();
void validate(ETextGranularity granularity=CHARACTER);
@@ -130,16 +132,16 @@ private:
Position m_start; // start position for the selection
Position m_end; // end position for the selection
- EState m_state; // the state of the selection
- EAffinity m_affinity; // the upstream/downstream affinity of the selection
+ EState m_state; // the state of the selection
+ EAffinity m_affinity; // the upstream/downstream affinity of the selection
- int m_caretX; // caret coordinates and size
- int m_caretY;
- int m_caretSize;
+ int m_caretX; // caret coordinates and size
+ int m_caretY;
+ int m_caretSize;
- bool m_baseIsStart : 1; // true if base node is before the extent node
- bool m_needsCaretLayout : 1; // true if the caret position needs to be calculated
- bool m_modifyBiasSet : 1; // true if the selection has been horizontally
+ bool m_baseIsStart : 1; // true if base node is before the extent node
+ bool m_needsCaretLayout : 1; // true if the caret position needs to be calculated
+ bool m_modifyBiasSet : 1; // true if the selection has been horizontally
// modified with EAlter::EXTEND
};
diff --git a/WebCore/khtml/editing/visible_text.cpp b/WebCore/khtml/editing/visible_text.cpp
new file mode 100644
index 0000000..98f2e44
--- /dev/null
+++ b/WebCore/khtml/editing/visible_text.cpp
@@ -0,0 +1,747 @@
+/*
+ * 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 "khtml_text_operations.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::Range;
+
+namespace khtml {
+
+const unsigned short nonBreakingSpace = 0xA0;
+
+// Iterates through the DOM range, returning all the text, and 0-length boundaries
+// at points where replaced elements break up the text flow.
+class TextIterator
+{
+public:
+ TextIterator();
+ explicit TextIterator(const DOM::Range &);
+
+ bool atEnd() const { return !m_positionNode; }
+ void advance();
+
+ long textLength() const { return m_textLength; }
+ const QChar *textCharacters() const { return m_textCharacters; }
+
+ DOM::Range position() const;
+
+private:
+ void exitNode();
+ bool handleTextNode();
+ bool handleReplacedElement();
+ bool handleNonTextNode();
+ void handleTextBox();
+ void emitCharacter(QChar, DOM::NodeImpl *textNode, long textStartOffset, long textEndOffset);
+
+ // Current position, not necessarily of the text being returned, but position
+ // as we walk through the DOM tree.
+ DOM::NodeImpl *m_node;
+ long m_offset;
+ bool m_handledNode;
+ bool m_handledChildren;
+
+ // End of the range.
+ DOM::NodeImpl *m_endNode;
+ long m_endOffset;
+
+ // The current text and its position, in the form to be returned from the iterator.
+ DOM::NodeImpl *m_positionNode;
+ long m_positionStartOffset;
+ long m_positionEndOffset;
+ const QChar *m_textCharacters;
+ long m_textLength;
+
+ // Used when there is still some pending text from the current node; when these
+ // are false and 0, we go back to normal iterating.
+ bool m_needAnotherNewline;
+ InlineTextBox *m_textBox;
+
+ // Used to do the whitespace collapsing logic.
+ DOM::NodeImpl *m_lastTextNode;
+ bool m_lastTextNodeEndedWithCollapsedSpace;
+ QChar m_lastCharacter;
+
+ // Used for whitespace characters that aren't in the DOM, so we can point at them.
+ QChar m_singleCharacterBuffer;
+};
+
+// Builds on the text iterator, adding a character position so we can walk one
+// character at a time, or faster, as needed. Useful for searching.
+class CharacterIterator {
+public:
+ CharacterIterator();
+ explicit CharacterIterator(const DOM::Range &r);
+
+ void advance(long numCharacters);
+
+ bool atBreak() const { return m_atBreak; }
+ bool atEnd() const { return m_textIterator.atEnd(); }
+
+ long numCharacters() const { return m_textIterator.textLength() - m_runOffset; }
+ const QChar *characters() const { return m_textIterator.textCharacters() + m_runOffset; }
+
+ long characterOffset() const { return m_offset; }
+ Range position() const;
+
+private:
+ long m_offset;
+ long m_runOffset;
+ bool m_atBreak;
+
+ TextIterator m_textIterator;
+};
+
+// Buffer that knows how to compare with a search target.
+// Keeps enough of the previous text to be able to search in the future,
+// but no more.
+class CircularSearchBuffer {
+public:
+ CircularSearchBuffer(const QString &target, bool isCaseSensitive);
+ ~CircularSearchBuffer() { free(m_buffer); }
+
+ void clear() { m_cursor = m_buffer; m_bufferFull = false; }
+ void append(long length, const QChar *characters);
+ void append(const QChar &);
+
+ long neededCharacters() const;
+ bool isMatch() const;
+ long length() const { return m_target.length(); }
+
+private:
+ QString m_target;
+ bool m_isCaseSensitive;
+
+ QChar *m_buffer;
+ QChar *m_cursor;
+ bool m_bufferFull;
+
+ CircularSearchBuffer(const CircularSearchBuffer&);
+ CircularSearchBuffer &operator=(const CircularSearchBuffer&);
+};
+
+TextIterator::TextIterator() : m_positionNode(0)
+{
+}
+
+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_positionNode = 0;
+ return;
+ }
+
+ NodeImpl *startNode = r.startContainer().handle();
+ NodeImpl *endNode = r.endContainer().handle();
+ long startOffset = r.startOffset();
+ long endOffset = r.endOffset();
+
+ if (!offsetInCharacters(startNode->nodeType())) {
+ if (startOffset >= 0 && startOffset < static_cast<long>(startNode->childNodeCount())) {
+ startNode = startNode->childNode(startOffset);
+ startOffset = 0;
+ }
+ }
+ if (!offsetInCharacters(endNode->nodeType())) {
+ if (endOffset > 0 && endOffset <= static_cast<long>(endNode->childNodeCount())) {
+ endNode = endNode->childNode(endOffset - 1);
+ endOffset = LONG_MAX;
+ }
+ }
+
+ m_node = startNode;
+ m_offset = startOffset;
+ m_handledNode = false;
+ m_handledChildren = false;
+
+ m_endNode = endNode;
+ m_endOffset = endOffset;
+
+ m_needAnotherNewline = false;
+ m_textBox = 0;
+
+ m_lastTextNode = 0;
+ m_lastTextNodeEndedWithCollapsedSpace = false;
+ m_lastCharacter = '\n';
+
+#ifndef NDEBUG
+ // Need this just because of the assert.
+ m_positionNode = startNode;
+#endif
+
+ advance();
+}
+
+void TextIterator::advance()
+{
+ assert(m_positionNode);
+
+ m_positionNode = 0;
+ m_textLength = 0;
+
+ if (m_needAnotherNewline) {
+ // Emit the newline, with position a collapsed range at the end of current node.
+ long offset = m_node->nodeIndex();
+ emitCharacter('\n', m_node->parentNode(), offset + 1, offset + 1);
+ m_needAnotherNewline = false;
+ return;
+ }
+
+ if (m_textBox) {
+ handleTextBox();
+ if (m_positionNode) {
+ return;
+ }
+ }
+
+ while (m_node) {
+ if (!m_handledNode) {
+ RenderObject *renderer = m_node->renderer();
+ if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) {
+ // FIXME: What about CDATA_SECTION_NODE?
+ if (renderer->style()->visibility() == VISIBLE) {
+ m_handledNode = handleTextNode();
+ }
+ } else if (renderer && (renderer->isImage() || renderer->isWidget())) {
+ if (renderer->style()->visibility() == VISIBLE) {
+ m_handledNode = handleReplacedElement();
+ }
+ } else {
+ m_handledNode = handleNonTextNode();
+ }
+ if (m_positionNode) {
+ return;
+ }
+ }
+
+ NodeImpl *next = m_handledChildren ? 0 : m_node->firstChild();
+ m_offset = 0;
+ if (!next && m_node != m_endNode) {
+ next = m_node->nextSibling();
+ while (!next && m_node->parentNode()) {
+ m_node = m_node->parentNode();
+ exitNode();
+ if (m_positionNode) {
+ m_handledNode = true;
+ m_handledChildren = true;
+ return;
+ }
+ if (m_node == m_endNode) {
+ break;
+ }
+ next = m_node->nextSibling();
+ }
+ }
+
+ m_node = next;
+ m_handledNode = false;
+ m_handledChildren = false;
+
+ if (m_positionNode) {
+ return;
+ }
+ }
+}
+
+bool TextIterator::handleTextNode()
+{
+ m_lastTextNode = m_node;
+
+ RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
+ DOMString str = m_node->nodeValue();
+
+ if (renderer->style()->whiteSpace() == khtml::PRE) {
+ long runStart = m_offset;
+ if (m_lastTextNodeEndedWithCollapsedSpace) {
+ emitCharacter(' ', m_node, runStart, runStart);
+ return false;
+ }
+ long strLength = str.length();
+ long end = (m_node == m_endNode) ? m_endOffset : LONG_MAX;
+ long runEnd = kMin(strLength, end);
+
+ m_positionNode = m_node;
+ m_positionStartOffset = runStart;
+ m_positionEndOffset = runEnd;
+ m_textCharacters = str.unicode() + runStart;
+ m_textLength = runEnd - runStart;
+
+ m_lastCharacter = str[runEnd - 1];
+
+ return true;
+ }
+
+ if (!renderer->firstTextBox() && str.length() > 0) {
+ m_lastTextNodeEndedWithCollapsedSpace = true; // entire block is collapsed space
+ return true;
+ }
+
+ m_textBox = renderer->firstTextBox();
+ handleTextBox();
+ return true;
+}
+
+void TextIterator::handleTextBox()
+{
+ RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
+ DOMString str = m_node->nodeValue();
+ long start = m_offset;
+ long end = (m_node == m_endNode) ? m_endOffset : LONG_MAX;
+ for (; m_textBox; m_textBox = m_textBox->nextTextBox()) {
+ long textBoxStart = m_textBox->m_start;
+ long runStart = kMax(textBoxStart, start);
+
+ // Check for collapsed space at the start of this run.
+ bool needSpace = m_lastTextNodeEndedWithCollapsedSpace
+ || (m_textBox == renderer->firstTextBox() && textBoxStart == runStart && runStart > 0);
+ if (needSpace && !m_lastCharacter.isSpace()) {
+ emitCharacter(' ', m_node, runStart, runStart);
+ return;
+ }
+
+ long textBoxEnd = textBoxStart + m_textBox->m_len;
+ long runEnd = kMin(textBoxEnd, end);
+
+ if (runStart < runEnd) {
+ // Handle either a single newline character (which becomes a space),
+ // or a run of characters that does not include a newline.
+ // This effectively translates newlines to spaces without copying the text.
+ if (str[runStart] == '\n') {
+ emitCharacter(' ', m_node, runStart, runStart + 1);
+ m_offset = runStart + 1;
+ } else {
+ long subrunEnd = str.find('\n', runStart);
+ if (subrunEnd == -1 || subrunEnd > runEnd) {
+ subrunEnd = runEnd;
+ }
+
+ m_offset = subrunEnd;
+
+ m_positionNode = m_node;
+ m_positionStartOffset = runStart;
+ m_positionEndOffset = subrunEnd;
+ m_textCharacters = str.unicode() + runStart;
+ m_textLength = subrunEnd - runStart;
+
+ m_lastCharacter = str[subrunEnd - 1];
+ }
+
+ // If we are doing a subrun that doesn't go to the end of the text box,
+ // come back again to finish handling this text box; don't advance to the next one.
+ if (m_positionEndOffset < runEnd) {
+ return;
+ }
+
+ // Advance to the next text box.
+ InlineTextBox *nextTextBox = m_textBox->nextTextBox();
+ long nextRunStart = nextTextBox ? nextTextBox->m_start : str.length();
+ if (nextRunStart > runEnd) {
+ m_lastTextNodeEndedWithCollapsedSpace = true; // collapsed space between runs or at the end
+ }
+ m_textBox = nextTextBox;
+ return;
+ }
+ }
+}
+
+bool TextIterator::handleReplacedElement()
+{
+ if (m_lastTextNodeEndedWithCollapsedSpace) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter(' ', m_lastTextNode->parentNode(), offset + 1, offset + 1);
+ return false;
+ }
+
+ long offset = m_node->nodeIndex();
+
+ m_positionNode = m_node->parentNode();
+ m_positionStartOffset = offset;
+ m_positionEndOffset = offset + 1;
+
+ m_textCharacters = 0;
+ m_textLength = 0;
+
+ m_lastCharacter = 0;
+
+ return true;
+}
+
+bool TextIterator::handleNonTextNode()
+{
+ switch (m_node->id()) {
+ case ID_BR: {
+ long offset = m_node->nodeIndex();
+ emitCharacter('\n', m_node->parentNode(), offset, offset + 1);
+ break;
+ }
+
+ case ID_TD:
+ case ID_TH:
+ if (m_lastCharacter != '\n' && m_lastTextNode) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter('\t', m_lastTextNode->parentNode(), offset, offset + 1);
+ }
+ break;
+
+ case ID_BLOCKQUOTE:
+ case ID_DD:
+ case ID_DIV:
+ case ID_DL:
+ case ID_DT:
+ case ID_H1:
+ case ID_H2:
+ case ID_H3:
+ case ID_H4:
+ case ID_H5:
+ case ID_H6:
+ case ID_HR:
+ case ID_LI:
+ case ID_OL:
+ case ID_P:
+ case ID_PRE:
+ case ID_TR:
+ case ID_UL:
+ if (m_lastCharacter != '\n' && m_lastTextNode) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter('\n', m_lastTextNode->parentNode(), offset, offset + 1);
+ }
+ break;
+ }
+
+ return true;
+}
+
+void TextIterator::exitNode()
+{
+ bool endLine = false;
+ bool addNewline = false;
+
+ switch (m_node->id()) {
+ case ID_BLOCKQUOTE:
+ case ID_DD:
+ case ID_DIV:
+ case ID_DL:
+ case ID_DT:
+ case ID_HR:
+ case ID_LI:
+ case ID_OL:
+ case ID_PRE:
+ case ID_TR:
+ case ID_UL:
+ endLine = true;
+ break;
+
+ case ID_H1:
+ case ID_H2:
+ case ID_H3:
+ case ID_H4:
+ case ID_H5:
+ case ID_H6:
+ case ID_P:
+ endLine = true;
+ addNewline = true;
+ break;
+ }
+
+ if (endLine && m_lastCharacter != '\n' && m_lastTextNode) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter('\n', m_lastTextNode->parentNode(), offset, offset + 1);
+ m_needAnotherNewline = addNewline;
+ } else if (addNewline && m_lastTextNode) {
+ long offset = m_node->childNodeCount();
+ emitCharacter('\n', m_node, offset, offset);
+ }
+}
+
+void TextIterator::emitCharacter(QChar c, NodeImpl *textNode, long textStartOffset, long textEndOffset)
+{
+ m_singleCharacterBuffer = c;
+ m_positionNode = textNode;
+ m_positionStartOffset = textStartOffset;
+ m_positionEndOffset = textEndOffset;
+ m_textCharacters = &m_singleCharacterBuffer;
+ m_textLength = 1;
+
+ m_lastTextNodeEndedWithCollapsedSpace = false;
+ m_lastCharacter = c;
+}
+
+Range TextIterator::position() const
+{
+ assert(m_positionNode);
+ return Range(m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset);
+}
+
+CharacterIterator::CharacterIterator()
+ : m_offset(0), m_runOffset(0), m_atBreak(true)
+{
+}
+
+CharacterIterator::CharacterIterator(const Range &r)
+ : m_offset(0), m_runOffset(0), m_atBreak(true), m_textIterator(r)
+{
+ while (!atEnd() && m_textIterator.textLength() == 0) {
+ m_textIterator.advance();
+ }
+}
+
+Range CharacterIterator::position() const
+{
+ Range r = m_textIterator.position();
+ if (m_textIterator.textLength() <= 1) {
+ assert(m_runOffset == 0);
+ } else {
+ Node n = r.startContainer();
+ assert(n == r.endContainer());
+ long offset = r.startOffset() + m_runOffset;
+ r.setStart(n, offset);
+ r.setEnd(n, offset + 1);
+ }
+ return r;
+}
+
+void CharacterIterator::advance(long count)
+{
+ assert(!atEnd());
+
+ m_atBreak = false;
+
+ long remaining = m_textIterator.textLength() - m_runOffset;
+ if (count < remaining) {
+ m_runOffset += count;
+ m_offset += count;
+ return;
+ }
+
+ count -= remaining;
+ m_offset += remaining;
+ for (m_textIterator.advance(); !atEnd(); m_textIterator.advance()) {
+ long runLength = m_textIterator.textLength();
+ if (runLength == 0) {
+ m_atBreak = true;
+ } else {
+ if (count < runLength) {
+ m_runOffset = count;
+ m_offset += count;
+ return;
+ }
+ count -= runLength;
+ m_offset += runLength;
+ }
+ }
+
+ m_atBreak = true;
+ m_runOffset = 0;
+}
+
+CircularSearchBuffer::CircularSearchBuffer(const QString &s, bool isCaseSensitive)
+ : m_target(s)
+{
+ assert(!s.isEmpty());
+
+ if (!isCaseSensitive) {
+ m_target = s.lower();
+ }
+ m_target.replace(nonBreakingSpace, ' ');
+ m_isCaseSensitive = isCaseSensitive;
+
+ m_buffer = static_cast<QChar *>(malloc(s.length() * sizeof(QChar)));
+ m_cursor = m_buffer;
+ m_bufferFull = false;
+}
+
+void CircularSearchBuffer::append(const QChar &c)
+{
+ if (m_isCaseSensitive) {
+ *m_cursor++ = c.unicode() == nonBreakingSpace ? ' ' : c.unicode();
+ } else {
+ *m_cursor++ = c.unicode() == nonBreakingSpace ? ' ' : c.lower().unicode();
+ }
+ if (m_cursor == m_buffer + length()) {
+ m_cursor = m_buffer;
+ m_bufferFull = true;
+ }
+}
+
+// This function can only be used when the buffer is not yet full,
+// and when then count is small enough to fit in the buffer.
+// No need for a more general version for the search algorithm.
+void CircularSearchBuffer::append(long count, const QChar *characters)
+{
+ long tailSpace = m_buffer + length() - m_cursor;
+
+ assert(!m_bufferFull);
+ assert(count <= tailSpace);
+
+ if (m_isCaseSensitive) {
+ for (long i = 0; i != count; ++i) {
+ QChar c = characters[i];
+ m_cursor[i] = c.unicode() == nonBreakingSpace ? ' ' : c.unicode();
+ }
+ } else {
+ for (long i = 0; i != count; ++i) {
+ QChar c = characters[i];
+ m_cursor[i] = c.unicode() == nonBreakingSpace ? ' ' : c.lower().unicode();
+ }
+ }
+ if (count < tailSpace) {
+ m_cursor += count;
+ } else {
+ m_bufferFull = true;
+ m_cursor = m_buffer;
+ }
+}
+
+long CircularSearchBuffer::neededCharacters() const
+{
+ return m_bufferFull ? 0 : m_buffer + length() - m_cursor;
+}
+
+bool CircularSearchBuffer::isMatch() const
+{
+ assert(m_bufferFull);
+
+ long headSpace = m_cursor - m_buffer;
+ long tailSpace = length() - headSpace;
+ return memcmp(m_cursor, m_target.unicode(), tailSpace * sizeof(QChar)) == 0
+ && memcmp(m_buffer, m_target.unicode() + tailSpace, headSpace * sizeof(QChar)) == 0;
+}
+
+QString plainText(const Range &r)
+{
+ // Allocate string at the right size, rather than building it up by successive append calls.
+ long length = 0;
+ for (TextIterator it(r); !it.atEnd(); it.advance()) {
+ length += it.textLength();
+ }
+ QString result("");
+ result.reserve(length);
+ for (TextIterator it(r); !it.atEnd(); it.advance()) {
+ result.append(it.textCharacters(), it.textLength());
+ }
+ return result;
+}
+
+Range findPlainText(const Range &r, const QString &s, bool forward, bool caseSensitive)
+{
+ // FIXME: Can we do Boyer-Moore or equivalent instead for speed?
+
+ // FIXME: This code does not allow \n at the moment because of issues with <br>.
+ // Once we fix those, we can remove this check.
+ if (s.isEmpty() || s.find('\n') != -1) {
+ Range result = r;
+ result.collapse(forward);
+ return result;
+ }
+
+ CircularSearchBuffer buffer(s, caseSensitive);
+
+ bool found = false;
+ CharacterIterator rangeEnd;
+
+ {
+ CharacterIterator it(r);
+ while (!it.atEnd()) {
+ // Fill the buffer.
+ while (long needed = buffer.neededCharacters()) {
+ long available = it.numCharacters();
+ long runLength = kMin(needed, available);
+ buffer.append(runLength, it.characters());
+ it.advance(runLength);
+ if (it.atBreak()) {
+ if (it.atEnd()) {
+ goto done;
+ }
+ buffer.clear();
+ }
+ }
+
+ // Do the search.
+ do {
+ if (buffer.isMatch()) {
+ // Compute the range for the result.
+ found = true;
+ rangeEnd = it;
+ // If searching forward, stop on the first match.
+ // If searching backward, don't stop, so we end up with the last match.
+ if (forward) {
+ goto done;
+ }
+ }
+ buffer.append(it.characters()[0]);
+ it.advance(1);
+ } while (!it.atBreak());
+ buffer.clear();
+ }
+ }
+
+done:
+ Range result = r;
+ if (!found) {
+ result.collapse(!forward);
+ } else {
+ CharacterIterator it(r);
+ it.advance(rangeEnd.characterOffset() - buffer.length());
+ result.setStart(it.position().startContainer(), it.position().startOffset());
+ it.advance(buffer.length() - 1);
+ result.setEnd(it.position().endContainer(), it.position().endOffset());
+ }
+ return result;
+}
+
+}
diff --git a/JavaScriptCore/bindings/objc/objc_header.h b/WebCore/khtml/editing/visible_text.h
similarity index 69%
copy from JavaScriptCore/bindings/objc/objc_header.h
copy to WebCore/khtml/editing/visible_text.h
index 2dd7a26..3e8a705 100644
--- a/JavaScriptCore/bindings/objc/objc_header.h
+++ b/WebCore/khtml/editing/visible_text.h
@@ -22,26 +22,22 @@
* (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 _BINDINGS_OBJC_HEADER_H_
-#define _BINDINGS_OBJC_HEADER_H_
-#ifdef __OBJC__
-#include <objc/objc-class.h>
-#include <objc/objc-runtime.h>
+#ifndef __khtml_text_iterator_h__
+#define __khtml_text_iterator_h__
-typedef struct objc_class *ClassStructPtr;
-typedef struct objc_object *ObjectStructPtr;
+#include <dom/dom2_range.h>
- at class NSMethodSignature;
+// FIXME: This class should probably use the render tree and not the DOM tree, since elements could
+// be hidden using CSS, or additional generated content could be added. For now, we just make sure
+// text objects walk their renderers' InlineTextBox objects, so that we at least get the whitespace
+// stripped out properly and obey CSS visibility for text runs.
-#else
+namespace khtml {
-typedef struct objc_ivar {} *Ivar;
-typedef struct objc_class {} *ClassStructPtr;
-typedef struct objc_object {} *ObjectStructPtr;
+QString plainText(const DOM::Range &);
+DOM::Range findPlainText(const DOM::Range &, const QString &, bool forward, bool caseSensitive);
-class NSMethodSignature;
+}
#endif
-
-#endif
\ No newline at end of file
diff --git a/WebCore/khtml/khtml_part.cpp b/WebCore/khtml/khtml_part.cpp
index 1383c3e..afb036a 100644
--- a/WebCore/khtml/khtml_part.cpp
+++ b/WebCore/khtml/khtml_part.cpp
@@ -7,7 +7,7 @@
* 2000 Simon Hausmann <hausmann at kde.org>
* 2000 Stefan Schimanski <1Stein at gmx.de>
* 2001 George Staikos <staikos at kde.org>
- * Copyright (C) 2003 Apple Computer, Inc.
+ * Copyright (C) 2004 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -53,6 +53,8 @@
#include "xml/xml_tokenizer.h"
#include "css/cssstyleselector.h"
#include "css/csshelper.h"
+#include "misc/khtml_text_operations.h"
+
using namespace DOM;
#include "khtmlview.h"
@@ -95,7 +97,7 @@ using namespace DOM;
#include "khtmlpart_p.h"
-#ifdef APPLE_CHANGES
+#if APPLE_CHANGES
#include <CoreServices/CoreServices.h>
#endif
@@ -104,6 +106,7 @@ using khtml::DeleteSelectionCommand;
using khtml::EditCommand;
using khtml::InlineTextBox;
using khtml::PasteMarkupCommand;
+using khtml::plainText;
using khtml::RenderObject;
using khtml::RenderText;
using khtml::Tokenizer;
@@ -274,7 +277,9 @@ void KHTMLPart::init( KHTMLView *view, GUIProfile prof )
connect( khtml::Cache::loader(), SIGNAL( requestFailed( khtml::DocLoader*, khtml::CachedObject *) ),
this, SLOT( slotLoaderRequestDone( khtml::DocLoader*, khtml::CachedObject *) ) );
+#if !APPLE_CHANGES
findTextBegin(); //reset find variables
+#endif
connect( &d->m_redirectionTimer, SIGNAL( timeout() ),
this, SLOT( slotRedirect() ) );
@@ -977,10 +982,10 @@ void KHTMLPart::clear()
(*it).m_run->abort();
}
}
-#endif
findTextBegin(); // resets d->m_findNode and d->m_findPos
+#endif
d->m_mousePressNode = DOM::Node();
@@ -1058,9 +1063,11 @@ void KHTMLPart::clear()
connect( kapp->clipboard(), SIGNAL( selectionChanged()), SLOT( slotClearSelection()));
#endif
+#if !APPLE_CHANGES
d->m_totalObjectCount = 0;
d->m_loadedObjects = 0;
d->m_jobPercent = 0;
+#endif
if ( !d->m_haveEncoding )
d->m_encoding = QString::null;
@@ -2165,6 +2172,8 @@ bool KHTMLPart::inEditMode() const
return editMode() == FlagEnabled;
}
+#if !APPLE_CHANGES
+
void KHTMLPart::findTextBegin(NodeImpl *startNode, int startPos)
{
d->m_findPos = startPos;
@@ -2203,7 +2212,6 @@ bool KHTMLPart::findTextNext( const QString &str, bool forward, bool caseSensiti
QConstString s(t->s, t->l);
int matchLen = 0;
-#if !APPLE_CHANGES
if ( isRegExp ) {
QRegExp matcher( str );
matcher.setCaseSensitive( caseSensitive );
@@ -2215,31 +2223,13 @@ bool KHTMLPart::findTextNext( const QString &str, bool forward, bool caseSensiti
d->m_findPos = s.string().find(str, d->m_findPos+1, caseSensitive);
matchLen = str.length();
}
-#else
- if (forward) {
- d->m_findPos = s.string().find(str, d->m_findPos+1, caseSensitive);
- } else {
- if (d->m_findPos == -1) {
- // search from end of node
- d->m_findPos = s.string().findRev(str, -1, caseSensitive);
- } else if (d->m_findPos != 0) {
- d->m_findPos = s.string().findRev(str, d->m_findPos-1, caseSensitive);
- } else {
- // already at start of this node, on to the next node
- d->m_findPos = -1;
- }
- }
- matchLen = str.length();
-#endif
if(d->m_findPos != -1)
{
-#if !APPLE_CHANGES
int x = 0, y = 0;
static_cast<khtml::RenderText *>(d->m_findNode->renderer())
->posOfChar(d->m_findPos, x, y);
d->m_view->setContentsPos(x-50, y-50);
-#endif
Position p1(d->m_findNode, d->m_findPos);
Position p2(d->m_findNode, d->m_findPos + matchLen);
setSelection(Selection(p1, p2));
@@ -2282,188 +2272,11 @@ bool KHTMLPart::findTextNext( const QString &str, bool forward, bool caseSensiti
}
}
+#endif // APPLE_CHANGES
+
QString KHTMLPart::text(const DOM::Range &r) const
{
- // FIXME: This whole function should use the render tree and not the DOM tree, since elements could
- // be hidden using CSS, or additional generated content could be added. For now, we just make sure
- // text objects walk their renderers' InlineTextBox objects, so that we at least get the whitespace
- // stripped out properly and obey CSS visibility for text runs.
-
- if (r.isNull())
- return QString();
-
- bool hasNewLine = true;
- bool addedSpace = true;
- bool needSpace = false;
- QString text;
- DOM::Node startNode = r.startContainer();
- DOM::Node endNode = r.endContainer();
- int startOffset = r.startOffset();
- int endOffset = r.endOffset();
- if (!startNode.isNull() && startNode.nodeType() == Node::ELEMENT_NODE) {
- if (startOffset >= 0 && startOffset < (int)startNode.childNodes().length()) {
- startNode = startNode.childNodes().item(r.startOffset());
- startOffset = -1;
- }
- }
- if (!endNode.isNull() && endNode.nodeType() == Node::ELEMENT_NODE) {
- if (endOffset > 0 && endOffset <= (int)endNode.childNodes().length()) {
- endNode = endNode.childNodes().item(endOffset - 1);
- endOffset = -1;
- }
- }
-
- DOM::Node n = startNode;
- while(!n.isNull()) {
- if(n.nodeType() == DOM::Node::TEXT_NODE) {
- if (hasNewLine) {
- addedSpace = true;
- needSpace = false;
- hasNewLine = false;
- }
- QString str = n.nodeValue().string();
- int start = (n == startNode) ? startOffset : -1;
- int end = (n == endNode) ? endOffset : -1;
- RenderObject* renderer = n.handle()->renderer();
- if (renderer && renderer->isText()) {
- if (renderer->style()->whiteSpace() == khtml::PRE) {
- if (needSpace && !addedSpace)
- text += ' ';
- int runStart = (start == -1) ? 0 : start;
- int runEnd = (end == -1) ? str.length() : end;
- text += str.mid(runStart, runEnd-runStart);
- needSpace = false;
- addedSpace = str[runEnd-1].direction() == QChar::DirWS;
- }
- else {
- RenderText* textObj = static_cast<RenderText*>(n.handle()->renderer());
- if (!textObj->firstTextBox() && str.length() > 0) {
- // We have no runs, but we do have a length. This means we must be
- // whitespace that collapsed away at the end of a line.
- needSpace = true;
- }
- else {
- for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) {
- int runStart = (start == -1) ? box->m_start : start;
- int runEnd = (end == -1) ? box->m_start + box->m_len : end;
- runEnd = QMIN(runEnd, box->m_start + box->m_len);
- if (runStart >= box->m_start &&
- runStart < box->m_start + box->m_len) {
- if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0)
- needSpace = true; // collapsed space at the start
- if (needSpace && !addedSpace)
- text += ' ';
- QString runText = str.mid(runStart, runEnd - runStart);
- runText.replace('\n', ' ');
- text += runText;
- int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end
- needSpace = nextRunStart > runEnd; // collapsed space between runs or at the end
- addedSpace = str[runEnd-1].direction() == QChar::DirWS;
- start = -1;
- }
- if (end != -1 && runEnd >= end)
- break;
- }
- }
- }
- }
- }
- else {
- // This is our simple HTML -> ASCII transformation:
- unsigned short id = n.elementId();
- switch(id) {
- case ID_BR:
- text += "\n";
- hasNewLine = true;
- break;
-
- case ID_TD:
- case ID_TH:
- case ID_HR:
- case ID_OL:
- case ID_UL:
- case ID_LI:
- case ID_DD:
- case ID_DL:
- case ID_DT:
- case ID_PRE:
- case ID_BLOCKQUOTE:
- case ID_DIV:
- if (!hasNewLine)
- text += "\n";
- hasNewLine = true;
- break;
- case ID_P:
- case ID_TR:
- case ID_H1:
- case ID_H2:
- case ID_H3:
- case ID_H4:
- case ID_H5:
- case ID_H6:
- if (!hasNewLine)
- text += "\n";
- text += "\n";
- hasNewLine = true;
- break;
- }
- }
- if(n == endNode) break;
- DOM::Node next = n.firstChild();
- if(next.isNull()) next = n.nextSibling();
- while( next.isNull() && !n.parentNode().isNull() ) {
- n = n.parentNode();
- if(n == endNode) break;
- next = n.nextSibling();
- unsigned short id = n.elementId();
- switch(id) {
- case ID_TD:
- case ID_TH:
- case ID_HR:
- case ID_OL:
- case ID_UL:
- case ID_LI:
- case ID_DD:
- case ID_DL:
- case ID_DT:
- case ID_PRE:
- case ID_BLOCKQUOTE:
- case ID_DIV:
- if (!hasNewLine)
- text += "\n";
- hasNewLine = true;
- break;
- case ID_P:
- case ID_TR:
- case ID_H1:
- case ID_H2:
- case ID_H3:
- case ID_H4:
- case ID_H5:
- case ID_H6:
- if (!hasNewLine)
- text += "\n";
- // An extra newline is needed at the start, not the end, of these types of tags,
- // so don't add another here.
- hasNewLine = true;
- break;
- }
- }
-
- n = next;
- }
- int start = 0;
- int end = text.length();
-
- // Strip leading LFs
- while ((start < end) && (text[start] == '\n'))
- start++;
-
- // Strip excessive trailing LFs
- while ((start < (end-1)) && (text[end-1] == '\n') && (text[end-2] == '\n'))
- end--;
-
- return text.mid(start, end-start);
+ return plainText(r);
}
QString KHTMLPart::selectedText() const
@@ -2557,7 +2370,7 @@ void KHTMLPart::setFocusNodeIfNeeded(const Selection &s)
return;
NodeImpl *n = s.start().node();
- NodeImpl *target = n->isContentEditable() ? n : 0;
+ NodeImpl *target = (n && n->isContentEditable()) ? n : 0;
if (!target) {
while (n && n != s.end().node()) {
if (n->isContentEditable()) {
diff --git a/WebCore/khtml/khtmlpart_p.h b/WebCore/khtml/khtmlpart_p.h
index 5407e85..bb4d65f 100644
--- a/WebCore/khtml/khtmlpart_p.h
+++ b/WebCore/khtml/khtmlpart_p.h
@@ -9,7 +9,7 @@
* 2000-2001 Simon Hausmann <hausmann at kde.org>
* 2000-2001 Dirk Mueller <mueller at kde.org>
* 2000 Stefan Schimanski <1Stein at gmx.de>
- * Copyright (C) 2003 Apple Computer, Inc.
+ * Copyright (C) 2004 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -123,10 +123,10 @@ public:
m_bDnd = true;
#if !APPLE_CHANGES
m_linkCursor = KCursor::handCursor();
-#endif
m_loadedObjects = 0;
m_totalObjectCount = 0;
m_jobPercent = 0;
+#endif
m_haveEncoding = false;
m_activeFrame = 0L;
#if !APPLE_CHANGES
@@ -322,8 +322,10 @@ public:
int m_zoomFactor;
+#if !APPLE_CHANGES
int m_findPos;
DOM::NodeImpl *m_findNode;
+#endif
QString m_strSelectedURL;
QString m_strSelectedURLTarget;
@@ -348,8 +350,10 @@ public:
DOM::Selection::ETextGranularity m_textElement;
bool m_mouseMovedSinceLastMousePress:1;
#endif
+#if !APPLE_CHANGES
QString m_overURL;
QString m_overURLTarget;
+#endif
DOM::Selection m_selection;
int m_caretBlinkTimer;
@@ -377,14 +381,12 @@ public:
#if !APPLE_CHANGES
QCursor m_linkCursor;
-#endif
QTimer m_scrollTimer;
unsigned long m_loadedObjects;
unsigned long m_totalObjectCount;
unsigned int m_jobPercent;
-#if !APPLE_CHANGES
KHTMLFind *m_findDialog;
struct findState
diff --git a/WebCore/khtml/misc/khtml_text_operations.cpp b/WebCore/khtml/misc/khtml_text_operations.cpp
new file mode 100644
index 0000000..98f2e44
--- /dev/null
+++ b/WebCore/khtml/misc/khtml_text_operations.cpp
@@ -0,0 +1,747 @@
+/*
+ * 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 "khtml_text_operations.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::Range;
+
+namespace khtml {
+
+const unsigned short nonBreakingSpace = 0xA0;
+
+// Iterates through the DOM range, returning all the text, and 0-length boundaries
+// at points where replaced elements break up the text flow.
+class TextIterator
+{
+public:
+ TextIterator();
+ explicit TextIterator(const DOM::Range &);
+
+ bool atEnd() const { return !m_positionNode; }
+ void advance();
+
+ long textLength() const { return m_textLength; }
+ const QChar *textCharacters() const { return m_textCharacters; }
+
+ DOM::Range position() const;
+
+private:
+ void exitNode();
+ bool handleTextNode();
+ bool handleReplacedElement();
+ bool handleNonTextNode();
+ void handleTextBox();
+ void emitCharacter(QChar, DOM::NodeImpl *textNode, long textStartOffset, long textEndOffset);
+
+ // Current position, not necessarily of the text being returned, but position
+ // as we walk through the DOM tree.
+ DOM::NodeImpl *m_node;
+ long m_offset;
+ bool m_handledNode;
+ bool m_handledChildren;
+
+ // End of the range.
+ DOM::NodeImpl *m_endNode;
+ long m_endOffset;
+
+ // The current text and its position, in the form to be returned from the iterator.
+ DOM::NodeImpl *m_positionNode;
+ long m_positionStartOffset;
+ long m_positionEndOffset;
+ const QChar *m_textCharacters;
+ long m_textLength;
+
+ // Used when there is still some pending text from the current node; when these
+ // are false and 0, we go back to normal iterating.
+ bool m_needAnotherNewline;
+ InlineTextBox *m_textBox;
+
+ // Used to do the whitespace collapsing logic.
+ DOM::NodeImpl *m_lastTextNode;
+ bool m_lastTextNodeEndedWithCollapsedSpace;
+ QChar m_lastCharacter;
+
+ // Used for whitespace characters that aren't in the DOM, so we can point at them.
+ QChar m_singleCharacterBuffer;
+};
+
+// Builds on the text iterator, adding a character position so we can walk one
+// character at a time, or faster, as needed. Useful for searching.
+class CharacterIterator {
+public:
+ CharacterIterator();
+ explicit CharacterIterator(const DOM::Range &r);
+
+ void advance(long numCharacters);
+
+ bool atBreak() const { return m_atBreak; }
+ bool atEnd() const { return m_textIterator.atEnd(); }
+
+ long numCharacters() const { return m_textIterator.textLength() - m_runOffset; }
+ const QChar *characters() const { return m_textIterator.textCharacters() + m_runOffset; }
+
+ long characterOffset() const { return m_offset; }
+ Range position() const;
+
+private:
+ long m_offset;
+ long m_runOffset;
+ bool m_atBreak;
+
+ TextIterator m_textIterator;
+};
+
+// Buffer that knows how to compare with a search target.
+// Keeps enough of the previous text to be able to search in the future,
+// but no more.
+class CircularSearchBuffer {
+public:
+ CircularSearchBuffer(const QString &target, bool isCaseSensitive);
+ ~CircularSearchBuffer() { free(m_buffer); }
+
+ void clear() { m_cursor = m_buffer; m_bufferFull = false; }
+ void append(long length, const QChar *characters);
+ void append(const QChar &);
+
+ long neededCharacters() const;
+ bool isMatch() const;
+ long length() const { return m_target.length(); }
+
+private:
+ QString m_target;
+ bool m_isCaseSensitive;
+
+ QChar *m_buffer;
+ QChar *m_cursor;
+ bool m_bufferFull;
+
+ CircularSearchBuffer(const CircularSearchBuffer&);
+ CircularSearchBuffer &operator=(const CircularSearchBuffer&);
+};
+
+TextIterator::TextIterator() : m_positionNode(0)
+{
+}
+
+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_positionNode = 0;
+ return;
+ }
+
+ NodeImpl *startNode = r.startContainer().handle();
+ NodeImpl *endNode = r.endContainer().handle();
+ long startOffset = r.startOffset();
+ long endOffset = r.endOffset();
+
+ if (!offsetInCharacters(startNode->nodeType())) {
+ if (startOffset >= 0 && startOffset < static_cast<long>(startNode->childNodeCount())) {
+ startNode = startNode->childNode(startOffset);
+ startOffset = 0;
+ }
+ }
+ if (!offsetInCharacters(endNode->nodeType())) {
+ if (endOffset > 0 && endOffset <= static_cast<long>(endNode->childNodeCount())) {
+ endNode = endNode->childNode(endOffset - 1);
+ endOffset = LONG_MAX;
+ }
+ }
+
+ m_node = startNode;
+ m_offset = startOffset;
+ m_handledNode = false;
+ m_handledChildren = false;
+
+ m_endNode = endNode;
+ m_endOffset = endOffset;
+
+ m_needAnotherNewline = false;
+ m_textBox = 0;
+
+ m_lastTextNode = 0;
+ m_lastTextNodeEndedWithCollapsedSpace = false;
+ m_lastCharacter = '\n';
+
+#ifndef NDEBUG
+ // Need this just because of the assert.
+ m_positionNode = startNode;
+#endif
+
+ advance();
+}
+
+void TextIterator::advance()
+{
+ assert(m_positionNode);
+
+ m_positionNode = 0;
+ m_textLength = 0;
+
+ if (m_needAnotherNewline) {
+ // Emit the newline, with position a collapsed range at the end of current node.
+ long offset = m_node->nodeIndex();
+ emitCharacter('\n', m_node->parentNode(), offset + 1, offset + 1);
+ m_needAnotherNewline = false;
+ return;
+ }
+
+ if (m_textBox) {
+ handleTextBox();
+ if (m_positionNode) {
+ return;
+ }
+ }
+
+ while (m_node) {
+ if (!m_handledNode) {
+ RenderObject *renderer = m_node->renderer();
+ if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) {
+ // FIXME: What about CDATA_SECTION_NODE?
+ if (renderer->style()->visibility() == VISIBLE) {
+ m_handledNode = handleTextNode();
+ }
+ } else if (renderer && (renderer->isImage() || renderer->isWidget())) {
+ if (renderer->style()->visibility() == VISIBLE) {
+ m_handledNode = handleReplacedElement();
+ }
+ } else {
+ m_handledNode = handleNonTextNode();
+ }
+ if (m_positionNode) {
+ return;
+ }
+ }
+
+ NodeImpl *next = m_handledChildren ? 0 : m_node->firstChild();
+ m_offset = 0;
+ if (!next && m_node != m_endNode) {
+ next = m_node->nextSibling();
+ while (!next && m_node->parentNode()) {
+ m_node = m_node->parentNode();
+ exitNode();
+ if (m_positionNode) {
+ m_handledNode = true;
+ m_handledChildren = true;
+ return;
+ }
+ if (m_node == m_endNode) {
+ break;
+ }
+ next = m_node->nextSibling();
+ }
+ }
+
+ m_node = next;
+ m_handledNode = false;
+ m_handledChildren = false;
+
+ if (m_positionNode) {
+ return;
+ }
+ }
+}
+
+bool TextIterator::handleTextNode()
+{
+ m_lastTextNode = m_node;
+
+ RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
+ DOMString str = m_node->nodeValue();
+
+ if (renderer->style()->whiteSpace() == khtml::PRE) {
+ long runStart = m_offset;
+ if (m_lastTextNodeEndedWithCollapsedSpace) {
+ emitCharacter(' ', m_node, runStart, runStart);
+ return false;
+ }
+ long strLength = str.length();
+ long end = (m_node == m_endNode) ? m_endOffset : LONG_MAX;
+ long runEnd = kMin(strLength, end);
+
+ m_positionNode = m_node;
+ m_positionStartOffset = runStart;
+ m_positionEndOffset = runEnd;
+ m_textCharacters = str.unicode() + runStart;
+ m_textLength = runEnd - runStart;
+
+ m_lastCharacter = str[runEnd - 1];
+
+ return true;
+ }
+
+ if (!renderer->firstTextBox() && str.length() > 0) {
+ m_lastTextNodeEndedWithCollapsedSpace = true; // entire block is collapsed space
+ return true;
+ }
+
+ m_textBox = renderer->firstTextBox();
+ handleTextBox();
+ return true;
+}
+
+void TextIterator::handleTextBox()
+{
+ RenderText *renderer = static_cast<RenderText *>(m_node->renderer());
+ DOMString str = m_node->nodeValue();
+ long start = m_offset;
+ long end = (m_node == m_endNode) ? m_endOffset : LONG_MAX;
+ for (; m_textBox; m_textBox = m_textBox->nextTextBox()) {
+ long textBoxStart = m_textBox->m_start;
+ long runStart = kMax(textBoxStart, start);
+
+ // Check for collapsed space at the start of this run.
+ bool needSpace = m_lastTextNodeEndedWithCollapsedSpace
+ || (m_textBox == renderer->firstTextBox() && textBoxStart == runStart && runStart > 0);
+ if (needSpace && !m_lastCharacter.isSpace()) {
+ emitCharacter(' ', m_node, runStart, runStart);
+ return;
+ }
+
+ long textBoxEnd = textBoxStart + m_textBox->m_len;
+ long runEnd = kMin(textBoxEnd, end);
+
+ if (runStart < runEnd) {
+ // Handle either a single newline character (which becomes a space),
+ // or a run of characters that does not include a newline.
+ // This effectively translates newlines to spaces without copying the text.
+ if (str[runStart] == '\n') {
+ emitCharacter(' ', m_node, runStart, runStart + 1);
+ m_offset = runStart + 1;
+ } else {
+ long subrunEnd = str.find('\n', runStart);
+ if (subrunEnd == -1 || subrunEnd > runEnd) {
+ subrunEnd = runEnd;
+ }
+
+ m_offset = subrunEnd;
+
+ m_positionNode = m_node;
+ m_positionStartOffset = runStart;
+ m_positionEndOffset = subrunEnd;
+ m_textCharacters = str.unicode() + runStart;
+ m_textLength = subrunEnd - runStart;
+
+ m_lastCharacter = str[subrunEnd - 1];
+ }
+
+ // If we are doing a subrun that doesn't go to the end of the text box,
+ // come back again to finish handling this text box; don't advance to the next one.
+ if (m_positionEndOffset < runEnd) {
+ return;
+ }
+
+ // Advance to the next text box.
+ InlineTextBox *nextTextBox = m_textBox->nextTextBox();
+ long nextRunStart = nextTextBox ? nextTextBox->m_start : str.length();
+ if (nextRunStart > runEnd) {
+ m_lastTextNodeEndedWithCollapsedSpace = true; // collapsed space between runs or at the end
+ }
+ m_textBox = nextTextBox;
+ return;
+ }
+ }
+}
+
+bool TextIterator::handleReplacedElement()
+{
+ if (m_lastTextNodeEndedWithCollapsedSpace) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter(' ', m_lastTextNode->parentNode(), offset + 1, offset + 1);
+ return false;
+ }
+
+ long offset = m_node->nodeIndex();
+
+ m_positionNode = m_node->parentNode();
+ m_positionStartOffset = offset;
+ m_positionEndOffset = offset + 1;
+
+ m_textCharacters = 0;
+ m_textLength = 0;
+
+ m_lastCharacter = 0;
+
+ return true;
+}
+
+bool TextIterator::handleNonTextNode()
+{
+ switch (m_node->id()) {
+ case ID_BR: {
+ long offset = m_node->nodeIndex();
+ emitCharacter('\n', m_node->parentNode(), offset, offset + 1);
+ break;
+ }
+
+ case ID_TD:
+ case ID_TH:
+ if (m_lastCharacter != '\n' && m_lastTextNode) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter('\t', m_lastTextNode->parentNode(), offset, offset + 1);
+ }
+ break;
+
+ case ID_BLOCKQUOTE:
+ case ID_DD:
+ case ID_DIV:
+ case ID_DL:
+ case ID_DT:
+ case ID_H1:
+ case ID_H2:
+ case ID_H3:
+ case ID_H4:
+ case ID_H5:
+ case ID_H6:
+ case ID_HR:
+ case ID_LI:
+ case ID_OL:
+ case ID_P:
+ case ID_PRE:
+ case ID_TR:
+ case ID_UL:
+ if (m_lastCharacter != '\n' && m_lastTextNode) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter('\n', m_lastTextNode->parentNode(), offset, offset + 1);
+ }
+ break;
+ }
+
+ return true;
+}
+
+void TextIterator::exitNode()
+{
+ bool endLine = false;
+ bool addNewline = false;
+
+ switch (m_node->id()) {
+ case ID_BLOCKQUOTE:
+ case ID_DD:
+ case ID_DIV:
+ case ID_DL:
+ case ID_DT:
+ case ID_HR:
+ case ID_LI:
+ case ID_OL:
+ case ID_PRE:
+ case ID_TR:
+ case ID_UL:
+ endLine = true;
+ break;
+
+ case ID_H1:
+ case ID_H2:
+ case ID_H3:
+ case ID_H4:
+ case ID_H5:
+ case ID_H6:
+ case ID_P:
+ endLine = true;
+ addNewline = true;
+ break;
+ }
+
+ if (endLine && m_lastCharacter != '\n' && m_lastTextNode) {
+ long offset = m_lastTextNode->nodeIndex();
+ emitCharacter('\n', m_lastTextNode->parentNode(), offset, offset + 1);
+ m_needAnotherNewline = addNewline;
+ } else if (addNewline && m_lastTextNode) {
+ long offset = m_node->childNodeCount();
+ emitCharacter('\n', m_node, offset, offset);
+ }
+}
+
+void TextIterator::emitCharacter(QChar c, NodeImpl *textNode, long textStartOffset, long textEndOffset)
+{
+ m_singleCharacterBuffer = c;
+ m_positionNode = textNode;
+ m_positionStartOffset = textStartOffset;
+ m_positionEndOffset = textEndOffset;
+ m_textCharacters = &m_singleCharacterBuffer;
+ m_textLength = 1;
+
+ m_lastTextNodeEndedWithCollapsedSpace = false;
+ m_lastCharacter = c;
+}
+
+Range TextIterator::position() const
+{
+ assert(m_positionNode);
+ return Range(m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset);
+}
+
+CharacterIterator::CharacterIterator()
+ : m_offset(0), m_runOffset(0), m_atBreak(true)
+{
+}
+
+CharacterIterator::CharacterIterator(const Range &r)
+ : m_offset(0), m_runOffset(0), m_atBreak(true), m_textIterator(r)
+{
+ while (!atEnd() && m_textIterator.textLength() == 0) {
+ m_textIterator.advance();
+ }
+}
+
+Range CharacterIterator::position() const
+{
+ Range r = m_textIterator.position();
+ if (m_textIterator.textLength() <= 1) {
+ assert(m_runOffset == 0);
+ } else {
+ Node n = r.startContainer();
+ assert(n == r.endContainer());
+ long offset = r.startOffset() + m_runOffset;
+ r.setStart(n, offset);
+ r.setEnd(n, offset + 1);
+ }
+ return r;
+}
+
+void CharacterIterator::advance(long count)
+{
+ assert(!atEnd());
+
+ m_atBreak = false;
+
+ long remaining = m_textIterator.textLength() - m_runOffset;
+ if (count < remaining) {
+ m_runOffset += count;
+ m_offset += count;
+ return;
+ }
+
+ count -= remaining;
+ m_offset += remaining;
+ for (m_textIterator.advance(); !atEnd(); m_textIterator.advance()) {
+ long runLength = m_textIterator.textLength();
+ if (runLength == 0) {
+ m_atBreak = true;
+ } else {
+ if (count < runLength) {
+ m_runOffset = count;
+ m_offset += count;
+ return;
+ }
+ count -= runLength;
+ m_offset += runLength;
+ }
+ }
+
+ m_atBreak = true;
+ m_runOffset = 0;
+}
+
+CircularSearchBuffer::CircularSearchBuffer(const QString &s, bool isCaseSensitive)
+ : m_target(s)
+{
+ assert(!s.isEmpty());
+
+ if (!isCaseSensitive) {
+ m_target = s.lower();
+ }
+ m_target.replace(nonBreakingSpace, ' ');
+ m_isCaseSensitive = isCaseSensitive;
+
+ m_buffer = static_cast<QChar *>(malloc(s.length() * sizeof(QChar)));
+ m_cursor = m_buffer;
+ m_bufferFull = false;
+}
+
+void CircularSearchBuffer::append(const QChar &c)
+{
+ if (m_isCaseSensitive) {
+ *m_cursor++ = c.unicode() == nonBreakingSpace ? ' ' : c.unicode();
+ } else {
+ *m_cursor++ = c.unicode() == nonBreakingSpace ? ' ' : c.lower().unicode();
+ }
+ if (m_cursor == m_buffer + length()) {
+ m_cursor = m_buffer;
+ m_bufferFull = true;
+ }
+}
+
+// This function can only be used when the buffer is not yet full,
+// and when then count is small enough to fit in the buffer.
+// No need for a more general version for the search algorithm.
+void CircularSearchBuffer::append(long count, const QChar *characters)
+{
+ long tailSpace = m_buffer + length() - m_cursor;
+
+ assert(!m_bufferFull);
+ assert(count <= tailSpace);
+
+ if (m_isCaseSensitive) {
+ for (long i = 0; i != count; ++i) {
+ QChar c = characters[i];
+ m_cursor[i] = c.unicode() == nonBreakingSpace ? ' ' : c.unicode();
+ }
+ } else {
+ for (long i = 0; i != count; ++i) {
+ QChar c = characters[i];
+ m_cursor[i] = c.unicode() == nonBreakingSpace ? ' ' : c.lower().unicode();
+ }
+ }
+ if (count < tailSpace) {
+ m_cursor += count;
+ } else {
+ m_bufferFull = true;
+ m_cursor = m_buffer;
+ }
+}
+
+long CircularSearchBuffer::neededCharacters() const
+{
+ return m_bufferFull ? 0 : m_buffer + length() - m_cursor;
+}
+
+bool CircularSearchBuffer::isMatch() const
+{
+ assert(m_bufferFull);
+
+ long headSpace = m_cursor - m_buffer;
+ long tailSpace = length() - headSpace;
+ return memcmp(m_cursor, m_target.unicode(), tailSpace * sizeof(QChar)) == 0
+ && memcmp(m_buffer, m_target.unicode() + tailSpace, headSpace * sizeof(QChar)) == 0;
+}
+
+QString plainText(const Range &r)
+{
+ // Allocate string at the right size, rather than building it up by successive append calls.
+ long length = 0;
+ for (TextIterator it(r); !it.atEnd(); it.advance()) {
+ length += it.textLength();
+ }
+ QString result("");
+ result.reserve(length);
+ for (TextIterator it(r); !it.atEnd(); it.advance()) {
+ result.append(it.textCharacters(), it.textLength());
+ }
+ return result;
+}
+
+Range findPlainText(const Range &r, const QString &s, bool forward, bool caseSensitive)
+{
+ // FIXME: Can we do Boyer-Moore or equivalent instead for speed?
+
+ // FIXME: This code does not allow \n at the moment because of issues with <br>.
+ // Once we fix those, we can remove this check.
+ if (s.isEmpty() || s.find('\n') != -1) {
+ Range result = r;
+ result.collapse(forward);
+ return result;
+ }
+
+ CircularSearchBuffer buffer(s, caseSensitive);
+
+ bool found = false;
+ CharacterIterator rangeEnd;
+
+ {
+ CharacterIterator it(r);
+ while (!it.atEnd()) {
+ // Fill the buffer.
+ while (long needed = buffer.neededCharacters()) {
+ long available = it.numCharacters();
+ long runLength = kMin(needed, available);
+ buffer.append(runLength, it.characters());
+ it.advance(runLength);
+ if (it.atBreak()) {
+ if (it.atEnd()) {
+ goto done;
+ }
+ buffer.clear();
+ }
+ }
+
+ // Do the search.
+ do {
+ if (buffer.isMatch()) {
+ // Compute the range for the result.
+ found = true;
+ rangeEnd = it;
+ // If searching forward, stop on the first match.
+ // If searching backward, don't stop, so we end up with the last match.
+ if (forward) {
+ goto done;
+ }
+ }
+ buffer.append(it.characters()[0]);
+ it.advance(1);
+ } while (!it.atBreak());
+ buffer.clear();
+ }
+ }
+
+done:
+ Range result = r;
+ if (!found) {
+ result.collapse(!forward);
+ } else {
+ CharacterIterator it(r);
+ it.advance(rangeEnd.characterOffset() - buffer.length());
+ result.setStart(it.position().startContainer(), it.position().startOffset());
+ it.advance(buffer.length() - 1);
+ result.setEnd(it.position().endContainer(), it.position().endOffset());
+ }
+ return result;
+}
+
+}
diff --git a/JavaScriptCore/bindings/objc/objc_header.h b/WebCore/khtml/misc/khtml_text_operations.h
similarity index 69%
copy from JavaScriptCore/bindings/objc/objc_header.h
copy to WebCore/khtml/misc/khtml_text_operations.h
index 2dd7a26..3e8a705 100644
--- a/JavaScriptCore/bindings/objc/objc_header.h
+++ b/WebCore/khtml/misc/khtml_text_operations.h
@@ -22,26 +22,22 @@
* (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 _BINDINGS_OBJC_HEADER_H_
-#define _BINDINGS_OBJC_HEADER_H_
-#ifdef __OBJC__
-#include <objc/objc-class.h>
-#include <objc/objc-runtime.h>
+#ifndef __khtml_text_iterator_h__
+#define __khtml_text_iterator_h__
-typedef struct objc_class *ClassStructPtr;
-typedef struct objc_object *ObjectStructPtr;
+#include <dom/dom2_range.h>
- at class NSMethodSignature;
+// FIXME: This class should probably use the render tree and not the DOM tree, since elements could
+// be hidden using CSS, or additional generated content could be added. For now, we just make sure
+// text objects walk their renderers' InlineTextBox objects, so that we at least get the whitespace
+// stripped out properly and obey CSS visibility for text runs.
-#else
+namespace khtml {
-typedef struct objc_ivar {} *Ivar;
-typedef struct objc_class {} *ClassStructPtr;
-typedef struct objc_object {} *ObjectStructPtr;
+QString plainText(const DOM::Range &);
+DOM::Range findPlainText(const DOM::Range &, const QString &, bool forward, bool caseSensitive);
-class NSMethodSignature;
+}
#endif
-
-#endif
\ No newline at end of file
diff --git a/WebCore/khtml/xml/dom_selection.cpp b/WebCore/khtml/xml/dom_selection.cpp
index 6f1774f..086487a 100644
--- a/WebCore/khtml/xml/dom_selection.cpp
+++ b/WebCore/khtml/xml/dom_selection.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003 Apple Computer, Inc. All rights reserved.
+ * 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
@@ -77,14 +77,24 @@ Selection::Selection()
Selection::Selection(const Position &pos)
{
init();
- assignBaseAndExtent(pos, pos);
+ assignBaseAndExtent(pos, pos);
+ validate();
+}
+
+Selection::Selection(const Range &r)
+{
+ const Position start(r.startContainer().handle(), r.startOffset());
+ const Position end(r.endContainer().handle(), r.endOffset());
+
+ init();
+ assignBaseAndExtent(start, end);
validate();
}
Selection::Selection(const Position &base, const Position &extent)
{
init();
- assignBaseAndExtent(base, extent);
+ assignBaseAndExtent(base, extent);
validate();
}
@@ -92,8 +102,8 @@ Selection::Selection(const Selection &o)
{
init();
- assignBaseAndExtent(o.base(), o.extent());
- assignStartAndEnd(o.start(), o.end());
+ assignBaseAndExtent(o.base(), o.extent());
+ assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
@@ -129,8 +139,8 @@ void Selection::init()
Selection &Selection::operator=(const Selection &o)
{
- assignBaseAndExtent(o.base(), o.extent());
- assignStartAndEnd(o.start(), o.end());
+ assignBaseAndExtent(o.base(), o.extent());
+ assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
@@ -166,22 +176,22 @@ void Selection::moveTo(const Range &r)
{
Position start(r.startContainer().handle(), r.startOffset());
Position end(r.endContainer().handle(), r.endOffset());
- moveTo(start, end);
+ moveTo(start, end);
}
void Selection::moveTo(const Selection &o)
{
- moveTo(o.start(), o.end());
+ moveTo(o.start(), o.end());
}
void Selection::moveTo(const Position &pos)
{
- moveTo(pos, pos);
+ moveTo(pos, pos);
}
void Selection::moveTo(const Position &base, const Position &extent)
{
- assignBaseAndExtent(base, extent);
+ assignBaseAndExtent(base, extent);
validate();
}
@@ -337,8 +347,8 @@ int Selection::xPosForVerticalArrowNavigation(EPositionType type, bool recalc) c
void Selection::clear()
{
- assignBaseAndExtent(emptyPosition(), emptyPosition());
- validate();
+ assignBaseAndExtent(emptyPosition(), emptyPosition());
+ validate();
}
void Selection::setBase(const Position &pos)
@@ -486,13 +496,13 @@ void Selection::validate(ETextGranularity granularity)
}
// make sure we do not have a dangling start or end
- if (base().isEmpty() && extent().isEmpty()) {
+ if (base().isEmpty() && extent().isEmpty()) {
assignStartAndEnd(emptyPosition(), emptyPosition());
m_baseIsStart = true;
}
- else if (base().isEmpty() || extent().isEmpty()) {
+ else if (base().isEmpty() || extent().isEmpty()) {
m_baseIsStart = true;
- }
+ }
else {
// adjust m_baseIsStart as needed
if (base().node() == extent().node()) {
@@ -572,13 +582,13 @@ void Selection::validate(ETextGranularity granularity)
}
#endif // APPLE_CHANGES
- // adjust the state
- if (start().isEmpty() && end().isEmpty())
- m_state = NONE;
- else if (start() == end())
- m_state = CARET;
- else
- m_state = RANGE;
+ // adjust the state
+ if (start().isEmpty() && end().isEmpty())
+ m_state = NONE;
+ else if (start() == end())
+ m_state = CARET;
+ else
+ m_state = RANGE;
m_needsCaretLayout = true;
@@ -618,13 +628,13 @@ bool Selection::moveToRenderedContent()
bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
{
- if (!n1 || !n2)
- return true;
-
- if (n1 == n2)
- return true;
-
- bool result = false;
+ if (!n1 || !n2)
+ return true;
+
+ if (n1 == n2)
+ return true;
+
+ bool result = false;
int n1Depth = 0;
int n2Depth = 0;
@@ -666,7 +676,7 @@ bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2)
}
n = n->nextSibling();
}
- return result;
+ return result;
}
#if APPLE_CHANGES
diff --git a/WebCore/khtml/xml/dom_selection.h b/WebCore/khtml/xml/dom_selection.h
index 05434bc..d131d40 100644
--- a/WebCore/khtml/xml/dom_selection.h
+++ b/WebCore/khtml/xml/dom_selection.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003 Apple Computer, Inc. All rights reserved.
+ * 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
@@ -45,10 +45,10 @@ class Range;
class Selection
{
public:
- enum EState { NONE, CARET, RANGE };
- enum EAlter { MOVE, EXTEND };
- enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT, UP, DOWN };
- enum ETextGranularity { CHARACTER, WORD, LINE };
+ enum EState { NONE, CARET, RANGE };
+ enum EAlter { MOVE, EXTEND };
+ enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT, UP, DOWN };
+ enum ETextGranularity { CHARACTER, WORD, LINE };
// These match the AppKit values for these concepts.
// From NSTextView.h:
@@ -57,13 +57,13 @@ public:
enum EAffinity { UPSTREAM = 0, DOWNSTREAM = 1 };
Selection();
+ Selection(const Range &);
Selection(const Position &);
Selection(const Position &, const Position &);
Selection(const Selection &);
- ~Selection() {}
- EState state() const { return m_state; }
- EAffinity affinity() const { return m_affinity; }
+ EState state() const { return m_state; }
+ EAffinity affinity() const { return m_affinity; }
void setAffinity(EAffinity);
void moveTo(const Range &);
@@ -100,6 +100,8 @@ public:
void debugRenderer(khtml::RenderObject *r, bool selected) const;
Selection &operator=(const Selection &o);
+ Selection &operator=(const Range &r) { moveTo(r); return *this; }
+ Selection &operator=(const Position &r) { moveTo(r); return *this; }
friend bool operator==(const Selection &a, const Selection &b);
friend bool operator!=(const Selection &a, const Selection &b);
@@ -107,7 +109,7 @@ public:
friend class KHTMLPart;
private:
- enum EPositionType { START, END, BASE, EXTENT };
+ enum EPositionType { START, END, BASE, EXTENT };
void init();
void validate(ETextGranularity granularity=CHARACTER);
@@ -130,16 +132,16 @@ private:
Position m_start; // start position for the selection
Position m_end; // end position for the selection
- EState m_state; // the state of the selection
- EAffinity m_affinity; // the upstream/downstream affinity of the selection
+ EState m_state; // the state of the selection
+ EAffinity m_affinity; // the upstream/downstream affinity of the selection
- int m_caretX; // caret coordinates and size
- int m_caretY;
- int m_caretSize;
+ int m_caretX; // caret coordinates and size
+ int m_caretY;
+ int m_caretSize;
- bool m_baseIsStart : 1; // true if base node is before the extent node
- bool m_needsCaretLayout : 1; // true if the caret position needs to be calculated
- bool m_modifyBiasSet : 1; // true if the selection has been horizontally
+ bool m_baseIsStart : 1; // true if base node is before the extent node
+ bool m_needsCaretLayout : 1; // true if the caret position needs to be calculated
+ bool m_modifyBiasSet : 1; // true if the selection has been horizontally
// modified with EAlter::EXTEND
};
diff --git a/WebCore/kwq/KWQKHTMLPart.mm b/WebCore/kwq/KWQKHTMLPart.mm
index 00566d9..7bfaff6 100644
--- a/WebCore/kwq/KWQKHTMLPart.mm
+++ b/WebCore/kwq/KWQKHTMLPart.mm
@@ -25,6 +25,7 @@
#import "KWQKHTMLPart.h"
+#import "DOMInternal.h"
#import "KWQDOMNode.h"
#import "KWQDummyView.h"
#import "KWQEditCommand.h"
@@ -33,20 +34,23 @@
#import "KWQLogging.h"
#import "KWQPageState.h"
#import "KWQPrinter.h"
+#import "KWQScrollBar.h"
#import "KWQWindowWidget.h"
#import "WebCoreBridge.h"
#import "WebCoreViewFactory.h"
-#import "DOMInternal.h"
#import "csshelper.h"
+#import "dom2_eventsimpl.h"
+#import "dom2_rangeimpl.h"
+#import "dom_selection.h"
#import "html_documentimpl.h"
#import "html_misc.h"
+#import "htmlattrs.h"
#import "htmltokenizer.h"
+#import "khtml_text_operations.h"
#import "khtmlpart_p.h"
#import "khtmlview.h"
#import "kjs_binding.h"
#import "kjs_window.h"
-#import "misc/htmlattrs.h"
-#import "qscrollbar.h"
#import "render_canvas.h"
#import "render_frames.h"
#import "render_image.h"
@@ -54,9 +58,6 @@
#import "render_style.h"
#import "render_table.h"
#import "render_text.h"
-#import "xml/dom_selection.h"
-#import "xml/dom2_eventsimpl.h"
-#import "xml/dom2_rangeimpl.h"
#import <JavaScriptCore/identifier.h>
#import <JavaScriptCore/property_map.h>
#import <JavaScriptCore/runtime.h>
@@ -77,12 +78,15 @@ using DOM::HTMLGenericFormElementImpl;
using DOM::HTMLTableCellElementImpl;
using DOM::Node;
using DOM::NodeImpl;
+using DOM::Range;
using DOM::RangeImpl;
using DOM::Selection;
using khtml::Cache;
using khtml::ChildFrame;
using khtml::Decoder;
+using khtml::findPlainText;
+using khtml::InlineTextBox;
using khtml::MouseDoubleClickEvent;
using khtml::MouseMoveEvent;
using khtml::MousePressEvent;
@@ -99,7 +103,6 @@ using khtml::RenderStyle;
using khtml::RenderTableCell;
using khtml::RenderText;
using khtml::RenderWidget;
-using khtml::InlineTextBox;
using khtml::VISIBLE;
using KIO::Job;
@@ -531,44 +534,40 @@ NSString *KWQKHTMLPart::matchLabelsAgainstElement(NSArray *labels, ElementImpl *
bool KWQKHTMLPart::findString(NSString *string, bool forward, bool caseFlag, bool wrapFlag)
{
QString target = QString::fromNSString(string);
- bool result;
- // start on the correct edge of the selection, search to end
- NodeImpl *selStart = selectionStart();
- int selStartOffset = selectionStartOffset();
- NodeImpl *selEnd = selectionEnd();
- int selEndOffset = selectionEndOffset();
- if (selStart) {
+ if (target.isEmpty()) {
+ return false;
+ }
+
+ // Start on the correct edge of the selection, search to edge of document.
+ Range searchRange(xmlDocImpl());
+ searchRange.selectNodeContents(xmlDocImpl());
+ if (selectionStart()) {
if (forward) {
- // point to last char of selection, find will start right afterwards
- findTextBegin(selEnd, selEndOffset-1);
+ searchRange.setStart(selectionEnd(), selectionEndOffset());
} else {
- // point to first char of selection, find will start right before
- findTextBegin(selStart, selStartOffset);
+ searchRange.setEnd(selectionStart(), selectionStartOffset());
}
- } else {
- findTextBegin();
- }
- result = findTextNext(target, forward, caseFlag, FALSE);
- if (!result && wrapFlag) {
- // start back at the other end, search the rest
- findTextBegin();
- result = findTextNext(target, forward, caseFlag, FALSE);
- // if we got back to the same place we started, that doesn't count as success
- if (result
- && selStart == selectionStart()
- && selStartOffset == selectionStartOffset())
- {
- result = false;
+ }
+
+ // Do the search once, then do it a second time to handle wrapped search.
+ // Searches some or all of document twice in the failure case, but that's probably OK.
+ Range resultRange = findPlainText(searchRange, target, forward, caseFlag);
+ if (resultRange.collapsed() && wrapFlag) {
+ searchRange.selectNodeContents(xmlDocImpl());
+ resultRange = findPlainText(searchRange, target, forward, caseFlag);
+ // If we got back to the same place we started, that doesn't count as success.
+ if (resultRange == selection().toRange()) {
+ return false;
}
}
- // khtml took care of moving the selection, but we need to move first responder too,
- // so the selection is primary. We also need to make the selection visible, since we
- // cut the implementation of this in khtml_part.
- if (result) {
- jumpToSelection();
+ if (resultRange.collapsed()) {
+ return false;
}
- return result;
+
+ setSelection(resultRange);
+ jumpToSelection();
+ return true;
}
void KWQKHTMLPart::clearRecordedFormValues()
@@ -832,6 +831,8 @@ void KWQKHTMLPart::jumpToSelection()
d->m_view->setContentsPos(x - 50, y - 50);
}
/*
+ Something like this would fix <rdar://problem/3154293>: "Find Next should not scroll page if the next target is already visible"
+
I think this would be a better way to do this, to avoid needless horizontal scrolling,
but it is not feasible until selectionRect() returns a tighter rect around the
selected text. Right now it works at element granularity.
--
WebKit Debian packaging
More information about the Pkg-webkit-commits
mailing list