1 /* AbiWord
2 * Copyright (C) 1998,1999 AbiSource, Inc.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA.
18 */
19
20 #include "fl_Squiggles.h"
21
22 #include "ut_debugmsg.h"
23 #include "ut_assert.h"
24 #include "ut_string.h"
25 #include "fv_View.h"
26 #include "pd_Document.h"
27 #include "fp_Run.h"
28
29 /*! \page squiggle_overview Squiggles
30
31 Squiggles are used to underline miss-spelled words. Instead of simply
32 erasing all squiggles and rechecking all words in a block when the
33 block is changed, the squiggles are handled much like the words they
34 underline.
35
36 The word currently being edited is the <b>pending word</b>. When the
37 cursor does not touch the pending word anymore (due to being moved
38 away or the user typing a word separator), the word is
39 spell-checked. If it is miss-spelled, it will be squiggled.
40
41 When text is added to the block, fl_Squiggles::textInserted is called
42 with information of where in the block the text was added, and how
43 much. It will then remove any squiggle located at that offset, move
44 all following squiggles (so they end up aligned with the words they
45 should underline) and spell-checks words in the added text (via
46 fl_BlockLayout::_recalcPendingWord).
47
48 When text is deleted from the block, fl_Squiggles::textDeleted is
49 called with information of where in the block text was deleted, and
50 how much. It removes squiggles intersecting with that area, moves all
51 following squiggles and makes pending the word at the deletion point
52 since two words may have been joined, or a word lost part of its
53 letters.
54
55 When a block is split in two, fl_Squiggles::split is called with
56 information of where the block was split, and a pointer to the new
57 block. The squiggles from the old block are split between it and the
58 new block. The word at the end of the old block (which may have been
59 broken), is spell-checked, and the first word of the new block is made
60 the pending word.
61
62 When two blocks are merged into one, fl_Squiggles::join is called with
63 information of the offset where squiggles from the second block should
64 be joined onto the first block. The word at the merge point is made
65 the pending word.
66
67 \fixme There's one known buglet: typing "gref' " correctly squiggles
68 the word when typing ' since it's a word separator. However,
69 deleting the s in "gref's" leaves gref unsquiggled because the
70 word gref was not pending when the ' changed from a word
71 character to a word delimiter. (hard to explain - just try it)
72
73 */
74
75
76 /*!
77 Constructor
78 \param pOwner The owning block
79 */
fl_Squiggles(fl_BlockLayout * pOwner,FL_SQUIGGLE_TYPE iType)80 fl_Squiggles::fl_Squiggles(fl_BlockLayout* pOwner,FL_SQUIGGLE_TYPE iType) :
81 m_pOwner(pOwner),
82 m_iSquiggleType(iType)
83 {
84 }
85
86 /*!
87 Destructor
88
89 Only purges the vector. It is assumed the squiggles have already been
90 cleared from the screen.
91 */
~fl_Squiggles(void)92 fl_Squiggles::~fl_Squiggles(void)
93 {
94 _purge();
95 }
96
97 /*!
98 Purge squiggles
99 Purges the squiggle list. This does not clear the squiggles
100 on the display.
101 */
102 void
_purge(void)103 fl_Squiggles::_purge(void)
104 {
105 m_vecSquiggles.clear();
106 }
107
108 /*!
109 Find first squiggle after the given offset
110 \param iOffset Offset
111 \result iIndex Index of POB, or index of last POB+1
112 \return True if found, otherwise false
113
114 \note Callers may use the iIndex result even if the search fails:
115 this allows them to look at the last POB on the line which may
116 span the point they are looking for (remember this function
117 finds the squiggle past a given offset, not the one spanning it
118 - there may well be a squiggle spanning a point without there
119 being further squiggles behind it).
120
121 \fixme This function should be rewritten using binary search
122 */
123 bool
_findFirstAfter(UT_sint32 iOffset,UT_sint32 & iIndex) const124 fl_Squiggles::_findFirstAfter(UT_sint32 iOffset, UT_sint32& iIndex) const
125 {
126 bool bRes = false;
127 UT_sint32 iSquiggles = _getCount();
128 UT_sint32 j;
129 for (j = 0; j < iSquiggles; j++)
130 {
131 // Look for the first POB past the offset
132 const fl_PartOfBlockPtr& pPOB = getNth(j);
133 if (pPOB->getOffset() > iOffset)
134 {
135 bRes = true;
136 break;
137 }
138 }
139
140 iIndex = j;
141 return bRes;
142 }
143
144 /*!
145 Find squiggle spanning offset
146 \param iOffset Offset
147 \return Index of squiggle spanning the offset, or -1 if there is no
148 squiggle at the offset.
149 */
150 UT_sint32
_find(UT_sint32 iOffset) const151 fl_Squiggles::_find(UT_sint32 iOffset) const
152 {
153 UT_sint32 i = 0;
154 UT_sint32 iSquiggles = _getCount();
155 for(i=0;i<iSquiggles;i++)
156 {
157 const fl_PartOfBlockPtr& pPOB = getNth(i);
158 xxx_UT_DEBUGMSG((" i %d pob->offset %d pob->length %d offset %d \n",i,pPOB->getOffset(),pPOB->getLength(),iOffset));
159 if((pPOB->getOffset() <= iOffset) && (iOffset <= (pPOB->getOffset() + pPOB->getPTLength())))
160 {
161 break;
162 }
163 }
164 if(i>=iSquiggles)
165 {
166 return -1;
167 }
168 return i;
169 }
170
171 /*!
172 Move squiggles to new block
173 \param iOffset Offset at which to split
174 \param chg Offset change. If >0 it's a new absolute position,
175 if <0 it's relative to the current offset.
176 \param pNewBlock New block the squiggles should be moved to,
177 or NULL to keep them in the current block.
178
179 Move existing squiggles to reflect insert/delete at iOffset.
180 All subsequent squiggles should be switched to (non-null) pBlock.
181
182 \note Only squiggles after the offset are moved. Squiggles spanning
183 the offset must be handled elsewhere.
184 */
185 void
_move(UT_sint32 iOffset,UT_sint32 chg,fl_BlockLayout * pNewBlock)186 fl_Squiggles::_move(UT_sint32 iOffset, UT_sint32 chg,
187 fl_BlockLayout* pNewBlock /* =NULL */)
188 {
189 xxx_UT_DEBUGMSG(("fl_Squiggles::_move(%d, %d, %p)\n",
190 iOffset, chg, pNewBlock));
191
192 UT_sint32 target = (chg > 0) ? iOffset : (iOffset - chg);
193
194 UT_sint32 iSquiggles = _getCount();
195 UT_sint32 j;
196 for (j = iSquiggles-1; j >= 0; j--)
197 {
198 const fl_PartOfBlockPtr& pPOB = getNth(j);
199
200 // Only interested in squiggles after change, and since they
201 // are sorted, stop searching when first one before target is
202 // found.
203 if (pPOB->getOffset() < target) break;
204
205 // Clear the squiggle before moving it
206 clear(pPOB);
207 pPOB->setOffset(pPOB->getOffset() + chg);
208
209 // Move squiggle to another block if requested
210 if (pNewBlock)
211 {
212 UT_ASSERT(pNewBlock != m_pOwner);
213 pNewBlock->getSpellSquiggles()->add(pPOB);
214 m_vecSquiggles.erase(m_vecSquiggles.begin() + j);
215 }
216 }
217 }
218
219 /*!
220 * Update the offsets in the POB's. We shifts the offsets around after text
221 * inside an emebdded section (like a footnote is changed).
222 \param iFirstOffset this is the first offset that is changed.
223 \param iShift this is the amount that the text is shifted.
224 */
updatePOBs(UT_sint32 iFirstOffset,UT_sint32 iShift)225 void fl_Squiggles::updatePOBs(UT_sint32 iFirstOffset, UT_sint32 iShift)
226 {
227 for (decltype(m_vecSquiggles)::size_type i = 0; i < m_vecSquiggles.size(); i++)
228 {
229 const fl_PartOfBlockPtr& pPOB = m_vecSquiggles.at(i);
230 if(pPOB->getOffset() >= iFirstOffset)
231 {
232 pPOB->setOffset(pPOB->getOffset() + iShift);
233 }
234 }
235 }
236
237 /*!
238 Add squiggle
239 \param POB for squiggle
240 Insert POB sorted by offset in vector.
241 */
242 void
add(const fl_PartOfBlockPtr & pPOB)243 fl_Squiggles::add(const fl_PartOfBlockPtr& pPOB)
244 {
245 xxx_UT_DEBUGMSG(("fl_Squiggles::add(%p) [%d:%d]\n", pPOB,
246 pPOB->getOffset(),
247 pPOB->getOffset() + pPOB->getLength()));
248 UT_ASSERT(pPOB->getOffset() >= 0);
249 UT_sint32 iIndex;
250
251 if (_findFirstAfter(pPOB->getOffset(), iIndex))
252 {
253 m_vecSquiggles.insert(m_vecSquiggles.begin() + iIndex, pPOB);
254 }
255 else
256 {
257 m_vecSquiggles.push_back(pPOB);
258 }
259 // Handle extension / merging of squiggles
260 if (iIndex > 0)
261 {
262 const fl_PartOfBlockPtr& pPrev = getNth(iIndex-1);
263
264 if (pPOB->getOffset() == pPrev->getOffset() && (getSquiggleType() == FL_SQUIGGLE_SPELL))
265 {
266 // Handle extension of existing squiggles. This happens
267 // because ' changes from being a word separator to not
268 // being one when characters are added after it. So while
269 // typing "gest'" >gest< will be squiggled, and after
270 // "gest's" the entire >gest's< is squiggled.
271 pPrev->setPTLength(pPOB->getPTLength());
272 _deleteNth(iIndex--);
273 markForRedraw(pPrev);
274 }
275 else if ((pPOB->getOffset() == pPrev->getOffset() + pPrev->getPTLength()) &&
276 (getSquiggleType() == FL_SQUIGGLE_SPELL))
277 {
278 // Handle merging of two squiggles - this happens e.g. in
279 // overwrite mode when two misspelled words are joined by
280 // typing a character ' between them.
281 pPrev->setPTLength(pPrev->getPTLength() + pPOB->getPTLength());
282 _deleteNth(iIndex--);
283 markForRedraw(pPrev);
284 }
285 else
286 {
287 markForRedraw(pPOB);
288 }
289
290 }
291 else
292 {
293 markForRedraw(pPOB);
294 }
295
296 #ifdef DEBUG
297 UT_sint32 iSquiggles = _getCount();
298 if (iSquiggles <= 1) return;
299 if(getSquiggleType() == FL_SQUIGGLE_SPELL)
300 {
301 //
302 // Grammar squiggles can over lap.
303 //
304 if (iIndex > 0)
305 {
306 UT_ASSERT((getNth(iIndex-1)->getOffset() + getNth(iIndex-1)->getPTLength())
307 < getNth(iIndex)->getOffset());
308 }
309 if (iSquiggles > (iIndex+1))
310 {
311 UT_ASSERT((getNth(iIndex)->getOffset() + getNth(iIndex)->getPTLength())
312 < getNth(iIndex+1)->getOffset());
313 }
314 }
315 #endif
316 }
317
318 /*!
319 Delete Nth squiggle
320 \param iIndex Index of squiggle to delete
321 Clear squiggle from screen and g_free the POB's memory
322 */
323 void
_deleteNth(UT_sint32 iIndex)324 fl_Squiggles::_deleteNth(UT_sint32 iIndex)
325 {
326 xxx_UT_DEBUGMSG(("fl_Squiggles::delelteNth(%d)\n", iIndex));
327 const fl_PartOfBlockPtr& pPOB = getNth(iIndex);
328 clear(pPOB);
329 m_vecSquiggles.erase(m_vecSquiggles.begin() + iIndex);
330 }
331
332
333 /*!
334 Delete all squiggles
335 \return True if display should be updated, otherwise false
336 Clear all squiggles from display, and purge the list.
337 */
338 bool
deleteAll(void)339 fl_Squiggles::deleteAll(void)
340 {
341 xxx_UT_DEBUGMSG(("fl_Squiggles::deleteAll()\n"));
342
343 // Remove any existing squiggles from the screen...
344 UT_sint32 iSquiggles = _getCount();
345 UT_sint32 j;
346 for (j = iSquiggles-1; j >= 0 ; j--)
347 {
348 _deleteNth(j);
349 }
350
351 return (0 == iSquiggles) ? false : true;
352 }
353
354 /*!
355 Delete squiggle at offset
356 \param iOffset Offset
357 \return True if a squiggle was deleted, otherwise false
358
359 If a squiggle spans the offset, delete it.
360 */
361 bool
_deleteAtOffset(UT_sint32 iOffset)362 fl_Squiggles::_deleteAtOffset(UT_sint32 iOffset)
363 {
364 xxx_UT_DEBUGMSG(("fl_Squiggles::_deleteAtOffset(%d)\n", iOffset));
365
366 bool res = false;
367 if(getSquiggleType() == FL_SQUIGGLE_GRAMMAR)
368 {
369 UT_sint32 i = 0;
370 UT_sint32 iLow = 0;
371 UT_sint32 iHigh = 0;
372 for(i=0; i< _getCount();)
373 {
374 const fl_PartOfBlockPtr& pPOB = getNth(i);
375 if(pPOB->isInvisible() && ((pPOB->getOffset() <= iOffset) &&
376 pPOB->getOffset()+ pPOB->getPTLength() >= iOffset))
377 {
378 iLow = pPOB->getOffset();
379 iHigh = pPOB->getOffset() + pPOB->getPTLength();
380 }
381 if(iOffset >= iLow && iOffset <= iHigh)
382 {
383 _deleteNth(i);
384 res = true;
385 }
386 else
387 {
388 i++;
389 }
390 }
391 }
392 if(res)
393 return res;
394 UT_sint32 iIndex = _find(iOffset);
395 if (iIndex >= 0)
396 {
397 _deleteNth(iIndex);
398 res = true;
399 }
400
401 return res;
402 }
403
404
405 /*!
406 * Mark all the runs overlapping with the POB for Redraw.
407 */
markForRedraw(const fl_PartOfBlockPtr & pPOB)408 void fl_Squiggles::markForRedraw(const fl_PartOfBlockPtr& pPOB)
409 {
410 PT_DocPosition pos1 = pPOB->getOffset();
411 PT_DocPosition pos2 = pos1 + pPOB->getPTLength();
412 //
413 // Make sure the runs in this POB get redrawn.
414 //
415 fp_Run * pRun = m_pOwner->getFirstRun();
416 while(pRun && (pRun->getBlockOffset() <= pos2))
417 {
418 if((pRun->getBlockOffset() + pRun->getLength()) >= pos1)
419 {
420 pRun->markAsDirty();
421 }
422 pRun = pRun->getNextRun();
423 }
424 }
425
426 /*!
427 Get squiggle at offset
428 \param iOffset Offset
429 \return pointer to POB (maybe NULL if there is no squiggle at the offset)
430 */
431 fl_PartOfBlockPtr
get(UT_sint32 iOffset) const432 fl_Squiggles::get(UT_sint32 iOffset) const
433 {
434 UT_sint32 i = _find(iOffset);
435
436 if (i >= 0) {
437 return getNth(i);
438 }
439 return fl_PartOfBlockPtr();
440 }
441
442 /*!
443 Clear squiggle
444 \param pPOB Part of block to clear squiggle for
445 This clears the squiggle graphics from the screen.
446 */
447 void
clear(const fl_PartOfBlockPtr & pPOB)448 fl_Squiggles::clear(const fl_PartOfBlockPtr& pPOB)
449 {
450 if(!m_pOwner->isOnScreen())
451 {
452 return;
453 }
454 FV_View* pView = m_pOwner->getDocLayout()->getView();
455 PT_DocPosition pos1 = m_pOwner->getPosition() + pPOB->getOffset();
456 PT_DocPosition pos2 = pos1 + pPOB->getPTLength();
457 if(pView->getDocument()->isPieceTableChanging())
458 {
459 //
460 // Make sure the runs in this POB get redrawn.
461 //
462 markForRedraw(pPOB);
463 return;
464 }
465 PT_DocPosition posEOD = 0;
466 m_pOwner->getDocument()->getBounds(true,posEOD);
467 if(pos2 > posEOD)
468 {
469 pos2 = posEOD;
470 }
471 if(pos1 > pos2)
472 {
473 pos1 = pos2 -1;
474 }
475 pView->_clearBetweenPositions(pos1, pos2, true);
476 xxx_UT_DEBUGMSG(("fl_Squiggles::clear posl %d pos2 %d \n", pos1,pos2));
477 }
478
479 /*!
480 Text inserted - update squiggles
481 \param iOffset Location at which insertion happens
482 \param iLength Length of inserted text
483 */
484 void
textInserted(UT_sint32 iOffset,UT_sint32 iLength)485 fl_Squiggles::textInserted(UT_sint32 iOffset, UT_sint32 iLength)
486 {
487 // Ignore operations on shadow blocks
488 if (m_pOwner->isHdrFtr())
489 return;
490
491 // Return if auto spell-checking disabled
492 if (!m_pOwner->getDocLayout()->getAutoSpellCheck())
493 return;
494
495 xxx_UT_DEBUGMSG(("fl_Squiggles::textInserted(%d, %d)\n",
496 iOffset, iLength));
497
498 UT_sint32 chg = iLength;
499
500 // Delete squiggle broken by this insert
501 _deleteAtOffset(iOffset);
502
503 // Move all trailing squiggles
504 _move(iOffset, chg);
505
506 // Deal with pending word, if any
507
508 if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
509 {
510 // If not affected by insert, check it
511 if (!m_pOwner->getDocLayout()->touchesPendingWordForSpell(m_pOwner, iOffset, 0))
512 {
513 const fl_PartOfBlockPtr& pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
514 // If pending word is later in the block, adjust its
515 // offset according to the change
516 if (pPending->getOffset() > iOffset)
517 pPending->setOffset(pPending->getOffset() + chg);
518
519 #if 0
520 m_pOwner->getDocLayout()->checkPendingWordForSpell();
521 #else
522 //
523 // Remove the pending word. Trying to spellcheck it is giving us troubles
524 //
525
526 // What kind of trouble? Please refer a Bug # or symptom when
527 // disabling code like this... Also see Bug 4453 jskov 2003.01.05
528
529 m_pOwner->getDocLayout()->setPendingWordForSpell(NULL,NULL);
530 #endif
531 }
532 }
533
534 // Recheck word at boundary
535 if(getSquiggleType() == FL_SQUIGGLE_SPELL)
536 {
537 m_pOwner->_recalcPendingWord(iOffset, chg);
538 }
539 }
540
541 /*!
542 Text deleted - update squiggles
543 \param iOffset Offset of deletion
544 \param iLength Length of deletion
545 */
546 void
textDeleted(UT_sint32 iOffset,UT_sint32 iLength)547 fl_Squiggles::textDeleted(UT_sint32 iOffset, UT_sint32 iLength)
548 {
549 // Ignore operations on shadow blocks
550 if (m_pOwner->isHdrFtr())
551 return;
552
553 // Return if auto spell-checking disabled
554 if (!m_pOwner->getDocLayout()->getAutoSpellCheck())
555 return;
556
557 xxx_UT_DEBUGMSG(("fl_Squiggles::textDeleted(%d, %d)\n",
558 iOffset, iLength));
559
560 UT_sint32 chg = -(UT_sint32)iLength;
561
562 UT_sint32 iFirst, iLast;
563 if (findRange(iOffset, iOffset+iLength, iFirst, iLast))
564 {
565 while ((iLast >= 0) && (iLast >= iFirst))
566 {
567 _deleteNth(iLast--);
568 }
569 }
570
571 // Move all trailing squiggles
572 _move(iOffset, chg);
573
574 // Deal with pending word, if any
575 if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
576 {
577 // If not affected by delete, check it
578 if (!m_pOwner->getDocLayout()->touchesPendingWordForSpell(m_pOwner, iOffset, chg))
579 {
580 const fl_PartOfBlockPtr& pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
581 // If pending word is later in the block, adjust its
582 // offset according to the change
583 if (pPending->getOffset() > iOffset)
584 pPending->setOffset(pPending->getOffset() + chg);
585
586 #if 0
587 m_pOwner->getDocLayout()->checkPendingWordForSpell();
588 //
589 // Remove the pending word. Trying to spellcheck it is giving us troubles
590 //
591
592 // What kind of trouble? Please refer a Bug # or symptom when
593 // disabling code like this... Also see Bug 4453 jskov 2003.01.05
594 m_pOwner->getDocLayout()->setPendingWordForSpell(NULL,NULL);
595 #endif
596 }
597 }
598
599 // Recheck at boundary
600 if(getSquiggleType() == FL_SQUIGGLE_SPELL)
601 m_pOwner->_recalcPendingWord(iOffset, chg);
602 }
603
604 /*!
605 change of fmt that impacts on spelling (e.g., delete in revisions mode, or undo of delete
606 in revisions mode)
607
608 \param iOffset Location at which insertion happens
609 \param iLength Length of inserted text
610 */
611 void
textRevised(UT_sint32 iOffset,UT_sint32 iLength)612 fl_Squiggles::textRevised(UT_sint32 iOffset, UT_sint32 iLength)
613 {
614 // Ignore operations on shadow blocks
615 if (m_pOwner->isHdrFtr())
616 return;
617
618 // Return if auto spell-checking disabled
619 if (!m_pOwner->getDocLayout()->getAutoSpellCheck())
620 return;
621
622 xxx_UT_DEBUGMSG(("fl_Squiggles::textRevised(%d, %d)\n",
623 iOffset, iLength));
624
625 UT_sint32 chg = iLength;
626
627 // Delete squiggle broken by this insert
628 _deleteAtOffset(iOffset);
629
630 if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
631 {
632 // If not affected by insert, remove it
633 if (!m_pOwner->getDocLayout()->touchesPendingWordForSpell(m_pOwner, iOffset, 0))
634 {
635 //fl_PartOfBlock* pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
636
637 m_pOwner->getDocLayout()->setPendingWordForSpell(NULL,NULL);
638 }
639 }
640
641 // Recheck word at boundary
642 if(getSquiggleType() == FL_SQUIGGLE_SPELL)
643 {
644 m_pOwner->_recalcPendingWord(iOffset, chg);
645 }
646 }
647
648 /*!
649 Split squiggles
650 \param iOffset Offset of split
651 \param pNewBL New block
652
653 Move squiggles after the offset to the new block. If there's a
654 squiggle spanning the offset, delete it.
655
656 If the old block is pending a background spell-check, check
657 both it and the new block.
658
659 Any pending word is forgotten (since we're splitting the word) and
660 the word (if any) at the end of the line is checked, while the word
661 at the start of the new line (if any) is made pending.
662 */
663 void
split(UT_sint32 iOffset,fl_BlockLayout * pNewBL)664 fl_Squiggles::split(UT_sint32 iOffset, fl_BlockLayout* pNewBL)
665 {
666 // Ignore operations on shadow blocks
667 if (m_pOwner->isHdrFtr())
668 return;
669
670 // Return if auto spell-checking disabled
671 if (!m_pOwner->getDocLayout()->getAutoSpellCheck() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
672 return;
673
674 xxx_UT_DEBUGMSG(("fl_Squiggles::split(%d, %p)\n", iOffset, pNewBL));
675
676 // When inserting block break, squiggles move in opposite direction
677 UT_sint32 chg = -(UT_sint32)iOffset;
678
679 // Check pending word - this is necessary to avoid forgetting
680 // words after an undo operation (which undos a block
681 // merge). Unfortunately it makes the word under the cursor
682 // squiggled (if badly spelled) instead of just pending - but it's
683 // hard to do anything about.
684
685 if (m_pOwner->getDocLayout()->isPendingWordForSpell()&& (getSquiggleType() == FL_SQUIGGLE_SPELL) )
686 {
687 const fl_BlockLayout *pBL;
688 const fl_PartOfBlockPtr& pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
689 pBL = m_pOwner->getDocLayout()->getPendingBlockForSpell();
690 // Copy details from pending POB - but don't actually use
691 // the object since it's owned by the code handling the
692 // pending word.
693 fl_PartOfBlockPtr pPOB(new fl_PartOfBlock(pPending->getOffset(),
694 pPending->getPTLength()));
695 // Clear pending word
696 m_pOwner->getDocLayout()->setPendingWordForSpell(NULL, NULL);
697 if (pBL == m_pOwner)
698 {
699 if(pPOB->getOffset() >= iOffset)
700 {
701 // If pending word is in this block after split,
702 // adjust details of the copy
703 pPOB->setOffset(pPOB->getOffset() + chg);
704 pBL = pNewBL;
705 }
706 else if (pPOB->getOffset() + pPOB->getPTLength() > iOffset)
707 {
708 // If pending word spans offset, adjust its length
709 pPOB->setPTLength(iOffset - pPOB->getOffset());
710 }
711 }
712 pBL->checkWord(pPOB);
713 }
714
715 if(getSquiggleType() == FL_SQUIGGLE_SPELL)
716 {
717 if (m_pOwner->getDocLayout()->dequeueBlockForBackgroundCheck(m_pOwner))
718 {
719 // This block was queuing for spell-checking. Do a check of
720 // both blocks, but clear any squiggle added at IP.
721 deleteAll();
722 m_pOwner->checkSpelling();
723 pNewBL->checkSpelling();
724 fl_Squiggles * pSq = pNewBL->getSpellSquiggles();
725 UT_return_if_fail( pSq );
726 pSq->_deleteAtOffset(0);
727 }
728 else
729 {
730 // This block was already spell-checked, so just move the
731 // squiggles around.
732
733 // Remove squiggle broken by this insert
734 _deleteAtOffset(iOffset);
735
736 // Move all following squiggles to the new block
737 _move(0, chg, pNewBL);
738
739 // Find bounds of word at end of this block and check it.
740 // Use _recalcPendingWord which is a bit overkill, but gets
741 // the job done.
742 if(getSquiggleType() == FL_SQUIGGLE_SPELL) {
743 m_pOwner->_recalcPendingWord(iOffset, 0);
744 }
745 if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
746 {
747 const fl_PartOfBlockPtr& pPending =
748 m_pOwner->getDocLayout()->getPendingWordForSpell();
749 // Copy details from pending POB - but don't actually use
750 // the object since it's owned by the code handling the
751 // pending word.
752 fl_PartOfBlockPtr pPOB(new fl_PartOfBlock(pPending->getOffset(),
753 pPending->getPTLength()));
754 m_pOwner->getDocLayout()->setPendingWordForSpell(NULL, NULL);
755 m_pOwner->checkWord(pPOB);
756 }
757 }
758 m_pOwner->getDocLayout()->setPendingBlockForGrammar(m_pOwner);
759 }
760
761 // Set start of new block to be pending word.
762 if(getSquiggleType() == FL_SQUIGGLE_SPELL)
763 pNewBL->_recalcPendingWord(0, 0);
764 }
765
766
767 /*!
768 Join squiggles
769 \param iOffset Offset to where squiggles are moved
770 \param pPrevBlock Block they should be moved to
771
772 This function is called when a paragrah break is deleted and two
773 blocks are joined.
774
775 If either block is pending a background spell-check, the combined
776 block is checked in full.
777
778 Any squiggle touching the IP is deleted and the word touching the IP
779 becomes the pending word. The previously pending word, if any,
780 becomes irrelevant.
781
782 */
783 void
join(UT_sint32 iOffset,fl_BlockLayout * pPrevBL)784 fl_Squiggles::join(UT_sint32 iOffset, fl_BlockLayout* pPrevBL)
785 {
786 // Ignore operations on shadow blocks
787 if (m_pOwner->isHdrFtr())
788 return;
789
790 // Return if auto spell-checking disabled
791 if (!m_pOwner->getDocLayout()->getAutoSpellCheck() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
792 return;
793
794 xxx_UT_DEBUGMSG(("fl_Squiggles::join(%d, %p)\n", iOffset, pPrevBL));
795
796 bool bFullCheck = m_pOwner->getDocLayout()->dequeueBlockForBackgroundCheck(m_pOwner);
797 bFullCheck |= m_pOwner->getDocLayout()->dequeueBlockForBackgroundCheck(pPrevBL);
798
799 if (bFullCheck)
800 {
801 // This or the previous block was queuing for
802 // spell-checking. Clear all existing squiggles and do a check
803 // of the combined block.
804 deleteAll();
805 pPrevBL->getSpellSquiggles()->deleteAll();
806 pPrevBL->checkSpelling();
807 }
808 else
809 {
810 // If there is a squiggle first in this block, delete it to
811 // prevent having to handle a merge with a squiggle last in
812 // the previous block (which we could, but only to delete it
813 // below).
814 _deleteAtOffset(0);
815 // Move all squiggles from this block to the previous block.
816 _move(0, iOffset, pPrevBL);
817 }
818 m_pOwner->getDocLayout()->setPendingBlockForGrammar(m_pOwner);
819
820 // Delete squiggle touching IP
821 if(getSquiggleType() == FL_SQUIGGLE_SPELL)
822 {
823 fl_Squiggles * pSq = pPrevBL->getSpellSquiggles();
824 UT_return_if_fail( pSq );
825
826 pSq->_deleteAtOffset(iOffset);
827
828 // Update pending word
829
830 pPrevBL->_recalcPendingWord(iOffset, 0);
831 }
832 }
833
834 /*!
835 Find squiggles intersecting with region
836 \param iStart Start offset of region
837 \param iEnd End offset of region
838 \result iFirst Index of first squiggle intersecting with region
839 \result iLast Index of last squiggle intersecting with region
840 \return True if range is not empty, otherwise false
841 */
842 bool
findRange(UT_sint32 iStart,UT_sint32 iEnd,UT_sint32 & iFirst,UT_sint32 & iLast,bool bDontExpand) const843 fl_Squiggles::findRange(UT_sint32 iStart, UT_sint32 iEnd,
844 UT_sint32& iFirst, UT_sint32& iLast, bool bDontExpand) const
845 {
846 xxx_UT_DEBUGMSG(("fl_Squiggles::findRange(%d, %d)\n", iStart, iEnd));
847
848 UT_sint32 iSquiggles = _getCount();
849 if (0 == iSquiggles) return false;
850 UT_sint32 s, e;
851
852 if((getSquiggleType() == FL_SQUIGGLE_GRAMMAR) && !bDontExpand)
853 {
854 // Grammar squiggles are preceded by a POB that covers the whole of the sentence.
855 // Expand the end point to cover it.
856
857 UT_sint32 i = 0;
858 for(i=0; i< iSquiggles;i++)
859 {
860 const fl_PartOfBlockPtr& pPOB = getNth(i);
861 if((iStart >= pPOB->getOffset()) && (iStart <= pPOB->getOffset() + pPOB->getPTLength()) && pPOB->isInvisible())
862 {
863 iStart = pPOB->getOffset();
864 }
865 if((iEnd >= pPOB->getOffset()) && (iEnd <= pPOB->getOffset() + pPOB->getPTLength()) && pPOB->isInvisible())
866 {
867 iEnd = pPOB->getOffset() + pPOB->getPTLength();
868 }
869 }
870 }
871 // Look for the first POB.start that is higher than the end offset
872 _findFirstAfter(iEnd, e);
873 // Note that the return value is not checked: either there is no
874 // POBs at all, in which case we'll catch it in the statement
875 // below (first POB's offset past end point). Otherwise we want to
876 // look at the last POB on the line since it may span past
877 // iOffset.
878
879 // Return with empty set if the offset of the first POB on the
880 // line is higher than the region end (i.e. there is no previous
881 // POB that could span the region end).
882 if (0 == e)
883 {
884 UT_ASSERT(getNth(0)->getOffset() > iEnd);
885 return false;
886 }
887 // Adjust to be the first POB inside the region.
888 e--;
889
890 // FIXME: this should also use _findFirstAfter
891 // Look for the last POB.end that is lower than the start offset
892 for (s = e; s >= 0; s--)
893 {
894 const fl_PartOfBlockPtr& pPOB = getNth(s);
895 if ((pPOB->getOffset() + pPOB->getPTLength()) < iStart) {
896 break;
897 }
898 }
899 // Return with empty set if the last POB's end offset is lower
900 // than the region start.
901 if (s == e)
902 {
903 // this assert is impossible anyway
904 //UT_ASSERT((pPOB->getOffset() + pPOB->getPTLength()) < iStart);
905 return false;
906 }
907 //Adjust to be the first POB inside the region
908 s++;
909 UT_ASSERT(s >= 0 && s < iSquiggles);
910 UT_ASSERT(e >= 0 && e < iSquiggles);
911 UT_ASSERT(s <= e);
912
913 iFirst = s;
914 iLast = e;
915
916 return true;
917 }
918
919
920 /*!
921 Recheck ignored words
922 \param pBlockText The block's text
923 \return True if any words squiggled, false otherwise
924 */
925 bool
recheckIgnoredWords(const UT_UCSChar * pBlockText)926 fl_Squiggles::recheckIgnoredWords(const UT_UCSChar* pBlockText)
927 {
928 xxx_UT_DEBUGMSG(("fl_Squiggles::recheckIgnoredWords(%p)\n", pBlockText));
929
930 bool bUpdate = false;
931
932 UT_sint32 iSquiggles = (UT_sint32) _getCount();
933 UT_sint32 i;
934 for (i = iSquiggles-1; i >= 0; i--)
935 {
936 const fl_PartOfBlockPtr& pPOB = getNth((UT_uint32) i);
937
938 if (m_pOwner->_doCheckWord(pPOB, pBlockText, false))
939 {
940 // Word squiggled
941 bUpdate = true;
942 }
943 else
944 {
945 // Word not squiggled, remove from squiggle list
946 _deleteNth((UT_uint32) i);
947 }
948 }
949
950 return bUpdate;
951 }
952
fl_SpellSquiggles(fl_BlockLayout * pOwner)953 fl_SpellSquiggles::fl_SpellSquiggles(fl_BlockLayout* pOwner) : fl_Squiggles(pOwner,FL_SQUIGGLE_SPELL)
954 {
955 }
956
957
fl_GrammarSquiggles(fl_BlockLayout * pOwner)958 fl_GrammarSquiggles::fl_GrammarSquiggles(fl_BlockLayout* pOwner) : fl_Squiggles(pOwner,FL_SQUIGGLE_GRAMMAR)
959 {
960 }
961
962