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