#include "dom_selection.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"
#if APPLE_CHANGES
#include "KWQAssertions.h"
#else
#define ASSERT(assertion) assert(assertion)
#endif
#define EDIT_DEBUG 0
using khtml::findWordBoundary;
using khtml::InlineTextBox;
using khtml::RenderObject;
using khtml::RenderText;
namespace DOM {
static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset);
static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection);
static inline Position &emptyPosition()
{
static Position EmptyPosition = Position();
return EmptyPosition;
}
Selection::Selection()
{
init();
}
Selection::Selection(const Position &pos)
{
init();
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);
validate();
}
Selection::Selection(const Selection &o)
{
init();
assignBaseAndExtent(o.base(), o.extent());
assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
m_baseIsStart = o.m_baseIsStart;
m_needsCaretLayout = o.m_needsCaretLayout;
m_modifyBiasSet = o.m_modifyBiasSet;
if (!m_needsCaretLayout) {
m_caretX = o.m_caretX;
m_caretY = o.m_caretY;
m_caretSize = o.m_caretSize;
}
}
void Selection::init()
{
m_base = m_extent = m_start = m_end = emptyPosition();
m_state = NONE;
m_caretX = 0;
m_caretY = 0;
m_caretSize = 0;
m_baseIsStart = true;
m_needsCaretLayout = true;
m_modifyBiasSet = false;
m_affinity = DOWNSTREAM;
}
Selection &Selection::operator=(const Selection &o)
{
assignBaseAndExtent(o.base(), o.extent());
assignStartAndEnd(o.start(), o.end());
m_state = o.m_state;
m_affinity = o.m_affinity;
m_baseIsStart = o.m_baseIsStart;
m_needsCaretLayout = o.m_needsCaretLayout;
m_modifyBiasSet = o.m_modifyBiasSet;
if (!m_needsCaretLayout) {
m_caretX = o.m_caretX;
m_caretY = o.m_caretY;
m_caretSize = o.m_caretSize;
}
return *this;
}
void Selection::setAffinity(EAffinity affinity)
{
if (affinity == m_affinity)
return;
m_affinity = affinity;
setNeedsLayout();
}
void Selection::moveTo(const Range &r)
{
Position start(r.startContainer().handle(), r.startOffset());
Position end(r.endContainer().handle(), r.endOffset());
moveTo(start, end);
}
void Selection::moveTo(const Selection &o)
{
moveTo(o.start(), o.end());
}
void Selection::moveTo(const Position &pos)
{
moveTo(pos, pos);
}
void Selection::moveTo(const Position &base, const Position &extent)
{
assignBaseAndExtent(base, extent);
validate();
}
bool Selection::modify(EAlter alter, EDirection dir, ETextGranularity granularity)
{
Position pos;
switch (dir) {
case RIGHT:
case FORWARD:
if (alter == EXTEND) {
if (!m_modifyBiasSet) {
m_modifyBiasSet = true;
assignBaseAndExtent(start(), end());
}
switch (granularity) {
case CHARACTER:
pos = extent().nextCharacterPosition();
break;
case WORD:
pos = extent().nextWordPosition();
break;
case LINE:
pos = extent().nextLinePosition(xPosForVerticalArrowNavigation(EXTENT));
break;
case PARAGRAPH:
break;
}
}
else {
m_modifyBiasSet = false;
switch (granularity) {
case CHARACTER:
pos = (state() == RANGE) ? end() : extent().nextCharacterPosition();
break;
case WORD:
pos = extent().nextWordPosition();
break;
case LINE:
pos = end().nextLinePosition(xPosForVerticalArrowNavigation(END, state() == RANGE));
break;
case PARAGRAPH:
break;
}
}
break;
case LEFT:
case BACKWARD:
if (alter == EXTEND) {
if (!m_modifyBiasSet) {
m_modifyBiasSet = true;
assignBaseAndExtent(end(), start());
}
switch (granularity) {
case CHARACTER:
pos = extent().previousCharacterPosition();
break;
case WORD:
pos = extent().previousWordPosition();
break;
case LINE:
pos = extent().previousLinePosition(xPosForVerticalArrowNavigation(EXTENT));
break;
case PARAGRAPH:
break;
}
}
else {
m_modifyBiasSet = false;
switch (granularity) {
case CHARACTER:
pos = (state() == RANGE) ? start() : extent().previousCharacterPosition();
break;
case WORD:
pos = extent().previousWordPosition();
break;
case LINE:
pos = start().previousLinePosition(xPosForVerticalArrowNavigation(START, state() == RANGE));
break;
case PARAGRAPH:
break;
}
}
break;
}
if (pos.isEmpty())
return false;
if (alter == MOVE)
moveTo(pos);
else setExtent(pos);
return true;
}
bool Selection::expandUsingGranularity(ETextGranularity granularity)
{
if (state() == NONE)
return false;
validate(granularity);
return true;
}
int Selection::xPosForVerticalArrowNavigation(EPositionType type, bool recalc) const
{
int x = 0;
if (state() == NONE)
return x;
Position pos;
switch (type) {
case START:
pos = start();
break;
case END:
pos = end();
break;
case BASE:
pos = base();
break;
case EXTENT:
pos = extent();
break;
}
KHTMLPart *part = pos.node()->getDocument()->part();
if (!part)
return x;
if (recalc || part->xPosForVerticalArrowNavigation() == KHTMLPart::NoXPosForVerticalArrowNavigation) {
int y, w, h;
pos.node()->renderer()->caretPos(pos.offset(), true, x, y, w, h);
part->setXPosForVerticalArrowNavigation(x);
}
else {
x = part->xPosForVerticalArrowNavigation();
}
return x;
}
void Selection::clear()
{
assignBaseAndExtent(emptyPosition(), emptyPosition());
validate();
}
void Selection::setBase(const Position &pos)
{
assignBase(pos);
validate();
}
void Selection::setExtent(const Position &pos)
{
assignExtent(pos);
validate();
}
void Selection::setBaseAndExtent(const Position &base, const Position &extent)
{
assignBaseAndExtent(base, extent);
validate();
}
void Selection::setStart(const Position &pos)
{
assignStart(pos);
validate();
}
void Selection::setEnd(const Position &pos)
{
assignEnd(pos);
validate();
}
void Selection::setStartAndEnd(const Position &start, const Position &end)
{
assignStartAndEnd(start, end);
validate();
}
void Selection::setNeedsLayout(bool flag)
{
m_needsCaretLayout = flag;
}
Range Selection::toRange() const
{
if (isEmpty())
return Range();
start().node()->getDocument()->updateLayout();
Position s, e;
if (state() == CARET) {
s = start().equivalentUpstreamPosition().equivalentRangeCompliantPosition();
e = s;
}
else {
ASSERT(state() == RANGE);
s = start().equivalentDownstreamPosition();
e = end().equivalentUpstreamPosition();
if ((s.node() == e.node() && s.offset() > e.offset()) || !nodeIsBeforeNode(s.node(), e.node())) {
Position tmp = s;
s = e;
e = tmp;
}
s = s.equivalentRangeCompliantPosition();
e = e.equivalentRangeCompliantPosition();
}
return Range(Node(s.node()), s.offset(), Node(e.node()), e.offset());
}
void Selection::layoutCaret()
{
if (isEmpty() || !start().node()->renderer()) {
m_caretX = m_caretY = m_caretSize = 0;
}
else {
int w;
start().node()->renderer()->caretPos(start().offset(), true, m_caretX, m_caretY, w, m_caretSize);
}
m_needsCaretLayout = false;
}
QRect Selection::getRepaintRect() const
{
if (m_needsCaretLayout) {
const_cast<Selection *>(this)->layoutCaret();
}
return QRect(m_caretX - 1, m_caretY - 1, 3, m_caretSize + 2);
}
void Selection::needsCaretRepaint()
{
if (isEmpty())
return;
if (!start().node()->getDocument())
return;
KHTMLView *v = start().node()->getDocument()->view();
if (!v)
return;
if (m_needsCaretLayout) {
v->updateContents(getRepaintRect(), false);
layoutCaret();
m_needsCaretLayout = true;
}
v->updateContents(getRepaintRect(), false);
}
void Selection::paintCaret(QPainter *p, const QRect &rect)
{
if (isEmpty())
return;
if (m_state != CARET)
return;
if (m_needsCaretLayout) {
Position pos = start();
if (!pos.inRenderedContent()) {
moveToRenderedContent();
}
layoutCaret();
}
QRect caretRect(m_caretX, m_caretY, 1, m_caretSize);
if (caretRect.intersects(rect)) {
QPen pen = p->pen();
pen.setStyle(Qt::SolidLine);
pen.setColor(Qt::black);
pen.setWidth(1);
p->setPen(pen);
p->drawLine(caretRect.left(), caretRect.top(), caretRect.left(), caretRect.bottom());
}
}
void Selection::validate(ETextGranularity granularity)
{
bool baseAndExtentEqual = base() == extent();
if (base().notEmpty()) {
Position pos = base().equivalentLeafPosition();
assignBase(pos);
if (baseAndExtentEqual)
assignExtent(pos);
}
if (extent().notEmpty() && !baseAndExtentEqual) {
assignExtent(extent().equivalentLeafPosition());
}
if (base().isEmpty() && extent().isEmpty()) {
assignStartAndEnd(emptyPosition(), emptyPosition());
m_baseIsStart = true;
}
else if (base().isEmpty() || extent().isEmpty()) {
m_baseIsStart = true;
}
else {
if (base().node() == extent().node()) {
if (base().offset() > extent().offset())
m_baseIsStart = false;
else
m_baseIsStart = true;
}
else if (nodeIsBeforeNode(base().node(), extent().node()))
m_baseIsStart = true;
else
m_baseIsStart = false;
}
if (granularity == CHARACTER) {
if (m_baseIsStart)
assignStartAndEnd(base(), extent());
else
assignStartAndEnd(extent(), base());
}
else if (granularity == WORD) {
int baseStartOffset = base().offset();
int baseEndOffset = base().offset();
int extentStartOffset = extent().offset();
int extentEndOffset = extent().offset();
if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
DOMString t = base().node()->nodeValue();
QChar *chars = t.unicode();
uint len = t.length();
findWordBoundary(chars, len, base().offset(), &baseStartOffset, &baseEndOffset);
}
if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
DOMString t = extent().node()->nodeValue();
QChar *chars = t.unicode();
uint len = t.length();
findWordBoundary(chars, len, extent().offset(), &extentStartOffset, &extentEndOffset);
}
if (m_baseIsStart) {
assignStart(Position(base().node(), baseStartOffset));
assignEnd(Position(extent().node(), extentEndOffset));
}
else {
assignStart(Position(extent().node(), extentStartOffset));
assignEnd(Position(base().node(), baseEndOffset));
}
}
else { Selection baseSelection = *this;
Selection extentSelection = *this;
if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
if (startAndEndLineNodesIncludingNode(base().node(), base().offset(), baseSelection)) {
assignStart(Position(baseSelection.base().node(), baseSelection.base().offset()));
assignEnd(Position(baseSelection.extent().node(), baseSelection.extent().offset()));
}
}
if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
if (startAndEndLineNodesIncludingNode(extent().node(), extent().offset(), extentSelection)) {
assignStart(Position(extentSelection.base().node(), extentSelection.base().offset()));
assignEnd(Position(extentSelection.extent().node(), extentSelection.extent().offset()));
}
}
if (m_baseIsStart) {
assignStart(baseSelection.start());
assignEnd(extentSelection.end());
}
else {
assignStart(extentSelection.start());
assignEnd(baseSelection.end());
}
}
if (start().isEmpty() && end().isEmpty())
m_state = NONE;
else if (start() == end())
m_state = CARET;
else
m_state = RANGE;
m_needsCaretLayout = true;
#if EDIT_DEBUG
debugPosition();
#endif
}
bool Selection::moveToRenderedContent()
{
if (isEmpty())
return false;
if (m_state != CARET)
return false;
Position pos = start();
if (pos.inRenderedContent())
return true;
Position prev = pos.previousCharacterPosition();
if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node())) {
moveTo(prev);
return true;
}
Position next = pos.nextCharacterPosition();
if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node())) {
moveTo(next);
return true;
}
return false;
}
bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) const
{
if (!n1 || !n2)
return true;
if (n1 == n2)
return true;
bool result = false;
int n1Depth = 0;
int n2Depth = 0;
NodeImpl *n = n1;
while (n->parentNode()) {
n = n->parentNode();
n1Depth++;
}
n = n2;
while (n->parentNode()) {
n = n->parentNode();
n2Depth++;
}
while (n2Depth > n1Depth) {
n2 = n2->parentNode();
n2Depth--;
}
while (n1Depth > n2Depth) {
n1 = n1->parentNode();
n1Depth--;
}
while (n1->parentNode() != n2->parentNode()) {
n1 = n1->parentNode();
n2 = n2->parentNode();
}
n = n1->parentNode() ? n1->parentNode()->firstChild() : n1->firstChild();
while (n) {
if (n == n1) {
result = true;
break;
}
else if (n == n2) {
result = false;
break;
}
n = n->nextSibling();
}
return result;
}
static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset)
{
for (RenderObject *n = renderNode; n; n = n->nextSibling()) {
if (n->isText()) {
RenderText *textRenderer = static_cast<khtml::RenderText *>(n);
for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
if (box->m_y == y) {
startNode = textRenderer->element();
startOffset = box->m_start;
return true;
}
}
}
if (firstRunAt(n->firstChild(), y, startNode, startOffset)) {
return true;
}
}
return false;
}
static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset)
{
RenderObject *n = renderNode;
if (!n) {
return false;
}
RenderObject *next;
while ((next = n->nextSibling())) {
n = next;
}
while (1) {
if (lastRunAt(n->firstChild(), y, endNode, endOffset)) {
return true;
}
if (n->isText()) {
RenderText *textRenderer = static_cast<khtml::RenderText *>(n);
for (InlineTextBox* box = textRenderer->lastTextBox(); box; box = box->prevTextBox()) {
if (box->m_y == y) {
endNode = textRenderer->element();
endOffset = box->m_start + box->m_len;
return true;
}
}
}
if (n == renderNode) {
return false;
}
n = n->previousSibling();
}
}
static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection)
{
if (node && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) {
int pos;
int selectionPointY;
RenderText *renderer = static_cast<RenderText *>(node->renderer());
InlineTextBox * run = renderer->findNextInlineTextBox( offset, pos );
DOMString t = node->nodeValue();
if (!run)
return false;
selectionPointY = run->m_y;
khtml::RenderObject *renderNode = renderer;
while (renderNode && renderNode->isInline())
renderNode = renderNode->parent();
renderNode = renderNode->firstChild();
NodeImpl *startNode = 0;
NodeImpl *endNode = 0;
long startOffset;
long endOffset;
if (!firstRunAt (renderNode, selectionPointY, startNode, startOffset))
return false;
if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
return false;
selection.moveTo(Position(startNode, startOffset), Position(endNode, endOffset));
return true;
}
return false;
}
void Selection::debugRenderer(RenderObject *r, bool selected) const
{
if (r->node()->isElementNode()) {
ElementImpl *element = static_cast<ElementImpl *>(r->node());
fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->tagName().string().latin1());
}
else if (r->isText()) {
RenderText *textRenderer = static_cast<RenderText *>(r);
if (textRenderer->stringLength() == 0 || !textRenderer->firstTextBox()) {
fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " ");
return;
}
static const int max = 36;
QString text = DOMString(textRenderer->string()).string();
int textLength = text.length();
if (selected) {
int offset = 0;
if (r->node() == start().node())
offset = start().offset();
else if (r->node() == end().node())
offset = end().offset();
int pos;
InlineTextBox *box = textRenderer->findNextInlineTextBox(offset, pos);
text = text.mid(box->m_start, box->m_len);
QString show;
int mid = max / 2;
int caret = 0;
if (textLength < max) {
show = text;
caret = pos;
}
else if (pos - mid < 0) {
show = text.left(max - 3) + "...";
caret = pos;
}
else if (pos - mid >= 0 && pos + mid <= textLength) {
show = "..." + text.mid(pos - mid + 3, max - 6) + "...";
caret = mid;
}
else {
show = "..." + text.right(max - 3);
caret = pos - (textLength - show.length());
}
show = show.replace("\n", " ");
show = show.replace("\r", " ");
fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.latin1(), pos);
fprintf(stderr, " ");
for (int i = 0; i < caret; i++)
fprintf(stderr, " ");
fprintf(stderr, "^\n");
}
else {
if ((int)text.length() > max)
text = text.left(max - 3) + "...";
else
text = text.left(max);
fprintf(stderr, " #text : \"%s\"\n", text.latin1());
}
}
}
void Selection::debugPosition() const
{
if (!start().node())
return;
fprintf(stderr, "Selection =================\n");
if (start() == end()) {
Position pos = start();
Position upstream = pos.equivalentUpstreamPosition();
Position downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
fprintf(stderr, "pos: %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
}
else {
Position pos = start();
Position upstream = pos.equivalentUpstreamPosition();
Position downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
fprintf(stderr, "start: %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
fprintf(stderr, "-----------------------------------\n");
pos = end();
upstream = pos.equivalentUpstreamPosition();
downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
fprintf(stderr, "end: %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
fprintf(stderr, "-----------------------------------\n");
}
#if 0
int back = 0;
r = start().node()->renderer();
for (int i = 0; i < context; i++, back++) {
if (r->previousRenderer())
r = r->previousRenderer();
else
break;
}
for (int i = 0; i < back; i++) {
debugRenderer(r, false);
r = r->nextRenderer();
}
fprintf(stderr, "\n");
if (start().node() == end().node())
debugRenderer(start().node()->renderer(), true);
else
for (r = start().node()->renderer(); r && r != end().node()->renderer(); r = r->nextRenderer())
debugRenderer(r, true);
fprintf(stderr, "\n");
r = end().node()->renderer();
for (int i = 0; i < context; i++) {
if (r->nextRenderer()) {
r = r->nextRenderer();
debugRenderer(r, false);
}
else
break;
}
#endif
fprintf(stderr, "================================\n");
}
}