xref: /aoo42x/main/sw/source/core/txtnode/txtedt.cxx (revision 00a33fa1)
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 namespace sw // #i120045# namespace to avoid XCode template-misoptimization
1594 {
1595 struct TransliterationChgData
1596 {
1597     xub_StrLen              nStart;
1598     xub_StrLen              nLen;
1599     String                  sChanged;
1600     Sequence< sal_Int32 >   aOffsets;
1601 };
1602 }
1603 using sw::TransliterationChgData;
1604 
1605 // change text to Upper/Lower/Hiragana/Katagana/...
1606 void SwTxtNode::TransliterateText(
1607     utl::TransliterationWrapper& rTrans,
1608     xub_StrLen nStt, xub_StrLen nEnd,
1609     SwUndoTransliterate* pUndo )
1610 {
1611     if (nStt < nEnd && pBreakIt->GetBreakIter().is())
1612 	{
1613         // since we don't use Hiragana/Katakana or half-width/full-width transliterations here
1614         // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will
1615         // occasionaly miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES
1616         // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the
1617         // proper thing to do.
1618         const sal_Int16 nWordType = WordType::ANYWORD_IGNOREWHITESPACES;
1619 
1620         //! In order to have less trouble with changing text size, e.g. because
1621         //! of ligatures or � (German small sz) being resolved, we need to process
1622         //! the text replacements from end to start.
1623         //! This way the offsets for the yet to be changed words will be
1624         //! left unchanged by the already replaced text.
1625         //! For this we temporarily save the changes to be done in this vector
1626         std::vector< TransliterationChgData >   aChanges;
1627         TransliterationChgData                  aChgData;
1628 
1629         if (rTrans.getType() == (sal_uInt32)TransliterationModulesExtra::TITLE_CASE)
1630         {
1631             // for 'capitalize every word' we need to iterate over each word
1632 
1633             Boundary aSttBndry;
1634             Boundary aEndBndry;
1635             aSttBndry = pBreakIt->GetBreakIter()->getWordBoundary(
1636                         GetTxt(), nStt,
1637                         pBreakIt->GetLocale( GetLang( nStt ) ),
1638                         nWordType,
1639                         sal_True /*prefer forward direction*/);
1640             aEndBndry = pBreakIt->GetBreakIter()->getWordBoundary(
1641                         GetTxt(), nEnd,
1642                         pBreakIt->GetLocale( GetLang( nEnd ) ),
1643                         nWordType,
1644                         sal_False /*prefer backward direction*/);
1645 
1646             // prevent backtracking to the previous word if selection is at word boundary
1647             if (aSttBndry.endPos <= nStt)
1648             {
1649                 aSttBndry = pBreakIt->GetBreakIter()->nextWord(
1650                         GetTxt(), aSttBndry.endPos,
1651                         pBreakIt->GetLocale( GetLang( aSttBndry.endPos ) ),
1652                         nWordType);
1653             }
1654             // prevent advancing to the next word if selection is at word boundary
1655             if (aEndBndry.startPos >= nEnd)
1656             {
1657                 aEndBndry = pBreakIt->GetBreakIter()->previousWord(
1658                         GetTxt(), aEndBndry.startPos,
1659                         pBreakIt->GetLocale( GetLang( aEndBndry.startPos ) ),
1660                         nWordType);
1661             }
1662 
1663             Boundary aCurWordBndry( aSttBndry );
1664             while (aCurWordBndry.startPos <= aEndBndry.startPos)
1665             {
1666                 nStt = (xub_StrLen)aCurWordBndry.startPos;
1667                 nEnd = (xub_StrLen)aCurWordBndry.endPos;
1668                 sal_Int32 nLen = nEnd - nStt;
1669                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
1670 #if OSL_DEBUG_LEVEL > 1
1671                 String aText( GetTxt().Copy( nStt, nLen ) );
1672 #endif
1673 
1674 		        Sequence <sal_Int32> aOffsets;
1675                 String sChgd( rTrans.transliterate( GetTxt(), GetLang( nStt ), nStt, nLen, &aOffsets ));
1676 
1677                 if (!m_Text.Equals( sChgd, nStt, nLen ))
1678                 {
1679                     aChgData.nStart     = nStt;
1680                     aChgData.nLen       = nLen;
1681                     aChgData.sChanged   = sChgd;
1682                     aChgData.aOffsets   = aOffsets;
1683                     aChanges.push_back( aChgData );
1684                 }
1685 
1686                 aCurWordBndry = pBreakIt->GetBreakIter()->nextWord(
1687                         GetTxt(), nEnd,
1688                         pBreakIt->GetLocale( GetLang( nEnd ) ),
1689                         nWordType);
1690             }
1691         }
1692         else if (rTrans.getType() == (sal_uInt32)TransliterationModulesExtra::SENTENCE_CASE)
1693         {
1694             // for 'sentence case' we need to iterate sentence by sentence
1695 
1696             sal_Int32 nLastStart = pBreakIt->GetBreakIter()->beginOfSentence(
1697                     GetTxt(), nEnd,
1698                     pBreakIt->GetLocale( GetLang( nEnd ) ) );
1699             sal_Int32 nLastEnd = pBreakIt->GetBreakIter()->endOfSentence(
1700                     GetTxt(), nLastStart,
1701                     pBreakIt->GetLocale( GetLang( nLastStart ) ) );
1702 
1703             // extend nStt, nEnd to the current sentence boundaries
1704             sal_Int32 nCurrentStart = pBreakIt->GetBreakIter()->beginOfSentence(
1705                     GetTxt(), nStt,
1706                     pBreakIt->GetLocale( GetLang( nStt ) ) );
1707             sal_Int32 nCurrentEnd = pBreakIt->GetBreakIter()->endOfSentence(
1708                     GetTxt(), nCurrentStart,
1709                     pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
1710 
1711             // prevent backtracking to the previous sentence if selection starts at end of a sentence
1712             if (nCurrentEnd <= nStt)
1713             {
1714                 // now nCurrentStart is probably located on a non-letter word. (unless we
1715                 // are in Asian text with no spaces...)
1716                 // Thus to get the real sentence start we should locate the next real word,
1717                 // that is one found by DICTIONARY_WORD
1718                 i18n::Boundary aBndry = pBreakIt->GetBreakIter()->nextWord(
1719                         GetTxt(), nCurrentEnd,
1720                         pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
1721                         i18n::WordType::DICTIONARY_WORD);
1722 
1723                 // now get new current sentence boundaries
1724                 nCurrentStart = pBreakIt->GetBreakIter()->beginOfSentence(
1725                         GetTxt(), aBndry.startPos,
1726                         pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
1727                 nCurrentEnd = pBreakIt->GetBreakIter()->endOfSentence(
1728                         GetTxt(), nCurrentStart,
1729                         pBreakIt->GetLocale( GetLang( nCurrentStart) ) );
1730             }
1731             // prevent advancing to the next sentence if selection ends at start of a sentence
1732             if (nLastStart >= nEnd)
1733             {
1734                 // now nCurrentStart is probably located on a non-letter word. (unless we
1735                 // are in Asian text with no spaces...)
1736                 // Thus to get the real sentence start we should locate the previous real word,
1737                 // that is one found by DICTIONARY_WORD
1738                 i18n::Boundary aBndry = pBreakIt->GetBreakIter()->previousWord(
1739                         GetTxt(), nLastStart,
1740                         pBreakIt->GetLocale( GetLang( nLastStart) ),
1741                         i18n::WordType::DICTIONARY_WORD);
1742                 nLastEnd = pBreakIt->GetBreakIter()->endOfSentence(
1743                         GetTxt(), aBndry.startPos,
1744                         pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
1745                 if (nCurrentEnd > nLastEnd)
1746                     nCurrentEnd = nLastEnd;
1747             }
1748 
1749             while (nCurrentStart < nLastEnd)
1750             {
1751                 sal_Int32 nLen = nCurrentEnd - nCurrentStart;
1752                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
1753 #if OSL_DEBUG_LEVEL > 1
1754                 String aText( GetTxt().Copy( nCurrentStart, nLen ) );
1755 #endif
1756 
1757 		        Sequence <sal_Int32> aOffsets;
1758                 String sChgd( rTrans.transliterate( GetTxt(),
1759                         GetLang( nCurrentStart ), nCurrentStart, nLen, &aOffsets ));
1760 
1761                 if (!m_Text.Equals( sChgd, nStt, nLen ))
1762                 {
1763                     aChgData.nStart     = nCurrentStart;
1764                     aChgData.nLen       = nLen;
1765                     aChgData.sChanged   = sChgd;
1766                     aChgData.aOffsets   = aOffsets;
1767                     aChanges.push_back( aChgData );
1768                 }
1769 
1770                 Boundary aFirstWordBndry;
1771                 aFirstWordBndry = pBreakIt->GetBreakIter()->nextWord(
1772                         GetTxt(), nCurrentEnd,
1773                         pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
1774                         nWordType);
1775                 nCurrentStart = aFirstWordBndry.startPos;
1776                 nCurrentEnd = pBreakIt->GetBreakIter()->endOfSentence(
1777                         GetTxt(), nCurrentStart,
1778                         pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
1779             }
1780         }
1781         else
1782         {
1783             // here we may transliterate over complete language portions...
1784 
1785 		    SwLanguageIterator* pIter;
1786 		    if( rTrans.needLanguageForTheMode() )
1787                 pIter = new SwLanguageIterator( *this, nStt );
1788 		    else
1789 			    pIter = 0;
1790 
1791 		    xub_StrLen nEndPos;
1792 		    sal_uInt16 nLang;
1793 		    do {
1794 			    if( pIter )
1795 			    {
1796 				    nLang = pIter->GetLanguage();
1797 				    nEndPos = pIter->GetChgPos();
1798 				    if( nEndPos > nEnd )
1799 					    nEndPos = nEnd;
1800 			    }
1801 			    else
1802 			    {
1803 				    nLang = LANGUAGE_SYSTEM;
1804 				    nEndPos = nEnd;
1805 			    }
1806                 xub_StrLen nLen = nEndPos - nStt;
1807 
1808 			    Sequence <sal_Int32> aOffsets;
1809                 String sChgd( rTrans.transliterate( m_Text, nLang, nStt, nLen, &aOffsets ));
1810 
1811                 if (!m_Text.Equals( sChgd, nStt, nLen ))
1812                 {
1813                     aChgData.nStart     = nStt;
1814                     aChgData.nLen       = nLen;
1815                     aChgData.sChanged   = sChgd;
1816                     aChgData.aOffsets   = aOffsets;
1817                     aChanges.push_back( aChgData );
1818                 }
1819 
1820                 nStt = nEndPos;
1821 		    } while( nEndPos < nEnd && pIter && pIter->Next() );
1822 		    delete pIter;
1823         }
1824 
1825         if (aChanges.size() > 0)
1826         {
1827             // now apply the changes from end to start to leave the offsets of the
1828             // yet unchanged text parts remain the same.
1829             for (size_t i = 0; i < aChanges.size(); ++i)
1830             {
1831                 TransliterationChgData &rData = aChanges[ aChanges.size() - 1 - i ];
1832                 if (pUndo)
1833                     pUndo->AddChanges( *this, rData.nStart, rData.nLen, rData.aOffsets );
1834                 ReplaceTextOnly( rData.nStart, rData.nLen, rData.sChanged, rData.aOffsets );
1835             }
1836         }
1837     }
1838 }
1839 
1840 
1841 void SwTxtNode::ReplaceTextOnly( xub_StrLen nPos, xub_StrLen nLen,
1842 								const XubString& rText,
1843 								const Sequence<sal_Int32>& rOffsets )
1844 {
1845     m_Text.Replace( nPos, nLen, rText );
1846 
1847 	xub_StrLen nTLen = rText.Len();
1848 	const sal_Int32* pOffsets = rOffsets.getConstArray();
1849 	// now look for no 1-1 mapping -> move the indizies!
1850 	xub_StrLen nI, nMyOff;
1851 	for( nI = 0, nMyOff = nPos; nI < nTLen; ++nI, ++nMyOff )
1852 	{
1853 		xub_StrLen nOff = (xub_StrLen)pOffsets[ nI ];
1854 		if( nOff < nMyOff )
1855 		{
1856 			// something is inserted
1857 			xub_StrLen nCnt = 1;
1858 			while( nI + nCnt < nTLen && nOff == pOffsets[ nI + nCnt ] )
1859 				++nCnt;
1860 
1861 			Update( SwIndex( this, nMyOff ), nCnt, sal_False );
1862 			nMyOff = nOff;
1863 			//nMyOff -= nCnt;
1864 			nI += nCnt - 1;
1865 		}
1866 		else if( nOff > nMyOff )
1867 		{
1868 			// something is deleted
1869 			Update( SwIndex( this, nMyOff+1 ), nOff - nMyOff, sal_True );
1870 			nMyOff = nOff;
1871 		}
1872 	}
1873 	if( nMyOff < nLen )
1874 		// something is deleted at the end
1875 		Update( SwIndex( this, nMyOff ), nLen - nMyOff, sal_True );
1876 
1877 	// notify the layout!
1878 	SwDelTxt aDelHint( nPos, nTLen );
1879 	NotifyClients( 0, &aDelHint );
1880 
1881 	SwInsTxt aHint( nPos, nTLen );
1882 	NotifyClients( 0, &aHint );
1883 }
1884 
1885 void SwTxtNode::CountWords( SwDocStat& rStat,
1886                             xub_StrLen nStt, xub_StrLen nEnd ) const
1887 {
1888     ++rStat.nAllPara; // #i93174#: count _all_ paragraphs
1889     if( nStt < nEnd )
1890     {
1891         if ( !IsHidden() )
1892         {
1893             ++rStat.nPara;
1894             sal_uLong nTmpWords = 0;
1895             sal_uLong nTmpChars = 0;
1896 
1897             // Shortcut: Whole paragraph should be considered and cached values
1898             // are valid:
1899             if ( 0 == nStt && GetTxt().Len() == nEnd && !IsWordCountDirty() )
1900             {
1901                 nTmpWords = GetParaNumberOfWords();
1902                 nTmpChars = GetParaNumberOfChars();
1903             }
1904             else
1905             {
1906                 String aOldStr( m_Text );
1907                 String& rCastStr = const_cast<String&>(m_Text);
1908 
1909                 // fills the deleted redlines and hidden ranges with cChar:
1910                 const xub_Unicode cChar(' ');
1911                 const sal_uInt16 nNumOfMaskedChars =
1912                         lcl_MaskRedlinesAndHiddenText( *this, rCastStr, nStt, nEnd, cChar, false );
1913 
1914                 // expand fields
1915                 rtl::OUString aExpandText;
1916                 const ModelToViewHelper::ConversionMap* pConversionMap =
1917                         BuildConversionMap( aExpandText );
1918 
1919                 const sal_uInt32 nExpandBegin = ModelToViewHelper::ConvertToViewPosition( pConversionMap, nStt );
1920                 const sal_uInt32 nExpandEnd   = ModelToViewHelper::ConvertToViewPosition( pConversionMap, nEnd );
1921                 aExpandText = aExpandText.copy( nExpandBegin, nExpandEnd - nExpandBegin );
1922 
1923                 const bool bCount = aExpandText.getLength() > 0;
1924 
1925                 // count words in 'regular' text:
1926                 if( bCount && pBreakIt->GetBreakIter().is() )
1927                 {
1928                     // split into different script languages
1929                     sal_Int32 nScriptBegin = 0;
1930                     while ( nScriptBegin < aExpandText.getLength() )
1931                     {
1932                         const sal_Int16 nCurrScript = pBreakIt->GetBreakIter()->getScriptType( aExpandText, nScriptBegin );
1933                         const sal_Int32 nScriptEnd = pBreakIt->GetBreakIter()->endOfScript( aExpandText, nScriptBegin, nCurrScript );
1934                         rtl::OUString aScriptText = aExpandText.copy( nScriptBegin, nScriptEnd - nScriptBegin );
1935 
1936                         // Asian languages count words as characters
1937                         if ( nCurrScript == ::com::sun::star::i18n::ScriptType::ASIAN )
1938                         {
1939                             // substract white spaces
1940                             sal_Int32 nSpaceCount = 0;
1941                             sal_Int32 nSpacePos = 0;
1942 
1943                             // substract normal white spaces
1944                             nSpacePos = -1;
1945                             while ( ( nSpacePos = aScriptText.indexOf( ' ', nSpacePos + 1 ) ) != -1 )
1946                             {
1947                                 nSpaceCount++;
1948                             }
1949                             // substract Asian full-width white spaces
1950                             nSpacePos = -1;
1951                             while ( ( nSpacePos = aScriptText.indexOf( 12288, nSpacePos + 1 ) ) != -1 )
1952                             {
1953                                 nSpaceCount++;
1954                             }
1955                             nTmpWords += nScriptEnd - nScriptBegin - nSpaceCount;
1956                         }
1957                         else
1958                         {
1959                             const String aScannerText( aScriptText );
1960                             SwScanner aScanner( *this, aScannerText, 0, pConversionMap,
1961                                                 i18n::WordType::WORD_COUNT,
1962                                                 (xub_StrLen)0, (xub_StrLen)aScriptText.getLength() );
1963 
1964                             const rtl::OUString aBreakWord( CH_TXTATR_BREAKWORD );
1965 
1966                             while ( aScanner.NextWord() )
1967                             {
1968                                 if ( aScanner.GetLen() > 1 ||
1969                                      CH_TXTATR_BREAKWORD != aScriptText.match(aBreakWord, aScanner.GetBegin() ) )
1970                                     ++nTmpWords;
1971                             }
1972                         }
1973                         nScriptBegin = nScriptEnd;
1974                     }
1975                 }
1976 
1977                 ASSERT( aExpandText.getLength() >= nNumOfMaskedChars,
1978                         "More characters hidden that characters in string!" )
1979                 nTmpChars = nExpandEnd - nExpandBegin - nNumOfMaskedChars;
1980 
1981                 // count words in numbering string:
1982                 if ( nStt == 0 && bCount )
1983                 {
1984                     // add numbering label
1985                     const String aNumString = GetNumString();
1986                     const xub_StrLen nNumStringLen = aNumString.Len();
1987                     if ( nNumStringLen > 0 )
1988                     {
1989                         LanguageType aLanguage = GetLang( 0 );
1990 
1991                         SwScanner aScanner( *this, aNumString, &aLanguage, 0,
1992                                             i18n::WordType::WORD_COUNT,
1993                                             0, nNumStringLen );
1994 
1995                         while ( aScanner.NextWord() )
1996                             ++nTmpWords;
1997 
1998                         nTmpChars += nNumStringLen;
1999                     }
2000                     else if ( HasBullet() )
2001                     {
2002                         ++nTmpWords;
2003                         ++nTmpChars;
2004                     }
2005                 }
2006 
2007                 delete pConversionMap;
2008 
2009                 rCastStr = aOldStr;
2010 
2011                 // If the whole paragraph has been calculated, update cached
2012                 // values:
2013                 if ( 0 == nStt && GetTxt().Len() == nEnd )
2014                 {
2015                     SetParaNumberOfWords( nTmpWords );
2016                     SetParaNumberOfChars( nTmpChars );
2017                     SetWordCountDirty( false );
2018                 }
2019             }
2020 
2021             rStat.nWord += nTmpWords;
2022             rStat.nChar += nTmpChars;
2023         }
2024     }
2025 }
2026 
2027 //
2028 // Paragraph statistics start
2029 //
2030 struct SwParaIdleData_Impl
2031 {
2032     SwWrongList* pWrong;            // for spell checking
2033     SwGrammarMarkUp* pGrammarCheck;     // for grammar checking /  proof reading
2034     SwWrongList* pSmartTags;
2035     sal_uLong nNumberOfWords;
2036     sal_uLong nNumberOfChars;
2037     bool bWordCountDirty        : 1;
2038     bool bWrongDirty            : 1;    // Ist das Wrong-Feld auf invalid?
2039     bool bGrammarCheckDirty     : 1;
2040     bool bSmartTagDirty         : 1;
2041     bool bAutoComplDirty        : 1;    // die ACompl-Liste muss angepasst werden
2042 
2043     SwParaIdleData_Impl() :
2044         pWrong              ( 0 ),
2045         pGrammarCheck       ( 0 ),
2046         pSmartTags          ( 0 ),
2047         nNumberOfWords      ( 0 ),
2048         nNumberOfChars      ( 0 ),
2049         bWordCountDirty     ( true ),
2050         bWrongDirty         ( true ),
2051         bGrammarCheckDirty  ( true ),
2052         bSmartTagDirty      ( true ),
2053         bAutoComplDirty     ( true ) {};
2054 };
2055 
2056 void SwTxtNode::InitSwParaStatistics( bool bNew )
2057 {
2058     if ( bNew )
2059     {
2060         m_pParaIdleData_Impl = new SwParaIdleData_Impl;
2061     }
2062     else if ( m_pParaIdleData_Impl )
2063     {
2064         delete m_pParaIdleData_Impl->pWrong;
2065         delete m_pParaIdleData_Impl->pGrammarCheck;
2066         delete m_pParaIdleData_Impl->pSmartTags;
2067         delete m_pParaIdleData_Impl;
2068         m_pParaIdleData_Impl = 0;
2069     }
2070 }
2071 
2072 void SwTxtNode::SetWrong( SwWrongList* pNew, bool bDelete )
2073 {
2074     if ( m_pParaIdleData_Impl )
2075     {
2076         if ( bDelete )
2077         {
2078             delete m_pParaIdleData_Impl->pWrong;
2079         }
2080         m_pParaIdleData_Impl->pWrong = pNew;
2081     }
2082 }
2083 
2084 SwWrongList* SwTxtNode::GetWrong()
2085 {
2086     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pWrong : 0;
2087 }
2088 
2089 // --> OD 2008-05-27 #i71360#
2090 const SwWrongList* SwTxtNode::GetWrong() const
2091 {
2092     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pWrong : 0;
2093 }
2094 // <--
2095 
2096 
2097 void SwTxtNode::SetGrammarCheck( SwGrammarMarkUp* pNew, bool bDelete )
2098 {
2099     if ( m_pParaIdleData_Impl )
2100     {
2101         if ( bDelete )
2102         {
2103             delete m_pParaIdleData_Impl->pGrammarCheck;
2104         }
2105         m_pParaIdleData_Impl->pGrammarCheck = pNew;
2106     }
2107 }
2108 
2109 SwGrammarMarkUp* SwTxtNode::GetGrammarCheck()
2110 {
2111     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pGrammarCheck : 0;
2112 }
2113 
2114 void SwTxtNode::SetSmartTags( SwWrongList* pNew, bool bDelete )
2115 {
2116     ASSERT( !pNew || SwSmartTagMgr::Get().IsSmartTagsEnabled(),
2117             "Weird - we have a smart tag list without any recognizers?" )
2118 
2119     if ( m_pParaIdleData_Impl )
2120     {
2121         if ( bDelete )
2122         {
2123             delete m_pParaIdleData_Impl->pSmartTags;
2124         }
2125         m_pParaIdleData_Impl->pSmartTags = pNew;
2126     }
2127 }
2128 
2129 SwWrongList* SwTxtNode::GetSmartTags()
2130 {
2131     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->pSmartTags : 0;
2132 }
2133 
2134 void SwTxtNode::SetParaNumberOfWords( sal_uLong nNew ) const
2135 {
2136     if ( m_pParaIdleData_Impl )
2137     {
2138         m_pParaIdleData_Impl->nNumberOfWords = nNew;
2139     }
2140 }
2141 sal_uLong SwTxtNode::GetParaNumberOfWords() const
2142 {
2143     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->nNumberOfWords : 0;
2144 }
2145 void SwTxtNode::SetParaNumberOfChars( sal_uLong nNew ) const
2146 {
2147     if ( m_pParaIdleData_Impl )
2148     {
2149         m_pParaIdleData_Impl->nNumberOfChars = nNew;
2150     }
2151 }
2152 sal_uLong SwTxtNode::GetParaNumberOfChars() const
2153 {
2154     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->nNumberOfChars : 0;
2155 }
2156 void SwTxtNode::SetWordCountDirty( bool bNew ) const
2157 {
2158     if ( m_pParaIdleData_Impl )
2159     {
2160         m_pParaIdleData_Impl->bWordCountDirty = bNew;
2161     }
2162 }
2163 bool SwTxtNode::IsWordCountDirty() const
2164 {
2165     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bWordCountDirty : 0;
2166 }
2167 void SwTxtNode::SetWrongDirty( bool bNew ) const
2168 {
2169     if ( m_pParaIdleData_Impl )
2170     {
2171         m_pParaIdleData_Impl->bWrongDirty = bNew;
2172     }
2173 }
2174 bool SwTxtNode::IsWrongDirty() const
2175 {
2176     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bWrongDirty : 0;
2177 }
2178 void SwTxtNode::SetGrammarCheckDirty( bool bNew ) const
2179 {
2180     if ( m_pParaIdleData_Impl )
2181     {
2182         m_pParaIdleData_Impl->bGrammarCheckDirty = bNew;
2183     }
2184 }
2185 bool SwTxtNode::IsGrammarCheckDirty() const
2186 {
2187     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bGrammarCheckDirty : 0;
2188 }
2189 void SwTxtNode::SetSmartTagDirty( bool bNew ) const
2190 {
2191     if ( m_pParaIdleData_Impl )
2192     {
2193         m_pParaIdleData_Impl->bSmartTagDirty = bNew;
2194     }
2195 }
2196 bool SwTxtNode::IsSmartTagDirty() const
2197 {
2198     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bSmartTagDirty : 0;
2199 }
2200 void SwTxtNode::SetAutoCompleteWordDirty( bool bNew ) const
2201 {
2202     if ( m_pParaIdleData_Impl )
2203     {
2204         m_pParaIdleData_Impl->bAutoComplDirty = bNew;
2205     }
2206 }
2207 bool SwTxtNode::IsAutoCompleteWordDirty() const
2208 {
2209     return m_pParaIdleData_Impl ? m_pParaIdleData_Impl->bAutoComplDirty : 0;
2210 }
2211 //
2212 // Paragraph statistics end
2213 //
2214