xref: /aoo41x/main/sw/source/core/txtnode/txtedt.cxx (revision efeef26f)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_sw.hxx"
26 
27 // So kann man die Linguistik-Statistik ( (Tmp-Path)\swlingu.stk ) aktivieren:
28 //#define LINGU_STATISTIK
29 #ifdef LINGU_STATISTIK
30 	#include <stdio.h>			// in SwLinguStatistik::DTOR
31 	#include <stdlib.h> 		// getenv()
32 	#include <time.h> 			// clock()
33     #include <tools/stream.hxx>
34 #endif
35 
36 #include <hintids.hxx>
37 #include <vcl/svapp.hxx>
38 #include <svl/itemiter.hxx>
39 #include <editeng/splwrap.hxx>
40 #include <editeng/langitem.hxx>
41 #include <editeng/fontitem.hxx>
42 #include <editeng/scripttypeitem.hxx>
43 #include <editeng/hangulhanja.hxx>
44 #include <SwSmartTagMgr.hxx>
45 #include <linguistic/lngprops.hxx>
46 #include <unotools/transliterationwrapper.hxx>
47 #include <unotools/charclass.hxx>
48 #include <dlelstnr.hxx>
49 #include <swmodule.hxx>
50 #include <splargs.hxx>
51 #include <viewopt.hxx>
52 #include <acmplwrd.hxx>
53 #include <doc.hxx>		// GetDoc()
54 #include <docsh.hxx>
55 #include <txtfld.hxx>
56 #include <fmtfld.hxx>
57 #include <txatbase.hxx>
58 #include <charatr.hxx>
59 #include <fldbas.hxx>
60 #include <pam.hxx>
61 #include <hints.hxx>
62 #include <ndtxt.hxx>
63 #include <txtfrm.hxx>
64 #include <SwGrammarMarkUp.hxx>
65 
66 #include <txttypes.hxx>
67 #include <breakit.hxx>
68 #include <crstate.hxx>
69 #include <UndoOverwrite.hxx>
70 #include <txatritr.hxx>
71 #include <redline.hxx>		// SwRedline
72 #include <docary.hxx>		// SwRedlineTbl
73 #include <scriptinfo.hxx>
74 #include <docstat.hxx>
75 #include <editsh.hxx>
76 #include <unotextmarkup.hxx>
77 #include <txtatr.hxx>
78 #include <fmtautofmt.hxx>
79 #include <istyleaccess.hxx>
80 
81 #include <unomid.h>
82 
83 #include <com/sun/star/beans/XPropertySet.hpp>
84 #include <com/sun/star/i18n/WordType.hdl>
85 #include <com/sun/star/i18n/ScriptType.hdl>
86 #include <com/sun/star/i18n/TransliterationModules.hpp>
87 #include <com/sun/star/i18n/TransliterationModulesExtra.hpp>
88 
89 #include <vector>
90 
91 
92 using rtl::OUString;
93 using namespace ::com::sun::star;
94 using namespace ::com::sun::star::frame;
95 using namespace ::com::sun::star::i18n;
96 using namespace ::com::sun::star::beans;
97 using namespace ::com::sun::star::uno;
98 using namespace ::com::sun::star::linguistic2;
99 using namespace ::com::sun::star::smarttags;
100 
101 // Wir ersparen uns in Hyphenate ein GetFrm()
102 // Achtung: in edlingu.cxx stehen die Variablen!
103 extern const SwTxtNode *pLinguNode;
104 extern       SwTxtFrm  *pLinguFrm;
105 
106 bool lcl_IsSkippableWhiteSpace( xub_Unicode cCh )
107 {
108     return 0x3000 == cCh ||
109            ' ' == cCh ||
110            '\t' == cCh ||
111            0x0a == cCh;
112 }
113 
114 /*
115  * This has basically the same function as SwScriptInfo::MaskHiddenRanges,
116  * only for deleted redlines
117  */
118 
119 sal_uInt16 lcl_MaskRedlines( const SwTxtNode& rNode, XubString& rText,
120                          const xub_StrLen nStt, const xub_StrLen nEnd,
121                          const xub_Unicode cChar )
122 {
123     sal_uInt16 nNumOfMaskedRedlines = 0;
124 
125     const SwDoc& rDoc = *rNode.GetDoc();
126     sal_uInt16 nAct = rDoc.GetRedlinePos( rNode, USHRT_MAX );
127 
128     for ( ; nAct < rDoc.GetRedlineTbl().Count(); nAct++ )
129     {
130         const SwRedline* pRed = rDoc.GetRedlineTbl()[ nAct ];
131 
132         if ( pRed->Start()->nNode > rNode.GetIndex() )
133             break;
134 
135         if( nsRedlineType_t::REDLINE_DELETE == pRed->GetType() )
136         {
137             xub_StrLen nRedlineEnd;
138             xub_StrLen nRedlineStart;
139 
140             pRed->CalcStartEnd( rNode.GetIndex(), nRedlineStart, nRedlineEnd );
141 
142             if ( nRedlineEnd < nStt || nRedlineStart > nEnd )
143                 continue;
144 
145             while ( nRedlineStart < nRedlineEnd && nRedlineStart < nEnd )
146             {
147                 if ( nRedlineStart >= nStt && nRedlineStart < nEnd )
148                 {
149                     rText.SetChar( nRedlineStart, cChar );
150                     ++nNumOfMaskedRedlines;
151                 }
152                 ++nRedlineStart;
153             }
154         }
155     }
156 
157     return nNumOfMaskedRedlines;
158 }
159 
160 /*
161  * Used for spell checking. Deleted redlines and hidden characters are masked
162  */
163 
164 sal_uInt16 lcl_MaskRedlinesAndHiddenText( const SwTxtNode& rNode, XubString& rText,
165                                       const xub_StrLen nStt, const xub_StrLen nEnd,
166                                       const xub_Unicode cChar = CH_TXTATR_INWORD,
167                                       bool bCheckShowHiddenChar = true )
168 {
169     sal_uInt16 nRedlinesMasked = 0;
170     sal_uInt16 nHiddenCharsMasked = 0;
171 
172     const SwDoc& rDoc = *rNode.GetDoc();
173     const bool bShowChg = 0 != IDocumentRedlineAccess::IsShowChanges( rDoc.GetRedlineMode() );
174 
175     // If called from word count or from spell checking, deleted redlines
176     // should be masked:
177     if ( bShowChg )
178     {
179         nRedlinesMasked = lcl_MaskRedlines( rNode, rText, nStt, nEnd, cChar );
180     }
181 
182     const bool bHideHidden = !SW_MOD()->GetViewOption(rDoc.get(IDocumentSettingAccess::HTML_MODE))->IsShowHiddenChar();
183 
184     // If called from word count, we want to mask the hidden ranges even
185     // if they are visible:
186     if ( !bCheckShowHiddenChar || bHideHidden )
187     {
188         nHiddenCharsMasked =
189             SwScriptInfo::MaskHiddenRanges( rNode, rText, nStt, nEnd, cChar );
190     }
191 
192     return nRedlinesMasked + nHiddenCharsMasked;
193 }
194 
195 /*
196  * Used for spell checking. Calculates a rectangle for repaint.
197  */
198 
199 static SwRect lcl_CalculateRepaintRect( SwTxtFrm& rTxtFrm, xub_StrLen nChgStart, xub_StrLen nChgEnd )
200 {
201     SwRect aRect;
202 
203     SwTxtNode *pNode = rTxtFrm.GetTxtNode();
204 
205     SwNodeIndex aNdIdx( *pNode );
206     SwPosition aPos( aNdIdx, SwIndex( pNode, nChgEnd ) );
207     SwCrsrMoveState aTmpState( MV_NONE );
208     aTmpState.b2Lines = sal_True;
209     rTxtFrm.GetCharRect( aRect, aPos, &aTmpState );
210     // information about end of repaint area
211     Sw2LinesPos* pEnd2Pos = aTmpState.p2Lines;
212 
213     const SwTxtFrm *pEndFrm = &rTxtFrm;
214 
215     while( pEndFrm->HasFollow() &&
216            nChgEnd >= pEndFrm->GetFollow()->GetOfst() )
217         pEndFrm = pEndFrm->GetFollow();
218 
219     if ( pEnd2Pos )
220     {
221         // we are inside a special portion, take left border
222         SWRECTFN( pEndFrm )
223         (aRect.*fnRect->fnSetTop)( (pEnd2Pos->aLine.*fnRect->fnGetTop)() );
224         if ( pEndFrm->IsRightToLeft() )
225             (aRect.*fnRect->fnSetLeft)( (pEnd2Pos->aPortion.*fnRect->fnGetLeft)() );
226         else
227             (aRect.*fnRect->fnSetLeft)( (pEnd2Pos->aPortion.*fnRect->fnGetRight)() );
228         (aRect.*fnRect->fnSetWidth)( 1 );
229         (aRect.*fnRect->fnSetHeight)( (pEnd2Pos->aLine.*fnRect->fnGetHeight)() );
230         delete pEnd2Pos;
231     }
232 
233     aTmpState.p2Lines = NULL;
234     SwRect aTmp;
235     aPos = SwPosition( aNdIdx, SwIndex( pNode, nChgStart ) );
236     rTxtFrm.GetCharRect( aTmp, aPos, &aTmpState );
237 
238     // i63141: GetCharRect(..) could cause a formatting,
239     // during the formatting SwTxtFrms could be joined, deleted, created...
240     // => we have to reinit pStartFrm and pEndFrm after the formatting
241     const SwTxtFrm* pStartFrm = &rTxtFrm;
242     while( pStartFrm->HasFollow() &&
243            nChgStart >= pStartFrm->GetFollow()->GetOfst() )
244         pStartFrm = pStartFrm->GetFollow();
245     pEndFrm = pStartFrm;
246     while( pEndFrm->HasFollow() &&
247            nChgEnd >= pEndFrm->GetFollow()->GetOfst() )
248         pEndFrm = pEndFrm->GetFollow();
249 
250     // information about start of repaint area
251     Sw2LinesPos* pSt2Pos = aTmpState.p2Lines;
252     if ( pSt2Pos )
253     {
254         // we are inside a special portion, take right border
255         SWRECTFN( pStartFrm )
256         (aTmp.*fnRect->fnSetTop)( (pSt2Pos->aLine.*fnRect->fnGetTop)() );
257         if ( pStartFrm->IsRightToLeft() )
258             (aTmp.*fnRect->fnSetLeft)( (pSt2Pos->aPortion.*fnRect->fnGetRight)() );
259         else
260             (aTmp.*fnRect->fnSetLeft)( (pSt2Pos->aPortion.*fnRect->fnGetLeft)() );
261         (aTmp.*fnRect->fnSetWidth)( 1 );
262         (aTmp.*fnRect->fnSetHeight)( (pSt2Pos->aLine.*fnRect->fnGetHeight)() );
263         delete pSt2Pos;
264     }
265 
266     sal_Bool bSameFrame = sal_True;
267 
268     if( rTxtFrm.HasFollow() )
269     {
270         if( pEndFrm != pStartFrm )
271         {
272             bSameFrame = sal_False;
273             SwRect aStFrm( pStartFrm->PaintArea() );
274             {
275                 SWRECTFN( pStartFrm )
276                 (aTmp.*fnRect->fnSetLeft)( (aStFrm.*fnRect->fnGetLeft)() );
277                 (aTmp.*fnRect->fnSetRight)( (aStFrm.*fnRect->fnGetRight)() );
278                 (aTmp.*fnRect->fnSetBottom)( (aStFrm.*fnRect->fnGetBottom)() );
279             }
280             aStFrm = pEndFrm->PaintArea();
281             {
282                 SWRECTFN( pEndFrm )
283                 (aRect.*fnRect->fnSetTop)( (aStFrm.*fnRect->fnGetTop)() );
284                 (aRect.*fnRect->fnSetLeft)( (aStFrm.*fnRect->fnGetLeft)() );
285                 (aRect.*fnRect->fnSetRight)( (aStFrm.*fnRect->fnGetRight)() );
286             }
287             aRect.Union( aTmp );
288             while( sal_True )
289             {
290                 pStartFrm = pStartFrm->GetFollow();
291                 if( pStartFrm == pEndFrm )
292                     break;
293                 aRect.Union( pStartFrm->PaintArea() );
294             }
295         }
296     }
297     if( bSameFrame )
298     {
299         SWRECTFN( pStartFrm )
300         if( (aTmp.*fnRect->fnGetTop)() == (aRect.*fnRect->fnGetTop)() )
301             (aRect.*fnRect->fnSetLeft)( (aTmp.*fnRect->fnGetLeft)() );
302         else
303         {
304             SwRect aStFrm( pStartFrm->PaintArea() );
305             (aRect.*fnRect->fnSetLeft)( (aStFrm.*fnRect->fnGetLeft)() );
306             (aRect.*fnRect->fnSetRight)( (aStFrm.*fnRect->fnGetRight)() );
307             (aRect.*fnRect->fnSetTop)( (aTmp.*fnRect->fnGetTop)() );
308         }
309 
310         if( aTmp.Height() > aRect.Height() )
311             aRect.Height( aTmp.Height() );
312     }
313 
314     return aRect;
315 }
316 
317 /*
318  * Used for automatic styles. Used during RstAttr.
319  */
320 
321 static bool lcl_HaveCommonAttributes( IStyleAccess& rStyleAccess,
322                                       const SfxItemSet* pSet1,
323                                       sal_uInt16 nWhichId,
324                                       const SfxItemSet& rSet2,
325                                       boost::shared_ptr<SfxItemSet>& pStyleHandle )
326 {
327     bool bRet = false;
328 
329     SfxItemSet* pNewSet = 0;
330 
331     if ( !pSet1 )
332     {
333         ASSERT( nWhichId, "lcl_HaveCommonAttributes not used correctly" )
334         if ( SFX_ITEM_SET == rSet2.GetItemState( nWhichId, sal_False ) )
335         {
336             pNewSet = rSet2.Clone( sal_True );
337             pNewSet->ClearItem( nWhichId );
338         }
339     }
340     else if ( pSet1->Count() )
341     {
342         SfxItemIter aIter( *pSet1 );
343         const SfxPoolItem* pItem = aIter.GetCurItem();
344         while( sal_True )
345         {
346             if ( SFX_ITEM_SET == rSet2.GetItemState( pItem->Which(), sal_False ) )
347             {
348                 if ( !pNewSet )
349                     pNewSet = rSet2.Clone( sal_True );
350                 pNewSet->ClearItem( pItem->Which() );
351             }
352 
353             if( aIter.IsAtEnd() )
354                 break;
355 
356             pItem = aIter.NextItem();
357         }
358     }
359 
360     if ( pNewSet )
361     {
362         if ( pNewSet->Count() )
363             pStyleHandle = rStyleAccess.getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR );
364         delete pNewSet;
365         bRet = true;
366     }
367 
368     return bRet;
369 }
370 
371 inline sal_Bool InRange(xub_StrLen nIdx, xub_StrLen nStart, xub_StrLen nEnd) {
372 	return ((nIdx >=nStart) && (nIdx <= nEnd));
373 }
374 
375 /*
376  * void SwTxtNode::RstAttr(const SwIndex &rIdx, sal_uInt16 nLen)
377  *
378  * Deletes all attributes, starting at position rIdx, for length nLen.
379  */
380 
381 /* 5 cases:
382  * 1) The attribute is completely in the deletion range:
383  *    -> delete it
384  * 2) The end of the attribute is in the deletion range:
385  *    -> delete it, then re-insert it with new end
386  * 3) The start of the attribute is in the deletion range:
387  *    -> delete it, then re-insert it with new start
388  * 4) The attribute contains the deletion range:
389  *       Split, i.e.,
390  *    -> Delete, re-insert from old start to start of deletion range
391  *    -> insert new attribute from end of deletion range to old end
392  * 5) The attribute is outside the deletion range
393  *    -> nothing to do
394  */
395 
396 void SwTxtNode::RstAttr(const SwIndex &rIdx, xub_StrLen nLen, sal_uInt16 nWhich,
397 						const SfxItemSet* pSet, sal_Bool bInclRefToxMark )
398 {
399 	// Attribute?
400 	if ( !GetpSwpHints() )
401 		return;
402 
403 	sal_uInt16 i = 0;
404     xub_StrLen nStt = rIdx.GetIndex();
405     xub_StrLen nEnd = nStt + nLen;
406 	xub_StrLen nAttrStart;
407 	SwTxtAttr *pHt;
408 
409 	sal_Bool	bChanged = sal_False;
410 
411     // nMin and nMax initialized to maximum / minimum (inverse)
412     xub_StrLen nMin = m_Text.Len();
413     xub_StrLen nMax = nStt;
414 
415 	const sal_Bool bNoLen = !nMin;
416 
417     // We have to remember the "new" attributes, which have
418     // been introduced by splitting surrounding attributes (case 4).
419     // They may not be forgotten inside the "Forget" function
420     //std::vector< const SwTxtAttr* > aNewAttributes;
421 
422     // iterate over attribute array until start of attribute is behind
423     // deletion range
424     while ((i < m_pSwpHints->Count()) &&
425         ((( nAttrStart = *(*m_pSwpHints)[i]->GetStart()) < nEnd ) || nLen==0) )
426     {
427         pHt = m_pSwpHints->GetTextHint(i);
428 
429         // attributes without end stay in!
430         xub_StrLen * const pAttrEnd = pHt->GetEnd();
431         if ( !pAttrEnd /*|| pHt->HasDummyChar()*/ ) // see bInclRefToxMark
432         {
433 			i++;
434 			continue;
435 		}
436 
437         // Default behavior is to process all attributes:
438         bool bSkipAttr = false;;
439         boost::shared_ptr<SfxItemSet> pStyleHandle;
440 
441         // 1. case: We want to reset only the attributes listed in pSet:
442         if ( pSet )
443         {
444             bSkipAttr = SFX_ITEM_SET != pSet->GetItemState( pHt->Which(), sal_False );
445             if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
446             {
447                 // if the current attribute is an autostyle, we have to check if the autostyle
448                 // and pSet have any attributes in common. If so, pStyleHandle will contain
449                 // a handle to AutoStyle / pSet:
450                 bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), pSet, 0, *static_cast<const SwFmtAutoFmt&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
451             }
452         }
453         else if ( nWhich )
454         {
455             // 2. case: We want to reset only the attributes with WhichId nWhich:
456             bSkipAttr = nWhich != pHt->Which();
457             if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
458             {
459                 bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), 0, nWhich, *static_cast<const SwFmtAutoFmt&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
460             }
461         }
462         else if ( !bInclRefToxMark )
463         {
464             // 3. case: Reset all attributes except from ref/toxmarks:
465             // skip hints with CH_TXTATR here
466             // (deleting those is ONLY allowed for UNDO!)
467             bSkipAttr = RES_TXTATR_REFMARK   == pHt->Which()
468                      || RES_TXTATR_TOXMARK   == pHt->Which()
469                      || RES_TXTATR_META      == pHt->Which()
470                      || RES_TXTATR_METAFIELD == pHt->Which();
471         }
472 
473         if ( bSkipAttr )
474         {
475 			i++;
476 			continue;
477 		}
478 
479 
480         if( nStt <= nAttrStart )          // Faelle: 1,3,5
481 		{
482 			if( nEnd > nAttrStart
483 				|| ( nEnd == *pAttrEnd && nEnd==nAttrStart ) )
484 			{
485 				// Faelle: 1,3
486 				if ( nMin > nAttrStart )
487 					nMin = nAttrStart;
488 				if ( nMax < *pAttrEnd )
489 					nMax = *pAttrEnd;
490 				// Falls wir nur ein nichtaufgespanntes Attribut entfernen,
491 				// tun wir mal so, als ob sich nichts geaendert hat.
492 				bChanged = bChanged || nEnd > nAttrStart || bNoLen;
493 				if( *pAttrEnd <= nEnd )		// Fall: 1
494 				{
495                     const xub_StrLen nAttrEnd = *pAttrEnd;
496 
497                     m_pSwpHints->DeleteAtPos(i);
498                     DestroyAttr( pHt );
499 
500                     if ( pStyleHandle.get() )
501                     {
502                         SwTxtAttr* pNew = MakeTxtAttr( *GetDoc(),
503                                 *pStyleHandle, nAttrStart, nAttrEnd );
504                         InsertHint( pNew, nsSetAttrMode::SETATTR_NOHINTADJUST );
505                     }
506 
507                     // if the last attribute is a Field, the HintsArray is
508                     // deleted!
509                     if ( !m_pSwpHints )
510                         break;
511 
512 					//JP 26.11.96:
513 					// beim DeleteAtPos wird ein Resort ausgefuehrt!!
514 					// darum muessen wir wieder bei 0 anfangen!!!
515 					// ueber den Fall 3 koennen Attribute nach hinten
516 					// verschoben worden sein; damit stimmt jetzt das i
517 					// nicht mehr!!!
518 					i = 0;
519 
520 					continue;
521 				}
522 				else						// Fall: 3
523                 {
524                     m_pSwpHints->NoteInHistory( pHt );
525                     *pHt->GetStart() = nEnd;
526                     m_pSwpHints->NoteInHistory( pHt, sal_True );
527 
528                     if ( pStyleHandle.get() && nAttrStart < nEnd )
529                     {
530                         SwTxtAttr* pNew = MakeTxtAttr( *GetDoc(),
531                                 *pStyleHandle, nAttrStart, nEnd );
532                         InsertHint( pNew, nsSetAttrMode::SETATTR_NOHINTADJUST );
533                     }
534 
535 					bChanged = sal_True;
536 				}
537 			}
538 		}
539 		else								// Faelle: 2,4,5
540             if( *pAttrEnd > nStt )     // Faelle: 2,4
541 			{
542 				if( *pAttrEnd < nEnd )		// Fall: 2
543 				{
544 					if ( nMin > nAttrStart )
545 						nMin = nAttrStart;
546 					if ( nMax < *pAttrEnd )
547 						nMax = *pAttrEnd;
548 					bChanged = sal_True;
549 
550                     const xub_StrLen nAttrEnd = *pAttrEnd;
551 
552                     m_pSwpHints->NoteInHistory( pHt );
553                     *pAttrEnd = nStt;
554                     m_pSwpHints->NoteInHistory( pHt, sal_True );
555 
556                     if ( pStyleHandle.get() )
557                     {
558                         SwTxtAttr* pNew = MakeTxtAttr( *GetDoc(),
559                                 *pStyleHandle, nStt, nAttrEnd );
560                         InsertHint( pNew, nsSetAttrMode::SETATTR_NOHINTADJUST );
561                     }
562 				}
563 				else if( nLen )				// Fall: 4
564 				{		// bei Lange 0 werden beide Hints vom Insert(Ht)
565 						// wieder zu einem zusammengezogen !!!!
566 					if ( nMin > nAttrStart )
567 						nMin = nAttrStart;
568 					if ( nMax < *pAttrEnd )
569 						nMax = *pAttrEnd;
570 					bChanged = sal_True;
571 					xub_StrLen nTmpEnd = *pAttrEnd;
572                     m_pSwpHints->NoteInHistory( pHt );
573                     *pAttrEnd = nStt;
574                     m_pSwpHints->NoteInHistory( pHt, sal_True );
575 
576                     if ( pStyleHandle.get() && nStt < nEnd )
577                     {
578                         SwTxtAttr* pNew = MakeTxtAttr( *GetDoc(),
579                                 *pStyleHandle, nStt, nEnd );
580                         InsertHint( pNew, nsSetAttrMode::SETATTR_NOHINTADJUST );
581                     }
582 
583                     if( nEnd < nTmpEnd )
584                     {
585                         SwTxtAttr* pNew = MakeTxtAttr( *GetDoc(),
586                                 pHt->GetAttr(), nEnd, nTmpEnd );
587                         if ( pNew )
588                         {
589                             SwTxtCharFmt* pCharFmt = dynamic_cast<SwTxtCharFmt*>(pHt);
590                             if ( pCharFmt )
591                                 static_cast<SwTxtCharFmt*>(pNew)->SetSortNumber( pCharFmt->GetSortNumber() );
592 
593                             InsertHint( pNew,
594                                 nsSetAttrMode::SETATTR_NOHINTADJUST );
595                         }
596 
597 
598                         // jetzt kein i+1, weil das eingefuegte Attribut
599 						// ein anderes auf die Position geschoben hat !
600 						continue;
601 					}
602 				}
603 			}
604 		++i;
605 	}
606 
607     TryDeleteSwpHints();
608     if (bChanged)
609     {
610         if ( HasHints() )
611         {
612             m_pSwpHints->Resort();
613         }
614 		//TxtFrm's reagieren auf aHint, andere auf aNew
615 		SwUpdateAttr aHint( nMin, nMax, 0 );
616 	    NotifyClients( 0, &aHint );
617 		SwFmtChg aNew( GetFmtColl() );
618 		NotifyClients( 0, &aNew );
619 	}
620 }
621 
622 
623 
624 /*************************************************************************
625  *				  SwTxtNode::GetCurWord()
626  *
627  * Aktuelles Wort zurueckliefern:
628  * Wir suchen immer von links nach rechts, es wird also das Wort
629  * vor nPos gesucht. Es sei denn, wir befinden uns am Anfang des
630  * Absatzes, dann wird das erste Wort zurueckgeliefert.
631  * Wenn dieses erste Wort nur aus Whitespaces besteht, returnen wir
632  * einen leeren String.
633  *************************************************************************/
634 
635 XubString SwTxtNode::GetCurWord( xub_StrLen nPos ) const
636 {
637     ASSERT( nPos <= m_Text.Len(), "SwTxtNode::GetCurWord: invalid index." );
638 
639     if (!m_Text.Len())
640         return m_Text;
641 
642 	Boundary aBndry;
643 	const uno::Reference< XBreakIterator > &rxBreak = pBreakIt->GetBreakIter();
644     if (rxBreak.is())
645     {
646         sal_Int16 nWordType = WordType::DICTIONARY_WORD;
647         lang::Locale aLocale( pBreakIt->GetLocale( GetLang( nPos ) ) );
648 #ifdef DEBUG
649         sal_Bool bBegin = rxBreak->isBeginWord( m_Text, nPos, aLocale, nWordType );
650         sal_Bool bEnd   = rxBreak->isEndWord  ( m_Text, nPos, aLocale, nWordType );
651         (void)bBegin;
652         (void)bEnd;
653 #endif
654         aBndry =
655             rxBreak->getWordBoundary( m_Text, nPos, aLocale, nWordType, sal_True );
656 
657         // if no word was found use previous word (if any)
658         if (aBndry.startPos == aBndry.endPos)
659         {
660             aBndry = rxBreak->previousWord( m_Text, nPos, aLocale, nWordType );
661         }
662     }
663 
664     // check if word was found and if it uses a symbol font, if so
665     // enforce returning an empty string
666     if (aBndry.endPos != aBndry.startPos && IsSymbol( (xub_StrLen)aBndry.startPos ))
667 		aBndry.endPos = aBndry.startPos;
668 
669     return m_Text.Copy( static_cast<xub_StrLen>(aBndry.startPos),
670                        static_cast<xub_StrLen>(aBndry.endPos - aBndry.startPos) );
671 }
672 
673 SwScanner::SwScanner( const SwTxtNode& rNd, const String& rTxt, const LanguageType* pLang,
674                       const ModelToViewHelper::ConversionMap* pConvMap,
675                       sal_uInt16 nType, xub_StrLen nStart, xub_StrLen nEnde, sal_Bool bClp )
676     : rNode( rNd ), rText( rTxt), pLanguage( pLang ), pConversionMap( pConvMap ), nLen( 0 ), nWordType( nType ), bClip( bClp )
677 {
678     ASSERT( rText.Len(), "SwScanner: EmptyString" );
679     nStartPos = nBegin = nStart;
680     nEndPos = nEnde;
681 
682     if ( pLanguage )
683     {
684         aCurrLang = *pLanguage;
685     }
686     else
687     {
688         ModelToViewHelper::ModelPosition aModelBeginPos = ModelToViewHelper::ConvertToModelPosition( pConversionMap, nBegin );
689         const xub_StrLen nModelBeginPos = (xub_StrLen)aModelBeginPos.mnPos;
690         aCurrLang = rNd.GetLang( nModelBeginPos );
691     }
692 }
693 
694 sal_Bool SwScanner::NextWord()
695 {
696     nBegin = nBegin + nLen;
697     Boundary aBound;
698 
699     CharClass& rCC = GetAppCharClass();
700     lang::Locale aOldLocale = rCC.getLocale();
701 
702 	while ( true )
703 	{
704         // skip non-letter characters:
705         while ( nBegin < rText.Len() )
706         {
707             if ( !lcl_IsSkippableWhiteSpace( rText.GetChar( nBegin ) ) )
708             {
709                 if ( !pLanguage )
710                 {
711                     const sal_uInt16 nNextScriptType = pBreakIt->GetBreakIter()->getScriptType( rText, nBegin );
712                     ModelToViewHelper::ModelPosition aModelBeginPos = ModelToViewHelper::ConvertToModelPosition( pConversionMap, nBegin );
713                     const xub_StrLen nBeginModelPos = (xub_StrLen)aModelBeginPos.mnPos;
714                     aCurrLang = rNode.GetLang( nBeginModelPos, 1, nNextScriptType );
715                 }
716 
717                 if ( nWordType != i18n::WordType::WORD_COUNT )
718                 {
719                     rCC.setLocale( pBreakIt->GetLocale( aCurrLang ) );
720                     if ( rCC.isLetterNumeric( rText.GetChar( nBegin ) ) )
721                         break;
722                 }
723                 else
724                     break;
725             }
726             ++nBegin;
727         }
728 
729 		if ( nBegin >= rText.Len() || nBegin >= nEndPos )
730 			return sal_False;
731 
732         // get the word boundaries
733         aBound = pBreakIt->GetBreakIter()->getWordBoundary( rText, nBegin,
734 				pBreakIt->GetLocale( aCurrLang ), nWordType, sal_True );
735         ASSERT( aBound.endPos >= aBound.startPos, "broken aBound result" );
736 
737         //no word boundaries could be found
738         if(aBound.endPos == aBound.startPos)
739             return sal_False;
740 
741         //if a word before is found it has to be searched for the next
742 		if(aBound.endPos == nBegin)
743             ++nBegin;
744         else
745 			break;
746     } // end while( true )
747 
748     rCC.setLocale( aOldLocale );
749 
750     // #i89042, as discussed with HDU: don't evaluate script changes for word count. Use whole word.
751     if ( nWordType == i18n::WordType::WORD_COUNT )
752     {
753         nBegin = Max( static_cast< xub_StrLen >(aBound.startPos), nBegin );
754         nLen   = 0;
755         if (static_cast< xub_StrLen >(aBound.endPos) > nBegin)
756             nLen = static_cast< xub_StrLen >(aBound.endPos) - nBegin;
757     }
758     else
759     {
760         // we have to differenciate between these cases:
761         if ( aBound.startPos <= nBegin )
762         {
763             ASSERT( aBound.endPos >= nBegin, "Unexpected aBound result" )
764 
765             // restrict boundaries to script boundaries and nEndPos
766             const sal_uInt16 nCurrScript = pBreakIt->GetBreakIter()->getScriptType( rText, nBegin );
767             XubString aTmpWord = rText.Copy( nBegin, static_cast<xub_StrLen>(aBound.endPos - nBegin) );
768             const sal_Int32 nScriptEnd = nBegin +
769                 pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript );
770             const sal_Int32 nEnd = Min( aBound.endPos, nScriptEnd );
771 
772             // restrict word start to last script change position
773             sal_Int32 nScriptBegin = 0;
774             if ( aBound.startPos < nBegin )
775             {
776                 // search from nBegin backwards until the next script change
777                 aTmpWord = rText.Copy( static_cast<xub_StrLen>(aBound.startPos),
778                                        static_cast<xub_StrLen>(nBegin - aBound.startPos + 1) );
779                 nScriptBegin = aBound.startPos +
780                     pBreakIt->GetBreakIter()->beginOfScript( aTmpWord, nBegin - aBound.startPos,
781                                                     nCurrScript );
782             }
783 
784             nBegin = (xub_StrLen)Max( aBound.startPos, nScriptBegin );
785 	        nLen = (xub_StrLen)(nEnd - nBegin);
786         }
787         else
788         {
789             const sal_uInt16 nCurrScript = pBreakIt->GetBreakIter()->getScriptType( rText, aBound.startPos );
790             XubString aTmpWord = rText.Copy( static_cast<xub_StrLen>(aBound.startPos),
791                                              static_cast<xub_StrLen>(aBound.endPos - aBound.startPos) );
792             const sal_Int32 nScriptEnd = aBound.startPos +
793                 pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript );
794             const sal_Int32 nEnd = Min( aBound.endPos, nScriptEnd );
795             nBegin = (xub_StrLen)aBound.startPos;
796 	        nLen = (xub_StrLen)(nEnd - nBegin);
797         }
798     }
799 
800 	// optionally clip the result of getWordBoundaries:
801 	if ( bClip )
802 	{
803 		aBound.startPos = Max( (xub_StrLen)aBound.startPos, nStartPos );
804 		aBound.endPos = Min( (xub_StrLen)aBound.endPos, nEndPos );
805         nBegin = (xub_StrLen)aBound.startPos;
806 	    nLen = (xub_StrLen)(aBound.endPos - nBegin);
807 	}
808 
809     if( ! nLen )
810         return sal_False;
811 
812     aWord = rText.Copy( nBegin, nLen );
813 
814     return sal_True;
815 }
816 
817 
818 sal_uInt16 SwTxtNode::Spell(SwSpellArgs* pArgs)
819 {
820 	// Die Aehnlichkeiten zu SwTxtFrm::_AutoSpell sind beabsichtigt ...
821 	// ACHTUNG: Ev. Bugs in beiden Routinen fixen!
822 
823 	uno::Reference<beans::XPropertySet> xProp( GetLinguPropertySet() );
824 
825 	xub_StrLen nBegin, nEnd;
826 
827     // modify string according to redline information and hidden text
828     const XubString aOldTxt( m_Text );
829     const bool bRestoreString =
830         lcl_MaskRedlinesAndHiddenText( *this, m_Text, 0, m_Text.Len() ) > 0;
831 
832     if ( pArgs->pStartNode != this )
833 		nBegin = 0;
834 	else
835         nBegin = pArgs->pStartIdx->GetIndex();
836 
837     nEnd = ( pArgs->pEndNode != this )
838             ? m_Text.Len()
839             : pArgs->pEndIdx->GetIndex();
840 
841 	pArgs->xSpellAlt = NULL;
842 
843     // 4 cases:
844     //
845     // 1. IsWrongDirty = 0 and GetWrong = 0
846     //      Everything is checked and correct
847     // 2. IsWrongDirty = 0 and GetWrong = 1
848     //      Everything is checked and errors are identified in the wrong list
849     // 3. IsWrongDirty = 1 and GetWrong = 0
850     //      Nothing has been checked
851     // 4. IsWrongDirty = 1 and GetWrong = 1
852     //      Text has been checked but there is an invalid range in the wrong list
853     //
854     // Nothing has to be done for case 1.
855     if ( ( IsWrongDirty() || GetWrong() ) && m_Text.Len() )
856     {
857         if ( nBegin > m_Text.Len() )
858         {
859             nBegin = m_Text.Len();
860         }
861         if ( nEnd > m_Text.Len() )
862         {
863             nEnd = m_Text.Len();
864         }
865         //
866         if(!IsWrongDirty())
867         {
868             xub_StrLen nTemp = GetWrong()->NextWrong( nBegin );
869             if(nTemp > nEnd)
870             {
871                 // reset original text
872                 if ( bRestoreString )
873                 {
874                     m_Text = aOldTxt;
875                 }
876                 return 0;
877             }
878             if(nTemp > nBegin)
879                 nBegin = nTemp;
880 
881         }
882 
883         // In case 2. we pass the wrong list to the scanned, because only
884         // the words in the wrong list have to be checked
885         SwScanner aScanner( *this, m_Text, 0, 0,
886                             WordType::DICTIONARY_WORD,
887                             nBegin, nEnd );
888         while( !pArgs->xSpellAlt.is() && aScanner.NextWord() )
889 		{
890 			const XubString& rWord = aScanner.GetWord();
891 
892             // get next language for next word, consider language attributes
893             // within the word
894             LanguageType eActLang = aScanner.GetCurrentLanguage();
895 
896             if( rWord.Len() > 0 && LANGUAGE_NONE != eActLang )
897 			{
898 				if (pArgs->xSpeller.is())
899 				{
900 					SvxSpellWrapper::CheckSpellLang( pArgs->xSpeller, eActLang );
901 					pArgs->xSpellAlt = pArgs->xSpeller->spell( rWord, eActLang,
902 											Sequence< PropertyValue >() );
903 				}
904 				if( (pArgs->xSpellAlt).is() )
905 				{
906 					if( IsSymbol( aScanner.GetBegin() ) )
907 					{
908 						pArgs->xSpellAlt = NULL;
909 					}
910 					else
911 					{
912 						// make sure the selection build later from the
913 						// data below does not include footnotes and other
914 						// "in word" character to the left and right in order
915 						// to preserve those. Therefore count those "in words"
916 						// in order to modify the selection accordingly.
917 						const sal_Unicode* pChar = rWord.GetBuffer();
918 						xub_StrLen nLeft = 0;
919 						while (pChar && *pChar++ == CH_TXTATR_INWORD)
920 							++nLeft;
921 						pChar = rWord.Len() ? rWord.GetBuffer() + rWord.Len() - 1 : 0;
922 						xub_StrLen nRight = 0;
923 						while (pChar && *pChar-- == CH_TXTATR_INWORD)
924 							++nRight;
925 
926 						pArgs->pStartNode = this;
927 						pArgs->pEndNode = this;
928                         pArgs->pStartIdx->Assign(this, aScanner.GetEnd() - nRight );
929                         pArgs->pEndIdx->Assign(this, aScanner.GetBegin() + nLeft );
930 					}
931 				}
932 			}
933         }
934 	}
935 
936     // reset original text
937     if ( bRestoreString )
938     {
939         m_Text = aOldTxt;
940     }
941 
942     return pArgs->xSpellAlt.is() ? 1 : 0;
943 }
944 
945 
946 void SwTxtNode::SetLanguageAndFont( const SwPaM &rPaM,
947     LanguageType nLang, sal_uInt16 nLangWhichId,
948     const Font *pFont,  sal_uInt16 nFontWhichId )
949 {
950     sal_uInt16 aRanges[] = {
951             nLangWhichId, nLangWhichId,
952             nFontWhichId, nFontWhichId,
953             0, 0, 0 };
954     if (!pFont)
955         aRanges[2] = aRanges[3] = 0;    // clear entries with font WhichId
956 
957     SwEditShell *pEditShell = GetDoc()->GetEditShell();
958     SfxItemSet aSet( pEditShell->GetAttrPool(), aRanges );
959     aSet.Put( SvxLanguageItem( nLang, nLangWhichId ) );
960 
961     DBG_ASSERT( pFont, "target font missing?" );
962     if (pFont)
963     {
964         SvxFontItem aFontItem = (SvxFontItem&) aSet.Get( nFontWhichId );
965         aFontItem.SetFamilyName(   pFont->GetName());
966         aFontItem.SetFamily(       pFont->GetFamily());
967         aFontItem.SetStyleName(    pFont->GetStyleName());
968         aFontItem.SetPitch(        pFont->GetPitch());
969         aFontItem.SetCharSet( pFont->GetCharSet() );
970         aSet.Put( aFontItem );
971     }
972 
973     GetDoc()->InsertItemSet( rPaM, aSet, 0 );
974     // SetAttr( aSet );    <- Does not set language attribute of empty paragraphs correctly,
975     //                     <- because since there is no selection the flag to garbage
976     //                     <- collect all attributes is set, and therefore attributes spanned
977     //                     <- over empty selection are removed.
978 
979 }
980 
981 
982 sal_uInt16 SwTxtNode::Convert( SwConversionArgs &rArgs )
983 {
984     // get range of text within node to be converted
985     // (either all the text or the the text within the selection
986     // when the conversion was started)
987     xub_StrLen nTextBegin, nTextEnd;
988     //
989     if ( rArgs.pStartNode != this )
990 	{
991         nTextBegin = 0;
992 	}
993     else
994         nTextBegin = rArgs.pStartIdx->GetIndex();
995     if (nTextBegin > m_Text.Len())
996     {
997         nTextBegin = m_Text.Len();
998     }
999 
1000     nTextEnd = ( rArgs.pEndNode != this )
1001         ?  m_Text.Len()
1002         :  ::std::min( rArgs.pEndIdx->GetIndex(), m_Text.Len() );
1003 
1004     rArgs.aConvText = rtl::OUString();
1005 
1006     // modify string according to redline information and hidden text
1007     const XubString aOldTxt( m_Text );
1008     const bool bRestoreString =
1009         lcl_MaskRedlinesAndHiddenText( *this, m_Text, 0, m_Text.Len() ) > 0;
1010 
1011     sal_Bool    bFound  = sal_False;
1012     xub_StrLen  nBegin  = nTextBegin;
1013     xub_StrLen  nLen = 0;
1014 	LanguageType nLangFound = LANGUAGE_NONE;
1015     if (!m_Text.Len())
1016     {
1017         if (rArgs.bAllowImplicitChangesForNotConvertibleText)
1018         {
1019             // create SwPaM with mark & point spanning empty paragraph
1020             //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
1021             SwPaM aCurPaM( *this, 0 );
1022 
1023             SetLanguageAndFont( aCurPaM,
1024                     rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE,
1025                     rArgs.pTargetFont, RES_CHRATR_CJK_FONT );
1026         }
1027     }
1028     else
1029     {
1030         SwLanguageIterator aIter( *this, nBegin );
1031 
1032         // find non zero length text portion of appropriate language
1033         do {
1034 			nLangFound = aIter.GetLanguage();
1035             sal_Bool bLangOk =  (nLangFound == rArgs.nConvSrcLang) ||
1036                                 (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
1037                                  editeng::HangulHanjaConversion::IsChinese( rArgs.nConvSrcLang ));
1038 
1039 			xub_StrLen nChPos = aIter.GetChgPos();
1040 			// the position at the end of the paragraph returns -1
1041 			// which becomes 65535 when converted to xub_StrLen,
1042 			// and thus must be cut to the end of the actual string.
1043 			if (nChPos == (xub_StrLen) -1)
1044             {
1045                 nChPos = m_Text.Len();
1046             }
1047 
1048 			nLen = nChPos - nBegin;
1049 			bFound = bLangOk && nLen > 0;
1050 			if (!bFound)
1051             {
1052                 // create SwPaM with mark & point spanning the attributed text
1053                 //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
1054                 SwPaM aCurPaM( *this, nBegin );
1055                 aCurPaM.SetMark();
1056                 aCurPaM.GetPoint()->nContent = nBegin + nLen;
1057 
1058                 // check script type of selected text
1059                 SwEditShell *pEditShell = GetDoc()->GetEditShell();
1060                 pEditShell->Push();             // save current cursor on stack
1061                 pEditShell->SetSelection( aCurPaM );
1062                 sal_Bool bIsAsianScript = (SCRIPTTYPE_ASIAN == pEditShell->GetScriptType());
1063                 pEditShell->Pop( sal_False );   // restore cursor from stack
1064 
1065                 if (!bIsAsianScript && rArgs.bAllowImplicitChangesForNotConvertibleText)
1066                 {
1067                     SetLanguageAndFont( aCurPaM,
1068                             rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE,
1069                             rArgs.pTargetFont, RES_CHRATR_CJK_FONT );
1070                 }
1071 				nBegin = nChPos;    // start of next language portion
1072             }
1073         } while (!bFound && aIter.Next());	/* loop while nothing was found and still sth is left to be searched */
1074     }
1075 
1076     // keep resulting text within selection / range of text to be converted
1077     if (nBegin < nTextBegin)
1078         nBegin = nTextBegin;
1079     if (nBegin + nLen > nTextEnd)
1080         nLen = nTextEnd - nBegin;
1081     sal_Bool bInSelection = nBegin < nTextEnd;
1082 
1083     if (bFound && bInSelection)     // convertible text found within selection/range?
1084     {
1085         const XubString aTxtPortion = m_Text.Copy( nBegin, nLen );
1086         DBG_ASSERT( m_Text.Len() > 0, "convertible text portion missing!" );
1087         rArgs.aConvText     = m_Text.Copy( nBegin, nLen );
1088 		rArgs.nConvTextLang = nLangFound;
1089 
1090         // position where to start looking in next iteration (after current ends)
1091 		rArgs.pStartNode = this;
1092         rArgs.pStartIdx->Assign(this, nBegin + nLen );
1093         // end position (when we have travelled over the whole document)
1094         rArgs.pEndNode = this;
1095         rArgs.pEndIdx->Assign(this, nBegin );
1096 	}
1097 
1098     // restore original text
1099     if ( bRestoreString )
1100     {
1101         m_Text = aOldTxt;
1102     }
1103 
1104     return rArgs.aConvText.getLength() ? 1 : 0;
1105 }
1106 
1107 // Die Aehnlichkeiten zu SwTxtNode::Spell sind beabsichtigt ...
1108 // ACHTUNG: Ev. Bugs in beiden Routinen fixen!
1109 SwRect SwTxtFrm::_AutoSpell( const SwCntntNode* pActNode, const SwViewOption& rViewOpt, xub_StrLen nActPos )
1110 {
1111     SwRect aRect;
1112 #if OSL_DEBUG_LEVEL > 1
1113     static sal_Bool bStop = sal_False;
1114     if ( bStop )
1115         return aRect;
1116 #endif
1117     // Die Aehnlichkeiten zu SwTxtNode::Spell sind beabsichtigt ...
1118     // ACHTUNG: Ev. Bugs in beiden Routinen fixen!
1119     SwTxtNode *pNode = GetTxtNode();
1120     if( pNode != pActNode || !nActPos )
1121         nActPos = STRING_LEN;
1122 
1123     SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords();
1124 
1125     // modify string according to redline information and hidden text
1126     const XubString aOldTxt( pNode->GetTxt() );
1127     const bool bRestoreString =
1128             lcl_MaskRedlinesAndHiddenText( *pNode, pNode->m_Text,
1129                 0, pNode->GetTxt().Len() )     > 0;
1130 
1131     // a change of data indicates that at least one word has been modified
1132     const sal_Bool bRedlineChg =
1133         ( pNode->GetTxt().GetBuffer() != aOldTxt.GetBuffer() );
1134 
1135     xub_StrLen nBegin = 0;
1136     xub_StrLen nEnd = pNode->GetTxt().Len();
1137     xub_StrLen nInsertPos = 0;
1138     xub_StrLen nChgStart = STRING_LEN;
1139     xub_StrLen nChgEnd = 0;
1140     xub_StrLen nInvStart = STRING_LEN;
1141     xub_StrLen nInvEnd = 0;
1142 
1143     const sal_Bool bAddAutoCmpl = pNode->IsAutoCompleteWordDirty() &&
1144                                   rViewOpt.IsAutoCompleteWords();
1145 
1146     if( pNode->GetWrong() )
1147     {
1148         nBegin = pNode->GetWrong()->GetBeginInv();
1149         if( STRING_LEN != nBegin )
1150         {
1151             nEnd = pNode->GetWrong()->GetEndInv();
1152             if ( nEnd > pNode->GetTxt().Len() )
1153             {
1154                 nEnd = pNode->GetTxt().Len();
1155             }
1156         }
1157 
1158         // get word around nBegin, we start at nBegin - 1
1159         if ( STRING_LEN != nBegin )
1160         {
1161             if ( nBegin )
1162                 --nBegin;
1163 
1164             LanguageType eActLang = pNode->GetLang( nBegin );
1165             Boundary aBound =
1166                 pBreakIt->GetBreakIter()->getWordBoundary( pNode->GetTxt(), nBegin,
1167                     pBreakIt->GetLocale( eActLang ),
1168                     WordType::DICTIONARY_WORD, sal_True );
1169             nBegin = xub_StrLen(aBound.startPos);
1170         }
1171 
1172         // get the position in the wrong list
1173         nInsertPos = pNode->GetWrong()->GetWrongPos( nBegin );
1174 
1175         // sometimes we have to skip one entry
1176         if( nInsertPos < pNode->GetWrong()->Count() &&
1177             nBegin == pNode->GetWrong()->Pos( nInsertPos ) +
1178                       pNode->GetWrong()->Len( nInsertPos ) )
1179                 nInsertPos++;
1180     }
1181 
1182     sal_Bool bFresh = nBegin < nEnd;
1183 
1184     if( nBegin < nEnd )
1185     {
1186         //! register listener to LinguServiceEvents now in order to get
1187         //! notified about relevant changes in the future
1188         SwModule *pModule = SW_MOD();
1189         if (!pModule->GetLngSvcEvtListener().is())
1190             pModule->CreateLngSvcEvtListener();
1191 
1192 		uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() );
1193         SwDoc* pDoc = pNode->GetDoc();
1194 
1195         SwScanner aScanner( *pNode, pNode->GetTxt(), 0, 0,
1196                             WordType::DICTIONARY_WORD, nBegin, nEnd);
1197 
1198         while( aScanner.NextWord() )
1199         {
1200             const XubString& rWord = aScanner.GetWord();
1201             nBegin = aScanner.GetBegin();
1202             xub_StrLen nLen = aScanner.GetLen();
1203 
1204             // get next language for next word, consider language attributes
1205             // within the word
1206             LanguageType eActLang = aScanner.GetCurrentLanguage();
1207 
1208             sal_Bool bSpell = sal_True;
1209             bSpell = xSpell.is() ? xSpell->hasLanguage( eActLang ) : sal_False;
1210             if( bSpell && rWord.Len() > 0 )
1211             {
1212                 // check for: bAlter => xHyphWord.is()
1213                 DBG_ASSERT(!bSpell || xSpell.is(), "NULL pointer");
1214 
1215                 if( !xSpell->isValid( rWord, eActLang, Sequence< PropertyValue >() ) )
1216                 {
1217                     xub_StrLen nSmartTagStt = nBegin;
1218                     xub_StrLen nDummy = 1;
1219                     if ( !pNode->GetSmartTags() || !pNode->GetSmartTags()->InWrongWord( nSmartTagStt, nDummy ) )
1220                     {
1221                         if( !pNode->GetWrong() )
1222                         {
1223                             pNode->SetWrong( new SwWrongList( WRONGLIST_SPELL ) );
1224                             pNode->GetWrong()->SetInvalid( 0, nEnd );
1225                         }
1226                         if( pNode->GetWrong()->Fresh( nChgStart, nChgEnd,
1227                             nBegin, nLen, nInsertPos, nActPos ) )
1228                             pNode->GetWrong()->Insert( rtl::OUString(), 0, nBegin, nLen, nInsertPos++ );
1229                         else
1230                         {
1231                             nInvStart = nBegin;
1232                             nInvEnd = nBegin + nLen;
1233                         }
1234                     }
1235                 }
1236                 else if( bAddAutoCmpl && rACW.GetMinWordLen() <= rWord.Len() )
1237                 {
1238                     if ( bRedlineChg )
1239                     {
1240                         XubString rNewWord( rWord );
1241                         rACW.InsertWord( rNewWord, *pDoc );
1242                     }
1243                     else
1244                         rACW.InsertWord( rWord, *pDoc );
1245                 }
1246             }
1247         }
1248     }
1249 
1250     // reset original text
1251     // i63141 before calling GetCharRect(..) with formatting!
1252     if ( bRestoreString )
1253     {
1254         pNode->m_Text = aOldTxt;
1255     }
1256     if( pNode->GetWrong() )
1257     {
1258         if( bFresh )
1259             pNode->GetWrong()->Fresh( nChgStart, nChgEnd,
1260                                       nEnd, 0, nInsertPos, nActPos );
1261 
1262         //
1263         // Calculate repaint area:
1264         //
1265         if( nChgStart < nChgEnd )
1266         {
1267             aRect = lcl_CalculateRepaintRect( *this, nChgStart, nChgEnd );
1268         }
1269 
1270         pNode->GetWrong()->SetInvalid( nInvStart, nInvEnd );
1271         pNode->SetWrongDirty( STRING_LEN != pNode->GetWrong()->GetBeginInv() );
1272         if( !pNode->GetWrong()->Count() && ! pNode->IsWrongDirty() )
1273             pNode->SetWrong( NULL );
1274     }
1275     else
1276         pNode->SetWrongDirty( false );
1277 
1278     if( bAddAutoCmpl )
1279         pNode->SetAutoCompleteWordDirty( false );
1280 
1281     return aRect;
1282 }
1283 
1284 /** Function: SmartTagScan
1285 
1286     Function scans words in current text and checks them in the
1287     smarttag libraries. If the check returns true to bounds of the
1288     recognized words are stored into a list which is used later for drawing
1289     the underline.
1290 
1291     @param SwCntntNode* pActNode
1292 
1293     @param xub_StrLen nActPos
1294 
1295     @return SwRect: Repaint area
1296 */
1297 SwRect SwTxtFrm::SmartTagScan( SwCntntNode* /*pActNode*/, xub_StrLen /*nActPos*/ )
1298 {
1299     SwRect aRet;
1300     SwTxtNode *pNode = GetTxtNode();
1301     const rtl::OUString& rText = pNode->GetTxt();
1302 
1303     // Iterate over language portions
1304     SmartTagMgr& rSmartTagMgr = SwSmartTagMgr::Get();
1305 
1306     SwWrongList* pSmartTagList = pNode->GetSmartTags();
1307 
1308     xub_StrLen nBegin = 0;
1309     xub_StrLen nEnd = static_cast< xub_StrLen >(rText.getLength());
1310 
1311     if ( pSmartTagList )
1312     {
1313         if ( pSmartTagList->GetBeginInv() != STRING_LEN )
1314         {
1315             nBegin = pSmartTagList->GetBeginInv();
1316             nEnd = Min( pSmartTagList->GetEndInv(), (xub_StrLen)rText.getLength() );
1317 
1318             if ( nBegin < nEnd )
1319             {
1320                 const LanguageType aCurrLang = pNode->GetLang( nBegin );
1321                 const com::sun::star::lang::Locale aCurrLocale = pBreakIt->GetLocale( aCurrLang );
1322                 nBegin = static_cast< xub_StrLen >(pBreakIt->GetBreakIter()->beginOfSentence( rText, nBegin, aCurrLocale ));
1323                 nEnd = static_cast< xub_StrLen >(Min( rText.getLength(), pBreakIt->GetBreakIter()->endOfSentence( rText, nEnd, aCurrLocale ) ));
1324             }
1325         }
1326     }
1327 
1328     const sal_uInt16 nNumberOfEntries = pSmartTagList ? pSmartTagList->Count() : 0;
1329     sal_uInt16 nNumberOfRemovedEntries = 0;
1330     sal_uInt16 nNumberOfInsertedEntries = 0;
1331 
1332     // clear smart tag list between nBegin and nEnd:
1333     if ( 0 != nNumberOfEntries )
1334     {
1335         xub_StrLen nChgStart = STRING_LEN;
1336         xub_StrLen nChgEnd = 0;
1337         const sal_uInt16 nCurrentIndex = pSmartTagList->GetWrongPos( nBegin );
1338         pSmartTagList->Fresh( nChgStart, nChgEnd, nBegin, nEnd - nBegin, nCurrentIndex, STRING_LEN );
1339         nNumberOfRemovedEntries = nNumberOfEntries - pSmartTagList->Count();
1340     }
1341 
1342     if ( nBegin < nEnd )
1343     {
1344         // Expand the string:
1345         rtl::OUString aExpandText;
1346         const ModelToViewHelper::ConversionMap* pConversionMap =
1347                 pNode->BuildConversionMap( aExpandText );
1348 
1349         // Ownership ov ConversionMap is passed to SwXTextMarkup object!
1350         Reference< com::sun::star::text::XTextMarkup > xTextMarkup =
1351              new SwXTextMarkup( *pNode, pConversionMap );
1352 
1353         Reference< ::com::sun::star::frame::XController > xController = pNode->GetDoc()->GetDocShell()->GetController();
1354 
1355         xub_StrLen nLangBegin = nBegin;
1356         xub_StrLen nLangEnd = nEnd;
1357 
1358         // smart tag recognization has to be done for each language portion:
1359         SwLanguageIterator aIter( *pNode, nLangBegin );
1360 
1361         do
1362         {
1363             const LanguageType nLang = aIter.GetLanguage();
1364             const com::sun::star::lang::Locale aLocale = pBreakIt->GetLocale( nLang );
1365             nLangEnd = Min( nEnd, aIter.GetChgPos() );
1366 
1367             const sal_uInt32 nExpandBegin = ModelToViewHelper::ConvertToViewPosition( pConversionMap, nLangBegin );
1368             const sal_uInt32 nExpandEnd   = ModelToViewHelper::ConvertToViewPosition( pConversionMap, nLangEnd );
1369 
1370             rSmartTagMgr.Recognize( aExpandText, xTextMarkup, xController, aLocale, nExpandBegin, nExpandEnd - nExpandBegin );
1371 
1372             nLangBegin = nLangEnd;
1373         }
1374         while ( aIter.Next() && nLangEnd < nEnd );
1375 
1376         pSmartTagList = pNode->GetSmartTags();
1377 
1378         const sal_uInt16 nNumberOfEntriesAfterRecognize = pSmartTagList ? pSmartTagList->Count() : 0;
1379         nNumberOfInsertedEntries = nNumberOfEntriesAfterRecognize - ( nNumberOfEntries - nNumberOfRemovedEntries );
1380     }
1381 
1382     if( pSmartTagList )
1383 	{
1384         //
1385         // Update WrongList stuff
1386         //
1387         pSmartTagList->SetInvalid( STRING_LEN, 0 );
1388         pNode->SetSmartTagDirty( STRING_LEN != pSmartTagList->GetBeginInv() );
1389 
1390         if( !pSmartTagList->Count() && !pNode->IsSmartTagDirty() )
1391             pNode->SetSmartTags( NULL );
1392 
1393         //
1394         // Calculate repaint area:
1395         //
1396 #if OSL_DEBUG_LEVEL > 1
1397         const sal_uInt16 nNumberOfEntriesAfterRecognize2 = pSmartTagList->Count();
1398 		(void) nNumberOfEntriesAfterRecognize2;
1399 #endif
1400         if ( nBegin < nEnd && ( 0 != nNumberOfRemovedEntries ||
1401                                 0 != nNumberOfInsertedEntries ) )
1402 		{
1403             aRet = lcl_CalculateRepaintRect( *this, nBegin, nEnd );
1404         }
1405     }
1406 	else
1407         pNode->SetSmartTagDirty( false );
1408 
1409     return aRet;
1410 }
1411 
1412 
1413 // Wird vom CollectAutoCmplWords gerufen
1414 void SwTxtFrm::CollectAutoCmplWrds( SwCntntNode* pActNode, xub_StrLen nActPos )
1415 {
1416 	SwTxtNode *pNode = GetTxtNode();
1417 	if( pNode != pActNode || !nActPos )
1418 		nActPos = STRING_LEN;
1419 
1420     SwDoc* pDoc = pNode->GetDoc();
1421     SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords();
1422 
1423 	xub_StrLen nBegin = 0;
1424     xub_StrLen nEnd = pNode->GetTxt().Len();
1425 	xub_StrLen nLen;
1426 	sal_Bool bACWDirty = sal_False, bAnyWrd = sal_False;
1427 
1428 
1429 	if( nBegin < nEnd )
1430 	{
1431         sal_uInt16 nCnt = 200;
1432         SwScanner aScanner( *pNode, pNode->GetTxt(), 0, 0,
1433                             WordType::DICTIONARY_WORD, nBegin, nEnd );
1434         while( aScanner.NextWord() )
1435 		{
1436 			nBegin = aScanner.GetBegin();
1437 			nLen = aScanner.GetLen();
1438 			if( rACW.GetMinWordLen() <= nLen )
1439 			{
1440 				const XubString& rWord = aScanner.GetWord();
1441 
1442 				if( nActPos < nBegin || ( nBegin + nLen ) < nActPos )
1443 				{
1444                     if( rACW.GetMinWordLen() <= rWord.Len() )
1445                         rACW.InsertWord( rWord, *pDoc );
1446                     bAnyWrd = sal_True;
1447 				}
1448 				else
1449 					bACWDirty = sal_True;
1450 			}
1451             if( !--nCnt )
1452             {
1453                 if ( Application::AnyInput( INPUT_ANY ) )
1454                     return;
1455                 nCnt = 100;
1456             }
1457 		}
1458 	}
1459 
1460 	if( bAnyWrd && !bACWDirty )
1461 		pNode->SetAutoCompleteWordDirty( sal_False );
1462 }
1463 
1464 
1465 /*************************************************************************
1466  *						SwTxtNode::Hyphenate
1467  *************************************************************************/
1468 // Findet den TxtFrm und sucht dessen CalcHyph
1469 
1470 sal_Bool SwTxtNode::Hyphenate( SwInterHyphInfo &rHyphInf )
1471 {
1472 	// Abkuerzung: am Absatz ist keine Sprache eingestellt:
1473     if ( LANGUAGE_NONE == sal_uInt16( GetSwAttrSet().GetLanguage().GetLanguage() )
1474          && USHRT_MAX == GetLang( 0, m_Text.Len() ) )
1475     {
1476 		if( !rHyphInf.IsCheck() )
1477 			rHyphInf.SetNoLang( sal_True );
1478 		return sal_False;
1479 	}
1480 
1481 	if( pLinguNode != this )
1482 	{
1483 		pLinguNode = this;
1484 		pLinguFrm = (SwTxtFrm*)getLayoutFrm( GetDoc()->GetCurrentLayout(), (Point*)(rHyphInf.GetCrsrPos()) );
1485 	}
1486 	SwTxtFrm *pFrm = pLinguFrm;
1487 	if( pFrm )
1488         pFrm = &(pFrm->GetFrmAtOfst( rHyphInf.nStart ));
1489 	else
1490 	{
1491 		// 4935: Seit der Trennung ueber Sonderbereiche sind Faelle
1492 		// moeglich, in denen kein Frame zum Node vorliegt.
1493 		// Also kein ASSERT!
1494 #if OSL_DEBUG_LEVEL > 1
1495 		ASSERT( pFrm, "!SwTxtNode::Hyphenate: can't find any frame" );
1496 #endif
1497 		return sal_False;
1498 	}
1499 
1500 	while( pFrm )
1501 	{
1502 		if( pFrm->Hyphenate( rHyphInf ) )
1503 		{
1504 			// Das Layout ist nicht robust gegen "Direktformatierung"
1505 			// (7821, 7662, 7408); vgl. layact.cxx,
1506 			// SwLayAction::_TurboAction(), if( !pCnt->IsValid() ...
1507 			pFrm->SetCompletePaint();
1508 			return sal_True;
1509 		}
1510 		pFrm = (SwTxtFrm*)(pFrm->GetFollow());
1511 		if( pFrm )
1512 		{
1513 			rHyphInf.nLen = rHyphInf.nLen - (pFrm->GetOfst() - rHyphInf.nStart);
1514 			rHyphInf.nStart = pFrm->GetOfst();
1515 		}
1516 	}
1517 	return sal_False;
1518 }
1519 
1520 #ifdef LINGU_STATISTIK
1521 
1522 // globale Variable
1523 SwLinguStatistik aSwLinguStat;
1524 
1525 
1526 void SwLinguStatistik::Flush()
1527 {
1528 	if ( !nWords )
1529 		return ;
1530 
1531 	static char *pLogName = 0;
1532 	const sal_Bool bFirstOpen = pLogName ? sal_False : sal_True;
1533 	if( bFirstOpen )
1534 	{
1535 		char *pPath = getenv( "TEMP" );
1536 		char *pName = "swlingu.stk";
1537 		if( !pPath )
1538 			pLogName = pName;
1539 		else
1540 		{
1541 			const int nLen = strlen(pPath);
1542 			// fuer dieses new wird es kein delete geben.
1543 			pLogName = new char[nLen + strlen(pName) + 3];
1544 			if(nLen && (pPath[nLen-1] == '\\') || (pPath[nLen-1] == '/'))
1545 				snprintf( pLogName, sizeof(pLogName), "%s%s", pPath, pName );
1546 			else
1547 				snprintf( pLogName, sizeof(pLogName), "%s/%s", pPath, pName );
1548 		}
1549 	}
1550 	SvFileStream aStream( String::CreateFromAscii(pLogName), (bFirstOpen
1551 										? STREAM_WRITE | STREAM_TRUNC
1552 										: STREAM_WRITE ));
1553 
1554 	if( !aStream.GetError() )
1555 	{
1556 		if ( bFirstOpen )
1557 			aStream << "\nLinguistik-Statistik\n";
1558 		aStream << endl << ++nFlushCnt << ". Messung\n";
1559 		aStream << "Rechtschreibung\n";
1560 		aStream << "gepruefte Worte: \t" << nWords << endl;
1561 		aStream << "als fehlerhaft erkannt:\t" << nWrong << endl;
1562 		aStream << "Alternativvorschlaege:\t" << nAlter << endl;
1563 		if ( nWrong )
1564 			aStream << "Durchschnitt:\t\t" << nAlter*1.0 / nWrong << endl;
1565 		aStream << "Dauer (msec):\t\t" << nSpellTime << endl;
1566 		aStream << "\nThesaurus\n";
1567 		aStream << "Synonyme gesamt:\t" << nSynonym << endl;
1568 		if ( nSynonym )
1569 			aStream << "Synonym-Durchschnitt:\t" <<
1570 							nSynonym*1.0 / ( nWords - nNoSynonym ) << endl;
1571 		aStream << "ohne Synonyme:\t\t" << nNoSynonym << endl;
1572 		aStream << "Bedeutungen gesamt:\t" << nSynonym << endl;
1573 		aStream << "keine Bedeutungen:\t"<< nNoSynonym << endl;
1574 		aStream << "Dauer (msec):\t\t" << nTheTime << endl;
1575 		aStream << "\nHyphenator\n";
1576 		aStream << "Trennstellen gesamt:\t" << nHyphens << endl;
1577 		if ( nHyphens )
1578 			aStream << "Hyphen-Durchschnitt:\t" <<
1579 					nHyphens*1.0 / ( nWords - nNoHyph - nHyphErr ) << endl;
1580 		aStream << "keine Trennstellen:\t" << nNoHyph << endl;
1581 		aStream << "Trennung verweigert:\t" << nHyphErr << endl;
1582 		aStream << "Dauer (msec):\t\t" << nHyphTime << endl;
1583 		aStream << "---------------------------------------------\n";
1584 	}
1585 	nWords = nWrong = nAlter = nSynonym = nNoSynonym =
1586 	nHyphens = nNoHyph = nHyphErr = nSpellTime = nTheTime =
1587 	nHyphTime = 0;
1588 	//pThes = NULL;
1589 }
1590 
1591 #endif
1592 
1593 
1594 struct TransliterationChgData
1595 {
1596     xub_StrLen              nStart;
1597     xub_StrLen              nLen;
1598     String                  sChanged;
1599     Sequence< sal_Int32 >   aOffsets;
1600 };
1601 
1602 // change text to Upper/Lower/Hiragana/Katagana/...
1603 void SwTxtNode::TransliterateText(
1604     utl::TransliterationWrapper& rTrans,
1605     xub_StrLen nStt, xub_StrLen nEnd,
1606     SwUndoTransliterate* pUndo )
1607 {
1608     if (nStt < nEnd && pBreakIt->GetBreakIter().is())
1609 	{
1610         // since we don't use Hiragana/Katakana or half-width/full-width transliterations here
1611         // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will
1612         // occasionaly miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES
1613         // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the
1614         // proper thing to do.
1615         const sal_Int16 nWordType = WordType::ANYWORD_IGNOREWHITESPACES;
1616 
1617         //! In order to have less trouble with changing text size, e.g. because
1618         //! of ligatures or � (German small sz) being resolved, we need to process
1619         //! the text replacements from end to start.
1620         //! This way the offsets for the yet to be changed words will be
1621         //! left unchanged by the already replaced text.
1622         //! For this we temporarily save the changes to be done in this vector
1623         std::vector< TransliterationChgData >   aChanges;
1624         TransliterationChgData                  aChgData;
1625 
1626         if (rTrans.getType() == (sal_uInt32)TransliterationModulesExtra::TITLE_CASE)
1627         {
1628             // for 'capitalize every word' we need to iterate over each word
1629 
1630             Boundary aSttBndry;
1631             Boundary aEndBndry;
1632             aSttBndry = pBreakIt->GetBreakIter()->getWordBoundary(
1633                         GetTxt(), nStt,
1634                         pBreakIt->GetLocale( GetLang( nStt ) ),
1635                         nWordType,
1636                         sal_True /*prefer forward direction*/);
1637             aEndBndry = pBreakIt->GetBreakIter()->getWordBoundary(
1638                         GetTxt(), nEnd,
1639                         pBreakIt->GetLocale( GetLang( nEnd ) ),
1640                         nWordType,
1641                         sal_False /*prefer backward direction*/);
1642 
1643             // prevent backtracking to the previous word if selection is at word boundary
1644             if (aSttBndry.endPos <= nStt)
1645             {
1646                 aSttBndry = pBreakIt->GetBreakIter()->nextWord(
1647                         GetTxt(), aSttBndry.endPos,
1648                         pBreakIt->GetLocale( GetLang( aSttBndry.endPos ) ),
1649                         nWordType);
1650             }
1651             // prevent advancing to the next word if selection is at word boundary
1652             if (aEndBndry.startPos >= nEnd)
1653             {
1654                 aEndBndry = pBreakIt->GetBreakIter()->previousWord(
1655                         GetTxt(), aEndBndry.startPos,
1656                         pBreakIt->GetLocale( GetLang( aEndBndry.startPos ) ),
1657                         nWordType);
1658             }
1659 
1660             Boundary aCurWordBndry( aSttBndry );
1661             while (aCurWordBndry.startPos <= aEndBndry.startPos)
1662             {
1663                 nStt = (xub_StrLen)aCurWordBndry.startPos;
1664                 nEnd = (xub_StrLen)aCurWordBndry.endPos;
1665                 sal_Int32 nLen = nEnd - nStt;
1666                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
1667 #if OSL_DEBUG_LEVEL > 1
1668                 String aText( GetTxt().Copy( nStt, nLen ) );
1669 #endif
1670 
1671 		        Sequence <sal_Int32> aOffsets;
1672                 String sChgd( rTrans.transliterate( GetTxt(), GetLang( nStt ), nStt, nLen, &aOffsets ));
1673 
1674                 if (!m_Text.Equals( sChgd, nStt, nLen ))
1675                 {
1676                     aChgData.nStart     = nStt;
1677                     aChgData.nLen       = nLen;
1678                     aChgData.sChanged   = sChgd;
1679                     aChgData.aOffsets   = aOffsets;
1680                     aChanges.push_back( aChgData );
1681                 }
1682 
1683                 aCurWordBndry = pBreakIt->GetBreakIter()->nextWord(
1684                         GetTxt(), nEnd,
1685                         pBreakIt->GetLocale( GetLang( nEnd ) ),
1686                         nWordType);
1687             }
1688         }
1689         else if (rTrans.getType() == (sal_uInt32)TransliterationModulesExtra::SENTENCE_CASE)
1690         {
1691             // for 'sentence case' we need to iterate sentence by sentence
1692 
1693             sal_Int32 nLastStart = pBreakIt->GetBreakIter()->beginOfSentence(
1694                     GetTxt(), nEnd,
1695                     pBreakIt->GetLocale( GetLang( nEnd ) ) );
1696             sal_Int32 nLastEnd = pBreakIt->GetBreakIter()->endOfSentence(
1697                     GetTxt(), nLastStart,
1698                     pBreakIt->GetLocale( GetLang( nLastStart ) ) );
1699 
1700             // extend nStt, nEnd to the current sentence boundaries
1701             sal_Int32 nCurrentStart = pBreakIt->GetBreakIter()->beginOfSentence(
1702                     GetTxt(), nStt,
1703                     pBreakIt->GetLocale( GetLang( nStt ) ) );
1704             sal_Int32 nCurrentEnd = pBreakIt->GetBreakIter()->endOfSentence(
1705                     GetTxt(), nCurrentStart,
1706                     pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
1707 
1708             // prevent backtracking to the previous sentence if selection starts at end of a sentence
1709             if (nCurrentEnd <= nStt)
1710             {
1711                 // now nCurrentStart is probably located on a non-letter word. (unless we
1712                 // are in Asian text with no spaces...)
1713                 // Thus to get the real sentence start we should locate the next real word,
1714                 // that is one found by DICTIONARY_WORD
1715                 i18n::Boundary aBndry = pBreakIt->GetBreakIter()->nextWord(
1716                         GetTxt(), nCurrentEnd,
1717                         pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
1718                         i18n::WordType::DICTIONARY_WORD);
1719 
1720                 // now get new current sentence boundaries
1721                 nCurrentStart = pBreakIt->GetBreakIter()->beginOfSentence(
1722                         GetTxt(), aBndry.startPos,
1723                         pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
1724                 nCurrentEnd = pBreakIt->GetBreakIter()->endOfSentence(
1725                         GetTxt(), nCurrentStart,
1726                         pBreakIt->GetLocale( GetLang( nCurrentStart) ) );
1727             }
1728             // prevent advancing to the next sentence if selection ends at start of a sentence
1729             if (nLastStart >= nEnd)
1730             {
1731                 // now nCurrentStart is probably located on a non-letter word. (unless we
1732                 // are in Asian text with no spaces...)
1733                 // Thus to get the real sentence start we should locate the previous real word,
1734                 // that is one found by DICTIONARY_WORD
1735                 i18n::Boundary aBndry = pBreakIt->GetBreakIter()->previousWord(
1736                         GetTxt(), nLastStart,
1737                         pBreakIt->GetLocale( GetLang( nLastStart) ),
1738                         i18n::WordType::DICTIONARY_WORD);
1739                 nLastEnd = pBreakIt->GetBreakIter()->endOfSentence(
1740                         GetTxt(), aBndry.startPos,
1741                         pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
1742                 if (nCurrentEnd > nLastEnd)
1743                     nCurrentEnd = nLastEnd;
1744             }
1745 
1746             while (nCurrentStart < nLastEnd)
1747             {
1748                 sal_Int32 nLen = nCurrentEnd - nCurrentStart;
1749                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
1750 #if OSL_DEBUG_LEVEL > 1
1751                 String aText( GetTxt().Copy( nCurrentStart, nLen ) );
1752 #endif
1753 
1754 		        Sequence <sal_Int32> aOffsets;
1755                 String sChgd( rTrans.transliterate( GetTxt(),
1756                         GetLang( nCurrentStart ), nCurrentStart, nLen, &aOffsets ));
1757 
1758                 if (!m_Text.Equals( sChgd, nStt, nLen ))
1759                 {
1760                     aChgData.nStart     = nCurrentStart;
1761                     aChgData.nLen       = nLen;
1762                     aChgData.sChanged   = sChgd;
1763                     aChgData.aOffsets   = aOffsets;
1764                     aChanges.push_back( aChgData );
1765                 }
1766 
1767                 Boundary aFirstWordBndry;
1768                 aFirstWordBndry = pBreakIt->GetBreakIter()->nextWord(
1769                         GetTxt(), nCurrentEnd,
1770                         pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
1771                         nWordType);
1772                 nCurrentStart = aFirstWordBndry.startPos;
1773                 nCurrentEnd = pBreakIt->GetBreakIter()->endOfSentence(
1774                         GetTxt(), nCurrentStart,
1775                         pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
1776             }
1777         }
1778         else
1779         {
1780             // here we may transliterate over complete language portions...
1781 
1782 		    SwLanguageIterator* pIter;
1783 		    if( rTrans.needLanguageForTheMode() )
1784                 pIter = new SwLanguageIterator( *this, nStt );
1785 		    else
1786 			    pIter = 0;
1787 
1788 		    xub_StrLen nEndPos;
1789 		    sal_uInt16 nLang;
1790 		    do {
1791 			    if( pIter )
1792 			    {
1793 				    nLang = pIter->GetLanguage();
1794 				    nEndPos = pIter->GetChgPos();
1795 				    if( nEndPos > nEnd )
1796 					    nEndPos = nEnd;
1797 			    }
1798 			    else
1799 			    {
1800 				    nLang = LANGUAGE_SYSTEM;
1801 				    nEndPos = nEnd;
1802 			    }
1803                 xub_StrLen nLen = nEndPos - nStt;
1804 
1805 			    Sequence <sal_Int32> aOffsets;
1806                 String sChgd( rTrans.transliterate( m_Text, nLang, nStt, nLen, &aOffsets ));
1807 
1808                 if (!m_Text.Equals( sChgd, nStt, nLen ))
1809                 {
1810                     aChgData.nStart     = nStt;
1811                     aChgData.nLen       = nLen;
1812                     aChgData.sChanged   = sChgd;
1813                     aChgData.aOffsets   = aOffsets;
1814                     aChanges.push_back( aChgData );
1815                 }
1816 
1817                 nStt = nEndPos;
1818 		    } while( nEndPos < nEnd && pIter && pIter->Next() );
1819 		    delete pIter;
1820         }
1821 
1822         if (aChanges.size() > 0)
1823         {
1824             // now apply the changes from end to start to leave the offsets of the
1825             // yet unchanged text parts remain the same.
1826             for (size_t i = 0; i < aChanges.size(); ++i)
1827             {
1828                 TransliterationChgData &rData = aChanges[ aChanges.size() - 1 - i ];
1829                 if (pUndo)
1830                     pUndo->AddChanges( *this, rData.nStart, rData.nLen, rData.aOffsets );
1831                 ReplaceTextOnly( rData.nStart, rData.nLen, rData.sChanged, rData.aOffsets );
1832             }
1833         }
1834     }
1835 }
1836 
1837 
1838 void SwTxtNode::ReplaceTextOnly( xub_StrLen nPos, xub_StrLen nLen,
1839 								const XubString& rText,
1840 								const Sequence<sal_Int32>& rOffsets )
1841 {
1842     m_Text.Replace( nPos, nLen, rText );
1843 
1844 	xub_StrLen nTLen = rText.Len();
1845 	const sal_Int32* pOffsets = rOffsets.getConstArray();
1846 	// now look for no 1-1 mapping -> move the indizies!
1847 	xub_StrLen nI, nMyOff;
1848 	for( nI = 0, nMyOff = nPos; nI < nTLen; ++nI, ++nMyOff )
1849 	{
1850 		xub_StrLen nOff = (xub_StrLen)pOffsets[ nI ];
1851 		if( nOff < nMyOff )
1852 		{
1853 			// something is inserted
1854 			xub_StrLen nCnt = 1;
1855 			while( nI + nCnt < nTLen && nOff == pOffsets[ nI + nCnt ] )
1856 				++nCnt;
1857 
1858 			Update( SwIndex( this, nMyOff ), nCnt, sal_False );
1859 			nMyOff = nOff;
1860 			//nMyOff -= nCnt;
1861 			nI += nCnt - 1;
1862 		}
1863 		else if( nOff > nMyOff )
1864 		{
1865 			// something is deleted
1866 			Update( SwIndex( this, nMyOff+1 ), nOff - nMyOff, sal_True );
1867 			nMyOff = nOff;
1868 		}
1869 	}
1870 	if( nMyOff < nLen )
1871 		// something is deleted at the end
1872 		Update( SwIndex( this, nMyOff ), nLen - nMyOff, sal_True );
1873 
1874 	// notify the layout!
1875 	SwDelTxt aDelHint( nPos, nTLen );
1876 	NotifyClients( 0, &aDelHint );
1877 
1878 	SwInsTxt aHint( nPos, nTLen );
1879 	NotifyClients( 0, &aHint );
1880 }
1881 
1882 void SwTxtNode::CountWords( SwDocStat& rStat,
1883                             xub_StrLen nStt, xub_StrLen nEnd ) const
1884 {
1885     ++rStat.nAllPara; // #i93174#: count _all_ paragraphs
1886     if( nStt < nEnd )
1887     {
1888         if ( !IsHidden() )
1889         {
1890             ++rStat.nPara;
1891             sal_uLong nTmpWords = 0;
1892             sal_uLong nTmpChars = 0;
1893 
1894             // Shortcut: Whole paragraph should be considered and cached values
1895             // are valid:
1896             if ( 0 == nStt && GetTxt().Len() == nEnd && !IsWordCountDirty() )
1897             {
1898                 nTmpWords = GetParaNumberOfWords();
1899                 nTmpChars = GetParaNumberOfChars();
1900             }
1901             else
1902             {
1903                 String aOldStr( m_Text );
1904                 String& rCastStr = const_cast<String&>(m_Text);
1905 
1906                 // fills the deleted redlines and hidden ranges with cChar:
1907                 const xub_Unicode cChar(' ');
1908                 const sal_uInt16 nNumOfMaskedChars =
1909                         lcl_MaskRedlinesAndHiddenText( *this, rCastStr, nStt, nEnd, cChar, false );
1910 
1911                 // expand fields
1912                 rtl::OUString aExpandText;
1913                 const ModelToViewHelper::ConversionMap* pConversionMap =
1914                         BuildConversionMap( aExpandText );
1915 
1916                 const sal_uInt32 nExpandBegin = ModelToViewHelper::ConvertToViewPosition( pConversionMap, nStt );
1917                 const sal_uInt32 nExpandEnd   = ModelToViewHelper::ConvertToViewPosition( pConversionMap, nEnd );
1918 
1919                 const bool bCount = aExpandText.getLength() > 0;
1920 
1921                 // count words in 'regular' text:
1922                 if( bCount && pBreakIt->GetBreakIter().is() )
1923                 {
1924                     const String aScannerText( aExpandText );
1925                     SwScanner aScanner( *this, aScannerText, 0, pConversionMap,
1926                                         i18n::WordType::WORD_COUNT,
1927                                         (xub_StrLen)nExpandBegin, (xub_StrLen)nExpandEnd );
1928 
1929                     const rtl::OUString aBreakWord( CH_TXTATR_BREAKWORD );
1930 
1931                     while ( aScanner.NextWord() )
1932                     {
1933                         if ( aScanner.GetLen() > 1 ||
1934                              CH_TXTATR_BREAKWORD != aExpandText.match(aBreakWord, aScanner.GetBegin() ) )
1935                             ++nTmpWords;
1936                     }
1937                 }
1938 
1939                 ASSERT( aExpandText.getLength() >= nNumOfMaskedChars,
1940                         "More characters hidden that characters in string!" )
1941                 nTmpChars = nExpandEnd - nExpandBegin - nNumOfMaskedChars;
1942 
1943                 // count words in numbering string:
1944                 if ( nStt == 0 && bCount )
1945                 {
1946                     // add numbering label
1947                     const String aNumString = GetNumString();
1948                     const xub_StrLen nNumStringLen = aNumString.Len();
1949                     if ( nNumStringLen > 0 )
1950                     {
1951                         LanguageType aLanguage = GetLang( 0 );
1952 
1953                         SwScanner aScanner( *this, aNumString, &aLanguage, 0,
1954                                             i18n::WordType::WORD_COUNT,
1955                                             0, nNumStringLen );
1956 
1957                         while ( aScanner.NextWord() )
1958                             ++nTmpWords;
1959 
1960                         nTmpChars += nNumStringLen;
1961                     }
1962                     else if ( HasBullet() )
1963                     {
1964                         ++nTmpWords;
1965                         ++nTmpChars;
1966                     }
1967                 }
1968 
1969                 delete pConversionMap;
1970 
1971                 rCastStr = aOldStr;
1972 
1973                 // If the whole paragraph has been calculated, update cached
1974                 // values:
1975                 if ( 0 == nStt && GetTxt().Len() == nEnd )
1976                 {
1977                     SetParaNumberOfWords( nTmpWords );
1978                     SetParaNumberOfChars( nTmpChars );
1979                     SetWordCountDirty( false );
1980                 }
1981             }
1982 
1983             rStat.nWord += nTmpWords;
1984             rStat.nChar += nTmpChars;
1985         }
1986     }
1987 }
1988 
1989 //
1990 // Paragraph statistics start
1991 //
1992 struct SwParaIdleData_Impl
1993 {
1994     SwWrongList* pWrong;            // for spell checking
1995     SwGrammarMarkUp* pGrammarCheck;     // for grammar checking /  proof reading
1996     SwWrongList* pSmartTags;
1997     sal_uLong nNumberOfWords;
1998     sal_uLong nNumberOfChars;
1999     bool bWordCountDirty        : 1;
2000     bool bWrongDirty            : 1;    // Ist das Wrong-Feld auf invalid?
2001     bool bGrammarCheckDirty     : 1;
2002     bool bSmartTagDirty         : 1;
2003     bool bAutoComplDirty        : 1;    // die ACompl-Liste muss angepasst werden
2004 
2005     SwParaIdleData_Impl() :
2006         pWrong              ( 0 ),
2007         pGrammarCheck       ( 0 ),
2008         pSmartTags          ( 0 ),
2009         nNumberOfWords      ( 0 ),
2010         nNumberOfChars      ( 0 ),
2011         bWordCountDirty     ( true ),
2012         bWrongDirty         ( true ),
2013         bGrammarCheckDirty  ( true ),
2014         bSmartTagDirty      ( true ),
2015         bAutoComplDirty     ( true ) {};
2016 };
2017 
2018 void SwTxtNode::InitSwParaStatistics( bool bNew )
2019 {
2020     if ( bNew )
2021     {
2022         m_pParaIdleData_Impl = new SwParaIdleData_Impl;
2023     }
2024     else if ( m_pParaIdleData_Impl )
2025     {
2026         delete m_pParaIdleData_Impl->pWrong;
2027         delete m_pParaIdleData_Impl->pGrammarCheck;
2028         delete m_pParaIdleData_Impl->pSmartTags;
2029         delete m_pParaIdleData_Impl;
2030         m_pParaIdleData_Impl = 0;
2031     }
2032 }
2033 
2034 void SwTxtNode::SetWrong( SwWrongList* pNew, bool bDelete )
2035 {
2036     if ( m_pParaIdleData_Impl )
2037     {
2038         if ( bDelete )
2039         {
2040             delete m_pParaIdleData_Impl->pWrong;
2041         }
2042         m_pParaIdleData_Impl->pWrong = pNew;
2043     }
2044 }
2045 
2046 SwWrongList* SwTxtNode::GetWrong()
2047 {
2048     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pWrong : 0;
2049 }
2050 
2051 // --> OD 2008-05-27 #i71360#
2052 const SwWrongList* SwTxtNode::GetWrong() const
2053 {
2054     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pWrong : 0;
2055 }
2056 // <--
2057 
2058 
2059 void SwTxtNode::SetGrammarCheck( SwGrammarMarkUp* pNew, bool bDelete )
2060 {
2061     if ( m_pParaIdleData_Impl )
2062     {
2063         if ( bDelete )
2064         {
2065             delete m_pParaIdleData_Impl->pGrammarCheck;
2066         }
2067         m_pParaIdleData_Impl->pGrammarCheck = pNew;
2068     }
2069 }
2070 
2071 SwGrammarMarkUp* SwTxtNode::GetGrammarCheck()
2072 {
2073     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pGrammarCheck : 0;
2074 }
2075 
2076 void SwTxtNode::SetSmartTags( SwWrongList* pNew, bool bDelete )
2077 {
2078     ASSERT( !pNew || SwSmartTagMgr::Get().IsSmartTagsEnabled(),
2079             "Weird - we have a smart tag list without any recognizers?" )
2080 
2081     if ( m_pParaIdleData_Impl )
2082     {
2083         if ( bDelete )
2084         {
2085             delete m_pParaIdleData_Impl->pSmartTags;
2086         }
2087         m_pParaIdleData_Impl->pSmartTags = pNew;
2088     }
2089 }
2090 
2091 SwWrongList* SwTxtNode::GetSmartTags()
2092 {
2093     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pSmartTags : 0;
2094 }
2095 
2096 void SwTxtNode::SetParaNumberOfWords( sal_uLong nNew ) const
2097 {
2098     if ( m_pParaIdleData_Impl )
2099     {
2100         m_pParaIdleData_Impl->nNumberOfWords = nNew;
2101     }
2102 }
2103 sal_uLong SwTxtNode::GetParaNumberOfWords() const
2104 {
2105     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->nNumberOfWords : 0;
2106 }
2107 void SwTxtNode::SetParaNumberOfChars( sal_uLong nNew ) const
2108 {
2109     if ( m_pParaIdleData_Impl )
2110     {
2111         m_pParaIdleData_Impl->nNumberOfChars = nNew;
2112     }
2113 }
2114 sal_uLong SwTxtNode::GetParaNumberOfChars() const
2115 {
2116     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->nNumberOfChars : 0;
2117 }
2118 void SwTxtNode::SetWordCountDirty( bool bNew ) const
2119 {
2120     if ( m_pParaIdleData_Impl )
2121     {
2122         m_pParaIdleData_Impl->bWordCountDirty = bNew;
2123     }
2124 }
2125 bool SwTxtNode::IsWordCountDirty() const
2126 {
2127     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bWordCountDirty : 0;
2128 }
2129 void SwTxtNode::SetWrongDirty( bool bNew ) const
2130 {
2131     if ( m_pParaIdleData_Impl )
2132     {
2133         m_pParaIdleData_Impl->bWrongDirty = bNew;
2134     }
2135 }
2136 bool SwTxtNode::IsWrongDirty() const
2137 {
2138     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bWrongDirty : 0;
2139 }
2140 void SwTxtNode::SetGrammarCheckDirty( bool bNew ) const
2141 {
2142     if ( m_pParaIdleData_Impl )
2143     {
2144         m_pParaIdleData_Impl->bGrammarCheckDirty = bNew;
2145     }
2146 }
2147 bool SwTxtNode::IsGrammarCheckDirty() const
2148 {
2149     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bGrammarCheckDirty : 0;
2150 }
2151 void SwTxtNode::SetSmartTagDirty( bool bNew ) const
2152 {
2153     if ( m_pParaIdleData_Impl )
2154     {
2155         m_pParaIdleData_Impl->bSmartTagDirty = bNew;
2156     }
2157 }
2158 bool SwTxtNode::IsSmartTagDirty() const
2159 {
2160     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bSmartTagDirty : 0;
2161 }
2162 void SwTxtNode::SetAutoCompleteWordDirty( bool bNew ) const
2163 {
2164     if ( m_pParaIdleData_Impl )
2165     {
2166         m_pParaIdleData_Impl->bAutoComplDirty = bNew;
2167     }
2168 }
2169 bool SwTxtNode::IsAutoCompleteWordDirty() const
2170 {
2171     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bAutoComplDirty : 0;
2172 }
2173 //
2174 // Paragraph statistics end
2175 //
2176