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