1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_editeng.hxx"
30 
31 #include <vcl/wrkwin.hxx>
32 #include <vcl/dialog.hxx>
33 #include <vcl/msgbox.hxx>
34 #include <vcl/svapp.hxx>
35 #include <svl/smplhint.hxx>
36 
37 #include <tools/rtti.hxx>
38 #include <editeng/lspcitem.hxx>
39 #include <editeng/adjitem.hxx>
40 #include <editeng/tstpitem.hxx>
41 
42 #include <editdoc.hxx>
43 #include <impedit.hxx>
44 #include <editdbg.hxx>
45 
46 #include <editeng/numitem.hxx>
47 
48 #include <editeng/akrnitem.hxx>
49 #include <editeng/cntritem.hxx>
50 #include <editeng/colritem.hxx>
51 #include <editeng/crsditem.hxx>
52 #include <editeng/escpitem.hxx>
53 #include <editeng/fhgtitem.hxx>
54 #include <editeng/fontitem.hxx>
55 #include <editeng/kernitem.hxx>
56 #include <editeng/lrspitem.hxx>
57 #include <editeng/postitem.hxx>
58 #include <editeng/shdditem.hxx>
59 #include <editeng/udlnitem.hxx>
60 #include <editeng/ulspitem.hxx>
61 #include <editeng/wghtitem.hxx>
62 #include <editeng/wrlmitem.hxx>
63 #include <editeng/charscaleitem.hxx>
64 
65 #include <vcl/svapp.hxx>	// Fuer AppWindow...
66 
67 DBG_NAME( EE_ParaPortion )
68 
69 SV_IMPL_VARARR( CharPosArray, sal_Int32 );
70 
71 /*
72 
73 sal_Bool EditStyleSheet::HasStyleAsAnyParent( SfxStyleSheet& rStyle )
74 {
75 	if ( GetParent() == rStyle.GetName() )
76 		return sal_True;
77 
78 	if ( GetParent().Len() && ( GetParent() != GetName() ) )
79 	{
80 		EditStyleSheet* pS = (EditStyleSheet*)GetPool().Find( GetParent(), rStyle.GetFamily() );
81 		if ( pS )
82 			return pS->HasStyleAsAnyParent( rStyle );
83 	}
84 	return sal_False;
85 }
86 
87 */
88 
89 // -------------------------------------------------------------------------
90 // class TextPortionList
91 // -------------------------------------------------------------------------
92 TextPortionList::TextPortionList()
93 {
94 }
95 
96 TextPortionList::~TextPortionList()
97 {
98 	Reset();
99 }
100 
101 void TextPortionList::Reset()
102 {
103 	for ( sal_uInt16 nPortion = 0; nPortion < Count(); nPortion++ )
104 		delete GetObject( nPortion );
105 	Remove( 0, Count() );
106 }
107 
108 void TextPortionList::DeleteFromPortion( sal_uInt16 nDelFrom )
109 {
110 	DBG_ASSERT( ( nDelFrom < Count() ) || ( (nDelFrom == 0) && (Count() == 0) ), "DeleteFromPortion: Out of range" );
111 	for ( sal_uInt16 nP = nDelFrom; nP < Count(); nP++ )
112 		delete GetObject( nP );
113 	Remove( nDelFrom, Count()-nDelFrom );
114 }
115 
116 sal_uInt16 TextPortionList::FindPortion( sal_uInt16 nCharPos, sal_uInt16& nPortionStart, sal_Bool bPreferStartingPortion )
117 {
118 	// Bei nCharPos an Portion-Grenze wird die linke Portion gefunden
119 	sal_uInt16 nTmpPos = 0;
120 	for ( sal_uInt16 nPortion = 0; nPortion < Count(); nPortion++ )
121 	{
122 		TextPortion* pPortion = GetObject( nPortion );
123 		nTmpPos = nTmpPos + pPortion->GetLen();
124 		if ( nTmpPos >= nCharPos )
125 		{
126             // take this one if we don't prefer the starting portion, or if it's the last one
127             if ( ( nTmpPos != nCharPos ) || !bPreferStartingPortion || ( nPortion == Count() - 1 ) )
128             {
129 			    nPortionStart = nTmpPos - pPortion->GetLen();
130 			    return nPortion;
131             }
132 		}
133 	}
134 	DBG_ERROR( "FindPortion: Nicht gefunden!" );
135 	return ( Count() - 1 );
136 }
137 
138 sal_uInt16 TextPortionList::GetStartPos( sal_uInt16 nPortion )
139 {
140     sal_uInt16 nPos = 0;
141 	for ( sal_uInt16 n = 0; n < nPortion; n++ )
142 	{
143 		TextPortion* pPortion = GetObject( n );
144 		nPos = nPos + pPortion->GetLen();
145 	}
146     return nPos;
147 }
148 
149 
150 // -------------------------------------------------------------------------
151 // class ExtraPortionInfo
152 // -------------------------------------------------------------------------
153 
154 ExtraPortionInfo::ExtraPortionInfo()
155 {
156     nOrgWidth = 0;
157     nWidthFullCompression = 0;
158     nMaxCompression100thPercent = 0;
159     nAsianCompressionTypes = 0;
160     nPortionOffsetX = 0;
161     bFirstCharIsRightPunktuation = sal_False;
162     bCompressed = sal_False;
163     pOrgDXArray = NULL;
164 }
165 
166 ExtraPortionInfo::~ExtraPortionInfo()
167 {
168     delete[] pOrgDXArray;
169 }
170 
171 void ExtraPortionInfo::SaveOrgDXArray( const sal_Int32* pDXArray, sal_uInt16 nLen )
172 {
173     delete[] pOrgDXArray;
174     pOrgDXArray = new sal_Int32[nLen];
175     memcpy( pOrgDXArray, pDXArray, nLen*sizeof(sal_Int32) );
176 }
177 
178 void ExtraPortionInfo::DestroyOrgDXArray()
179 {
180     delete[] pOrgDXArray;
181     pOrgDXArray = NULL;
182 }
183 
184 
185 // -------------------------------------------------------------------------
186 // class ParaPortion
187 // -------------------------------------------------------------------------
188 ParaPortion::ParaPortion( ContentNode* pN )
189 {
190 	DBG_CTOR( EE_ParaPortion, 0 );
191 
192 	pNode 				= pN;
193 	bInvalid 			= sal_True;
194 	bVisible 			= sal_True;
195 	bSimple 			= sal_False;
196 	bForceRepaint 		= sal_False;
197 	nInvalidPosStart	= 0;
198 	nInvalidDiff 		= 0;
199 	nHeight 			= 0;
200 	nFirstLineOffset 	= 0;
201 	nBulletX			= 0;
202 }
203 
204 ParaPortion::~ParaPortion()
205 {
206 	DBG_DTOR( EE_ParaPortion, 0 );
207 }
208 
209 void ParaPortion::MarkInvalid( sal_uInt16 nStart, short nDiff )
210 {
211 	if ( bInvalid == sal_False )
212 	{
213 //		nInvalidPosEnd = nStart;	// ??? => CreateLines
214 		nInvalidPosStart = ( nDiff >= 0 ) ? nStart : ( nStart + nDiff );
215 		nInvalidDiff = nDiff;
216 	}
217 	else
218 	{
219 		// Einfaches hintereinander tippen
220 		if ( ( nDiff > 0 ) && ( nInvalidDiff > 0 ) &&
221 			 ( ( nInvalidPosStart+nInvalidDiff ) == nStart ) )
222 		{
223 			nInvalidDiff = nInvalidDiff + nDiff;
224 		}
225 		// Einfaches hintereinander loeschen
226 		else if ( ( nDiff < 0 ) && ( nInvalidDiff < 0 ) && ( nInvalidPosStart == nStart ) )
227 		{
228 			nInvalidPosStart = nInvalidPosStart + nDiff;
229 			nInvalidDiff = nInvalidDiff + nDiff;
230 		}
231 		else
232 		{
233 //			nInvalidPosEnd = pNode->Len();
234 			DBG_ASSERT( ( nDiff >= 0 ) || ( (nStart+nDiff) >= 0 ), "MarkInvalid: Diff out of Range" );
235 			nInvalidPosStart = Min( nInvalidPosStart, (sal_uInt16) ( nDiff < 0 ? nStart+nDiff : nDiff ) );
236 			nInvalidDiff = 0;
237 			bSimple = sal_False;
238 		}
239 	}
240 	bInvalid = sal_True;
241 	aScriptInfos.clear();
242 	aWritingDirectionInfos.clear();
243 }
244 
245 void ParaPortion::MarkSelectionInvalid( sal_uInt16 nStart, sal_uInt16 /* nEnd */ )
246 {
247 	if ( bInvalid == sal_False )
248 	{
249 		nInvalidPosStart = nStart;
250 //		nInvalidPosEnd = nEnd;
251 	}
252 	else
253 	{
254 		nInvalidPosStart = Min( nInvalidPosStart, nStart );
255 //		nInvalidPosEnd = pNode->Len();
256 	}
257 	nInvalidDiff = 0;
258 	bInvalid = sal_True;
259 	bSimple = sal_False;
260 	aScriptInfos.clear();
261 	aWritingDirectionInfos.clear();
262 }
263 
264 sal_uInt16 ParaPortion::GetLineNumber( sal_uInt16 nIndex )
265 {
266 	DBG_ASSERTWARNING( aLineList.Count(), "Leere ParaPortion in GetLine!" );
267 	DBG_ASSERT( bVisible, "Wozu GetLine() bei einem unsichtbaren Absatz?" );
268 
269 	for ( sal_uInt16 nLine = 0; nLine < aLineList.Count(); nLine++ )
270 	{
271 		if ( aLineList[nLine]->IsIn( nIndex ) )
272 			return nLine;
273 	}
274 
275 	// Dann sollte es am Ende der letzten Zeile sein!
276 	DBG_ASSERT( nIndex == aLineList[ aLineList.Count() - 1 ]->GetEnd(), "Index voll daneben!" );
277 	return (aLineList.Count()-1);
278 }
279 
280 void ParaPortion::SetVisible( sal_Bool bMakeVisible )
281 {
282 	bVisible = bMakeVisible;
283 }
284 
285 void ParaPortion::CorrectValuesBehindLastFormattedLine( sal_uInt16 nLastFormattedLine )
286 {
287 	sal_uInt16 nLines = aLineList.Count();
288 	DBG_ASSERT( nLines, "CorrectPortionNumbersFromLine: Leere Portion?" );
289 	if ( nLastFormattedLine < ( nLines - 1 ) )
290 	{
291 		const EditLine* pLastFormatted = aLineList[ nLastFormattedLine ];
292 		const EditLine* pUnformatted = aLineList[ nLastFormattedLine+1 ];
293 		short nPortionDiff = pUnformatted->GetStartPortion() - pLastFormatted->GetEndPortion();
294 		short nTextDiff = pUnformatted->GetStart() - pLastFormatted->GetEnd();
295 		nTextDiff++;	// LastFormatted->GetEnd() war incl. => 1 zuviel abgezogen!
296 
297 		// Die erste unformatierte muss genau eine Portion hinter der letzten der
298 		// formatierten beginnen:
299 		// Wenn in der geaenderten Zeile eine Portion gesplittet wurde,
300 		// kann nLastEnd > nNextStart sein!
301 		int nPDiff = -( nPortionDiff-1 );
302 		int nTDiff = -( nTextDiff-1 );
303 		if ( nPDiff || nTDiff )
304 		{
305 			for ( sal_uInt16 nL = nLastFormattedLine+1; nL < nLines; nL++ )
306 			{
307 				EditLine* pLine = aLineList[ nL ];
308 
309 				pLine->GetStartPortion() = sal::static_int_cast< sal_uInt16 >(
310                     pLine->GetStartPortion() + nPDiff);
311 				pLine->GetEndPortion() = sal::static_int_cast< sal_uInt16 >(
312                     pLine->GetEndPortion() + nPDiff);
313 
314 				pLine->GetStart() = sal::static_int_cast< sal_uInt16 >(
315                     pLine->GetStart() + nTDiff);
316 				pLine->GetEnd() = sal::static_int_cast< sal_uInt16 >(
317                     pLine->GetEnd() + nTDiff);
318 
319 				pLine->SetValid();
320 			}
321 		}
322 	}
323 	DBG_ASSERT( aLineList[ aLineList.Count()-1 ]->GetEnd() == pNode->Len(), "CorrectLines: Ende stimmt nicht!" );
324 }
325 
326 // Shared reverse lookup acceleration pieces ...
327 
328 static sal_uInt16 FastGetPos( const VoidPtr *pPtrArray, sal_uInt16 nPtrArrayLen,
329 						  VoidPtr pPtr, sal_uInt16 &rLastPos )
330 {
331   // Through certain filter code-paths we do a lot of appends, which in
332   // turn call GetPos - creating some N^2 nightmares. If we have a
333   // non-trivially large list, do a few checks from the end first.
334   if( rLastPos > 16 )
335     {
336       sal_uInt16 nEnd;
337       if (rLastPos > nPtrArrayLen - 2)
338 		nEnd = nPtrArrayLen;
339       else
340 		nEnd = rLastPos + 2;
341 
342       for( sal_uInt16 nIdx = rLastPos - 2; nIdx < nEnd; nIdx++ )
343 		{
344 		  if( pPtrArray[ nIdx ] == pPtr )
345 			{
346 			  rLastPos = nIdx;
347 			  return nIdx;
348 			}
349 		}
350     }
351   // The world's lamest linear search from svarray ...
352   for( sal_uInt16 nIdx = 0; nIdx < nPtrArrayLen; nIdx++ )
353 	if (pPtrArray[ nIdx ] == pPtr )
354 	  return rLastPos = nIdx;
355   return USHRT_MAX;
356 }
357 
358 // -------------------------------------------------------------------------
359 // class ParaPortionList
360 // -------------------------------------------------------------------------
361 ParaPortionList::ParaPortionList() : nLastCache( 0 )
362 {
363 }
364 
365 ParaPortionList::~ParaPortionList()
366 {
367 	Reset();
368 }
369 
370 sal_uInt16 ParaPortionList::GetPos( const ParaPortionPtr &rPtr ) const
371 {
372 	return FastGetPos( reinterpret_cast<const VoidPtr *>( GetData() ),
373 					   Count(), static_cast<VoidPtr>( rPtr ),
374 					   ((ParaPortionList *)this)->nLastCache );
375 }
376 
377 sal_uInt16 ContentList::GetPos( const ContentNodePtr &rPtr ) const
378 {
379     return FastGetPos( reinterpret_cast<const VoidPtr *>( GetData() ),
380 					   Count(), static_cast<VoidPtr>( rPtr ),
381 					   ((ContentList *)this)->nLastCache );
382 }
383 
384 void ParaPortionList::Reset()
385 {
386 	for ( sal_uInt16 nPortion = 0; nPortion < Count(); nPortion++ )
387 		delete GetObject( nPortion );
388 	Remove( 0, Count() );
389 }
390 
391 long ParaPortionList::GetYOffset( ParaPortion* pPPortion )
392 {
393 	long nHeight = 0;
394 	for ( sal_uInt16 nPortion = 0; nPortion < Count(); nPortion++ )
395 	{
396 		ParaPortion* pTmpPortion = GetObject(nPortion);
397 		if ( pTmpPortion == pPPortion )
398 			return nHeight;
399 		nHeight += pTmpPortion->GetHeight();
400 	}
401 	DBG_ERROR( "GetYOffset: Portion nicht gefunden" );
402 	return nHeight;
403 }
404 
405 sal_uInt16 ParaPortionList::FindParagraph( long nYOffset )
406 {
407 	long nY = 0;
408 	for ( sal_uInt16 nPortion = 0; nPortion < Count(); nPortion++ )
409 	{
410 		nY += GetObject(nPortion)->GetHeight(); // sollte auch bei !bVisible richtig sein!
411 		if ( nY > nYOffset )
412 			return nPortion;
413 	}
414 	return 0xFFFF;	// solte mal ueber EE_PARA_NOT_FOUND erreicht werden!
415 }
416 
417 void ParaPortionList::DbgCheck( EditDoc&
418 #ifdef DBG_UTIL
419 							   rDoc
420 #endif
421 								)
422 {
423 #ifdef DBG_UTIL
424 	DBG_ASSERT( Count() == rDoc.Count(), "ParaPortionList::DbgCheck() - Count() ungleich!" );
425 	for ( sal_uInt16 i = 0; i < Count(); i++ )
426 	{
427 		DBG_ASSERT( SaveGetObject(i), "ParaPortionList::DbgCheck() - Null-Pointer in Liste!" );
428 		DBG_ASSERT( GetObject(i)->GetNode(), "ParaPortionList::DbgCheck() - Null-Pointer in Liste(2)!" );
429 		DBG_ASSERT( GetObject(i)->GetNode() == rDoc.GetObject(i), "ParaPortionList::DbgCheck() - Eintraege kreuzen sich!" );
430 	}
431 #endif
432 }
433 
434 
435 ContentAttribsInfo::ContentAttribsInfo( const SfxItemSet& rParaAttribs ) :
436 		aPrevParaAttribs( rParaAttribs)
437 {
438 }
439 
440 
441 void ConvertItem( SfxPoolItem& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit )
442 {
443 	DBG_ASSERT( eSourceUnit != eDestUnit, "ConvertItem - Why?!" );
444 
445 	switch ( rPoolItem.Which() )
446 	{
447 		case EE_PARA_LRSPACE:
448 		{
449 			DBG_ASSERT( rPoolItem.IsA( TYPE( SvxLRSpaceItem ) ), "ConvertItem: Ungueltiges Item!" );
450 			SvxLRSpaceItem& rItem = (SvxLRSpaceItem&)rPoolItem;
451 			rItem.SetTxtFirstLineOfst( sal::static_int_cast< short >( OutputDevice::LogicToLogic( rItem.GetTxtFirstLineOfst(), eSourceUnit, eDestUnit ) ) );
452 			rItem.SetTxtLeft( OutputDevice::LogicToLogic( rItem.GetTxtLeft(), eSourceUnit, eDestUnit ) );
453 //			rItem.SetLeft( OutputDevice::LogicToLogic( rItem.GetLeft(), eSourceUnit, eDestUnit ) ); // #96298# SetLeft manipulates nTxtLeft!
454 			rItem.SetRight( OutputDevice::LogicToLogic( rItem.GetRight(), eSourceUnit, eDestUnit ) );
455 		}
456 		break;
457 		case EE_PARA_ULSPACE:
458 		{
459 			DBG_ASSERT( rPoolItem.IsA( TYPE( SvxULSpaceItem ) ), "ConvertItem: Ungueltiges Item!" );
460 			SvxULSpaceItem& rItem = (SvxULSpaceItem&)rPoolItem;
461 			rItem.SetUpper( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetUpper(), eSourceUnit, eDestUnit ) ) );
462 			rItem.SetLower( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLower(), eSourceUnit, eDestUnit ) ) );
463 		}
464 		break;
465 		case EE_PARA_SBL:
466 		{
467 			DBG_ASSERT( rPoolItem.IsA( TYPE( SvxLineSpacingItem ) ), "ConvertItem: Ungueltiges Item!" );
468 			SvxLineSpacingItem& rItem = (SvxLineSpacingItem&)rPoolItem;
469             // #96298# SetLineHeight changes also eLineSpace!
470 		    if ( rItem.GetLineSpaceRule() == SVX_LINE_SPACE_MIN )
471 			    rItem.SetLineHeight( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLineHeight(), eSourceUnit, eDestUnit ) ) );
472 		}
473 		break;
474 		case EE_PARA_TABS:
475 		{
476 			DBG_ASSERT( rPoolItem.IsA( TYPE( SvxTabStopItem ) ), "ConvertItem: Ungueltiges Item!" );
477 			SvxTabStopItem& rItem = (SvxTabStopItem&)rPoolItem;
478 			SvxTabStopItem aNewItem( EE_PARA_TABS );
479 			for ( sal_uInt16 i = 0; i < rItem.Count(); i++ )
480 			{
481 				const SvxTabStop& rTab = rItem[i];
482 				SvxTabStop aNewStop( OutputDevice::LogicToLogic( rTab.GetTabPos(), eSourceUnit, eDestUnit ), rTab.GetAdjustment(), rTab.GetDecimal(), rTab.GetFill() );
483 				aNewItem.Insert( aNewStop );
484 			}
485 			rItem = aNewItem;
486 		}
487 		break;
488 		case EE_CHAR_FONTHEIGHT:
489 		case EE_CHAR_FONTHEIGHT_CJK:
490 		case EE_CHAR_FONTHEIGHT_CTL:
491 		{
492 			DBG_ASSERT( rPoolItem.IsA( TYPE( SvxFontHeightItem ) ), "ConvertItem: Ungueltiges Item!" );
493 			SvxFontHeightItem& rItem = (SvxFontHeightItem&)rPoolItem;
494 			rItem.SetHeight( OutputDevice::LogicToLogic( rItem.GetHeight(), eSourceUnit, eDestUnit ) );
495 		}
496 		break;
497 	}
498 }
499 
500 void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit, const MapUnit* pDestUnit )
501 {
502 	const SfxItemPool* pSourcePool = rSource.GetPool();
503 	const SfxItemPool* pDestPool = rDest.GetPool();
504 
505 	for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ )
506 	{
507 		// Wenn moeglich ueber SlotID gehen...
508 
509 		sal_uInt16 nSourceWhich = nWhich;
510 		sal_uInt16 nSlot = pDestPool->GetTrueSlotId( nWhich );
511 		if ( nSlot )
512 		{
513 			sal_uInt16 nW = pSourcePool->GetTrueWhich( nSlot );
514 			if ( nW )
515 				nSourceWhich = nW;
516 		}
517 
518 		if ( rSource.GetItemState( nSourceWhich, sal_False ) == SFX_ITEM_ON )
519 		{
520 			MapUnit eSourceUnit = pSourceUnit ? *pSourceUnit : (MapUnit)pSourcePool->GetMetric( nSourceWhich );
521 			MapUnit eDestUnit = pDestUnit ? *pDestUnit : (MapUnit)pDestPool->GetMetric( nWhich );
522 			if ( eSourceUnit != eDestUnit )
523 			{
524 				SfxPoolItem* pItem = rSource.Get( nSourceWhich ).Clone();
525 //				pItem->SetWhich( nWhich );
526 				ConvertItem( *pItem, eSourceUnit, eDestUnit );
527 				rDest.Put( *pItem, nWhich );
528 				delete pItem;
529 			}
530 			else
531 			{
532 				rDest.Put( rSource.Get( nSourceWhich ), nWhich );
533 			}
534 		}
535 		else
536 		{
537 			// MT 3.3.99: Waere so eigentlich richtig, aber schon seit Jahren nicht so...
538 //			rDest.ClearItem( nWhich );
539 		}
540 	}
541 }
542 
543