bool pt_PieceTable::_realInsertObject(PT_DocPosition dpos, PTObjectType pto, const gchar ** attributes, const gchar ** properties, pf_Frag_Object ** ppfo) { UT_ASSERT_HARMLESS((pto == PTO_Math) || (pto == PTO_Embed) || (properties == NULL)); // dpos == 1 seems to be generally bad. - plam // I'm curious about how often it happens. Please mail me if it does! UT_ASSERT_HARMLESS(dpos > 1); // TODO currently we force the caller to pass in the attr/prop. // TODO this is probably a good thing for Images, but might be // TODO bogus for things like Fields. UT_return_val_if_fail (m_pts==PTS_Editing, false); // store the attributes and properties and get an index to them. PT_AttrPropIndex apiOld = 0, indexAP; pf_Frag * pf = NULL; PT_BlockOffset fragOffset = 0; bool bFound = getFragFromPosition(dpos,&pf,&fragOffset); UT_return_val_if_fail (bFound,false); pf_Frag_Strux * pfs = NULL; bool bFoundStrux = _getStruxFromFrag(pf,&pfs); UT_return_val_if_fail (bFoundStrux,false); if(isEndFootnote((pf_Frag *) pfs)) { bFoundStrux = _getStruxFromFragSkip((pf_Frag *)pfs,&pfs); } UT_return_val_if_fail (bFoundStrux, false); apiOld = _chooseIndexAP(pf,fragOffset); if (!m_varset.mergeAP(PTC_AddFmt, apiOld, attributes, properties, &indexAP, m_pDocument)) return false; // get the fragment at the given document position. PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pf) + fragOffset; pf_Frag_Object * pfo = NULL; if (!_insertObject(pf,fragOffset,pto,indexAP,pfo)) return false; // create a change record, add it to the history, and notify // anyone listening. PX_ChangeRecord_Object * pcr = new PX_ChangeRecord_Object(PX_ChangeRecord::PXT_InsertObject, dpos,indexAP, pfo->getXID(), pto,blockOffset, pfo->getField(),reinterpret_cast<PL_ObjectHandle>(pfo)); UT_return_val_if_fail (pcr,false); m_history.addChangeRecord(pcr); m_pDocument->notifyListeners(pfs,pcr); *ppfo = pfo; return true; }
PT_AttrPropIndex pt_PieceTable::_chooseIndexAP(pf_Frag * pf, PT_BlockOffset fragOffset) { // decide what indexAP to give an insertSpan inserting at the given // position in the document [pf,fragOffset]. // try to get it from the current fragment. if (pf->getType() == pf_Frag::PFT_FmtMark) { pf_Frag_FmtMark * pffm = static_cast<pf_Frag_FmtMark *>(pf); return pffm->getIndexAP(); } if ((pf->getType() == pf_Frag::PFT_Text) && (fragOffset > 0)) { // if we are inserting at the middle or end of a text fragment, // we take the A/P of this text fragment. pf_Frag_Text * pft = static_cast<pf_Frag_Text *>(pf); return pft->getIndexAP(); } // we are not looking forward at a text fragment or // we are at the beginning of a text fragment. // look to the previous fragment to see what to do. pf_Frag * pfPrev = pf->getPrev(); switch (pfPrev->getType()) { case pf_Frag::PFT_Text: { // if we immediately follow another text fragment, we // take the A/P of that fragment. pf_Frag_Text * pftPrev = static_cast<pf_Frag_Text *>(pfPrev); return pftPrev->getIndexAP(); } case pf_Frag::PFT_Strux: { // if we immediately follow a block (paragraph), // (and don't have a FmtMark (tested earlier) at // the beginning of the block), look to the right. if (pf->getType() == pf_Frag::PFT_Text) { // we take the A/P of this text fragment. pf_Frag_Text * pft = static_cast<pf_Frag_Text *>(pf); return pft->getIndexAP(); } // we can't find anything, just use the default. return 0; } case pf_Frag::PFT_Object: { // if we immediately follow an object, then we may or may not // want to use the A/P of the object. for an image, it is // probably not correct to use it (and we should probably use // the text to the right of the image). for a field, it may // be a valid to use the A/P of the object. pf_Frag_Object * pfo = static_cast<pf_Frag_Object *>(pfPrev); switch (pfo->getObjectType()) { case PTO_Image: return _chooseIndexAP(pf->getPrev(),pf->getPrev()->getLength()); case PTO_Field: case PTO_Math: case PTO_Embed: return pfo->getIndexAP(); // TODO: determine what we want to do about these guys case PTO_Bookmark: case PTO_Hyperlink: case PTO_RDFAnchor: return 0; default: UT_ASSERT_HARMLESS(0); return 0; } } case pf_Frag::PFT_FmtMark: { // TODO i'm not sure this is possible pf_Frag_FmtMark * pffm = static_cast<pf_Frag_FmtMark *>(pfPrev); return pffm->getIndexAP(); } default: UT_ASSERT_HARMLESS(0); return 0; } }
bool pt_PieceTable::_realInsertSpan(PT_DocPosition dpos, const UT_UCSChar * p, UT_uint32 length, const gchar ** attributes, const gchar ** properties, fd_Field * pField, bool bAddChangeRec) { // insert character data into the document at the given position. UT_return_val_if_fail (m_pts==PTS_Editing, false); // get the fragment at the given document position. pf_Frag * pf = NULL; PT_BlockOffset fragOffset = 0; bool bFound = getFragFromPosition(dpos,&pf,&fragOffset); UT_return_val_if_fail (bFound,false); // append the text data to the end of the current buffer. PT_BufIndex bi; if (!m_varset.appendBuf(p,length,&bi)) return false; pf_Frag_Strux * pfs = NULL; bool bFoundStrux = _getStruxFromFrag(pf,&pfs); UT_return_val_if_fail (bFoundStrux,false); if(isEndFootnote((pf_Frag *)pfs)) { bFoundStrux = _getStruxFromFragSkip((pf_Frag *) pfs,&pfs); } UT_return_val_if_fail (pfs,false); if(pfs->getStruxType() == PTX_EndFrame) { bFoundStrux = _getStruxFromFragSkip((pf_Frag *) pfs,&pfs); } // we just did a getFragFromPosition() which gives us the // the thing *starting* at that position. if we have a // fragment boundary at that position, it's sort of arbitrary // whether we treat this insert as a prepend to the one we just found // or an append to the previous one (when it's a text frag). // in the normal case, we want the Attr/Prop of a character // insertion to take the AP of the thing to the immediate // left (seems to be what MS-Word and MS-WordPad do). It's also // useful when the user hits the BOLD button (without a) // selection) and then starts typing -- ideally you'd like // all of the text to have bold not just the first. therefore, // we will see if we are on a text-text boundary and backup // (and thus appending) to the previous. bool bNeedGlob = false; PT_AttrPropIndex indexAP = 0; if ( (fragOffset==0) && (pf->getPrev()) ) { bool bRightOfFmtMark = (pf->getPrev()->getType() == pf_Frag::PFT_FmtMark); if (bRightOfFmtMark) { // if we're just to the right of a _FmtMark, we want to replace // it with a _Text frag with the same attr/prop (we // only used the _FmtMark to remember a toggle format // before we had text for it). pf_Frag_FmtMark * pfPrevFmtMark = static_cast<pf_Frag_FmtMark *>(pf->getPrev()); indexAP = pfPrevFmtMark->getIndexAP(); if (_lastUndoIsThisFmtMark(dpos)) { // if the last thing in the undo history is the insertion of this // _FmtMark, then let's remember the indexAP, do an undo, and then // insert the text. this way the only thing remaining in the undo // is the insertion of this text (with no globbing around it). then // a user-undo will undo all of the coalesced text back to this point // and leave the insertion point as if the original InsertFmtMark // had never happened. // // we don't allow consecutive FmtMarks, but the undo may be a // changeFmtMark and thus just re-change the mark frag rather // than actually deleting it. so we loop here to get back to // the original insertFmtMark (this is the case if the user hit // BOLD then ITALIC then UNDERLINE then typed a character). do { undoCmd(); } while (_lastUndoIsThisFmtMark(dpos)); } else { // for some reason, something else has happened to the document // since this _FmtMark was inserted (perhaps it was one that we // inserted when we did a paragraph break and inserted several // to remember the current inline formatting). // // here we have to do it the hard way and use a glob and an // explicit deleteFmtMark. note that this messes up the undo // coalescing. that is, if the user starts typing at this // position and then hits UNDO, we will erase all of the typing // except for the first character. the second UNDO, will erase // the first character and restores the current FmtMark. if the // user BACKSPACES instead of doing the second UNDO, both the // first character and the FmtMark would be gone. // // TODO decide if we like this... // NOTE this causes BUG#431.... :-) bNeedGlob = true; beginMultiStepGlob(); _deleteFmtMarkWithNotify(dpos,pfPrevFmtMark,pfs,&pf,&fragOffset); } // we now need to consider pf invalid, since the fragment list may have // been coalesced as the FmtMarks were deleted. let's recompute them // but with a few shortcuts. bFound = getFragFromPosition(dpos,&pf,&fragOffset); UT_return_val_if_fail (bFound, false); bFoundStrux = _getStruxFromFrag(pf,&pfs); UT_return_val_if_fail (bFoundStrux,false); if(isEndFootnote((pf_Frag *)pfs)) { bFoundStrux = _getStruxFromFragSkip((pf_Frag *)pfs,&pfs); } UT_return_val_if_fail (bFoundStrux, false); xxx_UT_DEBUGMSG(("Got FragStrux at Pos %d \n",pfs->getPos())); // with the FmtMark now gone, we make a minor adjustment so that we // try to append text to the previous rather than prepend to the current. // this makes us consistent with other places in the code. if ( (fragOffset==0) && (pf->getPrev()) && (pf->getPrev()->getType() == pf_Frag::PFT_Text) && pf->getPrev()->getField()== NULL ) { // append to the end of the previous frag rather than prepend to the current one. pf = pf->getPrev(); fragOffset = pf->getLength(); } } else if (pf->getPrev()->getType() == pf_Frag::PFT_Text && pf->getPrev()->getField()==NULL) { pf_Frag_Text * pfPrevText = static_cast<pf_Frag_Text *>(pf->getPrev()); indexAP = pfPrevText->getIndexAP(); // append to the end of the previous frag rather than prepend to the current one. pf = pf->getPrev(); fragOffset = pf->getLength(); } else { indexAP = _chooseIndexAP(pf,fragOffset); // PLAM: This is the list of field attrs that should not inherit // PLAM: to the span following a field. const gchar * pFieldAttrs[12]; pFieldAttrs[0] = "type"; pFieldAttrs[1] = NULL; pFieldAttrs[2] = "param"; pFieldAttrs[3] = NULL; pFieldAttrs[4] = "name"; pFieldAttrs[5] = NULL; pFieldAttrs[6] = "endnote-id"; pFieldAttrs[7] = NULL; pFieldAttrs[8] = NULL; pFieldAttrs[9] = NULL; pFieldAttrs[10] = NULL; pFieldAttrs[11] = NULL; const PP_AttrProp * pAP = NULL; if (!getAttrProp(indexAP, &pAP)) return false; if (pAP->areAnyOfTheseNamesPresent(pFieldAttrs, NULL)) { // We do not want to inherit a char style from a field. pFieldAttrs[8] = "style"; PP_AttrProp * pAPNew = pAP->cloneWithElimination(pFieldAttrs, NULL); if (!pAPNew) return false; pAPNew->markReadOnly(); if (!m_varset.addIfUniqueAP(pAPNew, &indexAP)) return false; } } } else { // is existing fragment a field? If so do nothing // Or should we display a message to the user? if(pf->getField() != NULL) { return false; } indexAP = _chooseIndexAP(pf,fragOffset); } PT_BlockOffset blockOffset = _computeBlockOffset(pfs,pf) + fragOffset; PX_ChangeRecord_Span * pcr = NULL; if(attributes || properties) { // we need to add the attrs and props passed to us ... PT_AttrPropIndex indexNewAP; bool bMerged; bMerged = m_varset.mergeAP(PTC_AddFmt,indexAP,attributes,properties,&indexNewAP,getDocument()); UT_ASSERT_HARMLESS( bMerged ); if(bMerged) indexAP = indexNewAP; } if (!_insertSpan(pf,bi,fragOffset,length,indexAP,pField)) { if (bNeedGlob) endMultiStepGlob(); return false; } // note: because of coalescing, pf should be considered invalid at this point. // create a change record, add it to the history, and notify // anyone listening. pcr = new PX_ChangeRecord_Span(PX_ChangeRecord::PXT_InsertSpan, dpos,indexAP,bi,length, blockOffset, pField); UT_return_val_if_fail (pcr, false); pcr->setDocument(m_pDocument); bool canCoalesce = _canCoalesceInsertSpan(pcr); if (!bAddChangeRec || (canCoalesce && !m_pDocument->isCoalescingMasked())) { if (canCoalesce) m_history.coalesceHistory(pcr); m_pDocument->notifyListeners(pfs,pcr); delete pcr; } else { m_history.addChangeRecord(pcr); m_pDocument->notifyListeners(pfs,pcr); } if (bNeedGlob) endMultiStepGlob(); return true; }