Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /* This file is part of the KDE project |
---|---|
2 | * |
3 | * Copyright (C) 2003-2004 Leo Savernik <l.savernik@aon.at> |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public License |
16 | * along with this library; see the file COPYING.LIB. If not, write to |
17 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
18 | * Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | |
22 | #include "khtml_caret_p.h" |
23 | |
24 | #include "html/html_documentimpl.h" |
25 | |
26 | namespace khtml { |
27 | |
28 | /** Flags representing the type of advance that has been made. |
29 | * @param LeftObject a render object was left and an ascent to its parent has |
30 | * taken place |
31 | * @param AdvancedToSibling an actual advance to a sibling has taken place |
32 | * @param EnteredObject a render object was entered by descending into it from |
33 | * its parent object. |
34 | */ |
35 | enum ObjectAdvanceState { |
36 | LeftObject = 0x01, AdvancedToSibling = 0x02, EnteredObject = 0x04 |
37 | }; |
38 | |
39 | /** All possible states that may occur during render object traversal. |
40 | * @param OutsideDescending outside of the current object, ready to descend |
41 | * into children |
42 | * @param InsideDescending inside the current object, descending into |
43 | * children |
44 | * @param InsideAscending inside the current object, ascending to parents |
45 | * @param OutsideAscending outsie the current object, ascending to parents |
46 | */ |
47 | enum ObjectTraversalState { |
48 | OutsideDescending, InsideDescending, InsideAscending, OutsideAscending |
49 | }; |
50 | |
51 | /** Traverses the render object tree in a fine granularity. |
52 | * @param obj render object |
53 | * @param trav object traversal state |
54 | * @param toBegin traverse towards beginning |
55 | * @param base base render object which this method must not advance beyond |
56 | * (0 means document) |
57 | * @param state object advance state (enum ObjectAdvanceState) |
58 | * @return the render object according to the state. May be the same as \c obj |
59 | */ |
60 | static RenderObject* traverseRenderObjects(RenderObject *obj, |
61 | ObjectTraversalState &trav, bool toBegin, RenderObject *base, |
62 | int &state) |
63 | { |
64 | RenderObject *r; |
65 | switch (trav) { |
66 | case OutsideDescending: |
67 | trav = InsideDescending; |
68 | break; |
69 | case InsideDescending: |
70 | r = toBegin ? obj->lastChild() : obj->firstChild(); |
71 | if (r) { |
72 | trav = OutsideDescending; |
73 | obj = r; |
74 | state |= EnteredObject; |
75 | } else { |
76 | trav = InsideAscending; |
77 | } |
78 | break; |
79 | case InsideAscending: |
80 | trav = OutsideAscending; |
81 | break; |
82 | case OutsideAscending: |
83 | r = toBegin ? obj->previousSibling() : obj->nextSibling(); |
84 | if (r) { |
85 | trav = OutsideDescending; |
86 | state |= AdvancedToSibling; |
87 | } else { |
88 | r = obj->parent(); |
89 | if (r == base) r = 0; |
90 | trav = InsideAscending; |
91 | state |= LeftObject; |
92 | } |
93 | obj = r; |
94 | break; |
95 | }/*end switch*/ |
96 | |
97 | return obj; |
98 | } |
99 | |
100 | /** Like RenderObject::objectBelow, but confined to stay within \c base. |
101 | * @param obj render object to begin with |
102 | * @param trav object traversal state, will be reset within this function |
103 | * @param base base render object (0: no base) |
104 | */ |
105 | static inline RenderObject *renderObjectBelow(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) |
106 | { |
107 | trav = InsideDescending; |
108 | int state; // we don't need the state, so we don't initialize it |
109 | RenderObject *r = obj; |
110 | while (r && trav != OutsideDescending) { |
111 | r = traverseRenderObjects(r, trav, false, base, state); |
112 | #if DEBUG_CARETMODE > 3 |
113 | kDebug(6200) << "renderObjectBelow: r "<< r << " trav "<< trav; |
114 | #endif |
115 | } |
116 | trav = InsideDescending; |
117 | return r; |
118 | } |
119 | |
120 | /** Like RenderObject::objectAbove, but confined to stay within \c base. |
121 | * @param obj render object to begin with |
122 | * @param trav object traversal state, will be reset within this function |
123 | * @param base base render object (0: no base) |
124 | */ |
125 | static inline RenderObject *renderObjectAbove(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) |
126 | { |
127 | trav = OutsideAscending; |
128 | int state; // we don't need the state, so we don't initialize it |
129 | RenderObject *r = obj; |
130 | while (r && trav != InsideAscending) { |
131 | r = traverseRenderObjects(r, trav, true, base, state); |
132 | #if DEBUG_CARETMODE > 3 |
133 | kDebug(6200) << "renderObjectAbove: r "<< r << " trav "<< trav; |
134 | #endif |
135 | } |
136 | trav = InsideAscending; |
137 | return r; |
138 | } |
139 | |
140 | /** Checks whether the given inline box matches the IndicatedFlows policy |
141 | * @param box inline box to test |
142 | * @return true on match |
143 | */ |
144 | static inline bool isIndicatedInlineBox(InlineBox *box) |
145 | { |
146 | // text boxes are never indicated. |
147 | if (box->isInlineTextBox()) return false; |
148 | RenderStyle *s = box->object()->style(); |
149 | return s->borderLeftWidth() || s->borderRightWidth() |
150 | || s->borderTopWidth() || s->borderBottomWidth() |
151 | || s->paddingLeft().value() || s->paddingRight().value() |
152 | || s->paddingTop().value() || s->paddingBottom().value() |
153 | // ### Can inline elements have top/bottom margins? Couldn't find |
154 | // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. |
155 | || s->marginLeft().value() || s->marginRight().value(); |
156 | } |
157 | |
158 | /** Checks whether the given render object matches the IndicatedFlows policy |
159 | * @param r render object to test |
160 | * @return true on match |
161 | */ |
162 | static inline bool isIndicatedFlow(RenderObject *r) |
163 | { |
164 | RenderStyle *s = r->style(); |
165 | return s->borderLeftStyle() != BNONE || s->borderRightStyle() != BNONE |
166 | || s->borderTopStyle() != BNONE || s->borderBottomStyle() != BNONE |
167 | // || s->paddingLeft().value() || s->paddingRight().value() |
168 | // || s->paddingTop().value() || s->paddingBottom().value() |
169 | // || s->marginLeft().value() || s->marginRight().value() |
170 | || s->hasClip() || s->hidesOverflow() |
171 | || s->backgroundColor().isValid() || s->backgroundImage(); |
172 | } |
173 | |
174 | /** Advances to the next render object, taking into account the current |
175 | * traversal state. |
176 | * |
177 | * @param r render object |
178 | * @param trav object traversal state |
179 | * @param toBegin @p true, advance towards beginning, @p false, advance toward end |
180 | * @param base base render object which this method must not advance beyond |
181 | * (0 means document) |
182 | * @param state object advance state (enum ObjectAdvanceState) (unchanged |
183 | * on LeafsOnly) |
184 | * @return a pointer to the render object which we advanced to, |
185 | * or 0 if the last possible object has been reached. |
186 | */ |
187 | static RenderObject *advanceObject(RenderObject *r, |
188 | ObjectTraversalState &trav, bool toBegin, |
189 | RenderObject *base, int &state) |
190 | { |
191 | |
192 | ObjectTraversalState origtrav = trav; |
193 | RenderObject *a = traverseRenderObjects(r, trav, toBegin, base, state); |
194 | |
195 | bool ignoreOutsideDesc = toBegin && origtrav == OutsideAscending; |
196 | |
197 | // render object and traversal state at which look ahead has been started |
198 | RenderObject *la = 0; |
199 | ObjectTraversalState latrav = trav; |
200 | ObjectTraversalState lasttrav = origtrav; |
201 | |
202 | while (a) { |
203 | #if DEBUG_CARETMODE > 5 |
204 | kDebug(6200) << "a "<< a << " trav "<< trav; |
205 | #endif |
206 | if (a->element()) { |
207 | #if DEBUG_CARETMODE > 4 |
208 | kDebug(6200) << "a "<< a << " trav "<< trav << " origtrav "<< origtrav << " ignoreOD "<< ignoreOutsideDesc; |
209 | #endif |
210 | if (toBegin) { |
211 | |
212 | switch (origtrav) { |
213 | case OutsideDescending: |
214 | if (trav == InsideAscending) return a; |
215 | if (trav == OutsideDescending) return a; |
216 | break; |
217 | case InsideDescending: |
218 | if (trav == OutsideDescending) return a; |
219 | // fall through |
220 | case InsideAscending: |
221 | if (trav == OutsideAscending) return a; |
222 | break; |
223 | case OutsideAscending: |
224 | if (trav == OutsideAscending) return a; |
225 | if (trav == InsideAscending && lasttrav == InsideDescending) return a; |
226 | if (trav == OutsideDescending && !ignoreOutsideDesc) return a; |
227 | // ignore this outside descending position, as it effectively |
228 | // demarkates the same position, but remember it in case we fall off |
229 | // the document. |
230 | la = a; latrav = trav; |
231 | ignoreOutsideDesc = false; |
232 | break; |
233 | }/*end switch*/ |
234 | |
235 | } else { |
236 | |
237 | switch (origtrav) { |
238 | case OutsideDescending: |
239 | if (trav == InsideAscending) return a; |
240 | if (trav == OutsideDescending) return a; |
241 | break; |
242 | case InsideDescending: |
243 | // if (trav == OutsideDescending) return a; |
244 | // fall through |
245 | case InsideAscending: |
246 | // if (trav == OutsideAscending) return a; |
247 | // break; |
248 | case OutsideAscending: |
249 | // ### what if origtrav == OA, and immediately afterwards trav |
250 | // becomes OD? In this case the effective position hasn't changed -> |
251 | // the caret gets stuck. Otherwise, it apparently cannot happen in |
252 | // real usage patterns. |
253 | if (trav == OutsideDescending) return a; |
254 | if (trav == OutsideAscending) { |
255 | if (la) return la; |
256 | // starting lookahead here. Remember old object in case we fall off |
257 | // the document. |
258 | la = a; latrav = trav; |
259 | } |
260 | break; |
261 | }/*end switch*/ |
262 | |
263 | }/*end if*/ |
264 | }/*end if*/ |
265 | |
266 | lasttrav = trav; |
267 | a = traverseRenderObjects(a, trav, toBegin, base, state); |
268 | }/*wend*/ |
269 | |
270 | if (la) trav = latrav, a = la; |
271 | return a; |
272 | |
273 | } |
274 | |
275 | /** Check whether the current render object is unsuitable in caret mode handling. |
276 | * |
277 | * Some render objects cannot be handled correctly in caret mode. These objects |
278 | * are therefore considered to be unsuitable. The null object is suitable, as |
279 | * it denotes reaching the end. |
280 | * @param r current render object |
281 | * @param trav current traversal state |
282 | */ |
283 | static inline bool isUnsuitable(RenderObject *r, ObjectTraversalState trav) |
284 | { |
285 | if (!r) return false; |
286 | return r->isTableCol() || r->isTableSection() || r->isTableRow() |
287 | || (r->isText() && !static_cast<RenderText *>(r)->firstTextBox()); |
288 | ; |
289 | Q_UNUSED(trav); |
290 | } |
291 | |
292 | /** Advances to the next render object, taking into account the current |
293 | * traversal state, but skipping render objects which are not suitable for |
294 | * having placed the caret into them. |
295 | * @param r render object |
296 | * @param trav object traversal state (unchanged on LeafsOnly) |
297 | * @param toBegin @p true, advance towards beginning, @p false, advance toward end |
298 | * @param base base render object which this method must not advance beyond |
299 | * (0 means document) |
300 | * @param state object advance state (enum ObjectAdvanceState) (unchanged |
301 | * on LeafsOnly) |
302 | * @return a pointer to the advanced render object or 0 if the last possible |
303 | * object has been reached. |
304 | */ |
305 | static inline RenderObject *advanceSuitableObject(RenderObject *r, |
306 | ObjectTraversalState &trav, bool toBegin, |
307 | RenderObject *base, int &state) |
308 | { |
309 | do { |
310 | r = advanceObject(r, trav, toBegin, base, state); |
311 | #if DEBUG_CARETMODE > 2 |
312 | kDebug(6200) << "after advanceSWP: r "<< r << " trav "<< trav << " toBegin "<< toBegin; |
313 | #endif |
314 | } while (isUnsuitable(r, trav)); |
315 | return r; |
316 | } |
317 | |
318 | /** |
319 | * Returns the next leaf node. |
320 | * |
321 | * Using this function delivers leaf nodes as if the whole DOM tree |
322 | * were a linear chain of its leaf nodes. |
323 | * @param r dom node |
324 | * @param baseElem base element not to search beyond |
325 | * @return next leaf node or 0 if there are no more. |
326 | */ |
327 | static NodeImpl *nextLeafNode(NodeImpl *r, NodeImpl *baseElem) |
328 | { |
329 | NodeImpl *n = r->firstChild(); |
330 | if (n) { |
331 | while (n) { r = n; n = n->firstChild(); } |
332 | return const_cast<NodeImpl *>(r); |
333 | }/*end if*/ |
334 | n = r->nextSibling(); |
335 | if (n) { |
336 | r = n; |
337 | while (n) { r = n; n = n->firstChild(); } |
338 | return const_cast<NodeImpl *>(r); |
339 | }/*end if*/ |
340 | |
341 | n = r->parentNode(); |
342 | if (n == baseElem) n = 0; |
343 | while (n) { |
344 | r = n; |
345 | n = r->nextSibling(); |
346 | if (n) { |
347 | r = n; |
348 | n = r->firstChild(); |
349 | while (n) { r = n; n = n->firstChild(); } |
350 | return const_cast<NodeImpl *>(r); |
351 | }/*end if*/ |
352 | n = r->parentNode(); |
353 | if (n == baseElem) n = 0; |
354 | }/*wend*/ |
355 | return 0; |
356 | } |
357 | |
358 | #if 0 // currently not used |
359 | /** (Not part of the official DOM) |
360 | * Returns the previous leaf node. |
361 | * |
362 | * Using this function delivers leaf nodes as if the whole DOM tree |
363 | * were a linear chain of its leaf nodes. |
364 | * @param r dom node |
365 | * @param baseElem base element not to search beyond |
366 | * @return previous leaf node or 0 if there are no more. |
367 | */ |
368 | static NodeImpl *prevLeafNode(NodeImpl *r, NodeImpl *baseElem) |
369 | { |
370 | NodeImpl *n = r->firstChild(); |
371 | if (n) { |
372 | while (n) { r = n; n = n->firstChild(); } |
373 | return const_cast<NodeImpl *>(r); |
374 | }/*end if*/ |
375 | n = r->previousSibling(); |
376 | if (n) { |
377 | r = n; |
378 | while (n) { r = n; n = n->firstChild(); } |
379 | return const_cast<NodeImpl *>(r); |
380 | }/*end if*/ |
381 | |
382 | n = r->parentNode(); |
383 | if (n == baseElem) n = 0; |
384 | while (n) { |
385 | r = n; |
386 | n = r->previousSibling(); |
387 | if (n) { |
388 | r = n; |
389 | n = r->lastChild(); |
390 | while (n) { r = n; n = n->lastChild(); } |
391 | return const_cast<NodeImpl *>(r); |
392 | }/*end if*/ |
393 | n = r->parentNode(); |
394 | if (n == baseElem) n = 0; |
395 | }/*wend*/ |
396 | return 0; |
397 | } |
398 | #endif |
399 | |
400 | /** Maps a DOM Range position to the corresponding caret position. |
401 | * |
402 | * The offset boundary is not checked for validity. |
403 | * @param node DOM node |
404 | * @param offset zero-based offset within node |
405 | * @param r returns render object (may be 0 if DOM node has no render object) |
406 | * @param r_ofs returns the appropriate offset for the found render object r |
407 | * @param outside returns true when offset is applied to the outside of |
408 | * \c r, or false for the inside. |
409 | * @param outsideEnd return true when the caret position is at the outside end. |
410 | */ |
411 | void /*KDE_NO_EXPORT*/ mapDOMPosToRenderPos(NodeImpl *node, long offset, |
412 | RenderObject *&r, long &r_ofs, bool &outside, bool &outsideEnd) |
413 | { |
414 | if (node->nodeType() == Node::TEXT_NODE) { |
415 | outside = false; |
416 | outsideEnd = false; |
417 | r = node->renderer(); |
418 | r_ofs = offset; |
419 | } else if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::DOCUMENT_NODE) { |
420 | |
421 | // Though offset points between two children, attach it to the visually |
422 | // most suitable one (and only there, because the mapping must stay bijective) |
423 | if (node->firstChild()) { |
424 | outside = true; |
425 | NodeImpl *child = offset <= 0 ? node->firstChild() |
426 | // childNode is expensive |
427 | : node->childNode((unsigned long)offset); |
428 | // index was child count or out of bounds |
429 | bool atEnd = !child; |
430 | #if DEBUG_CARETMODE > 5 |
431 | kDebug(6200) << "mapDTR: child "<< child << "@"<< (child ? child->nodeName().string() : QString()) << " atEnd "<< atEnd; |
432 | #endif |
433 | if (atEnd) child = node->lastChild(); |
434 | |
435 | r = child->renderer(); |
436 | r_ofs = 0; |
437 | outsideEnd = atEnd; |
438 | |
439 | // Outside text nodes most likely stem from a continuation. Seek |
440 | // the enclosing continued render object and use this one instead. |
441 | if (r && child->nodeType() == Node::TEXT_NODE) { |
442 | r = r->parent(); |
443 | RenderObject *o = node->renderer(); |
444 | while (o->continuation() && o->continuation() != r) |
445 | o = o->continuation(); |
446 | if (!r || o->continuation() != r) { |
447 | r = child->renderer(); |
448 | } |
449 | }/*end if*/ |
450 | |
451 | // BRs cause troubles. Returns the previous render object instead, |
452 | // giving it the attributes outside, outside end. |
453 | if (r && r->isBR()) { |
454 | r = r->objectAbove(); |
455 | outsideEnd = true; |
456 | }/*end if*/ |
457 | |
458 | } else { |
459 | // Element has no children, treat offset to be inside the node. |
460 | outside = false; |
461 | outsideEnd = false; |
462 | r = node->renderer(); |
463 | r_ofs = 0; // only offset 0 possible |
464 | } |
465 | |
466 | } else { |
467 | r = 0; |
468 | kWarning() << "Mapping from nodes of type "<< node->nodeType() |
469 | << " not supported!"<< endl; |
470 | } |
471 | } |
472 | |
473 | /** Maps a caret position to the corresponding DOM Range position. |
474 | * |
475 | * @param r render object |
476 | * @param r_ofs offset within render object |
477 | * @param outside true when offset is interpreted to be on the outside of |
478 | * \c r, or false if on the inside. |
479 | * @param outsideEnd true when the caret position is at the outside end. |
480 | * @param node returns DOM node |
481 | * @param offset returns zero-based offset within node |
482 | */ |
483 | void /*KDE_NO_EXPORT*/ mapRenderPosToDOMPos(RenderObject *r, long r_ofs, |
484 | bool outside, bool outsideEnd, NodeImpl *&node, long &offset) |
485 | { |
486 | node = r->element(); |
487 | Q_ASSERT(node); |
488 | #if DEBUG_CARETMODE > 5 |
489 | kDebug(6200) << "mapRTD: r "<< r << ".node ") + QString::number((unsigned)r->element(),16) + " outside "<< outside << " outsideEnd "<< outsideEnd; |
490 | #endif |
491 | if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::TEXT_NODE) { |
492 | |
493 | if (outside) { |
494 | NodeImpl *parent = node->parent(); |
495 | |
496 | // If this is part of a continuation, use the actual node as the parent, |
497 | // and the first render child as the node. |
498 | if (r != node->renderer()) { |
499 | RenderObject *o = node->renderer(); |
500 | while (o->continuation() && o->continuation() != r) |
501 | o = o->continuation(); |
502 | if (o->continuation() == r) { |
503 | parent = node; |
504 | // ### What if the first render child does not map to a child of |
505 | // the continued node? |
506 | node = r->firstChild() ? r->firstChild()->element() : node; |
507 | } |
508 | }/*end if*/ |
509 | |
510 | if (!parent) goto inside; |
511 | |
512 | offset = (long)node->nodeIndex() + outsideEnd; |
513 | node = parent; |
514 | #if DEBUG_CARETMODE > 5 |
515 | kDebug(6200) << node << "@"<< (node ? node->nodeName().string() : QString()) << " offset "<< offset; |
516 | #endif |
517 | } else { // !outside |
518 | inside: |
519 | offset = r_ofs; |
520 | } |
521 | |
522 | } else { |
523 | offset = 0; |
524 | kWarning() << "Mapping to nodes of type "<< node->nodeType() |
525 | << " not supported!"<< endl; |
526 | } |
527 | } |
528 | |
529 | /** Make sure the given node is a leaf node. */ |
530 | static inline void ensureLeafNode(NodeImpl *&node, NodeImpl *base) |
531 | { |
532 | if (node && node->hasChildNodes()) node = nextLeafNode(node, base); |
533 | } |
534 | |
535 | /** Converts a caret position to its respective object traversal state. |
536 | * @param outside whether the caret is outside the object |
537 | * @param atEnd whether the caret position is at the end |
538 | * @param toBegin \c true when advancing towards the beginning |
539 | * @param trav returns the corresponding traversal state |
540 | */ |
541 | static inline void mapRenderPosToTraversalState(bool outside, bool atEnd, |
542 | bool toBegin, ObjectTraversalState &trav) |
543 | { |
544 | if (!outside) atEnd = !toBegin; |
545 | if (!atEnd ^ toBegin) |
546 | trav = outside ? OutsideDescending : InsideDescending; |
547 | else |
548 | trav = outside ? OutsideAscending : InsideAscending; |
549 | } |
550 | |
551 | /** Converts a traversal state to its respective caret position |
552 | * @param trav object traversal state |
553 | * @param toBegin \c true when advancing towards the beginning |
554 | * @param outside whether the caret is outside the object |
555 | * @param atEnd whether the caret position is at the end |
556 | */ |
557 | static inline void mapTraversalStateToRenderPos(ObjectTraversalState trav, |
558 | bool toBegin, bool &outside, bool &atEnd) |
559 | { |
560 | outside = false; |
561 | switch (trav) { |
562 | case OutsideDescending: outside = true; // fall through |
563 | case InsideDescending: atEnd = toBegin; break; |
564 | case OutsideAscending: outside = true; // fall through |
565 | case InsideAscending: atEnd = !toBegin; break; |
566 | } |
567 | } |
568 | |
569 | /** Finds the next node that has a renderer. |
570 | * |
571 | * Note that if the initial @p node has a renderer, this will be returned, |
572 | * regardless of the caret advance policy. |
573 | * Otherwise, for the next nodes, only leaf nodes are considered. |
574 | * @param node node to start with, will be updated accordingly |
575 | * @param offset offset of caret within \c node |
576 | * @param base base render object which this method must not advance beyond |
577 | * (0 means document) |
578 | * @param r_ofs return the caret offset within the returned renderer |
579 | * @param outside returns whether offset is to be interpreted to the outside |
580 | * (true) or the inside (false) of the render object. |
581 | * @param outsideEnd returns whether the end of the outside position is meant |
582 | * @return renderer or 0 if no following node has a renderer. |
583 | */ |
584 | static RenderObject* findRenderer(NodeImpl *&node, long offset, |
585 | RenderObject *base, long &r_ofs, |
586 | bool &outside, bool &outsideEnd) |
587 | { |
588 | if (!node) return 0; |
589 | RenderObject *r; |
590 | mapDOMPosToRenderPos(node, offset, r, r_ofs, outside, outsideEnd); |
591 | #if DEBUG_CARETMODE > 2 |
592 | kDebug(6200) << "findRenderer: node "<< node << " "<< (node ? node->nodeName().string() : QString()) << " offset "<< offset << " r "<< r << "["<< (r ? r->renderName() : QString()) << "] r_ofs "<< r_ofs << " outside "<< outside << " outsideEnd "<< outsideEnd; |
593 | #endif |
594 | if (r) return r; |
595 | NodeImpl *baseElem = base ? base->element() : 0; |
596 | while (!r) { |
597 | node = nextLeafNode(node, baseElem); |
598 | if (!node) break; |
599 | r = node->renderer(); |
600 | if (r) r_ofs = offset; |
601 | } |
602 | #if DEBUG_CARETMODE > 3 |
603 | kDebug(6200) << "1r "<< r; |
604 | #endif |
605 | ObjectTraversalState trav; |
606 | int state; // not used |
607 | mapRenderPosToTraversalState(outside, outsideEnd, false, trav); |
608 | if (r && isUnsuitable(r, trav)) { |
609 | r = advanceSuitableObject(r, trav, false, base, state); |
610 | mapTraversalStateToRenderPos(trav, false, outside, outsideEnd); |
611 | if (r) r_ofs = r->minOffset(); |
612 | } |
613 | #if DEBUG_CARETMODE > 3 |
614 | kDebug(6200) << "2r "<< r; |
615 | #endif |
616 | return r; |
617 | } |
618 | |
619 | /** returns a suitable base element |
620 | * @param caretNode current node containing caret. |
621 | */ |
622 | static ElementImpl *determineBaseElement(NodeImpl *caretNode) |
623 | { |
624 | // ### for now, only body is delivered for html documents, |
625 | // and 0 for xml documents. |
626 | |
627 | DocumentImpl *doc = caretNode->getDocument(); |
628 | if (!doc) return 0; // should not happen, but who knows. |
629 | |
630 | if (doc->isHTMLDocument()) |
631 | return static_cast<HTMLDocumentImpl *>(doc)->body(); |
632 | |
633 | return 0; |
634 | } |
635 | |
636 | // == class CaretBox implementation |
637 | |
638 | #if DEBUG_CARETMODE > 0 |
639 | void CaretBox::dump(QTextStream &ts, const QString &ind) const |
640 | { |
641 | ts << ind << "b@"<< _box; |
642 | |
643 | if (_box) { |
644 | ts << "<"<< _box->object() << ":"<< _box->object()->renderName() << ">"; |
645 | }/*end if*/ |
646 | |
647 | ts << " "<< _x << "+"<< _y << "+"<< _w << "*"<< _h; |
648 | |
649 | ts << " cb@"<< cb; |
650 | if (cb) ts << ":"<< cb->renderName(); |
651 | |
652 | ts << " "<< (_outside ? (outside_end ? "oe": "o-") : "i-"); |
653 | // ts << endl; |
654 | } |
655 | #endif |
656 | |
657 | // == class CaretBoxLine implementation |
658 | |
659 | #if DEBUG_CARETMODE > 0 |
660 | # define DEBUG_ACIB 1 |
661 | #else |
662 | # define DEBUG_ACIB DEBUG_CARETMODE |
663 | #endif |
664 | void CaretBoxLine::addConvertedInlineBox(InlineBox *box, SeekBoxParams &sbp) /*KDE_NO_EXPORT*/ |
665 | { |
666 | // Generate only one outside caret box between two elements. If |
667 | // coalesceOutsideBoxes is true, generating left outside boxes is inhibited. |
668 | bool coalesceOutsideBoxes = false; |
669 | CaretBoxIterator lastCoalescedBox; |
670 | for (; box; box = box->nextOnLine()) { |
671 | #if DEBUG_ACIB |
672 | kDebug(6200) << "box "<< box; |
673 | kDebug(6200) << "box->object "<< box->object(); |
674 | kDebug(6200) << "x "<< box->m_x << " y "<< box->m_y << " w "<< box->m_width << " h "<< box->m_height << " baseline "<< box->m_baseline << " ifb "<< box->isInlineFlowBox() << " itb "<< box->isInlineTextBox() << " rlb "<< box->isRootInlineBox(); |
675 | #endif |
676 | // ### Why the hell can object() ever be 0?! |
677 | if (!box->object()) continue; |
678 | |
679 | RenderStyle *s = box->object()->style(box->m_firstLine); |
680 | // parent style for outside caret boxes |
681 | RenderStyle *ps = box->parent() && box->parent()->object() |
682 | ? box->parent()->object()->style(box->parent()->m_firstLine) |
683 | : s; |
684 | |
685 | if (box->isInlineFlowBox()) { |
686 | #if DEBUG_ACIB |
687 | kDebug(6200) << "isinlineflowbox "<< box; |
688 | #endif |
689 | InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(box); |
690 | bool rtl = ps->direction() == RTL; |
691 | const QFontMetrics &pfm = ps->fontMetrics(); |
692 | |
693 | if (flowBox->includeLeftEdge()) { |
694 | // If this box is to be coalesced with the outside end box of its |
695 | // predecessor, then check if it is the searched box. If it is, we |
696 | // substitute the outside end box. |
697 | if (coalesceOutsideBoxes) { |
698 | if (sbp.equalsBox(flowBox, true, false)) { |
699 | sbp.it = lastCoalescedBox; |
700 | Q_ASSERT(!sbp.found); |
701 | sbp.found = true; |
702 | } |
703 | } else { |
704 | addCreatedFlowBoxEdge(flowBox, pfm, true, rtl); |
705 | sbp.check(preEnd()); |
706 | } |
707 | }/*end if*/ |
708 | |
709 | if (flowBox->firstChild()) { |
710 | #if DEBUG_ACIB |
711 | kDebug(6200) << "this "<< this << " flowBox "<< flowBox << " firstChild "<< flowBox->firstChild(); |
712 | kDebug(6200) << "== recursive invocation"; |
713 | #endif |
714 | addConvertedInlineBox(flowBox->firstChild(), sbp); |
715 | #if DEBUG_ACIB |
716 | kDebug(6200) << "== recursive invocation end"; |
717 | #endif |
718 | } |
719 | else { |
720 | addCreatedFlowBoxInside(flowBox, s->fontMetrics()); |
721 | sbp.check(preEnd()); |
722 | } |
723 | |
724 | if (flowBox->includeRightEdge()) { |
725 | addCreatedFlowBoxEdge(flowBox, pfm, false, rtl); |
726 | lastCoalescedBox = preEnd(); |
727 | sbp.check(lastCoalescedBox); |
728 | coalesceOutsideBoxes = true; |
729 | } |
730 | |
731 | } else if (box->isInlineTextBox()) { |
732 | #if DEBUG_ACIB |
733 | kDebug(6200) << "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()); |
734 | #endif |
735 | caret_boxes.append(new CaretBox(box, false, false)); |
736 | sbp.check(preEnd()); |
737 | // coalescing has been interrupted |
738 | coalesceOutsideBoxes = false; |
739 | |
740 | } else { |
741 | #if DEBUG_ACIB |
742 | kDebug(6200) << "some replaced or what "<< box; |
743 | #endif |
744 | // must be an inline-block, inline-table, or any RenderReplaced |
745 | bool rtl = ps->direction() == RTL; |
746 | const QFontMetrics &pfm = ps->fontMetrics(); |
747 | |
748 | if (coalesceOutsideBoxes) { |
749 | if (sbp.equalsBox(box, true, false)) { |
750 | sbp.it = lastCoalescedBox; |
751 | Q_ASSERT(!sbp.found); |
752 | sbp.found = true; |
753 | } |
754 | } else { |
755 | addCreatedInlineBoxEdge(box, pfm, true, rtl); |
756 | sbp.check(preEnd()); |
757 | } |
758 | |
759 | caret_boxes.append(new CaretBox(box, false, false)); |
760 | sbp.check(preEnd()); |
761 | |
762 | addCreatedInlineBoxEdge(box, pfm, false, rtl); |
763 | lastCoalescedBox = preEnd(); |
764 | sbp.check(lastCoalescedBox); |
765 | coalesceOutsideBoxes = true; |
766 | }/*end if*/ |
767 | }/*next box*/ |
768 | } |
769 | #undef DEBUG_ACIB |
770 | |
771 | void CaretBoxLine::addCreatedFlowBoxInside(InlineFlowBox *flowBox, const QFontMetrics &fm) /*KDE_NO_EXPORT*/ |
772 | { |
773 | |
774 | CaretBox *caretBox = new CaretBox(flowBox, false, false); |
775 | caret_boxes.append(caretBox); |
776 | |
777 | // afaik an inner flow box can only have the width 0, therefore we don't |
778 | // have to care for rtl or alignment |
779 | // ### can empty inline elements have a width? css 2 spec isn't verbose about it |
780 | |
781 | caretBox->_y += flowBox->baseline() - fm.ascent(); |
782 | caretBox->_h = fm.height(); |
783 | } |
784 | |
785 | void CaretBoxLine::addCreatedFlowBoxEdge(InlineFlowBox *flowBox, const QFontMetrics &fm, bool left, bool rtl) /*KDE_NO_EXPORT*/ |
786 | { |
787 | CaretBox *caretBox = new CaretBox(flowBox, true, !left); |
788 | caret_boxes.append(caretBox); |
789 | |
790 | if (left ^ rtl) caretBox->_x -= flowBox->paddingLeft() + flowBox->borderLeft() + 1; |
791 | else caretBox->_x += caretBox->_w + flowBox->paddingRight() + flowBox->borderRight(); |
792 | |
793 | caretBox->_y += flowBox->baseline() - fm.ascent(); |
794 | caretBox->_h = fm.height(); |
795 | caretBox->_w = 1; |
796 | } |
797 | |
798 | void CaretBoxLine::addCreatedInlineBoxEdge(InlineBox *box, const QFontMetrics &fm, bool left, bool rtl) /*KDE_NO_EXPORT*/ |
799 | { |
800 | CaretBox *caretBox = new CaretBox(box, true, !left); |
801 | caret_boxes.append(caretBox); |
802 | |
803 | if (left ^ rtl) caretBox->_x--; |
804 | else caretBox->_x += caretBox->_w; |
805 | |
806 | caretBox->_y += box->baseline() - fm.ascent(); |
807 | caretBox->_h = fm.height(); |
808 | caretBox->_w = 1; |
809 | } |
810 | |
811 | CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, |
812 | InlineFlowBox *basicFlowBox, InlineBox *seekBox, bool seekOutside, |
813 | bool seekOutsideEnd, CaretBoxIterator &iter, RenderObject *seekObject) |
814 | // KDE_NO_EXPORT |
815 | { |
816 | // Iterate all inline boxes within this inline flow box. |
817 | // Caret boxes will be created for each |
818 | // - outside begin of an inline flow box (except for the basic inline flow box) |
819 | // - outside end of an inline flow box (except for the basic inline flow box) |
820 | // - inside of an empty inline flow box |
821 | // - outside begin of an inline box resembling a replaced element |
822 | // - outside end of an inline box resembling a replaced element |
823 | // - inline text box |
824 | // - inline replaced box |
825 | |
826 | CaretBoxLine *result = new CaretBoxLine(basicFlowBox); |
827 | deleter->append(result); |
828 | |
829 | SeekBoxParams sbp(seekBox, seekOutside, seekOutsideEnd, seekObject, iter); |
830 | |
831 | // iterate recursively, I'm too lazy to do it iteratively |
832 | result->addConvertedInlineBox(basicFlowBox, sbp); |
833 | |
834 | if (!sbp.found) sbp.it = result->end(); |
835 | |
836 | return result; |
837 | } |
838 | |
839 | CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, |
840 | RenderBox *cb, bool outside, bool outsideEnd, CaretBoxIterator &iter) /*KDE_NO_EXPORT*/ |
841 | { |
842 | int _x = cb->xPos(); |
843 | int _y = cb->yPos(); |
844 | int height; |
845 | int width = 1; // no override is indicated in boxes |
846 | |
847 | if (outside) { |
848 | |
849 | RenderStyle *s = cb->element() && cb->element()->parent() |
850 | && cb->element()->parent()->renderer() |
851 | ? cb->element()->parent()->renderer()->style() |
852 | : cb->style(); |
853 | bool rtl = s->direction() == RTL; |
854 | |
855 | const QFontMetrics &fm = s->fontMetrics(); |
856 | height = fm.height(); |
857 | |
858 | if (!outsideEnd) { |
859 | _x--; |
860 | } else { |
861 | _x += cb->width(); |
862 | } |
863 | |
864 | int hl = fm.leading() / 2; |
865 | int baseline = cb->baselinePosition(false); |
866 | if (!cb->isReplaced() || cb->style()->display() == BLOCK) { |
867 | if (!outsideEnd ^ rtl) |
868 | _y -= fm.leading() / 2; |
869 | else |
870 | _y += qMax(cb->height() - fm.ascent() - hl, 0); |
871 | } else { |
872 | _y += baseline - fm.ascent() - hl; |
873 | } |
874 | |
875 | } else { // !outside |
876 | |
877 | RenderStyle *s = cb->style(); |
878 | const QFontMetrics &fm = s->fontMetrics(); |
879 | height = fm.height(); |
880 | |
881 | _x += cb->borderLeft() + cb->paddingLeft(); |
882 | _y += cb->borderTop() + cb->paddingTop(); |
883 | |
884 | // ### regard direction |
885 | switch (s->textAlign()) { |
886 | case LEFT: |
887 | case KHTML_LEFT: |
888 | case TAAUTO: // ### find out what this does |
889 | case JUSTIFY: |
890 | break; |
891 | case CENTER: |
892 | case KHTML_CENTER: |
893 | _x += cb->contentWidth() / 2; |
894 | break; |
895 | case KHTML_RIGHT: |
896 | case RIGHT: |
897 | _x += cb->contentWidth(); |
898 | break; |
899 | }/*end switch*/ |
900 | }/*end if*/ |
901 | |
902 | CaretBoxLine *result = new CaretBoxLine; |
903 | deleter->append(result); |
904 | result->caret_boxes.append(new CaretBox(_x, _y, width, height, cb, |
905 | outside, outsideEnd)); |
906 | iter = result->begin(); |
907 | return result; |
908 | } |
909 | |
910 | #if DEBUG_CARETMODE > 0 |
911 | void CaretBoxLine::dump(QTextStream &ts, const QString &ind) const |
912 | { |
913 | ts << ind << "cbl: baseFlowBox@"<< basefb << endl; |
914 | QString ind2 = ind + " "; |
915 | for (size_t i = 0; i < caret_boxes.size(); i++) { |
916 | if (i > 0) ts << endl; |
917 | caret_boxes[i]->dump(ts, ind2); |
918 | } |
919 | } |
920 | #endif |
921 | |
922 | // == caret mode related helper functions |
923 | |
924 | /** seeks the root line box that is the parent of the given inline box. |
925 | * @param b given inline box |
926 | * @param base base render object which not to step over. If \c base's |
927 | * inline flow box is hit before the root line box, the flow box |
928 | * is returned instead. |
929 | * @return the root line box or the base flow box. |
930 | */ |
931 | inline InlineFlowBox *seekBaseFlowBox(InlineBox *b, RenderObject *base = 0) |
932 | { |
933 | // Seek root line box or base inline flow box, if \c base is interfering. |
934 | while (b->parent() && b->object() != base) { |
935 | b = b->parent(); |
936 | }/*wend*/ |
937 | Q_ASSERT(b->isInlineFlowBox()); |
938 | return static_cast<InlineFlowBox *>(b); |
939 | } |
940 | |
941 | /** determines whether the given element is a block level replaced element. |
942 | */ |
943 | inline bool isBlockRenderReplaced(RenderObject *r) |
944 | { |
945 | return r->isRenderReplaced() && r->style()->display() == BLOCK; |
946 | } |
947 | |
948 | /** determines the caret line box that contains the given position. |
949 | * |
950 | * If the node does not map to a render object, the function will snap to |
951 | * the next suitable render object following it. |
952 | * |
953 | * @param node node to begin with |
954 | * @param offset zero-based offset within node. |
955 | * @param cblDeleter deleter for caret box lines |
956 | * @param base base render object which the caret must not be placed beyond. |
957 | * @param r_ofs adjusted offset within render object |
958 | * @param caretBoxIt returns an iterator to the caret box that contains the |
959 | * given position. |
960 | * @return the determined caret box lineor 0 if either the node is 0 or |
961 | * there is no inline flow box containing this node. The containing block |
962 | * will still be set. If it is 0 too, @p node was invalid. |
963 | */ |
964 | static CaretBoxLine* findCaretBoxLine(DOM::NodeImpl *node, long offset, |
965 | CaretBoxLineDeleter *cblDeleter, RenderObject *base, |
966 | long &r_ofs, CaretBoxIterator &caretBoxIt) |
967 | { |
968 | bool outside, outsideEnd; |
969 | RenderObject *r = findRenderer(node, offset, base, r_ofs, outside, outsideEnd); |
970 | if (!r) { return 0; } |
971 | #if DEBUG_CARETMODE > 0 |
972 | kDebug(6200) << "=================== findCaretBoxLine"; |
973 | kDebug(6200) << "node "<< node << " offset: "<< offset << " r "<< r->renderName() << "["<< r << "].node "<< r->element()->nodeName().string() << "["<< r->element() << "]"<< " r_ofs "<< r_ofs << " outside "<< outside << " outsideEnd "<< outsideEnd; |
974 | #endif |
975 | |
976 | // There are two strategies to find the correct line box. (The third is failsafe) |
977 | // (A) First, if node's renderer is a RenderText, we only traverse its text |
978 | // runs and return the root line box (saves much time for long blocks). |
979 | // This should be the case 99% of the time. |
980 | // (B) Second, we derive the inline flow box directly when the renderer is |
981 | // a RenderBlock, RenderInline, or blocked RenderReplaced. |
982 | // (C) Otherwise, we iterate linearly through all line boxes in order to find |
983 | // the renderer. |
984 | |
985 | // (A) |
986 | if (r->isText()) do { |
987 | RenderText *t = static_cast<RenderText *>(r); |
988 | int dummy; |
989 | InlineBox *b = t->findInlineTextBox(offset, dummy, true); |
990 | // Actually b should never be 0, but some render texts don't have text |
991 | // boxes, so we insert the last run as an error correction. |
992 | // If there is no last run, we resort to (B) |
993 | if (!b) { |
994 | if (!t->lastTextBox()) |
995 | break; |
996 | b = t->lastTextBox(); |
997 | }/*end if*/ |
998 | Q_ASSERT(b); |
999 | outside = false; // text boxes cannot have outside positions |
1000 | InlineFlowBox *baseFlowBox = seekBaseFlowBox(b, base); |
1001 | #if DEBUG_CARETMODE > 2 |
1002 | kDebug(6200) << "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()); |
1003 | #endif |
1004 | #if 0 |
1005 | if (t->containingBlock()->isListItem()) dumpLineBoxes(static_cast<RenderFlow *>(t->containingBlock())); |
1006 | #endif |
1007 | #if DEBUG_CARETMODE > 0 |
1008 | kDebug(6200) << "=================== end findCaretBoxLine (renderText)"; |
1009 | #endif |
1010 | return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, |
1011 | b, outside, outsideEnd, caretBoxIt); |
1012 | } while(false);/*end if*/ |
1013 | |
1014 | // (B) |
1015 | bool isrepl = isBlockRenderReplaced(r); |
1016 | if (r->isRenderBlock() || r->isRenderInline() || isrepl) { |
1017 | RenderFlow *flow = static_cast<RenderFlow *>(r); |
1018 | InlineFlowBox *firstLineBox = isrepl ? 0 : flow->firstLineBox(); |
1019 | |
1020 | // On render blocks, if we are outside, or have a totally empty render |
1021 | // block, we simply construct a special caret box line. |
1022 | // The latter case happens only when the render block is a leaf object itself. |
1023 | if (isrepl || r->isRenderBlock() && (outside || !firstLineBox) |
1024 | || r->isRenderInline() && !firstLineBox) { |
1025 | #if DEBUG_CARETMODE > 0 |
1026 | kDebug(6200) << "=================== end findCaretBoxLine (box "<< (outside ? (outsideEnd ? "outside end": "outside begin") : "inside") << ")"; |
1027 | #endif |
1028 | Q_ASSERT(r->isBox()); |
1029 | return CaretBoxLine::constructCaretBoxLine(cblDeleter, |
1030 | static_cast<RenderBox *>(r), outside, outsideEnd, caretBoxIt); |
1031 | }/*end if*/ |
1032 | |
1033 | kDebug(6200) << "firstlinebox "<< firstLineBox; |
1034 | InlineFlowBox *baseFlowBox = seekBaseFlowBox(firstLineBox, base); |
1035 | return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, |
1036 | firstLineBox, outside, outsideEnd, caretBoxIt); |
1037 | }/*end if*/ |
1038 | |
1039 | RenderBlock *cb = r->containingBlock(); |
1040 | //if ( !cb ) return 0L; |
1041 | Q_ASSERT(cb); |
1042 | |
1043 | // ### which element doesn't have a block as its containing block? |
1044 | // Is it still possible after the RenderBlock/RenderInline merge? |
1045 | if (!cb->isRenderBlock()) { |
1046 | kWarning() << "containing block is no render block!!! crash imminent"; |
1047 | }/*end if*/ |
1048 | |
1049 | InlineFlowBox *flowBox = cb->firstLineBox(); |
1050 | // (C) |
1051 | // This case strikes when the element is replaced, but neither a |
1052 | // RenderBlock nor a RenderInline |
1053 | if (!flowBox) { // ### utter emergency (why is this possible at all?) |
1054 | // flowBox = generateDummyFlowBox(arena, cb, r); |
1055 | // if (ibox) *ibox = flowBox->firstChild(); |
1056 | // outside = outside_end = true; |
1057 | |
1058 | // kWarning() << "containing block contains no inline flow boxes!!! crash imminent"; |
1059 | #if DEBUG_CARETMODE > 0 |
1060 | kDebug(6200) << "=================== end findCaretBoxLine (2)"; |
1061 | #endif |
1062 | return CaretBoxLine::constructCaretBoxLine(cblDeleter, cb, |
1063 | outside, outsideEnd, caretBoxIt); |
1064 | }/*end if*/ |
1065 | |
1066 | // We iterate the inline flow boxes of the containing block until |
1067 | // we find the given node. This has one major flaw: it is linear, and therefore |
1068 | // painfully slow for really large blocks. |
1069 | for (; flowBox; flowBox = static_cast<InlineFlowBox *>(flowBox->nextLineBox())) { |
1070 | #if DEBUG_CARETMODE > 0 |
1071 | kDebug(6200) << "[scan line]"; |
1072 | #endif |
1073 | |
1074 | // construct a caret line box and stop when the element is contained within |
1075 | InlineFlowBox *baseFlowBox = seekBaseFlowBox(flowBox, base); |
1076 | CaretBoxLine *cbl = CaretBoxLine::constructCaretBoxLine(cblDeleter, |
1077 | baseFlowBox, 0, outside, outsideEnd, caretBoxIt, r); |
1078 | #if DEBUG_CARETMODE > 5 |
1079 | kDebug(6200) << cbl->information(); |
1080 | #endif |
1081 | if (caretBoxIt != cbl->end()) { |
1082 | #if DEBUG_CARETMODE > 0 |
1083 | kDebug(6200) << "=================== end findCaretBoxLine (3)"; |
1084 | #endif |
1085 | return cbl; |
1086 | } |
1087 | }/*next flowBox*/ |
1088 | |
1089 | // no inline flow box found, approximate to nearest following node. |
1090 | // Danger: this is O(n^2). It's only called to recover from |
1091 | // errors, that means, theoretically, never. (Practically, far too often :-( ) |
1092 | Q_ASSERT(!flowBox); |
1093 | CaretBoxLine *cbl = findCaretBoxLine(nextLeafNode(node, base ? base->element() : 0), 0, cblDeleter, base, r_ofs, caretBoxIt); |
1094 | #if DEBUG_CARETMODE > 0 |
1095 | kDebug(6200) << "=================== end findCaretBoxLine"; |
1096 | #endif |
1097 | return cbl; |
1098 | } |
1099 | |
1100 | /** finds the innermost table object @p r is contained within, but no |
1101 | * farther than @p cb. |
1102 | * @param r leaf element to begin with |
1103 | * @param cb bottom element where to stop search at least. |
1104 | * @return the table object or 0 if none found. |
1105 | */ |
1106 | static inline RenderTable *findTableUpTo(RenderObject *r, RenderFlow *cb) |
1107 | { |
1108 | while (r && r != cb && !r->isTable()) r = r->parent(); |
1109 | return r && r->isTable() ? static_cast<RenderTable *>(r) : 0; |
1110 | } |
1111 | |
1112 | /** checks whether @p r is a descendant of @p cb, or r == cb |
1113 | */ |
1114 | static inline bool isDescendant(RenderObject *r, RenderObject *cb) |
1115 | { |
1116 | while (r && r != cb) r = r->parent(); |
1117 | return r; |
1118 | } |
1119 | |
1120 | /** checks whether the given block contains at least one editable element. |
1121 | * |
1122 | * Warning: This function has linear complexity, and therefore is expensive. |
1123 | * Use it sparingly, and cache the result. |
1124 | * @param part part |
1125 | * @param cb block to be searched |
1126 | * @param table returns the nested table if there is one directly at the beginning |
1127 | * or at the end. |
1128 | * @param fromEnd begin search from end (default: begin from beginning) |
1129 | */ |
1130 | static bool containsEditableElement(KHTMLPart *part, RenderBlock *cb, |
1131 | RenderTable *&table, bool fromEnd = false) |
1132 | { |
1133 | RenderObject *r = cb; |
1134 | if (fromEnd) |
1135 | while (r->lastChild()) r = r->lastChild(); |
1136 | else |
1137 | while (r->firstChild()) r = r->firstChild(); |
1138 | |
1139 | RenderTable *tempTable = 0; |
1140 | table = 0; |
1141 | bool withinCb; |
1142 | // int state; // not used |
1143 | ObjectTraversalState trav = InsideDescending; |
1144 | do { |
1145 | bool modWithinCb = withinCb = isDescendant(r, cb); |
1146 | |
1147 | // treat cb extra, it would not be considered otherwise |
1148 | if (!modWithinCb) { |
1149 | modWithinCb = true; |
1150 | r = cb; |
1151 | } else |
1152 | tempTable = findTableUpTo(r, cb); |
1153 | |
1154 | #if DEBUG_CARETMODE > 1 |
1155 | kDebug(6201) << "cee: r "<< (r ? r->renderName() : QString()) << "@"<< r << " cb "<< cb << " withinCb "<< withinCb << " modWithinCb "<< modWithinCb << " tempTable "<< tempTable; |
1156 | #endif |
1157 | if (r && modWithinCb && r->element() && !isUnsuitable(r, trav) |
1158 | && (part->isCaretMode() || part->isEditable() |
1159 | || r->style()->userInput() == UI_ENABLED)) { |
1160 | table = tempTable; |
1161 | #if DEBUG_CARETMODE > 1 |
1162 | kDebug(6201) << "cee: editable"; |
1163 | #endif |
1164 | return true; |
1165 | }/*end if*/ |
1166 | |
1167 | // RenderObject *oldr = r; |
1168 | // while (r && r == oldr) |
1169 | // r = advanceSuitableObject(r, trav, fromEnd, cb->parent(), state); |
1170 | r = fromEnd ? r->objectAbove() : r->objectBelow(); |
1171 | } while (r && withinCb); |
1172 | return false; |
1173 | } |
1174 | |
1175 | /** checks whether the given block contains at least one editable child |
1176 | * element, beginning with but excluding @p start. |
1177 | * |
1178 | * Warning: This function has linear complexity, and therefore is expensive. |
1179 | * Use it sparingly, and cache the result. |
1180 | * @param part part |
1181 | * @param cb block to be searched |
1182 | * @param table returns the nested table if there is one directly before/after |
1183 | * the start object. |
1184 | * @param fromEnd begin search from end (default: begin from beginning) |
1185 | * @param start object after which to begin search. |
1186 | */ |
1187 | static bool containsEditableChildElement(KHTMLPart *part, RenderBlock *cb, |
1188 | RenderTable *&table, bool fromEnd, RenderObject *start) |
1189 | { |
1190 | int state = 0; |
1191 | ObjectTraversalState trav = OutsideAscending; |
1192 | // kDebug(6201) << "start: " << start; |
1193 | RenderObject *r = start; |
1194 | do { |
1195 | r = traverseRenderObjects(r, trav, fromEnd, cb->parent(), state); |
1196 | } while(r && !(state & AdvancedToSibling)); |
1197 | // kDebug(6201) << "r: " << r; |
1198 | //advanceObject(start, trav, fromEnd, cb->parent(), state); |
1199 | // RenderObject *oldr = r; |
1200 | // while (r && r == oldr) |
1201 | if (!r) return false; |
1202 | |
1203 | if (fromEnd) |
1204 | while (r->firstChild()) r = r->firstChild(); |
1205 | else |
1206 | while (r->lastChild()) r = r->lastChild(); |
1207 | // kDebug(6201) << "child r: " << r; |
1208 | if (!r) return false; |
1209 | |
1210 | RenderTable *tempTable = 0; |
1211 | table = 0; |
1212 | bool withinCb = false; |
1213 | do { |
1214 | |
1215 | bool modWithinCb = withinCb = isDescendant(r, cb); |
1216 | |
1217 | // treat cb extra, it would not be considered otherwise |
1218 | if (!modWithinCb) { |
1219 | modWithinCb = true; |
1220 | r = cb; |
1221 | } else |
1222 | tempTable = findTableUpTo(r, cb); |
1223 | |
1224 | #if DEBUG_CARETMODE > 1 |
1225 | kDebug(6201) << "cece: r "<< (r ? r->renderName() : QString()) << "@"<< r << " cb "<< cb << " withinCb "<< withinCb << " modWithinCb "<< modWithinCb << " tempTable "<< tempTable; |
1226 | #endif |
1227 | if (r && withinCb && r->element() && !isUnsuitable(r, trav) |
1228 | && (part->isCaretMode() || part->isEditable() |
1229 | || r->style()->userInput() == UI_ENABLED)) { |
1230 | table = tempTable; |
1231 | #if DEBUG_CARETMODE > 1 |
1232 | kDebug(6201) << "cece: editable"; |
1233 | #endif |
1234 | return true; |
1235 | }/*end if*/ |
1236 | |
1237 | r = fromEnd ? r->objectAbove() : r->objectBelow(); |
1238 | } while (withinCb); |
1239 | return false; |
1240 | } |
1241 | |
1242 | // == class LinearDocument implementation |
1243 | |
1244 | LinearDocument::LinearDocument(KHTMLPart *part, NodeImpl *node, long offset, |
1245 | CaretAdvancePolicy advancePolicy, ElementImpl *baseElem) |
1246 | : node(node), offset(offset), m_part(part), |
1247 | advPol(advancePolicy), base(0) |
1248 | { |
1249 | if (node == 0) return; |
1250 | |
1251 | if (baseElem) { |
1252 | RenderObject *b = baseElem->renderer(); |
1253 | if (b && (b->isRenderBlock() || b->isRenderInline())) |
1254 | base = b; |
1255 | } |
1256 | |
1257 | initPreBeginIterator(); |
1258 | initEndIterator(); |
1259 | } |
1260 | |
1261 | LinearDocument::~LinearDocument() |
1262 | { |
1263 | } |
1264 | |
1265 | int LinearDocument::count() const |
1266 | { |
1267 | // FIXME: not implemented |
1268 | return 1; |
1269 | } |
1270 | |
1271 | LinearDocument::Iterator LinearDocument::current() |
1272 | { |
1273 | return LineIterator(this, node, offset); |
1274 | } |
1275 | |
1276 | LinearDocument::Iterator LinearDocument::begin() |
1277 | { |
1278 | NodeImpl *n = base ? base->element() : 0; |
1279 | if (!base) n = node ? node->getDocument() : 0; |
1280 | if (!n) return end(); |
1281 | |
1282 | n = n->firstChild(); |
1283 | if (advPol == LeafsOnly) |
1284 | while (n->firstChild()) n = n->firstChild(); |
1285 | |
1286 | if (!n) return end(); // must be empty document or empty base element |
1287 | return LineIterator(this, n, n->minOffset()); |
1288 | } |
1289 | |
1290 | LinearDocument::Iterator LinearDocument::preEnd() |
1291 | { |
1292 | NodeImpl *n = base ? base->element() : 0; |
1293 | if (!base) n = node ? node->getDocument() : 0; |
1294 | if (!n) return preBegin(); |
1295 | |
1296 | n = n->lastChild(); |
1297 | if (advPol == LeafsOnly) |
1298 | while (n->lastChild()) n = n->lastChild(); |
1299 | |
1300 | if (!n) return preBegin(); // must be empty document or empty base element |
1301 | return LineIterator(this, n, n->maxOffset()); |
1302 | } |
1303 | |
1304 | void LinearDocument::initPreBeginIterator() |
1305 | { |
1306 | _preBegin = LineIterator(this, 0, 0); |
1307 | } |
1308 | |
1309 | void LinearDocument::initEndIterator() |
1310 | { |
1311 | _end = LineIterator(this, 0, 1); |
1312 | } |
1313 | |
1314 | // == class LineIterator implementation |
1315 | |
1316 | CaretBoxIterator LineIterator::currentBox /*KDE_NO_EXPORT*/; |
1317 | long LineIterator::currentOffset /*KDE_NO_EXPORT*/; |
1318 | |
1319 | LineIterator::LineIterator(LinearDocument *l, DOM::NodeImpl *node, long offset) |
1320 | : lines(l) |
1321 | { |
1322 | // kDebug(6200) << "LineIterator: node " << node << " offset " << offset; |
1323 | if (!node) { cbl = 0; return; } |
1324 | cbl = findCaretBoxLine(node, offset, &lines->cblDeleter, |
1325 | l->baseObject(), currentOffset, currentBox); |
1326 | // can happen on partially loaded documents |
1327 | #if DEBUG_CARETMODE > 0 |
1328 | if (!cbl) kDebug(6200) << "no render object found!"; |
1329 | #endif |
1330 | if (!cbl) return; |
1331 | #if DEBUG_CARETMODE > 1 |
1332 | kDebug(6200) << "LineIterator: offset "<< offset << " outside "<< cbl->isOutside(); |
1333 | #endif |
1334 | #if DEBUG_CARETMODE > 3 |
1335 | kDebug(6200) << cbl->information(); |
1336 | #endif |
1337 | if (currentBox == cbl->end()) { |
1338 | #if DEBUG_CARETMODE > 0 |
1339 | kDebug(6200) << "LineIterator: findCaretBoxLine failed"; |
1340 | #endif |
1341 | cbl = 0; |
1342 | }/*end if*/ |
1343 | } |
1344 | |
1345 | void LineIterator::nextBlock() |
1346 | { |
1347 | RenderObject *base = lines->baseObject(); |
1348 | |
1349 | bool cb_outside = cbl->isOutside(); |
1350 | bool cb_outside_end = cbl->isOutsideEnd(); |
1351 | |
1352 | { |
1353 | RenderObject *r = cbl->enclosingObject(); |
1354 | |
1355 | ObjectTraversalState trav; |
1356 | int state; // not used |
1357 | mapRenderPosToTraversalState(cb_outside, cb_outside_end, false, trav); |
1358 | #if DEBUG_CARETMODE > 1 |
1359 | kDebug(6200) << "nextBlock: before adv r"<< r << " 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; |
1360 | #endif |
1361 | r = advanceSuitableObject(r, trav, false, base, state); |
1362 | if (!r) { |
1363 | cbl = 0; |
1364 | return; |
1365 | }/*end if*/ |
1366 | |
1367 | mapTraversalStateToRenderPos(trav, false, cb_outside, cb_outside_end); |
1368 | #if DEBUG_CARETMODE > 1 |
1369 | kDebug(6200) << "nextBlock: after r"<< r << " trav "<< trav << " cb_outside "<< cb_outside << " cb_outside_end "<< cb_outside_end; |
1370 | #endif |
1371 | #if DEBUG_CARETMODE > 0 |
1372 | kDebug(6200) << "++: r "<< r << "["<< (r?r->renderName():QString()) << "]"; |
1373 | #endif |
1374 | |
1375 | RenderBlock *cb; |
1376 | |
1377 | // If we hit a block or replaced object, use this as its enclosing object |
1378 | bool isrepl = isBlockRenderReplaced(r); |
1379 | if (r->isRenderBlock() || isrepl) { |
1380 | RenderBox *cb = static_cast<RenderBox *>(r); |
1381 | |
1382 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, |
1383 | cb_outside, cb_outside_end, currentBox); |
1384 | |
1385 | #if DEBUG_CARETMODE > 0 |
1386 | kDebug(6200) << "r->isFlow is cb. continuation @"<< cb->continuation(); |
1387 | #endif |
1388 | return; |
1389 | } else { |
1390 | cb = r->containingBlock(); |
1391 | Q_ASSERT(cb->isRenderBlock()); |
1392 | }/*end if*/ |
1393 | InlineFlowBox *flowBox = cb->firstLineBox(); |
1394 | #if DEBUG_CARETMODE > 0 |
1395 | kDebug(6200) << "++: flowBox "<< flowBox << " cb "<< cb << ".node ")+QString::number((unsigned)cb->element(),16)+(cb->element()? |
1396 | #endif |
1397 | Q_ASSERT(flowBox); |
1398 | if (!flowBox) { // ### utter emergency (why is this possible at all?) |
1399 | cb_outside = cb_outside_end = true; |
1400 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, |
1401 | cb_outside, cb_outside_end, currentBox); |
1402 | return; |
1403 | } |
1404 | |
1405 | bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning |
1406 | CaretBoxIterator it; |
1407 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, |
1408 | flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); |
1409 | } |
1410 | } |
1411 | |
1412 | void LineIterator::prevBlock() |
1413 | { |
1414 | RenderObject *base = lines->baseObject(); |
1415 | |
1416 | bool cb_outside = cbl->isOutside(); |
1417 | bool cb_outside_end = cbl->isOutsideEnd(); |
1418 | |
1419 | { |
1420 | RenderObject *r = cbl->enclosingObject(); |
1421 | if (r->isAnonymous() && !cb_outside) |
1422 | cb_outside = true, cb_outside_end = false; |
1423 | |
1424 | ObjectTraversalState trav; |
1425 | int state; // not used |
1426 | mapRenderPosToTraversalState(cb_outside, cb_outside_end, true, trav); |
1427 | #if DEBUG_CARETMODE > 1 |
1428 | kDebug(6200) << "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; |
1429 | #endif |
1430 | r = advanceSuitableObject(r, trav, true, base, state); |
1431 | if (!r) { |
1432 | cbl = 0; |
1433 | return; |
1434 | }/*end if*/ |
1435 | |
1436 | mapTraversalStateToRenderPos(trav, true, cb_outside, cb_outside_end); |
1437 | #if DEBUG_CARETMODE > 1 |
1438 | kDebug(6200) << "prevBlock: after r"<< r << " trav "<< trav << " cb_outside "<< cb_outside << " cb_outside_end "<< cb_outside_end; |
1439 | #endif |
1440 | #if DEBUG_CARETMODE > 0 |
1441 | kDebug(6200) << "--: r "<< r << "["<< (r?r->renderName():QString()) << "]"; |
1442 | #endif |
1443 | |
1444 | RenderBlock *cb; |
1445 | |
1446 | // If we hit a block, use this as its enclosing object |
1447 | bool isrepl = isBlockRenderReplaced(r); |
1448 | // kDebug(6200) << "isrepl " << isrepl << " isblock " << r->isRenderBlock(); |
1449 | if (r->isRenderBlock() || isrepl) { |
1450 | RenderBox *cb = static_cast<RenderBox *>(r); |
1451 | |
1452 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, |
1453 | cb_outside, cb_outside_end, currentBox); |
1454 | |
1455 | #if DEBUG_CARETMODE > 0 |
1456 | kDebug(6200) << "r->isFlow is cb. continuation @"<< cb->continuation(); |
1457 | #endif |
1458 | return; |
1459 | } else { |
1460 | cb = r->containingBlock(); |
1461 | Q_ASSERT(cb->isRenderBlock()); |
1462 | }/*end if*/ |
1463 | InlineFlowBox *flowBox = cb->lastLineBox(); |
1464 | #if DEBUG_CARETMODE > 0 |
1465 | kDebug(6200) << "--: flowBox "<< flowBox << " cb "<< cb << "["<< (cb?cb->renderName()+QString( ".node ")+QString::number((unsigned)cb->element(),16)+(cb->element()? "@"+cb->element()->nodeName().string():QString()):QString()) << "]"; |
1466 | #endif |
1467 | Q_ASSERT(flowBox); |
1468 | if (!flowBox) { // ### utter emergency (why is this possible at all?) |
1469 | cb_outside = true; cb_outside_end = false; |
1470 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, |
1471 | cb_outside, cb_outside_end, currentBox); |
1472 | return; |
1473 | } |
1474 | |
1475 | bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning |
1476 | CaretBoxIterator it; |
1477 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, |
1478 | flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); |
1479 | } |
1480 | } |
1481 | |
1482 | void LineIterator::advance(bool toBegin) |
1483 | { |
1484 | InlineFlowBox *flowBox = cbl->baseFlowBox(); |
1485 | if (flowBox) { |
1486 | flowBox = static_cast<InlineFlowBox *>(toBegin ? flowBox->prevLineBox() : flowBox->nextLineBox()); |
1487 | if (flowBox) { |
1488 | bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning |
1489 | CaretBoxIterator it; |
1490 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, |
1491 | flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); |
1492 | }/*end if*/ |
1493 | }/*end if*/ |
1494 | |
1495 | // if there are no more lines in this block, move towards block to come |
1496 | if (!flowBox) { if (toBegin) prevBlock(); else nextBlock(); } |
1497 | |
1498 | #if DEBUG_CARETMODE > 3 |
1499 | if (cbl) kDebug(6200) << cbl->information(); |
1500 | #endif |
1501 | } |
1502 | |
1503 | // == class EditableCaretBoxIterator implementation |
1504 | |
1505 | void EditableCaretBoxIterator::advance(bool toBegin) |
1506 | { |
1507 | #if DEBUG_CARETMODE > 3 |
1508 | kDebug(6200) << "---------------"<< "toBegin "<< toBegin; |
1509 | #endif |
1510 | const CaretBoxIterator preBegin = cbl->preBegin(); |
1511 | const CaretBoxIterator end = cbl->end(); |
1512 | |
1513 | CaretBoxIterator lastbox = *this, curbox; |
1514 | bool islastuseable = true; // silence gcc |
1515 | bool iscuruseable; |
1516 | // Assume adjacency of caret boxes. Will be falsified later if applicable. |
1517 | adjacent = true; |
1518 | |
1519 | #if DEBUG_CARETMODE > 4 |
1520 | // kDebug(6200) << "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(); |
1521 | #endif |
1522 | |
1523 | if (toBegin) CaretBoxIterator::operator --(); else CaretBoxIterator::operator ++(); |
1524 | bool curAtEnd = *this == preBegin || *this == end; |
1525 | curbox = *this; |
1526 | bool atEnd = true; |
1527 | if (!curAtEnd) { |
1528 | iscuruseable = isEditable(curbox, toBegin); |
1529 | if (toBegin) CaretBoxIterator::operator --(); else CaretBoxIterator::operator ++(); |
1530 | atEnd = *this == preBegin || *this == end; |
1531 | } |
1532 | while (!curAtEnd) { |
1533 | bool haslast = lastbox != end && lastbox != preBegin; |
1534 | bool hascoming = !atEnd; |
1535 | bool iscominguseable = true; // silence gcc |
1536 | |
1537 | if (!atEnd) iscominguseable = isEditable(*this, toBegin); |
1538 | if (iscuruseable) { |
1539 | #if DEBUG_CARETMODE > 3 |
1540 | kDebug(6200) << "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(); |
1541 | #endif |
1542 | |
1543 | CaretBox *box = *curbox; |
1544 | if (box->isOutside()) { |
1545 | // if this caret box represents no inline box, it is an outside box |
1546 | // which has to be considered unconditionally |
1547 | if (!box->isInline()) break; |
1548 | |
1549 | if (advpol == VisibleFlows) break; |
1550 | |
1551 | // IndicatedFlows and LeafsOnly are treated equally in caret box lines |
1552 | |
1553 | InlineBox *ibox = box->inlineBox(); |
1554 | // get previous inline box |
1555 | InlineBox *prev = box->isOutsideEnd() ? ibox : ibox->prevOnLine(); |
1556 | // get next inline box |
1557 | InlineBox *next = box->isOutsideEnd() ? ibox->nextOnLine() : ibox; |
1558 | |
1559 | const bool isprevindicated = !prev || isIndicatedInlineBox(prev); |
1560 | const bool isnextindicated = !next || isIndicatedInlineBox(next); |
1561 | const bool last = haslast && !islastuseable; |
1562 | const bool coming = hascoming && !iscominguseable; |
1563 | const bool left = !prev || prev->isInlineFlowBox() && isprevindicated |
1564 | || (toBegin && coming || !toBegin && last); |
1565 | const bool right = !next || next->isInlineFlowBox() && isnextindicated |
1566 | || (!toBegin && coming || toBegin && last); |
1567 | const bool text2indicated = toBegin && next && next->isInlineTextBox() |
1568 | && isprevindicated |
1569 | || !toBegin && prev && prev->isInlineTextBox() && isnextindicated; |
1570 | const bool indicated2text = !toBegin && next && next->isInlineTextBox() |
1571 | && prev && isprevindicated |
1572 | // ### this code is so broken. |
1573 | /*|| toBegin && prev && prev->isInlineTextBox() && isnextindicated*/; |
1574 | #if DEBUG_CARETMODE > 5 |
1575 | kDebug(6200) << "prev "<< prev << " haslast "<< haslast << " islastuseable "<< islastuseable << " left "<< left << " next "<< next << " hascoming "<< hascoming << " iscominguseable "<< iscominguseable << " right "<< right << " text2indicated "<< text2indicated << " indicated2text "<< indicated2text; |
1576 | #endif |
1577 | |
1578 | if (left && right && !text2indicated || indicated2text) { |
1579 | adjacent = false; |
1580 | #if DEBUG_CARETMODE > 4 |
1581 | kDebug(6200) << "left && right && !text2indicated || indicated2text"; |
1582 | #endif |
1583 | break; |
1584 | } |
1585 | |
1586 | } else { |
1587 | // inside boxes are *always* valid |
1588 | #if DEBUG_CARETMODE > 4 |
1589 | if (box->isInline()) { |
1590 | InlineBox *ibox = box->inlineBox(); |
1591 | kDebug(6200) << "inside "<< (!ibox->isInlineFlowBox() || static_cast<InlineFlowBox *>(ibox)->firstChild() ? "non-empty": "empty") << (isIndicatedInlineBox(ibox) ? " indicated": "") << " adjacent="<< adjacent; |
1592 | } |
1593 | #if 0 |
1594 | RenderStyle *s = ibox->object()->style(); |
1595 | kDebug(6200) << "bordls "<< s->borderLeftStyle() |
1596 | << " bordl "<< (s->borderLeftStyle() != BNONE) |
1597 | << " bordr "<< (s->borderRightStyle() != BNONE) |
1598 | << " bordt "<< (s->borderTopStyle() != BNONE) |
1599 | << " bordb "<< (s->borderBottomStyle() != BNONE) |
1600 | << " padl "<< s->paddingLeft().value() |
1601 | << " padr "<< s->paddingRight().value() |
1602 | << " padt "<< s->paddingTop().value() |
1603 | << " padb "<< s->paddingBottom().value() |
1604 | // ### Can inline elements have top/bottom margins? Couldn't find |
1605 | // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. |
1606 | << " marl "<< s->marginLeft().value() |
1607 | << " marr "<< s->marginRight().value() |
1608 | << endl; |
1609 | #endif |
1610 | #endif |
1611 | break; |
1612 | }/*end if*/ |
1613 | |
1614 | } else { |
1615 | |
1616 | if (!(*curbox)->isOutside()) { |
1617 | // cannot be adjacent anymore |
1618 | adjacent = false; |
1619 | } |
1620 | |
1621 | }/*end if*/ |
1622 | lastbox = curbox; |
1623 | islastuseable = iscuruseable; |
1624 | curbox = *this; |
1625 | iscuruseable = iscominguseable; |
1626 | curAtEnd = atEnd; |
1627 | if (!atEnd) { |
1628 | if (toBegin) CaretBoxIterator::operator --(); else CaretBoxIterator::operator ++(); |
1629 | atEnd = *this == preBegin || *this == end; |
1630 | }/*end if*/ |
1631 | }/*wend*/ |
1632 | |
1633 | *static_cast<CaretBoxIterator *>(this) = curbox; |
1634 | #if DEBUG_CARETMODE > 4 |
1635 | // kDebug(6200) << "still valid? " << (*this != preBegin && *this != end); |
1636 | #endif |
1637 | #if DEBUG_CARETMODE > 3 |
1638 | kDebug(6200) << "---------------"<< "end "; |
1639 | #endif |
1640 | } |
1641 | |
1642 | bool EditableCaretBoxIterator::isEditable(const CaretBoxIterator &boxit, bool fromEnd) |
1643 | { |
1644 | Q_ASSERT(boxit != cbl->end() && boxit != cbl->preBegin()); |
1645 | CaretBox *b = *boxit; |
1646 | RenderObject *r = b->object(); |
1647 | #if DEBUG_CARETMODE > 0 |
1648 | // if (b->isInlineFlowBox()) kDebug(6200) << "b is inline flow box" << (outside ? " (outside)" : ""); |
1649 | kDebug(6200) << "isEditable r"<< r << ": "<< (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \""+ QString(((RenderText *)r)->str->s, qMin(((RenderText *)r)->str->l,15)) + "\"": QString()); |
1650 | #endif |
1651 | // Must check caret mode or design mode *after* r->element(), otherwise |
1652 | // lines without a backing DOM node get regarded, leading to a crash. |
1653 | // ### check should actually be in InlineBoxIterator |
1654 | NodeImpl *node = r->element(); |
1655 | ObjectTraversalState trav; |
1656 | mapRenderPosToTraversalState(b->isOutside(), b->isOutsideEnd(), fromEnd, trav); |
1657 | if (isUnsuitable(r, trav) || !node) { |
1658 | return false; |
1659 | } |
1660 | |
1661 | // generally exclude replaced elements with no children from navigation |
1662 | if (!b->isOutside() && r->isRenderReplaced() && !r->firstChild()) |
1663 | return false; |
1664 | |
1665 | RenderObject *eff_r = r; |
1666 | bool globallyNavigable = m_part->isCaretMode() || m_part->isEditable(); |
1667 | |
1668 | // calculate the parent element's editability if this inline box is outside. |
1669 | if (b->isOutside() && !globallyNavigable) { |
1670 | NodeImpl *par = node->parent(); |
1671 | // I wonder whether par can be 0. It shouldn't be possible if the |
1672 | // algorithm contained no bugs. |
1673 | Q_ASSERT(par); |
1674 | if (par) node = par; |
1675 | eff_r = node->renderer(); |
1676 | Q_ASSERT(eff_r); // this is a hard requirement |
1677 | } |
1678 | |
1679 | bool result = globallyNavigable || eff_r->style()->userInput() == UI_ENABLED; |
1680 | #if DEBUG_CARETMODE > 0 |
1681 | kDebug(6200) << result; |
1682 | #endif |
1683 | return result; |
1684 | } |
1685 | |
1686 | // == class EditableLineIterator implementation |
1687 | |
1688 | void EditableLineIterator::advance(bool toBegin) |
1689 | { |
1690 | CaretAdvancePolicy advpol = lines->advancePolicy(); |
1691 | LineIterator lasteditable, lastindicated; |
1692 | bool haslasteditable = false; |
1693 | bool haslastindicated = false; |
1694 | bool uselasteditable = false; |
1695 | |
1696 | LineIterator::advance(toBegin); |
1697 | while (cbl) { |
1698 | if (isEditable(*this)) { |
1699 | #if DEBUG_CARETMODE > 3 |
1700 | kDebug(6200) << "advance: "<< cbl->enclosingObject() << "@"<< cbl->enclosingObject()->renderName() << ".node "<< cbl->enclosingObject()->element() << "["<< (cbl->enclosingObject()->element() ? cbl->enclosingObject()->element()->nodeName().string() : QString()) << "]"; |
1701 | #endif |
1702 | |
1703 | bool hasindicated = isIndicatedFlow(cbl->enclosingObject()); |
1704 | if (hasindicated) { |
1705 | haslastindicated = true; |
1706 | lastindicated = *this; |
1707 | } |
1708 | |
1709 | switch (advpol) { |
1710 | case IndicatedFlows: |
1711 | if (hasindicated) goto wend; |
1712 | // fall through |
1713 | case LeafsOnly: |
1714 | if (cbl->isOutside()) break; |
1715 | // fall through |
1716 | case VisibleFlows: goto wend; |
1717 | }/*end switch*/ |
1718 | |
1719 | // remember rejected editable element |
1720 | lasteditable = *this; |
1721 | haslasteditable = true; |
1722 | #if DEBUG_CARETMODE > 4 |
1723 | kDebug(6200) << "remembered lasteditable "<< *lasteditable; |
1724 | #endif |
1725 | } else { |
1726 | |
1727 | // If this element isn't editable, but the last one was, and it was only |
1728 | // rejected because it didn't match the caret advance policy, force it. |
1729 | // Otherwise certain combinations of editable and uneditable elements |
1730 | // could never be reached with some policies. |
1731 | if (haslasteditable) { uselasteditable = true; break; } |
1732 | |
1733 | } |
1734 | LineIterator::advance(toBegin); |
1735 | }/*wend*/ |
1736 | wend: |
1737 | |
1738 | if (uselasteditable) *this = haslastindicated ? lastindicated : lasteditable; |
1739 | if (!cbl && haslastindicated) *this = lastindicated; |
1740 | } |
1741 | |
1742 | // == class EditableCharacterIterator implementation |
1743 | |
1744 | void EditableCharacterIterator::initFirstChar() |
1745 | { |
1746 | CaretBox *box = *ebit; |
1747 | InlineBox *b = box->inlineBox(); |
1748 | if (_offset == box->maxOffset()) |
1749 | peekNext(); |
1750 | else if (b && !box->isOutside() && b->isInlineTextBox()) |
1751 | _char = static_cast<RenderText *>(b->object())->str->s[_offset].unicode(); |
1752 | else |
1753 | _char = -1; |
1754 | } |
1755 | |
1756 | /** returns true when the given caret box is empty, i. e. should not |
1757 | * take place in caret movement. |
1758 | */ |
1759 | static inline bool isCaretBoxEmpty(CaretBox *box) { |
1760 | if (!box->isInline()) return false; |
1761 | InlineBox *ibox = box->inlineBox(); |
1762 | return ibox->isInlineFlowBox() |
1763 | && !static_cast<InlineFlowBox *>(ibox)->firstChild() |
1764 | && !isIndicatedInlineBox(ibox); |
1765 | } |
1766 | |
1767 | EditableCharacterIterator &EditableCharacterIterator::operator ++() |
1768 | { |
1769 | _offset++; |
1770 | |
1771 | CaretBox *box = *ebit; |
1772 | InlineBox *b = box->inlineBox(); |
1773 | long maxofs = box->maxOffset(); |
1774 | #if DEBUG_CARETMODE > 0 |
1775 | kDebug(6200) << "box->maxOffset() "<< box->maxOffset() << " box->minOffset() "<< box->minOffset(); |
1776 | #endif |
1777 | if (_offset == maxofs) { |
1778 | #if DEBUG_CARETMODE > 2 |
1779 | kDebug(6200) << "_offset == maxofs: "<< _offset << " == "<< maxofs; |
1780 | #endif |
1781 | peekNext(); |
1782 | } else if (_offset > maxofs) { |
1783 | #if DEBUG_CARETMODE > 2 |
1784 | kDebug(6200) << "_offset > maxofs: "<< _offset << " > "<< maxofs /*<< " _peekNext: " << _peekNext*/; |
1785 | #endif |
1786 | if (/*!_peekNext*/true) { |
1787 | ++ebit; |
1788 | if (ebit == (*_it)->end()) { // end of line reached, go to next line |
1789 | ++_it; |
1790 | #if DEBUG_CARETMODE > 3 |
1791 | kDebug(6200) << "++_it"; |
1792 | #endif |
1793 | if (_it != _it.lines->end()) { |
1794 | ebit = _it; |
1795 | box = *ebit; |
1796 | b = box->inlineBox(); |
1797 | #if DEBUG_CARETMODE > 3 |
1798 | kDebug(6200) << "box "<< box << " b "<< b << " isText "<< box->isInlineTextBox(); |
1799 | #endif |
1800 | |
1801 | #if DEBUG_CARETMODE > 3 |
1802 | RenderObject *_r = box->object(); |
1803 | kDebug(6200) << "_r "<< _r << ":"<< _r->element()->nodeName().string(); |
1804 | #endif |
1805 | _offset = box->minOffset(); |
1806 | #if DEBUG_CARETMODE > 3 |
1807 | kDebug(6200) << "_offset "<< _offset; |
1808 | #endif |
1809 | } else { |
1810 | b = 0; |
1811 | _end = true; |
1812 | }/*end if*/ |
1813 | goto readchar; |
1814 | }/*end if*/ |
1815 | }/*end if*/ |
1816 | |
1817 | bool adjacent = ebit.isAdjacent(); |
1818 | #if 0 |
1819 | // Jump over element if this one is not a text node. |
1820 | if (adjacent && !(*ebit)->isInlineTextBox()) { |
1821 | EditableCaretBoxIterator copy = ebit; |
1822 | ++ebit; |
1823 | if (ebit != (*_it)->end() && (*ebit)->isInlineTextBox() |
1824 | /*&& (!(*ebit)->isInlineFlowBox() |
1825 | || static_cast<InlineFlowBox *>(*ebit)->)*/) |
1826 | adjacent = false; |
1827 | else ebit = copy; |
1828 | }/*end if*/ |
1829 | #endif |
1830 | // Jump over empty elements. |
1831 | if (adjacent && !(*ebit)->isInlineTextBox()) { |
1832 | bool noemptybox = true; |
1833 | while (isCaretBoxEmpty(*ebit)) { |
1834 | noemptybox = false; |
1835 | EditableCaretBoxIterator copy = ebit; |
1836 | ++ebit; |
1837 | if (ebit == (*_it)->end()) { ebit = copy; break; } |
1838 | } |
1839 | if (noemptybox) adjacent = false; |
1840 | }/*end if*/ |
1841 | // _r = (*ebit)->object(); |
1842 | /*if (!_it.outside) */_offset = (*ebit)->minOffset() + adjacent; |
1843 | //_peekNext = 0; |
1844 | box = *ebit; |
1845 | b = box->inlineBox(); |
1846 | goto readchar; |
1847 | } else { |
1848 | readchar: |
1849 | // get character |
1850 | if (b && !box->isOutside() && b->isInlineTextBox() && _offset < b->maxOffset()) |
1851 | _char = static_cast<RenderText *>(b->object())->str->s[_offset].unicode(); |
1852 | else |
1853 | _char = -1; |
1854 | }/*end if*/ |
1855 | #if DEBUG_CARETMODE > 2 |
1856 | kDebug(6200) << "_offset: "<< _offset /*<< " _peekNext: " << _peekNext*/ << " char '"<< (char)_char << "'"; |
1857 | #endif |
1858 | |
1859 | #if DEBUG_CARETMODE > 0 |
1860 | if (!_end && ebit != (*_it)->end()) { |
1861 | CaretBox *box = *ebit; |
1862 | RenderObject *_r = box->object(); |
1863 | kDebug(6200) << "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>")); |
1864 | } |
1865 | #endif |
1866 | return *this; |
1867 | } |
1868 | |
1869 | EditableCharacterIterator &EditableCharacterIterator::operator --() |
1870 | { |
1871 | _offset--; |
1872 | //kDebug(6200) << "--: _offset=" << _offset; |
1873 | |
1874 | CaretBox *box = *ebit; |
1875 | CaretBox *_peekPrev = 0; |
1876 | CaretBox *_peekNext = 0; |
1877 | InlineBox *b = box->inlineBox(); |
1878 | long minofs = box->minOffset(); |
1879 | #if DEBUG_CARETMODE > 0 |
1880 | kDebug(6200) << "box->maxOffset() "<< box->maxOffset() << " box->minOffset() "<< box->minOffset(); |
1881 | #endif |
1882 | if (_offset == minofs) { |
1883 | #if DEBUG_CARETMODE > 2 |
1884 | kDebug(6200) << "_offset == minofs: "<< _offset << " == "<< minofs; |
1885 | #endif |
1886 | // _peekNext = b; |
1887 | // get character |
1888 | if (b && !box->isOutside() && b->isInlineTextBox()) |
1889 | _char = static_cast<RenderText *>(b->object())->text()[_offset].unicode(); |
1890 | else |
1891 | _char = -1; |
1892 | |
1893 | //peekPrev(); |
1894 | bool do_prev = false; |
1895 | { |
1896 | EditableCaretBoxIterator copy; |
1897 | _peekPrev = 0; |
1898 | do { |
1899 | copy = ebit; |
1900 | --ebit; |
1901 | if (ebit == (*_it)->preBegin()) { ebit = copy; break; } |
1902 | } while (isCaretBoxEmpty(*ebit)); |
1903 | // Jump to end of previous element if it's adjacent, and a text box |
1904 | if (ebit.isAdjacent() && ebit != (*_it)->preBegin() && (*ebit)->isInlineTextBox()) { |
1905 | _peekPrev = *ebit; |
1906 | do_prev = true; |
1907 | } else |
1908 | ebit = copy; |
1909 | } |
1910 | if (do_prev) goto prev; |
1911 | } else if (_offset < minofs) { |
1912 | prev: |
1913 | #if DEBUG_CARETMODE > 2 |
1914 | kDebug(6200) << "_offset < minofs: "<< _offset << " < "<< minofs /*<< " _peekNext: " << _peekNext*/; |
1915 | #endif |
1916 | if (!_peekPrev) { |
1917 | _peekNext = *ebit; |
1918 | --ebit; |
1919 | if (ebit == (*_it)->preBegin()) { // end of line reached, go to previous line |
1920 | --_it; |
1921 | #if DEBUG_CARETMODE > 3 |
1922 | kDebug(6200) << "--_it"; |
1923 | #endif |
1924 | if (_it != _it.lines->preBegin()) { |
1925 | // kDebug(6200) << "begin from end!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; |
1926 | ebit = EditableCaretBoxIterator(_it, true); |
1927 | box = *ebit; |
1928 | // RenderObject *r = box->object(); |
1929 | #if DEBUG_CARETMODE > 3 |
1930 | kDebug(6200) << "box "<< box << " b "<< box->inlineBox() << " isText "<< box->isInlineTextBox(); |
1931 | #endif |
1932 | _offset = box->maxOffset(); |
1933 | // if (!_it.outside) _offset = r->isBR() ? (*ebit)->minOffset() : (*ebit)->maxOffset(); |
1934 | _char = -1; |
1935 | #if DEBUG_CARETMODE > 0 |
1936 | kDebug(6200) << "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()); |
1937 | #endif |
1938 | } else |
1939 | _end = true; |
1940 | return *this; |
1941 | }/*end if*/ |
1942 | }/*end if*/ |
1943 | |
1944 | #if DEBUG_CARETMODE > 0 |
1945 | bool adjacent = ebit.isAdjacent(); |
1946 | kDebug(6200) << "adjacent "<< adjacent << " _peekNext "<< _peekNext << " _peekNext->isInlineTextBox: "<< (_peekNext ? _peekNext->isInlineTextBox() : false) << " !((*ebit)->isInlineTextBox): "<< (*ebit ? !(*ebit)->isInlineTextBox() : true); |
1947 | #endif |
1948 | #if 0 |
1949 | // Ignore this box if it isn't a text box, but the previous box was |
1950 | if (adjacent && _peekNext && _peekNext->isInlineTextBox() |
1951 | && !(*ebit)->isInlineTextBox()) { |
1952 | EditableCaretBoxIterator copy = ebit; |
1953 | --ebit; |
1954 | if (ebit == (*_it)->preBegin()) /*adjacent = false; |
1955 | else */ebit = copy; |
1956 | }/*end if*/ |
1957 | #endif |
1958 | #if 0 |
1959 | // Jump over empty elements. |
1960 | if (adjacent //&& _peekNext && _peekNext->isInlineTextBox() |
1961 | && !(*ebit)->isInlineTextBox()) { |
1962 | bool noemptybox = true; |
1963 | while (isCaretBoxEmpty(*ebit)) { |
1964 | noemptybox = false; |
1965 | EditableCaretBoxIterator copy = ebit; |
1966 | --ebit; |
1967 | if (ebit == (*_it)->preBegin()) { ebit = copy; break; } |
1968 | else _peekNext = *copy; |
1969 | } |
1970 | if (noemptybox) adjacent = false; |
1971 | }/*end if*/ |
1972 | #endif |
1973 | #if DEBUG_CARETMODE > 0 |
1974 | kDebug(6200) << "(*ebit)->obj "<< (*ebit)->object()->renderName() << "["<< (*ebit)->object() << "]"<< " minOffset: "<< (*ebit)->minOffset() << " maxOffset: "<< (*ebit)->maxOffset(); |
1975 | #endif |
1976 | #if DEBUG_CARETMODE > 3 |
1977 | RenderObject *_r = (*ebit)->object(); |
1978 | kDebug(6200) << "_r "<< _r << ":"<< _r->element()->nodeName().string(); |
1979 | #endif |
1980 | _offset = (*ebit)->maxOffset(); |
1981 | // if (!_it.outside) _offset = (*ebit)->maxOffset()/* - adjacent*/; |
1982 | #if DEBUG_CARETMODE > 3 |
1983 | kDebug(6200) << "_offset "<< _offset; |
1984 | #endif |
1985 | _peekPrev = 0; |
1986 | } else { |
1987 | #if DEBUG_CARETMODE > 0 |
1988 | kDebug(6200) << "_offset: "<< _offset << " _peekNext: "<< _peekNext; |
1989 | #endif |
1990 | // get character |
1991 | if (_peekNext && _offset >= box->maxOffset() && _peekNext->isInlineTextBox()) |
1992 | _char = static_cast<RenderText *>(_peekNext->object())->text()[_peekNext->minOffset()].unicode(); |
1993 | else if (b && _offset < b->maxOffset() && b->isInlineTextBox()) |
1994 | _char = static_cast<RenderText *>(b->object())->text()[_offset].unicode(); |
1995 | else |
1996 | _char = -1; |
1997 | }/*end if*/ |
1998 | |
1999 | #if DEBUG_CARETMODE > 0 |
2000 | if (!_end && ebit != (*_it)->preBegin()) { |
2001 | CaretBox *box = *ebit; |
2002 | kDebug(6200) << "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()); |
2003 | } |
2004 | #endif |
2005 | return *this; |
2006 | } |
2007 | |
2008 | // == class TableRowIterator implementation |
2009 | |
2010 | TableRowIterator::TableRowIterator(RenderTable *table, bool fromEnd, |
2011 | RenderTableSection::RowStruct *row) |
2012 | : sec(table, fromEnd) |
2013 | { |
2014 | // set index |
2015 | if (*sec) { |
2016 | if (fromEnd) index = (*sec)->grid.size() - 1; |
2017 | else index = 0; |
2018 | }/*end if*/ |
2019 | |
2020 | // initialize with given row |
2021 | if (row && *sec) { |
2022 | while (operator *() != row) |
2023 | if (fromEnd) operator --(); else operator ++(); |
2024 | }/*end if*/ |
2025 | } |
2026 | |
2027 | TableRowIterator &TableRowIterator::operator ++() |
2028 | { |
2029 | index++; |
2030 | |
2031 | if (index >= (int)(*sec)->grid.size()) { |
2032 | ++sec; |
2033 | |
2034 | if (*sec) index = 0; |
2035 | }/*end if*/ |
2036 | return *this; |
2037 | } |
2038 | |
2039 | TableRowIterator &TableRowIterator::operator --() |
2040 | { |
2041 | index--; |
2042 | |
2043 | if (index < 0) { |
2044 | --sec; |
2045 | |
2046 | if (*sec) index = (*sec)->grid.size() - 1; |
2047 | }/*end if*/ |
2048 | return *this; |
2049 | } |
2050 | |
2051 | // == class ErgonomicEditableLineIterator implementation |
2052 | |
2053 | // some decls |
2054 | static RenderTableCell *findNearestTableCellInRow(KHTMLPart *part, int x, |
2055 | RenderTableSection::RowStruct *row, bool fromEnd); |
2056 | |
2057 | /** finds the cell corresponding to absolute x-coordinate @p x in the given |
2058 | * table. |
2059 | * |
2060 | * If there is no direct cell, or the cell is not accessible, the function |
2061 | * will return the nearest suitable cell. |
2062 | * @param part part containing the document |
2063 | * @param x absolute x-coordinate |
2064 | * @param it table row iterator, will be adapted accordingly as more rows are |
2065 | * investigated. |
2066 | * @param fromEnd @p true to begin search from end and work towards the |
2067 | * beginning |
2068 | * @return the cell, or 0 if no editable cell was found. |
2069 | */ |
2070 | static inline RenderTableCell *findNearestTableCell(KHTMLPart *part, int x, |
2071 | TableRowIterator &it, bool fromEnd) |
2072 | { |
2073 | RenderTableCell *result = 0; |
2074 | |
2075 | while (*it) { |
2076 | result = findNearestTableCellInRow(part, x, *it, fromEnd); |
2077 | if (result) break; |
2078 | |
2079 | if (fromEnd) --it; else ++it; |
2080 | }/*wend*/ |
2081 | |
2082 | return result; |
2083 | } |
2084 | |
2085 | /** finds the nearest editable cell around the given absolute x-coordinate |
2086 | * |
2087 | * It will dive into nested tables as necessary to provide seamless navigation. |
2088 | * |
2089 | * If the cell at @p x is not editable, its left neighbor is tried, then its |
2090 | * right neighbor, then the left neighbor's left neighbor etc. If no |
2091 | * editable cell can be found, 0 is returned. |
2092 | * @param part khtml part |
2093 | * @param x absolute x-coordinate |
2094 | * @param row table row to be searched |
2095 | * @param fromEnd @p true, begin from end (applies only to nested tables) |
2096 | * @return the found cell or 0 if no editable cell was found |
2097 | */ |
2098 | static RenderTableCell *findNearestTableCellInRow(KHTMLPart *part, int x, |
2099 | RenderTableSection::RowStruct *row, bool fromEnd) |
2100 | { |
2101 | // First pass. Find spatially nearest cell. |
2102 | int n = (int)row->row->size(); |
2103 | int i; |
2104 | for (i = 0; i < n; i++) { |
2105 | RenderTableCell *cell = row->row->at(i); |
2106 | if (!cell || (long)cell == -1) continue; |
2107 | |
2108 | int absx, absy; |
2109 | cell->absolutePosition(absx, absy, false); // ### position: fixed? |
2110 | #if DEBUG_CARETMODE > 1 |
2111 | kDebug(6201) << "i/n "<< i << "/"<< n << " absx "<< absx << " absy "<< absy; |
2112 | #endif |
2113 | |
2114 | // I rely on the assumption that all cells are in ascending visual order |
2115 | // ### maybe this assumption is wrong for bidi? |
2116 | #if DEBUG_CARETMODE > 1 |
2117 | kDebug(6201) << "x "<< x << " < "<< (absx + cell->width()) << "?"; |
2118 | #endif |
2119 | if (x < absx + cell->width()) break; |
2120 | }/*next i*/ |
2121 | if (i >= n) i = n - 1; |
2122 | |
2123 | // Second pass. Find editable cell, beginning with the currently found, |
2124 | // extending to the left, and to the right, alternating. |
2125 | for (int cnt = 0; cnt < 2*n; cnt++) { |
2126 | int index = i - ((cnt >> 1) + 1)*(cnt & 1) + (cnt >> 1)*!(cnt & 1); |
2127 | if (index < 0 || index >= n) continue; |
2128 | |
2129 | RenderTableCell *cell = row->row->at(index); |
2130 | if (!cell || (long)cell == -1) continue; |
2131 | |
2132 | #if DEBUG_CARETMODE > 1 |
2133 | kDebug(6201) << "index "<< index << " cell "<< cell; |
2134 | #endif |
2135 | RenderTable *nestedTable; |
2136 | if (containsEditableElement(part, cell, nestedTable, fromEnd)) { |
2137 | |
2138 | if (nestedTable) { |
2139 | TableRowIterator it(nestedTable, fromEnd); |
2140 | while (*it) { |
2141 | // kDebug(6201) << "=== recursive invocation"; |
2142 | cell = findNearestTableCell(part, x, it, fromEnd); |
2143 | if (cell) break; |
2144 | if (fromEnd) --it; else ++it; |
2145 | }/*wend*/ |
2146 | }/*end if*/ |
2147 | |
2148 | return cell; |
2149 | }/*end if*/ |
2150 | }/*next i*/ |
2151 | return 0; |
2152 | } |
2153 | |
2154 | /** returns the nearest common ancestor of two objects that is a table cell, |
2155 | * a table section, or 0 if not inside a common table. |
2156 | * |
2157 | * If @p r1 and @p r2 belong to the same table, but different sections, @p r1's |
2158 | * section is returned. |
2159 | */ |
2160 | static RenderObject *commonAncestorTableSectionOrCell(RenderObject *r1, |
2161 | RenderObject *r2) |
2162 | { |
2163 | if (!r1 || !r2) return 0; |
2164 | RenderTableSection *sec = 0; |
2165 | int start_depth=0, end_depth=0; |
2166 | // First we find the depths of the two objects in the tree (start_depth, end_depth) |
2167 | RenderObject *n = r1; |
2168 | while (n->parent()) { |
2169 | n = n->parent(); |
2170 | start_depth++; |
2171 | }/*wend*/ |
2172 | n = r2; |
2173 | while( n->parent()) { |
2174 | n = n->parent(); |
2175 | end_depth++; |
2176 | }/*wend*/ |
2177 | // here we climb up the tree with the deeper object, until both objects have equal depth |
2178 | while (end_depth > start_depth) { |
2179 | r2 = r2->parent(); |
2180 | end_depth--; |
2181 | }/*wend*/ |
2182 | while (start_depth > end_depth) { |
2183 | r1 = r1->parent(); |
2184 | // if (r1->isTableSection()) sec = static_cast<RenderTableSection *>(r1); |
2185 | start_depth--; |
2186 | }/*wend*/ |
2187 | // Climb the tree with both r1 and r2 until they are the same |
2188 | while (r1 != r2){ |
2189 | r1 = r1->parent(); |
2190 | if (r1->isTableSection()) sec = static_cast<RenderTableSection *>(r1); |
2191 | r2 = r2->parent(); |
2192 | }/*wend*/ |
2193 | |
2194 | // At this point, we found the most approximate common ancestor. Now climb |
2195 | // up until the condition of the function return value is satisfied. |
2196 | while (r1 && !r1->isTableCell() && !r1->isTableSection() && !r1->isTable()) |
2197 | r1 = r1->parent(); |
2198 | |
2199 | return r1 && r1->isTable() ? sec : r1; |
2200 | } |
2201 | |
2202 | /** Finds the row that contains the given cell, directly, or indirectly |
2203 | * @param section section to be searched |
2204 | * @param cell table cell |
2205 | * @param row returns the row |
2206 | * @param directCell returns the direct cell that contains @p cell |
2207 | * @return the index of the row. |
2208 | */ |
2209 | static int findRowInSection(RenderTableSection *section, RenderTableCell *cell, |
2210 | RenderTableSection::RowStruct *&row, RenderTableCell *&directCell) |
2211 | { |
2212 | // Seek direct cell |
2213 | RenderObject *r = cell; |
2214 | while (r != section) { |
2215 | if (r->isTableCell()) directCell = static_cast<RenderTableCell *>(r); |
2216 | r = r->parent(); |
2217 | }/*wend*/ |
2218 | |
2219 | // So, and this is really nasty: As we have no indices, we have to do a |
2220 | // linear comparison. Oh, that sucks so much for long tables, you can't |
2221 | // imagine. |
2222 | int n = section->numRows(); |
2223 | for (int i = 0; i < n; i++) { |
2224 | row = §ion->grid[i]; |
2225 | |
2226 | // check for cell |
2227 | int m = row->row->size(); |
2228 | for (int j = 0; j < m; j++) { |
2229 | RenderTableCell *c = row->row->at(j); |
2230 | if (c == directCell) return i; |
2231 | }/*next j*/ |
2232 | |
2233 | }/*next i*/ |
2234 | Q_ASSERT(false); |
2235 | return -1; |
2236 | } |
2237 | |
2238 | /** finds the table that is the first direct or indirect descendant of @p block. |
2239 | * @param leaf object to begin search from. |
2240 | * @param block object to search to, or 0 to search up to top. |
2241 | * @return the table or 0 if there were none. |
2242 | */ |
2243 | static inline RenderTable *findFirstDescendantTable(RenderObject *leaf, RenderBlock *block) |
2244 | { |
2245 | RenderTable *result = 0; |
2246 | while (leaf && leaf != block) { |
2247 | if (leaf->isTable()) result = static_cast<RenderTable *>(leaf); |
2248 | leaf = leaf->parent(); |
2249 | }/*wend*/ |
2250 | return result; |
2251 | } |
2252 | |
2253 | /** looks for the table cell the given object @p r is contained within. |
2254 | * @return the table cell or 0 if not contained in any table. |
2255 | */ |
2256 | static inline RenderTableCell *containingTableCell(RenderObject *r) |
2257 | { |
2258 | while (r && !r->isTableCell()) r = r->parent(); |
2259 | return static_cast<RenderTableCell *>(r); |
2260 | } |
2261 | |
2262 | inline void ErgonomicEditableLineIterator::calcAndStoreNewLine( |
2263 | RenderBlock *newBlock, bool toBegin) |
2264 | { |
2265 | // take the first/last editable element in the found cell as the new |
2266 | // value for the iterator |
2267 | CaretBoxIterator it; |
2268 | cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, |
2269 | newBlock, true, toBegin, it); |
2270 | #if DEBUG_CARETMODE > 3 |
2271 | kDebug(6201) << cbl->information(); |
2272 | #endif |
2273 | // if (toBegin) prevBlock(); else nextBlock(); |
2274 | |
2275 | if (!cbl) { |
2276 | return; |
2277 | }/*end if*/ |
2278 | |
2279 | EditableLineIterator::advance(toBegin); |
2280 | } |
2281 | |
2282 | void ErgonomicEditableLineIterator::determineTopologicalElement( |
2283 | RenderTableCell *oldCell, RenderObject *newObject, bool toBegin) |
2284 | { |
2285 | // When we arrive here, a transition between cells has happened. |
2286 | // Now determine the type of the transition. This can be |
2287 | // (1) a transition from this cell into a table inside this cell. |
2288 | // (2) a transition from this cell into another cell of this table |
2289 | |
2290 | TableRowIterator it; |
2291 | |
2292 | RenderObject *commonAncestor = commonAncestorTableSectionOrCell(oldCell, newObject); |
2293 | #if DEBUG_CARETMODE > 1 |
2294 | kDebug(6201) << " ancestor "<< commonAncestor; |
2295 | #endif |
2296 | |
2297 | // The whole document is treated as a table cell. |
2298 | if (!commonAncestor || commonAncestor->isTableCell()) { // (1) |
2299 | |
2300 | RenderTableCell *cell = static_cast<RenderTableCell *>(commonAncestor); |
2301 | RenderTable *table = findFirstDescendantTable(newObject, cell); |
2302 | |
2303 | #if DEBUG_CARETMODE > 0 |
2304 | kDebug(6201) << "table cell: "<< cell; |
2305 | #endif |
2306 | |
2307 | // if there is no table, we fell out of the previous table, and are now |
2308 | // in some table-less block. Therefore, done. |
2309 | if (!table) return; |
2310 | |
2311 | it = TableRowIterator(table, toBegin); |
2312 | |
2313 | } else if (commonAncestor->isTableSection()) { // (2) |
2314 | |
2315 | RenderTableSection *section = static_cast<RenderTableSection *>(commonAncestor); |
2316 | RenderTableSection::RowStruct *row; |
2317 | int idx = findRowInSection(section, oldCell, row, oldCell); |
2318 | #if DEBUG_CARETMODE > 1 |
2319 | kDebug(6201) << "table section: row idx "<< idx; |
2320 | #endif |
2321 | |
2322 | it = TableRowIterator(section, idx); |
2323 | |
2324 | // advance rowspan rows |
2325 | int rowspan = oldCell->rowSpan(); |
2326 | while (*it && rowspan--) { |
2327 | if (toBegin) --it; else ++it; |
2328 | }/*wend*/ |
2329 | |
2330 | } else { |
2331 | kError(6201) << "Neither common cell nor section! "<< commonAncestor->renderName() << endl; |
2332 | // will crash on uninitialized table row iterator |
2333 | }/*end if*/ |
2334 | |
2335 | RenderTableCell *cell = findNearestTableCell(lines->m_part, xCoor, it, toBegin); |
2336 | #if DEBUG_CARETMODE > 1 |
2337 | kDebug(6201) << "findNearestTableCell result: "<< cell; |
2338 | #endif |
2339 | |
2340 | RenderBlock *newBlock = cell; |
2341 | if (!cell) { |
2342 | Q_ASSERT(commonAncestor->isTableSection()); |
2343 | RenderTableSection *section = static_cast<RenderTableSection *>(commonAncestor); |
2344 | cell = containingTableCell(section); |
2345 | #if DEBUG_CARETMODE > 1 |
2346 | kDebug(6201) << "containing cell: "<< cell; |
2347 | #endif |
2348 | |
2349 | RenderTable *nestedTable; |
2350 | bool editableChild = cell && containsEditableChildElement(lines->m_part, |
2351 | cell, nestedTable, toBegin, section->table()); |
2352 | |
2353 | if (cell && !editableChild) { |
2354 | #if DEBUG_CARETMODE > 1 |
2355 | kDebug(6201) << "========= recursive invocation outer ========="; |
2356 | #endif |
2357 | determineTopologicalElement(cell, cell->section(), toBegin); |
2358 | #if DEBUG_CARETMODE > 1 |
2359 | kDebug(6201) << "========= end recursive invocation outer ========="; |
2360 | #endif |
2361 | return; |
2362 | |
2363 | } else if (cell && nestedTable) { |
2364 | #if DEBUG_CARETMODE > 1 |
2365 | kDebug(6201) << "========= recursive invocation inner ========="; |
2366 | #endif |
2367 | determineTopologicalElement(cell, nestedTable, toBegin); |
2368 | #if DEBUG_CARETMODE > 1 |
2369 | kDebug(6201) << "========= end recursive invocation inner ========="; |
2370 | #endif |
2371 | return; |
2372 | |
2373 | } else { |
2374 | #if DEBUG_CARETMODE > 1 |
2375 | kDebug(6201) << "newBlock is table: "<< section->table(); |
2376 | #endif |
2377 | RenderObject *r = section->table(); |
2378 | int state; // not used |
2379 | ObjectTraversalState trav = OutsideAscending; |
2380 | r = advanceSuitableObject(r, trav, toBegin, lines->baseObject(), state); |
2381 | if (!r) { cbl = 0; return; } |
2382 | // if (toBegin) prevBlock(); else nextBlock(); |
2383 | newBlock = static_cast<RenderBlock *>(!r || r->isRenderBlock() ? r : r->containingBlock()); |
2384 | }/*end if*/ |
2385 | #if 0 |
2386 | } else { |
2387 | // adapt cell so that prevBlock/nextBlock works as expected |
2388 | newBlock = cell; |
2389 | // on forward advancing, we must start from the outside end of the |
2390 | // previous object |
2391 | if (!toBegin) { |
2392 | RenderObject *r = newBlock; |
2393 | int state; // not used |
2394 | ObjectTraversalState trav = OutsideAscending; |
2395 | r = advanceSuitableObject(r, trav, true, lines->advancePolicy(), lines->baseObject(), state); |
2396 | newBlock = static_cast<RenderBlock *>(!r || r->isRenderBlock() ? r : r->containingBlock()); |
2397 | }/*end if*/ |
2398 | #endif |
2399 | }/*end if*/ |
2400 | |
2401 | calcAndStoreNewLine(newBlock, toBegin); |
2402 | } |
2403 | |
2404 | ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator ++() |
2405 | { |
2406 | RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); |
2407 | |
2408 | EditableLineIterator::operator ++(); |
2409 | if (*this == lines->end() || *this == lines->preBegin()) return *this; |
2410 | |
2411 | RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); |
2412 | |
2413 | if (!newCell || newCell == oldCell) return *this; |
2414 | |
2415 | determineTopologicalElement(oldCell, newCell, false); |
2416 | |
2417 | return *this; |
2418 | } |
2419 | |
2420 | ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator --() |
2421 | { |
2422 | RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); |
2423 | |
2424 | EditableLineIterator::operator --(); |
2425 | if (*this == lines->end() || *this == lines->preBegin()) return *this; |
2426 | |
2427 | RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); |
2428 | |
2429 | if (!newCell || newCell == oldCell) return *this; |
2430 | |
2431 | determineTopologicalElement(oldCell, newCell, true); |
2432 | |
2433 | return *this; |
2434 | } |
2435 | |
2436 | // == Navigational helper functions == |
2437 | |
2438 | /** seeks the caret box which contains or is the nearest to @p x |
2439 | * @param it line iterator pointing to line to be searched |
2440 | * @param cv caret view context |
2441 | * @param x returns the cv->origX approximation, relatively positioned to the |
2442 | * containing block. |
2443 | * @param absx returns absolute x-coordinate of containing block |
2444 | * @param absy returns absolute y-coordinate of containing block |
2445 | * @return the most suitable caret box |
2446 | */ |
2447 | static CaretBox *nearestCaretBox(LineIterator &it, CaretViewContext *cv, |
2448 | int &x, int &absx, int &absy) |
2449 | { |
2450 | // Find containing block |
2451 | RenderObject *cb = (*it)->containingBlock(); |
2452 | #if DEBUG_CARETMODE > 4 |
2453 | kDebug(6200) << "nearestCB: cb "<< cb << "@"<< (cb ? cb->renderName() : ""); |
2454 | #endif |
2455 | |
2456 | if (cb) cb->absolutePosition(absx, absy); |
2457 | else absx = absy = 0; |
2458 | |
2459 | // Otherwise find out in which inline box the caret is to be placed. |
2460 | |
2461 | // this horizontal position is to be approximated |
2462 | x = cv->origX - absx; |
2463 | CaretBox *caretBox = 0; // Inline box containing the caret |
2464 | // NodeImpl *lastnode = 0; // node of previously checked render object. |
2465 | int xPos; // x-coordinate of current inline box |
2466 | int oldXPos = -1; // x-coordinate of last inline box |
2467 | EditableCaretBoxIterator fbit = it; |
2468 | #if DEBUG_CARETMODE > 0 |
2469 | /* if (it.linearDocument()->advancePolicy() != LeafsOnly) |
2470 | kWarning() << "nearestInlineBox is only prepared to handle the LeafsOnly advance policy";*/ |
2471 | // kDebug(6200) << "*fbit = " << *fbit; |
2472 | #endif |
2473 | // Iterate through all children |
2474 | for (CaretBox *b; fbit != (*it)->end(); ++fbit) { |
2475 | b = *fbit; |
2476 | |
2477 | #if DEBUG_CARETMODE > 0 |
2478 | // RenderObject *r = b->object(); |
2479 | // if (b->isInlineFlowBox()) kDebug(6200) << "b is inline flow box"; |
2480 | // kDebug(6200) << "approximate r" << r << ": " << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, ((RenderText *)r)->str->l) + "\"" : QString()); |
2481 | #endif |
2482 | xPos = b->xPos(); |
2483 | |
2484 | // the caret is before this box |
2485 | if (x < xPos) { |
2486 | // snap to nearest box |
2487 | if (oldXPos < 0 || x - (oldXPos + caretBox->width()) > xPos - x) { |
2488 | caretBox = b; // current box is nearer |
2489 | }/*end if*/ |
2490 | break; // Otherwise, preceding box is implicitly used |
2491 | } |
2492 | |
2493 | caretBox = b; |
2494 | |
2495 | // the caret is within this box |
2496 | if (x >= xPos && x < xPos + caretBox->width()) |
2497 | break; |
2498 | oldXPos = xPos; |
2499 | |
2500 | // the caret can only be after the last box which is automatically |
2501 | // contained in caretBox when we fall out of the loop. |
2502 | }/*next fbit*/ |
2503 | |
2504 | return caretBox; |
2505 | } |
2506 | |
2507 | /** moves the given iterator to the beginning of the next word. |
2508 | * |
2509 | * If the end is reached, the iterator will be positioned there. |
2510 | * @param it character iterator to be moved |
2511 | */ |
2512 | static void moveItToNextWord(EditableCharacterIterator &it) |
2513 | { |
2514 | #if DEBUG_CARETMODE > 0 |
2515 | kDebug(6200) << "%%%%%%%%%%%%%%%%%%%%% moveItToNextWord"; |
2516 | #endif |
2517 | EditableCharacterIterator copy; |
2518 | while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()) { |
2519 | #if DEBUG_CARETMODE > 2 |
2520 | kDebug(6200) << "reading1 '"<< (*it).toLatin1().constData() << "'"; |
2521 | #endif |
2522 | copy = it; |
2523 | ++it; |
2524 | } |
2525 | |
2526 | if (it.isEnd()) { |
2527 | it = copy; |
2528 | return; |
2529 | }/*end if*/ |
2530 | |
2531 | while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())) { |
2532 | #if DEBUG_CARETMODE > 2 |
2533 | kDebug(6200) << "reading2 '"<< (*it).toLatin1().constData() << "'"; |
2534 | #endif |
2535 | copy = it; |
2536 | ++it; |
2537 | } |
2538 | |
2539 | if (it.isEnd()) it = copy; |
2540 | } |
2541 | |
2542 | /** moves the given iterator to the beginning of the previous word. |
2543 | * |
2544 | * If the beginning is reached, the iterator will be positioned there. |
2545 | * @param it character iterator to be moved |
2546 | */ |
2547 | static void moveItToPrevWord(EditableCharacterIterator &it) |
2548 | { |
2549 | if (it.isEnd()) return; |
2550 | |
2551 | #if DEBUG_CARETMODE > 0 |
2552 | kDebug(6200) << "%%%%%%%%%%%%%%%%%%%%% moveItToPrevWord"; |
2553 | #endif |
2554 | EditableCharacterIterator copy; |
2555 | |
2556 | // Jump over all space and punctuation characters first |
2557 | do { |
2558 | copy = it; |
2559 | --it; |
2560 | #if DEBUG_CARETMODE > 2 |
2561 | if (!it.isEnd()) kDebug(6200) << "reading1 '"<< (*it).toLatin1().constData() << "'"; |
2562 | #endif |
2563 | } while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())); |
2564 | |
2565 | if (it.isEnd()) { |
2566 | it = copy; |
2567 | return; |
2568 | }/*end if*/ |
2569 | |
2570 | do { |
2571 | copy = it; |
2572 | --it; |
2573 | #if DEBUG_CARETMODE > 0 |
2574 | if (!it.isEnd()) kDebug(6200) << "reading2 '"<< (*it).toLatin1().constData() << "' ("<< (int)(*it).toLatin1().constData() << ") box "<< it.caretBox(); |
2575 | #endif |
2576 | } while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()); |
2577 | |
2578 | it = copy; |
2579 | #if DEBUG_CARETMODE > 1 |
2580 | if (!it.isEnd()) kDebug(6200) << "effective '"<< (*it).toLatin1().constData() << "' ("<< (int)(*it).toLatin1().constData() << ") box "<< it.caretBox(); |
2581 | #endif |
2582 | } |
2583 | |
2584 | /** moves the iterator by one page. |
2585 | * @param ld linear document |
2586 | * @param it line iterator, will be updated accordingly |
2587 | * @param mindist minimum distance in pixel the iterator should be moved |
2588 | * (if possible) |
2589 | * @param next @p true, move downward, @p false move upward |
2590 | */ |
2591 | static void moveIteratorByPage(LinearDocument &ld, |
2592 | ErgonomicEditableLineIterator &it, int mindist, bool next) |
2593 | { |
2594 | // ### This whole routine plainly sucks. Use an inverse strategie for pgup/pgdn. |
2595 | |
2596 | if (it == ld.end() || it == ld.preBegin()) return; |
2597 | |
2598 | ErgonomicEditableLineIterator copy = it; |
2599 | #if DEBUG_CARETMODE > 0 |
2600 | kDebug(6200) << " mindist: "<< mindist; |
2601 | #endif |
2602 | |
2603 | CaretBoxLine *cbl = *copy; |
2604 | int absx = 0, absy = 0; |
2605 | |
2606 | RenderBlock *lastcb = cbl->containingBlock(); |
2607 | Q_ASSERT(lastcb->isRenderBlock()); |
2608 | lastcb->absolutePosition(absx, absy, false); // ### what about fixed? |
2609 | |
2610 | int lastfby = cbl->begin().data()->yPos(); |
2611 | int lastheight = 0; |
2612 | int rescue = 1000; // ### this is a hack to keep stuck carets from hanging the ua |
2613 | do { |
2614 | if (next) ++copy; else --copy; |
2615 | if (copy == ld.end() || copy == ld.preBegin()) break; |
2616 | |
2617 | cbl = *copy; |
2618 | RenderBlock *cb = cbl->containingBlock(); |
2619 | |
2620 | int diff = 0; |
2621 | // ### actually flowBox->yPos() should suffice, but this is not ported |
2622 | // over yet from WebCore |
2623 | int fby = cbl->begin().data()->yPos(); |
2624 | if (cb != lastcb) { |
2625 | if (next) { |
2626 | diff = absy + lastfby + lastheight; |
2627 | cb->absolutePosition(absx, absy, false); // ### what about fixed? |
2628 | diff = absy - diff + fby; |
2629 | lastfby = 0; |
2630 | } else { |
2631 | diff = absy; |
2632 | cb->absolutePosition(absx, absy, false); // ### what about fixed? |
2633 | diff -= absy + fby + lastheight; |
2634 | lastfby = fby - lastheight; |
2635 | }/*end if*/ |
2636 | #if DEBUG_CARETMODE > 2 |
2637 | kDebug(6200) << "absdiff "<< diff; |
2638 | #endif |
2639 | } else { |
2640 | diff = qAbs(fby - lastfby); |
2641 | }/*end if*/ |
2642 | #if DEBUG_CARETMODE > 2 |
2643 | kDebug(6200) << "cbl->begin().data()->yPos(): "<< fby << " diff "<< diff; |
2644 | #endif |
2645 | |
2646 | mindist -= diff; |
2647 | |
2648 | lastheight = qAbs(fby - lastfby); |
2649 | lastfby = fby; |
2650 | lastcb = cb; |
2651 | it = copy; |
2652 | #if DEBUG_CARETMODE > 0 |
2653 | kDebug(6200) << " mindist: "<< mindist; |
2654 | #endif |
2655 | // trick: actually the distance is always one line short, but we cannot |
2656 | // calculate the height of the first line (### WebCore will make it better) |
2657 | // Therefore, we simply approximate that excess line by using the last |
2658 | // caluculated line height. |
2659 | } while (mindist - lastheight > 0 && --rescue); |
2660 | } |
2661 | |
2662 | |
2663 | }/*end namespace*/ |
2664 |
Warning: That file was not part of the compilation database. It may have many parsing errors.