File indexing completed on 2023-05-30 09:09:50
0001 /* This file is part of the KDE project 0002 * 0003 * Copyright (C) 2003-2004 Leo Savernik <l.savernik@aon.at> 0004 * 0005 * This library is free software; you can redistribute it and/or 0006 * modify it under the terms of the GNU Library General Public 0007 * License as published by the Free Software Foundation; either 0008 * version 2 of the License, or (at your option) any later version. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 * Library General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Library General Public License 0016 * along with this library; see the file COPYING.LIB. If not, write to 0017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 * Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "khtml_caret_p.h" 0022 0023 #include "html/html_documentimpl.h" 0024 0025 namespace khtml 0026 { 0027 0028 /** Flags representing the type of advance that has been made. 0029 * @param LeftObject a render object was left and an ascent to its parent has 0030 * taken place 0031 * @param AdvancedToSibling an actual advance to a sibling has taken place 0032 * @param EnteredObject a render object was entered by descending into it from 0033 * its parent object. 0034 */ 0035 enum ObjectAdvanceState { 0036 LeftObject = 0x01, AdvancedToSibling = 0x02, EnteredObject = 0x04 0037 }; 0038 0039 /** All possible states that may occur during render object traversal. 0040 * @param OutsideDescending outside of the current object, ready to descend 0041 * into children 0042 * @param InsideDescending inside the current object, descending into 0043 * children 0044 * @param InsideAscending inside the current object, ascending to parents 0045 * @param OutsideAscending outsie the current object, ascending to parents 0046 */ 0047 enum ObjectTraversalState { 0048 OutsideDescending, InsideDescending, InsideAscending, OutsideAscending 0049 }; 0050 0051 /** Traverses the render object tree in a fine granularity. 0052 * @param obj render object 0053 * @param trav object traversal state 0054 * @param toBegin traverse towards beginning 0055 * @param base base render object which this method must not advance beyond 0056 * (0 means document) 0057 * @param state object advance state (enum ObjectAdvanceState) 0058 * @return the render object according to the state. May be the same as \c obj 0059 */ 0060 static RenderObject *traverseRenderObjects(RenderObject *obj, 0061 ObjectTraversalState &trav, bool toBegin, RenderObject *base, 0062 int &state) 0063 { 0064 RenderObject *r; 0065 switch (trav) { 0066 case OutsideDescending: 0067 trav = InsideDescending; 0068 break; 0069 case InsideDescending: 0070 r = toBegin ? obj->lastChild() : obj->firstChild(); 0071 if (r) { 0072 trav = OutsideDescending; 0073 obj = r; 0074 state |= EnteredObject; 0075 } else { 0076 trav = InsideAscending; 0077 } 0078 break; 0079 case InsideAscending: 0080 trav = OutsideAscending; 0081 break; 0082 case OutsideAscending: 0083 r = toBegin ? obj->previousSibling() : obj->nextSibling(); 0084 if (r) { 0085 trav = OutsideDescending; 0086 state |= AdvancedToSibling; 0087 } else { 0088 r = obj->parent(); 0089 if (r == base) { 0090 r = 0; 0091 } 0092 trav = InsideAscending; 0093 state |= LeftObject; 0094 } 0095 obj = r; 0096 break; 0097 }/*end switch*/ 0098 0099 return obj; 0100 } 0101 0102 /** Like RenderObject::objectBelow, but confined to stay within \c base. 0103 * @param obj render object to begin with 0104 * @param trav object traversal state, will be reset within this function 0105 * @param base base render object (0: no base) 0106 */ 0107 static inline RenderObject *renderObjectBelow(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) 0108 { 0109 trav = InsideDescending; 0110 int state; // we don't need the state, so we don't initialize it 0111 RenderObject *r = obj; 0112 while (r && trav != OutsideDescending) { 0113 r = traverseRenderObjects(r, trav, false, base, state); 0114 #if DEBUG_CARETMODE > 3 0115 // qCDebug(KHTML_LOG) << "renderObjectBelow: r " << r << " trav " << trav; 0116 #endif 0117 } 0118 trav = InsideDescending; 0119 return r; 0120 } 0121 0122 /** Like RenderObject::objectAbove, but confined to stay within \c base. 0123 * @param obj render object to begin with 0124 * @param trav object traversal state, will be reset within this function 0125 * @param base base render object (0: no base) 0126 */ 0127 static inline RenderObject *renderObjectAbove(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) 0128 { 0129 trav = OutsideAscending; 0130 int state; // we don't need the state, so we don't initialize it 0131 RenderObject *r = obj; 0132 while (r && trav != InsideAscending) { 0133 r = traverseRenderObjects(r, trav, true, base, state); 0134 #if DEBUG_CARETMODE > 3 0135 // qCDebug(KHTML_LOG) << "renderObjectAbove: r " << r << " trav " << trav; 0136 #endif 0137 } 0138 trav = InsideAscending; 0139 return r; 0140 } 0141 0142 /** Checks whether the given inline box matches the IndicatedFlows policy 0143 * @param box inline box to test 0144 * @return true on match 0145 */ 0146 static inline bool isIndicatedInlineBox(InlineBox *box) 0147 { 0148 // text boxes are never indicated. 0149 if (box->isInlineTextBox()) { 0150 return false; 0151 } 0152 RenderStyle *s = box->object()->style(); 0153 return s->borderLeftWidth() || s->borderRightWidth() 0154 || s->borderTopWidth() || s->borderBottomWidth() 0155 || s->paddingLeft().value() || s->paddingRight().value() 0156 || s->paddingTop().value() || s->paddingBottom().value() 0157 // ### Can inline elements have top/bottom margins? Couldn't find 0158 // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. 0159 || s->marginLeft().value() || s->marginRight().value(); 0160 } 0161 0162 /** Checks whether the given render object matches the IndicatedFlows policy 0163 * @param r render object to test 0164 * @return true on match 0165 */ 0166 static inline bool isIndicatedFlow(RenderObject *r) 0167 { 0168 RenderStyle *s = r->style(); 0169 return s->borderLeftStyle() != BNONE || s->borderRightStyle() != BNONE 0170 || s->borderTopStyle() != BNONE || s->borderBottomStyle() != BNONE 0171 // || s->paddingLeft().value() || s->paddingRight().value() 0172 // || s->paddingTop().value() || s->paddingBottom().value() 0173 // || s->marginLeft().value() || s->marginRight().value() 0174 || s->hasClip() || s->hidesOverflow() 0175 || s->backgroundColor().isValid() || s->backgroundImage(); 0176 } 0177 0178 /** Advances to the next render object, taking into account the current 0179 * traversal state. 0180 * 0181 * @param r render object 0182 * @param trav object traversal state 0183 * @param toBegin @p true, advance towards beginning, @p false, advance toward end 0184 * @param base base render object which this method must not advance beyond 0185 * (0 means document) 0186 * @param state object advance state (enum ObjectAdvanceState) (unchanged 0187 * on LeafsOnly) 0188 * @return a pointer to the render object which we advanced to, 0189 * or 0 if the last possible object has been reached. 0190 */ 0191 static RenderObject *advanceObject(RenderObject *r, 0192 ObjectTraversalState &trav, bool toBegin, 0193 RenderObject *base, int &state) 0194 { 0195 0196 ObjectTraversalState origtrav = trav; 0197 RenderObject *a = traverseRenderObjects(r, trav, toBegin, base, state); 0198 0199 bool ignoreOutsideDesc = toBegin && origtrav == OutsideAscending; 0200 0201 // render object and traversal state at which look ahead has been started 0202 RenderObject *la = 0; 0203 ObjectTraversalState latrav = trav; 0204 ObjectTraversalState lasttrav = origtrav; 0205 0206 while (a) { 0207 #if DEBUG_CARETMODE > 5 0208 // qCDebug(KHTML_LOG) << "a " << a << " trav " << trav; 0209 #endif 0210 if (a->element()) { 0211 #if DEBUG_CARETMODE > 4 0212 // qCDebug(KHTML_LOG) << "a " << a << " trav " << trav << " origtrav " << origtrav << " ignoreOD " << ignoreOutsideDesc; 0213 #endif 0214 if (toBegin) { 0215 0216 switch (origtrav) { 0217 case OutsideDescending: 0218 if (trav == InsideAscending) { 0219 return a; 0220 } 0221 if (trav == OutsideDescending) { 0222 return a; 0223 } 0224 break; 0225 case InsideDescending: 0226 if (trav == OutsideDescending) { 0227 return a; 0228 } 0229 // fall through 0230 case InsideAscending: 0231 if (trav == OutsideAscending) { 0232 return a; 0233 } 0234 break; 0235 case OutsideAscending: 0236 if (trav == OutsideAscending) { 0237 return a; 0238 } 0239 if (trav == InsideAscending && lasttrav == InsideDescending) { 0240 return a; 0241 } 0242 if (trav == OutsideDescending && !ignoreOutsideDesc) { 0243 return a; 0244 } 0245 // ignore this outside descending position, as it effectively 0246 // demarkates the same position, but remember it in case we fall off 0247 // the document. 0248 la = a; latrav = trav; 0249 ignoreOutsideDesc = false; 0250 break; 0251 }/*end switch*/ 0252 0253 } else { 0254 0255 switch (origtrav) { 0256 case OutsideDescending: 0257 if (trav == InsideAscending) { 0258 return a; 0259 } 0260 if (trav == OutsideDescending) { 0261 return a; 0262 } 0263 break; 0264 case InsideDescending: 0265 // if (trav == OutsideDescending) return a; 0266 // fall through 0267 case InsideAscending: 0268 // if (trav == OutsideAscending) return a; 0269 // break; 0270 case OutsideAscending: 0271 // ### what if origtrav == OA, and immediately afterwards trav 0272 // becomes OD? In this case the effective position hasn't changed -> 0273 // the caret gets stuck. Otherwise, it apparently cannot happen in 0274 // real usage patterns. 0275 if (trav == OutsideDescending) { 0276 return a; 0277 } 0278 if (trav == OutsideAscending) { 0279 if (la) { 0280 return la; 0281 } 0282 // starting lookahead here. Remember old object in case we fall off 0283 // the document. 0284 la = a; latrav = trav; 0285 } 0286 break; 0287 }/*end switch*/ 0288 0289 }/*end if*/ 0290 }/*end if*/ 0291 0292 lasttrav = trav; 0293 a = traverseRenderObjects(a, trav, toBegin, base, state); 0294 }/*wend*/ 0295 0296 if (la) { 0297 trav = latrav, a = la; 0298 } 0299 return a; 0300 0301 } 0302 0303 /** Check whether the current render object is unsuitable in caret mode handling. 0304 * 0305 * Some render objects cannot be handled correctly in caret mode. These objects 0306 * are therefore considered to be unsuitable. The null object is suitable, as 0307 * it denotes reaching the end. 0308 * @param r current render object 0309 * @param trav current traversal state 0310 */ 0311 static inline bool isUnsuitable(RenderObject *r, ObjectTraversalState trav) 0312 { 0313 if (!r) { 0314 return false; 0315 } 0316 return r->isTableCol() || r->isTableSection() || r->isTableRow() 0317 || (r->isText() && !static_cast<RenderText *>(r)->firstTextBox()); 0318 ; 0319 Q_UNUSED(trav); 0320 } 0321 0322 /** Advances to the next render object, taking into account the current 0323 * traversal state, but skipping render objects which are not suitable for 0324 * having placed the caret into them. 0325 * @param r render object 0326 * @param trav object traversal state (unchanged on LeafsOnly) 0327 * @param toBegin @p true, advance towards beginning, @p false, advance toward end 0328 * @param base base render object which this method must not advance beyond 0329 * (0 means document) 0330 * @param state object advance state (enum ObjectAdvanceState) (unchanged 0331 * on LeafsOnly) 0332 * @return a pointer to the advanced render object or 0 if the last possible 0333 * object has been reached. 0334 */ 0335 static inline RenderObject *advanceSuitableObject(RenderObject *r, 0336 ObjectTraversalState &trav, bool toBegin, 0337 RenderObject *base, int &state) 0338 { 0339 do { 0340 r = advanceObject(r, trav, toBegin, base, state); 0341 #if DEBUG_CARETMODE > 2 0342 // qCDebug(KHTML_LOG) << "after advanceSWP: r " << r << " trav " << trav << " toBegin " << toBegin; 0343 #endif 0344 } while (isUnsuitable(r, trav)); 0345 return r; 0346 } 0347 0348 /** 0349 * Returns the next leaf node. 0350 * 0351 * Using this function delivers leaf nodes as if the whole DOM tree 0352 * were a linear chain of its leaf nodes. 0353 * @param r dom node 0354 * @param baseElem base element not to search beyond 0355 * @return next leaf node or 0 if there are no more. 0356 */ 0357 static NodeImpl *nextLeafNode(NodeImpl *r, NodeImpl *baseElem) 0358 { 0359 NodeImpl *n = r->firstChild(); 0360 if (n) { 0361 while (n) { 0362 r = n; 0363 n = n->firstChild(); 0364 } 0365 return const_cast<NodeImpl *>(r); 0366 }/*end if*/ 0367 n = r->nextSibling(); 0368 if (n) { 0369 r = n; 0370 while (n) { 0371 r = n; 0372 n = n->firstChild(); 0373 } 0374 return const_cast<NodeImpl *>(r); 0375 }/*end if*/ 0376 0377 n = r->parentNode(); 0378 if (n == baseElem) { 0379 n = 0; 0380 } 0381 while (n) { 0382 r = n; 0383 n = r->nextSibling(); 0384 if (n) { 0385 r = n; 0386 n = r->firstChild(); 0387 while (n) { 0388 r = n; 0389 n = n->firstChild(); 0390 } 0391 return const_cast<NodeImpl *>(r); 0392 }/*end if*/ 0393 n = r->parentNode(); 0394 if (n == baseElem) { 0395 n = 0; 0396 } 0397 }/*wend*/ 0398 return 0; 0399 } 0400 0401 #if 0 // currently not used 0402 /** (Not part of the official DOM) 0403 * Returns the previous leaf node. 0404 * 0405 * Using this function delivers leaf nodes as if the whole DOM tree 0406 * were a linear chain of its leaf nodes. 0407 * @param r dom node 0408 * @param baseElem base element not to search beyond 0409 * @return previous leaf node or 0 if there are no more. 0410 */ 0411 static NodeImpl *prevLeafNode(NodeImpl *r, NodeImpl *baseElem) 0412 { 0413 NodeImpl *n = r->firstChild(); 0414 if (n) { 0415 while (n) { 0416 r = n; 0417 n = n->firstChild(); 0418 } 0419 return const_cast<NodeImpl *>(r); 0420 }/*end if*/ 0421 n = r->previousSibling(); 0422 if (n) { 0423 r = n; 0424 while (n) { 0425 r = n; 0426 n = n->firstChild(); 0427 } 0428 return const_cast<NodeImpl *>(r); 0429 }/*end if*/ 0430 0431 n = r->parentNode(); 0432 if (n == baseElem) { 0433 n = 0; 0434 } 0435 while (n) { 0436 r = n; 0437 n = r->previousSibling(); 0438 if (n) { 0439 r = n; 0440 n = r->lastChild(); 0441 while (n) { 0442 r = n; 0443 n = n->lastChild(); 0444 } 0445 return const_cast<NodeImpl *>(r); 0446 }/*end if*/ 0447 n = r->parentNode(); 0448 if (n == baseElem) { 0449 n = 0; 0450 } 0451 }/*wend*/ 0452 return 0; 0453 } 0454 #endif 0455 0456 /** Maps a DOM Range position to the corresponding caret position. 0457 * 0458 * The offset boundary is not checked for validity. 0459 * @param node DOM node 0460 * @param offset zero-based offset within node 0461 * @param r returns render object (may be 0 if DOM node has no render object) 0462 * @param r_ofs returns the appropriate offset for the found render object r 0463 * @param outside returns true when offset is applied to the outside of 0464 * \c r, or false for the inside. 0465 * @param outsideEnd return true when the caret position is at the outside end. 0466 */ 0467 void /*KHTML_NO_EXPORT*/ mapDOMPosToRenderPos(NodeImpl *node, long offset, 0468 RenderObject *&r, long &r_ofs, bool &outside, bool &outsideEnd) 0469 { 0470 if (node->nodeType() == Node::TEXT_NODE) { 0471 outside = false; 0472 outsideEnd = false; 0473 r = node->renderer(); 0474 r_ofs = offset; 0475 } else if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::DOCUMENT_NODE) { 0476 0477 // Though offset points between two children, attach it to the visually 0478 // most suitable one (and only there, because the mapping must stay bijective) 0479 if (node->firstChild()) { 0480 outside = true; 0481 NodeImpl *child = offset <= 0 ? node->firstChild() 0482 // childNode is expensive 0483 : node->childNode((unsigned long)offset); 0484 // index was child count or out of bounds 0485 bool atEnd = !child; 0486 #if DEBUG_CARETMODE > 5 0487 // qCDebug(KHTML_LOG) << "mapDTR: child " << child << "@" << (child ? child->nodeName().string() : QString()) << " atEnd " << atEnd; 0488 #endif 0489 if (atEnd) { 0490 child = node->lastChild(); 0491 } 0492 0493 r = child->renderer(); 0494 r_ofs = 0; 0495 outsideEnd = atEnd; 0496 0497 // Outside text nodes most likely stem from a continuation. Seek 0498 // the enclosing continued render object and use this one instead. 0499 if (r && child->nodeType() == Node::TEXT_NODE) { 0500 r = r->parent(); 0501 RenderObject *o = node->renderer(); 0502 while (o->continuation() && o->continuation() != r) { 0503 o = o->continuation(); 0504 } 0505 if (!r || o->continuation() != r) { 0506 r = child->renderer(); 0507 } 0508 }/*end if*/ 0509 0510 // BRs cause troubles. Returns the previous render object instead, 0511 // giving it the attributes outside, outside end. 0512 if (r && r->isBR()) { 0513 r = r->objectAbove(); 0514 outsideEnd = true; 0515 }/*end if*/ 0516 0517 } else { 0518 // Element has no children, treat offset to be inside the node. 0519 outside = false; 0520 outsideEnd = false; 0521 r = node->renderer(); 0522 r_ofs = 0; // only offset 0 possible 0523 } 0524 0525 } else { 0526 r = 0; 0527 qCWarning(KHTML_LOG) << "Mapping from nodes of type " << node->nodeType() 0528 << " not supported!"; 0529 } 0530 } 0531 0532 /** Maps a caret position to the corresponding DOM Range position. 0533 * 0534 * @param r render object 0535 * @param r_ofs offset within render object 0536 * @param outside true when offset is interpreted to be on the outside of 0537 * \c r, or false if on the inside. 0538 * @param outsideEnd true when the caret position is at the outside end. 0539 * @param node returns DOM node 0540 * @param offset returns zero-based offset within node 0541 */ 0542 void /*KHTML_NO_EXPORT*/ mapRenderPosToDOMPos(RenderObject *r, long r_ofs, 0543 bool outside, bool outsideEnd, NodeImpl *&node, long &offset) 0544 { 0545 node = r->element(); 0546 Q_ASSERT(node); 0547 #if DEBUG_CARETMODE > 5 0548 // qCDebug(KHTML_LOG) << "mapRTD: r " << r << '@' << (r ? r->renderName() : QString()) << (r && r->element() ? QString(".node ") + QString::number((unsigned)r->element(),16) + '@' + r->element()->nodeName().string() : QString()) << " outside " << outside << " outsideEnd " << outsideEnd; 0549 #endif 0550 if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::TEXT_NODE) { 0551 0552 if (outside) { 0553 NodeImpl *parent = node->parent(); 0554 0555 // If this is part of a continuation, use the actual node as the parent, 0556 // and the first render child as the node. 0557 if (r != node->renderer()) { 0558 RenderObject *o = node->renderer(); 0559 while (o->continuation() && o->continuation() != r) { 0560 o = o->continuation(); 0561 } 0562 if (o->continuation() == r) { 0563 parent = node; 0564 // ### What if the first render child does not map to a child of 0565 // the continued node? 0566 node = r->firstChild() ? r->firstChild()->element() : node; 0567 } 0568 }/*end if*/ 0569 0570 if (!parent) { 0571 goto inside; 0572 } 0573 0574 offset = (long)node->nodeIndex() + outsideEnd; 0575 node = parent; 0576 #if DEBUG_CARETMODE > 5 0577 // qCDebug(KHTML_LOG) << node << "@" << (node ? node->nodeName().string() : QString()) << " offset " << offset; 0578 #endif 0579 } else { // !outside 0580 inside: 0581 offset = r_ofs; 0582 } 0583 0584 } else { 0585 offset = 0; 0586 qCWarning(KHTML_LOG) << "Mapping to nodes of type " << node->nodeType() 0587 << " not supported!"; 0588 } 0589 } 0590 0591 /** Make sure the given node is a leaf node. */ 0592 static inline void ensureLeafNode(NodeImpl *&node, NodeImpl *base) 0593 { 0594 if (node && node->hasChildNodes()) { 0595 node = nextLeafNode(node, base); 0596 } 0597 } 0598 0599 /** Converts a caret position to its respective object traversal state. 0600 * @param outside whether the caret is outside the object 0601 * @param atEnd whether the caret position is at the end 0602 * @param toBegin \c true when advancing towards the beginning 0603 * @param trav returns the corresponding traversal state 0604 */ 0605 static inline void mapRenderPosToTraversalState(bool outside, bool atEnd, 0606 bool toBegin, ObjectTraversalState &trav) 0607 { 0608 if (!outside) { 0609 atEnd = !toBegin; 0610 } 0611 if (!atEnd ^ toBegin) { 0612 trav = outside ? OutsideDescending : InsideDescending; 0613 } else { 0614 trav = outside ? OutsideAscending : InsideAscending; 0615 } 0616 } 0617 0618 /** Converts a traversal state to its respective caret position 0619 * @param trav object traversal state 0620 * @param toBegin \c true when advancing towards the beginning 0621 * @param outside whether the caret is outside the object 0622 * @param atEnd whether the caret position is at the end 0623 */ 0624 static inline void mapTraversalStateToRenderPos(ObjectTraversalState trav, 0625 bool toBegin, bool &outside, bool &atEnd) 0626 { 0627 outside = false; 0628 switch (trav) { 0629 case OutsideDescending: outside = true; // fall through 0630 case InsideDescending: atEnd = toBegin; break; 0631 case OutsideAscending: outside = true; // fall through 0632 case InsideAscending: atEnd = !toBegin; break; 0633 } 0634 } 0635 0636 /** Finds the next node that has a renderer. 0637 * 0638 * Note that if the initial @p node has a renderer, this will be returned, 0639 * regardless of the caret advance policy. 0640 * Otherwise, for the next nodes, only leaf nodes are considered. 0641 * @param node node to start with, will be updated accordingly 0642 * @param offset offset of caret within \c node 0643 * @param base base render object which this method must not advance beyond 0644 * (0 means document) 0645 * @param r_ofs return the caret offset within the returned renderer 0646 * @param outside returns whether offset is to be interpreted to the outside 0647 * (true) or the inside (false) of the render object. 0648 * @param outsideEnd returns whether the end of the outside position is meant 0649 * @return renderer or 0 if no following node has a renderer. 0650 */ 0651 static RenderObject *findRenderer(NodeImpl *&node, long offset, 0652 RenderObject *base, long &r_ofs, 0653 bool &outside, bool &outsideEnd) 0654 { 0655 if (!node) { 0656 return 0; 0657 } 0658 RenderObject *r; 0659 mapDOMPosToRenderPos(node, offset, r, r_ofs, outside, outsideEnd); 0660 #if DEBUG_CARETMODE > 2 0661 // qCDebug(KHTML_LOG) << "findRenderer: node " << node << " " << (node ? node->nodeName().string() : QString()) << " offset " << offset << " r " << r << "[" << (r ? r->renderName() : QString()) << "] r_ofs " << r_ofs << " outside " << outside << " outsideEnd " << outsideEnd; 0662 #endif 0663 if (r) { 0664 return r; 0665 } 0666 NodeImpl *baseElem = base ? base->element() : 0; 0667 while (!r) { 0668 node = nextLeafNode(node, baseElem); 0669 if (!node) { 0670 break; 0671 } 0672 r = node->renderer(); 0673 if (r) { 0674 r_ofs = offset; 0675 } 0676 } 0677 #if DEBUG_CARETMODE > 3 0678 // qCDebug(KHTML_LOG) << "1r " << r; 0679 #endif 0680 ObjectTraversalState trav; 0681 int state; // not used 0682 mapRenderPosToTraversalState(outside, outsideEnd, false, trav); 0683 if (r && isUnsuitable(r, trav)) { 0684 r = advanceSuitableObject(r, trav, false, base, state); 0685 mapTraversalStateToRenderPos(trav, false, outside, outsideEnd); 0686 if (r) { 0687 r_ofs = r->minOffset(); 0688 } 0689 } 0690 #if DEBUG_CARETMODE > 3 0691 // qCDebug(KHTML_LOG) << "2r " << r; 0692 #endif 0693 return r; 0694 } 0695 0696 /** returns a suitable base element 0697 * @param caretNode current node containing caret. 0698 */ 0699 static ElementImpl *determineBaseElement(NodeImpl *caretNode) 0700 { 0701 // ### for now, only body is delivered for html documents, 0702 // and 0 for xml documents. 0703 0704 DocumentImpl *doc = caretNode->getDocument(); 0705 if (!doc) { 0706 return 0; // should not happen, but who knows. 0707 } 0708 0709 if (doc->isHTMLDocument()) { 0710 return static_cast<HTMLDocumentImpl *>(doc)->body(); 0711 } 0712 0713 return 0; 0714 } 0715 0716 // == class CaretBox implementation 0717 0718 #if DEBUG_CARETMODE > 0 0719 void CaretBox::dump(QTextStream &ts, const QString &ind) const 0720 { 0721 ts << ind << "b@" << _box; 0722 0723 if (_box) { 0724 ts << "<" << _box->object() << ":" << _box->object()->renderName() << ">"; 0725 }/*end if*/ 0726 0727 ts << " " << _x << "+" << _y << "+" << _w << "*" << _h; 0728 0729 ts << " cb@" << cb; 0730 if (cb) { 0731 ts << ":" << cb->renderName(); 0732 } 0733 0734 ts << " " << (_outside ? (outside_end ? "oe" : "o-") : "i-"); 0735 // ts << endl; 0736 } 0737 #endif 0738 0739 // == class CaretBoxLine implementation 0740 0741 #if DEBUG_CARETMODE > 0 0742 # define DEBUG_ACIB 1 0743 #else 0744 # define DEBUG_ACIB DEBUG_CARETMODE 0745 #endif 0746 void CaretBoxLine::addConvertedInlineBox(InlineBox *box, SeekBoxParams &sbp) /*KHTML_NO_EXPORT*/ 0747 { 0748 // Generate only one outside caret box between two elements. If 0749 // coalesceOutsideBoxes is true, generating left outside boxes is inhibited. 0750 bool coalesceOutsideBoxes = false; 0751 CaretBoxIterator lastCoalescedBox; 0752 for (; box; box = box->nextOnLine()) { 0753 #if DEBUG_ACIB 0754 // qCDebug(KHTML_LOG) << "box " << box; 0755 // qCDebug(KHTML_LOG) << "box->object " << box->object(); 0756 // qCDebug(KHTML_LOG) << "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(); 0757 #endif 0758 // ### Why the hell can object() ever be 0?! 0759 if (!box->object()) { 0760 continue; 0761 } 0762 0763 RenderStyle *s = box->object()->style(box->m_firstLine); 0764 // parent style for outside caret boxes 0765 RenderStyle *ps = box->parent() && box->parent()->object() 0766 ? box->parent()->object()->style(box->parent()->m_firstLine) 0767 : s; 0768 0769 if (box->isInlineFlowBox()) { 0770 #if DEBUG_ACIB 0771 // qCDebug(KHTML_LOG) << "isinlineflowbox " << box; 0772 #endif 0773 InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(box); 0774 bool rtl = ps->direction() == RTL; 0775 const QFontMetrics &pfm = ps->fontMetrics(); 0776 0777 if (flowBox->includeLeftEdge()) { 0778 // If this box is to be coalesced with the outside end box of its 0779 // predecessor, then check if it is the searched box. If it is, we 0780 // substitute the outside end box. 0781 if (coalesceOutsideBoxes) { 0782 if (sbp.equalsBox(flowBox, true, false)) { 0783 sbp.it = lastCoalescedBox; 0784 Q_ASSERT(!sbp.found); 0785 sbp.found = true; 0786 } 0787 } else { 0788 addCreatedFlowBoxEdge(flowBox, pfm, true, rtl); 0789 sbp.check(preEnd()); 0790 } 0791 }/*end if*/ 0792 0793 if (flowBox->firstChild()) { 0794 #if DEBUG_ACIB 0795 // qCDebug(KHTML_LOG) << "this " << this << " flowBox " << flowBox << " firstChild " << flowBox->firstChild(); 0796 // qCDebug(KHTML_LOG) << "== recursive invocation"; 0797 #endif 0798 addConvertedInlineBox(flowBox->firstChild(), sbp); 0799 #if DEBUG_ACIB 0800 // qCDebug(KHTML_LOG) << "== recursive invocation end"; 0801 #endif 0802 } else { 0803 addCreatedFlowBoxInside(flowBox, s->fontMetrics()); 0804 sbp.check(preEnd()); 0805 } 0806 0807 if (flowBox->includeRightEdge()) { 0808 addCreatedFlowBoxEdge(flowBox, pfm, false, rtl); 0809 lastCoalescedBox = preEnd(); 0810 sbp.check(lastCoalescedBox); 0811 coalesceOutsideBoxes = true; 0812 } 0813 0814 } else if (box->isInlineTextBox()) { 0815 #if DEBUG_ACIB 0816 // qCDebug(KHTML_LOG) << "isinlinetextbox " << box << (box->object() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), qMin(box->maxOffset() - box->minOffset(), 15L)).string()) : QString()); 0817 #endif 0818 caret_boxes.append(new CaretBox(box, false, false)); 0819 sbp.check(preEnd()); 0820 // coalescing has been interrupted 0821 coalesceOutsideBoxes = false; 0822 0823 } else { 0824 #if DEBUG_ACIB 0825 // qCDebug(KHTML_LOG) << "some replaced or what " << box; 0826 #endif 0827 // must be an inline-block, inline-table, or any RenderReplaced 0828 bool rtl = ps->direction() == RTL; 0829 const QFontMetrics &pfm = ps->fontMetrics(); 0830 0831 if (coalesceOutsideBoxes) { 0832 if (sbp.equalsBox(box, true, false)) { 0833 sbp.it = lastCoalescedBox; 0834 Q_ASSERT(!sbp.found); 0835 sbp.found = true; 0836 } 0837 } else { 0838 addCreatedInlineBoxEdge(box, pfm, true, rtl); 0839 sbp.check(preEnd()); 0840 } 0841 0842 caret_boxes.append(new CaretBox(box, false, false)); 0843 sbp.check(preEnd()); 0844 0845 addCreatedInlineBoxEdge(box, pfm, false, rtl); 0846 lastCoalescedBox = preEnd(); 0847 sbp.check(lastCoalescedBox); 0848 coalesceOutsideBoxes = true; 0849 }/*end if*/ 0850 }/*next box*/ 0851 } 0852 #undef DEBUG_ACIB 0853 0854 void CaretBoxLine::addCreatedFlowBoxInside(InlineFlowBox *flowBox, const QFontMetrics &fm) /*KHTML_NO_EXPORT*/ 0855 { 0856 0857 CaretBox *caretBox = new CaretBox(flowBox, false, false); 0858 caret_boxes.append(caretBox); 0859 0860 // afaik an inner flow box can only have the width 0, therefore we don't 0861 // have to care for rtl or alignment 0862 // ### can empty inline elements have a width? css 2 spec isn't verbose about it 0863 0864 caretBox->_y += flowBox->baseline() - fm.ascent(); 0865 caretBox->_h = fm.height(); 0866 } 0867 0868 void CaretBoxLine::addCreatedFlowBoxEdge(InlineFlowBox *flowBox, const QFontMetrics &fm, bool left, bool rtl) /*KHTML_NO_EXPORT*/ 0869 { 0870 CaretBox *caretBox = new CaretBox(flowBox, true, !left); 0871 caret_boxes.append(caretBox); 0872 0873 if (left ^ rtl) { 0874 caretBox->_x -= flowBox->paddingLeft() + flowBox->borderLeft() + 1; 0875 } else { 0876 caretBox->_x += caretBox->_w + flowBox->paddingRight() + flowBox->borderRight(); 0877 } 0878 0879 caretBox->_y += flowBox->baseline() - fm.ascent(); 0880 caretBox->_h = fm.height(); 0881 caretBox->_w = 1; 0882 } 0883 0884 void CaretBoxLine::addCreatedInlineBoxEdge(InlineBox *box, const QFontMetrics &fm, bool left, bool rtl) /*KHTML_NO_EXPORT*/ 0885 { 0886 CaretBox *caretBox = new CaretBox(box, true, !left); 0887 caret_boxes.append(caretBox); 0888 0889 if (left ^ rtl) { 0890 caretBox->_x--; 0891 } else { 0892 caretBox->_x += caretBox->_w; 0893 } 0894 0895 caretBox->_y += box->baseline() - fm.ascent(); 0896 caretBox->_h = fm.height(); 0897 caretBox->_w = 1; 0898 } 0899 0900 CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, 0901 InlineFlowBox *basicFlowBox, InlineBox *seekBox, bool seekOutside, 0902 bool seekOutsideEnd, CaretBoxIterator &iter, RenderObject *seekObject) 0903 // KHTML_NO_EXPORT 0904 { 0905 // Iterate all inline boxes within this inline flow box. 0906 // Caret boxes will be created for each 0907 // - outside begin of an inline flow box (except for the basic inline flow box) 0908 // - outside end of an inline flow box (except for the basic inline flow box) 0909 // - inside of an empty inline flow box 0910 // - outside begin of an inline box resembling a replaced element 0911 // - outside end of an inline box resembling a replaced element 0912 // - inline text box 0913 // - inline replaced box 0914 0915 CaretBoxLine *result = new CaretBoxLine(basicFlowBox); 0916 deleter->append(result); 0917 0918 SeekBoxParams sbp(seekBox, seekOutside, seekOutsideEnd, seekObject, iter); 0919 0920 // iterate recursively, I'm too lazy to do it iteratively 0921 result->addConvertedInlineBox(basicFlowBox, sbp); 0922 0923 if (!sbp.found) { 0924 sbp.it = result->end(); 0925 } 0926 0927 return result; 0928 } 0929 0930 CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, 0931 RenderBox *cb, bool outside, bool outsideEnd, CaretBoxIterator &iter) /*KHTML_NO_EXPORT*/ 0932 { 0933 int _x = cb->xPos(); 0934 int _y = cb->yPos(); 0935 int height; 0936 int width = 1; // no override is indicated in boxes 0937 0938 if (outside) { 0939 0940 RenderStyle *s = cb->element() && cb->element()->parent() 0941 && cb->element()->parent()->renderer() 0942 ? cb->element()->parent()->renderer()->style() 0943 : cb->style(); 0944 bool rtl = s->direction() == RTL; 0945 0946 const QFontMetrics &fm = s->fontMetrics(); 0947 height = fm.height(); 0948 0949 if (!outsideEnd) { 0950 _x--; 0951 } else { 0952 _x += cb->width(); 0953 } 0954 0955 int hl = fm.leading() / 2; 0956 int baseline = cb->baselinePosition(false); 0957 if (!cb->isReplaced() || cb->style()->display() == BLOCK) { 0958 if (!outsideEnd ^ rtl) { 0959 _y -= fm.leading() / 2; 0960 } else { 0961 _y += qMax(cb->height() - fm.ascent() - hl, 0); 0962 } 0963 } else { 0964 _y += baseline - fm.ascent() - hl; 0965 } 0966 0967 } else { // !outside 0968 0969 RenderStyle *s = cb->style(); 0970 const QFontMetrics &fm = s->fontMetrics(); 0971 height = fm.height(); 0972 0973 _x += cb->borderLeft() + cb->paddingLeft(); 0974 _y += cb->borderTop() + cb->paddingTop(); 0975 0976 // ### regard direction 0977 switch (s->textAlign()) { 0978 case LEFT: 0979 case KHTML_LEFT: 0980 case TAAUTO: // ### find out what this does 0981 case JUSTIFY: 0982 break; 0983 case CENTER: 0984 case KHTML_CENTER: 0985 _x += cb->contentWidth() / 2; 0986 break; 0987 case KHTML_RIGHT: 0988 case RIGHT: 0989 _x += cb->contentWidth(); 0990 break; 0991 }/*end switch*/ 0992 }/*end if*/ 0993 0994 CaretBoxLine *result = new CaretBoxLine; 0995 deleter->append(result); 0996 result->caret_boxes.append(new CaretBox(_x, _y, width, height, cb, 0997 outside, outsideEnd)); 0998 iter = result->begin(); 0999 return result; 1000 } 1001 1002 #if DEBUG_CARETMODE > 0 1003 void CaretBoxLine::dump(QTextStream &ts, const QString &ind) const 1004 { 1005 ts << ind << "cbl: baseFlowBox@" << basefb << endl; 1006 QString ind2 = ind + " "; 1007 for (size_t i = 0; i < caret_boxes.size(); i++) { 1008 if (i > 0) { 1009 ts << endl; 1010 } 1011 caret_boxes[i]->dump(ts, ind2); 1012 } 1013 } 1014 #endif 1015 1016 // == caret mode related helper functions 1017 1018 /** seeks the root line box that is the parent of the given inline box. 1019 * @param b given inline box 1020 * @param base base render object which not to step over. If \c base's 1021 * inline flow box is hit before the root line box, the flow box 1022 * is returned instead. 1023 * @return the root line box or the base flow box. 1024 */ 1025 inline InlineFlowBox *seekBaseFlowBox(InlineBox *b, RenderObject *base = 0) 1026 { 1027 // Seek root line box or base inline flow box, if \c base is interfering. 1028 while (b->parent() && b->object() != base) { 1029 b = b->parent(); 1030 }/*wend*/ 1031 Q_ASSERT(b->isInlineFlowBox()); 1032 return static_cast<InlineFlowBox *>(b); 1033 } 1034 1035 /** determines whether the given element is a block level replaced element. 1036 */ 1037 inline bool isBlockRenderReplaced(RenderObject *r) 1038 { 1039 return r->isRenderReplaced() && r->style()->display() == BLOCK; 1040 } 1041 1042 /** determines the caret line box that contains the given position. 1043 * 1044 * If the node does not map to a render object, the function will snap to 1045 * the next suitable render object following it. 1046 * 1047 * @param node node to begin with 1048 * @param offset zero-based offset within node. 1049 * @param cblDeleter deleter for caret box lines 1050 * @param base base render object which the caret must not be placed beyond. 1051 * @param r_ofs adjusted offset within render object 1052 * @param caretBoxIt returns an iterator to the caret box that contains the 1053 * given position. 1054 * @return the determined caret box lineor 0 if either the node is 0 or 1055 * there is no inline flow box containing this node. The containing block 1056 * will still be set. If it is 0 too, @p node was invalid. 1057 */ 1058 static CaretBoxLine *findCaretBoxLine(DOM::NodeImpl *node, long offset, 1059 CaretBoxLineDeleter *cblDeleter, RenderObject *base, 1060 long &r_ofs, CaretBoxIterator &caretBoxIt) 1061 { 1062 bool outside, outsideEnd; 1063 RenderObject *r = findRenderer(node, offset, base, r_ofs, outside, outsideEnd); 1064 if (!r) { 1065 return 0; 1066 } 1067 #if DEBUG_CARETMODE > 0 1068 // qCDebug(KHTML_LOG) << "=================== findCaretBoxLine"; 1069 // qCDebug(KHTML_LOG) << "node " << node << " offset: " << offset << " r " << r->renderName() << "[" << r << "].node " << r->element()->nodeName().string() << "[" << r->element() << "]" << " r_ofs " << r_ofs << " outside " << outside << " outsideEnd " << outsideEnd; 1070 #endif 1071 1072 // There are two strategies to find the correct line box. (The third is failsafe) 1073 // (A) First, if node's renderer is a RenderText, we only traverse its text 1074 // runs and return the root line box (saves much time for long blocks). 1075 // This should be the case 99% of the time. 1076 // (B) Second, we derive the inline flow box directly when the renderer is 1077 // a RenderBlock, RenderInline, or blocked RenderReplaced. 1078 // (C) Otherwise, we iterate linearly through all line boxes in order to find 1079 // the renderer. 1080 1081 // (A) 1082 if (r->isText()) do { 1083 RenderText *t = static_cast<RenderText *>(r); 1084 int dummy; 1085 InlineBox *b = t->findInlineTextBox(offset, dummy, true); 1086 // Actually b should never be 0, but some render texts don't have text 1087 // boxes, so we insert the last run as an error correction. 1088 // If there is no last run, we resort to (B) 1089 if (!b) { 1090 if (!t->lastTextBox()) { 1091 break; 1092 } 1093 b = t->lastTextBox(); 1094 }/*end if*/ 1095 Q_ASSERT(b); 1096 outside = false; // text boxes cannot have outside positions 1097 InlineFlowBox *baseFlowBox = seekBaseFlowBox(b, base); 1098 #if DEBUG_CARETMODE > 2 1099 // qCDebug(KHTML_LOG) << "text-box b: " << b << " baseFlowBox: " << baseFlowBox << (b && b->object() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(b->object())->str->s+b->minOffset(), qMin(b->maxOffset() - b->minOffset(), 15L)).string()) : QString()); 1100 #endif 1101 #if 0 1102 if (t->containingBlock()->isListItem()) { 1103 dumpLineBoxes(static_cast<RenderFlow *>(t->containingBlock())); 1104 } 1105 #endif 1106 #if DEBUG_CARETMODE > 0 1107 // qCDebug(KHTML_LOG) << "=================== end findCaretBoxLine (renderText)"; 1108 #endif 1109 return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, 1110 b, outside, outsideEnd, caretBoxIt); 1111 } while (false); /*end if*/ 1112 1113 // (B) 1114 bool isrepl = isBlockRenderReplaced(r); 1115 if (r->isRenderBlock() || r->isRenderInline() || isrepl) { 1116 RenderFlow *flow = static_cast<RenderFlow *>(r); 1117 InlineFlowBox *firstLineBox = isrepl ? 0 : flow->firstLineBox(); 1118 1119 // On render blocks, if we are outside, or have a totally empty render 1120 // block, we simply construct a special caret box line. 1121 // The latter case happens only when the render block is a leaf object itself. 1122 if (isrepl || r->isRenderBlock() && (outside || !firstLineBox) 1123 || r->isRenderInline() && !firstLineBox) { 1124 #if DEBUG_CARETMODE > 0 1125 // qCDebug(KHTML_LOG) << "=================== end findCaretBoxLine (box " << (outside ? (outsideEnd ? "outside end" : "outside begin") : "inside") << ")"; 1126 #endif 1127 Q_ASSERT(r->isBox()); 1128 return CaretBoxLine::constructCaretBoxLine(cblDeleter, 1129 static_cast<RenderBox *>(r), outside, outsideEnd, caretBoxIt); 1130 }/*end if*/ 1131 1132 // qCDebug(KHTML_LOG) << "firstlinebox " << firstLineBox; 1133 InlineFlowBox *baseFlowBox = seekBaseFlowBox(firstLineBox, base); 1134 return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, 1135 firstLineBox, outside, outsideEnd, caretBoxIt); 1136 }/*end if*/ 1137 1138 RenderBlock *cb = r->containingBlock(); 1139 //if ( !cb ) return 0L; 1140 Q_ASSERT(cb); 1141 1142 // ### which element doesn't have a block as its containing block? 1143 // Is it still possible after the RenderBlock/RenderInline merge? 1144 if (!cb->isRenderBlock()) { 1145 qCWarning(KHTML_LOG) << "containing block is no render block!!! crash imminent"; 1146 }/*end if*/ 1147 1148 InlineFlowBox *flowBox = cb->firstLineBox(); 1149 // (C) 1150 // This case strikes when the element is replaced, but neither a 1151 // RenderBlock nor a RenderInline 1152 if (!flowBox) { // ### utter emergency (why is this possible at all?) 1153 // flowBox = generateDummyFlowBox(arena, cb, r); 1154 // if (ibox) *ibox = flowBox->firstChild(); 1155 // outside = outside_end = true; 1156 1157 // qCWarning(KHTML_LOG) << "containing block contains no inline flow boxes!!! crash imminent"; 1158 #if DEBUG_CARETMODE > 0 1159 // qCDebug(KHTML_LOG) << "=================== end findCaretBoxLine (2)"; 1160 #endif 1161 return CaretBoxLine::constructCaretBoxLine(cblDeleter, cb, 1162 outside, outsideEnd, caretBoxIt); 1163 }/*end if*/ 1164 1165 // We iterate the inline flow boxes of the containing block until 1166 // we find the given node. This has one major flaw: it is linear, and therefore 1167 // painfully slow for really large blocks. 1168 for (; flowBox; flowBox = static_cast<InlineFlowBox *>(flowBox->nextLineBox())) { 1169 #if DEBUG_CARETMODE > 0 1170 // qCDebug(KHTML_LOG) << "[scan line]"; 1171 #endif 1172 1173 // construct a caret line box and stop when the element is contained within 1174 InlineFlowBox *baseFlowBox = seekBaseFlowBox(flowBox, base); 1175 CaretBoxLine *cbl = CaretBoxLine::constructCaretBoxLine(cblDeleter, 1176 baseFlowBox, 0, outside, outsideEnd, caretBoxIt, r); 1177 #if DEBUG_CARETMODE > 5 1178 // qCDebug(KHTML_LOG) << cbl->information(); 1179 #endif 1180 if (caretBoxIt != cbl->end()) { 1181 #if DEBUG_CARETMODE > 0 1182 // qCDebug(KHTML_LOG) << "=================== end findCaretBoxLine (3)"; 1183 #endif 1184 return cbl; 1185 } 1186 }/*next flowBox*/ 1187 1188 // no inline flow box found, approximate to nearest following node. 1189 // Danger: this is O(n^2). It's only called to recover from 1190 // errors, that means, theoretically, never. (Practically, far too often :-( ) 1191 Q_ASSERT(!flowBox); 1192 CaretBoxLine *cbl = findCaretBoxLine(nextLeafNode(node, base ? base->element() : 0), 0, cblDeleter, base, r_ofs, caretBoxIt); 1193 #if DEBUG_CARETMODE > 0 1194 // qCDebug(KHTML_LOG) << "=================== end findCaretBoxLine"; 1195 #endif 1196 return cbl; 1197 } 1198 1199 /** finds the innermost table object @p r is contained within, but no 1200 * farther than @p cb. 1201 * @param r leaf element to begin with 1202 * @param cb bottom element where to stop search at least. 1203 * @return the table object or 0 if none found. 1204 */ 1205 static inline RenderTable *findTableUpTo(RenderObject *r, RenderFlow *cb) 1206 { 1207 while (r && r != cb && !r->isTable()) { 1208 r = r->parent(); 1209 } 1210 return r && r->isTable() ? static_cast<RenderTable *>(r) : 0; 1211 } 1212 1213 /** checks whether @p r is a descendant of @p cb, or r == cb 1214 */ 1215 static inline bool isDescendant(RenderObject *r, RenderObject *cb) 1216 { 1217 while (r && r != cb) { 1218 r = r->parent(); 1219 } 1220 return r; 1221 } 1222 1223 /** checks whether the given block contains at least one editable element. 1224 * 1225 * Warning: This function has linear complexity, and therefore is expensive. 1226 * Use it sparingly, and cache the result. 1227 * @param part part 1228 * @param cb block to be searched 1229 * @param table returns the nested table if there is one directly at the beginning 1230 * or at the end. 1231 * @param fromEnd begin search from end (default: begin from beginning) 1232 */ 1233 static bool containsEditableElement(KHTMLPart *part, RenderBlock *cb, 1234 RenderTable *&table, bool fromEnd = false) 1235 { 1236 RenderObject *r = cb; 1237 if (fromEnd) 1238 while (r->lastChild()) { 1239 r = r->lastChild(); 1240 } 1241 else 1242 while (r->firstChild()) { 1243 r = r->firstChild(); 1244 } 1245 1246 RenderTable *tempTable = 0; 1247 table = 0; 1248 bool withinCb; 1249 // int state; // not used 1250 ObjectTraversalState trav = InsideDescending; 1251 do { 1252 bool modWithinCb = withinCb = isDescendant(r, cb); 1253 1254 // treat cb extra, it would not be considered otherwise 1255 if (!modWithinCb) { 1256 modWithinCb = true; 1257 r = cb; 1258 } else { 1259 tempTable = findTableUpTo(r, cb); 1260 } 1261 1262 #if DEBUG_CARETMODE > 1 1263 // qCDebug(KHTML_LOG) << "cee: r " << (r ? r->renderName() : QString()) << "@" << r << " cb " << cb << " withinCb " << withinCb << " modWithinCb " << modWithinCb << " tempTable " << tempTable; 1264 #endif 1265 if (r && modWithinCb && r->element() && !isUnsuitable(r, trav) 1266 && (part->isCaretMode() || part->isEditable() 1267 || r->style()->userInput() == UI_ENABLED)) { 1268 table = tempTable; 1269 #if DEBUG_CARETMODE > 1 1270 // qCDebug(KHTML_LOG) << "cee: editable"; 1271 #endif 1272 return true; 1273 }/*end if*/ 1274 1275 // RenderObject *oldr = r; 1276 // while (r && r == oldr) 1277 // r = advanceSuitableObject(r, trav, fromEnd, cb->parent(), state); 1278 r = fromEnd ? r->objectAbove() : r->objectBelow(); 1279 } while (r && withinCb); 1280 return false; 1281 } 1282 1283 /** checks whether the given block contains at least one editable child 1284 * element, beginning with but excluding @p start. 1285 * 1286 * Warning: This function has linear complexity, and therefore is expensive. 1287 * Use it sparingly, and cache the result. 1288 * @param part part 1289 * @param cb block to be searched 1290 * @param table returns the nested table if there is one directly before/after 1291 * the start object. 1292 * @param fromEnd begin search from end (default: begin from beginning) 1293 * @param start object after which to begin search. 1294 */ 1295 static bool containsEditableChildElement(KHTMLPart *part, RenderBlock *cb, 1296 RenderTable *&table, bool fromEnd, RenderObject *start) 1297 { 1298 int state = 0; 1299 ObjectTraversalState trav = OutsideAscending; 1300 // qCDebug(KHTML_LOG) << "start: " << start; 1301 RenderObject *r = start; 1302 do { 1303 r = traverseRenderObjects(r, trav, fromEnd, cb->parent(), state); 1304 } while (r && !(state & AdvancedToSibling)); 1305 // qCDebug(KHTML_LOG) << "r: " << r; 1306 //advanceObject(start, trav, fromEnd, cb->parent(), state); 1307 // RenderObject *oldr = r; 1308 // while (r && r == oldr) 1309 if (!r) { 1310 return false; 1311 } 1312 1313 if (fromEnd) 1314 while (r->firstChild()) { 1315 r = r->firstChild(); 1316 } 1317 else 1318 while (r->lastChild()) { 1319 r = r->lastChild(); 1320 } 1321 // qCDebug(KHTML_LOG) << "child r: " << r; 1322 if (!r) { 1323 return false; 1324 } 1325 1326 RenderTable *tempTable = 0; 1327 table = 0; 1328 bool withinCb = false; 1329 do { 1330 1331 bool modWithinCb = withinCb = isDescendant(r, cb); 1332 1333 // treat cb extra, it would not be considered otherwise 1334 if (!modWithinCb) { 1335 modWithinCb = true; 1336 r = cb; 1337 } else { 1338 tempTable = findTableUpTo(r, cb); 1339 } 1340 1341 #if DEBUG_CARETMODE > 1 1342 // qCDebug(KHTML_LOG) << "cece: r " << (r ? r->renderName() : QString()) << "@" << r << " cb " << cb << " withinCb " << withinCb << " modWithinCb " << modWithinCb << " tempTable " << tempTable; 1343 #endif 1344 if (r && withinCb && r->element() && !isUnsuitable(r, trav) 1345 && (part->isCaretMode() || part->isEditable() 1346 || r->style()->userInput() == UI_ENABLED)) { 1347 table = tempTable; 1348 #if DEBUG_CARETMODE > 1 1349 // qCDebug(KHTML_LOG) << "cece: editable"; 1350 #endif 1351 return true; 1352 }/*end if*/ 1353 1354 r = fromEnd ? r->objectAbove() : r->objectBelow(); 1355 } while (withinCb); 1356 return false; 1357 } 1358 1359 // == class LinearDocument implementation 1360 1361 LinearDocument::LinearDocument(KHTMLPart *part, NodeImpl *node, long offset, 1362 CaretAdvancePolicy advancePolicy, ElementImpl *baseElem) 1363 : node(node), offset(offset), m_part(part), 1364 advPol(advancePolicy), base(0) 1365 { 1366 if (node == 0) { 1367 return; 1368 } 1369 1370 if (baseElem) { 1371 RenderObject *b = baseElem->renderer(); 1372 if (b && (b->isRenderBlock() || b->isRenderInline())) { 1373 base = b; 1374 } 1375 } 1376 1377 initPreBeginIterator(); 1378 initEndIterator(); 1379 } 1380 1381 LinearDocument::~LinearDocument() 1382 { 1383 } 1384 1385 int LinearDocument::count() const 1386 { 1387 // FIXME: not implemented 1388 return 1; 1389 } 1390 1391 LinearDocument::Iterator LinearDocument::current() 1392 { 1393 return LineIterator(this, node, offset); 1394 } 1395 1396 LinearDocument::Iterator LinearDocument::begin() 1397 { 1398 NodeImpl *n = base ? base->element() : 0; 1399 if (!base) { 1400 n = node ? node->getDocument() : 0; 1401 } 1402 if (!n) { 1403 return end(); 1404 } 1405 1406 n = n->firstChild(); 1407 if (advPol == LeafsOnly) 1408 while (n->firstChild()) { 1409 n = n->firstChild(); 1410 } 1411 1412 if (!n) { 1413 return end(); // must be empty document or empty base element 1414 } 1415 return LineIterator(this, n, n->minOffset()); 1416 } 1417 1418 LinearDocument::Iterator LinearDocument::preEnd() 1419 { 1420 NodeImpl *n = base ? base->element() : 0; 1421 if (!base) { 1422 n = node ? node->getDocument() : 0; 1423 } 1424 if (!n) { 1425 return preBegin(); 1426 } 1427 1428 n = n->lastChild(); 1429 if (advPol == LeafsOnly) 1430 while (n->lastChild()) { 1431 n = n->lastChild(); 1432 } 1433 1434 if (!n) { 1435 return preBegin(); // must be empty document or empty base element 1436 } 1437 return LineIterator(this, n, n->maxOffset()); 1438 } 1439 1440 void LinearDocument::initPreBeginIterator() 1441 { 1442 _preBegin = LineIterator(this, 0, 0); 1443 } 1444 1445 void LinearDocument::initEndIterator() 1446 { 1447 _end = LineIterator(this, 0, 1); 1448 } 1449 1450 // == class LineIterator implementation 1451 1452 CaretBoxIterator LineIterator::currentBox /*KHTML_NO_EXPORT*/; 1453 long LineIterator::currentOffset /*KHTML_NO_EXPORT*/; 1454 1455 LineIterator::LineIterator(LinearDocument *l, DOM::NodeImpl *node, long offset) 1456 : lines(l) 1457 { 1458 // qCDebug(KHTML_LOG) << "LineIterator: node " << node << " offset " << offset; 1459 if (!node) { 1460 cbl = 0; 1461 return; 1462 } 1463 cbl = findCaretBoxLine(node, offset, &lines->cblDeleter, 1464 l->baseObject(), currentOffset, currentBox); 1465 // can happen on partially loaded documents 1466 #if DEBUG_CARETMODE > 0 1467 if (!cbl) // qCDebug(KHTML_LOG) << "no render object found!"; 1468 #endif 1469 if (!cbl) { 1470 return; 1471 } 1472 #if DEBUG_CARETMODE > 1 1473 // qCDebug(KHTML_LOG) << "LineIterator: offset " << offset << " outside " << cbl->isOutside(); 1474 #endif 1475 #if DEBUG_CARETMODE > 3 1476 // qCDebug(KHTML_LOG) << cbl->information(); 1477 #endif 1478 if (currentBox == cbl->end()) { 1479 #if DEBUG_CARETMODE > 0 1480 // qCDebug(KHTML_LOG) << "LineIterator: findCaretBoxLine failed"; 1481 #endif 1482 cbl = 0; 1483 }/*end if*/ 1484 } 1485 1486 void LineIterator::nextBlock() 1487 { 1488 RenderObject *base = lines->baseObject(); 1489 1490 bool cb_outside = cbl->isOutside(); 1491 bool cb_outside_end = cbl->isOutsideEnd(); 1492 1493 { 1494 RenderObject *r = cbl->enclosingObject(); 1495 1496 ObjectTraversalState trav; 1497 int state; // not used 1498 mapRenderPosToTraversalState(cb_outside, cb_outside_end, false, trav); 1499 #if DEBUG_CARETMODE > 1 1500 // qCDebug(KHTML_LOG) << "nextBlock: before adv r" << r << ' ' << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, qMin(((RenderText *)r)->str->l,15)) + "\"" : QString()) << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; 1501 #endif 1502 r = advanceSuitableObject(r, trav, false, base, state); 1503 if (!r) { 1504 cbl = 0; 1505 return; 1506 }/*end if*/ 1507 1508 mapTraversalStateToRenderPos(trav, false, cb_outside, cb_outside_end); 1509 #if DEBUG_CARETMODE > 1 1510 // qCDebug(KHTML_LOG) << "nextBlock: after r" << r << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; 1511 #endif 1512 #if DEBUG_CARETMODE > 0 1513 // qCDebug(KHTML_LOG) << "++: r " << r << "[" << (r?r->renderName():QString()) << "]"; 1514 #endif 1515 1516 RenderBlock *cb; 1517 1518 // If we hit a block or replaced object, use this as its enclosing object 1519 bool isrepl = isBlockRenderReplaced(r); 1520 if (r->isRenderBlock() || isrepl) { 1521 RenderBox *cb = static_cast<RenderBox *>(r); 1522 1523 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, 1524 cb_outside, cb_outside_end, currentBox); 1525 1526 #if DEBUG_CARETMODE > 0 1527 // qCDebug(KHTML_LOG) << "r->isFlow is cb. continuation @" << cb->continuation(); 1528 #endif 1529 return; 1530 } else { 1531 cb = r->containingBlock(); 1532 Q_ASSERT(cb->isRenderBlock()); 1533 }/*end if*/ 1534 InlineFlowBox *flowBox = cb->firstLineBox(); 1535 #if DEBUG_CARETMODE > 0 1536 // qCDebug(KHTML_LOG) << "++: flowBox " << flowBox << " cb " << cb << '[' << (cb?cb->renderName()+QString(".node ")+QString::number((unsigned)cb->element(),16)+(cb->element()?'@'+cb->element()->nodeName().string():QString()):QString()) << ']'; 1537 #endif 1538 Q_ASSERT(flowBox); 1539 if (!flowBox) { // ### utter emergency (why is this possible at all?) 1540 cb_outside = cb_outside_end = true; 1541 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, 1542 cb_outside, cb_outside_end, currentBox); 1543 return; 1544 } 1545 1546 bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning 1547 CaretBoxIterator it; 1548 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, 1549 flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); 1550 } 1551 } 1552 1553 void LineIterator::prevBlock() 1554 { 1555 RenderObject *base = lines->baseObject(); 1556 1557 bool cb_outside = cbl->isOutside(); 1558 bool cb_outside_end = cbl->isOutsideEnd(); 1559 1560 { 1561 RenderObject *r = cbl->enclosingObject(); 1562 if (r->isAnonymous() && !cb_outside) { 1563 cb_outside = true, cb_outside_end = false; 1564 } 1565 1566 ObjectTraversalState trav; 1567 int state; // not used 1568 mapRenderPosToTraversalState(cb_outside, cb_outside_end, true, trav); 1569 #if DEBUG_CARETMODE > 1 1570 // qCDebug(KHTML_LOG) << "prevBlock: before adv r" << r << " " << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, qMin(((RenderText *)r)->str->l,15)) + "\"" : QString()) << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; 1571 #endif 1572 r = advanceSuitableObject(r, trav, true, base, state); 1573 if (!r) { 1574 cbl = 0; 1575 return; 1576 }/*end if*/ 1577 1578 mapTraversalStateToRenderPos(trav, true, cb_outside, cb_outside_end); 1579 #if DEBUG_CARETMODE > 1 1580 // qCDebug(KHTML_LOG) << "prevBlock: after r" << r << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; 1581 #endif 1582 #if DEBUG_CARETMODE > 0 1583 // qCDebug(KHTML_LOG) << "--: r " << r << "[" << (r?r->renderName():QString()) << "]"; 1584 #endif 1585 1586 RenderBlock *cb; 1587 1588 // If we hit a block, use this as its enclosing object 1589 bool isrepl = isBlockRenderReplaced(r); 1590 // qCDebug(KHTML_LOG) << "isrepl " << isrepl << " isblock " << r->isRenderBlock(); 1591 if (r->isRenderBlock() || isrepl) { 1592 RenderBox *cb = static_cast<RenderBox *>(r); 1593 1594 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, 1595 cb_outside, cb_outside_end, currentBox); 1596 1597 #if DEBUG_CARETMODE > 0 1598 // qCDebug(KHTML_LOG) << "r->isFlow is cb. continuation @" << cb->continuation(); 1599 #endif 1600 return; 1601 } else { 1602 cb = r->containingBlock(); 1603 Q_ASSERT(cb->isRenderBlock()); 1604 }/*end if*/ 1605 InlineFlowBox *flowBox = cb->lastLineBox(); 1606 #if DEBUG_CARETMODE > 0 1607 // qCDebug(KHTML_LOG) << "--: flowBox " << flowBox << " cb " << cb << "[" << (cb?cb->renderName()+QString(".node ")+QString::number((unsigned)cb->element(),16)+(cb->element()?"@"+cb->element()->nodeName().string():QString()):QString()) << "]"; 1608 #endif 1609 Q_ASSERT(flowBox); 1610 if (!flowBox) { // ### utter emergency (why is this possible at all?) 1611 cb_outside = true; cb_outside_end = false; 1612 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, 1613 cb_outside, cb_outside_end, currentBox); 1614 return; 1615 } 1616 1617 bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning 1618 CaretBoxIterator it; 1619 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, 1620 flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); 1621 } 1622 } 1623 1624 void LineIterator::advance(bool toBegin) 1625 { 1626 InlineFlowBox *flowBox = cbl->baseFlowBox(); 1627 if (flowBox) { 1628 flowBox = static_cast<InlineFlowBox *>(toBegin ? flowBox->prevLineBox() : flowBox->nextLineBox()); 1629 if (flowBox) { 1630 bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning 1631 CaretBoxIterator it; 1632 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, 1633 flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); 1634 }/*end if*/ 1635 }/*end if*/ 1636 1637 // if there are no more lines in this block, move towards block to come 1638 if (!flowBox) { 1639 if (toBegin) { 1640 prevBlock(); 1641 } else { 1642 nextBlock(); 1643 } 1644 } 1645 1646 #if DEBUG_CARETMODE > 3 1647 if (cbl) // qCDebug(KHTML_LOG) << cbl->information(); 1648 #endif 1649 } 1650 1651 // == class EditableCaretBoxIterator implementation 1652 1653 void EditableCaretBoxIterator::advance(bool toBegin) 1654 { 1655 #if DEBUG_CARETMODE > 3 1656 // qCDebug(KHTML_LOG) << "---------------" << "toBegin " << toBegin; 1657 #endif 1658 const CaretBoxIterator preBegin = cbl->preBegin(); 1659 const CaretBoxIterator end = cbl->end(); 1660 1661 CaretBoxIterator lastbox = *this, curbox; 1662 bool islastuseable = true; // silence gcc 1663 bool iscuruseable; 1664 // Assume adjacency of caret boxes. Will be falsified later if applicable. 1665 adjacent = true; 1666 1667 #if DEBUG_CARETMODE > 4 1668 // qCDebug(KHTML_LOG) << "ebit::advance: before: " << (**this)->object() << "@" << (**this)->object()->renderName() << ".node " << (**this)->object()->element() << "[" << ((**this)->object()->element() ? (**this)->object()->element()->nodeName().string() : QString()) << "] inline " << (**this)->isInline() << " outside " << (**this)->isOutside() << " outsideEnd " << (**this)->isOutsideEnd(); 1669 #endif 1670 1671 if (toBegin) { 1672 CaretBoxIterator::operator --(); 1673 } else { 1674 CaretBoxIterator::operator ++(); 1675 } 1676 bool curAtEnd = *this == preBegin || *this == end; 1677 curbox = *this; 1678 bool atEnd = true; 1679 if (!curAtEnd) { 1680 iscuruseable = isEditable(curbox, toBegin); 1681 if (toBegin) { 1682 CaretBoxIterator::operator --(); 1683 } else { 1684 CaretBoxIterator::operator ++(); 1685 } 1686 atEnd = *this == preBegin || *this == end; 1687 } 1688 while (!curAtEnd) { 1689 bool haslast = lastbox != end && lastbox != preBegin; 1690 bool hascoming = !atEnd; 1691 bool iscominguseable = true; // silence gcc 1692 1693 if (!atEnd) { 1694 iscominguseable = isEditable(*this, toBegin); 1695 } 1696 if (iscuruseable) { 1697 #if DEBUG_CARETMODE > 3 1698 // qCDebug(KHTML_LOG) << "ebit::advance: " << (*curbox)->object() << "@" << (*curbox)->object()->renderName() << ".node " << (*curbox)->object()->element() << "[" << ((*curbox)->object()->element() ? (*curbox)->object()->element()->nodeName().string() : QString()) << "] inline " << (*curbox)->isInline() << " outside " << (*curbox)->isOutside() << " outsideEnd " << (*curbox)->isOutsideEnd(); 1699 #endif 1700 1701 CaretBox *box = *curbox; 1702 if (box->isOutside()) { 1703 // if this caret box represents no inline box, it is an outside box 1704 // which has to be considered unconditionally 1705 if (!box->isInline()) { 1706 break; 1707 } 1708 1709 if (advpol == VisibleFlows) { 1710 break; 1711 } 1712 1713 // IndicatedFlows and LeafsOnly are treated equally in caret box lines 1714 1715 InlineBox *ibox = box->inlineBox(); 1716 // get previous inline box 1717 InlineBox *prev = box->isOutsideEnd() ? ibox : ibox->prevOnLine(); 1718 // get next inline box 1719 InlineBox *next = box->isOutsideEnd() ? ibox->nextOnLine() : ibox; 1720 1721 const bool isprevindicated = !prev || isIndicatedInlineBox(prev); 1722 const bool isnextindicated = !next || isIndicatedInlineBox(next); 1723 const bool last = haslast && !islastuseable; 1724 const bool coming = hascoming && !iscominguseable; 1725 const bool left = !prev || prev->isInlineFlowBox() && isprevindicated 1726 || (toBegin && coming || !toBegin && last); 1727 const bool right = !next || next->isInlineFlowBox() && isnextindicated 1728 || (!toBegin && coming || toBegin && last); 1729 const bool text2indicated = toBegin && next && next->isInlineTextBox() 1730 && isprevindicated 1731 || !toBegin && prev && prev->isInlineTextBox() && isnextindicated; 1732 const bool indicated2text = !toBegin && next && next->isInlineTextBox() 1733 && prev && isprevindicated 1734 // ### this code is so broken. 1735 /*|| toBegin && prev && prev->isInlineTextBox() && isnextindicated*/; 1736 #if DEBUG_CARETMODE > 5 1737 // qCDebug(KHTML_LOG) << "prev " << prev << " haslast " << haslast << " islastuseable " << islastuseable << " left " << left << " next " << next << " hascoming " << hascoming << " iscominguseable " << iscominguseable << " right " << right << " text2indicated " << text2indicated << " indicated2text " << indicated2text; 1738 #endif 1739 1740 if (left && right && !text2indicated || indicated2text) { 1741 adjacent = false; 1742 #if DEBUG_CARETMODE > 4 1743 // qCDebug(KHTML_LOG) << "left && right && !text2indicated || indicated2text"; 1744 #endif 1745 break; 1746 } 1747 1748 } else { 1749 // inside boxes are *always* valid 1750 #if DEBUG_CARETMODE > 4 1751 if (box->isInline()) { 1752 InlineBox *ibox = box->inlineBox(); 1753 // qCDebug(KHTML_LOG) << "inside " << (!ibox->isInlineFlowBox() || static_cast<InlineFlowBox *>(ibox)->firstChild() ? "non-empty" : "empty") << (isIndicatedInlineBox(ibox) ? " indicated" : "") << " adjacent=" << adjacent; 1754 } 1755 #if 0 1756 RenderStyle *s = ibox->object()->style(); 1757 // qCDebug(KHTML_LOG) << "bordls " << s->borderLeftStyle() 1758 << " bordl " << (s->borderLeftStyle() != BNONE) 1759 << " bordr " << (s->borderRightStyle() != BNONE) 1760 << " bordt " << (s->borderTopStyle() != BNONE) 1761 << " bordb " << (s->borderBottomStyle() != BNONE) 1762 << " padl " << s->paddingLeft().value() 1763 << " padr " << s->paddingRight().value() 1764 << " padt " << s->paddingTop().value() 1765 << " padb " << s->paddingBottom().value() 1766 // ### Can inline elements have top/bottom margins? Couldn't find 1767 // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. 1768 << " marl " << s->marginLeft().value() 1769 << " marr " << s->marginRight().value(); 1770 #endif 1771 #endif 1772 break; 1773 }/*end if*/ 1774 1775 } else { 1776 1777 if (!(*curbox)->isOutside()) { 1778 // cannot be adjacent anymore 1779 adjacent = false; 1780 } 1781 1782 }/*end if*/ 1783 lastbox = curbox; 1784 islastuseable = iscuruseable; 1785 curbox = *this; 1786 iscuruseable = iscominguseable; 1787 curAtEnd = atEnd; 1788 if (!atEnd) { 1789 if (toBegin) { 1790 CaretBoxIterator::operator --(); 1791 } else { 1792 CaretBoxIterator::operator ++(); 1793 } 1794 atEnd = *this == preBegin || *this == end; 1795 }/*end if*/ 1796 }/*wend*/ 1797 1798 *static_cast<CaretBoxIterator *>(this) = curbox; 1799 #if DEBUG_CARETMODE > 4 1800 // qCDebug(KHTML_LOG) << "still valid? " << (*this != preBegin && *this != end); 1801 #endif 1802 #if DEBUG_CARETMODE > 3 1803 // qCDebug(KHTML_LOG) << "---------------" << "end "; 1804 #endif 1805 } 1806 1807 bool EditableCaretBoxIterator::isEditable(const CaretBoxIterator &boxit, bool fromEnd) 1808 { 1809 Q_ASSERT(boxit != cbl->end() && boxit != cbl->preBegin()); 1810 CaretBox *b = *boxit; 1811 RenderObject *r = b->object(); 1812 #if DEBUG_CARETMODE > 0 1813 // if (b->isInlineFlowBox()) qCDebug(KHTML_LOG) << "b is inline flow box" << (outside ? " (outside)" : ""); 1814 // qCDebug(KHTML_LOG) << "isEditable r" << r << ": " << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, qMin(((RenderText *)r)->str->l,15)) + "\"" : QString()); 1815 #endif 1816 // Must check caret mode or design mode *after* r->element(), otherwise 1817 // lines without a backing DOM node get regarded, leading to a crash. 1818 // ### check should actually be in InlineBoxIterator 1819 NodeImpl *node = r->element(); 1820 ObjectTraversalState trav; 1821 mapRenderPosToTraversalState(b->isOutside(), b->isOutsideEnd(), fromEnd, trav); 1822 if (isUnsuitable(r, trav) || !node) { 1823 return false; 1824 } 1825 1826 // generally exclude replaced elements with no children from navigation 1827 if (!b->isOutside() && r->isRenderReplaced() && !r->firstChild()) { 1828 return false; 1829 } 1830 1831 RenderObject *eff_r = r; 1832 bool globallyNavigable = m_part->isCaretMode() || m_part->isEditable(); 1833 1834 // calculate the parent element's editability if this inline box is outside. 1835 if (b->isOutside() && !globallyNavigable) { 1836 NodeImpl *par = node->parent(); 1837 // I wonder whether par can be 0. It shouldn't be possible if the 1838 // algorithm contained no bugs. 1839 Q_ASSERT(par); 1840 if (par) { 1841 node = par; 1842 } 1843 eff_r = node->renderer(); 1844 Q_ASSERT(eff_r); // this is a hard requirement 1845 } 1846 1847 bool result = globallyNavigable || eff_r->style()->userInput() == UI_ENABLED; 1848 #if DEBUG_CARETMODE > 0 1849 // qCDebug(KHTML_LOG) << result; 1850 #endif 1851 return result; 1852 } 1853 1854 // == class EditableLineIterator implementation 1855 1856 void EditableLineIterator::advance(bool toBegin) 1857 { 1858 CaretAdvancePolicy advpol = lines->advancePolicy(); 1859 LineIterator lasteditable, lastindicated; 1860 bool haslasteditable = false; 1861 bool haslastindicated = false; 1862 bool uselasteditable = false; 1863 1864 LineIterator::advance(toBegin); 1865 while (cbl) { 1866 if (isEditable(*this)) { 1867 #if DEBUG_CARETMODE > 3 1868 // qCDebug(KHTML_LOG) << "advance: " << cbl->enclosingObject() << "@" << cbl->enclosingObject()->renderName() << ".node " << cbl->enclosingObject()->element() << "[" << (cbl->enclosingObject()->element() ? cbl->enclosingObject()->element()->nodeName().string() : QString()) << "]"; 1869 #endif 1870 1871 bool hasindicated = isIndicatedFlow(cbl->enclosingObject()); 1872 if (hasindicated) { 1873 haslastindicated = true; 1874 lastindicated = *this; 1875 } 1876 1877 switch (advpol) { 1878 case IndicatedFlows: 1879 if (hasindicated) { 1880 goto wend; 1881 } 1882 // fall through 1883 case LeafsOnly: 1884 if (cbl->isOutside()) { 1885 break; 1886 } 1887 // fall through 1888 case VisibleFlows: goto wend; 1889 }/*end switch*/ 1890 1891 // remember rejected editable element 1892 lasteditable = *this; 1893 haslasteditable = true; 1894 #if DEBUG_CARETMODE > 4 1895 // qCDebug(KHTML_LOG) << "remembered lasteditable " << *lasteditable; 1896 #endif 1897 } else { 1898 1899 // If this element isn't editable, but the last one was, and it was only 1900 // rejected because it didn't match the caret advance policy, force it. 1901 // Otherwise certain combinations of editable and uneditable elements 1902 // could never be reached with some policies. 1903 if (haslasteditable) { 1904 uselasteditable = true; 1905 break; 1906 } 1907 1908 } 1909 LineIterator::advance(toBegin); 1910 }/*wend*/ 1911 wend: 1912 1913 if (uselasteditable) { 1914 *this = haslastindicated ? lastindicated : lasteditable; 1915 } 1916 if (!cbl && haslastindicated) { 1917 *this = lastindicated; 1918 } 1919 } 1920 1921 // == class EditableCharacterIterator implementation 1922 1923 void EditableCharacterIterator::initFirstChar() 1924 { 1925 CaretBox *box = *ebit; 1926 InlineBox *b = box->inlineBox(); 1927 if (_offset == box->maxOffset()) { 1928 peekNext(); 1929 } else if (b && !box->isOutside() && b->isInlineTextBox()) { 1930 _char = static_cast<RenderText *>(b->object())->str->s[_offset].unicode(); 1931 } else { 1932 _char = -1; 1933 } 1934 } 1935 1936 /** returns true when the given caret box is empty, i. e. should not 1937 * take place in caret movement. 1938 */ 1939 static inline bool isCaretBoxEmpty(CaretBox *box) 1940 { 1941 if (!box->isInline()) { 1942 return false; 1943 } 1944 InlineBox *ibox = box->inlineBox(); 1945 return ibox->isInlineFlowBox() 1946 && !static_cast<InlineFlowBox *>(ibox)->firstChild() 1947 && !isIndicatedInlineBox(ibox); 1948 } 1949 1950 EditableCharacterIterator &EditableCharacterIterator::operator ++() 1951 { 1952 _offset++; 1953 1954 CaretBox *box = *ebit; 1955 InlineBox *b = box->inlineBox(); 1956 long maxofs = box->maxOffset(); 1957 #if DEBUG_CARETMODE > 0 1958 // qCDebug(KHTML_LOG) << "box->maxOffset() " << box->maxOffset() << " box->minOffset() " << box->minOffset(); 1959 #endif 1960 if (_offset == maxofs) { 1961 #if DEBUG_CARETMODE > 2 1962 // qCDebug(KHTML_LOG) << "_offset == maxofs: " << _offset << " == " << maxofs; 1963 #endif 1964 peekNext(); 1965 } else if (_offset > maxofs) { 1966 #if DEBUG_CARETMODE > 2 1967 // qCDebug(KHTML_LOG) << "_offset > maxofs: " << _offset << " > " << maxofs /*<< " _peekNext: " << _peekNext*/; 1968 #endif 1969 if (/*!_peekNext*/true) { 1970 ++ebit; 1971 if (ebit == (*_it)->end()) { // end of line reached, go to next line 1972 ++_it; 1973 #if DEBUG_CARETMODE > 3 1974 // qCDebug(KHTML_LOG) << "++_it"; 1975 #endif 1976 if (_it != _it.lines->end()) { 1977 ebit = _it; 1978 box = *ebit; 1979 b = box->inlineBox(); 1980 #if DEBUG_CARETMODE > 3 1981 // qCDebug(KHTML_LOG) << "box " << box << " b " << b << " isText " << box->isInlineTextBox(); 1982 #endif 1983 1984 #if DEBUG_CARETMODE > 3 1985 RenderObject *_r = box->object(); 1986 // qCDebug(KHTML_LOG) << "_r " << _r << ":" << _r->element()->nodeName().string(); 1987 #endif 1988 _offset = box->minOffset(); 1989 #if DEBUG_CARETMODE > 3 1990 // qCDebug(KHTML_LOG) << "_offset " << _offset; 1991 #endif 1992 } else { 1993 b = 0; 1994 _end = true; 1995 }/*end if*/ 1996 goto readchar; 1997 }/*end if*/ 1998 }/*end if*/ 1999 2000 bool adjacent = ebit.isAdjacent(); 2001 #if 0 2002 // Jump over element if this one is not a text node. 2003 if (adjacent && !(*ebit)->isInlineTextBox()) { 2004 EditableCaretBoxIterator copy = ebit; 2005 ++ebit; 2006 if (ebit != (*_it)->end() && (*ebit)->isInlineTextBox() 2007 /*&& (!(*ebit)->isInlineFlowBox() 2008 || static_cast<InlineFlowBox *>(*ebit)->)*/) { 2009 adjacent = false; 2010 } else { 2011 ebit = copy; 2012 } 2013 }/*end if*/ 2014 #endif 2015 // Jump over empty elements. 2016 if (adjacent && !(*ebit)->isInlineTextBox()) { 2017 bool noemptybox = true; 2018 while (isCaretBoxEmpty(*ebit)) { 2019 noemptybox = false; 2020 EditableCaretBoxIterator copy = ebit; 2021 ++ebit; 2022 if (ebit == (*_it)->end()) { 2023 ebit = copy; 2024 break; 2025 } 2026 } 2027 if (noemptybox) { 2028 adjacent = false; 2029 } 2030 }/*end if*/ 2031 // _r = (*ebit)->object(); 2032 /*if (!_it.outside) */_offset = (*ebit)->minOffset() + adjacent; 2033 //_peekNext = 0; 2034 box = *ebit; 2035 b = box->inlineBox(); 2036 goto readchar; 2037 } else { 2038 readchar: 2039 // get character 2040 if (b && !box->isOutside() && b->isInlineTextBox() && _offset < b->maxOffset()) { 2041 _char = static_cast<RenderText *>(b->object())->str->s[_offset].unicode(); 2042 } else { 2043 _char = -1; 2044 } 2045 }/*end if*/ 2046 #if DEBUG_CARETMODE > 2 2047 // qCDebug(KHTML_LOG) << "_offset: " << _offset /*<< " _peekNext: " << _peekNext*/ << " char '" << (char)_char << "'"; 2048 #endif 2049 2050 #if DEBUG_CARETMODE > 0 2051 if (!_end && ebit != (*_it)->end()) { 2052 CaretBox *box = *ebit; 2053 RenderObject *_r = box->object(); 2054 // qCDebug(KHTML_LOG) << "echit++(1): box " << box << (box && box->isInlineTextBox() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : QString()) << " _r " << (_r ? _r->element()->nodeName().string() : QString("<nil>")); 2055 } 2056 #endif 2057 return *this; 2058 } 2059 2060 EditableCharacterIterator &EditableCharacterIterator::operator --() 2061 { 2062 _offset--; 2063 //qCDebug(KHTML_LOG) << "--: _offset=" << _offset; 2064 2065 CaretBox *box = *ebit; 2066 CaretBox *_peekPrev = 0; 2067 CaretBox *_peekNext = 0; 2068 InlineBox *b = box->inlineBox(); 2069 long minofs = box->minOffset(); 2070 #if DEBUG_CARETMODE > 0 2071 // qCDebug(KHTML_LOG) << "box->maxOffset() " << box->maxOffset() << " box->minOffset() " << box->minOffset(); 2072 #endif 2073 if (_offset == minofs) { 2074 #if DEBUG_CARETMODE > 2 2075 // qCDebug(KHTML_LOG) << "_offset == minofs: " << _offset << " == " << minofs; 2076 #endif 2077 // _peekNext = b; 2078 // get character 2079 if (b && !box->isOutside() && b->isInlineTextBox()) { 2080 _char = static_cast<RenderText *>(b->object())->text()[_offset].unicode(); 2081 } else { 2082 _char = -1; 2083 } 2084 2085 //peekPrev(); 2086 bool do_prev = false; 2087 { 2088 EditableCaretBoxIterator copy; 2089 _peekPrev = 0; 2090 do { 2091 copy = ebit; 2092 --ebit; 2093 if (ebit == (*_it)->preBegin()) { 2094 ebit = copy; 2095 break; 2096 } 2097 } while (isCaretBoxEmpty(*ebit)); 2098 // Jump to end of previous element if it's adjacent, and a text box 2099 if (ebit.isAdjacent() && ebit != (*_it)->preBegin() && (*ebit)->isInlineTextBox()) { 2100 _peekPrev = *ebit; 2101 do_prev = true; 2102 } else { 2103 ebit = copy; 2104 } 2105 } 2106 if (do_prev) { 2107 goto prev; 2108 } 2109 } else if (_offset < minofs) { 2110 prev: 2111 #if DEBUG_CARETMODE > 2 2112 // qCDebug(KHTML_LOG) << "_offset < minofs: " << _offset << " < " << minofs /*<< " _peekNext: " << _peekNext*/; 2113 #endif 2114 if (!_peekPrev) { 2115 _peekNext = *ebit; 2116 --ebit; 2117 if (ebit == (*_it)->preBegin()) { // end of line reached, go to previous line 2118 --_it; 2119 #if DEBUG_CARETMODE > 3 2120 // qCDebug(KHTML_LOG) << "--_it"; 2121 #endif 2122 if (_it != _it.lines->preBegin()) { 2123 // qCDebug(KHTML_LOG) << "begin from end!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; 2124 ebit = EditableCaretBoxIterator(_it, true); 2125 box = *ebit; 2126 // RenderObject *r = box->object(); 2127 #if DEBUG_CARETMODE > 3 2128 // qCDebug(KHTML_LOG) << "box " << box << " b " << box->inlineBox() << " isText " << box->isInlineTextBox(); 2129 #endif 2130 _offset = box->maxOffset(); 2131 // if (!_it.outside) _offset = r->isBR() ? (*ebit)->minOffset() : (*ebit)->maxOffset(); 2132 _char = -1; 2133 #if DEBUG_CARETMODE > 0 2134 // qCDebug(KHTML_LOG) << "echit--(2): box " << box << " b " << box->inlineBox() << (box->isInlineTextBox() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : QString()); 2135 #endif 2136 } else { 2137 _end = true; 2138 } 2139 return *this; 2140 }/*end if*/ 2141 }/*end if*/ 2142 2143 #if DEBUG_CARETMODE > 0 2144 bool adjacent = ebit.isAdjacent(); 2145 // qCDebug(KHTML_LOG) << "adjacent " << adjacent << " _peekNext " << _peekNext << " _peekNext->isInlineTextBox: " << (_peekNext ? _peekNext->isInlineTextBox() : false) << " !((*ebit)->isInlineTextBox): " << (*ebit ? !(*ebit)->isInlineTextBox() : true); 2146 #endif 2147 #if 0 2148 // Ignore this box if it isn't a text box, but the previous box was 2149 if (adjacent && _peekNext && _peekNext->isInlineTextBox() 2150 && !(*ebit)->isInlineTextBox()) { 2151 EditableCaretBoxIterator copy = ebit; 2152 --ebit; 2153 if (ebit == (*_it)->preBegin()) /*adjacent = false; 2154 else */{ 2155 ebit = copy; 2156 } 2157 }/*end if*/ 2158 #endif 2159 #if 0 2160 // Jump over empty elements. 2161 if (adjacent //&& _peekNext && _peekNext->isInlineTextBox() 2162 && !(*ebit)->isInlineTextBox()) { 2163 bool noemptybox = true; 2164 while (isCaretBoxEmpty(*ebit)) { 2165 noemptybox = false; 2166 EditableCaretBoxIterator copy = ebit; 2167 --ebit; 2168 if (ebit == (*_it)->preBegin()) { 2169 ebit = copy; 2170 break; 2171 } else { 2172 _peekNext = *copy; 2173 } 2174 } 2175 if (noemptybox) { 2176 adjacent = false; 2177 } 2178 }/*end if*/ 2179 #endif 2180 #if DEBUG_CARETMODE > 0 2181 // qCDebug(KHTML_LOG) << "(*ebit)->obj " << (*ebit)->object()->renderName() << "[" << (*ebit)->object() << "]" << " minOffset: " << (*ebit)->minOffset() << " maxOffset: " << (*ebit)->maxOffset(); 2182 #endif 2183 #if DEBUG_CARETMODE > 3 2184 RenderObject *_r = (*ebit)->object(); 2185 // qCDebug(KHTML_LOG) << "_r " << _r << ":" << _r->element()->nodeName().string(); 2186 #endif 2187 _offset = (*ebit)->maxOffset(); 2188 // if (!_it.outside) _offset = (*ebit)->maxOffset()/* - adjacent*/; 2189 #if DEBUG_CARETMODE > 3 2190 // qCDebug(KHTML_LOG) << "_offset " << _offset; 2191 #endif 2192 _peekPrev = 0; 2193 } else { 2194 #if DEBUG_CARETMODE > 0 2195 // qCDebug(KHTML_LOG) << "_offset: " << _offset << " _peekNext: " << _peekNext; 2196 #endif 2197 // get character 2198 if (_peekNext && _offset >= box->maxOffset() && _peekNext->isInlineTextBox()) { 2199 _char = static_cast<RenderText *>(_peekNext->object())->text()[_peekNext->minOffset()].unicode(); 2200 } else if (b && _offset < b->maxOffset() && b->isInlineTextBox()) { 2201 _char = static_cast<RenderText *>(b->object())->text()[_offset].unicode(); 2202 } else { 2203 _char = -1; 2204 } 2205 }/*end if*/ 2206 2207 #if DEBUG_CARETMODE > 0 2208 if (!_end && ebit != (*_it)->preBegin()) { 2209 CaretBox *box = *ebit; 2210 // qCDebug(KHTML_LOG) << "echit--(1): box " << box << " b " << box->inlineBox() << (box->isInlineTextBox() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : QString()); 2211 } 2212 #endif 2213 return *this; 2214 } 2215 2216 // == class TableRowIterator implementation 2217 2218 TableRowIterator::TableRowIterator(RenderTable *table, bool fromEnd, 2219 RenderTableSection::RowStruct *row) 2220 : sec(table, fromEnd) 2221 { 2222 // set index 2223 if (*sec) { 2224 if (fromEnd) { 2225 index = (*sec)->grid.size() - 1; 2226 } else { 2227 index = 0; 2228 } 2229 }/*end if*/ 2230 2231 // initialize with given row 2232 if (row && *sec) { 2233 while (operator *() != row) 2234 if (fromEnd) { 2235 operator --(); 2236 } else { 2237 operator ++(); 2238 } 2239 }/*end if*/ 2240 } 2241 2242 TableRowIterator &TableRowIterator::operator ++() 2243 { 2244 index++; 2245 2246 if (index >= (int)(*sec)->grid.size()) { 2247 ++sec; 2248 2249 if (*sec) { 2250 index = 0; 2251 } 2252 }/*end if*/ 2253 return *this; 2254 } 2255 2256 TableRowIterator &TableRowIterator::operator --() 2257 { 2258 index--; 2259 2260 if (index < 0) { 2261 --sec; 2262 2263 if (*sec) { 2264 index = (*sec)->grid.size() - 1; 2265 } 2266 }/*end if*/ 2267 return *this; 2268 } 2269 2270 // == class ErgonomicEditableLineIterator implementation 2271 2272 // some decls 2273 static RenderTableCell *findNearestTableCellInRow(KHTMLPart *part, int x, 2274 RenderTableSection::RowStruct *row, bool fromEnd); 2275 2276 /** finds the cell corresponding to absolute x-coordinate @p x in the given 2277 * table. 2278 * 2279 * If there is no direct cell, or the cell is not accessible, the function 2280 * will return the nearest suitable cell. 2281 * @param part part containing the document 2282 * @param x absolute x-coordinate 2283 * @param it table row iterator, will be adapted accordingly as more rows are 2284 * investigated. 2285 * @param fromEnd @p true to begin search from end and work towards the 2286 * beginning 2287 * @return the cell, or 0 if no editable cell was found. 2288 */ 2289 static inline RenderTableCell *findNearestTableCell(KHTMLPart *part, int x, 2290 TableRowIterator &it, bool fromEnd) 2291 { 2292 RenderTableCell *result = 0; 2293 2294 while (*it) { 2295 result = findNearestTableCellInRow(part, x, *it, fromEnd); 2296 if (result) { 2297 break; 2298 } 2299 2300 if (fromEnd) { 2301 --it; 2302 } else { 2303 ++it; 2304 } 2305 }/*wend*/ 2306 2307 return result; 2308 } 2309 2310 /** finds the nearest editable cell around the given absolute x-coordinate 2311 * 2312 * It will dive into nested tables as necessary to provide seamless navigation. 2313 * 2314 * If the cell at @p x is not editable, its left neighbor is tried, then its 2315 * right neighbor, then the left neighbor's left neighbor etc. If no 2316 * editable cell can be found, 0 is returned. 2317 * @param part khtml part 2318 * @param x absolute x-coordinate 2319 * @param row table row to be searched 2320 * @param fromEnd @p true, begin from end (applies only to nested tables) 2321 * @return the found cell or 0 if no editable cell was found 2322 */ 2323 static RenderTableCell *findNearestTableCellInRow(KHTMLPart *part, int x, 2324 RenderTableSection::RowStruct *row, bool fromEnd) 2325 { 2326 // First pass. Find spatially nearest cell. 2327 int n = (int)row->row->size(); 2328 int i; 2329 for (i = 0; i < n; i++) { 2330 RenderTableCell *cell = row->row->at(i); 2331 if (!cell || (long)cell == -1) { 2332 continue; 2333 } 2334 2335 int absx, absy; 2336 cell->absolutePosition(absx, absy, false); // ### position: fixed? 2337 #if DEBUG_CARETMODE > 1 2338 // qCDebug(KHTML_LOG) << "i/n " << i << "/" << n << " absx " << absx << " absy " << absy; 2339 #endif 2340 2341 // I rely on the assumption that all cells are in ascending visual order 2342 // ### maybe this assumption is wrong for bidi? 2343 #if DEBUG_CARETMODE > 1 2344 // qCDebug(KHTML_LOG) << "x " << x << " < " << (absx + cell->width()) << "?"; 2345 #endif 2346 if (x < absx + cell->width()) { 2347 break; 2348 } 2349 }/*next i*/ 2350 if (i >= n) { 2351 i = n - 1; 2352 } 2353 2354 // Second pass. Find editable cell, beginning with the currently found, 2355 // extending to the left, and to the right, alternating. 2356 for (int cnt = 0; cnt < 2 * n; cnt++) { 2357 int index = i - ((cnt >> 1) + 1) * (cnt & 1) + (cnt >> 1) * !(cnt & 1); 2358 if (index < 0 || index >= n) { 2359 continue; 2360 } 2361 2362 RenderTableCell *cell = row->row->at(index); 2363 if (!cell || (long)cell == -1) { 2364 continue; 2365 } 2366 2367 #if DEBUG_CARETMODE > 1 2368 // qCDebug(KHTML_LOG) << "index " << index << " cell " << cell; 2369 #endif 2370 RenderTable *nestedTable; 2371 if (containsEditableElement(part, cell, nestedTable, fromEnd)) { 2372 2373 if (nestedTable) { 2374 TableRowIterator it(nestedTable, fromEnd); 2375 while (*it) { 2376 // qCDebug(KHTML_LOG) << "=== recursive invocation"; 2377 cell = findNearestTableCell(part, x, it, fromEnd); 2378 if (cell) { 2379 break; 2380 } 2381 if (fromEnd) { 2382 --it; 2383 } else { 2384 ++it; 2385 } 2386 }/*wend*/ 2387 }/*end if*/ 2388 2389 return cell; 2390 }/*end if*/ 2391 }/*next i*/ 2392 return 0; 2393 } 2394 2395 /** returns the nearest common ancestor of two objects that is a table cell, 2396 * a table section, or 0 if not inside a common table. 2397 * 2398 * If @p r1 and @p r2 belong to the same table, but different sections, @p r1's 2399 * section is returned. 2400 */ 2401 static RenderObject *commonAncestorTableSectionOrCell(RenderObject *r1, 2402 RenderObject *r2) 2403 { 2404 if (!r1 || !r2) { 2405 return 0; 2406 } 2407 RenderTableSection *sec = 0; 2408 int start_depth = 0, end_depth = 0; 2409 // First we find the depths of the two objects in the tree (start_depth, end_depth) 2410 RenderObject *n = r1; 2411 while (n->parent()) { 2412 n = n->parent(); 2413 start_depth++; 2414 }/*wend*/ 2415 n = r2; 2416 while (n->parent()) { 2417 n = n->parent(); 2418 end_depth++; 2419 }/*wend*/ 2420 // here we climb up the tree with the deeper object, until both objects have equal depth 2421 while (end_depth > start_depth) { 2422 r2 = r2->parent(); 2423 end_depth--; 2424 }/*wend*/ 2425 while (start_depth > end_depth) { 2426 r1 = r1->parent(); 2427 // if (r1->isTableSection()) sec = static_cast<RenderTableSection *>(r1); 2428 start_depth--; 2429 }/*wend*/ 2430 // Climb the tree with both r1 and r2 until they are the same 2431 while (r1 != r2) { 2432 r1 = r1->parent(); 2433 if (r1->isTableSection()) { 2434 sec = static_cast<RenderTableSection *>(r1); 2435 } 2436 r2 = r2->parent(); 2437 }/*wend*/ 2438 2439 // At this point, we found the most approximate common ancestor. Now climb 2440 // up until the condition of the function return value is satisfied. 2441 while (r1 && !r1->isTableCell() && !r1->isTableSection() && !r1->isTable()) { 2442 r1 = r1->parent(); 2443 } 2444 2445 return r1 && r1->isTable() ? sec : r1; 2446 } 2447 2448 /** Finds the row that contains the given cell, directly, or indirectly 2449 * @param section section to be searched 2450 * @param cell table cell 2451 * @param row returns the row 2452 * @param directCell returns the direct cell that contains @p cell 2453 * @return the index of the row. 2454 */ 2455 static int findRowInSection(RenderTableSection *section, RenderTableCell *cell, 2456 RenderTableSection::RowStruct *&row, RenderTableCell *&directCell) 2457 { 2458 // Seek direct cell 2459 RenderObject *r = cell; 2460 while (r != section) { 2461 if (r->isTableCell()) { 2462 directCell = static_cast<RenderTableCell *>(r); 2463 } 2464 r = r->parent(); 2465 }/*wend*/ 2466 2467 // So, and this is really nasty: As we have no indices, we have to do a 2468 // linear comparison. Oh, that sucks so much for long tables, you can't 2469 // imagine. 2470 int n = section->numRows(); 2471 for (int i = 0; i < n; i++) { 2472 row = §ion->grid[i]; 2473 2474 // check for cell 2475 int m = row->row->size(); 2476 for (int j = 0; j < m; j++) { 2477 RenderTableCell *c = row->row->at(j); 2478 if (c == directCell) { 2479 return i; 2480 } 2481 }/*next j*/ 2482 2483 }/*next i*/ 2484 Q_ASSERT(false); 2485 return -1; 2486 } 2487 2488 /** finds the table that is the first direct or indirect descendant of @p block. 2489 * @param leaf object to begin search from. 2490 * @param block object to search to, or 0 to search up to top. 2491 * @return the table or 0 if there were none. 2492 */ 2493 static inline RenderTable *findFirstDescendantTable(RenderObject *leaf, RenderBlock *block) 2494 { 2495 RenderTable *result = 0; 2496 while (leaf && leaf != block) { 2497 if (leaf->isTable()) { 2498 result = static_cast<RenderTable *>(leaf); 2499 } 2500 leaf = leaf->parent(); 2501 }/*wend*/ 2502 return result; 2503 } 2504 2505 /** looks for the table cell the given object @p r is contained within. 2506 * @return the table cell or 0 if not contained in any table. 2507 */ 2508 static inline RenderTableCell *containingTableCell(RenderObject *r) 2509 { 2510 while (r && !r->isTableCell()) { 2511 r = r->parent(); 2512 } 2513 return static_cast<RenderTableCell *>(r); 2514 } 2515 2516 inline void ErgonomicEditableLineIterator::calcAndStoreNewLine( 2517 RenderBlock *newBlock, bool toBegin) 2518 { 2519 // take the first/last editable element in the found cell as the new 2520 // value for the iterator 2521 CaretBoxIterator it; 2522 cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, 2523 newBlock, true, toBegin, it); 2524 #if DEBUG_CARETMODE > 3 2525 // qCDebug(KHTML_LOG) << cbl->information(); 2526 #endif 2527 // if (toBegin) prevBlock(); else nextBlock(); 2528 2529 if (!cbl) { 2530 return; 2531 }/*end if*/ 2532 2533 EditableLineIterator::advance(toBegin); 2534 } 2535 2536 void ErgonomicEditableLineIterator::determineTopologicalElement( 2537 RenderTableCell *oldCell, RenderObject *newObject, bool toBegin) 2538 { 2539 // When we arrive here, a transition between cells has happened. 2540 // Now determine the type of the transition. This can be 2541 // (1) a transition from this cell into a table inside this cell. 2542 // (2) a transition from this cell into another cell of this table 2543 2544 TableRowIterator it; 2545 2546 RenderObject *commonAncestor = commonAncestorTableSectionOrCell(oldCell, newObject); 2547 #if DEBUG_CARETMODE > 1 2548 // qCDebug(KHTML_LOG) << " ancestor " << commonAncestor; 2549 #endif 2550 2551 // The whole document is treated as a table cell. 2552 if (!commonAncestor || commonAncestor->isTableCell()) { // (1) 2553 2554 RenderTableCell *cell = static_cast<RenderTableCell *>(commonAncestor); 2555 RenderTable *table = findFirstDescendantTable(newObject, cell); 2556 2557 #if DEBUG_CARETMODE > 0 2558 // qCDebug(KHTML_LOG) << "table cell: " << cell; 2559 #endif 2560 2561 // if there is no table, we fell out of the previous table, and are now 2562 // in some table-less block. Therefore, done. 2563 if (!table) { 2564 return; 2565 } 2566 2567 it = TableRowIterator(table, toBegin); 2568 2569 } else if (commonAncestor->isTableSection()) { // (2) 2570 2571 RenderTableSection *section = static_cast<RenderTableSection *>(commonAncestor); 2572 RenderTableSection::RowStruct *row; 2573 int idx = findRowInSection(section, oldCell, row, oldCell); 2574 #if DEBUG_CARETMODE > 1 2575 // qCDebug(KHTML_LOG) << "table section: row idx " << idx; 2576 #endif 2577 2578 it = TableRowIterator(section, idx); 2579 2580 // advance rowspan rows 2581 int rowspan = oldCell->rowSpan(); 2582 while (*it && rowspan--) { 2583 if (toBegin) { 2584 --it; 2585 } else { 2586 ++it; 2587 } 2588 }/*wend*/ 2589 2590 } else { 2591 kError(6201) << "Neither common cell nor section! " << commonAncestor->renderName(); 2592 // will crash on uninitialized table row iterator 2593 }/*end if*/ 2594 2595 RenderTableCell *cell = findNearestTableCell(lines->m_part, xCoor, it, toBegin); 2596 #if DEBUG_CARETMODE > 1 2597 // qCDebug(KHTML_LOG) << "findNearestTableCell result: " << cell; 2598 #endif 2599 2600 RenderBlock *newBlock = cell; 2601 if (!cell) { 2602 Q_ASSERT(commonAncestor->isTableSection()); 2603 RenderTableSection *section = static_cast<RenderTableSection *>(commonAncestor); 2604 cell = containingTableCell(section); 2605 #if DEBUG_CARETMODE > 1 2606 // qCDebug(KHTML_LOG) << "containing cell: " << cell; 2607 #endif 2608 2609 RenderTable *nestedTable; 2610 bool editableChild = cell && containsEditableChildElement(lines->m_part, 2611 cell, nestedTable, toBegin, section->table()); 2612 2613 if (cell && !editableChild) { 2614 #if DEBUG_CARETMODE > 1 2615 // qCDebug(KHTML_LOG) << "========= recursive invocation outer ========="; 2616 #endif 2617 determineTopologicalElement(cell, cell->section(), toBegin); 2618 #if DEBUG_CARETMODE > 1 2619 // qCDebug(KHTML_LOG) << "========= end recursive invocation outer ========="; 2620 #endif 2621 return; 2622 2623 } else if (cell && nestedTable) { 2624 #if DEBUG_CARETMODE > 1 2625 // qCDebug(KHTML_LOG) << "========= recursive invocation inner ========="; 2626 #endif 2627 determineTopologicalElement(cell, nestedTable, toBegin); 2628 #if DEBUG_CARETMODE > 1 2629 // qCDebug(KHTML_LOG) << "========= end recursive invocation inner ========="; 2630 #endif 2631 return; 2632 2633 } else { 2634 #if DEBUG_CARETMODE > 1 2635 // qCDebug(KHTML_LOG) << "newBlock is table: " << section->table(); 2636 #endif 2637 RenderObject *r = section->table(); 2638 int state; // not used 2639 ObjectTraversalState trav = OutsideAscending; 2640 r = advanceSuitableObject(r, trav, toBegin, lines->baseObject(), state); 2641 if (!r) { 2642 cbl = 0; 2643 return; 2644 } 2645 // if (toBegin) prevBlock(); else nextBlock(); 2646 newBlock = static_cast<RenderBlock *>(!r || r->isRenderBlock() ? r : r->containingBlock()); 2647 }/*end if*/ 2648 #if 0 2649 } else { 2650 // adapt cell so that prevBlock/nextBlock works as expected 2651 newBlock = cell; 2652 // on forward advancing, we must start from the outside end of the 2653 // previous object 2654 if (!toBegin) { 2655 RenderObject *r = newBlock; 2656 int state; // not used 2657 ObjectTraversalState trav = OutsideAscending; 2658 r = advanceSuitableObject(r, trav, true, lines->advancePolicy(), lines->baseObject(), state); 2659 newBlock = static_cast<RenderBlock *>(!r || r->isRenderBlock() ? r : r->containingBlock()); 2660 }/*end if*/ 2661 #endif 2662 }/*end if*/ 2663 2664 calcAndStoreNewLine(newBlock, toBegin); 2665 } 2666 2667 ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator ++() 2668 { 2669 RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); 2670 2671 EditableLineIterator::operator ++(); 2672 if (*this == lines->end() || *this == lines->preBegin()) { 2673 return *this; 2674 } 2675 2676 RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); 2677 2678 if (!newCell || newCell == oldCell) { 2679 return *this; 2680 } 2681 2682 determineTopologicalElement(oldCell, newCell, false); 2683 2684 return *this; 2685 } 2686 2687 ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator --() 2688 { 2689 RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); 2690 2691 EditableLineIterator::operator --(); 2692 if (*this == lines->end() || *this == lines->preBegin()) { 2693 return *this; 2694 } 2695 2696 RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); 2697 2698 if (!newCell || newCell == oldCell) { 2699 return *this; 2700 } 2701 2702 determineTopologicalElement(oldCell, newCell, true); 2703 2704 return *this; 2705 } 2706 2707 // == Navigational helper functions == 2708 2709 /** seeks the caret box which contains or is the nearest to @p x 2710 * @param it line iterator pointing to line to be searched 2711 * @param cv caret view context 2712 * @param x returns the cv->origX approximation, relatively positioned to the 2713 * containing block. 2714 * @param absx returns absolute x-coordinate of containing block 2715 * @param absy returns absolute y-coordinate of containing block 2716 * @return the most suitable caret box 2717 */ 2718 static CaretBox *nearestCaretBox(LineIterator &it, CaretViewContext *cv, 2719 int &x, int &absx, int &absy) 2720 { 2721 // Find containing block 2722 RenderObject *cb = (*it)->containingBlock(); 2723 #if DEBUG_CARETMODE > 4 2724 // qCDebug(KHTML_LOG) << "nearestCB: cb " << cb << "@" << (cb ? cb->renderName() : ""); 2725 #endif 2726 2727 if (cb) { 2728 cb->absolutePosition(absx, absy); 2729 } else { 2730 absx = absy = 0; 2731 } 2732 2733 // Otherwise find out in which inline box the caret is to be placed. 2734 2735 // this horizontal position is to be approximated 2736 x = cv->origX - absx; 2737 CaretBox *caretBox = 0; // Inline box containing the caret 2738 // NodeImpl *lastnode = 0; // node of previously checked render object. 2739 int xPos; // x-coordinate of current inline box 2740 int oldXPos = -1; // x-coordinate of last inline box 2741 EditableCaretBoxIterator fbit = it; 2742 #if DEBUG_CARETMODE > 0 2743 /* if (it.linearDocument()->advancePolicy() != LeafsOnly) 2744 qCWarning(KHTML_LOG) << "nearestInlineBox is only prepared to handle the LeafsOnly advance policy";*/ 2745 // qCDebug(KHTML_LOG) << "*fbit = " << *fbit; 2746 #endif 2747 // Iterate through all children 2748 for (CaretBox * b; fbit != (*it)->end(); ++fbit) { 2749 b = *fbit; 2750 2751 #if DEBUG_CARETMODE > 0 2752 // RenderObject *r = b->object(); 2753 // if (b->isInlineFlowBox()) qCDebug(KHTML_LOG) << "b is inline flow box"; 2754 // qCDebug(KHTML_LOG) << "approximate r" << r << ": " << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, ((RenderText *)r)->str->l) + "\"" : QString()); 2755 #endif 2756 xPos = b->xPos(); 2757 2758 // the caret is before this box 2759 if (x < xPos) { 2760 // snap to nearest box 2761 if (oldXPos < 0 || x - (oldXPos + caretBox->width()) > xPos - x) { 2762 caretBox = b; // current box is nearer 2763 }/*end if*/ 2764 break; // Otherwise, preceding box is implicitly used 2765 } 2766 2767 caretBox = b; 2768 2769 // the caret is within this box 2770 if (x >= xPos && x < xPos + caretBox->width()) { 2771 break; 2772 } 2773 oldXPos = xPos; 2774 2775 // the caret can only be after the last box which is automatically 2776 // contained in caretBox when we fall out of the loop. 2777 }/*next fbit*/ 2778 2779 return caretBox; 2780 } 2781 2782 /** moves the given iterator to the beginning of the next word. 2783 * 2784 * If the end is reached, the iterator will be positioned there. 2785 * @param it character iterator to be moved 2786 */ 2787 static void moveItToNextWord(EditableCharacterIterator &it) 2788 { 2789 #if DEBUG_CARETMODE > 0 2790 // qCDebug(KHTML_LOG) << "%%%%%%%%%%%%%%%%%%%%% moveItToNextWord"; 2791 #endif 2792 EditableCharacterIterator copy; 2793 while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()) { 2794 #if DEBUG_CARETMODE > 2 2795 // qCDebug(KHTML_LOG) << "reading1 '" << (*it).toLatin1().constData() << "'"; 2796 #endif 2797 copy = it; 2798 ++it; 2799 } 2800 2801 if (it.isEnd()) { 2802 it = copy; 2803 return; 2804 }/*end if*/ 2805 2806 while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())) { 2807 #if DEBUG_CARETMODE > 2 2808 // qCDebug(KHTML_LOG) << "reading2 '" << (*it).toLatin1().constData() << "'"; 2809 #endif 2810 copy = it; 2811 ++it; 2812 } 2813 2814 if (it.isEnd()) { 2815 it = copy; 2816 } 2817 } 2818 2819 /** moves the given iterator to the beginning of the previous word. 2820 * 2821 * If the beginning is reached, the iterator will be positioned there. 2822 * @param it character iterator to be moved 2823 */ 2824 static void moveItToPrevWord(EditableCharacterIterator &it) 2825 { 2826 if (it.isEnd()) { 2827 return; 2828 } 2829 2830 #if DEBUG_CARETMODE > 0 2831 // qCDebug(KHTML_LOG) << "%%%%%%%%%%%%%%%%%%%%% moveItToPrevWord"; 2832 #endif 2833 EditableCharacterIterator copy; 2834 2835 // Jump over all space and punctuation characters first 2836 do { 2837 copy = it; 2838 --it; 2839 #if DEBUG_CARETMODE > 2 2840 if (!it.isEnd()) // qCDebug(KHTML_LOG) << "reading1 '" << (*it).toLatin1().constData() << "'"; 2841 #endif 2842 } while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())); 2843 2844 if (it.isEnd()) { 2845 it = copy; 2846 return; 2847 }/*end if*/ 2848 2849 do { 2850 copy = it; 2851 --it; 2852 #if DEBUG_CARETMODE > 0 2853 if (!it.isEnd()) // qCDebug(KHTML_LOG) << "reading2 '" << (*it).toLatin1().constData() << "' (" << (int)(*it).toLatin1().constData() << ") box " << it.caretBox(); 2854 #endif 2855 } while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()); 2856 2857 it = copy; 2858 #if DEBUG_CARETMODE > 1 2859 if (!it.isEnd()) // qCDebug(KHTML_LOG) << "effective '" << (*it).toLatin1().constData() << "' (" << (int)(*it).toLatin1().constData() << ") box " << it.caretBox(); 2860 #endif 2861 } 2862 2863 /** moves the iterator by one page. 2864 * @param ld linear document 2865 * @param it line iterator, will be updated accordingly 2866 * @param mindist minimum distance in pixel the iterator should be moved 2867 * (if possible) 2868 * @param next @p true, move downward, @p false move upward 2869 */ 2870 static void moveIteratorByPage(LinearDocument &ld, 2871 ErgonomicEditableLineIterator &it, int mindist, bool next) 2872 { 2873 // ### This whole routine plainly sucks. Use an inverse strategie for pgup/pgdn. 2874 2875 if (it == ld.end() || it == ld.preBegin()) { 2876 return; 2877 } 2878 2879 ErgonomicEditableLineIterator copy = it; 2880 #if DEBUG_CARETMODE > 0 2881 // qCDebug(KHTML_LOG) << " mindist: " << mindist; 2882 #endif 2883 2884 CaretBoxLine *cbl = *copy; 2885 int absx = 0, absy = 0; 2886 2887 RenderBlock *lastcb = cbl->containingBlock(); 2888 Q_ASSERT(lastcb->isRenderBlock()); 2889 lastcb->absolutePosition(absx, absy, false); // ### what about fixed? 2890 2891 int lastfby = cbl->begin().data()->yPos(); 2892 int lastheight = 0; 2893 int rescue = 1000; // ### this is a hack to keep stuck carets from hanging the ua 2894 do { 2895 if (next) { 2896 ++copy; 2897 } else { 2898 --copy; 2899 } 2900 if (copy == ld.end() || copy == ld.preBegin()) { 2901 break; 2902 } 2903 2904 cbl = *copy; 2905 RenderBlock *cb = cbl->containingBlock(); 2906 2907 int diff = 0; 2908 // ### actually flowBox->yPos() should suffice, but this is not ported 2909 // over yet from WebCore 2910 int fby = cbl->begin().data()->yPos(); 2911 if (cb != lastcb) { 2912 if (next) { 2913 diff = absy + lastfby + lastheight; 2914 cb->absolutePosition(absx, absy, false); // ### what about fixed? 2915 diff = absy - diff + fby; 2916 lastfby = 0; 2917 } else { 2918 diff = absy; 2919 cb->absolutePosition(absx, absy, false); // ### what about fixed? 2920 diff -= absy + fby + lastheight; 2921 lastfby = fby - lastheight; 2922 }/*end if*/ 2923 #if DEBUG_CARETMODE > 2 2924 // qCDebug(KHTML_LOG) << "absdiff " << diff; 2925 #endif 2926 } else { 2927 diff = qAbs(fby - lastfby); 2928 }/*end if*/ 2929 #if DEBUG_CARETMODE > 2 2930 // qCDebug(KHTML_LOG) << "cbl->begin().data()->yPos(): " << fby << " diff " << diff; 2931 #endif 2932 2933 mindist -= diff; 2934 2935 lastheight = qAbs(fby - lastfby); 2936 lastfby = fby; 2937 lastcb = cb; 2938 it = copy; 2939 #if DEBUG_CARETMODE > 0 2940 // qCDebug(KHTML_LOG) << " mindist: " << mindist; 2941 #endif 2942 // trick: actually the distance is always one line short, but we cannot 2943 // calculate the height of the first line (### WebCore will make it better) 2944 // Therefore, we simply approximate that excess line by using the last 2945 // caluculated line height. 2946 } while (mindist - lastheight > 0 && --rescue); 2947 } 2948 2949 }/*end namespace*/