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