/* This file is part of the KDE project * * Copyright (C) 2003-2004 Leo Savernik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "tdehtml_caret_p.h" #include "html/html_documentimpl.h" namespace tdehtml { /** Flags representing the type of advance that has been made. * @param LeftObject a render object was left and an ascent to its parent has * taken place * @param AdvancedToSibling an actual advance to a sibling has taken place * @param EnteredObject a render object was entered by descending into it from * its parent object. */ enum ObjectAdvanceState { LeftObject = 0x01, AdvancedToSibling = 0x02, EnteredObject = 0x04 }; /** All possible states that may occur during render object traversal. * @param OutsideDescending outside of the current object, ready to descend * into children * @param InsideDescending inside the current object, descending into * children * @param InsideAscending inside the current object, ascending to parents * @param OutsideAscending outsie the current object, ascending to parents */ enum ObjectTraversalState { OutsideDescending, InsideDescending, InsideAscending, OutsideAscending }; /** Traverses the render object tree in a fine granularity. * @param obj render object * @param trav object traversal state * @param toBegin traverse towards beginning * @param base base render object which this method must not advance beyond * (0 means document) * @param state object advance state (enum ObjectAdvanceState) * @return the render object according to the state. May be the same as \c obj */ static RenderObject* traverseRenderObjects(RenderObject *obj, ObjectTraversalState &trav, bool toBegin, RenderObject *base, int &state) { RenderObject *r; switch (trav) { case OutsideDescending: trav = InsideDescending; break; case InsideDescending: r = toBegin ? obj->lastChild() : obj->firstChild(); if (r) { trav = OutsideDescending; obj = r; state |= EnteredObject; } else { trav = InsideAscending; } break; case InsideAscending: trav = OutsideAscending; break; case OutsideAscending: r = toBegin ? obj->previousSibling() : obj->nextSibling(); if (r) { trav = OutsideDescending; state |= AdvancedToSibling; } else { r = obj->parent(); if (r == base) r = 0; trav = InsideAscending; state |= LeftObject; } obj = r; break; }/*end switch*/ return obj; } /** Like RenderObject::objectBelow, but confined to stay within \c base. * @param obj render object to begin with * @param trav object traversal state, will be reset within this function * @param base base render object (0: no base) */ static inline RenderObject *renderObjectBelow(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) { trav = InsideDescending; int state; // we don't need the state, so we don't initialize it RenderObject *r = obj; while (r && trav != OutsideDescending) { r = traverseRenderObjects(r, trav, false, base, state); #if DEBUG_CARETMODE > 3 kdDebug(6200) << "renderObjectBelow: r " << r << " trav " << trav << endl; #endif } trav = InsideDescending; return r; } /** Like RenderObject::objectAbove, but confined to stay within \c base. * @param obj render object to begin with * @param trav object traversal state, will be reset within this function * @param base base render object (0: no base) */ static inline RenderObject *renderObjectAbove(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) { trav = OutsideAscending; int state; // we don't need the state, so we don't initialize it RenderObject *r = obj; while (r && trav != InsideAscending) { r = traverseRenderObjects(r, trav, true, base, state); #if DEBUG_CARETMODE > 3 kdDebug(6200) << "renderObjectAbove: r " << r << " trav " << trav << endl; #endif } trav = InsideAscending; return r; } /** Checks whether the given inline box matches the IndicatedFlows policy * @param box inline box to test * @return true on match */ static inline bool isIndicatedInlineBox(InlineBox *box) { // text boxes are never indicated. if (box->isInlineTextBox()) return false; RenderStyle *s = box->object()->style(); return s->borderLeftWidth() || s->borderRightWidth() || s->borderTopWidth() || s->borderBottomWidth() || s->paddingLeft().value() || s->paddingRight().value() || s->paddingTop().value() || s->paddingBottom().value() // ### Can inline elements have top/bottom margins? Couldn't find // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. || s->marginLeft().value() || s->marginRight().value(); } /** Checks whether the given render object matches the IndicatedFlows policy * @param r render object to test * @return true on match */ static inline bool isIndicatedFlow(RenderObject *r) { RenderStyle *s = r->style(); return s->borderLeftStyle() != BNONE || s->borderRightStyle() != BNONE || s->borderTopStyle() != BNONE || s->borderBottomStyle() != BNONE // || s->paddingLeft().value() || s->paddingRight().value() // || s->paddingTop().value() || s->paddingBottom().value() // || s->marginLeft().value() || s->marginRight().value() || s->hasClip() || s->hidesOverflow() || s->backgroundColor().isValid() || s->backgroundImage(); } /** Advances to the next render object, taking into account the current * traversal state. * * @param r render object * @param trav object traversal state * @param toBegin @p true, advance towards beginning, @p false, advance toward end * @param base base render object which this method must not advance beyond * (0 means document) * @param state object advance state (enum ObjectAdvanceState) (unchanged * on LeafsOnly) * @return a pointer to the render object which we advanced to, * or 0 if the last possible object has been reached. */ static RenderObject *advanceObject(RenderObject *r, ObjectTraversalState &trav, bool toBegin, RenderObject *base, int &state) { ObjectTraversalState origtrav = trav; RenderObject *a = traverseRenderObjects(r, trav, toBegin, base, state); bool ignoreOutsideDesc = toBegin && origtrav == OutsideAscending; // render object and traversal state at which look ahead has been started RenderObject *la = 0; ObjectTraversalState latrav = trav; ObjectTraversalState lasttrav = origtrav; while (a) { #if DEBUG_CARETMODE > 5 kdDebug(6200) << "a " << a << " trav " << trav << endl; #endif if (a->element()) { #if DEBUG_CARETMODE > 4 kdDebug(6200) << "a " << a << " trav " << trav << " origtrav " << origtrav << " ignoreOD " << ignoreOutsideDesc << endl; #endif if (toBegin) { switch (origtrav) { case OutsideDescending: if (trav == InsideAscending) return a; if (trav == OutsideDescending) return a; break; case InsideDescending: if (trav == OutsideDescending) return a; // fall through case InsideAscending: if (trav == OutsideAscending) return a; break; case OutsideAscending: if (trav == OutsideAscending) return a; if (trav == InsideAscending && lasttrav == InsideDescending) return a; if (trav == OutsideDescending && !ignoreOutsideDesc) return a; // ignore this outside descending position, as it effectively // demarkates the same position, but remember it in case we fall off // the document. la = a; latrav = trav; ignoreOutsideDesc = false; break; }/*end switch*/ } else { switch (origtrav) { case OutsideDescending: if (trav == InsideAscending) return a; if (trav == OutsideDescending) return a; break; case InsideDescending: // if (trav == OutsideDescending) return a; // fall through case InsideAscending: // if (trav == OutsideAscending) return a; // break; case OutsideAscending: // ### what if origtrav == OA, and immediately afterwards trav // becomes OD? In this case the effective position hasn't changed -> // the caret gets stuck. Otherwise, it apparently cannot happen in // real usage patterns. if (trav == OutsideDescending) return a; if (trav == OutsideAscending) { if (la) return la; // starting lookahead here. Remember old object in case we fall off // the document. la = a; latrav = trav; } break; }/*end switch*/ }/*end if*/ }/*end if*/ lasttrav = trav; a = traverseRenderObjects(a, trav, toBegin, base, state); }/*wend*/ if (la) trav = latrav, a = la; return a; } /** Check whether the current render object is unsuitable in caret mode handling. * * Some render objects cannot be handled correctly in caret mode. These objects * are therefore considered to be unsuitable. The null object is suitable, as * it denotes reaching the end. * @param r current render object * @param trav current traversal state */ static inline bool isUnsuitable(RenderObject *r, ObjectTraversalState trav) { if (!r) return false; return r->isTableCol() || r->isTableSection() || r->isTableRow() || (r->isText() && static_cast(r)->inlineTextBoxCount() == 0); ; Q_UNUSED(trav); } /** Advances to the next render object, taking into account the current * traversal state, but skipping render objects which are not suitable for * having placed the caret into them. * @param r render object * @param trav object traversal state (unchanged on LeafsOnly) * @param toBegin @p true, advance towards beginning, @p false, advance toward end * @param base base render object which this method must not advance beyond * (0 means document) * @param state object advance state (enum ObjectAdvanceState) (unchanged * on LeafsOnly) * @return a pointer to the advanced render object or 0 if the last possible * object has been reached. */ static inline RenderObject *advanceSuitableObject(RenderObject *r, ObjectTraversalState &trav, bool toBegin, RenderObject *base, int &state) { do { r = advanceObject(r, trav, toBegin, base, state); #if DEBUG_CARETMODE > 2 kdDebug(6200) << "after advanceSWP: r " << r << " trav " << trav << " toBegin " << toBegin << endl; #endif } while (isUnsuitable(r, trav)); return r; } /** * Returns the next leaf node. * * Using this function delivers leaf nodes as if the whole DOM tree * were a linear chain of its leaf nodes. * @param r dom node * @param baseElem base element not to search beyond * @return next leaf node or 0 if there are no more. */ static NodeImpl *nextLeafNode(NodeImpl *r, NodeImpl *baseElem) { NodeImpl *n = r->firstChild(); if (n) { while (n) { r = n; n = n->firstChild(); } return const_cast(r); }/*end if*/ n = r->nextSibling(); if (n) { r = n; while (n) { r = n; n = n->firstChild(); } return const_cast(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) n = 0; while (n) { r = n; n = r->nextSibling(); if (n) { r = n; n = r->firstChild(); while (n) { r = n; n = n->firstChild(); } return const_cast(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) n = 0; }/*wend*/ return 0; } #if 0 // currently not used /** (Not part of the official DOM) * Returns the previous leaf node. * * Using this function delivers leaf nodes as if the whole DOM tree * were a linear chain of its leaf nodes. * @param r dom node * @param baseElem base element not to search beyond * @return previous leaf node or 0 if there are no more. */ static NodeImpl *prevLeafNode(NodeImpl *r, NodeImpl *baseElem) { NodeImpl *n = r->firstChild(); if (n) { while (n) { r = n; n = n->firstChild(); } return const_cast(r); }/*end if*/ n = r->previousSibling(); if (n) { r = n; while (n) { r = n; n = n->firstChild(); } return const_cast(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) n = 0; while (n) { r = n; n = r->previousSibling(); if (n) { r = n; n = r->lastChild(); while (n) { r = n; n = n->lastChild(); } return const_cast(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) n = 0; }/*wend*/ return 0; } #endif /** Maps a DOM Range position to the corresponding caret position. * * The offset boundary is not checked for validity. * @param node DOM node * @param offset zero-based offset within node * @param r returns render object (may be 0 if DOM node has no render object) * @param r_ofs returns the appropriate offset for the found render object r * @param outside returns true when offset is applied to the outside of * \c r, or false for the inside. * @param outsideEnd return true when the caret position is at the outside end. */ void /*KDE_NO_EXPORT*/ mapDOMPosToRenderPos(NodeImpl *node, long offset, RenderObject *&r, long &r_ofs, bool &outside, bool &outsideEnd) { if (node->nodeType() == Node::TEXT_NODE) { outside = false; outsideEnd = false; r = node->renderer(); r_ofs = offset; } else if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::DOCUMENT_NODE) { // Though offset points between two children, attach it to the visually // most suitable one (and only there, because the mapping must stay bijective) if (node->firstChild()) { outside = true; NodeImpl *child = offset <= 0 ? node->firstChild() // childNode is expensive : node->childNode((unsigned long)offset); // index was child count or out of bounds bool atEnd = !child; #if DEBUG_CARETMODE > 5 kdDebug(6200) << "mapDTR: child " << child << "@" << (child ? child->nodeName().string() : TQString::null) << " atEnd " << atEnd << endl; #endif if (atEnd) child = node->lastChild(); r = child->renderer(); r_ofs = 0; outsideEnd = atEnd; // Outside text nodes most likely stem from a continuation. Seek // the enclosing continued render object and use this one instead. if (r && child->nodeType() == Node::TEXT_NODE) { r = r->parent(); RenderObject *o = node->renderer(); while (o->continuation() && o->continuation() != r) o = o->continuation(); if (!r || o->continuation() != r) { r = child->renderer(); } }/*end if*/ // BRs cause troubles. Returns the previous render object instead, // giving it the attributes outside, outside end. if (r && r->isBR()) { r = r->objectAbove(); outsideEnd = true; }/*end if*/ } else { // Element has no children, treat offset to be inside the node. outside = false; outsideEnd = false; r = node->renderer(); r_ofs = 0; // only offset 0 possible } } else { r = 0; kdWarning() << k_funcinfo << "Mapping from nodes of type " << node->nodeType() << " not supported!" << endl; } } /** Maps a caret position to the corresponding DOM Range position. * * @param r render object * @param r_ofs offset within render object * @param outside true when offset is interpreted to be on the outside of * \c r, or false if on the inside. * @param outsideEnd true when the caret position is at the outside end. * @param node returns DOM node * @param offset returns zero-based offset within node */ void /*KDE_NO_EXPORT*/ mapRenderPosToDOMPos(RenderObject *r, long r_ofs, bool outside, bool outsideEnd, NodeImpl *&node, long &offset) { node = r->element(); Q_ASSERT(node); #if DEBUG_CARETMODE > 5 kdDebug(6200) << "mapRTD: r " << r << "@" << (r ? r->renderName() : TQString::null) << (r && r->element() ? TQString(".node ") + TQString::number((unsigned)r->element(),16) + "@" + r->element()->nodeName().string() : TQString::null) << " outside " << outside << " outsideEnd " << outsideEnd << endl; #endif if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::TEXT_NODE) { if (outside) { NodeImpl *parent = node->parent(); // If this is part of a continuation, use the actual node as the parent, // and the first render child as the node. if (r != node->renderer()) { RenderObject *o = node->renderer(); while (o->continuation() && o->continuation() != r) o = o->continuation(); if (o->continuation() == r) { parent = node; // ### What if the first render child does not map to a child of // the continued node? node = r->firstChild() ? r->firstChild()->element() : node; } }/*end if*/ if (!parent) goto inside; offset = (long)node->nodeIndex() + outsideEnd; node = parent; #if DEBUG_CARETMODE > 5 kdDebug(6200) << node << "@" << (node ? node->nodeName().string() : TQString::null) << " offset " << offset << endl; #endif } else { // !outside inside: offset = r_ofs; } } else { offset = 0; kdWarning() << k_funcinfo << "Mapping to nodes of type " << node->nodeType() << " not supported!" << endl; } } /** Make sure the given node is a leaf node. */ static inline void ensureLeafNode(NodeImpl *&node, NodeImpl *base) { if (node && node->hasChildNodes()) node = nextLeafNode(node, base); } /** Converts a caret position to its respective object traversal state. * @param outside whether the caret is outside the object * @param atEnd whether the caret position is at the end * @param toBegin \c true when advancing towards the beginning * @param trav returns the corresponding traversal state */ static inline void mapRenderPosToTraversalState(bool outside, bool atEnd, bool toBegin, ObjectTraversalState &trav) { if (!outside) atEnd = !toBegin; if (!atEnd ^ toBegin) trav = outside ? OutsideDescending : InsideDescending; else trav = outside ? OutsideAscending : InsideAscending; } /** Converts a traversal state to its respective caret position * @param trav object traversal state * @param toBegin \c true when advancing towards the beginning * @param outside whether the caret is outside the object * @param atEnd whether the caret position is at the end */ static inline void mapTraversalStateToRenderPos(ObjectTraversalState trav, bool toBegin, bool &outside, bool &atEnd) { outside = false; switch (trav) { case OutsideDescending: outside = true; // fall through case InsideDescending: atEnd = toBegin; break; case OutsideAscending: outside = true; // fall through case InsideAscending: atEnd = !toBegin; break; } } /** Finds the next node that has a renderer. * * Note that if the initial @p node has a renderer, this will be returned, * regardless of the caret advance policy. * Otherwise, for the next nodes, only leaf nodes are considered. * @param node node to start with, will be updated accordingly * @param offset offset of caret within \c node * @param base base render object which this method must not advance beyond * (0 means document) * @param r_ofs return the caret offset within the returned renderer * @param outside returns whether offset is to be interpreted to the outside * (true) or the inside (false) of the render object. * @param outsideEnd returns whether the end of the outside position is meant * @return renderer or 0 if no following node has a renderer. */ static RenderObject* findRenderer(NodeImpl *&node, long offset, RenderObject *base, long &r_ofs, bool &outside, bool &outsideEnd) { if (!node) return 0; RenderObject *r; mapDOMPosToRenderPos(node, offset, r, r_ofs, outside, outsideEnd); #if DEBUG_CARETMODE > 2 kdDebug(6200) << "findRenderer: node " << node << " " << (node ? node->nodeName().string() : TQString::null) << " offset " << offset << " r " << r << "[" << (r ? r->renderName() : TQString::null) << "] r_ofs " << r_ofs << " outside " << outside << " outsideEnd " << outsideEnd << endl; #endif if (r) return r; NodeImpl *baseElem = base ? base->element() : 0; while (!r) { node = nextLeafNode(node, baseElem); if (!node) break; r = node->renderer(); if (r) r_ofs = offset; } #if DEBUG_CARETMODE > 3 kdDebug(6200) << "1r " << r << endl; #endif ObjectTraversalState trav; int state; // not used mapRenderPosToTraversalState(outside, outsideEnd, false, trav); if (r && isUnsuitable(r, trav)) { r = advanceSuitableObject(r, trav, false, base, state); mapTraversalStateToRenderPos(trav, false, outside, outsideEnd); if (r) r_ofs = r->minOffset(); } #if DEBUG_CARETMODE > 3 kdDebug(6200) << "2r " << r << endl; #endif return r; } /** returns a suitable base element * @param caretNode current node containing caret. */ static ElementImpl *determineBaseElement(NodeImpl *caretNode) { // ### for now, only body is delivered for html documents, // and 0 for xml documents. DocumentImpl *doc = caretNode->getDocument(); if (!doc) return 0; // should not happen, but who knows. if (doc->isHTMLDocument()) return static_cast(doc)->body(); return 0; } // == class CaretBox implementation #if DEBUG_CARETMODE > 0 void CaretBox::dump(TQTextStream &ts, const TQString &ind) const { ts << ind << "b@" << _box; if (_box) { ts << "<" << _box->object() << ":" << _box->object()->renderName() << ">"; }/*end if*/ ts << " " << _x << "+" << _y << "+" << _w << "*" << _h; ts << " cb@" << cb; if (cb) ts << ":" << cb->renderName(); ts << " " << (_outside ? (outside_end ? "oe" : "o-") : "i-"); // ts << endl; } #endif // == class CaretBoxLine implementation #if DEBUG_CARETMODE > 0 # define DEBUG_ACIB 1 #else # define DEBUG_ACIB DEBUG_CARETMODE #endif void CaretBoxLine::addConvertedInlineBox(InlineBox *box, SeekBoxParams &sbp) /*KDE_NO_EXPORT*/ { // Generate only one outside caret box between two elements. If // coalesceOutsideBoxes is true, generating left outside boxes is inhibited. bool coalesceOutsideBoxes = false; CaretBoxIterator lastCoalescedBox; for (; box; box = box->nextOnLine()) { #if DEBUG_ACIB kdDebug(6200) << "box " << box << endl; kdDebug(6200) << "box->object " << box->object() << endl; kdDebug(6200) << "x " << box->m_x << " y " << box->m_y << " w " << box->m_width << " h " << box->m_height << " baseline " << box->m_baseline << " ifb " << box->isInlineFlowBox() << " itb " << box->isInlineTextBox() << " rlb " << box->isRootInlineBox() << endl; #endif // ### Why the hell can object() ever be 0?! if (!box->object()) continue; RenderStyle *s = box->object()->style(box->m_firstLine); // parent style for outside caret boxes RenderStyle *ps = box->parent() && box->parent()->object() ? box->parent()->object()->style(box->parent()->m_firstLine) : s; if (box->isInlineFlowBox()) { #if DEBUG_ACIB kdDebug(6200) << "isinlineflowbox " << box << endl; #endif InlineFlowBox *flowBox = static_cast(box); bool rtl = ps->direction() == RTL; const TQFontMetrics &pfm = ps->fontMetrics(); if (flowBox->includeLeftEdge()) { // If this box is to be coalesced with the outside end box of its // predecessor, then check if it is the searched box. If it is, we // substitute the outside end box. if (coalesceOutsideBoxes) { if (sbp.equalsBox(flowBox, true, false)) { sbp.it = lastCoalescedBox; Q_ASSERT(!sbp.found); sbp.found = true; } } else { addCreatedFlowBoxEdge(flowBox, pfm, true, rtl); sbp.check(preEnd()); } }/*end if*/ if (flowBox->firstChild()) { #if DEBUG_ACIB kdDebug(6200) << "this " << this << " flowBox " << flowBox << " firstChild " << flowBox->firstChild() << endl; kdDebug(6200) << "== recursive invocation" << endl; #endif addConvertedInlineBox(flowBox->firstChild(), sbp); #if DEBUG_ACIB kdDebug(6200) << "== recursive invocation end" << endl; #endif } else { addCreatedFlowBoxInside(flowBox, s->fontMetrics()); sbp.check(preEnd()); } if (flowBox->includeRightEdge()) { addCreatedFlowBoxEdge(flowBox, pfm, false, rtl); lastCoalescedBox = preEnd(); sbp.check(lastCoalescedBox); coalesceOutsideBoxes = true; } } else if (box->isInlineTextBox()) { #if DEBUG_ACIB kdDebug(6200) << "isinlinetextbox " << box << (box->object() ? TQString(" contains \"%1\"").arg(TQConstString(static_cast(box->object())->str->s+box->minOffset(), kMin(box->maxOffset() - box->minOffset(), 15L)).string()) : TQString::null) << endl; #endif caret_boxes.append(new CaretBox(box, false, false)); sbp.check(preEnd()); // coalescing has been interrupted coalesceOutsideBoxes = false; } else { #if DEBUG_ACIB kdDebug(6200) << "some replaced or what " << box << endl; #endif // must be an inline-block, inline-table, or any RenderReplaced bool rtl = ps->direction() == RTL; const TQFontMetrics &pfm = ps->fontMetrics(); if (coalesceOutsideBoxes) { if (sbp.equalsBox(box, true, false)) { sbp.it = lastCoalescedBox; Q_ASSERT(!sbp.found); sbp.found = true; } } else { addCreatedInlineBoxEdge(box, pfm, true, rtl); sbp.check(preEnd()); } caret_boxes.append(new CaretBox(box, false, false)); sbp.check(preEnd()); addCreatedInlineBoxEdge(box, pfm, false, rtl); lastCoalescedBox = preEnd(); sbp.check(lastCoalescedBox); coalesceOutsideBoxes = true; }/*end if*/ }/*next box*/ } #undef DEBUG_ACIB void CaretBoxLine::addCreatedFlowBoxInside(InlineFlowBox *flowBox, const TQFontMetrics &fm) /*KDE_NO_EXPORT*/ { CaretBox *caretBox = new CaretBox(flowBox, false, false); caret_boxes.append(caretBox); // afaik an inner flow box can only have the width 0, therefore we don't // have to care for rtl or alignment // ### can empty inline elements have a width? css 2 spec isn't verbose about it caretBox->_y += flowBox->baseline() - fm.ascent(); caretBox->_h = fm.height(); } void CaretBoxLine::addCreatedFlowBoxEdge(InlineFlowBox *flowBox, const TQFontMetrics &fm, bool left, bool rtl) /*KDE_NO_EXPORT*/ { CaretBox *caretBox = new CaretBox(flowBox, true, !left); caret_boxes.append(caretBox); if (left ^ rtl) caretBox->_x -= flowBox->paddingLeft() + flowBox->borderLeft() + 1; else caretBox->_x += caretBox->_w + flowBox->paddingRight() + flowBox->borderRight(); caretBox->_y += flowBox->baseline() - fm.ascent(); caretBox->_h = fm.height(); caretBox->_w = 1; } void CaretBoxLine::addCreatedInlineBoxEdge(InlineBox *box, const TQFontMetrics &fm, bool left, bool rtl) /*KDE_NO_EXPORT*/ { CaretBox *caretBox = new CaretBox(box, true, !left); caret_boxes.append(caretBox); if (left ^ rtl) caretBox->_x--; else caretBox->_x += caretBox->_w; caretBox->_y += box->baseline() - fm.ascent(); caretBox->_h = fm.height(); caretBox->_w = 1; } CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, InlineFlowBox *basicFlowBox, InlineBox *seekBox, bool seekOutside, bool seekOutsideEnd, CaretBoxIterator &iter, RenderObject *seekObject) // KDE_NO_EXPORT { // Iterate all inline boxes within this inline flow box. // Caret boxes will be created for each // - outside begin of an inline flow box (except for the basic inline flow box) // - outside end of an inline flow box (except for the basic inline flow box) // - inside of an empty inline flow box // - outside begin of an inline box resembling a replaced element // - outside end of an inline box resembling a replaced element // - inline text box // - inline replaced box CaretBoxLine *result = new CaretBoxLine(basicFlowBox); deleter->append(result); SeekBoxParams sbp(seekBox, seekOutside, seekOutsideEnd, seekObject, iter); // iterate recursively, I'm too lazy to do it iteratively result->addConvertedInlineBox(basicFlowBox, sbp); if (!sbp.found) sbp.it = result->end(); return result; } CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, RenderBox *cb, bool outside, bool outsideEnd, CaretBoxIterator &iter) /*KDE_NO_EXPORT*/ { int _x = cb->xPos(); int _y = cb->yPos(); int height; int width = 1; // no override is indicated in boxes if (outside) { RenderStyle *s = cb->element() && cb->element()->parent() && cb->element()->parent()->renderer() ? cb->element()->parent()->renderer()->style() : cb->style(); bool rtl = s->direction() == RTL; const TQFontMetrics &fm = s->fontMetrics(); height = fm.height(); if (!outsideEnd) { _x--; } else { _x += cb->width(); } int hl = fm.leading() / 2; int baseline = cb->baselinePosition(false); if (!cb->isReplaced() || cb->style()->display() == BLOCK) { if (!outsideEnd ^ rtl) _y -= fm.leading() / 2; else _y += kMax(cb->height() - fm.ascent() - hl, 0); } else { _y += baseline - fm.ascent() - hl; } } else { // !outside RenderStyle *s = cb->style(); const TQFontMetrics &fm = s->fontMetrics(); height = fm.height(); _x += cb->borderLeft() + cb->paddingLeft(); _y += cb->borderTop() + cb->paddingTop(); // ### regard direction switch (s->textAlign()) { case LEFT: case TDEHTML_LEFT: case TAAUTO: // ### find out what this does case JUSTIFY: break; case CENTER: case TDEHTML_CENTER: _x += cb->contentWidth() / 2; break; case TDEHTML_RIGHT: case RIGHT: _x += cb->contentWidth(); break; }/*end switch*/ }/*end if*/ CaretBoxLine *result = new CaretBoxLine; deleter->append(result); result->caret_boxes.append(new CaretBox(_x, _y, width, height, cb, outside, outsideEnd)); iter = result->begin(); return result; } #if DEBUG_CARETMODE > 0 void CaretBoxLine::dump(TQTextStream &ts, const TQString &ind) const { ts << ind << "cbl: baseFlowBox@" << basefb << endl; TQString ind2 = ind + " "; for (size_t i = 0; i < caret_boxes.size(); i++) { if (i > 0) ts << endl; caret_boxes[i]->dump(ts, ind2); } } #endif // == caret mode related helper functions /** seeks the root line box that is the parent of the given inline box. * @param b given inline box * @param base base render object which not to step over. If \c base's * inline flow box is hit before the root line box, the flow box * is returned instead. * @return the root line box or the base flow box. */ inline InlineFlowBox *seekBaseFlowBox(InlineBox *b, RenderObject *base = 0) { // Seek root line box or base inline flow box, if \c base is interfering. while (b->parent() && b->object() != base) { b = b->parent(); }/*wend*/ Q_ASSERT(b->isInlineFlowBox()); return static_cast(b); } /** determines whether the given element is a block level replaced element. */ inline bool isBlockRenderReplaced(RenderObject *r) { return r->isRenderReplaced() && r->style()->display() == BLOCK; } /** determines the caret line box that contains the given position. * * If the node does not map to a render object, the function will snap to * the next suitable render object following it. * * @param node node to begin with * @param offset zero-based offset within node. * @param cblDeleter deleter for caret box lines * @param base base render object which the caret must not be placed beyond. * @param r_ofs adjusted offset within render object * @param caretBoxIt returns an iterator to the caret box that contains the * given position. * @return the determined caret box lineor 0 if either the node is 0 or * there is no inline flow box containing this node. The containing block * will still be set. If it is 0 too, @p node was invalid. */ static CaretBoxLine* findCaretBoxLine(DOM::NodeImpl *node, long offset, CaretBoxLineDeleter *cblDeleter, RenderObject *base, long &r_ofs, CaretBoxIterator &caretBoxIt) { bool outside, outsideEnd; RenderObject *r = findRenderer(node, offset, base, r_ofs, outside, outsideEnd); if (!r) { return 0; } #if DEBUG_CARETMODE > 0 kdDebug(6200) << "=================== findCaretBoxLine" << endl; kdDebug(6200) << "node " << node << " offset: " << offset << " r " << r->renderName() << "[" << r << "].node " << r->element()->nodeName().string() << "[" << r->element() << "]" << " r_ofs " << r_ofs << " outside " << outside << " outsideEnd " << outsideEnd << endl; #endif // There are two strategies to find the correct line box. (The third is failsafe) // (A) First, if node's renderer is a RenderText, we only traverse its text // runs and return the root line box (saves much time for long blocks). // This should be the case 99% of the time. // (B) Second, we derive the inline flow box directly when the renderer is // a RenderBlock, RenderInline, or blocked RenderReplaced. // (C) Otherwise, we iterate linearly through all line boxes in order to find // the renderer. // (A) if (r->isText()) do { RenderText *t = static_cast(r); int dummy; InlineBox *b = t->findInlineTextBox(offset, dummy, true); // Actually b should never be 0, but some render texts don't have text // boxes, so we insert the last run as an error correction. // If there is no last run, we resort to (B) if (!b) { if (t->m_lines.count() > 0) b = t->m_lines[t->m_lines.count() - 1]; else break; }/*end if*/ Q_ASSERT(b); outside = false; // text boxes cannot have outside positions InlineFlowBox *baseFlowBox = seekBaseFlowBox(b, base); #if DEBUG_CARETMODE > 2 kdDebug(6200) << "text-box b: " << b << " baseFlowBox: " << baseFlowBox << (b && b->object() ? TQString(" contains \"%1\"").arg(TQConstString(static_cast(b->object())->str->s+b->minOffset(), kMin(b->maxOffset() - b->minOffset(), 15L)).string()) : TQString::null) << endl; #endif #if 0 if (t->containingBlock()->isListItem()) dumpLineBoxes(static_cast(t->containingBlock())); #endif #if DEBUG_CARETMODE > 0 kdDebug(6200) << "=================== end findCaretBoxLine (renderText)" << endl; #endif return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, b, outside, outsideEnd, caretBoxIt); } while(false);/*end if*/ // (B) bool isrepl = isBlockRenderReplaced(r); if (r->isRenderBlock() || r->isRenderInline() || isrepl) { RenderFlow *flow = static_cast(r); InlineFlowBox *firstLineBox = isrepl ? 0 : flow->firstLineBox(); // On render blocks, if we are outside, or have a totally empty render // block, we simply construct a special caret box line. // The latter case happens only when the render block is a leaf object itself. if (isrepl || r->isRenderBlock() && (outside || !firstLineBox) || r->isRenderInline() && !firstLineBox) { #if DEBUG_CARETMODE > 0 kdDebug(6200) << "=================== end findCaretBoxLine (box " << (outside ? (outsideEnd ? "outside end" : "outside begin") : "inside") << ")" << endl; #endif Q_ASSERT(r->isBox()); return CaretBoxLine::constructCaretBoxLine(cblDeleter, static_cast(r), outside, outsideEnd, caretBoxIt); }/*end if*/ kdDebug(6200) << "firstlinebox " << firstLineBox << endl; InlineFlowBox *baseFlowBox = seekBaseFlowBox(firstLineBox, base); return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, firstLineBox, outside, outsideEnd, caretBoxIt); }/*end if*/ RenderBlock *cb = r->containingBlock(); //if ( !cb ) return 0L; Q_ASSERT(cb); // ### which element doesn't have a block as its containing block? // Is it still possible after the RenderBlock/RenderInline merge? if (!cb->isRenderBlock()) { kdWarning() << "containing block is no render block!!! crash imminent" << endl; }/*end if*/ InlineFlowBox *flowBox = cb->firstLineBox(); // (C) // This case strikes when the element is replaced, but neither a // RenderBlock nor a RenderInline if (!flowBox) { // ### utter emergency (why is this possible at all?) // flowBox = generateDummyFlowBox(arena, cb, r); // if (ibox) *ibox = flowBox->firstChild(); // outside = outside_end = true; // kdWarning() << "containing block contains no inline flow boxes!!! crash imminent" << endl; #if DEBUG_CARETMODE > 0 kdDebug(6200) << "=================== end findCaretBoxLine (2)" << endl; #endif return CaretBoxLine::constructCaretBoxLine(cblDeleter, cb, outside, outsideEnd, caretBoxIt); }/*end if*/ // We iterate the inline flow boxes of the containing block until // we find the given node. This has one major flaw: it is linear, and therefore // painfully slow for really large blocks. for (; flowBox; flowBox = static_cast(flowBox->nextLineBox())) { #if DEBUG_CARETMODE > 0 kdDebug(6200) << "[scan line]" << endl; #endif // construct a caret line box and stop when the element is contained within InlineFlowBox *baseFlowBox = seekBaseFlowBox(flowBox, base); CaretBoxLine *cbl = CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, 0, outside, outsideEnd, caretBoxIt, r); #if DEBUG_CARETMODE > 5 kdDebug(6200) << cbl->information() << endl; #endif if (caretBoxIt != cbl->end()) { #if DEBUG_CARETMODE > 0 kdDebug(6200) << "=================== end findCaretBoxLine (3)" << endl; #endif return cbl; } }/*next flowBox*/ // no inline flow box found, approximate to nearest following node. // Danger: this is O(n^2). It's only called to recover from // errors, that means, theoretically, never. (Practically, far too often :-( ) Q_ASSERT(!flowBox); CaretBoxLine *cbl = findCaretBoxLine(nextLeafNode(node, base ? base->element() : 0), 0, cblDeleter, base, r_ofs, caretBoxIt); #if DEBUG_CARETMODE > 0 kdDebug(6200) << "=================== end findCaretBoxLine" << endl; #endif return cbl; } /** finds the innermost table object @p r is contained within, but no * farther than @p cb. * @param r leaf element to begin with * @param cb bottom element where to stop search at least. * @return the table object or 0 if none found. */ static inline RenderTable *findTableUpTo(RenderObject *r, RenderFlow *cb) { while (r && r != cb && !r->isTable()) r = r->parent(); return r && r->isTable() ? static_cast(r) : 0; } /** checks whether @p r is a descendant of @p cb, or r == cb */ static inline bool isDescendant(RenderObject *r, RenderObject *cb) { while (r && r != cb) r = r->parent(); return r; } /** checks whether the given block contains at least one editable element. * * Warning: This function has linear complexity, and therefore is expensive. * Use it sparingly, and cache the result. * @param part part * @param cb block to be searched * @param table returns the nested table if there is one directly at the beginning * or at the end. * @param fromEnd begin search from end (default: begin from beginning) */ static bool containsEditableElement(TDEHTMLPart *part, RenderBlock *cb, RenderTable *&table, bool fromEnd = false) { RenderObject *r = cb; if (fromEnd) while (r->lastChild()) r = r->lastChild(); else while (r->firstChild()) r = r->firstChild(); RenderTable *tempTable = 0; table = 0; bool withinCb; // int state; // not used ObjectTraversalState trav = InsideDescending; do { bool modWithinCb = withinCb = isDescendant(r, cb); // treat cb extra, it would not be considered otherwise if (!modWithinCb) { modWithinCb = true; r = cb; } else tempTable = findTableUpTo(r, cb); #if DEBUG_CARETMODE > 1 kdDebug(6201) << "cee: r " << (r ? r->renderName() : TQString::null) << "@" << r << " cb " << cb << " withinCb " << withinCb << " modWithinCb " << modWithinCb << " tempTable " << tempTable << endl; #endif if (r && modWithinCb && r->element() && !isUnsuitable(r, trav) && (part->isCaretMode() || part->isEditable() || r->style()->userInput() == UI_ENABLED)) { table = tempTable; #if DEBUG_CARETMODE > 1 kdDebug(6201) << "cee: editable" << endl; #endif return true; }/*end if*/ // RenderObject *oldr = r; // while (r && r == oldr) // r = advanceSuitableObject(r, trav, fromEnd, cb->parent(), state); r = fromEnd ? r->objectAbove() : r->objectBelow(); } while (r && withinCb); return false; } /** checks whether the given block contains at least one editable child * element, beginning with but excluding @p start. * * Warning: This function has linear complexity, and therefore is expensive. * Use it sparingly, and cache the result. * @param part part * @param cb block to be searched * @param table returns the nested table if there is one directly before/after * the start object. * @param fromEnd begin search from end (default: begin from beginning) * @param start object after which to begin search. */ static bool containsEditableChildElement(TDEHTMLPart *part, RenderBlock *cb, RenderTable *&table, bool fromEnd, RenderObject *start) { int state = 0; ObjectTraversalState trav = OutsideAscending; // kdDebug(6201) << "start: " << start << endl; RenderObject *r = start; do { r = traverseRenderObjects(r, trav, fromEnd, cb->parent(), state); } while(r && !(state & AdvancedToSibling)); // kdDebug(6201) << "r: " << r << endl; //advanceObject(start, trav, fromEnd, cb->parent(), state); // RenderObject *oldr = r; // while (r && r == oldr) if (!r) return false; if (fromEnd) while (r->firstChild()) r = r->firstChild(); else while (r->lastChild()) r = r->lastChild(); // kdDebug(6201) << "child r: " << r << endl; if (!r) return false; RenderTable *tempTable = 0; table = 0; bool withinCb = false; do { bool modWithinCb = withinCb = isDescendant(r, cb); // treat cb extra, it would not be considered otherwise if (!modWithinCb) { modWithinCb = true; r = cb; } else tempTable = findTableUpTo(r, cb); #if DEBUG_CARETMODE > 1 kdDebug(6201) << "cece: r " << (r ? r->renderName() : TQString::null) << "@" << r << " cb " << cb << " withinCb " << withinCb << " modWithinCb " << modWithinCb << " tempTable " << tempTable << endl; #endif if (r && withinCb && r->element() && !isUnsuitable(r, trav) && (part->isCaretMode() || part->isEditable() || r->style()->userInput() == UI_ENABLED)) { table = tempTable; #if DEBUG_CARETMODE > 1 kdDebug(6201) << "cece: editable" << endl; #endif return true; }/*end if*/ r = fromEnd ? r->objectAbove() : r->objectBelow(); } while (withinCb); return false; } // == class LinearDocument implementation LinearDocument::LinearDocument(TDEHTMLPart *part, NodeImpl *node, long offset, CaretAdvancePolicy advancePolicy, ElementImpl *baseElem) : node(node), offset(offset), m_part(part), advPol(advancePolicy), base(0) { if (node == 0) return; if (baseElem) { RenderObject *b = baseElem->renderer(); if (b && (b->isRenderBlock() || b->isRenderInline())) base = b; } initPreBeginIterator(); initEndIterator(); } LinearDocument::~LinearDocument() { } int LinearDocument::count() const { // FIXME: not implemented return 1; } LinearDocument::Iterator LinearDocument::current() { return LineIterator(this, node, offset); } LinearDocument::Iterator LinearDocument::begin() { NodeImpl *n = base ? base->element() : 0; if (!base) n = node ? node->getDocument() : 0; if (!n) return end(); n = n->firstChild(); if (advPol == LeafsOnly) while (n->firstChild()) n = n->firstChild(); if (!n) return end(); // must be empty document or empty base element return LineIterator(this, n, n->minOffset()); } LinearDocument::Iterator LinearDocument::preEnd() { NodeImpl *n = base ? base->element() : 0; if (!base) n = node ? node->getDocument() : 0; if (!n) return preBegin(); n = n->lastChild(); if (advPol == LeafsOnly) while (n->lastChild()) n = n->lastChild(); if (!n) return preBegin(); // must be empty document or empty base element return LineIterator(this, n, n->maxOffset()); } void LinearDocument::initPreBeginIterator() { _preBegin = LineIterator(this, 0, 0); } void LinearDocument::initEndIterator() { _end = LineIterator(this, 0, 1); } // == class LineIterator implementation CaretBoxIterator LineIterator::currentBox /*KDE_NO_EXPORT*/; long LineIterator::currentOffset /*KDE_NO_EXPORT*/; LineIterator::LineIterator(LinearDocument *l, DOM::NodeImpl *node, long offset) : lines(l) { // kdDebug(6200) << "LineIterator: node " << node << " offset " << offset << endl; if (!node) { cbl = 0; return; } cbl = findCaretBoxLine(node, offset, &lines->cblDeleter, l->baseObject(), currentOffset, currentBox); // can happen on partially loaded documents #if DEBUG_CARETMODE > 0 if (!cbl) kdDebug(6200) << "no render object found!" << endl; #endif if (!cbl) return; #if DEBUG_CARETMODE > 1 kdDebug(6200) << "LineIterator: offset " << offset << " outside " << cbl->isOutside() << endl; #endif #if DEBUG_CARETMODE > 3 kdDebug(6200) << cbl->information() << endl; #endif if (currentBox == cbl->end()) { #if DEBUG_CARETMODE > 0 kdDebug(6200) << "LineIterator: findCaretBoxLine failed" << endl; #endif cbl = 0; }/*end if*/ } void LineIterator::nextBlock() { RenderObject *base = lines->baseObject(); bool cb_outside = cbl->isOutside(); bool cb_outside_end = cbl->isOutsideEnd(); { RenderObject *r = cbl->enclosingObject(); ObjectTraversalState trav; int state; // not used mapRenderPosToTraversalState(cb_outside, cb_outside_end, false, trav); #if DEBUG_CARETMODE > 1 kdDebug(6200) << "nextBlock: before adv r" << r << " " << (r ? r->renderName() : TQString::null) << (r && r->isText() ? " contains \"" + TQString(((RenderText *)r)->str->s, TQMIN(((RenderText *)r)->str->l,15)) + "\"" : TQString::null) << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end << endl; #endif r = advanceSuitableObject(r, trav, false, base, state); if (!r) { cbl = 0; return; }/*end if*/ mapTraversalStateToRenderPos(trav, false, cb_outside, cb_outside_end); #if DEBUG_CARETMODE > 1 kdDebug(6200) << "nextBlock: after r" << r << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end << endl; #endif #if DEBUG_CARETMODE > 0 kdDebug(6200) << "++: r " << r << "[" << (r?r->renderName():TQString::null) << "]" << endl; #endif RenderBlock *cb; // If we hit a block or replaced object, use this as its enclosing object bool isrepl = isBlockRenderReplaced(r); if (r->isRenderBlock() || isrepl) { RenderBox *cb = static_cast(r); cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); #if DEBUG_CARETMODE > 0 kdDebug(6200) << "r->isFlow is cb. continuation @" << cb->continuation() << endl; #endif return; } else { cb = r->containingBlock(); Q_ASSERT(cb->isRenderBlock()); }/*end if*/ InlineFlowBox *flowBox = cb->firstLineBox(); #if DEBUG_CARETMODE > 0 kdDebug(6200) << "++: flowBox " << flowBox << " cb " << cb << "[" << (cb?cb->renderName()+TQString(".node ")+TQString::number((unsigned)cb->element(),16)+(cb->element()?"@"+cb->element()->nodeName().string():TQString::null):TQString::null) << "]" << endl; #endif Q_ASSERT(flowBox); if (!flowBox) { // ### utter emergency (why is this possible at all?) cb_outside = cb_outside_end = true; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); return; } bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); } } void LineIterator::prevBlock() { RenderObject *base = lines->baseObject(); bool cb_outside = cbl->isOutside(); bool cb_outside_end = cbl->isOutsideEnd(); { RenderObject *r = cbl->enclosingObject(); if (r->isAnonymous() && !cb_outside) cb_outside = true, cb_outside_end = false; ObjectTraversalState trav; int state; // not used mapRenderPosToTraversalState(cb_outside, cb_outside_end, true, trav); #if DEBUG_CARETMODE > 1 kdDebug(6200) << "prevBlock: before adv r" << r << " " << (r ? r->renderName() : TQString::null) << (r && r->isText() ? " contains \"" + TQString(((RenderText *)r)->str->s, TQMIN(((RenderText *)r)->str->l,15)) + "\"" : TQString::null) << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end << endl; #endif r = advanceSuitableObject(r, trav, true, base, state); if (!r) { cbl = 0; return; }/*end if*/ mapTraversalStateToRenderPos(trav, true, cb_outside, cb_outside_end); #if DEBUG_CARETMODE > 1 kdDebug(6200) << "prevBlock: after r" << r << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end << endl; #endif #if DEBUG_CARETMODE > 0 kdDebug(6200) << "--: r " << r << "[" << (r?r->renderName():TQString::null) << "]" << endl; #endif RenderBlock *cb; // If we hit a block, use this as its enclosing object bool isrepl = isBlockRenderReplaced(r); // kdDebug(6200) << "isrepl " << isrepl << " isblock " << r->isRenderBlock() << endl; if (r->isRenderBlock() || isrepl) { RenderBox *cb = static_cast(r); cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); #if DEBUG_CARETMODE > 0 kdDebug(6200) << "r->isFlow is cb. continuation @" << cb->continuation() << endl; #endif return; } else { cb = r->containingBlock(); Q_ASSERT(cb->isRenderBlock()); }/*end if*/ InlineFlowBox *flowBox = cb->lastLineBox(); #if DEBUG_CARETMODE > 0 kdDebug(6200) << "--: flowBox " << flowBox << " cb " << cb << "[" << (cb?cb->renderName()+TQString(".node ")+TQString::number((unsigned)cb->element(),16)+(cb->element()?"@"+cb->element()->nodeName().string():TQString::null):TQString::null) << "]" << endl; #endif Q_ASSERT(flowBox); if (!flowBox) { // ### utter emergency (why is this possible at all?) cb_outside = true; cb_outside_end = false; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); return; } bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); } } void LineIterator::advance(bool toBegin) { InlineFlowBox *flowBox = cbl->baseFlowBox(); if (flowBox) { flowBox = static_cast(toBegin ? flowBox->prevLineBox() : flowBox->nextLineBox()); if (flowBox) { bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); }/*end if*/ }/*end if*/ // if there are no more lines in this block, move towards block to come if (!flowBox) { if (toBegin) prevBlock(); else nextBlock(); } #if DEBUG_CARETMODE > 3 if (cbl) kdDebug(6200) << cbl->information() << endl; #endif } // == class EditableCaretBoxIterator implementation void EditableCaretBoxIterator::advance(bool toBegin) { #if DEBUG_CARETMODE > 3 kdDebug(6200) << "---------------" << k_funcinfo << "toBegin " << toBegin << endl; #endif const CaretBoxIterator preBegin = cbl->preBegin(); const CaretBoxIterator end = cbl->end(); CaretBoxIterator lastbox = *this, curbox; bool islastuseable = true; // silence gcc bool iscuruseable; // Assume adjacency of caret boxes. Will be falsified later if applicable. adjacent = true; #if DEBUG_CARETMODE > 4 // kdDebug(6200) << "ebit::advance: before: " << (**this)->object() << "@" << (**this)->object()->renderName() << ".node " << (**this)->object()->element() << "[" << ((**this)->object()->element() ? (**this)->object()->element()->nodeName().string() : TQString::null) << "] inline " << (**this)->isInline() << " outside " << (**this)->isOutside() << " outsideEnd " << (**this)->isOutsideEnd() << endl; #endif if (toBegin) CaretBoxIterator::operator --(); else CaretBoxIterator::operator ++(); bool curAtEnd = *this == preBegin || *this == end; curbox = *this; bool atEnd = true; if (!curAtEnd) { iscuruseable = isEditable(curbox, toBegin); if (toBegin) CaretBoxIterator::operator --(); else CaretBoxIterator::operator ++(); atEnd = *this == preBegin || *this == end; } while (!curAtEnd) { bool haslast = lastbox != end && lastbox != preBegin; bool hascoming = !atEnd; bool iscominguseable = true; // silence gcc if (!atEnd) iscominguseable = isEditable(*this, toBegin); if (iscuruseable) { #if DEBUG_CARETMODE > 3 kdDebug(6200) << "ebit::advance: " << (*curbox)->object() << "@" << (*curbox)->object()->renderName() << ".node " << (*curbox)->object()->element() << "[" << ((*curbox)->object()->element() ? (*curbox)->object()->element()->nodeName().string() : TQString::null) << "] inline " << (*curbox)->isInline() << " outside " << (*curbox)->isOutside() << " outsideEnd " << (*curbox)->isOutsideEnd() << endl; #endif CaretBox *box = *curbox; if (box->isOutside()) { // if this caret box represents no inline box, it is an outside box // which has to be considered unconditionally if (!box->isInline()) break; if (advpol == VisibleFlows) break; // IndicatedFlows and LeafsOnly are treated equally in caret box lines InlineBox *ibox = box->inlineBox(); // get previous inline box InlineBox *prev = box->isOutsideEnd() ? ibox : ibox->prevOnLine(); // get next inline box InlineBox *next = box->isOutsideEnd() ? ibox->nextOnLine() : ibox; const bool isprevindicated = !prev || isIndicatedInlineBox(prev); const bool isnextindicated = !next || isIndicatedInlineBox(next); const bool last = haslast && !islastuseable; const bool coming = hascoming && !iscominguseable; const bool left = !prev || prev->isInlineFlowBox() && isprevindicated || (toBegin && coming || !toBegin && last); const bool right = !next || next->isInlineFlowBox() && isnextindicated || (!toBegin && coming || toBegin && last); const bool text2indicated = toBegin && next && next->isInlineTextBox() && isprevindicated || !toBegin && prev && prev->isInlineTextBox() && isnextindicated; const bool indicated2text = !toBegin && next && next->isInlineTextBox() && prev && isprevindicated // ### this code is so broken. /*|| toBegin && prev && prev->isInlineTextBox() && isnextindicated*/; #if DEBUG_CARETMODE > 5 kdDebug(6200) << "prev " << prev << " haslast " << haslast << " islastuseable " << islastuseable << " left " << left << " next " << next << " hascoming " << hascoming << " iscominguseable " << iscominguseable << " right " << right << " text2indicated " << text2indicated << " indicated2text " << indicated2text << endl; #endif if (left && right && !text2indicated || indicated2text) { adjacent = false; #if DEBUG_CARETMODE > 4 kdDebug(6200) << "left && right && !text2indicated || indicated2text" << endl; #endif break; } } else { // inside boxes are *always* valid #if DEBUG_CARETMODE > 4 if (box->isInline()) { InlineBox *ibox = box->inlineBox(); kdDebug(6200) << "inside " << (!ibox->isInlineFlowBox() || static_cast(ibox)->firstChild() ? "non-empty" : "empty") << (isIndicatedInlineBox(ibox) ? " indicated" : "") << " adjacent=" << adjacent << endl; } #if 0 RenderStyle *s = ibox->object()->style(); kdDebug(6200) << "bordls " << s->borderLeftStyle() << " bordl " << (s->borderLeftStyle() != BNONE) << " bordr " << (s->borderRightStyle() != BNONE) << " bordt " << (s->borderTopStyle() != BNONE) << " bordb " << (s->borderBottomStyle() != BNONE) << " padl " << s->paddingLeft().value() << " padr " << s->paddingRight().value() << " padt " << s->paddingTop().value() << " padb " << s->paddingBottom().value() // ### Can inline elements have top/bottom margins? Couldn't find // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. << " marl " << s->marginLeft().value() << " marr " << s->marginRight().value() << endl; #endif #endif break; }/*end if*/ } else { if (!(*curbox)->isOutside()) { // cannot be adjacent anymore adjacent = false; } }/*end if*/ lastbox = curbox; islastuseable = iscuruseable; curbox = *this; iscuruseable = iscominguseable; curAtEnd = atEnd; if (!atEnd) { if (toBegin) CaretBoxIterator::operator --(); else CaretBoxIterator::operator ++(); atEnd = *this == preBegin || *this == end; }/*end if*/ }/*wend*/ *static_cast(this) = curbox; #if DEBUG_CARETMODE > 4 // kdDebug(6200) << "still valid? " << (*this != preBegin && *this != end) << endl; #endif #if DEBUG_CARETMODE > 3 kdDebug(6200) << "---------------" << k_funcinfo << "end " << endl; #endif } bool EditableCaretBoxIterator::isEditable(const CaretBoxIterator &boxit, bool fromEnd) { Q_ASSERT(boxit != cbl->end() && boxit != cbl->preBegin()); CaretBox *b = *boxit; RenderObject *r = b->object(); #if DEBUG_CARETMODE > 0 // if (b->isInlineFlowBox()) kdDebug(6200) << "b is inline flow box" << (outside ? " (outside)" : "") << endl; kdDebug(6200) << "isEditable r" << r << ": " << (r ? r->renderName() : TQString::null) << (r && r->isText() ? " contains \"" + TQString(((RenderText *)r)->str->s, TQMIN(((RenderText *)r)->str->l,15)) + "\"" : TQString::null) << endl; #endif // Must check caret mode or design mode *after* r->element(), otherwise // lines without a backing DOM node get regarded, leading to a crash. // ### check should actually be in InlineBoxIterator NodeImpl *node = r->element(); ObjectTraversalState trav; mapRenderPosToTraversalState(b->isOutside(), b->isOutsideEnd(), fromEnd, trav); if (isUnsuitable(r, trav) || !node) { return false; } // generally exclude replaced elements with no children from navigation if (!b->isOutside() && r->isRenderReplaced() && !r->firstChild()) return false; RenderObject *eff_r = r; bool globallyNavigable = m_part->isCaretMode() || m_part->isEditable(); // calculate the parent element's editability if this inline box is outside. if (b->isOutside() && !globallyNavigable) { NodeImpl *par = node->parent(); // I wonder whether par can be 0. It shouldn't be possible if the // algorithm contained no bugs. Q_ASSERT(par); if (par) node = par; eff_r = node->renderer(); Q_ASSERT(eff_r); // this is a hard requirement } bool result = globallyNavigable || eff_r->style()->userInput() == UI_ENABLED; #if DEBUG_CARETMODE > 0 kdDebug(6200) << result << endl; #endif return result; } // == class EditableLineIterator implementation void EditableLineIterator::advance(bool toBegin) { CaretAdvancePolicy advpol = lines->advancePolicy(); LineIterator lasteditable, lastindicated; bool haslasteditable = false; bool haslastindicated = false; bool uselasteditable = false; LineIterator::advance(toBegin); while (cbl) { if (isEditable(*this)) { #if DEBUG_CARETMODE > 3 kdDebug(6200) << "advance: " << cbl->enclosingObject() << "@" << cbl->enclosingObject()->renderName() << ".node " << cbl->enclosingObject()->element() << "[" << (cbl->enclosingObject()->element() ? cbl->enclosingObject()->element()->nodeName().string() : TQString::null) << "]" << endl; #endif bool hasindicated = isIndicatedFlow(cbl->enclosingObject()); if (hasindicated) { haslastindicated = true; lastindicated = *this; } switch (advpol) { case IndicatedFlows: if (hasindicated) goto wend; // fall through case LeafsOnly: if (cbl->isOutside()) break; // fall through case VisibleFlows: goto wend; }/*end switch*/ // remember rejected editable element lasteditable = *this; haslasteditable = true; #if DEBUG_CARETMODE > 4 kdDebug(6200) << "remembered lasteditable " << *lasteditable << endl; #endif } else { // If this element isn't editable, but the last one was, and it was only // rejected because it didn't match the caret advance policy, force it. // Otherwise certain combinations of editable and uneditable elements // could never be reached with some policies. if (haslasteditable) { uselasteditable = true; break; } } LineIterator::advance(toBegin); }/*wend*/ wend: if (uselasteditable) *this = haslastindicated ? lastindicated : lasteditable; if (!cbl && haslastindicated) *this = lastindicated; } // == class EditableCharacterIterator implementation void EditableCharacterIterator::initFirstChar() { CaretBox *box = *ebit; InlineBox *b = box->inlineBox(); if (_offset == box->maxOffset()) peekNext(); else if (b && !box->isOutside() && b->isInlineTextBox()) _char = static_cast(b->object())->str->s[_offset].unicode(); else _char = -1; } /** returns true when the given caret box is empty, i. e. should not * take place in caret movement. */ static inline bool isCaretBoxEmpty(CaretBox *box) { if (!box->isInline()) return false; InlineBox *ibox = box->inlineBox(); return ibox->isInlineFlowBox() && !static_cast(ibox)->firstChild() && !isIndicatedInlineBox(ibox); } EditableCharacterIterator &EditableCharacterIterator::operator ++() { _offset++; CaretBox *box = *ebit; InlineBox *b = box->inlineBox(); long maxofs = box->maxOffset(); #if DEBUG_CARETMODE > 0 kdDebug(6200) << "box->maxOffset() " << box->maxOffset() << " box->minOffset() " << box->minOffset() << endl; #endif if (_offset == maxofs) { #if DEBUG_CARETMODE > 2 kdDebug(6200) << "_offset == maxofs: " << _offset << " == " << maxofs << endl; #endif peekNext(); } else if (_offset > maxofs) { #if DEBUG_CARETMODE > 2 kdDebug(6200) << "_offset > maxofs: " << _offset << " > " << maxofs /*<< " _peekNext: " << _peekNext*/ << endl; #endif if (/*!_peekNext*/true) { ++ebit; if (ebit == (*_it)->end()) { // end of line reached, go to next line ++_it; #if DEBUG_CARETMODE > 3 kdDebug(6200) << "++_it" << endl; #endif if (_it != _it.lines->end()) { ebit = _it; box = *ebit; b = box->inlineBox(); #if DEBUG_CARETMODE > 3 kdDebug(6200) << "box " << box << " b " << b << " isText " << box->isInlineTextBox() << endl; #endif #if DEBUG_CARETMODE > 3 RenderObject *_r = box->object(); kdDebug(6200) << "_r " << _r << ":" << _r->element()->nodeName().string() << endl; #endif _offset = box->minOffset(); #if DEBUG_CARETMODE > 3 kdDebug(6200) << "_offset " << _offset << endl; #endif } else { b = 0; _end = true; }/*end if*/ goto readchar; }/*end if*/ }/*end if*/ bool adjacent = ebit.isAdjacent(); #if 0 // Jump over element if this one is not a text node. if (adjacent && !(*ebit)->isInlineTextBox()) { EditableCaretBoxIterator copy = ebit; ++ebit; if (ebit != (*_it)->end() && (*ebit)->isInlineTextBox() /*&& (!(*ebit)->isInlineFlowBox() || static_cast(*ebit)->)*/) adjacent = false; else ebit = copy; }/*end if*/ #endif // Jump over empty elements. if (adjacent && !(*ebit)->isInlineTextBox()) { bool noemptybox = true; while (isCaretBoxEmpty(*ebit)) { noemptybox = false; EditableCaretBoxIterator copy = ebit; ++ebit; if (ebit == (*_it)->end()) { ebit = copy; break; } } if (noemptybox) adjacent = false; }/*end if*/ // _r = (*ebit)->object(); /*if (!_it.outside) */_offset = (*ebit)->minOffset() + adjacent; //_peekNext = 0; box = *ebit; b = box->inlineBox(); goto readchar; } else { readchar: // get character if (b && !box->isOutside() && b->isInlineTextBox() && _offset < b->maxOffset()) _char = static_cast(b->object())->str->s[_offset].unicode(); else _char = -1; }/*end if*/ #if DEBUG_CARETMODE > 2 kdDebug(6200) << "_offset: " << _offset /*<< " _peekNext: " << _peekNext*/ << " char '" << (char)_char << "'" << endl; #endif #if DEBUG_CARETMODE > 0 if (!_end && ebit != (*_it)->end()) { CaretBox *box = *ebit; RenderObject *_r = box->object(); kdDebug(6200) << "echit++(1): box " << box << (box && box->isInlineTextBox() ? TQString(" contains \"%1\"").arg(TQConstString(static_cast(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : TQString::null) << " _r " << (_r ? _r->element()->nodeName().string() : TQString("")) << endl; } #endif return *this; } EditableCharacterIterator &EditableCharacterIterator::operator --() { _offset--; //kdDebug(6200) << "--: _offset=" << _offset << endl; CaretBox *box = *ebit; CaretBox *_peekPrev = 0; CaretBox *_peekNext = 0; InlineBox *b = box->inlineBox(); long minofs = box->minOffset(); #if DEBUG_CARETMODE > 0 kdDebug(6200) << "box->maxOffset() " << box->maxOffset() << " box->minOffset() " << box->minOffset() << endl; #endif if (_offset == minofs) { #if DEBUG_CARETMODE > 2 kdDebug(6200) << "_offset == minofs: " << _offset << " == " << minofs << endl; #endif // _peekNext = b; // get character if (b && !box->isOutside() && b->isInlineTextBox()) _char = static_cast(b->object())->text()[_offset].unicode(); else _char = -1; //peekPrev(); bool do_prev = false; { EditableCaretBoxIterator copy; _peekPrev = 0; do { copy = ebit; --ebit; if (ebit == (*_it)->preBegin()) { ebit = copy; break; } } while (isCaretBoxEmpty(*ebit)); // Jump to end of previous element if it's adjacent, and a text box if (ebit.isAdjacent() && ebit != (*_it)->preBegin() && (*ebit)->isInlineTextBox()) { _peekPrev = *ebit; do_prev = true; } else ebit = copy; } if (do_prev) goto prev; } else if (_offset < minofs) { prev: #if DEBUG_CARETMODE > 2 kdDebug(6200) << "_offset < minofs: " << _offset << " < " << minofs /*<< " _peekNext: " << _peekNext*/ << endl; #endif if (!_peekPrev) { _peekNext = *ebit; --ebit; if (ebit == (*_it)->preBegin()) { // end of line reached, go to previous line --_it; #if DEBUG_CARETMODE > 3 kdDebug(6200) << "--_it" << endl; #endif if (_it != _it.lines->preBegin()) { // kdDebug(6200) << "begin from end!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl; ebit = EditableCaretBoxIterator(_it, true); box = *ebit; // RenderObject *r = box->object(); #if DEBUG_CARETMODE > 3 kdDebug(6200) << "box " << box << " b " << box->inlineBox() << " isText " << box->isInlineTextBox() << endl; #endif _offset = box->maxOffset(); // if (!_it.outside) _offset = r->isBR() ? (*ebit)->minOffset() : (*ebit)->maxOffset(); _char = -1; #if DEBUG_CARETMODE > 0 kdDebug(6200) << "echit--(2): box " << box << " b " << box->inlineBox() << (box->isInlineTextBox() ? TQString(" contains \"%1\"").arg(TQConstString(static_cast(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : TQString::null) << endl; #endif } else _end = true; return *this; }/*end if*/ }/*end if*/ #if DEBUG_CARETMODE > 0 bool adjacent = ebit.isAdjacent(); kdDebug(6200) << "adjacent " << adjacent << " _peekNext " << _peekNext << " _peekNext->isInlineTextBox: " << (_peekNext ? _peekNext->isInlineTextBox() : false) << " !((*ebit)->isInlineTextBox): " << (*ebit ? !(*ebit)->isInlineTextBox() : true) << endl; #endif #if 0 // Ignore this box if it isn't a text box, but the previous box was if (adjacent && _peekNext && _peekNext->isInlineTextBox() && !(*ebit)->isInlineTextBox()) { EditableCaretBoxIterator copy = ebit; --ebit; if (ebit == (*_it)->preBegin()) /*adjacent = false; else */ ebit = copy; }/*end if*/ #endif #if 0 // Jump over empty elements. if (adjacent //&& _peekNext && _peekNext->isInlineTextBox() && !(*ebit)->isInlineTextBox()) { bool noemptybox = true; while (isCaretBoxEmpty(*ebit)) { noemptybox = false; EditableCaretBoxIterator copy = ebit; --ebit; if (ebit == (*_it)->preBegin()) { ebit = copy; break; } else _peekNext = *copy; } if (noemptybox) adjacent = false; }/*end if*/ #endif #if DEBUG_CARETMODE > 0 kdDebug(6200) << "(*ebit)->obj " << (*ebit)->object()->renderName() << "[" << (*ebit)->object() << "]" << " minOffset: " << (*ebit)->minOffset() << " maxOffset: " << (*ebit)->maxOffset() << endl; #endif #if DEBUG_CARETMODE > 3 RenderObject *_r = (*ebit)->object(); kdDebug(6200) << "_r " << _r << ":" << _r->element()->nodeName().string() << endl; #endif _offset = (*ebit)->maxOffset(); // if (!_it.outside) _offset = (*ebit)->maxOffset()/* - adjacent*/; #if DEBUG_CARETMODE > 3 kdDebug(6200) << "_offset " << _offset << endl; #endif _peekPrev = 0; } else { #if DEBUG_CARETMODE > 0 kdDebug(6200) << "_offset: " << _offset << " _peekNext: " << _peekNext << endl; #endif // get character if (_peekNext && _offset >= box->maxOffset() && _peekNext->isInlineTextBox()) _char = static_cast(_peekNext->object())->text()[_peekNext->minOffset()].unicode(); else if (b && _offset < b->maxOffset() && b->isInlineTextBox()) _char = static_cast(b->object())->text()[_offset].unicode(); else _char = -1; }/*end if*/ #if DEBUG_CARETMODE > 0 if (!_end && ebit != (*_it)->preBegin()) { CaretBox *box = *ebit; kdDebug(6200) << "echit--(1): box " << box << " b " << box->inlineBox() << (box->isInlineTextBox() ? TQString(" contains \"%1\"").arg(TQConstString(static_cast(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : TQString::null) << endl; } #endif return *this; } // == class TableRowIterator implementation TableRowIterator::TableRowIterator(RenderTable *table, bool fromEnd, RenderTableSection::RowStruct *row) : sec(table, fromEnd) { // set index if (*sec) { if (fromEnd) index = (*sec)->grid.size() - 1; else index = 0; }/*end if*/ // initialize with given row if (row && *sec) { while (operator *() != row) if (fromEnd) operator --(); else operator ++(); }/*end if*/ } TableRowIterator &TableRowIterator::operator ++() { index++; if (index >= (int)(*sec)->grid.size()) { ++sec; if (*sec) index = 0; }/*end if*/ return *this; } TableRowIterator &TableRowIterator::operator --() { index--; if (index < 0) { --sec; if (*sec) index = (*sec)->grid.size() - 1; }/*end if*/ return *this; } // == class ErgonomicEditableLineIterator implementation // some decls static RenderTableCell *findNearestTableCellInRow(TDEHTMLPart *part, int x, RenderTableSection::RowStruct *row, bool fromEnd); /** finds the cell corresponding to absolute x-coordinate @p x in the given * table. * * If there is no direct cell, or the cell is not accessible, the function * will return the nearest suitable cell. * @param part part containing the document * @param x absolute x-coordinate * @param it table row iterator, will be adapted accordingly as more rows are * investigated. * @param fromEnd @p true to begin search from end and work towards the * beginning * @return the cell, or 0 if no editable cell was found. */ static inline RenderTableCell *findNearestTableCell(TDEHTMLPart *part, int x, TableRowIterator &it, bool fromEnd) { RenderTableCell *result = 0; while (*it) { result = findNearestTableCellInRow(part, x, *it, fromEnd); if (result) break; if (fromEnd) --it; else ++it; }/*wend*/ return result; } /** finds the nearest editable cell around the given absolute x-coordinate * * It will dive into nested tables as necessary to provide seamless navigation. * * If the cell at @p x is not editable, its left neighbor is tried, then its * right neighbor, then the left neighbor's left neighbor etc. If no * editable cell can be found, 0 is returned. * @param part tdehtml part * @param x absolute x-coordinate * @param row table row to be searched * @param fromEnd @p true, begin from end (applies only to nested tables) * @return the found cell or 0 if no editable cell was found */ static RenderTableCell *findNearestTableCellInRow(TDEHTMLPart *part, int x, RenderTableSection::RowStruct *row, bool fromEnd) { // First pass. Find spatially nearest cell. int n = (int)row->row->size(); int i; for (i = 0; i < n; i++) { RenderTableCell *cell = row->row->at(i); if (!cell || (long)cell == -1) continue; int absx, absy; cell->absolutePosition(absx, absy, false); // ### position: fixed? #if DEBUG_CARETMODE > 1 kdDebug(6201) << "i/n " << i << "/" << n << " absx " << absx << " absy " << absy << endl; #endif // I rely on the assumption that all cells are in ascending visual order // ### maybe this assumption is wrong for bidi? #if DEBUG_CARETMODE > 1 kdDebug(6201) << "x " << x << " < " << (absx + cell->width()) << "?" << endl; #endif if (x < absx + cell->width()) break; }/*next i*/ if (i >= n) i = n - 1; // Second pass. Find editable cell, beginning with the currently found, // extending to the left, and to the right, alternating. for (int cnt = 0; cnt < 2*n; cnt++) { int index = i - ((cnt >> 1) + 1)*(cnt & 1) + (cnt >> 1)*!(cnt & 1); if (index < 0 || index >= n) continue; RenderTableCell *cell = row->row->at(index); if (!cell || (long)cell == -1) continue; #if DEBUG_CARETMODE > 1 kdDebug(6201) << "index " << index << " cell " << cell << endl; #endif RenderTable *nestedTable; if (containsEditableElement(part, cell, nestedTable, fromEnd)) { if (nestedTable) { TableRowIterator it(nestedTable, fromEnd); while (*it) { // kdDebug(6201) << "=== recursive invocation" << endl; cell = findNearestTableCell(part, x, it, fromEnd); if (cell) break; if (fromEnd) --it; else ++it; }/*wend*/ }/*end if*/ return cell; }/*end if*/ }/*next i*/ return 0; } /** returns the nearest common ancestor of two objects that is a table cell, * a table section, or 0 if not inside a common table. * * If @p r1 and @p r2 belong to the same table, but different sections, @p r1's * section is returned. */ static RenderObject *commonAncestorTableSectionOrCell(RenderObject *r1, RenderObject *r2) { if (!r1 || !r2) return 0; RenderTableSection *sec = 0; int start_depth=0, end_depth=0; // First we find the depths of the two objects in the tree (start_depth, end_depth) RenderObject *n = r1; while (n->parent()) { n = n->parent(); start_depth++; }/*wend*/ n = r2; while( n->parent()) { n = n->parent(); end_depth++; }/*wend*/ // here we climb up the tree with the deeper object, until both objects have equal depth while (end_depth > start_depth) { r2 = r2->parent(); end_depth--; }/*wend*/ while (start_depth > end_depth) { r1 = r1->parent(); // if (r1->isTableSection()) sec = static_cast(r1); start_depth--; }/*wend*/ // Climb the tree with both r1 and r2 until they are the same while (r1 != r2){ r1 = r1->parent(); if (r1->isTableSection()) sec = static_cast(r1); r2 = r2->parent(); }/*wend*/ // At this point, we found the most approximate common ancestor. Now climb // up until the condition of the function return value is satisfied. while (r1 && !r1->isTableCell() && !r1->isTableSection() && !r1->isTable()) r1 = r1->parent(); return r1 && r1->isTable() ? sec : r1; } /** Finds the row that contains the given cell, directly, or indirectly * @param section section to be searched * @param cell table cell * @param row returns the row * @param directCell returns the direct cell that contains @p cell * @return the index of the row. */ static int findRowInSection(RenderTableSection *section, RenderTableCell *cell, RenderTableSection::RowStruct *&row, RenderTableCell *&directCell) { // Seek direct cell RenderObject *r = cell; while (r != section) { if (r->isTableCell()) directCell = static_cast(r); r = r->parent(); }/*wend*/ // So, and this is really nasty: As we have no indices, we have to do a // linear comparison. Oh, that sucks so much for long tables, you can't // imagine. int n = section->numRows(); for (int i = 0; i < n; i++) { row = §ion->grid[i]; // check for cell int m = row->row->size(); for (int j = 0; j < m; j++) { RenderTableCell *c = row->row->at(j); if (c == directCell) return i; }/*next j*/ }/*next i*/ Q_ASSERT(false); return -1; } /** finds the table that is the first direct or indirect descendant of @p block. * @param leaf object to begin search from. * @param block object to search to, or 0 to search up to top. * @return the table or 0 if there were none. */ static inline RenderTable *findFirstDescendantTable(RenderObject *leaf, RenderBlock *block) { RenderTable *result = 0; while (leaf && leaf != block) { if (leaf->isTable()) result = static_cast(leaf); leaf = leaf->parent(); }/*wend*/ return result; } /** looks for the table cell the given object @p r is contained within. * @return the table cell or 0 if not contained in any table. */ static inline RenderTableCell *containingTableCell(RenderObject *r) { while (r && !r->isTableCell()) r = r->parent(); return static_cast(r); } inline void ErgonomicEditableLineIterator::calcAndStoreNewLine( RenderBlock *newBlock, bool toBegin) { // take the first/last editable element in the found cell as the new // value for the iterator CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, newBlock, true, toBegin, it); #if DEBUG_CARETMODE > 3 kdDebug(6201) << cbl->information() << endl; #endif // if (toBegin) prevBlock(); else nextBlock(); if (!cbl) { return; }/*end if*/ EditableLineIterator::advance(toBegin); } void ErgonomicEditableLineIterator::determineTopologicalElement( RenderTableCell *oldCell, RenderObject *newObject, bool toBegin) { // When we arrive here, a transition between cells has happened. // Now determine the type of the transition. This can be // (1) a transition from this cell into a table inside this cell. // (2) a transition from this cell into another cell of this table TableRowIterator it; RenderObject *commonAncestor = commonAncestorTableSectionOrCell(oldCell, newObject); #if DEBUG_CARETMODE > 1 kdDebug(6201) << " ancestor " << commonAncestor << endl; #endif // The whole document is treated as a table cell. if (!commonAncestor || commonAncestor->isTableCell()) { // (1) RenderTableCell *cell = static_cast(commonAncestor); RenderTable *table = findFirstDescendantTable(newObject, cell); #if DEBUG_CARETMODE > 0 kdDebug(6201) << "table cell: " << cell << endl; #endif // if there is no table, we fell out of the previous table, and are now // in some table-less block. Therefore, done. if (!table) return; it = TableRowIterator(table, toBegin); } else if (commonAncestor->isTableSection()) { // (2) RenderTableSection *section = static_cast(commonAncestor); RenderTableSection::RowStruct *row; int idx = findRowInSection(section, oldCell, row, oldCell); #if DEBUG_CARETMODE > 1 kdDebug(6201) << "table section: row idx " << idx << endl; #endif it = TableRowIterator(section, idx); // advance rowspan rows int rowspan = oldCell->rowSpan(); while (*it && rowspan--) { if (toBegin) --it; else ++it; }/*wend*/ } else { kdError(6201) << "Neither common cell nor section! " << commonAncestor->renderName() << endl; // will crash on uninitialized table row iterator }/*end if*/ RenderTableCell *cell = findNearestTableCell(lines->m_part, xCoor, it, toBegin); #if DEBUG_CARETMODE > 1 kdDebug(6201) << "findNearestTableCell result: " << cell << endl; #endif RenderBlock *newBlock = cell; if (!cell) { Q_ASSERT(commonAncestor->isTableSection()); RenderTableSection *section = static_cast(commonAncestor); cell = containingTableCell(section); #if DEBUG_CARETMODE > 1 kdDebug(6201) << "containing cell: " << cell << endl; #endif RenderTable *nestedTable; bool editableChild = cell && containsEditableChildElement(lines->m_part, cell, nestedTable, toBegin, section->table()); if (cell && !editableChild) { #if DEBUG_CARETMODE > 1 kdDebug(6201) << "========= recursive invocation outer =========" << endl; #endif determineTopologicalElement(cell, cell->section(), toBegin); #if DEBUG_CARETMODE > 1 kdDebug(6201) << "========= end recursive invocation outer =========" << endl; #endif return; } else if (cell && nestedTable) { #if DEBUG_CARETMODE > 1 kdDebug(6201) << "========= recursive invocation inner =========" << endl; #endif determineTopologicalElement(cell, nestedTable, toBegin); #if DEBUG_CARETMODE > 1 kdDebug(6201) << "========= end recursive invocation inner =========" << endl; #endif return; } else { #if DEBUG_CARETMODE > 1 kdDebug(6201) << "newBlock is table: " << section->table() << endl; #endif RenderObject *r = section->table(); int state; // not used ObjectTraversalState trav = OutsideAscending; r = advanceSuitableObject(r, trav, toBegin, lines->baseObject(), state); if (!r) { cbl = 0; return; } // if (toBegin) prevBlock(); else nextBlock(); newBlock = static_cast(!r || r->isRenderBlock() ? r : r->containingBlock()); }/*end if*/ #if 0 } else { // adapt cell so that prevBlock/nextBlock works as expected newBlock = cell; // on forward advancing, we must start from the outside end of the // previous object if (!toBegin) { RenderObject *r = newBlock; int state; // not used ObjectTraversalState trav = OutsideAscending; r = advanceSuitableObject(r, trav, true, lines->advancePolicy(), lines->baseObject(), state); newBlock = static_cast(!r || r->isRenderBlock() ? r : r->containingBlock()); }/*end if*/ #endif }/*end if*/ calcAndStoreNewLine(newBlock, toBegin); } ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator ++() { RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); EditableLineIterator::operator ++(); if (*this == lines->end() || *this == lines->preBegin()) return *this; RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); if (!newCell || newCell == oldCell) return *this; determineTopologicalElement(oldCell, newCell, false); return *this; } ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator --() { RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); EditableLineIterator::operator --(); if (*this == lines->end() || *this == lines->preBegin()) return *this; RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); if (!newCell || newCell == oldCell) return *this; determineTopologicalElement(oldCell, newCell, true); return *this; } // == Navigational helper functions == /** seeks the caret box which contains or is the nearest to @p x * @param it line iterator pointing to line to be searched * @param cv caret view context * @param x returns the cv->origX approximation, relatively positioned to the * containing block. * @param absx returns absolute x-coordinate of containing block * @param absy returns absolute y-coordinate of containing block * @return the most suitable caret box */ static CaretBox *nearestCaretBox(LineIterator &it, CaretViewContext *cv, int &x, int &absx, int &absy) { // Find containing block RenderObject *cb = (*it)->containingBlock(); #if DEBUG_CARETMODE > 4 kdDebug(6200) << "nearestCB: cb " << cb << "@" << (cb ? cb->renderName() : "") << endl; #endif if (cb) cb->absolutePosition(absx, absy); else absx = absy = 0; // Otherwise find out in which inline box the caret is to be placed. // this horizontal position is to be approximated x = cv->origX - absx; CaretBox *caretBox = 0; // Inline box containing the caret // NodeImpl *lastnode = 0; // node of previously checked render object. int xPos; // x-coordinate of current inline box int oldXPos = -1; // x-coordinate of last inline box EditableCaretBoxIterator fbit = it; #if DEBUG_CARETMODE > 0 /* if (it.linearDocument()->advancePolicy() != LeafsOnly) kdWarning() << "nearestInlineBox is only prepared to handle the LeafsOnly advance policy" << endl;*/ // kdDebug(6200) << "*fbit = " << *fbit << endl; #endif // Iterate through all children for (CaretBox *b; fbit != (*it)->end(); ++fbit) { b = *fbit; #if DEBUG_CARETMODE > 0 // RenderObject *r = b->object(); // if (b->isInlineFlowBox()) kdDebug(6200) << "b is inline flow box" << endl; // kdDebug(6200) << "approximate r" << r << ": " << (r ? r->renderName() : TQString::null) << (r && r->isText() ? " contains \"" + TQString(((RenderText *)r)->str->s, ((RenderText *)r)->str->l) + "\"" : TQString::null) << endl; #endif xPos = b->xPos(); // the caret is before this box if (x < xPos) { // snap to nearest box if (oldXPos < 0 || x - (oldXPos + caretBox->width()) > xPos - x) { caretBox = b; // current box is nearer }/*end if*/ break; // Otherwise, preceding box is implicitly used } caretBox = b; // the caret is within this box if (x >= xPos && x < xPos + caretBox->width()) break; oldXPos = xPos; // the caret can only be after the last box which is automatically // contained in caretBox when we fall out of the loop. }/*next fbit*/ return caretBox; } /** moves the given iterator to the beginning of the next word. * * If the end is reached, the iterator will be positioned there. * @param it character iterator to be moved */ static void moveItToNextWord(EditableCharacterIterator &it) { #if DEBUG_CARETMODE > 0 kdDebug(6200) << "%%%%%%%%%%%%%%%%%%%%% moveItToNextWord" << endl; #endif EditableCharacterIterator copy; while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()) { #if DEBUG_CARETMODE > 2 kdDebug(6200) << "reading1 '" << (*it).latin1() << "'" << endl; #endif copy = it; ++it; } if (it.isEnd()) { it = copy; return; }/*end if*/ while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())) { #if DEBUG_CARETMODE > 2 kdDebug(6200) << "reading2 '" << (*it).latin1() << "'" << endl; #endif copy = it; ++it; } if (it.isEnd()) it = copy; } /** moves the given iterator to the beginning of the previous word. * * If the beginning is reached, the iterator will be positioned there. * @param it character iterator to be moved */ static void moveItToPrevWord(EditableCharacterIterator &it) { if (it.isEnd()) return; #if DEBUG_CARETMODE > 0 kdDebug(6200) << "%%%%%%%%%%%%%%%%%%%%% moveItToPrevWord" << endl; #endif EditableCharacterIterator copy; // Jump over all space and punctuation characters first do { copy = it; --it; #if DEBUG_CARETMODE > 2 if (!it.isEnd()) kdDebug(6200) << "reading1 '" << (*it).latin1() << "'" << endl; #endif } while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())); if (it.isEnd()) { it = copy; return; }/*end if*/ do { copy = it; --it; #if DEBUG_CARETMODE > 0 if (!it.isEnd()) kdDebug(6200) << "reading2 '" << (*it).latin1() << "' (" << (int)(*it).latin1() << ") box " << it.caretBox() << endl; #endif } while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()); it = copy; #if DEBUG_CARETMODE > 1 if (!it.isEnd()) kdDebug(6200) << "effective '" << (*it).latin1() << "' (" << (int)(*it).latin1() << ") box " << it.caretBox() << endl; #endif } /** moves the iterator by one page. * @param ld linear document * @param it line iterator, will be updated accordingly * @param mindist minimum distance in pixel the iterator should be moved * (if possible) * @param next @p true, move downward, @p false move upward */ static void moveIteratorByPage(LinearDocument &ld, ErgonomicEditableLineIterator &it, int mindist, bool next) { // ### This whole routine plainly sucks. Use an inverse strategie for pgup/pgdn. if (it == ld.end() || it == ld.preBegin()) return; ErgonomicEditableLineIterator copy = it; #if DEBUG_CARETMODE > 0 kdDebug(6200) << " mindist: " << mindist << endl; #endif CaretBoxLine *cbl = *copy; int absx = 0, absy = 0; RenderBlock *lastcb = cbl->containingBlock(); Q_ASSERT(lastcb->isRenderBlock()); lastcb->absolutePosition(absx, absy, false); // ### what about fixed? int lastfby = cbl->begin().data()->yPos(); int lastheight = 0; int rescue = 1000; // ### this is a hack to keep stuck carets from hanging the ua do { if (next) ++copy; else --copy; if (copy == ld.end() || copy == ld.preBegin()) break; cbl = *copy; RenderBlock *cb = cbl->containingBlock(); int diff = 0; // ### actually flowBox->yPos() should suffice, but this is not ported // over yet from WebCore int fby = cbl->begin().data()->yPos(); if (cb != lastcb) { if (next) { diff = absy + lastfby + lastheight; cb->absolutePosition(absx, absy, false); // ### what about fixed? diff = absy - diff + fby; lastfby = 0; } else { diff = absy; cb->absolutePosition(absx, absy, false); // ### what about fixed? diff -= absy + fby + lastheight; lastfby = fby - lastheight; }/*end if*/ #if DEBUG_CARETMODE > 2 kdDebug(6200) << "absdiff " << diff << endl; #endif } else { diff = kAbs(fby - lastfby); }/*end if*/ #if DEBUG_CARETMODE > 2 kdDebug(6200) << "cbl->begin().data()->yPos(): " << fby << " diff " << diff << endl; #endif mindist -= diff; lastheight = kAbs(fby - lastfby); lastfby = fby; lastcb = cb; it = copy; #if DEBUG_CARETMODE > 0 kdDebug(6200) << " mindist: " << mindist << endl; #endif // trick: actually the distance is always one line short, but we cannot // calculate the height of the first line (### WebCore will make it better) // Therefore, we simply approximate that excess line by using the last // caluculated line height. } while (mindist - lastheight > 0 && --rescue); } }/*end namespace*/