/* AbiWord
* Copyright (C) 1998,1999 AbiSource, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "fl_Squiggles.h"
#include "ut_debugmsg.h"
#include "ut_assert.h"
#include "ut_string.h"
#include "fv_View.h"
#include "pd_Document.h"
#include "fp_Run.h"
/*! \page squiggle_overview Squiggles
Squiggles are used to underline miss-spelled words. Instead of simply
erasing all squiggles and rechecking all words in a block when the
block is changed, the squiggles are handled much like the words they
underline.
The word currently being edited is the pending word. When the
cursor does not touch the pending word anymore (due to being moved
away or the user typing a word separator), the word is
spell-checked. If it is miss-spelled, it will be squiggled.
When text is added to the block, fl_Squiggles::textInserted is called
with information of where in the block the text was added, and how
much. It will then remove any squiggle located at that offset, move
all following squiggles (so they end up aligned with the words they
should underline) and spell-checks words in the added text (via
fl_BlockLayout::_recalcPendingWord).
When text is deleted from the block, fl_Squiggles::textDeleted is
called with information of where in the block text was deleted, and
how much. It removes squiggles intersecting with that area, moves all
following squiggles and makes pending the word at the deletion point
since two words may have been joined, or a word lost part of its
letters.
When a block is split in two, fl_Squiggles::split is called with
information of where the block was split, and a pointer to the new
block. The squiggles from the old block are split between it and the
new block. The word at the end of the old block (which may have been
broken), is spell-checked, and the first word of the new block is made
the pending word.
When two blocks are merged into one, fl_Squiggles::join is called with
information of the offset where squiggles from the second block should
be joined onto the first block. The word at the merge point is made
the pending word.
\fixme There's one known buglet: typing "gref' " correctly squiggles
the word when typing ' since it's a word separator. However,
deleting the s in "gref's" leaves gref unsquiggled because the
word gref was not pending when the ' changed from a word
character to a word delimiter. (hard to explain - just try it)
*/
/*!
Constructor
\param pOwner The owning block
*/
fl_Squiggles::fl_Squiggles(fl_BlockLayout* pOwner,FL_SQUIGGLE_TYPE iType) :
m_pOwner(pOwner),
m_iSquiggleType(iType)
{
}
/*!
Destructor
Only purges the vector. It is assumed the squiggles have already been
cleared from the screen.
*/
fl_Squiggles::~fl_Squiggles(void)
{
_purge();
}
/*!
Purge squiggles
Purges the squiggle list. This does not clear the squiggles
on the display.
*/
void
fl_Squiggles::_purge(void)
{
UT_VECTOR_PURGEALL(fl_PartOfBlock *, m_vecSquiggles);
m_vecSquiggles.clear();
}
/*!
Find first squiggle after the given offset
\param iOffset Offset
\result iIndex Index of POB, or index of last POB+1
\return True if found, otherwise false
\note Callers may use the iIndex result even if the search fails:
this allows them to look at the last POB on the line which may
span the point they are looking for (remember this function
finds the squiggle past a given offset, not the one spanning it
- there may well be a squiggle spanning a point without there
being further squiggles behind it).
\fixme This function should be rewritten using binary search
*/
bool
fl_Squiggles::_findFirstAfter(UT_sint32 iOffset, UT_sint32& iIndex) const
{
bool bRes = false;
UT_sint32 iSquiggles = _getCount();
fl_PartOfBlock* pPOB;
UT_sint32 j;
for (j = 0; j < iSquiggles; j++)
{
// Look for the first POB past the offset
pPOB = getNth(j);
if (pPOB->getOffset() > iOffset)
{
bRes = true;
break;
}
}
iIndex = j;
return bRes;
}
/*!
Find squiggle spanning offset
\param iOffset Offset
\return Index of squiggle spanning the offset, or -1 if there is no
squiggle at the offset.
*/
UT_sint32
fl_Squiggles::_find(UT_sint32 iOffset) const
{
UT_sint32 i = 0;
UT_sint32 iSquiggles = _getCount();
fl_PartOfBlock* pPOB;
for(i=0;ioffset %d pob->length %d offset %d \n",i,pPOB->getOffset(),pPOB->getLength(),iOffset));
if((pPOB->getOffset() <= iOffset) && (iOffset <= (pPOB->getOffset() + pPOB->getPTLength())))
{
break;
}
}
if(i>=iSquiggles)
{
return -1;
}
return i;
}
/*!
Move squiggles to new block
\param iOffset Offset at which to split
\param chg Offset change. If >0 it's a new absolute position,
if <0 it's relative to the current offset.
\param pNewBlock New block the squiggles should be moved to,
or NULL to keep them in the current block.
Move existing squiggles to reflect insert/delete at iOffset.
All subsequent squiggles should be switched to (non-null) pBlock.
\note Only squiggles after the offset are moved. Squiggles spanning
the offset must be handled elsewhere.
*/
void
fl_Squiggles::_move(UT_sint32 iOffset, UT_sint32 chg,
fl_BlockLayout* pNewBlock /* =NULL */)
{
xxx_UT_DEBUGMSG(("fl_Squiggles::_move(%d, %d, %p)\n",
iOffset, chg, pNewBlock));
UT_sint32 target = (chg > 0) ? iOffset : (iOffset - chg);
UT_sint32 iSquiggles = _getCount();
UT_sint32 j;
for (j = iSquiggles-1; j >= 0; j--)
{
fl_PartOfBlock* pPOB = getNth(j);
// Only interested in squiggles after change, and since they
// are sorted, stop searching when first one before target is
// found.
if (pPOB->getOffset() < target) break;
// Clear the squiggle before moving it
clear(pPOB);
pPOB->setOffset(pPOB->getOffset() + chg);
// Move squiggle to another block if requested
if (pNewBlock)
{
UT_ASSERT(pNewBlock != m_pOwner);
pNewBlock->getSpellSquiggles()->add(pPOB);
m_vecSquiggles.deleteNthItem(j);
}
}
}
/*!
* Update the offsets in the POB's. We shifts the offsets around after text
* inside an emebdded section (like a footnote is changed).
\param iFirstOffset this is the first offset that is changed.
\param iShift this is the amount that the text is shifted.
*/
void fl_Squiggles::updatePOBs(UT_sint32 iFirstOffset, UT_sint32 iShift)
{
UT_sint32 i =0;
for(i=0; i< m_vecSquiggles.getItemCount(); i++)
{
fl_PartOfBlock * pPOB = (fl_PartOfBlock *) m_vecSquiggles.getNthItem(i);
if(pPOB->getOffset() >= iFirstOffset)
{
pPOB->setOffset(pPOB->getOffset() + iShift);
}
}
}
/*!
Add squiggle
\param POB for squiggle
Insert POB sorted by offset in vector.
*/
void
fl_Squiggles::add(fl_PartOfBlock* pPOB)
{
xxx_UT_DEBUGMSG(("fl_Squiggles::add(%p) [%d:%d]\n", pPOB,
pPOB->getOffset(),
pPOB->getOffset() + pPOB->getLength()));
UT_ASSERT(pPOB->getOffset() >= 0);
UT_sint32 iIndex;
if (_findFirstAfter(pPOB->getOffset(), iIndex))
{
m_vecSquiggles.insertItemAt(pPOB, iIndex);
}
else
{
m_vecSquiggles.addItem(pPOB);
}
// Handle extension / merging of squiggles
if (iIndex > 0)
{
fl_PartOfBlock* pPrev = getNth(iIndex-1);
if (pPOB->getOffset() == pPrev->getOffset() && (getSquiggleType() == FL_SQUIGGLE_SPELL))
{
// Handle extension of existing squiggles. This happens
// because ' changes from being a word separator to not
// being one when characters are added after it. So while
// typing "gest'" >gest< will be squiggled, and after
// "gest's" the entire >gest's< is squiggled.
pPrev->setPTLength(pPOB->getPTLength());
_deleteNth(iIndex--);
markForRedraw(pPrev);
}
else if ((pPOB->getOffset() == pPrev->getOffset() + pPrev->getPTLength()) &&
(getSquiggleType() == FL_SQUIGGLE_SPELL))
{
// Handle merging of two squiggles - this happens e.g. in
// overwrite mode when two misspelled words are joined by
// typing a character ' between them.
pPrev->setPTLength(pPrev->getPTLength() + pPOB->getPTLength());
_deleteNth(iIndex--);
markForRedraw(pPrev);
}
else
{
markForRedraw(pPOB);
}
}
else
{
markForRedraw(pPOB);
}
#ifdef DEBUG
UT_sint32 iSquiggles = _getCount();
if (iSquiggles <= 1) return;
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
{
//
// Grammar squiggles can over lap.
//
if (iIndex > 0)
{
UT_ASSERT((getNth(iIndex-1)->getOffset() + getNth(iIndex-1)->getPTLength())
< getNth(iIndex)->getOffset());
}
if (iSquiggles > (iIndex+1))
{
UT_ASSERT((getNth(iIndex)->getOffset() + getNth(iIndex)->getPTLength())
< getNth(iIndex+1)->getOffset());
}
}
#endif
}
/*!
Delete Nth squiggle
\param iIndex Index of squiggle to delete
Clear squiggle from screen and g_free the POB's memory
*/
void
fl_Squiggles::_deleteNth(UT_sint32 iIndex)
{
xxx_UT_DEBUGMSG(("fl_Squiggles::delelteNth(%d)\n", iIndex));
fl_PartOfBlock* pPOB = getNth(iIndex);
m_vecSquiggles.deleteNthItem(iIndex);
clear(pPOB);
delete pPOB;
}
/*!
Delete all squiggles
\return True if display should be updated, otherwise false
Clear all squiggles from display, and purge the list.
*/
bool
fl_Squiggles::deleteAll(void)
{
xxx_UT_DEBUGMSG(("fl_Squiggles::deleteAll()\n"));
// Remove any existing squiggles from the screen...
UT_sint32 iSquiggles = _getCount();
UT_sint32 j;
for (j = iSquiggles-1; j >= 0 ; j--)
{
_deleteNth(j);
}
return (0 == iSquiggles) ? false : true;
}
/*!
Delete squiggle at offset
\param iOffset Offset
\return True if a squiggle was deleted, otherwise false
If a squiggle spans the offset, delete it.
*/
bool
fl_Squiggles::_deleteAtOffset(UT_sint32 iOffset)
{
xxx_UT_DEBUGMSG(("fl_Squiggles::_deleteAtOffset(%d)\n", iOffset));
bool res = false;
if(getSquiggleType() == FL_SQUIGGLE_GRAMMAR)
{
fl_PartOfBlock* pPOB = 0;
UT_sint32 i = 0;
UT_sint32 iLow = 0;
UT_sint32 iHigh = 0;
for(i=0; i< _getCount();)
{
pPOB = getNth(i);
if(pPOB->isInvisible() && ((pPOB->getOffset() <= iOffset) &&
pPOB->getOffset()+ pPOB->getPTLength() >= iOffset))
{
iLow = pPOB->getOffset();
iHigh = pPOB->getOffset() + pPOB->getPTLength();
}
if(iOffset >= iLow && iOffset <= iHigh)
{
_deleteNth(i);
res = true;
}
else
{
i++;
}
}
}
if(res)
return res;
UT_sint32 iIndex = _find(iOffset);
if (iIndex >= 0)
{
_deleteNth(iIndex);
res = true;
}
return res;
}
/*!
* Mark all the runs overlapping with the POB for Redraw.
*/
void fl_Squiggles::markForRedraw(fl_PartOfBlock* pPOB)
{
PT_DocPosition pos1 = pPOB->getOffset();
PT_DocPosition pos2 = pos1 + pPOB->getPTLength();
//
// Make sure the runs in this POB get redrawn.
//
fp_Run * pRun = m_pOwner->getFirstRun();
while(pRun && (pRun->getBlockOffset() <= pos2))
{
if((pRun->getBlockOffset() + pRun->getLength()) >= pos1)
{
pRun->markAsDirty();
}
pRun = pRun->getNextRun();
}
}
/*!
Get squiggle at offset
\param iOffset Offset
\return Pointer to POB or NULL if there is no squiggle at the offset
*/
fl_PartOfBlock*
fl_Squiggles::get(UT_sint32 iOffset) const
{
fl_PartOfBlock* pPOB = NULL;
UT_sint32 i = _find(iOffset);
if (i >= 0)
pPOB = getNth(i);
return pPOB;
}
/*!
Clear squiggle
\param pPOB Part of block to clear squiggle for
This clears the squiggle graphics from the screen.
*/
void
fl_Squiggles::clear(fl_PartOfBlock* pPOB)
{
if(!m_pOwner->isOnScreen())
{
return;
}
FV_View* pView = m_pOwner->getDocLayout()->getView();
PT_DocPosition pos1 = m_pOwner->getPosition() + pPOB->getOffset();
PT_DocPosition pos2 = pos1 + pPOB->getPTLength();
if(pView->getDocument()->isPieceTableChanging())
{
//
// Make sure the runs in this POB get redrawn.
//
markForRedraw(pPOB);
return;
}
PT_DocPosition posEOD = 0;
m_pOwner->getDocument()->getBounds(true,posEOD);
if(pos2 > posEOD)
{
pos2 = posEOD;
}
if(pos1 > pos2)
{
pos1 = pos2 -1;
}
pView->_clearBetweenPositions(pos1, pos2, true);
xxx_UT_DEBUGMSG(("fl_Squiggles::clear posl %d pos2 %d \n", pos1,pos2));
}
/*!
Text inserted - update squiggles
\param iOffset Location at which insertion happens
\param iLength Length of inserted text
*/
void
fl_Squiggles::textInserted(UT_sint32 iOffset, UT_sint32 iLength)
{
// Ignore operations on shadow blocks
if (m_pOwner->isHdrFtr())
return;
// Return if auto spell-checking disabled
if (!m_pOwner->getDocLayout()->getAutoSpellCheck())
return;
xxx_UT_DEBUGMSG(("fl_Squiggles::textInserted(%d, %d)\n",
iOffset, iLength));
UT_sint32 chg = iLength;
// Delete squiggle broken by this insert
_deleteAtOffset(iOffset);
// Move all trailing squiggles
_move(iOffset, chg);
// Deal with pending word, if any
if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
{
// If not affected by insert, check it
if (!m_pOwner->getDocLayout()->touchesPendingWordForSpell(m_pOwner, iOffset, 0))
{
fl_PartOfBlock* pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
// If pending word is later in the block, adjust its
// offset according to the change
if (pPending->getOffset() > iOffset)
pPending->setOffset(pPending->getOffset() + chg);
#if 0
m_pOwner->getDocLayout()->checkPendingWordForSpell();
#else
//
// Remove the pending word. Trying to spellcheck it is giving us troubles
//
// What kind of trouble? Please refer a Bug # or symptom when
// disabling code like this... Also see Bug 4453 jskov 2003.01.05
m_pOwner->getDocLayout()->setPendingWordForSpell(NULL,NULL);
#endif
}
}
// Recheck word at boundary
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
{
m_pOwner->_recalcPendingWord(iOffset, chg);
}
}
/*!
Text deleted - update squiggles
\param iOffset Offset of deletion
\param iLength Length of deletion
*/
void
fl_Squiggles::textDeleted(UT_sint32 iOffset, UT_sint32 iLength)
{
// Ignore operations on shadow blocks
if (m_pOwner->isHdrFtr())
return;
// Return if auto spell-checking disabled
if (!m_pOwner->getDocLayout()->getAutoSpellCheck())
return;
xxx_UT_DEBUGMSG(("fl_Squiggles::textDeleted(%d, %d)\n",
iOffset, iLength));
UT_sint32 chg = -(UT_sint32)iLength;
UT_sint32 iFirst, iLast;
if (findRange(iOffset, iOffset+iLength, iFirst, iLast))
{
while ((iLast >= 0) && (iLast >= iFirst))
{
_deleteNth(iLast--);
}
}
// Move all trailing squiggles
_move(iOffset, chg);
// Deal with pending word, if any
if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
{
// If not affected by delete, check it
if (!m_pOwner->getDocLayout()->touchesPendingWordForSpell(m_pOwner, iOffset, chg))
{
fl_PartOfBlock* pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
// If pending word is later in the block, adjust its
// offset according to the change
if (pPending->getOffset() > iOffset)
pPending->setOffset(pPending->getOffset() + chg);
#if 0
m_pOwner->getDocLayout()->checkPendingWordForSpell();
//
// Remove the pending word. Trying to spellcheck it is giving us troubles
//
// What kind of trouble? Please refer a Bug # or symptom when
// disabling code like this... Also see Bug 4453 jskov 2003.01.05
m_pOwner->getDocLayout()->setPendingWordForSpell(NULL,NULL);
#endif
}
}
// Recheck at boundary
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
m_pOwner->_recalcPendingWord(iOffset, chg);
}
/*!
change of fmt that impacts on spelling (e.g., delete in revisions mode, or undo of delete
in revisions mode)
\param iOffset Location at which insertion happens
\param iLength Length of inserted text
*/
void
fl_Squiggles::textRevised(UT_sint32 iOffset, UT_sint32 iLength)
{
// Ignore operations on shadow blocks
if (m_pOwner->isHdrFtr())
return;
// Return if auto spell-checking disabled
if (!m_pOwner->getDocLayout()->getAutoSpellCheck())
return;
xxx_UT_DEBUGMSG(("fl_Squiggles::textRevised(%d, %d)\n",
iOffset, iLength));
UT_sint32 chg = iLength;
// Delete squiggle broken by this insert
_deleteAtOffset(iOffset);
if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
{
// If not affected by insert, remove it
if (!m_pOwner->getDocLayout()->touchesPendingWordForSpell(m_pOwner, iOffset, 0))
{
//fl_PartOfBlock* pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
m_pOwner->getDocLayout()->setPendingWordForSpell(NULL,NULL);
}
}
// Recheck word at boundary
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
{
m_pOwner->_recalcPendingWord(iOffset, chg);
}
}
/*!
Split squiggles
\param iOffset Offset of split
\param pNewBL New block
Move squiggles after the offset to the new block. If there's a
squiggle spanning the offset, delete it.
If the old block is pending a background spell-check, check
both it and the new block.
Any pending word is forgotten (since we're splitting the word) and
the word (if any) at the end of the line is checked, while the word
at the start of the new line (if any) is made pending.
*/
void
fl_Squiggles::split(UT_sint32 iOffset, fl_BlockLayout* pNewBL)
{
// Ignore operations on shadow blocks
if (m_pOwner->isHdrFtr())
return;
// Return if auto spell-checking disabled
if (!m_pOwner->getDocLayout()->getAutoSpellCheck() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
return;
xxx_UT_DEBUGMSG(("fl_Squiggles::split(%d, %p)\n", iOffset, pNewBL));
// When inserting block break, squiggles move in opposite direction
UT_sint32 chg = -(UT_sint32)iOffset;
// Check pending word - this is necessary to avoid forgetting
// words after an undo operation (which undos a block
// merge). Unfortunately it makes the word under the cursor
// squiggled (if badly spelled) instead of just pending - but it's
// hard to do anything about.
if (m_pOwner->getDocLayout()->isPendingWordForSpell()&& (getSquiggleType() == FL_SQUIGGLE_SPELL) )
{
fl_PartOfBlock *pPending, *pPOB;
const fl_BlockLayout *pBL;
pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
pBL = m_pOwner->getDocLayout()->getPendingBlockForSpell();
// Copy details from pending POB - but don't actually use
// the object since it's owned by the code handling the
// pending word.
pPOB = new fl_PartOfBlock(pPending->getOffset(),
pPending->getPTLength());
// Clear pending word
m_pOwner->getDocLayout()->setPendingWordForSpell(NULL, NULL);
if (pBL == m_pOwner)
{
if(pPOB->getOffset() >= iOffset)
{
// If pending word is in this block after split,
// adjust details of the copy
pPOB->setOffset(pPOB->getOffset() + chg);
pBL = pNewBL;
}
else if (pPOB->getOffset() + pPOB->getPTLength() > iOffset)
{
// If pending word spans offset, adjust its length
pPOB->setPTLength(iOffset - pPOB->getOffset());
}
}
pBL->checkWord(pPOB);
}
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
{
if (m_pOwner->getDocLayout()->dequeueBlockForBackgroundCheck(m_pOwner))
{
// This block was queuing for spell-checking. Do a check of
// both blocks, but clear any squiggle added at IP.
deleteAll();
m_pOwner->checkSpelling();
pNewBL->checkSpelling();
fl_Squiggles * pSq = pNewBL->getSpellSquiggles();
UT_return_if_fail( pSq );
pSq->_deleteAtOffset(0);
}
else
{
// This block was already spell-checked, so just move the
// squiggles around.
// Remove squiggle broken by this insert
_deleteAtOffset(iOffset);
// Move all following squiggles to the new block
_move(0, chg, pNewBL);
// Find bounds of word at end of this block and check it.
// Use _recalcPendingWord which is a bit overkill, but gets
// the job done.
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
m_pOwner->_recalcPendingWord(iOffset, 0);
if (m_pOwner->getDocLayout()->isPendingWordForSpell() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
{
fl_PartOfBlock *pPending, *pPOB;
pPending = m_pOwner->getDocLayout()->getPendingWordForSpell();
// Copy details from pending POB - but don't actually use
// the object since it's owned by the code handling the
// pending word.
pPOB = new fl_PartOfBlock(pPending->getOffset(),
pPending->getPTLength());
m_pOwner->getDocLayout()->setPendingWordForSpell(NULL, NULL);
m_pOwner->checkWord(pPOB);
}
}
m_pOwner->getDocLayout()->setPendingBlockForGrammar(m_pOwner);
}
// Set start of new block to be pending word.
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
pNewBL->_recalcPendingWord(0, 0);
}
/*!
Join squiggles
\param iOffset Offset to where squiggles are moved
\param pPrevBlock Block they should be moved to
This function is called when a paragrah break is deleted and two
blocks are joined.
If either block is pending a background spell-check, the combined
block is checked in full.
Any squiggle touching the IP is deleted and the word touching the IP
becomes the pending word. The previously pending word, if any,
becomes irrelevant.
*/
void
fl_Squiggles::join(UT_sint32 iOffset, fl_BlockLayout* pPrevBL)
{
// Ignore operations on shadow blocks
if (m_pOwner->isHdrFtr())
return;
// Return if auto spell-checking disabled
if (!m_pOwner->getDocLayout()->getAutoSpellCheck() && (getSquiggleType() == FL_SQUIGGLE_SPELL) )
return;
xxx_UT_DEBUGMSG(("fl_Squiggles::join(%d, %p)\n", iOffset, pPrevBL));
bool bFullCheck = m_pOwner->getDocLayout()->dequeueBlockForBackgroundCheck(m_pOwner);
bFullCheck |= m_pOwner->getDocLayout()->dequeueBlockForBackgroundCheck(pPrevBL);
if (bFullCheck)
{
// This or the previous block was queuing for
// spell-checking. Clear all existing squiggles and do a check
// of the combined block.
deleteAll();
pPrevBL->getSpellSquiggles()->deleteAll();
pPrevBL->checkSpelling();
}
else
{
// If there is a squiggle first in this block, delete it to
// prevent having to handle a merge with a squiggle last in
// the previous block (which we could, but only to delete it
// below).
_deleteAtOffset(0);
// Move all squiggles from this block to the previous block.
_move(0, iOffset, pPrevBL);
}
m_pOwner->getDocLayout()->setPendingBlockForGrammar(m_pOwner);
// Delete squiggle touching IP
if(getSquiggleType() == FL_SQUIGGLE_SPELL)
{
fl_Squiggles * pSq = pPrevBL->getSpellSquiggles();
UT_return_if_fail( pSq );
pSq->_deleteAtOffset(iOffset);
// Update pending word
pPrevBL->_recalcPendingWord(iOffset, 0);
}
}
/*!
Find squiggles intersecting with region
\param iStart Start offset of region
\param iEnd End offset of region
\result iFirst Index of first squiggle intersecting with region
\result iLast Index of last squiggle intersecting with region
\return True if range is not empty, otherwise false
*/
bool
fl_Squiggles::findRange(UT_sint32 iStart, UT_sint32 iEnd,
UT_sint32& iFirst, UT_sint32& iLast, bool bDontExpand) const
{
xxx_UT_DEBUGMSG(("fl_Squiggles::findRange(%d, %d)\n", iStart, iEnd));
UT_sint32 iSquiggles = _getCount();
if (0 == iSquiggles) return false;
fl_PartOfBlock* pPOB = 0;
UT_sint32 s, e;
if((getSquiggleType() == FL_SQUIGGLE_GRAMMAR) && !bDontExpand)
{
// Grammar squiggles are preceded by a POB that covers the whole of the sentence.
// Expand the end point to cover it.
UT_sint32 i = 0;
for(i=0; i< iSquiggles;i++)
{
pPOB = getNth(i);
if((iStart >= pPOB->getOffset()) && (iStart <= pPOB->getOffset() + pPOB->getPTLength()) && pPOB->isInvisible())
{
iStart = pPOB->getOffset();
}
if((iEnd >= pPOB->getOffset()) && (iEnd <= pPOB->getOffset() + pPOB->getPTLength()) && pPOB->isInvisible())
{
iEnd = pPOB->getOffset() + pPOB->getPTLength();
}
}
}
// Look for the first POB.start that is higher than the end offset
_findFirstAfter(iEnd, e);
// Note that the return value is not checked: either there is no
// POBs at all, in which case we'll catch it in the statement
// below (first POB's offset past end point). Otherwise we want to
// look at the last POB on the line since it may span past
// iOffset.
// Return with empty set if the offset of the first POB on the
// line is higher than the region end (i.e. there is no previous
// POB that could span the region end).
if (0 == e)
{
UT_ASSERT(getNth(0)->getOffset() > iEnd);
return false;
}
// Adjust to be the first POB inside the region.
e--;
// FIXME: this should also use _findFirstAfter
// Look for the last POB.end that is lower than the start offset
for (s = e; s >= 0; s--)
{
pPOB = getNth(s);
if ((pPOB->getOffset() + pPOB->getPTLength()) < iStart) break;
}
// Return with empty set if the last POB's end offset is lower
// than the region start.
if (s == e)
{
UT_ASSERT((pPOB->getOffset() + pPOB->getPTLength()) < iStart);
return false;
}
//Adjust to be the first POB inside the region
s++;
UT_ASSERT(s >= 0 && s < iSquiggles);
UT_ASSERT(e >= 0 && e < iSquiggles);
UT_ASSERT(s <= e);
iFirst = s;
iLast = e;
return true;
}
/*!
Recheck ignored words
\param pBlockText The block's text
\return True if any words squiggled, false otherwise
*/
bool
fl_Squiggles::recheckIgnoredWords(const UT_UCSChar* pBlockText)
{
xxx_UT_DEBUGMSG(("fl_Squiggles::recheckIgnoredWords(%p)\n", pBlockText));
bool bUpdate = false;
UT_sint32 iSquiggles = (UT_sint32) _getCount();
UT_sint32 i;
for (i = iSquiggles-1; i >= 0; i--)
{
fl_PartOfBlock* pPOB = getNth((UT_uint32) i);
if (m_pOwner->_doCheckWord(pPOB, pBlockText, false))
{
// Word squiggled
bUpdate = true;
}
else
{
// Word not squiggled, remove from squiggle list
_deleteNth((UT_uint32) i);
}
}
return bUpdate;
}
fl_SpellSquiggles::fl_SpellSquiggles(fl_BlockLayout* pOwner) : fl_Squiggles(pOwner,FL_SQUIGGLE_SPELL)
{
}
fl_GrammarSquiggles::fl_GrammarSquiggles(fl_BlockLayout* pOwner) : fl_Squiggles(pOwner,FL_SQUIGGLE_GRAMMAR)
{
}