1 /* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */
2 /* AbiWord
3  * Copyright (C) 1998 AbiSource, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301 USA.
19  */
20 
21 
22 // insertSpan-related routined for class pt_PieceTable.
23 #include "ut_string_class.h"
24 #include "ut_types.h"
25 #include "ut_misc.h"
26 #include "ut_assert.h"
27 #include "ut_debugmsg.h"
28 #include "ut_growbuf.h"
29 #include "pt_PieceTable.h"
30 #include "pf_Frag.h"
31 #include "pf_Frag_FmtMark.h"
32 #include "pf_Frag_Object.h"
33 #include "pf_Frag_Strux.h"
34 #include "pf_Frag_Strux_Block.h"
35 #include "pf_Frag_Strux_Section.h"
36 #include "pf_Frag_Text.h"
37 #include "pf_Fragments.h"
38 #include "px_ChangeRecord.h"
39 #include "px_CR_Span.h"
40 #include "px_CR_SpanChange.h"
41 #include "px_CR_Strux.h"
42 #include "fd_Field.h"
43 #include "pp_Revision.h"
44 #include "pd_Document.h"
45 
46 /****************************************************************/
47 /****************************************************************/
48 
49 
insertSpan(PT_DocPosition dpos,const UT_UCSChar * p,UT_uint32 length,fd_Field * pField,bool bAddChangeRec)50 bool pt_PieceTable::insertSpan(PT_DocPosition dpos,
51 							   const UT_UCSChar * p,
52 							   UT_uint32 length, fd_Field * pField,
53 							   bool bAddChangeRec)
54 {
55 	if(bAddChangeRec && m_pDocument->isMarkRevisions())
56 	{
57 		PP_RevisionAttr Revisions(NULL);
58 		const gchar ** ppRevAttrib = NULL;
59 		const gchar ** ppRevProps  = NULL;
60 
61 		pf_Frag * pf = NULL;
62 		PT_BlockOffset fragOffset = 0;
63 		bool bFound = getFragFromPosition(dpos,&pf,&fragOffset);
64 		UT_return_val_if_fail( bFound, false );
65 
66 		if(pf->getType() == pf_Frag::PFT_EndOfDoc)
67 			pf = pf->getPrev();
68 
69 		UT_return_val_if_fail( pf, false );
70 
71 		PT_AttrPropIndex indexAP = pf->getIndexAP();
72 
73 		_translateRevisionAttribute(Revisions, indexAP, PP_REVISION_ADDITION, ppRevAttrib, ppRevProps, 0, 0);
74 
75 		//return _realChangeSpanFmt(PTC_AddFmt, dpos, dpos + length, ppRevAttrib, ppRevProps);
76 		return _realInsertSpan(dpos, p, length, ppRevAttrib, ppRevProps, pField, bAddChangeRec);
77 	}
78 	else if(bAddChangeRec)
79 	{
80 		// When the revision marking is not on, we need to make sure
81 		// that the text does not get inserted with a leftover
82 		// revision attribute (e.g., if we are inserting it next to
83 		// revisioned text
84 		const gchar name[] = "revision";
85 		const gchar * ppRevAttrib[5];
86 		ppRevAttrib[0] = name;
87 		ppRevAttrib[1] = NULL;
88 		ppRevAttrib[2] = NULL;
89 		ppRevAttrib[3] = NULL;
90 		ppRevAttrib[4] = NULL;
91 
92 		const gchar * pRevision = NULL;
93 
94 		// first retrive the fmt we have (_realChangeSpanFmt()) is
95 		// quite involved, so we want to avoid calling it, if we can)
96 		pf_Frag * pf1;
97 		PT_BlockOffset Offset1;
98 
99 		if(!getFragFromPosition(dpos, &pf1, &Offset1))
100 			return false;
101 
102 		const PP_AttrProp * pAP;
103 		if(_getSpanAttrPropHelper(pf1, &pAP))
104 		{
105 		        const gchar * szStyleNameVal = NULL;
106 		        pAP->getAttribute(PT_STYLE_ATTRIBUTE_NAME,szStyleNameVal);
107 			if(!pAP->getAttribute(name, pRevision))
108 			{
109 			    return _realInsertSpan(dpos, p, length,NULL , NULL, pField, bAddChangeRec);
110 			}
111 			if(szStyleNameVal != NULL)
112 			{
113 			        ppRevAttrib[2] = PT_STYLE_ATTRIBUTE_NAME;;
114 				ppRevAttrib[3] = szStyleNameVal;
115 
116 			}
117 			//if(!_realChangeSpanFmt(PTC_RemoveFmt, dpos, dpos+length, ppRevAttrib,NULL))
118 			//	return false;
119 			return _realInsertSpan(dpos, p, length, ppRevAttrib, NULL, pField, bAddChangeRec);
120 		}
121 		else
122 		{
123 			// no AP, this is probably OK
124 			UT_DEBUGMSG(("pt_PieceTable::insertSpan: no AP\n"));
125 			return _realInsertSpan(dpos, p, length, NULL, NULL, pField, bAddChangeRec);
126 		}
127 	}
128 	else
129 	{
130 		return _realInsertSpan(dpos, p, length, NULL, NULL, pField, bAddChangeRec);
131 	}
132 }
133 
134 
_insertSpan(pf_Frag * pf,PT_BufIndex bi,PT_BlockOffset fragOffset,UT_uint32 length,PT_AttrPropIndex indexAP,fd_Field * pField)135 bool pt_PieceTable::_insertSpan(pf_Frag * pf,
136 								   PT_BufIndex bi,
137 								   PT_BlockOffset fragOffset,
138 								   UT_uint32 length,
139 								   PT_AttrPropIndex indexAP,
140                                    fd_Field * pField)
141 {
142 	// update the fragment and/or the fragment list.
143 	// return true if successful.
144 
145 	pf_Frag_Text * pft = NULL;
146 	switch (pf->getType())
147 	{
148 	default:
149 		UT_ASSERT_HARMLESS(0);
150 		return false;
151 
152 	case pf_Frag::PFT_EndOfDoc:
153 	case pf_Frag::PFT_Strux:
154 	case pf_Frag::PFT_Object:
155 		// if the position they gave us is the position of a strux
156 		// we probably need to re-interpret it slightly.  inserting
157 		// prior to a paragraph should probably be interpreted as
158 		// appending to the previous paragraph.  likewise, if they
159 		// gave us the EOD marker or an Object, we probably want to
160 		// try to append previous text fragment.
161 
162 		if (pf->getPrev() && (pf->getPrev()->getType() == pf_Frag::PFT_Text))
163 		{
164 			pf = pf->getPrev();
165 			pft = static_cast<pf_Frag_Text *>(pf);
166 			fragOffset = pft->getLength();
167 			break;
168 		}
169 
170 		// otherwise, we will just insert it before us.
171 		fragOffset = 0;
172 		break;
173 
174 	case pf_Frag::PFT_Text:
175 		pft = static_cast<pf_Frag_Text *>(pf);
176 		break;
177 
178 	case pf_Frag::PFT_FmtMark:
179 		// insert after the FmtMark.  This is an error here.
180 		// we need to replace the FmtMark with a Text frag with
181 		// the same API.  This needs to be handled at the higher
182 		// level (so the glob markers can be set).
183 		UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
184 		return false;
185 	}
186 
187 	if (pft&&pField==NULL)
188 	{
189 		// we have a text frag containing or adjacent to the position.
190 		// deal with merging/splitting/etc.
191 
192 		UT_uint32 fragLen = pft->getLength();
193 
194 		// try to coalesce this character data with an existing fragment.
195 		// this is very likely to be sucessful during normal data entry.
196 
197 		if (fragOffset == fragLen)
198 		{
199 			// we are to insert it immediately after this fragment.
200 			// if we are coalescable, just append it to this fragment
201 			// rather than create a new one.
202 			if (   (pft->getIndexAP()==indexAP)
203 			       && m_varset.isContiguous(pft->getBufIndex(),fragLen,bi))
204 			{
205 				// new text is contiguous, we just update the length of this fragment.
206 
207 				pft->changeLength(fragLen+length);
208 
209 				// see if this (enlarged) fragment is now contiguous with the
210 				// one that follows (this can happen after a delete-char followed
211 				// by undo).  if so, we coalesce them.
212 
213 				if (pft->getNext() && (pft->getNext()->getType() == pf_Frag::PFT_Text) && (pft->getNext()->getField()==NULL))
214 				{
215 					pf_Frag_Text * pftNext = static_cast<pf_Frag_Text *>(pft->getNext());
216 					if (   (pft->getIndexAP() == pftNext->getIndexAP())
217 						&& m_varset.isContiguous(pft->getBufIndex(),pft->getLength(),pftNext->getBufIndex()))
218 					{
219 						pft->changeLength(pft->getLength()+pftNext->getLength());
220 						m_fragments.unlinkFrag(pftNext);
221 						delete pftNext;
222 					}
223 				}
224 
225 				return true;
226 			}
227 		}
228 
229 		if (fragOffset == 0)
230 		{
231 			// we are to insert it immediately before this fragment.
232 			// if we are coalescable, just prepend it to this fragment.
233 
234 			if (   (pft->getIndexAP()==indexAP)
235 				&& m_varset.isContiguous(bi,length,pft->getBufIndex()))
236 			{
237 				// new text is contiguous, we just update the offset and length of
238 				// of this fragment.
239 
240 				pft->adjustOffsetLength(bi,length+fragLen);
241 
242 				// see if this (enlarged) fragment is now contiguous with the
243 				// one that preceeds us (this can happen after a delete-char followed
244 				// by undo).  if so, we coalesce them.
245 
246 				if (pft->getPrev() && (pft->getPrev()->getType() == pf_Frag::PFT_Text)&&(pft->getPrev()->getField()==NULL))
247 				{
248 					pf_Frag_Text * pftPrev = static_cast<pf_Frag_Text *>(pft->getPrev());
249 					if (   (pft->getIndexAP() == pftPrev->getIndexAP())
250 						&& m_varset.isContiguous(pftPrev->getBufIndex(),pftPrev->getLength(),pft->getBufIndex()))
251 					{
252 						pftPrev->changeLength(pftPrev->getLength()+pft->getLength());
253 						m_fragments.unlinkFrag(pft);
254 						delete pft;
255 					}
256 				}
257 
258 				return true;
259 			}
260 
261 			// one last attempt to coalesce.  if we are at the beginning of
262 			// the fragment, and this fragment and the previous fragment have
263 			// the same properties, and the character data is contiguous with
264 			// it, let's stick it in the previous fragment.
265 
266 			pf_Frag * pfPrev = pft->getPrev();
267 			if (pfPrev && pfPrev->getType()==pf_Frag::PFT_Text && (pfPrev->getField()==NULL))
268 			{
269 				pf_Frag_Text * pftPrev = static_cast<pf_Frag_Text *>(pfPrev);
270 				UT_uint32 prevLength = pftPrev->getLength();
271 
272 				if (   (pftPrev->getIndexAP() == indexAP)
273 					&& (m_varset.isContiguous(pftPrev->getBufIndex(),prevLength,bi)))
274 				{
275 					pftPrev->changeLength(prevLength+length);
276 					return true;
277 				}
278 			}
279 		}
280 	}
281 
282 	// new text is not contiguous, we need to insert one or two new text
283 	// fragment(s) into the list.  first we construct a new text fragment
284 	// for the data that we inserted.
285 
286 	pf_Frag_Text * pftNew = new pf_Frag_Text(this,bi,length,indexAP,pField);
287 	if (!pftNew)
288 		return false;
289 
290 	if (fragOffset == 0)
291 	{
292 		// if change is at the beginning of the fragment, we insert a
293 		// single new text fragment before the one we found.
294 
295 		m_fragments.insertFrag(pf->getPrev(),pftNew);
296 		return true;
297 	}
298 
299 	UT_uint32 fragLen = pf->getLength();
300 	if (fragLen==fragOffset)
301 	{
302 		// if the change is after this fragment, we insert a single
303 		// new text fragment after the one we found.
304 
305 		m_fragments.insertFrag(pf,pftNew);
306 		return true;
307 	}
308 
309 	// if the change is in the middle of the fragment, we construct
310 	// a second new text fragment for the portion after the insert.
311 
312 	UT_return_val_if_fail (pft,false);
313 
314 	UT_uint32 lenTail = pft->getLength() - fragOffset;
315 	PT_BufIndex biTail = m_varset.getBufIndex(pft->getBufIndex(),fragOffset);
316 	pf_Frag_Text * pftTail = new pf_Frag_Text(this,biTail,lenTail,pft->getIndexAP(),pft->getField());
317 	if (!pftTail)
318 		return false;
319 
320 	pft->changeLength(fragOffset);
321 	m_fragments.insertFrag(pft,pftNew);
322 	m_fragments.insertFrag(pftNew,pftTail);
323 
324 	return true;
325 }
326 
_lastUndoIsThisFmtMark(PT_DocPosition dpos)327 bool pt_PieceTable::_lastUndoIsThisFmtMark(PT_DocPosition dpos)
328 {
329 	// look backwards thru the undo from this point and see
330 	// if we have <InsertFmtMark>[<ChangeFmtMark>*]
331 
332 	PX_ChangeRecord * pcr;
333 	UT_uint32 undoNdx = 0;
334 
335 	while (1)
336 	{
337 		bool bHaveUndo = m_history.getNthUndo(&pcr,undoNdx);
338 
339 		if (!bHaveUndo)
340 			return false;
341 		if (!pcr)
342 			return false;
343 		if (pcr->getPosition() != dpos)
344 			return false;
345 
346 		switch (pcr->getType())
347 		{
348 		default:
349 			return false;
350 		case PX_ChangeRecord::PXT_InsertFmtMark:
351 			return true;
352 		case PX_ChangeRecord::PXT_ChangeFmtMark:
353 			undoNdx++;
354 			break;
355 		}
356 	}
357 
358 	UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
359 	return false;
360 }
361 
_realInsertSpan(PT_DocPosition dpos,const UT_UCSChar * p,UT_uint32 length,const gchar ** attributes,const gchar ** properties,fd_Field * pField,bool bAddChangeRec)362 bool pt_PieceTable::_realInsertSpan(PT_DocPosition dpos,
363 									const UT_UCSChar * p,
364 									UT_uint32 length,
365 									const gchar ** attributes,
366 									const gchar ** properties,
367 									fd_Field * pField,
368 									bool bAddChangeRec)
369 {
370 	// insert character data into the document at the given position.
371 
372 	UT_return_val_if_fail (m_pts==PTS_Editing, false);
373 
374 	// get the fragment at the given document position.
375 
376 	pf_Frag * pf = NULL;
377 	PT_BlockOffset fragOffset = 0;
378 	bool bFound = getFragFromPosition(dpos,&pf,&fragOffset);
379 	UT_return_val_if_fail (bFound,false);
380 
381 
382 	// append the text data to the end of the current buffer.
383 
384 	PT_BufIndex bi;
385 	if (!m_varset.appendBuf(p,length,&bi))
386 		return false;
387 
388 	pf_Frag_Strux * pfs = NULL;
389 	bool bFoundStrux = _getStruxFromFrag(pf,&pfs);
390 	UT_return_val_if_fail (bFoundStrux,false);
391 	if(isEndFootnote((pf_Frag *)pfs))
392 	{
393 		bFoundStrux = _getStruxFromFragSkip((pf_Frag *) pfs,&pfs);
394 	}
395 	UT_return_val_if_fail (pfs,false);
396 	if(pfs->getStruxType() == PTX_EndFrame)
397 	{
398 		bFoundStrux = _getStruxFromFragSkip((pf_Frag *) pfs,&pfs);
399 	}
400 	// we just did a getFragFromPosition() which gives us the
401 	// the thing *starting* at that position.  if we have a
402 	// fragment boundary at that position, it's sort of arbitrary
403 	// whether we treat this insert as a prepend to the one we just found
404 	// or an append to the previous one (when it's a text frag).
405 	// in the normal case, we want the Attr/Prop of a character
406 	// insertion to take the AP of the thing to the immediate
407 	// left (seems to be what MS-Word and MS-WordPad do).  It's also
408 	// useful when the user hits the BOLD button (without a)
409 	// selection) and then starts typing -- ideally you'd like
410 	// all of the text to have bold not just the first.  therefore,
411 	// we will see if we are on a text-text boundary and backup
412 	// (and thus appending) to the previous.
413 
414 	bool bNeedGlob = false;
415 	PT_AttrPropIndex indexAP = 0;
416 
417 	if ( (fragOffset==0) && (pf->getPrev()) )
418 	{
419 		bool bRightOfFmtMark = (pf->getPrev()->getType() == pf_Frag::PFT_FmtMark);
420 		if (bRightOfFmtMark)
421 		{
422 			// if we're just to the right of a _FmtMark, we want to replace
423 			// it with a _Text frag with the same attr/prop (we
424 			// only used the _FmtMark to remember a toggle format
425 			// before we had text for it).
426 
427 			pf_Frag_FmtMark * pfPrevFmtMark = static_cast<pf_Frag_FmtMark *>(pf->getPrev());
428 			indexAP = pfPrevFmtMark->getIndexAP();
429 
430 			if (_lastUndoIsThisFmtMark(dpos))
431 			{
432 				// if the last thing in the undo history is the insertion of this
433 				// _FmtMark, then let's remember the indexAP, do an undo, and then
434 				// insert the text.  this way the only thing remaining in the undo
435 				// is the insertion of this text (with no globbing around it).  then
436 				// a user-undo will undo all of the coalesced text back to this point
437 				// and leave the insertion point as if the original InsertFmtMark
438 				// had never happened.
439 				//
440 				// we don't allow consecutive FmtMarks, but the undo may be a
441 				// changeFmtMark and thus just re-change the mark frag rather
442 				// than actually deleting it.  so we loop here to get back to
443 				// the original insertFmtMark (this is the case if the user hit
444 				// BOLD then ITALIC then UNDERLINE then typed a character).
445 
446 				do { undoCmd(); } while (_lastUndoIsThisFmtMark(dpos));
447 			}
448 			else
449 			{
450 				// for some reason, something else has happened to the document
451 				// since this _FmtMark was inserted (perhaps it was one that we
452 				// inserted when we did a paragraph break and inserted several
453 				// to remember the current inline formatting).
454 				//
455 				// here we have to do it the hard way and use a glob and an
456 				// explicit deleteFmtMark.  note that this messes up the undo
457 				// coalescing.  that is, if the user starts typing at this
458 				// position and then hits UNDO, we will erase all of the typing
459 				// except for the first character.  the second UNDO, will erase
460 				// the first character and restores the current FmtMark.  if the
461 				// user BACKSPACES instead of doing the second UNDO, both the
462 				// first character and the FmtMark would be gone.
463 				//
464 				// TODO decide if we like this...
465 				// NOTE this causes BUG#431.... :-)
466 
467 				bNeedGlob = true;
468 				beginMultiStepGlob();
469 				_deleteFmtMarkWithNotify(dpos,pfPrevFmtMark,pfs,&pf,&fragOffset);
470 			}
471 
472 			// we now need to consider pf invalid, since the fragment list may have
473 			// been coalesced as the FmtMarks were deleted.  let's recompute them
474 			// but with a few shortcuts.
475 
476 			bFound = getFragFromPosition(dpos,&pf,&fragOffset);
477 			UT_return_val_if_fail (bFound, false);
478 
479 			bFoundStrux = _getStruxFromFrag(pf,&pfs);
480 			UT_return_val_if_fail (bFoundStrux,false);
481 			if(isEndFootnote((pf_Frag *)pfs))
482 			{
483 				bFoundStrux = _getStruxFromFragSkip((pf_Frag *)pfs,&pfs);
484 			}
485 			UT_return_val_if_fail (bFoundStrux, false);
486 			xxx_UT_DEBUGMSG(("Got FragStrux at Pos %d \n",pfs->getPos()));
487 
488 			// with the FmtMark now gone, we make a minor adjustment so that we
489 			// try to append text to the previous rather than prepend to the current.
490 			// this makes us consistent with other places in the code.
491 
492 			if ( (fragOffset==0) && (pf->getPrev()) && (pf->getPrev()->getType() == pf_Frag::PFT_Text) && pf->getPrev()->getField()== NULL )
493 			{
494 				// append to the end of the previous frag rather than prepend to the current one.
495 				pf = pf->getPrev();
496 				fragOffset = pf->getLength();
497 			}
498 		}
499 		else if (pf->getPrev()->getType() == pf_Frag::PFT_Text && pf->getPrev()->getField()==NULL)
500 		{
501 			pf_Frag_Text * pfPrevText = static_cast<pf_Frag_Text *>(pf->getPrev());
502 			indexAP = pfPrevText->getIndexAP();
503 
504 			// append to the end of the previous frag rather than prepend to the current one.
505 			pf = pf->getPrev();
506 			fragOffset = pf->getLength();
507 		}
508 		else
509 		{
510 			indexAP = _chooseIndexAP(pf,fragOffset);
511 			// PLAM: This is the list of field attrs that should not inherit
512 			// PLAM: to the span following a field.
513 			const gchar * pFieldAttrs[12];
514 			pFieldAttrs[0] = "type";  pFieldAttrs[1] = NULL;
515 			pFieldAttrs[2] = "param"; pFieldAttrs[3] = NULL;
516 			pFieldAttrs[4] = "name";  pFieldAttrs[5] = NULL;
517 			pFieldAttrs[6] = "endnote-id"; pFieldAttrs[7] = NULL;
518 			pFieldAttrs[8] = NULL;   pFieldAttrs[9] = NULL;
519 			pFieldAttrs[10] = NULL;   pFieldAttrs[11] = NULL;
520 
521 			const PP_AttrProp * pAP = NULL;
522 
523 			if (!getAttrProp(indexAP, &pAP))
524 				return false;
525 
526 			if (pAP->areAnyOfTheseNamesPresent(pFieldAttrs, NULL))
527 			{
528 				// We do not want to inherit a char style from a field.
529 				pFieldAttrs[8] = "style";
530 				PP_AttrProp * pAPNew = pAP->cloneWithElimination(pFieldAttrs, NULL);
531 				if (!pAPNew)
532 					return false;
533 				pAPNew->markReadOnly();
534 
535 				if (!m_varset.addIfUniqueAP(pAPNew, &indexAP))
536 					return false;
537 			}
538 		}
539 	}
540 	else
541 	{
542 		// is existing fragment a field? If so do nothing
543 		// Or should we display a message to the user?
544 
545 		if(pf->getField() != NULL)
546 		{
547 		       return false;
548 		}
549 
550 		indexAP = _chooseIndexAP(pf,fragOffset);
551 	}
552 	PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pf) + fragOffset;
553 	PX_ChangeRecord_Span * pcr = NULL;
554 
555 	if(attributes || properties)
556 	{
557 		// we need to add the attrs and props passed to us ...
558 		PT_AttrPropIndex indexNewAP;
559 		bool bMerged;
560 		bMerged = m_varset.mergeAP(PTC_AddFmt,indexAP,attributes,properties,&indexNewAP,getDocument());
561 		UT_ASSERT_HARMLESS( bMerged );
562 
563 		if(bMerged)
564 			indexAP = indexNewAP;
565 	}
566 
567 	if (!_insertSpan(pf,bi,fragOffset,length,indexAP,pField))
568 	{
569 		if (bNeedGlob)
570 			endMultiStepGlob();
571 		return false;
572 	}
573 
574 	// note: because of coalescing, pf should be considered invalid at this point.
575 	// create a change record, add it to the history, and notify
576 	// anyone listening.
577 
578 	pcr = new PX_ChangeRecord_Span(PX_ChangeRecord::PXT_InsertSpan,
579 								   dpos,indexAP,bi,length,
580 								   blockOffset, pField);
581 	UT_return_val_if_fail (pcr, false);
582 
583 	pcr->setDocument(m_pDocument);
584 	bool canCoalesce = _canCoalesceInsertSpan(pcr);
585 	if (!bAddChangeRec || (canCoalesce && !m_pDocument->isCoalescingMasked()))
586 	{
587 		if (canCoalesce)
588 			m_history.coalesceHistory(pcr);
589 
590 		m_pDocument->notifyListeners(pfs,pcr);
591 		delete pcr;
592 	}
593 	else
594 	{
595 		m_history.addChangeRecord(pcr);
596 		m_pDocument->notifyListeners(pfs,pcr);
597 	}
598 
599 	if (bNeedGlob)
600 		endMultiStepGlob();
601 	return true;
602 }
603 
_canCoalesceInsertSpan(PX_ChangeRecord_Span * pcrSpan) const604 bool pt_PieceTable::_canCoalesceInsertSpan(PX_ChangeRecord_Span * pcrSpan) const
605 {
606 	// see if this record can be coalesced with the most recent undo record.
607 
608 	UT_return_val_if_fail (pcrSpan->getType() == PX_ChangeRecord::PXT_InsertSpan, false);
609 
610 	PX_ChangeRecord * pcrUndo;
611 	if (!m_history.getUndo(&pcrUndo,true))
612 		return false;
613 	if (pcrSpan->getType() != pcrUndo->getType())
614 		return false;
615 	if (pcrSpan->getIndexAP() != pcrUndo->getIndexAP())
616 		return false;
617 
618 	PX_ChangeRecord_Span * pcrUndoSpan = static_cast<PX_ChangeRecord_Span *>(pcrUndo);
619 	if((pcrUndoSpan->isFromThisDoc() != pcrSpan->isFromThisDoc()))
620 	   return false;
621 
622 	UT_uint32 lengthUndo = pcrUndoSpan->getLength();
623 
624 	if ((pcrUndo->getPosition() + lengthUndo) != pcrSpan->getPosition())
625 		return false;
626 
627 	PT_BufIndex biUndo = pcrUndoSpan->getBufIndex();
628 	PT_BufIndex biSpan = pcrSpan->getBufIndex();
629 
630 	if (m_varset.getBufIndex(biUndo,lengthUndo) != biSpan)
631 		return false;
632 
633 	// "Coalescing not allowed across a save." - PL
634 	// So, if we're clean, make us dirty.
635 	if (!m_history.isDirty())
636 		return false;
637 
638 	return true;
639 }
640 
_chooseIndexAP(pf_Frag * pf,PT_BlockOffset fragOffset)641 PT_AttrPropIndex pt_PieceTable::_chooseIndexAP(pf_Frag * pf, PT_BlockOffset fragOffset)
642 {
643 	// decide what indexAP to give an insertSpan inserting at the given
644 	// position in the document [pf,fragOffset].
645 	// try to get it from the current fragment.
646 
647 	if (pf->getType() == pf_Frag::PFT_FmtMark)
648 	{
649 		pf_Frag_FmtMark * pffm = static_cast<pf_Frag_FmtMark *>(pf);
650 		return pffm->getIndexAP();
651 	}
652 
653 	if ((pf->getType() == pf_Frag::PFT_Text) && (fragOffset > 0))
654 	{
655 		// if we are inserting at the middle or end of a text fragment,
656 		// we take the A/P of this text fragment.
657 
658 		pf_Frag_Text * pft = static_cast<pf_Frag_Text *>(pf);
659 		return pft->getIndexAP();
660 	}
661 
662 	// we are not looking forward at a text fragment or
663 	// we are at the beginning of a text fragment.
664 	// look to the previous fragment to see what to do.
665 
666 	pf_Frag * pfPrev = pf->getPrev();
667 	switch (pfPrev->getType())
668 	{
669 	case pf_Frag::PFT_Text:
670 		{
671 			// if we immediately follow another text fragment, we
672 			// take the A/P of that fragment.
673 
674 			pf_Frag_Text * pftPrev = static_cast<pf_Frag_Text *>(pfPrev);
675 			return pftPrev->getIndexAP();
676 		}
677 
678 	case pf_Frag::PFT_Strux:
679 		{
680 			// if we immediately follow a block (paragraph),
681 			// (and don't have a FmtMark (tested earlier) at
682 			// the beginning of the block), look to the right.
683 
684 			if (pf->getType() == pf_Frag::PFT_Text)
685 			{
686 				// we take the A/P of this text fragment.
687 
688 				pf_Frag_Text * pft = static_cast<pf_Frag_Text *>(pf);
689 				return pft->getIndexAP();
690 			}
691 
692 			// we can't find anything, just use the default.
693 
694 			return 0;
695 		}
696 
697 	case pf_Frag::PFT_Object:
698 		{
699 			// if we immediately follow an object, then we may or may not
700 			// want to use the A/P of the object.  for an image, it is
701 			// probably not correct to use it (and we should probably use
702 			// the text to the right of the image).  for a field, it may
703 			// be a valid to use the A/P of the object.
704 
705 			pf_Frag_Object * pfo = static_cast<pf_Frag_Object *>(pfPrev);
706 			switch (pfo->getObjectType())
707 			{
708 			case PTO_Image:
709 				return _chooseIndexAP(pf->getPrev(),pf->getPrev()->getLength());
710 
711 			case PTO_Field:
712 			case PTO_Math:
713 			case PTO_Embed:
714 				return pfo->getIndexAP();
715 
716 			// TODO: determine what we want to do about these guys
717 			case PTO_Bookmark:
718 			case PTO_Hyperlink:
719 			case PTO_RDFAnchor:
720 				return 0;
721 
722 			default:
723 				UT_ASSERT_HARMLESS(0);
724 				return 0;
725 			}
726 		}
727 
728 	case pf_Frag::PFT_FmtMark:
729 		{
730 			// TODO i'm not sure this is possible
731 
732 			pf_Frag_FmtMark * pffm = static_cast<pf_Frag_FmtMark *>(pfPrev);
733 			return pffm->getIndexAP();
734 		}
735 
736 	default:
737 		UT_ASSERT_HARMLESS(0);
738 		return 0;
739 	}
740 }
741