bool pt_PieceTable::_realInsertStrux(PT_DocPosition dpos, PTStruxType pts, const gchar ** attributes, const gchar ** properties, pf_Frag_Strux ** ppfs_ret) { // insert a new structure fragment at the given document position. // this function can only be called while editing the document. // Also can specify an indexAP to be used for the frag rather // than that obtained by default. Very useful for insertting // Cells where you can immediately specify the cell position in // a table. this function can only be called while editing the // document. UT_return_val_if_fail (m_pts==PTS_Editing, false); // get the fragment at the doc postion containing the given // document position. pf_Frag * pf = NULL; PT_BlockOffset fragOffset = 0; bool bFoundFrag = getFragFromPosition(dpos,&pf,&fragOffset); UT_return_val_if_fail (bFoundFrag, false); // get the strux containing the given position. pf_Frag_Strux * pfsContainer = NULL; bool bFoundContainer = _getStruxFromPosition(dpos,&pfsContainer); UT_return_val_if_fail (bFoundContainer,false); // // Can only insert an endTOC into a TOC // if((pfsContainer->getStruxType() == PTX_SectionTOC) && (pts != PTX_EndTOC)) { bFoundContainer = _getStruxFromPosition(pfsContainer->getPos(),&pfsContainer); dpos--; } if(isEndFootnote(pfsContainer)) { bFoundContainer = _getStruxFromFragSkip(pfsContainer,&pfsContainer); } // if we are inserting something similar to the previous strux, // we will clone the attributes/properties; we assume that the // new strux should have the same AP as the one which preceeds us. // This is generally true for inserting a paragraph -- it should // inherit the style of the one we just broke. PT_AttrPropIndex indexAP = 0; if (pfsContainer->getStruxType() == pts) { // TODO paul, add code here to see if this strux has a "followed-by" // TODO paul, property (or property in the style) and get the a/p // TODO paul, from there rather than just taking the attr/prop // TODO paul, of the previous strux. indexAP = pfsContainer->getIndexAP(); } // // Look to see if we're in the middle of a hyperlink span now. // pf_Frag * pHype = _findPrevHyperlink(pf); if(pHype != NULL && (pts != PTX_SectionFrame) // allow annotations in // hyperlinks && (pts != PTX_SectionAnnotation) && (pts != PTX_EndAnnotation)) // frames are always placed // at the end of blocks // so we don't need this { // // We have an open hyperlink! FIXME later we should allow this by terminating // the hyperlink span just before this strux, then doing the insert strux. // Instead for now we'll just disallow this insertStrux. // // This assert is to remind use to write the code to terminate // the hyperlink. // pf_Frag * pEndHype = _findNextHyperlink(pf); PT_DocPosition posEnd = 0; if(pEndHype) { posEnd = pEndHype->getPos(); } // // OK now insert a new end of hyperlink at pf // insertObject(dpos, PTO_Hyperlink,NULL,NULL); m_fragments.cleanFrags(); dpos++; if(posEnd > 0) { // // Now delete the old endhyperlink. // pf_Frag * pfEnd = NULL; UT_uint32 newOff = 0; posEnd++; // from the insert UT_uint32 offset = 0; _deleteObjectWithNotify(posEnd, static_cast<pf_Frag_Object*>(pEndHype), offset,1,pfsContainer,&pfEnd,&newOff,true); } m_fragments.cleanFrags(); bFoundFrag = getFragFromPosition(dpos,&pf,&fragOffset); UT_return_val_if_fail (bFoundFrag, false); } // // If desired, merge in the specified attributes/properties. This // enables cells to inherit the properties of the block from which // they were inserted. // if (attributes || properties) { PT_AttrPropIndex pAPIold = indexAP; bool bMerged = m_varset.mergeAP(PTC_AddFmt,pAPIold,attributes,properties,&indexAP,getDocument()); UT_UNUSED(bMerged); UT_ASSERT_HARMLESS(bMerged); } pf_Frag_Strux * pfsNew = NULL; if (!_createStrux(pts,indexAP,&pfsNew)) return false; pfsNew->setXID(getXID()); // when inserting paragraphs, we try to remember the current // span formatting active at the insertion point and add a // FmtMark immediately after the block. this way, if the // user keeps typing text, the FmtMark will control it's // attr/prop -- if the user warps away and/or edits elsewhere // and then comes back to this point (the FmtMark may or may // not still be here) new text will either use the FmtMark or // look to the right. bool bNeedGlob = false; PT_AttrPropIndex apFmtMark = 0; if (pfsNew->getStruxType() == PTX_Block) { bNeedGlob = _computeFmtMarkForNewBlock(pfsNew,pf,fragOffset,&apFmtMark); if (bNeedGlob) beginMultiStepGlob(); // if we are leaving an empty block (are stealing all it's content) we should // put a FmtMark in it to remember the active span fmt at the time. // this lets things like hitting two consecutive CR's and then comming // back to the first empty paragraph behave as expected. // fixme sevior here if ((pf->getType()==pf_Frag::PFT_Text) && (fragOffset == 0) && (pf->getPrev()!=NULL) && (pf->getPrev()->getType()==pf_Frag::PFT_Strux)) { pf_Frag_Strux *pfsStrux = static_cast<pf_Frag_Strux *>(pf->getPrev()); if(pfsStrux->getStruxType() == PTX_Block) { _insertFmtMarkAfterBlockWithNotify(pfsContainer,dpos,apFmtMark); } } } // // Look if we're placing an endcell in an empty block. If so, // insert a format mark // if (pfsNew->getStruxType() == PTX_EndCell) { if((pf->getPrev()!=NULL) && (pf->getPrev()->getType()==pf_Frag::PFT_Strux)) { pf_Frag_Strux *pfsStrux = static_cast<pf_Frag_Strux *>(pf->getPrev()); if(pfsStrux->getStruxType() == PTX_Block) { _insertFmtMarkAfterBlockWithNotify(pfsContainer,dpos,apFmtMark); } } } // insert this frag into the fragment list. Update the container strux as needed _insertStrux(pf,fragOffset,pfsNew); if (ppfs_ret) *ppfs_ret = pfsNew; // create a change record to describe the change, add // it to the history, and let our listeners know about it. if(pfsNew->getStruxType() == PTX_SectionFrame) { // Inserting a sectionFrame screws up dos. It goes just before the next // block strux found. dpos = pfsNew->getPrev()->getPos() + pfsNew->getPrev()->getLength(); } PX_ChangeRecord_Strux * pcrs = new PX_ChangeRecord_Strux(PX_ChangeRecord::PXT_InsertStrux, dpos,indexAP,pfsNew->getXID(), pts); UT_return_val_if_fail (pcrs,false); // add record to history. we do not attempt to coalesce these. m_history.addChangeRecord(pcrs); m_pDocument->notifyListeners(pfsContainer,pfsNew,pcrs); if (bNeedGlob) { UT_return_val_if_fail (!pfsNew->getNext() || pfsNew->getNext()->getType()!=pf_Frag::PFT_FmtMark, false); _insertFmtMarkAfterBlockWithNotify(pfsNew,dpos+pfsNew->getLength(),apFmtMark); endMultiStepGlob(); } return true; }
bool pt_PieceTable::_realChangeSpanFmt(PTChangeFmt ptc, PT_DocPosition dpos1, PT_DocPosition dpos2, const gchar ** attributes, const gchar ** properties, bool bRevisionDelete) { // apply a span-level formatting change to the given region. UT_return_val_if_fail (m_pts==PTS_Editing,false); _tweakFieldSpan(dpos1,dpos2); // // Deal with case of exactly selecting the endOfFootnote // pf_Frag * pfEndDum = m_fragments.findFirstFragBeforePos(dpos2); if(isEndFootnote(pfEndDum)) { if(dpos2 > dpos1) { dpos2--; } } // // Deal with addStyle // bool bApplyStyle = (PTC_AddStyle == ptc); const gchar ** sProps = NULL; const gchar ** lProps = properties; if(bApplyStyle) { // // OK for styles we expand out all defined properties including BasedOn styles // Then we use these to eliminate any specfic properties in the current strux // Then properties in the current strux will resolve to those defined in the // style (they exist there) to specifc values in strux (if not overridden by // the style) then finally to default value. // const gchar * szStyle = UT_getAttribute(PT_STYLE_ATTRIBUTE_NAME,attributes); PD_Style * pStyle = NULL; UT_return_val_if_fail (szStyle,false); getDocument()->getStyle(szStyle,&pStyle); UT_return_val_if_fail (pStyle,false); UT_Vector vProps; // // Get the vector of properties // pStyle->getAllProperties(&vProps,0); // // Finally make the const gchar * array of properties // UT_uint32 countp = vProps.getItemCount() + 1; sProps = (const gchar **) UT_calloc(countp, sizeof(gchar *)); countp--; UT_uint32 i; for(i=0; i<countp; i++) { sProps[i] = (const gchar *) vProps.getNthItem(i); } sProps[i] = NULL; lProps = sProps; } if (dpos1 == dpos2) // if length of change is zero, then we have a toggle format. { UT_uint32 startUndoPos = m_history.getUndoPos(); bool bRes = _insertFmtMarkFragWithNotify(ptc,dpos1,attributes,lProps); UT_uint32 endUndoPos = m_history.getUndoPos(); // Won't be a persistant change if it's just a toggle PX_ChangeRecord *pcr=0; m_history.getUndo(&pcr,true); if (pcr && (startUndoPos != endUndoPos) ) { UT_DEBUGMSG(("Setting persistance of change to false\n")); pcr->setPersistance(false); m_history.setSavePosition(m_history.getSavePosition()+1); } if(bApplyStyle) { FREEP(sProps); } return bRes; } UT_return_val_if_fail (dpos1 < dpos2,false); bool bHaveAttributes, bHaveProperties; bHaveAttributes = (attributes && *attributes); bHaveProperties = (lProps && *lProps); pf_Frag * pf_First; pf_Frag * pf_End; PT_BlockOffset fragOffset_First; PT_BlockOffset fragOffset_End; bool bFound; bFound = getFragsFromPositions(dpos1,dpos2,&pf_First,&fragOffset_First,&pf_End,&fragOffset_End); UT_return_val_if_fail (bFound, false); #if 0 { pf_Frag * pf1, * pf2; PT_BlockOffset fo1, fo2; bool bFound1 = getFragFromPosition(dpos1,&pf1,&fo1); bool bFound2 = getFragFromPosition(dpos2,&pf2,&fo2); UT_return_val_if_fail (bFound1 && bFound2, false); UT_return_val_if_fail ((pf1==pf_First) && (fragOffset_First==fo1), false); UT_return_val_if_fail ((pf2==pf_End) && (fragOffset_End==fo2), false); } #endif // see if the amount of text to be changed is completely // contained within a single fragment. if so, we have a // simple change. otherwise, we need to set up a multi-step // change -- it may not actually take more than one step, // but it is too complicated to tell at this point, so we // assume it will and don't worry about it. // // we are in a simple change if the beginning and end are // within the same fragment. // NOTE: if we call beginMultiStepGlob() we ***MUST*** call // NOTE: endMultiStepGlob() before we return -- otherwise, // NOTE: the undo/redo won't be properly bracketed. bool bSimple = (pf_First == pf_End); if (!bSimple) beginMultiStepGlob(); pf_Frag_Strux * pfsContainer = NULL; pf_Frag * pfNewEnd; UT_uint32 fragOffsetNewEnd; UT_uint32 length = dpos2 - dpos1; while (length != 0) { // FIXME: Special check to support a FmtMark at the end of the // document. This is necessary because FmtMarks don't have a // length... See bug 452. if (0 == length && (!pf_First || pf_Frag::PFT_FmtMark != pf_First->getType())) break; UT_return_val_if_fail (dpos1+length==dpos2, false); UT_uint32 lengthInFrag = pf_First->getLength() - fragOffset_First; UT_uint32 lengthThisStep = UT_MIN(lengthInFrag, length); switch (pf_First->getType()) { case pf_Frag::PFT_EndOfDoc: default: UT_DEBUGMSG(("fragment type: %d\n",pf_First->getType())); UT_ASSERT_HARMLESS(0); if(bApplyStyle) { FREEP(sProps); } return false; case pf_Frag::PFT_Strux: { // we are only applying span-level changes, so we ignore strux. // but we still need to update our loop indices. pfNewEnd = pf_First->getNext(); fragOffsetNewEnd = 0; pfsContainer = static_cast<pf_Frag_Strux *> (pf_First); bool bFoundStrux = false; if(isEndFootnote(pfsContainer)) { bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer); UT_return_val_if_fail (bFoundStrux, false); } } break; case pf_Frag::PFT_Text: { if (!pfsContainer) { bool bFoundStrux; bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer); UT_return_val_if_fail (bFoundStrux,false); if(isEndFootnote(pfsContainer)) { bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer); UT_return_val_if_fail (bFoundStrux,false); } } bool bResult; bResult = _fmtChangeSpanWithNotify(ptc,static_cast<pf_Frag_Text *>(pf_First), fragOffset_First,dpos1,lengthThisStep, attributes,lProps, pfsContainer,&pfNewEnd,&fragOffsetNewEnd,bRevisionDelete); UT_return_val_if_fail (bResult,false); } break; case pf_Frag::PFT_Object: { if (!pfsContainer) { bool bFoundStrux; bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer); UT_return_val_if_fail (bFoundStrux,false); if(isEndFootnote(pfsContainer)) { bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer); UT_return_val_if_fail (bFoundStrux,false); } } bool bResult; bResult = _fmtChangeObjectWithNotify(ptc,static_cast<pf_Frag_Object *>(pf_First), fragOffset_First,dpos1,lengthThisStep, attributes,lProps, pfsContainer,&pfNewEnd,&fragOffsetNewEnd,false); UT_return_val_if_fail (bResult,false); } break; case pf_Frag::PFT_FmtMark: { if (!pfsContainer) { bool bFoundStrux; bFoundStrux = _getStruxFromPosition(dpos1,&pfsContainer); UT_return_val_if_fail (bFoundStrux,false); if(isEndFootnote(pfsContainer)) { bFoundStrux = _getStruxFromFragSkip(pfsContainer,&pfsContainer); UT_return_val_if_fail (bFoundStrux,false); } } bool bResult; bResult = _fmtChangeFmtMarkWithNotify(ptc,static_cast<pf_Frag_FmtMark *>(pf_First), dpos1, attributes,lProps, pfsContainer,&pfNewEnd,&fragOffsetNewEnd); UT_return_val_if_fail (bResult,false); } break; } dpos1 += lengthThisStep; length -= lengthThisStep; // since _fmtChange{Span,FmtMark,...}WithNotify(), can delete pf_First, mess with the // fragment list, and does some aggressive coalescing of // fragments, we cannot just do a pf_First->getNext() here. // to advance to the next fragment, we use the *NewEnd variables // that each of the cases routines gave us. pf_First = pfNewEnd; if (!pf_First) length = 0; fragOffset_First = fragOffsetNewEnd; } if(bApplyStyle) { FREEP(sProps); } if (!bSimple) endMultiStepGlob(); return true; }
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; }