/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */ /* AbiWord * Copyright (C) 1998,1999 AbiSource, Inc. * BIDI Copyright (c) 2001,2002 Tomas Frydrych * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include #include #include #include #include "fl_BlockLayout.h" #include "pf_Frag_Strux.h" #include "fl_Squiggles.h" #include "fl_Layout.h" #include "fl_DocLayout.h" #include "fl_SectionLayout.h" #include "fl_FootnoteLayout.h" #include "fl_TableLayout.h" #include "fl_AutoNum.h" #include "fl_TOCLayout.h" #include "fb_LineBreaker.h" #include "fb_Alignment.h" #include "fp_Column.h" #include "fp_FootnoteContainer.h" #include "fp_Line.h" #include "fp_Run.h" #include "fp_TextRun.h" #include "fp_FieldListLabelRun.h" #include "fp_DirectionMarkerRun.h" #include "fp_FrameContainer.h" #include "pd_Document.h" #include "fd_Field.h" #include "pd_Style.h" #include "pp_Property.h" #include "pp_AttrProp.h" #include "pt_Types.h" #include "gr_Graphics.h" #include "spell_manager.h" #include "px_CR_FmtMark.h" #include "px_CR_FmtMarkChange.h" #include "px_CR_Object.h" #include "px_CR_ObjectChange.h" #include "px_CR_Span.h" #include "px_CR_SpanChange.h" #include "px_CR_Strux.h" #include "px_CR_StruxChange.h" #include "pd_Iterator.h" #include "fv_View.h" #include "xap_App.h" #include "xap_Clipboard.h" #include "ut_png.h" #include "ut_sleep.h" #include "fg_Graphic.h" #include "ap_Prefs.h" #include "ap_Prefs_SchemeIds.h" #include "ut_rand.h" #include "fp_FieldTOCNum.h" #include "ut_debugmsg.h" #include "ut_assert.h" #include "ut_string.h" #include "fp_MathRun.h" #include "xap_EncodingManager.h" #if 1 // todo: work around to remove the INPUTWORDLEN restriction for pspell #include "ispell_def.h" #endif SpellChecker * fl_BlockLayout::_getSpellChecker (UT_uint32 blockPos) { // the idea behind the static's here is to cache the dictionary, so // we do not have to do dictionary lookup all the time; rather, we // will cache the AP's and checker, and if the AP's have not // changed we will reuse the previous dictionary // this works because when the PT is asked to change formatting, // it will create a new AP with the new attr/props, rather than // add them to the existing AP for the section of the document, so // that identical AP's always imply identical formatting, and thus // language static SpellChecker * checker = NULL; // initialize these to 1, so as to force initial lang evaluation static const PP_AttrProp * pPrevSpanAP = reinterpret_cast(1); static const PP_AttrProp * pPrevBlockAP = reinterpret_cast(1); const PP_AttrProp * pSpanAP = NULL; const PP_AttrProp * pBlockAP = NULL; getSpanAP(blockPos, false, pSpanAP); getAP(pBlockAP); if(pSpanAP != pPrevSpanAP || pBlockAP != pPrevBlockAP) { const char * szLang = static_cast(PP_evalProperty("lang",pSpanAP,pBlockAP,NULL,m_pDoc,true)); if (szLang) { //UT_DEBUGMSG(("fl_BlockLaout::_spellCheckWord: lang = %s\n", szLang)); // we get smart and request the proper dictionary checker = SpellManager::instance().requestDictionary(szLang); } else { // we just (dumbly) default to the last dictionary checker = SpellManager::instance().lastDictionary(); } pPrevSpanAP = pSpanAP; pPrevBlockAP = pBlockAP; } return checker; } bool fl_BlockLayout::_spellCheckWord(const UT_UCSChar * word, UT_uint32 len, UT_uint32 blockPos) { SpellChecker * checker = _getSpellChecker (blockPos); if (!checker) { // no checker found, don't mark as wrong return true; } if (checker->checkWord (word, len) == SpellChecker::LOOKUP_SUCCEEDED) return true; return false; } ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// fl_BlockLayout::fl_BlockLayout(PL_StruxDocHandle sdh, fl_ContainerLayout* pPrev, fl_SectionLayout* pSectionLayout, PT_AttrPropIndex indexAP, bool bIsHdrFtr) : fl_ContainerLayout(pSectionLayout,sdh,indexAP,PTX_Block,FL_CONTAINER_BLOCK), m_uBackgroundCheckReasons(0), m_iNeedsReformat(0), m_bNeedsRedraw(false), m_bIsHdrFtr(bIsHdrFtr), m_pFirstRun(NULL), m_pSectionLayout(pSectionLayout), m_pAlignment(NULL), m_bKeepTogether(false), m_bKeepWithNext(false), m_bStartList(false), m_bStopList(false), m_bListLabelCreated(false), m_pSpellSquiggles(NULL), m_bListItem(false), m_szStyle(NULL), m_bIsCollapsed(true), m_iDomDirection(UT_BIDI_UNSET), m_iDirOverride(UT_BIDI_UNSET), m_bIsTOC(false), m_bStyleInTOC(false), m_iTOCLevel(0), m_bSameYAsPrevious(false), m_iAccumulatedHeight(0), m_pVertContainer(NULL), m_iLinePosInContainer(0), m_bForceSectionBreak(false), m_bPrevListLabel(false), m_pGrammarSquiggles(NULL) { UT_DEBUGMSG(("BlockLayout %x created sdh %x \n",this,getStruxDocHandle())); setPrev(pPrev); UT_ASSERT(myContainingLayout() != NULL); // insert us into the list if (pPrev) pPrev->_insertIntoList(this); else { setNext(myContainingLayout()->getFirstLayout()); if (myContainingLayout()->getFirstLayout()) myContainingLayout()->getFirstLayout()->setPrev(this); } if(m_pSectionLayout && m_pSectionLayout->getType() == FL_SECTION_HDRFTR) { m_bIsHdrFtr = true; } m_pLayout = m_pSectionLayout->getDocLayout(); m_pDoc = m_pLayout->getDocument(); UT_ASSERT(m_pDoc); setAttrPropIndex(indexAP); const PP_AttrProp * pAP = 0; getAP(pAP); UT_ASSERT(pAP); if (!pAP->getAttribute (PT_STYLE_ATTRIBUTE_NAME, m_szStyle)) { m_szStyle = NULL; } m_bIsTOC = (pSectionLayout->getContainerType() == FL_CONTAINER_TOC); if(m_bIsTOC) { UT_DEBUGMSG(("TOC BLOck created %x \n",this)); fl_TOCLayout * pTOCL= static_cast(getSectionLayout()); m_iTOCLevel = pTOCL->getCurrentLevel(); } if (m_szStyle != NULL) { PD_Style * pStyle = NULL; m_pDoc->getStyle(static_cast(m_szStyle), &pStyle); if(pStyle != NULL) { pStyle->used(1); UT_sint32 iLoop = 0; while((pStyle->getBasedOn()) != NULL && (iLoop < 10)) { pStyle->getBasedOn()->used(1); pStyle = pStyle->getBasedOn(); iLoop++; } } } lookupProperties(); // // Since the Style doesn't change we need to look to see if this // block should added now. We need to wait until after the lookupProperties // to get the field list label inserted. // if(!m_bIsTOC) { if(!isNotTOCable()) { m_bStyleInTOC = m_pLayout->addOrRemoveBlockFromTOC(this); } } if(!isHdrFtr() || (static_cast(getSectionLayout())->getDocSectionLayout() != NULL)) { _insertEndOfParagraphRun(); } m_pSpellSquiggles = new fl_SpellSquiggles(this); m_pGrammarSquiggles = new fl_GrammarSquiggles(this); UT_ASSERT(m_pSpellSquiggles); UT_ASSERT(m_pGrammarSquiggles); setUpdatableField(false); updateEnclosingBlockIfNeeded(); } fl_TabStop::fl_TabStop() { iPosition = 0.0; iType = FL_TAB_NONE; iLeader = FL_LEADER_NONE; } static int compare_tabs(const void* p1, const void* p2) { const fl_TabStop * const * ppTab1 = reinterpret_cast(p1); const fl_TabStop * const * ppTab2 = reinterpret_cast(p2); if ((*ppTab1)->getPosition() < (*ppTab2)->getPosition()) { return -1; } if ((*ppTab1)->getPosition() > (*ppTab2)->getPosition()) { return 1; } return 0; } void buildTabStops(GR_Graphics * pG, const char* pszTabStops, UT_GenericVector &m_vecTabs) { // no matter what, clear prior tabstops UT_uint32 iCount = m_vecTabs.getItemCount(); UT_uint32 i; for (i=0; i= '0' && p1[2] <= ((static_cast(__FL_LEADER_MAX))+'0') ) iLeader = static_cast(p1[2]-'0'); } char pszPosition[32]; UT_uint32 iPosLen = p1 - pStart; UT_ASSERT(iPosLen < sizeof pszPosition); memcpy(pszPosition, pStart, iPosLen); pszPosition[iPosLen] = 0; iPosition = UT_convertToLogicalUnits(pszPosition); UT_ASSERT(iType > 0); /* The following assert is probably bogus, since tabs are column-relative, rather than block-relative. */ // UT_ASSERT(iPosition >= 0); fl_TabStop* pTabStop = new fl_TabStop(); pTabStop->setPosition(iPosition); pTabStop->setType(iType); pTabStop->setLeader(iLeader); pTabStop->setOffset(pStart - pszTabStops); m_vecTabs.addItem(pTabStop); pStart = pEnd; if (*pStart) { pStart++; // skip past delimiter while (*pStart == UCS_SPACE) { pStart++; } } } m_vecTabs.qsort(compare_tabs); } } /*! this function is only to be called by fl_ContainerLayout::lookupProperties() all other code must call lookupProperties() instead */ void fl_BlockLayout::_lookupProperties(const PP_AttrProp* pBlockAP) { UT_return_if_fail(pBlockAP); { // The EOP Run is an integral part of the block so also make // sure it does lookup. fp_Line* pLine = static_cast(getLastContainer()); if (pLine) { fp_Run* pRun = pLine->getLastRun(); pRun->lookupProperties(); } } UT_ASSERT(myContainingLayout() != NULL); UT_UTF8String sOldStyle(""); if(m_szStyle) { sOldStyle = m_szStyle; } if(!pBlockAP) { m_szStyle = NULL; } else if (!pBlockAP->getAttribute(PT_STYLE_ATTRIBUTE_NAME, m_szStyle)) { m_szStyle = NULL; } UT_UTF8String sNewStyle(""); if(m_szStyle) { sNewStyle = m_szStyle; } // Now work out our dominant direction // First, test if this is not a block that is the wrapper around a // footnote text, if it is, we will get the direction from the // document section that contains the footnote const XML_Char * pszFntId = NULL; const XML_Char * pszDir = NULL; if (pBlockAP && pBlockAP->getAttribute("footnote-id", pszFntId )) { if(pszFntId && *pszFntId) { UT_return_if_fail(m_pSectionLayout->getContainerType() == FL_CONTAINER_FOOTNOTE); fl_FootnoteLayout * pFL = (fl_FootnoteLayout*) m_pSectionLayout; fl_DocSectionLayout * pDSL= pFL->getDocSectionLayout(); UT_return_if_fail(pDSL); const PP_AttrProp * pSectionAP = NULL; pDSL->getAP(pSectionAP); pszDir = PP_evalProperty("dom-dir",NULL,NULL,pSectionAP,m_pDoc,false); } } if(!pszDir) { pszDir = getProperty("dom-dir", true); } UT_BidiCharType iOldDirection = m_iDomDirection; FV_View * pView = getView(); if(pView && pView->getBidiOrder() != FV_Order_Visual) { if(pView->getBidiOrder() == FV_Order_Logical_LTR) m_iDomDirection = UT_BIDI_LTR; else m_iDomDirection = UT_BIDI_RTL; } else if(!strcmp(pszDir,"rtl")) { m_iDomDirection = UT_BIDI_RTL; } else m_iDomDirection = UT_BIDI_LTR; // if the direction was previously set and the new dominant // direction is different, we have to split all runs in this // block at their direciton boundaries, because the base // direction influences the visual direciton of weak characters if(iOldDirection != UT_BIDI_UNSET && iOldDirection != m_iDomDirection) { fp_Run * pRun = getFirstRun(); while(pRun) { if (pRun->getType() == FPRUN_TEXT) { fp_TextRun * pTextRun = static_cast(pRun); //we get the next run in line prior to breaking this //one up, so that we do not break those already broken pRun = pRun->getNextRun(); pTextRun->breakMeAtDirBoundaries(UT_BIDI_IGNORE); } else if(pRun->getType() == FPRUN_ENDOFPARAGRAPH) { // need to set the direction correctly pRun->setDirection(m_iDomDirection); pRun->setVisDirection(m_iDomDirection); pRun = pRun->getNextRun(); } else pRun = pRun->getNextRun(); } } { const PP_PropertyTypeInt *pOrphans = static_cast(getPropertyType("orphans", Property_type_int)); UT_ASSERT(pOrphans); m_iOrphansProperty = pOrphans->getValue(); const PP_PropertyTypeInt *pWidows = static_cast(getPropertyType("widows", Property_type_int)); UT_ASSERT(pWidows); m_iWidowsProperty = pWidows->getValue(); if (m_iOrphansProperty < 1) { m_iOrphansProperty = 1; } if (m_iWidowsProperty < 1) { m_iWidowsProperty = 1; } } { const char* pszKeepTogether = getProperty("keep-together"); if (pszKeepTogether) { if (0 == UT_strcmp("yes", pszKeepTogether)) { m_bKeepTogether = true; } else { m_bKeepTogether = false; } } const char* pszKeepWithNext = getProperty("keep-with-next"); if (pszKeepWithNext) { if (0 == UT_strcmp("yes", pszKeepWithNext)) { m_bKeepWithNext = true; } else { m_bKeepWithNext = false; } } } GR_Graphics* pG = m_pLayout->getGraphics(); struct MarginAndIndent_t { const char* szProp; double* pVar; } const rgProps[] = { { "margin-top", &m_iTopMargin }, { "margin-bottom", &m_iBottomMargin }, { "margin-left", &m_iLeftMargin, }, { "margin-right", &m_iRightMargin, }, { "text-indent", &m_iTextIndent, } }; for (UT_uint32 iRg = 0; iRg < NrElements(rgProps); ++iRg) { const MarginAndIndent_t& mai = rgProps[iRg]; const PP_PropertyTypeSize * pProp = static_cast(getPropertyType(static_cast(mai.szProp), Property_type_size)); *mai.pVar = UT_convertSizeToLayoutUnits(pProp->getValue(), pProp->getDim()); xxx_UT_DEBUGMSG(("para prop %s layout size %d \n",mai.szProp,*mai.pVar)); } { const char* pszAlign = getProperty("text-align"); // we will only delete and reallocate the alignment if it is different // than the current one //DELETEP(m_pAlignment); xxx_UT_DEBUGMSG(("block: _lookupProperties, text-align=%s, current %d\n", pszAlign, m_pAlignment?m_pAlignment->getType():0xffff)); if (0 == UT_strcmp(pszAlign, "left")) { if(!m_pAlignment || m_pAlignment->getType() != FB_ALIGNMENT_LEFT) { DELETEP(m_pAlignment); m_pAlignment = new fb_Alignment_left; } } else if (0 == UT_strcmp(pszAlign, "center")) { if(!m_pAlignment || m_pAlignment->getType() != FB_ALIGNMENT_CENTER) { DELETEP(m_pAlignment); m_pAlignment = new fb_Alignment_center; } } else if (0 == UT_strcmp(pszAlign, "right")) { if(!m_pAlignment || m_pAlignment->getType() != FB_ALIGNMENT_RIGHT) { DELETEP(m_pAlignment); m_pAlignment = new fb_Alignment_right; } } else if (0 == UT_strcmp(pszAlign, "justify")) { if(!m_pAlignment || m_pAlignment->getType() != FB_ALIGNMENT_JUSTIFY) { DELETEP(m_pAlignment); m_pAlignment = new fb_Alignment_justify; } } else { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } } // parse any new tabstops const char* pszTabStops = getProperty("tabstops"); buildTabStops(pG, pszTabStops, m_vecTabs); #if 0 UT_DEBUGMSG(("XXXX: [default-tab-interval:%s][yields %d][resolution %d][zoom %d]\n", getProperty("default-tab-interval"), UT_convertToLogicalUnits(getProperty("default-tab-interval")), pG->getResolution(), pG->getZoomPercentage())); #endif const PP_PropertyTypeSize * pProp = static_cast(getPropertyType("default-tab-interval", Property_type_size)); // TODO: this should probably change the stored property instead m_iDefaultTabInterval = UT_convertSizeToLayoutUnits(pProp->getValue(), pProp->getDim()); if (!m_iDefaultTabInterval) { m_iDefaultTabInterval = UT_convertToLogicalUnits("1pt"); } const char * pszSpacing = getProperty("line-height"); // NOTE : Parsing spacing strings: // NOTE : - if spacing string ends with "+", it's marked as an "At Least" measurement // NOTE : - if spacing has a unit in it, it's an "Exact" measurement // NOTE : - if spacing is a unitless number, it's just a "Multiple" // UT_uint32 nLen = strlen(pszSpacing); // this assumed that only spacing 1 can be represented by a single charcter // but that is not very safe assumption, for there should be nothing stoping // us to use 2 or 3 in place of 2.0 or 3.0, so I commented this this out // Tomas 21/1/2002 // if (nLen > 1) { const char * pPlusFound = strrchr(pszSpacing, '+'); if (pPlusFound && *(pPlusFound + 1) == 0) { m_eSpacingPolicy = spacing_ATLEAST; // need to strip the plus first int posPlus = pPlusFound - pszSpacing; UT_ASSERT(posPlus>=0); UT_ASSERT(posPlus<100); UT_String pTmp(pszSpacing); pTmp[posPlus] = 0; m_dLineSpacing = UT_convertToLogicalUnits(pTmp.c_str()); } else if (UT_hasDimensionComponent(pszSpacing)) { m_eSpacingPolicy = spacing_EXACT; m_dLineSpacing = UT_convertToLogicalUnits(pszSpacing); } else { m_eSpacingPolicy = spacing_MULTIPLE; m_dLineSpacing = UT_convertDimensionless(pszSpacing); } } // // No numbering in headers/footers // if(getSectionLayout() && (getSectionLayout()->getType()== FL_SECTION_HDRFTR)) { return; } //const PP_AttrProp * pBlockAP = NULL; //getAttrProp(&pBlockAP); const XML_Char * szLid=NULL; const XML_Char * szPid=NULL; const XML_Char * szLevel=NULL; UT_uint32 id,parent_id,level; if (!pBlockAP || !pBlockAP->getAttribute(PT_LISTID_ATTRIBUTE_NAME, szLid)) szLid = NULL; if (szLid) { id = atoi(szLid); } else id = 0; if (!pBlockAP || !pBlockAP->getAttribute(PT_PARENTID_ATTRIBUTE_NAME, szPid)) szPid = NULL; if (szPid) parent_id = atoi(szPid); else parent_id = 0; if (!pBlockAP || !pBlockAP->getAttribute(PT_LEVEL_ATTRIBUTE_NAME, szLevel)) szLevel = NULL; if (szLevel) level = atoi(szLevel); else level = 0; fl_BlockLayout * prevBlockInList = NULL; fl_BlockLayout * nextBlockInList = NULL; fl_AutoNum * pAutoNum; if ((m_pAutoNum) && (id) && (m_pAutoNum->getID() != id)) { // We have stopped or started a multi-level list // this struxdochandle may already have been removed if there is another // view on this document. So check first if(m_pAutoNum->isItem(getStruxDocHandle())) { m_pAutoNum->removeItem(getStruxDocHandle()); } m_pAutoNum = NULL; UT_DEBUGMSG(("Started/Stopped Multi-Level\n")); } if (id == 0 && (m_pAutoNum)) { // We have stopped a final list item. m_bStopList = true; m_pAutoNum->markAsDirty(); if(m_pAutoNum->isItem(getStruxDocHandle())) m_pAutoNum->removeItem(getStruxDocHandle()); m_bListItem = false; _deleteListLabel(); if (m_pAutoNum->isEmpty()) { m_pDoc->removeList(m_pAutoNum,getStruxDocHandle()); DELETEP(m_pAutoNum); } else { m_pAutoNum->update(0); } m_bStopList = false; m_pAutoNum = NULL; UT_DEBUGMSG(("Stopped List\n")); } if (id != 0 && !m_pAutoNum) { UT_DEBUGMSG(("Adding to List, id= %d parent_id = %d \n",id,parent_id)); pAutoNum = m_pDoc->getListByID(id); // // Create new list if none exists // if(pAutoNum == NULL) { const XML_Char * pszStart = getProperty("start-value",true); const XML_Char * lDelim = getProperty("list-delim",true); const XML_Char * lDecimal = getProperty("list-decimal",true); UT_uint32 start = atoi(pszStart); const XML_Char * style = NULL; style = getProperty("list-style",true); if(!style) { pBlockAP->getAttribute(PT_STYLE_ATTRIBUTE_NAME,style); } UT_ASSERT(style); FL_ListType lType = getListTypeFromStyle( style); pAutoNum = new fl_AutoNum(id, parent_id, lType, start, lDelim, lDecimal, m_pDoc, getView()); UT_DEBUGMSG(("SEVIOR: Created new list id = %d\n",id)); m_pDoc->addList(pAutoNum); } UT_ASSERT(pAutoNum); m_pAutoNum = pAutoNum; m_bListItem = true; prevBlockInList = getPreviousList(id); nextBlockInList = getNextList(id); if (prevBlockInList) m_pAutoNum->insertItem(getStruxDocHandle(), prevBlockInList->getStruxDocHandle()); else if (nextBlockInList) m_pAutoNum->prependItem(getStruxDocHandle(),nextBlockInList->getStruxDocHandle()); else { if (pAutoNum->getParent()) prevBlockInList = getParentItem(); else prevBlockInList = NULL; PL_StruxDocHandle pItem = getStruxDocHandle(); PL_StruxDocHandle ppItem; if(prevBlockInList != NULL ) { ppItem = prevBlockInList->getStruxDocHandle(); } else { ppItem = static_cast(NULL); } m_pAutoNum->insertFirstItem(pItem,ppItem,0); m_bStartList = true; } xxx_UT_DEBUGMSG(("Added Item to List\n")); } // Add this in for loading - see if better way to fix. // if (m_bListItem && !m_bListLabelCreated && m_pFirstRun) // _createListLabel(); xxx_UT_DEBUGMSG(("BlockLayout %x Folded Level %d sdh %x \n",this,getFoldedLevel(),getStruxDocHandle())); if(getFoldedLevel()>0) { if(m_pAutoNum == NULL) { UT_DEBUGMSG(("BlockLayout %x Set Hidden \n",this)); setVisibility(FP_HIDDEN_FOLDED); } else if(!m_pAutoNum->isIDSomeWhere(getFoldedID()) || (static_cast(m_pAutoNum->getLevel()) > getFoldedLevel())) { UT_DEBUGMSG(("BlockLayout %x Set Hidden \n",this)); setVisibility(FP_HIDDEN_FOLDED); } } // // Look after TOC handling now. // if(!m_bIsTOC && !(sNewStyle == sOldStyle)) { if(!isNotTOCable()) { if(m_bStyleInTOC) { m_pLayout->removeBlockFromTOC(this); // remove old one } m_bStyleInTOC = m_pLayout->addOrRemoveBlockFromTOC(this); } } // latter we will need to add here revision handling ... } fl_BlockLayout::~fl_BlockLayout() { DELETEP(m_pSpellSquiggles); DELETEP(m_pGrammarSquiggles); purgeLayout(); UT_VECTOR_PURGEALL(fl_TabStop *, m_vecTabs); DELETEP(m_pAlignment); // if (m_pAutoNum) // { // m_pAutoNum->removeItem(getStruxDocHandle()); // if (m_pAutoNum->isEmpty()) // DELETEP(m_pAutoNum); // } if(!m_bIsTOC) { if(!isNotTOCable()) { m_pLayout->removeBlockFromTOC(this); } } UT_ASSERT(m_pLayout != NULL); m_pLayout->notifyBlockIsBeingDeleted(this); m_pLayout->dequeueBlockForBackgroundCheck(this); m_pDoc = NULL; m_pLayout = NULL; UT_DEBUGMSG(("~fl_BlockLayout: Deleting block %x sdh %x \n",this,getStruxDocHandle())); } void fl_BlockLayout::getStyle(UT_UTF8String & sStyle) { sStyle = m_szStyle; } /*! * This method returns true if the block is contained with a section embedded * in a block, like a footnote or a table or frame with text wrapping. */ bool fl_BlockLayout::isEmbeddedType(void) { fl_ContainerLayout * pCL = myContainingLayout(); if(pCL && (pCL->getContainerType() == FL_CONTAINER_FOOTNOTE || pCL->getContainerType() == FL_CONTAINER_ENDNOTE ) ) { return true; } return false; } /*! * This method returns true if the block is contained with a section embedded * that should not be included in TOC like, footnote,endnotes,HdrFtr's * and other TOC's. */ bool fl_BlockLayout::isNotTOCable(void) { fl_ContainerLayout * pCL = myContainingLayout(); if(pCL && (pCL->getContainerType() == FL_CONTAINER_FOOTNOTE || pCL->getContainerType() == FL_CONTAINER_ENDNOTE || pCL->getContainerType() == FL_CONTAINER_HDRFTR || pCL->getContainerType() == FL_CONTAINER_TOC || pCL->getContainerType() == FL_CONTAINER_SHADOW ) ) { return true; } if(pCL == NULL) { return true; } if(pCL->getContainerType() == FL_CONTAINER_CELL) { pCL = pCL->myContainingLayout(); // should be a table if(pCL == NULL) { return true; } pCL = pCL->myContainingLayout(); // is it a Hdrftr? if(pCL && (pCL->getContainerType() == FL_CONTAINER_HDRFTR || pCL->getContainerType() == FL_CONTAINER_SHADOW ) ) { return true; } } return false; } /*! * This method returns the offset of the next embedded strux within the * the block. (Like a footnote or endnote) * It returns -1 if none is found. * Also returns the id of the embedded strux. */ UT_sint32 fl_BlockLayout::getEmbeddedOffset(UT_sint32 offset, fl_ContainerLayout *& pEmbedCL) { UT_sint32 iEmbed = -1; PT_DocPosition posOff = static_cast(offset); PL_StruxDocHandle sdhEmbed; pEmbedCL = NULL; iEmbed = m_pDoc->getEmbeddedOffset(getStruxDocHandle(), posOff, sdhEmbed); if( iEmbed < 0) { return iEmbed; } PL_StruxFmtHandle sfhEmbed = NULL; bool bFound = false; sfhEmbed = m_pDoc->getNthFmtHandle(sdhEmbed,m_pLayout->getLID()); if( sfhEmbed == NULL) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return -1; } pEmbedCL = reinterpret_cast(const_cast(sfhEmbed)); if(pEmbedCL->getDocSectionLayout() == getDocSectionLayout()) { bFound = true; } else { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); pEmbedCL = NULL; return -1; } if(bFound) { if(pEmbedCL->getContainerType() == FL_CONTAINER_TOC) { return -1; } return iEmbed; } pEmbedCL = NULL; return -1; } /*! * This method scans through the list of runs from the first position listed * and updates the offsets. This is used following an operation on an embedded * type section (Like a footnote). Also updates the char widths and the POB's * in the squiggles. \param posEmbedded the position of the embedded Section. \param iEmbeddedSize the size of the embedded Section. */ void fl_BlockLayout::updateOffsets(PT_DocPosition posEmbedded, UT_uint32 iEmbeddedSize, UT_sint32 iSuggestDiff) { xxx_UT_DEBUGMSG(("In update Offsets posEmbedded %d EmbeddedSize %d shift %d \n",posEmbedded,iEmbeddedSize,iSuggestDiff)); fp_Run * pRun = getFirstRun(); PT_DocPosition posOfBlock = getPosition(true); PT_DocPosition posAtStartOfBlock = getPosition(); fp_Run * pPrev = NULL; #if DEBUG while(pRun) { xxx_UT_DEBUGMSG(("!!Initially run %x runType %d posindoc %d end run %d \n",pRun,pRun->getType(),posAtStartOfBlock+pRun->getBlockOffset(),posAtStartOfBlock+pRun->getBlockOffset()+pRun->getLength())); pRun = pRun->getNextRun(); } pRun = getFirstRun(); #endif while(pRun && (posAtStartOfBlock + pRun->getBlockOffset() < posEmbedded)) { xxx_UT_DEBUGMSG(("Look at run %x runType %d posindoc %d \n",pRun,pRun->getType(),posAtStartOfBlock+pRun->getBlockOffset())); pPrev = pRun; pRun = pRun->getNextRun(); } PT_DocPosition posRun = 0; if(pRun == NULL) { if(pPrev == NULL) { UT_DEBUGMSG(("!!!YIKES NO RUN or PREV RUN!!! \n")); return; } // // Catch case of EOP actually containing posEmebedded // if((posOfBlock + pPrev->getBlockOffset() +1) < posEmbedded) { xxx_UT_DEBUGMSG(("!!! POSEMBEDDED past end of block!! \n")); xxx_UT_DEBUGMSG(("End of block %d PosEmbedded %d \n",posOfBlock+pPrev->getBlockOffset()+1,posEmbedded)); return; } else { pRun = pPrev; pPrev = pRun->getPrevRun(); } } else { posRun = posAtStartOfBlock + pRun->getBlockOffset(); if((posRun > posEmbedded) && pPrev) { posRun = posAtStartOfBlock + pPrev->getBlockOffset(); if(posRun < posEmbedded) { pRun = pPrev; pPrev = pRun->getPrevRun(); } } } // // Position of pRun should be <= posEmbedded // posRun = posAtStartOfBlock + pRun->getBlockOffset(); fp_Run * pNext = pRun->getNextRun(); if(pNext && (posRun + pRun->getLength() <= posEmbedded) && ((pNext->getBlockOffset() + posAtStartOfBlock) > posEmbedded)) { // // OK it's obvious here. Run previous is before posEmbedded next // is after. Use it pRun = pNext; } else if(posRun < posEmbedded) { UT_uint32 splitOffset = posEmbedded - posOfBlock -1; if(splitOffset > pRun->getBlockOffset() && (pRun->getBlockOffset() + pRun->getLength() > splitOffset)) { UT_ASSERT(pRun->getType() == FPRUN_TEXT); fp_TextRun * pTRun = static_cast(pRun); xxx_UT_DEBUGMSG(("updateOffsets: Split at offset %d \n",splitOffset)); bool bres = pTRun->split(splitOffset); UT_ASSERT(bres); pRun = pTRun->getNextRun(); pPrev = pTRun; xxx_UT_DEBUGMSG(("New Run %x created offset %d \n",pRun,pRun->getBlockOffset())); xxx_UT_DEBUGMSG(("Old Run %x offset %d length\n",pPrev,pPrev->getBlockOffset(),pPrev->getLength())); } else { // Split point is actually after this run UT_ASSERT(splitOffset == pRun->getBlockOffset()); pPrev = pRun; pRun = pRun->getNextRun(); UT_ASSERT(pRun); if(pRun == NULL) { pPrev = pRun; } } } // // pRun is the first run that gets shifted // UT_ASSERT(pRun); if(iSuggestDiff != 0) { // // Now shift all the offsets in the runs. // UT_sint32 iFirstOffset = static_cast(pRun->getBlockOffset()); while(pRun) { UT_uint32 iNew = pRun->getBlockOffset() + iSuggestDiff; UT_ASSERT(iNew >= 0); xxx_UT_DEBUGMSG(("Run %x Old offset %d New Offset %d \n",pRun,pRun->getBlockOffset(),iNew)); pRun->setBlockOffset(static_cast(iNew)); pRun = pRun->getNextRun(); } // // Now update the PartOfBlocks in the squiggles // getSpellSquiggles()->updatePOBs(iFirstOffset,iSuggestDiff); getGrammarSquiggles()->updatePOBs(iFirstOffset,iSuggestDiff); } #if 0 #if DEBUG pRun = getFirstRun(); while(pRun) { if(pRun->getType() == FPRUN_TEXT) { fp_TextRun * pTRun = static_cast(pRun); pTRun->printText(); } UT_DEBUGMSG(("update offsets!!!!--- Run %x offset %d Type %d \n",pRun,pRun->getBlockOffset(),pRun->getType())); pRun = pRun->getNextRun(); } #endif #endif setNeedsReformat(); updateEnclosingBlockIfNeeded(); } /*! * This method updates the enclosing Block which contains the embedded Section * which in turn contains this Block. If this is not a block in an embedded * section type, we just return and do nothing. */ void fl_BlockLayout::updateEnclosingBlockIfNeeded(void) { UT_return_if_fail (m_pLayout); if(!isEmbeddedType()) { xxx_UT_DEBUGMSG(("Block %x is Not enclosed - returning \n")); return; } fl_ContainerLayout * pCL = myContainingLayout(); UT_ASSERT((pCL->getContainerType() == FL_CONTAINER_FOOTNOTE) || (pCL->getContainerType() == FL_CONTAINER_ENDNOTE) ); fl_EmbedLayout * pFL = static_cast(pCL); if(!pFL->isEndFootnoteIn()) { return; } PL_StruxDocHandle sdhStart = pCL->getStruxDocHandle(); PL_StruxDocHandle sdhEnd = NULL; if(pCL->getContainerType() == FL_CONTAINER_FOOTNOTE) { getDocument()->getNextStruxOfType(sdhStart,PTX_EndFootnote, &sdhEnd); } else { getDocument()->getNextStruxOfType(sdhStart,PTX_EndEndnote, &sdhEnd); } UT_return_if_fail(sdhEnd != NULL); PT_DocPosition posStart = getDocument()->getStruxPosition(sdhStart); PT_DocPosition posEnd = getDocument()->getStruxPosition(sdhEnd); UT_uint32 iSize = posEnd - posStart + 1; PL_StruxFmtHandle psfh = NULL; getDocument()->getStruxOfTypeFromPosition(m_pLayout->getLID(),posStart,PTX_Block, &psfh); fl_BlockLayout * pBL = reinterpret_cast(const_cast(psfh)); UT_ASSERT(pBL->getContainerType() == FL_CONTAINER_BLOCK); UT_ASSERT(iSize > 1); UT_sint32 iOldSize = pFL->getOldSize(); pFL->setOldSize(iSize); pBL->updateOffsets(posStart,iSize,iSize-iOldSize); } /*! * This method returns the DocSectionLayout that this block is associated with */ fl_DocSectionLayout * fl_BlockLayout::getDocSectionLayout(void) const { fl_DocSectionLayout * pDSL = NULL; if(getSectionLayout()->getType() == FL_SECTION_DOC) { pDSL = static_cast( m_pSectionLayout); return pDSL; } else if (getSectionLayout()->getType() == FL_SECTION_TOC) { pDSL = static_cast(getSectionLayout())->getDocSectionLayout(); return pDSL; } else if (getSectionLayout()->getType() == FL_SECTION_FOOTNOTE) { pDSL = static_cast(getSectionLayout())->getDocSectionLayout(); return pDSL; } else if (getSectionLayout()->getType() == FL_SECTION_ENDNOTE) { pDSL = static_cast(getSectionLayout())->getDocSectionLayout(); return pDSL; } else if (getSectionLayout()->getType() == FL_SECTION_HDRFTR) { pDSL = static_cast(getSectionLayout())->getDocSectionLayout(); return pDSL; } else if (getSectionLayout()->getType() == FL_SECTION_SHADOW) { pDSL = static_cast( getSectionLayout())->getHdrFtrSectionLayout()->getDocSectionLayout(); return pDSL; } else if (getSectionLayout()->getType() == FL_SECTION_CELL) { pDSL = static_cast(getSectionLayout())->getDocSectionLayout(); return pDSL; } else if (getSectionLayout()->getType() == FL_SECTION_FRAME) { pDSL = static_cast(getSectionLayout())->getDocSectionLayout(); return pDSL; } UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return NULL; } fp_Line * fl_BlockLayout::findLineWithFootnotePID(UT_uint32 pid) { fp_Line * pLine = static_cast(getFirstContainer()); UT_GenericVector vecFoots; bool bFound = false; while(pLine && !bFound) { vecFoots.clear(); if(pLine->getFootnoteContainers(&vecFoots)) { UT_uint32 i = 0; for(i=0; i< vecFoots.getItemCount(); i++) { fp_FootnoteContainer * pFC = vecFoots.getNthItem(i); fl_FootnoteLayout * pFL = static_cast(pFC->getSectionLayout()); if(pFL->getFootnotePID() == pid) { bFound = true; break; } } } pLine = static_cast(pLine->getNext()); } if(bFound) { return pLine; } return NULL; } FootnoteType fl_BlockLayout::getTOCNumType(void) { UT_ASSERT(m_bIsTOC); fl_TOCLayout * pTOCL = static_cast(getSectionLayout()); UT_ASSERT(pTOCL->getContainerType() == FL_CONTAINER_TOC); return pTOCL->getNumType(m_iTOCLevel); } eTabLeader fl_BlockLayout::getTOCTabLeader(UT_sint32 iOff) { UT_ASSERT(m_bIsTOC); fl_TOCLayout * pTOCL = static_cast(getSectionLayout()); UT_ASSERT(pTOCL->getContainerType() == FL_CONTAINER_TOC); if(iOff > 1) { return pTOCL->getTabLeader(m_iTOCLevel); } return FL_LEADER_NONE; } double fl_BlockLayout::getTOCTabPosition(UT_sint32 iOff) { UT_ASSERT(m_bIsTOC); fl_TOCLayout * pTOCL = static_cast(getSectionLayout()); UT_ASSERT(pTOCL->getContainerType() == FL_CONTAINER_TOC); if(iOff > 1) { return pTOCL->getTabPosition(m_iTOCLevel,this); } return 0; } double fl_BlockLayout::getMaxNonBreakableRun(void) { fp_Run * pRun = getFirstRun(); double dMax = 6.0; // this is the pixel width of a typical 12 point char if(pRun) { #if 0 if(pRun->getGraphics()) { GR_Font *pFont = pRun->getGraphics()->getGUIFont(); if(pFont) { iMax = pRun->getGraphics()->measureUnRemappedChar(static_cast('i')); } } #endif } while(pRun) { if(pRun->getType() == FPRUN_IMAGE) { dMax = UT_MAX(dMax,pRun->getWidth()); } pRun = pRun->getNextRun(); } return dMax; } bool fl_BlockLayout::isHdrFtr(void) { if(getSectionLayout()!= NULL) { return (getSectionLayout()->getType() == FL_SECTION_HDRFTR); } else return m_bIsHdrFtr; } void fl_BlockLayout::clearScreen(GR_Graphics* /* pG */) { fp_Line* pLine = static_cast(getFirstContainer()); if(isHdrFtr()) { return; } while (pLine) { // I have commented this assert out, since due to the call from doclistener_deleteStrux // clearScreen can be called _after_ the contents of this paragraph have been cleared // Tomas 28/02/2002 //UT_ASSERT(!pLine->isEmpty()); if(!pLine->isEmpty()) pLine->clearScreen(); pLine = static_cast(pLine->getNext()); } } void fl_BlockLayout::_mergeRuns(fp_Run* pFirstRunToMerge, fp_Run* pLastRunToMerge) { UT_ASSERT(pFirstRunToMerge != pLastRunToMerge); UT_ASSERT(pFirstRunToMerge->getType() == FPRUN_TEXT); UT_ASSERT(pLastRunToMerge->getType() == FPRUN_TEXT); _assertRunListIntegrity(); fp_TextRun* pFirst = static_cast(pFirstRunToMerge); bool bDone = false; while (!bDone) { if (pFirst->getNextRun() == pLastRunToMerge) { bDone = true; } pFirst->mergeWithNext(); } _assertRunListIntegrity(); } void fl_BlockLayout::coalesceRuns(void) { _assertRunListIntegrity(); #if 1 xxx_UT_DEBUGMSG(("fl_BlockLayout::coalesceRuns\n")); fp_Line* pLine = static_cast(getFirstContainer()); while (pLine) { pLine->coalesceRuns(); pLine = static_cast(pLine->getNext()); } #else fp_Run* pFirstRunInChain = NULL; UT_uint32 iNumRunsInChain = 0; fp_Run* pCurrentRun = m_pFirstRun; fp_Run* pLastRun = NULL; while (pCurrentRun) { if (pCurrentRun->getType() == FPRUN_TEXT) { if (pFirstRunInChain) { if ( (pCurrentRun->getLine() == pFirstRunInChain->getLine()) && (pCurrentRun->getAP() == pFirstRunInChain->getAP()) && ((!pLastRun) || ( (pCurrentRun->getBlockOffset() == (pLastRun->getBlockOffset() + pLastRun->getLength())) ) ) ) { iNumRunsInChain++; } else { if (iNumRunsInChain > 1) { _mergeRuns(pFirstRunInChain, pLastRun); } pFirstRunInChain = pCurrentRun; iNumRunsInChain = 1; } } else { pFirstRunInChain = pCurrentRun; iNumRunsInChain = 1; } } else { if (iNumRunsInChain > 1) { _mergeRuns(pFirstRunInChain, pLastRun); } iNumRunsInChain = 0; pFirstRunInChain = NULL; } pLastRun = pCurrentRun; pCurrentRun = pCurrentRun->getNextRun(); } if (iNumRunsInChain > 1) { _mergeRuns(pFirstRunInChain, pLastRun); } #endif _assertRunListIntegrity(); } void fl_BlockLayout::collapse(void) { xxx_UT_DEBUGMSG(("Collapsing Block %x No containers %d \n",this,findLineInBlock(static_cast(getLastContainer())))); fp_Run* pRun = m_pFirstRun; while (pRun) { pRun->setLine(NULL); pRun = pRun->getNextRun(); } fp_Line* pLine = static_cast(getFirstContainer()); while (pLine) { fl_DocSectionLayout * pDSL = getDocSectionLayout(); if(!pDSL->isCollapsing()) { _removeLine(pLine,true); } else { _removeLine(pLine,false); } pLine = static_cast(getFirstContainer()); } xxx_UT_DEBUGMSG(("Block collapsed in collapsed %x \n",this)); m_bIsCollapsed = true; m_iNeedsReformat = 0; UT_ASSERT(getFirstContainer() == NULL); UT_ASSERT(getLastContainer() == NULL); } void fl_BlockLayout::purgeLayout(void) { fp_Line* pLine = static_cast(getFirstContainer()); while (pLine) { _purgeLine(pLine); pLine = static_cast(getFirstContainer()); } UT_ASSERT(getFirstContainer() == NULL); UT_ASSERT(getLastContainer() == NULL); while (m_pFirstRun) { fp_Run* pNext = m_pFirstRun->getNextRun(); m_pFirstRun->setBlock(NULL); delete m_pFirstRun; m_pFirstRun = pNext; } } void fl_BlockLayout::_removeLine(fp_Line* pLine, bool bRemoveFromContainer) { if (getFirstContainer() == static_cast(pLine)) { setFirstContainer(static_cast(getFirstContainer()->getNext())); // we have to call recalcMaxWidth so that the new line has the correct // x offset and width if(getFirstContainer()) getFirstContainer()->recalcMaxWidth(); } if (getLastContainer() == static_cast(pLine)) { setLastContainer(static_cast(getLastContainer()->getPrev())); // we have to call recalcMaxWidth so that the new line has the correct // x offset and width if(getLastContainer()) getLastContainer()->recalcMaxWidth(); } pLine->setBlock(NULL); pLine->remove(); if(pLine->getContainer() && bRemoveFromContainer) { fp_VerticalContainer * pVert = static_cast(pLine->getContainer()); pVert->removeContainer(pLine); pLine->setContainer(NULL); } xxx_UT_DEBUGMSG(("Removed line %x \n",pLine)); UT_ASSERT(findLineInBlock(pLine) == -1); delete pLine; #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif } void fl_BlockLayout::_purgeLine(fp_Line* pLine) { if (getLastContainer() == static_cast(pLine)) { setLastContainer(static_cast(getLastContainer()->getPrev())); } if (getFirstContainer() == static_cast(pLine)) { setFirstContainer(static_cast(getFirstContainer()->getNext())); } pLine->setBlock(NULL); pLine->remove(); delete pLine; #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif } void fl_BlockLayout::_removeAllEmptyLines(void) { fp_Line* pLine; pLine = static_cast(getFirstContainer()); while (pLine) { if (pLine->isEmpty()) { fp_Line * pNext = static_cast(pLine->getNext()); _removeLine(pLine, true); pLine = pNext; } else { pLine = static_cast(pLine->getNext()); } } } /*! Truncate layout from the specified Run \param pTruncRun First Run to be truncated \return True This will remove all Runs starting from pTruncRun to the last Run on the block from their lines (and delete them from the display). \note The Run list may be inconsistent when this function is called, so no assertion. */ bool fl_BlockLayout::_truncateLayout(fp_Run* pTruncRun) { // Special case, nothing to do if (!pTruncRun) { return true; } if (m_pFirstRun == pTruncRun) { m_pFirstRun = NULL; } fp_Run * pRun = NULL; // Remove runs from screen. No need for HdrFtr's though if(!isHdrFtr()) { fp_Line * pLine = pTruncRun->getLine(); if(pLine != NULL) { pLine->clearScreenFromRunToEnd(pTruncRun); pLine = static_cast(pLine->getNext()); while(pLine) { pLine->clearScreen(); pLine= static_cast(pLine->getNext()); } } else { pRun = pTruncRun; while (pRun) { pRun->clearScreen(); pRun = pRun->getNextRun(); } } } // Remove runs from lines pRun = pTruncRun; while (pRun) { fp_Line* pLine = pRun->getLine(); if (pLine) pLine->removeRun(pRun, true); pRun = pRun->getNextRun(); } _removeAllEmptyLines(); return true; } /*! Move all Runs in the block onto a new line. This is only called during block creation when there are no existing lines in the block. */ void fl_BlockLayout::_stuffAllRunsOnALine(void) { UT_ASSERT(getFirstContainer() == NULL); fp_Line* pLine = static_cast(getNewContainer()); UT_return_if_fail(pLine); if(pLine->getContainer() == NULL) { fp_VerticalContainer * pContainer = NULL; if(m_pSectionLayout->getFirstContainer()) { // TODO assert something here about what's in that container pContainer = static_cast(m_pSectionLayout->getFirstContainer()); } else { pContainer = static_cast(m_pSectionLayout->getNewContainer()); UT_ASSERT(pContainer->getWidth() >0); } pContainer->insertContainer(static_cast(pLine)); } fp_Run* pTempRun = m_pFirstRun; while (pTempRun) { pTempRun->lookupProperties(); pLine->addRun(pTempRun); if(pTempRun->getType() == FPRUN_TEXT && !UT_BIDI_IS_STRONG(pTempRun->getDirection())) { // if the runs is not of a strong type, we have to ensure its visual direction gets // recalculated and buffer refreshed ... pTempRun->setVisDirection(UT_BIDI_UNSET); } pTempRun = pTempRun->getNextRun(); } UT_ASSERT(pLine->getContainer()); xxx_UT_DEBUGMSG(("fl_BlockLayout: Containing container for line is %x \n",pLine->getContainer())); pLine->recalcMaxWidth(); } /*! Add end-of-paragraph Run to block This function adds the EOP Run to the block. The presence of the EOP is an invariant (except for when merging / splitting blocks) and ensures that the cursor can always be placed on the last line of a block. If there are multiple lines, the first N-1 lines will have a forced break of some kind which can also hold the cursor. */ void fl_BlockLayout::_insertEndOfParagraphRun(void) { UT_ASSERT(!m_pFirstRun); fp_EndOfParagraphRun* pEOPRun = new fp_EndOfParagraphRun(this, 0, 0); m_pFirstRun = pEOPRun; m_bNeedsRedraw = true; // FIXME:jskov Why don't the header/footer need the line? //if (getSectionLayout() // && (getSectionLayout()->getType()== FL_SECTION_HDRFTR)) //{ // return; //} if (!getFirstContainer()) { getNewContainer(); m_bIsCollapsed = false; } fp_Line * pFirst = static_cast(getFirstContainer()); UT_ASSERT(pFirst && pFirst->countRuns() == 0); pFirst->addRun(m_pFirstRun); // only do the line layout if this block is not hidden ... FV_View * pView = getView(); bool bShowHidden = pView && pView->getShowPara(); FPVisibility eHidden = isHidden(); bool bHidden = ((eHidden == FP_HIDDEN_TEXT && !bShowHidden) || eHidden == FP_HIDDEN_REVISION || eHidden == FP_HIDDEN_FOLDED || eHidden == FP_HIDDEN_REVISION_AND_TEXT); if(!bHidden) pFirst->layout(); // Run list should be valid now. _assertRunListIntegrity(); } /*! Remove end-of-paragraph Run from block This function does the opposite of the _insertEndOfParagraphRun function. \note It should only be called by functions that do really low-level handling of blocks and only on newly created blocks. */ void fl_BlockLayout::_purgeEndOfParagraphRun(void) { UT_ASSERT(m_pFirstRun && FPRUN_ENDOFPARAGRAPH == m_pFirstRun->getType()); fp_Line * pFirstLine = static_cast(getFirstContainer()); UT_ASSERT(pFirstLine && pFirstLine->countRuns() == 1); // Run list should be valid when called (but not at exit!) _assertRunListIntegrity(); pFirstLine->removeRun(m_pFirstRun); delete m_pFirstRun; m_pFirstRun = NULL; pFirstLine->remove(); delete pFirstLine; setFirstContainer(NULL); setLastContainer(NULL); } /*! Split the line the Run resides on \param pRun The Run to split the line after There is never added any Runs as it happened in the past to ensure that both lines can hold the point. This is because the caller always does that now. */ void fl_BlockLayout::_breakLineAfterRun(fp_Run* pRun) { _assertRunListIntegrity(); // When loading a document, there may not have been created // lines yet. Get a first one created and hope for the best... // Sevior: Ah here is one source of the multi-level list bug we // need a last line from the previous block before we call this. if (getPrev() != NULL && getPrev()->getLastContainer() == NULL) { xxx_UT_DEBUGMSG(("In _breakLineAfterRun no LastLine \n")); xxx_UT_DEBUGMSG(("getPrev = %d this = %d \n", getPrev(), this)); //UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } // Add a line for the Run if necessary if (getFirstContainer() == NULL) _stuffAllRunsOnALine(); // Create the new line fp_Line* pNewLine = new fp_Line(getSectionLayout()); UT_ASSERT(pNewLine); // Insert it after the current line fp_Line* pLine = pRun->getLine(); pNewLine->setPrev(pLine); pNewLine->setNext(pLine->getNext()); if(pLine->getNext()) pLine->getNext()->setPrev(pNewLine); pLine->setNext(pNewLine); // Update LastContainer if necessary if (getLastContainer() == static_cast(pLine)) setLastContainer(pNewLine); // Set the block pNewLine->setBlock(this); // Add the line to the container static_cast(pLine->getContainer())->insertContainerAfter(static_cast(pNewLine), static_cast(pLine)); // Now add Runs following pRun on the same line to the new // line. fp_Run* pCurrentRun = pRun->getNextRun(); while (pCurrentRun && pCurrentRun->getLine() == pLine) { pLine->removeRun(pCurrentRun, true); pNewLine->addRun(pCurrentRun); pCurrentRun = pCurrentRun->getNextRun(); } // Update the layout information in the lines. pLine->layout(); pNewLine->layout(); #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif _assertRunListIntegrity(); } /*! * This method is called at the end of the layout method in * fp_VerticalContainer. It places the frames pointed to within the block at * the appropriate place on the appropriate page. Since we don't know where * this is until the lines in the block are placed in a column we have to * wait until botht eh column and lines are placed on the page. * * pLastLine is the last line placed inthe column. If the frame should be * placed after this line we don't place any frames that should be below * this line now. In this case we wait until pLastLine is below the frame. * * If pLastLine is NULL we place all the frames in this block on the screen. * */ bool fl_BlockLayout::setFramesOnPage(fp_Line * pLastLine) { if(getNumFrames() == 0) { return true; } UT_sint32 i = 0; for(i=0; i< getNumFrames();i++) { fl_FrameLayout * pFrame = getNthFrameLayout(i); if(pFrame->getContainerType() != FL_CONTAINER_FRAME) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); continue; } if(pFrame->getFramePositionTo() == FL_FRAME_POSITIONED_TO_BLOCK) { double xFpos = pFrame->getFrameXpos(); double yFpos = pFrame->getFrameYpos(); UT_DEBUGMSG(("xFpos %d yFpos %d \n",xFpos,yFpos)); // Now scan through the lines until we find a line below // yFpos double yoff = 0; fp_Line * pFirstLine = static_cast(getFirstContainer()); fp_Line * pCon = pFirstLine; if(pCon == NULL) { return false; } if(pCon->getNext() != NULL) { while(pCon && (pCon != pLastLine) && yoff < yFpos ) { yoff += pCon->getHeight(); yoff += pCon->getMarginBefore(); yoff += pCon->getMarginAfter(); pCon = static_cast(pCon->getNext()); } } if(pCon && (pCon == pLastLine) && (pCon != static_cast(getLastContainer())) && (yoff < yFpos)) { // Frame is not within the container so far filled. // try later continue; } // // Do this if we've found a line below the frame // if(pCon && pCon != pLastLine && yoff >= yFpos) { if(pCon->getPrev()) { pCon = static_cast(pCon->getPrev()); yoff -= pCon->getHeight(); yoff -= pCon->getMarginBefore(); yoff -= pCon->getMarginAfter(); UT_DEBUGMSG(("Final yoff %d \n",yoff)); } } if(!pCon) { pCon = pFirstLine; } // // OK at this point pCon is the first line above our frame. // The Frame should be placed on the same page as this line // fp_Page * pPage = pCon->getPage(); double Xref = pCon->getX(); if(pPage == NULL) { return false; } // // OK now calculate the offset from the first line to this page. // fp_Page * pFirstPage = pFirstLine->getPage(); UT_sint32 iFirstPageNo = getDocLayout()->findPage(pFirstPage); UT_sint32 iThisPageNo = getDocLayout()->findPage(pPage); double pageHeight = 0; double yLineOff,xLineOff; fp_VerticalContainer * pVCon = NULL; if(iThisPageNo > iFirstPageNo) { // // Calculate the distance from the position from the top // of the first line to the bottom of the page. // pVCon = (static_cast(pFirstLine->getContainer())); pVCon->getOffsets(pFirstLine, xLineOff, yLineOff); // // this is the offset relative to the page. now subtract it // from the height of the page fl_DocSectionLayout * pDSL = getDocSectionLayout(); pageHeight = pFirstPage->getHeight() - pDSL->getTopMargin() - pDSL->getBottomMargin(); yoff = pageHeight - yLineOff; yoff = pageHeight * (iThisPageNo- iFirstPageNo -1); } // // OK subtract this off from yFpos // yFpos -= yoff; pVCon = (static_cast(pCon->getContainer())); pVCon->getOffsets(pCon, xLineOff, yLineOff); UT_DEBUGMSG(("xLineOff %d yLineOff %d in block \n",xLineOff,yLineOff)); xFpos += xLineOff - Xref; // Never use the x-position // of the Line!!! yFpos += yLineOff; // OK, we have the X and Y positions of the frame relative to // the page. fp_FrameContainer * pFrameCon = getNthFrameContainer(i); // // The frame container may not yet be created. // if(pFrameCon) { pFrameCon->setX(xFpos); pFrameCon->setY(yFpos); if(pPage->findFrameContainer(pFrameCon) < 0) { pPage->insertFrameContainer(pFrameCon); } } } else if(pFrame->getFramePositionTo() == FL_FRAME_POSITIONED_TO_COLUMN) { fp_FrameContainer * pFrameCon = getNthFrameContainer(i); // // The frame container may not yet be created. // if(pFrameCon) { // // Handle case of block spanning two pages // fp_Line * pLLast = static_cast(getLastContainer()); UT_return_val_if_fail(pLLast,false); fp_Page * pPageLast = pLLast->getPage(); UT_return_val_if_fail(pPageLast,false); fp_Line * pLFirst = static_cast(getFirstContainer()); UT_return_val_if_fail(pLFirst,false); fp_Page * pPageFirst = pLFirst->getPage(); UT_return_val_if_fail(pPageFirst,false); fp_Page * pPage = pPageLast; fp_Line * pLine = pLLast; if(pPageFirst != pPageLast) { double idLast = fabs(pLLast->getY() - pFrame->getFrameYColpos()); double idFirst = fabs(pLFirst->getY() - pFrame->getFrameYColpos()); if(idFirst < idLast) { pPage = pPageFirst; pLine = pLFirst; } } fp_Container * pCol = pLine->getColumn(); UT_return_val_if_fail(pCol,false); pFrameCon->setX(pFrame->getFrameXColpos()+pCol->getX()); pFrameCon->setY(pFrame->getFrameYColpos()+pCol->getY()); if(pPage->findFrameContainer(pFrameCon) < 0) { pPage->insertFrameContainer(pFrameCon); } } } else if(pFrame->getFramePositionTo() == FL_FRAME_POSITIONED_TO_PAGE) { fp_FrameContainer * pFrameCon = getNthFrameContainer(i); // // The frame container may not yet be created. // if(pFrameCon) { // // Handle case of block spanning two pages // fp_Line * pLLast = static_cast(getLastContainer()); UT_return_val_if_fail(pLLast,false); fp_Page * pPageLast = pLLast->getPage(); UT_return_val_if_fail(pPageLast,false); fp_Line * pLFirst = static_cast(getFirstContainer()); UT_return_val_if_fail(pLFirst,false); fp_Page * pPageFirst = pLFirst->getPage(); UT_return_val_if_fail(pPageFirst,false); fp_Page * pPage = pPageLast; if(pPageFirst != pPageLast) { double idLast = fabs(pLLast->getY() - pFrame->getFrameYColpos()); double idFirst = fabs(pLFirst->getY() - pFrame->getFrameYColpos()); if(idFirst < idLast) { pPage = pPageFirst; } } pFrameCon->setX(pFrame->getFrameXPagepos()); pFrameCon->setY(pFrame->getFrameYPagepos()); if(pPage->findFrameContainer(pFrameCon) < 0) { pPage->insertFrameContainer(pFrameCon); } } } else { UT_DEBUGMSG(("Not implemented Yet \n")); UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } } return true; } /*! * This returns the distance from the first line in the block to the * line presented here. */ bool fl_BlockLayout::getXYOffsetToLine(double & xoff, double & yoff, fp_Line * pLine) { if(pLine == NULL) { return false; } xoff = 0; yoff = 0; fp_Line * pCon = static_cast(getFirstContainer()); while(pCon && (pCon != pLine)) { yoff += pCon->getHeight(); yoff += pCon->getMarginBefore(); yoff += pCon->getMarginAfter(); pCon = static_cast(pCon->getNext()); } if(pCon != pLine) { return false; } return true; } /*! * Calculate the height of the all the text contained by this block * Lines can force a sectionbreak by setting the m_bForceSectionBreak. * If a line height * changes or an ascent/descent changes we must do a section break. */ double fl_BlockLayout::getHeightOfBlock(void) { if(m_bForceSectionBreak) { m_bForceSectionBreak = false; return 0; } double iHeight = 0; fp_Line * pCon = static_cast(getFirstContainer()); while(pCon) { if(!pCon->isSameYAsPrevious()) { iHeight += pCon->getHeight(); iHeight += pCon->getMarginBefore(); iHeight += pCon->getMarginAfter(); } pCon = static_cast(pCon->getNext()); } return iHeight; } /*! * Force a sectionBreak by setting StartHeight to a ridiculus value */ void fl_BlockLayout::forceSectionBreak(void) { m_bForceSectionBreak = true; } /*! * Reformat a block from the line given. */ void fl_BlockLayout::formatWrappedFromHere(fp_Line * pLine, fp_Page * pPage) { // // Check line is in this block. It might haave been removed. // fp_Line *pCLine = static_cast(getFirstContainer()); bool bFound = false; while(pCLine) { if(pCLine == pLine) { bFound = true; break; } pCLine = static_cast(pCLine->getNext()); } if(!bFound) { _removeAllEmptyLines(); // try again return; } fp_Run * pRun = pLine->getLastRun(); if(pLine->getHeight() == 0) { pLine->recalcHeight(pRun); } pRun = pRun->getNextRun(); m_pVertContainer = static_cast(pLine->getContainer()); m_iLinePosInContainer = m_pVertContainer->findCon(pLine)+1; if(m_iLinePosInContainer < 0) { m_iLinePosInContainer = 0; } UT_Rect * pRec = pLine->getScreenRect(); m_iAccumulatedHeight = pRec->top; UT_Rect rec; rec.height = pRec->height; rec.width = pRec->width; rec.top = pRec->top; rec.left = pRec->left; delete pRec; m_bSameYAsPrevious = pLine->isSameYAsPrevious(); double iHeight = pLine->getHeight() + pLine->getMarginAfter(); // // Stuff remaining content on the line // while(pRun) { pLine->addRun(pRun); pRun= pRun->getNextRun(); } // // Remove all the lines after this // fp_Line * pDumLine = static_cast(pLine->getNext()); while(pDumLine) { fp_Line * pLineToDelete = pDumLine; pDumLine = static_cast(pDumLine->getNext()); pLineToDelete->setBlock(NULL); // delete this and remove from container _removeLine(pLineToDelete,true); } // // OK our line is the last line left // setLastContainer(pLine); // // OK now we have to adjust the X and max width of pLine to fit around // the wrapped objects. We do this by looping though the wrapped objects // on the page // double iX = getLeftMargin(); double iMaxX = m_pVertContainer->getWidth(); iMaxX -= getLeftMargin(); iMaxX -= getRightMargin(); bool bFirst = false; if(pLine == static_cast(getFirstContainer())) { bFirst = true; UT_BidiCharType iBlockDir = getDominantDirection(); if(iBlockDir == UT_BIDI_LTR) { iMaxX -= getTextIndent(); iX += getTextIndent(); } } // // OK Now adjust the left pos to make it bump up against either the // the left side of the container or the previous line. // fp_Line * pPrev = static_cast(pLine->getPrev()); double iWidth = 0; if(pPrev) { if(pLine->isSameYAsPrevious() && (pPrev->getY() == pLine->getY())) { iX = pPrev->getX() + pPrev->getMaxWidth(); iWidth = iMaxX - iX; } else { iWidth = iMaxX; pLine->setSameYAsPrevious(false); } } else { iWidth = iMaxX; } double xoff = rec.left - pLine->getX(); if(iWidth < 20*4) { xxx_UT_DEBUGMSG(("!!!!!!! ttttOOOO NAAARRRROOOWWWW iMaxX %d iX %d \n",iMaxX,iX)); // // Can't fit on this line. // transfer to new wrapped line and delete the old one // m_iAccumulatedHeight += iHeight; m_bSameYAsPrevious = false; fp_Line * pNew = getNextWrappedLine(iX,iHeight,pPage); while(pNew && (pNew->getPrev() != pLine)) { pNew = static_cast(pNew->getPrev()); } fp_Run * pRun = pLine->getFirstRun(); while(pRun) { pNew->addRun(pRun); pRun= pRun->getNextRun(); } _removeLine(pLine,true); pLine = pNew; if(bFirst) { setFirstContainer(pLine); } } else { rec.left = iX + xoff; rec.width = iWidth; UT_sint32 i = 0; fp_FrameContainer * pFC = NULL; for(i=0; i< static_cast(pPage->countFrameContainers());i++) { pFC = pPage->getNthFrameContainer(i); if(!pFC->isWrappingSet()) { continue; } UT_Rect * pRec = pFC->getScreenRect(); bool bIsTight = pFC->isTightWrapped(); fl_FrameLayout * pFL = static_cast(pFC->getSectionLayout()); double iExpand = pFL->getBoundingSpace() + 2.0; pRec->height += 2*iExpand; pRec->width += 2*iExpand; pRec->left -= iExpand; pRec->top -= iExpand; if(rec.intersectsRect(pRec)) { if(!pFC->overlapsRect(rec)) { delete pRec; continue; } if((pRec->left <= rec.left) && (pRec->left + pRec->width) > rec.left) { double iRightP = 0; if(bIsTight) { // // Project back into image over the transparent region // iRightP = pFC->getRightPad(m_iAccumulatedHeight,iHeight) - iExpand +2; // FIXME: this random, non-tlu should NOT be here - MARCM UT_DEBUGMSG(("Project Right (3) %d \n",iRightP)); } double diff = pRec->left + pRec->width - rec.left - iRightP; rec.left = pRec->left + pRec->width + iRightP; rec.width -= diff; } else if((pRec->left >= rec.left) && (rec.left + rec.width > pRec->left)) { double iLeftP = 0; if(bIsTight) { // // Project into the image over the transparent region // iLeftP = pFC->getLeftPad(m_iAccumulatedHeight,iHeight); UT_DEBUGMSG(("Project into (3) image with distance %d \n",iLeftP)); } double diff = pRec->left - rec.left - iLeftP; rec.width = diff; } } delete pRec; } iX = rec.left - xoff; pLine->setX(iX); if(rec.width < 20*4) { // // Can't fit on this line. // transfer to new wrapped line and delete the old one // xxx_UT_DEBUGMSG(("Line too narrow in formatwrapped %x block %d \n",pLine,this)); iX = getLeftMargin(); bool bFirst = false; if(pLine == static_cast(getFirstContainer())) { bFirst = true; UT_BidiCharType iBlockDir = getDominantDirection(); if(iBlockDir == UT_BIDI_LTR) { iX += getTextIndent(); } } m_iAccumulatedHeight += iHeight; m_bSameYAsPrevious = false; fp_Line * pNew = getNextWrappedLine(iX,iHeight,pPage); while(pNew && static_cast(pNew->getPrev()) != pLine) { pNew = static_cast(pNew->getPrev()); } fp_Run * pRun = pLine->getFirstRun(); while(pRun) { pNew->addRun(pRun); pRun= pRun->getNextRun(); } _removeLine(pLine,true); pLine = pNew; if(bFirst) { pLine->setPrev(NULL); setFirstContainer(pLine); } } else { m_bSameYAsPrevious = true; xxx_UT_DEBUGMSG(("Max width 1 set to %d \n",rec.width)); pLine->setMaxWidth(rec.width); } } // // OK, Now we have one long line with all our remaining content. // Break it to fit in the container and around the wrapped objects // // Reformat paragraph m_Breaker.breakParagraph(this, pLine,pPage); xxx_UT_DEBUGMSG(("Format wrapped text in blobk %x \n",this)); pLine = static_cast(getFirstContainer()); while(pLine) { pLine->recalcHeight(); pLine = static_cast(pLine->getNext()); } UT_ASSERT(getLastContainer()); if(!m_pLayout->isLayoutFilling()) { m_iNeedsReformat = -1; } if(m_pAlignment && m_pAlignment->getType() == FB_ALIGNMENT_JUSTIFY) { fp_Line* pLastLine = static_cast(getLastContainer()); pLastLine->resetJustification(true); // permanent reset } #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif return; } /*! * Create a new line that will fit between positioned objects on the page. * iX is the position of the last X coordinate of the previous * Line relative to it's container. The X location of wrapped line will be greater than this. * iHeight is the assumed height of the line (at first approximation this is the height of the previous line). * pPage Pointer to the page with the positioned objects. */ fp_Line * fl_BlockLayout::getNextWrappedLine(double iX, double iHeight, fp_Page * pPage) { double iMaxX = m_pVertContainer->getWidth(); double iXDiff = getLeftMargin(); UT_ASSERT(iHeight > 0); if(iHeight == 0) { if(getLastContainer()) { iHeight = getLastContainer()->getHeight(); } if(iHeight == 0) { iHeight = m_pLayout->getGraphics()->tlu(2); } } iMaxX -= getLeftMargin(); iMaxX -= getRightMargin(); if (getFirstContainer() == NULL) { UT_BidiCharType iBlockDir = getDominantDirection(); if(iBlockDir == UT_BIDI_LTR) { iMaxX -= getTextIndent(); iXDiff += getTextIndent(); } } double xoff,yoff; pPage->getScreenOffsets(m_pVertContainer,xoff,yoff); fp_FrameContainer * pFC = NULL; fp_Line * pLine = NULL; if((iMaxX - iX) < 20*4) { xxx_UT_DEBUGMSG(("!!!!!!! ttttOOOO NAAARRRROOOWWWW iMaxX %d iX %d \n",iMaxX,iX)); iX = getLeftMargin(); m_iAccumulatedHeight += iHeight; m_bSameYAsPrevious = false; } else { UT_sint32 i = 0; double iScreenX = iX + xoff; UT_Rect projRec; projRec.left = iScreenX; projRec.height = iHeight; projRec.width = iMaxX - (iX - iXDiff); projRec.top = m_iAccumulatedHeight; bool bIsTight = false; for(i=0; i< static_cast(pPage->countFrameContainers());i++) { pFC = pPage->getNthFrameContainer(i); if(!pFC->isWrappingSet()) { continue; } bIsTight = pFC->isTightWrapped(); UT_Rect * pRec = pFC->getScreenRect(); fl_FrameLayout * pFL = static_cast(pFC->getSectionLayout()); double iExpand = pFL->getBoundingSpace() + 2; pRec->height += 2*iExpand; pRec->width += 2*iExpand; pRec->left -= iExpand; pRec->top -= iExpand; if(projRec.intersectsRect(pRec)) { if(!pFC->overlapsRect(projRec)) { delete pRec; continue; } if((pRec->left <= projRec.left) && (pRec->left + pRec->width) > projRec.left) { double iRightP = 0; if(bIsTight) { // // Project back into image over the transparent region // iRightP = pFC->getRightPad(m_iAccumulatedHeight,iHeight) - iExpand +2; // FIXME: this random non-tlu 2 should NOT be here - MARCM UT_DEBUGMSG(("Project Right %d \n",iRightP)); } double diff = pRec->left + pRec->width - projRec.left - iRightP; projRec.left = pRec->left + pRec->width + iRightP; projRec.width -= diff; } else if((pRec->left >= projRec.left) && (projRec.left + projRec.width > pRec->left)) { double iLeftP = 0; if(bIsTight) { // // Project into the image over the transparent region // iLeftP = pFC->getLeftPad(m_iAccumulatedHeight,iHeight); UT_DEBUGMSG(("Project into (1) image with distance %d \n",iLeftP)); } double diff = pRec->left - projRec.left - iLeftP; projRec.width = diff; } } delete pRec; } if(projRec.width < 20*4) { iX = getLeftMargin(); if (getFirstContainer() == NULL) { UT_BidiCharType iBlockDir = getDominantDirection(); if(iBlockDir == UT_BIDI_LTR) iX += getTextIndent(); } m_iAccumulatedHeight += iHeight; m_bSameYAsPrevious = false; } else { pLine = new fp_Line(getSectionLayout()); fp_Line* pOldLastLine = static_cast(getLastContainer()); if(pOldLastLine == NULL) { setFirstContainer(pLine); setLastContainer(pLine); pLine->setBlock(this); m_pVertContainer->insertConAt(pLine,m_iLinePosInContainer); m_iLinePosInContainer++; pLine->setContainer(m_pVertContainer); UT_DEBUGMSG(("Max width 2 set to %d \n",projRec.width)); pLine->setMaxWidth(projRec.width); pLine->setX(projRec.left-xoff); pLine->setSameYAsPrevious(false); pLine->setWrapped((iMaxX != projRec.width)); m_bSameYAsPrevious = true; } else { pLine->setPrev(getLastContainer()); getLastContainer()->setNext(pLine); setLastContainer(pLine); fp_VerticalContainer * pContainer = static_cast(pOldLastLine->getContainer()); pLine->setWrapped((iMaxX != projRec.width)); pLine->setBlock(this); if(pContainer) { pContainer->insertContainerAfter(static_cast(pLine), static_cast(pOldLastLine)); m_iLinePosInContainer = pContainer->findCon(pLine)+1; pLine->setContainer(pContainer); } UT_DEBUGMSG(("Max width 3 set to %d \n",projRec.width)); pLine->setMaxWidth(projRec.width); pLine->setX(projRec.left-xoff); pLine->setSameYAsPrevious(m_bSameYAsPrevious); m_bSameYAsPrevious = true; } UT_DEBUGMSG(("-1- New line %x has X %d Max width %d wrapped %d sameY %d \n",pLine,pLine->getX(),pLine->getMaxWidth(),pLine->isWrapped(),pLine->isSameYAsPrevious())); pLine->setHeight(iHeight); #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif UT_ASSERT(findLineInBlock(pLine) >= 0); return pLine; } } bool bStop = false; while(!bStop) { UT_sint32 i = 0; double iScreenX = iX + xoff; UT_Rect projRec; projRec.left = iScreenX; projRec.height = iHeight; projRec.width = iMaxX - (iX - iXDiff); projRec.top = m_iAccumulatedHeight; bool bIsTight = false; for(i=0; i< static_cast(pPage->countFrameContainers());i++) { pFC = pPage->getNthFrameContainer(i); if(!pFC->isWrappingSet()) { continue; } bIsTight = pFC->isTightWrapped(); UT_Rect * pRec = pFC->getScreenRect(); fl_FrameLayout * pFL = static_cast(pFC->getSectionLayout()); double iExpand = pFL->getBoundingSpace() +2; pRec->height += 2*iExpand; pRec->width += 2*iExpand; pRec->left -= iExpand; pRec->top -= iExpand; if(projRec.intersectsRect(pRec)) { if(!pFC->overlapsRect(projRec)) { delete pRec; continue; } if((pRec->left <= projRec.left) && (pRec->left + pRec->width) > projRec.left) { double iRightP = 0; if(bIsTight) { // // Project back into image over the transparent region // iRightP = pFC->getRightPad(m_iAccumulatedHeight,iHeight) - iExpand +2; // FIXME: this ranomd non-tlu +2 should NOT be here - MARCM UT_DEBUGMSG(("Project Right %d \n",iRightP)); } double diff = pRec->left + pRec->width - projRec.left - iRightP; projRec.left = pRec->left + pRec->width + iRightP; projRec.width -= diff; } else if((pRec->left >= projRec.left) && (projRec.left + projRec.width > pRec->left)) { double iLeftP = 0; if(bIsTight) { // // Project into the image over the transparent region // iLeftP = pFC->getLeftPad(m_iAccumulatedHeight,iHeight); UT_DEBUGMSG(("Project into (2) image with distance %d \n",iLeftP)); } double diff = pRec->left - projRec.left - iLeftP; projRec.width = diff; } } delete pRec; } fp_Line* pLine = new fp_Line(getSectionLayout()); fp_Line* pOldLastLine = static_cast(getLastContainer()); if(projRec.width > 20*4) { if(pOldLastLine == NULL) { xxx_UT_DEBUGMSG(("Old Lastline NULL?????? \n")); setFirstContainer(pLine); setLastContainer(pLine); pLine->setBlock(this); m_pVertContainer->insertConAt(pLine,m_iLinePosInContainer); m_iLinePosInContainer++; pLine->setContainer(m_pVertContainer); xxx_UT_DEBUGMSG(("Max width 4 set to %d \n",projRec.width)); pLine->setMaxWidth(projRec.width); pLine->setX(projRec.left-xoff); pLine->setSameYAsPrevious(false); pLine->setWrapped((iMaxX != projRec.width)); m_bSameYAsPrevious = true; } else { pLine->setPrev(getLastContainer()); getLastContainer()->setNext(pLine); setLastContainer(pLine); fp_VerticalContainer * pContainer = static_cast(pOldLastLine->getContainer()); pLine->setWrapped((iMaxX != projRec.width)); pLine->setBlock(this); if(pContainer) { pContainer->insertContainerAfter(static_cast(pLine), static_cast(pOldLastLine)); m_iLinePosInContainer = pContainer->findCon(pLine)+1; pLine->setContainer(pContainer); } xxx_UT_DEBUGMSG(("Max width 5 set to %d \n",projRec.width)); pLine->setMaxWidth(projRec.width); pLine->setX(projRec.left-xoff); pLine->setSameYAsPrevious(m_bSameYAsPrevious); m_bSameYAsPrevious = true; } xxx_UT_DEBUGMSG(("-2- New line %x has X %d Max width %d wrapped %d sameY %d \n",pLine,pLine->getX(),pLine->getMaxWidth(),pLine->isWrapped(),pLine->isSameYAsPrevious())); pLine->setHeight(iHeight); UT_ASSERT(findLineInBlock(pLine) >= 0); return pLine; } xxx_UT_DEBUGMSG(("Max width 6 set to %d \n",20)); pLine->setMaxWidth(20); pLine->setX(projRec.left-xoff); pLine->setBlock(this); pLine->setSameYAsPrevious(false); pLine->setWrapped((iMaxX != projRec.width)); pOldLastLine = static_cast(getLastContainer()); if(pOldLastLine) { pLine->setPrev(getLastContainer()); getLastContainer()->setNext(pLine); setLastContainer(pLine); fp_VerticalContainer * pContainer = static_cast(pOldLastLine->getContainer()); if(pContainer) { pContainer->insertContainerAfter(static_cast(pLine), static_cast(pOldLastLine)); m_iLinePosInContainer = pContainer->findCon(pLine)+1; pLine->setContainer(pContainer); } } else { setFirstContainer(pLine); setLastContainer(pLine); m_pVertContainer->insertConAt(pLine,m_iLinePosInContainer); m_iLinePosInContainer++; pLine->setContainer(m_pVertContainer); } m_bSameYAsPrevious = false; iX = getLeftMargin(); m_iAccumulatedHeight += iHeight; } xxx_UT_DEBUGMSG(("-3- New line %x has X %d Max width %d wrapped %d sameY %d \n",pLine,pLine->getX(),pLine->getMaxWidth(),pLine->isWrapped(),pLine->isSameYAsPrevious())); pLine->setHeight(iHeight); #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif UT_ASSERT(findLineInBlock(pLine) >= 0); return pLine; } void fl_BlockLayout::formatAll(void) { m_iNeedsReformat = 0; format(); } /*! Format paragraph: split the content into lines which will fit in the container. */ void fl_BlockLayout::format() { if((isHidden() >= FP_HIDDEN_FOLDED) || (m_pSectionLayout->isHidden() >= FP_HIDDEN_FOLDED)) { xxx_UT_DEBUGMSG(("Don't format coz I'm hidden! \n")); return; } #if 0 if(m_pLayout->isLayoutFilling()) { if(!m_bIsTOC) { if(!isNotTOCable()) { m_bStyleInTOC = m_pLayout->addOrRemoveBlockFromTOC(this); } } } #endif bool bJustifyStuff = false; xxx_UT_DEBUGMSG(("Format block %x needsreformat %d m_pFirstRun %x \n",this,m_iNeedsReformat,m_pFirstRun)); fl_ContainerLayout * pCL = myContainingLayout(); while(pCL && (pCL->getContainerType() != FL_CONTAINER_DOCSECTION) && (pCL->getContainerType() != FL_CONTAINER_SHADOW)) { pCL = pCL->myContainingLayout(); } if(pCL && (pCL->getContainerType() == FL_CONTAINER_SHADOW)) { xxx_UT_DEBUGMSG(("Formatting a block in a shadow \n")); xxx_UT_DEBUGMSG(("m_pSectionLayout Type is %d \n",m_pSectionLayout->getContainerType())); } // // If block hasn't changed don't format it. // if((m_iNeedsReformat == -1) && !m_bIsCollapsed) { return; } // // Should be ablke to get away with just formatting from the first line // containing m_iNeedsReformat // if(m_pAlignment && m_pAlignment->getType() == FB_ALIGNMENT_JUSTIFY) { m_iNeedsReformat = 0; bJustifyStuff = true; } // // Save the old height of the block. We compare to the new height after // the format. // double iOldHeight = getHeightOfBlock(); xxx_UT_DEBUGMSG(("Old Height of block %d \n",iOldHeight)); // // Need this to find where to break section in the document. // fl_ContainerLayout * pPrevCL = getPrev(); while(pPrevCL && pPrevCL->getContainerType() != FL_CONTAINER_BLOCK) { pPrevCL = pPrevCL->getPrev(); } fp_Page * pPrevP = NULL; if(pPrevCL) { fp_Container * pPrevCon = pPrevCL->getFirstContainer(); if(pPrevCon) { pPrevP = pPrevCon->getPage(); } } xxx_UT_DEBUGMSG(("fl_BlockLayout - format \n")); _assertRunListIntegrity(); fp_Run *pRunToStartAt = NULL; // TODO -- is this really needed? // is should not be, since _lookupProperties is explicitely called // by our listeners when the format changes // please do not uncomment this as a quick bugfix to some other // problem, and if you do uncomment it, please explain why - Tomas // lookupProperties(); // Some fields like clock, character count etc need to be constantly updated // This is best done in the background updater which examines every block // in the document. To save scanning every run in the entire document we // set a bool in blocks with these sort of fields. // setUpdatableField(false); xxx_UT_DEBUGMSG(("formatBlock 3: pPage %x \n",pPrevP)); if (m_pFirstRun) { if(m_iNeedsReformat > 0) { // only a part of this block need reformat, find the run that // contains this offset fp_Run * pR = m_pFirstRun; while(pR && (pR->getBlockOffset() + pR->getLength()) <= static_cast(m_iNeedsReformat)) pR = pR->getNextRun(); UT_ASSERT( pR ); pRunToStartAt = pR; } else pRunToStartAt = m_pFirstRun; // // Reset justification before we recalc width of runs // fp_Run* pRun = m_pFirstRun; // // Save old X position and width // while(pRun) { pRun->setTmpX(pRun->getX()); pRun->setTmpY(pRun->getY()); pRun->setTmpWidth(pRun->getWidth()); pRun->setTmpLine(pRun->getLine()); pRun = pRun->getNextRun(); } fp_Line* pLine = static_cast(getFirstContainer()); while(pLine && bJustifyStuff) { pLine->resetJustification(!bJustifyStuff); // temporary reset pLine = static_cast(pLine->getNext()); } // Recalculate widths of Runs if necessary. bool bDoit = false; // was false. Same kludge from pRun = m_pFirstRun; // sevior. Kludge very expensive, // proper fix required. Tomas while (pRun) { if(pRun->getType() == FPRUN_FIELD) { fp_FieldRun * pFRun = static_cast( pRun); if(pFRun->needsFrequentUpdates()) { setUpdatableField(true); } } if(pRun == pRunToStartAt) bDoit = true; if(bJustifyStuff || (bDoit && (pRun->getType() != FPRUN_ENDOFPARAGRAPH))) { pRun->recalcWidth(); xxx_UT_DEBUGMSG(("Run %x has width %d \n",pRun,pRun->getWidth())); } if(pRun->getType() == FPRUN_ENDOFPARAGRAPH) { pRun->lookupProperties(); } pRun = pRun->getNextRun(); } // Create the first line if necessary. if (!getFirstContainer()) { collapse(); // remove all old content _stuffAllRunsOnALine(); fp_Line * pLine = static_cast(getFirstContainer()); pLine->resetJustification(true); } recalculateFields(0); // Reformat paragraph m_Breaker.breakParagraph(this, NULL,NULL); } else { UT_DEBUGMSG(("NO block content. Insert an EOP \n")); // No paragraph content. Just insert the EOP Run. _removeAllEmptyLines(); _insertEndOfParagraphRun(); } if ((m_pAutoNum && isListLabelInBlock() == true) && (m_bListLabelCreated == false)) { m_bListLabelCreated =true; } _assertRunListIntegrity(); fp_Line* pLastLine = static_cast(getLastContainer()); if(pLastLine->getContainerType() == FP_CONTAINER_LINE) { if( bJustifyStuff) { pLastLine->resetJustification(bJustifyStuff); // permanent reset pLastLine->layout(); } } fp_Run * pRun = m_pFirstRun; // // Compare old positions and width. Clear those that don't match. // while(pRun) { pRun->clearIfNeeded(); pRun = pRun->getNextRun(); } #if 1 // was previously after breakParagraph. Idea is to make this a less // frequent occurance. So the paragraph get's lines coalessed // whenever the height changes. So we don't do this on every key press // but on average the paragraph gets coalessed. // the down-side of this is that on the active line we keep // spliting/merging if the editing position is not at either // end; the up-side is that at any given time our document // is represented by the minimal number of runs necessary, // which not only means that we use less memory, but more // importantly, we draw faster since any line with uniform // formatting is drawn by a single call to OS text drawing // routine coalesceRuns(); #endif m_bIsCollapsed = false; xxx_UT_DEBUGMSG(("Block Uncollapsed in format \n")); // // Only break section if the height of the block changes. // double iNewHeight = getHeightOfBlock(); xxx_UT_DEBUGMSG(("New height of block %f.2 \n",iNewHeight)); if(UT_dEQ(iNewHeight,0) || !UT_dEQ(iOldHeight,iNewHeight)) { if(getSectionLayout()->getContainerType() != FL_CONTAINER_DOCSECTION) { getSectionLayout()->setNeedsReformat(); if(getSectionLayout()->getContainerType() == FL_CONTAINER_CELL) { // // Can speed up things by doing an immediate format on the // the cell. // fl_CellLayout * pCL = static_cast(getSectionLayout()); if(!pCL->isDoingFormat()) { getSectionLayout()->format(); } } } getDocSectionLayout()->setNeedsSectionBreak(true,pPrevP); } // Paragraph has been reformatted. if(!m_pLayout->isLayoutFilling()) { m_iNeedsReformat = -1; } else { m_iNeedsReformat = 0; } return; // TODO return code } UT_sint32 fl_BlockLayout::findLineInBlock(fp_Line * pLine) { fp_Line * pTmpLine = static_cast(getFirstContainer()); UT_sint32 i = 0; while(pTmpLine && pTmpLine != pLine) { i++; pTmpLine = static_cast(pTmpLine->getNext()); } if(pTmpLine == NULL) { return -1; } return i; } void fl_BlockLayout::markAllRunsDirty(void) { fp_Run * pRun = m_pFirstRun; while(pRun) { pRun->markAsDirty(); pRun = pRun->getNextRun(); } fp_Line * pLine = static_cast(getFirstContainer()); while(pLine) { pLine->setNeedsRedraw(); pLine = static_cast(pLine->getNext()); } } void fl_BlockLayout::redrawUpdate() { // // This can happen from the new deleteStrux code // bool bFirstLineOn = false; bool bLineOff = false; xxx_UT_DEBUGMSG(("redrawUpdate Called \n")); // TODO -- is this really needed ?? // we should not need to lookup properties on redraw, // lookupProperties() gets explicitely called by our listeners // when format changes. // please do not uncomment this as a quick bugfix to some other // problem, and if you do uncomment it, please explain why - Tomas // lookupProperties(); if(isHdrFtr()) return; if(needsReformat()) { xxx_UT_DEBUGMSG(("redrawUpdate Called doing format \n")); format(); if(m_pAlignment && m_pAlignment->getType() == FB_ALIGNMENT_JUSTIFY) { markAllRunsDirty(); fp_Line* pLine = static_cast(getFirstContainer()); while (pLine) { xxx_UT_DEBUGMSG(("Drawing line in redraw update after format %x \n",pLine)); pLine->draw(m_pFirstRun->getGraphics()); pLine = static_cast(pLine->getNext()); } m_bNeedsRedraw = false; return; } } fp_Line* pLine = static_cast(getFirstContainer()); while (pLine) { if (pLine->needsRedraw()) { bLineOff = pLine->redrawUpdate(); bFirstLineOn |= bLineOff; } if(bFirstLineOn && !bLineOff) { // we are past all visible lines break; } pLine = static_cast(pLine->getNext()); } m_bNeedsRedraw = false; // lookupProperties(); } fp_Container* fl_BlockLayout::getNewContainer(fp_Container * /* pCon*/) { fp_Line* pLine = new fp_Line(getSectionLayout()); // TODO: Handle out-of-memory UT_ASSERT(pLine); fp_TableContainer * pPrevTable = NULL; fp_TOCContainer * pPrevTOC = NULL; pLine->setBlock(this); pLine->setNext(NULL); fp_VerticalContainer* pContainer = NULL; if (getLastContainer()) { fp_Line* pOldLastLine = static_cast(getLastContainer()); UT_ASSERT(getFirstContainer()); UT_ASSERT(!getLastContainer()->getNext()); pLine->setPrev(getLastContainer()); getLastContainer()->setNext(pLine); setLastContainer(pLine); pContainer = static_cast(pOldLastLine->getContainer()); pContainer->insertContainerAfter(static_cast(pLine), static_cast(pOldLastLine)); } else { UT_ASSERT(!getFirstContainer()); setFirstContainer(pLine); setLastContainer(getFirstContainer()); pLine->setPrev(NULL); fp_Line* pPrevLine = NULL; if(getPrev()) { if(getPrev()->getLastContainer() == NULL) { // Previous block exists but doesn't have a last line. // This is a BUG. Try a work around for now. TODO Fix this elsewhere UT_DEBUGMSG(("BUG!!! Previous block exists with no last line. This should not happen \n")); // getPrev()->format(); } } if (getPrev() && getPrev()->getLastContainer()) { fp_Container * pPrevCon = static_cast(getPrev()->getLastContainer()); if(pPrevCon->getContainerType() == FP_CONTAINER_LINE) { pContainer = static_cast(pPrevCon->getContainer()); pPrevLine = static_cast(pPrevCon); UT_ASSERT(pContainer); UT_ASSERT(pContainer->getWidth() >0); } else { fp_Container * ppPrev = static_cast(pPrevCon); if(ppPrev && ((ppPrev->getContainerType() == FP_CONTAINER_ENDNOTE) || (ppPrev->getContainerType() == FP_CONTAINER_FOOTNOTE) || (ppPrev->getContainerType() == FP_CONTAINER_FRAME) )) { fl_ContainerLayout * pCL = static_cast(ppPrev->getSectionLayout()); while(pCL && (pCL->getContainerType() == FL_CONTAINER_FOOTNOTE) || (pCL->getContainerType() == FL_CONTAINER_ENDNOTE)|| (pCL->getContainerType() == FL_CONTAINER_FRAME)) { pCL = pCL->getPrev(); } if(pCL) { ppPrev = pCL->getLastContainer(); } else { ppPrev = NULL; } } if(ppPrev && (ppPrev->getContainerType() == FP_CONTAINER_LINE)) { pPrevLine = static_cast(ppPrev); pContainer = static_cast(pPrevLine->getContainer()); UT_ASSERT(pContainer); UT_ASSERT(pContainer->getWidth() >0); } else if(ppPrev && (ppPrev->getContainerType() == FP_CONTAINER_TABLE)) { pContainer = (fp_VerticalContainer *) ppPrev->getContainer(); pPrevLine = NULL; pPrevTable = (fp_TableContainer*)ppPrev; } else if(ppPrev && (ppPrev->getContainerType() == FP_CONTAINER_TOC)) { pContainer = (fp_VerticalContainer *) ppPrev->getContainer(); pPrevLine = NULL; pPrevTOC = (fp_TOCContainer*)ppPrev; } else { pPrevLine = NULL; pContainer = NULL; } } } else { // // Skip any footnotes or endnotes // fl_ContainerLayout * pCL = getNext(); while(pCL && ((pCL->getContainerType() == FL_CONTAINER_ENDNOTE) || (pCL->getContainerType() == FL_CONTAINER_FOOTNOTE)) ) { pCL = pCL->getNext(); } if (pCL && pCL->getFirstContainer() && pCL->getFirstContainer()->getContainer()) { pContainer = static_cast(pCL->getFirstContainer()->getContainer()); UT_return_val_if_fail(pContainer, NULL); UT_ASSERT_HARMLESS(pContainer->getWidth() >0); } else if (myContainingLayout()->getFirstContainer()) { // TODO assert something here about what's in that container pContainer = static_cast(myContainingLayout()->getFirstContainer()); UT_return_val_if_fail(pContainer, NULL); UT_ASSERT_HARMLESS(pContainer->getWidth() >0); } else { pContainer = static_cast(myContainingLayout()->getNewContainer()); UT_return_val_if_fail(pContainer, NULL); UT_ASSERT_HARMLESS(pContainer->getWidth() >0); } } if(pContainer == NULL) { pContainer = static_cast(m_pSectionLayout->getNewContainer()); UT_return_val_if_fail(pContainer, NULL); UT_ASSERT_HARMLESS(pContainer->getWidth() >0); } if ((pPrevLine==NULL) && (pPrevTable== NULL) && (pPrevTOC == NULL)) { pContainer->insertContainer(static_cast(pLine)); } else if((pPrevLine==NULL) &&(NULL!=pPrevTable)) { pContainer->insertContainerAfter((fp_Container *)pLine, (fp_Container *) pPrevTable); } else if((pPrevLine==NULL) &&(NULL!=pPrevTOC)) { pContainer->insertContainerAfter((fp_Container *)pLine, (fp_Container *) pPrevTOC); } else { pContainer->insertContainerAfter(static_cast(pLine), static_cast(pPrevLine)); } } UT_ASSERT(pLine->getContainer()); #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif UT_ASSERT(findLineInBlock(pLine) >= 0); return static_cast(pLine); } void fl_BlockLayout::setNeedsReformat(UT_uint32 offset) { // _lesser_ value is the one that matter here, Tomas, Nov 28, 2003 if(m_iNeedsReformat < 0 || static_cast(offset) < m_iNeedsReformat) m_iNeedsReformat = offset; getSectionLayout()->setNeedsReformat(); setNeedsRedraw(); } void fl_BlockLayout::setNeedsRedraw(void) { m_bNeedsRedraw = true; getSectionLayout()->setNeedsRedraw(); } const char* fl_BlockLayout::getProperty(const XML_Char * pszName, bool bExpandStyles) const { const PP_AttrProp * pSpanAP = NULL; const PP_AttrProp * pBlockAP = NULL; const PP_AttrProp * pSectionAP = NULL; getAP(pBlockAP); // at the moment this is only needed in the bidi build, where dom-dir property // can be inherited from the section; however, it the future this might need to // be added for the normal build too. m_pSectionLayout->getAP(pSectionAP); return PP_evalProperty(pszName,pSpanAP,pBlockAP,pSectionAP,m_pDoc,bExpandStyles); } /*! * This method returns the length of the Block, including the initial strux. * so if "i" is the position of the block strux, i+getLength() will be the * position of the strux (whatever it might be), following this block. * The length includes any embedded struxes (like footnotes and endnotes). */ UT_sint32 fl_BlockLayout::getLength() { PT_DocPosition posThis = getPosition(true); PL_StruxDocHandle nextSDH =NULL; m_pDoc->getNextStrux(getStruxDocHandle(),&nextSDH); if(nextSDH == NULL) { // // Here if we reach EOD. // PT_DocPosition docEnd; m_pDoc->getBounds(true, docEnd); UT_sint32 length = static_cast(docEnd) - static_cast(posThis); return length; } PT_DocPosition posNext = m_pDoc->getStruxPosition(nextSDH); // // OK Look to see if we've got a TOC/ENDTOC at the end. If so subtract // it's length. // pf_Frag * pf = m_pDoc->getFragFromPosition(posNext-1); if(pf->getType() == pf_Frag::PFT_Strux) { pf_Frag_Strux * pfsTemp = static_cast(pf); if (pfsTemp->getStruxType() == PTX_EndTOC) // did we find it { posNext -= 2; } } UT_sint32 length = static_cast(posNext) - static_cast(posThis); return length; } const PP_PropertyType * fl_BlockLayout::getPropertyType(const XML_Char * pszName, tProperty_type Type, bool bExpandStyles) const { const PP_AttrProp * pSpanAP = NULL; const PP_AttrProp * pBlockAP = NULL; const PP_AttrProp * pSectionAP = NULL; getAP(pBlockAP); return PP_evalPropertyType(pszName,pSpanAP,pBlockAP,pSectionAP,Type,m_pDoc,bExpandStyles); } /*! Get block's position in document \param bActualBlockPos When true return block's position. When false return position of first run in block \return Position of block (or first run in block) \fixme Split in two functions if called most often with FALSE */ PT_DocPosition fl_BlockLayout::getPosition(bool bActualBlockPos) const { PT_DocPosition pos = m_pDoc->getStruxPosition(getStruxDocHandle()); // it's usually more useful to know where the runs start if (!bActualBlockPos) pos += fl_BLOCK_STRUX_OFFSET; return pos; } void fl_BlockLayout::getLineSpacing(double& dSpacing, eSpacingPolicy& eSpacing) const { dSpacing = m_dLineSpacing; eSpacing = m_eSpacingPolicy; } bool fl_BlockLayout::getBlockBuf(UT_GrowBuf * pgb) const { return m_pDoc->getBlockBuf(getStruxDocHandle(), pgb); } /*! Compute insertion point (caret) coordinates and size \param iPos Document position of cursor \param bEOL Set if EOL position is wanted \retval x X position (LTR) \retval y Y position (LTR) \retval x2 X position (RTL) \retval y2 Y position (RTL) \retval height Height of carret \retval bDirection Editing direction (true = LTR, false = RTL) \return The Run containing (or next to) the carret, or NULL if the block has no formatting information. \fixme bDirection should be an enum type */ fp_Run* fl_BlockLayout::findPointCoords(PT_DocPosition iPos, bool bEOL, double& x, double& y, double& x2, double& y2, double& height, bool& bDirection) { if (!getFirstContainer() || !m_pFirstRun) { // when we have no formatting information, can't find anything return NULL; } // find the run which has this offset inside it. PT_DocPosition dPos = getPosition(); const UT_uint32 iRelOffset = iPos - dPos; // By default, the Run just before the one we find is the one we // want the coords from. This is because insertion is done with // the properties of the Run before the point. // In some situations, we need to override that and use the coords // of the found Run - this flag tells us when to do what. bool bCoordOfPrevRun = true; // Some of the special cases below fix up pRun/bCoordOfPrevRun // so we can use the first exit point. We could just as well // fiddle bEOL in those cases, but using this variable makes // the intention more clear (IMO). bool bUseFirstExit = false; // Find first Run past (or at) the requested offset. By scanning // in this manner, we do a mimimum of computation to find the // approximate location. fp_Run* pRun = m_pFirstRun; while (pRun->getNextRun() && pRun->getBlockOffset() < iRelOffset) { pRun = pRun->getNextRun(); } // Now scan farther if necessary - the block may contain Runs // with zero length. This is only a problem when empty Runs // appear for no good reason (i.e., an empty Run on an empty // line should be OK). // // The original test for block offset + len < iRelOffset was no // good as that condition is always false by the time we get here. // The test would need to be for length == 0 // however, testing for 0 length makes us skip over fmt marks, // which we do not want (I wonder if this is really needed at all) while (pRun->getNextRun() && pRun->getLength() == 0 && pRun->getType() != FPRUN_FMTMARK) { pRun = pRun->getNextRun(); } // We may have scanned past the last Run in the block. Back up. if (!pRun) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); pRun = static_cast(getLastContainer())->getLastRun(); bCoordOfPrevRun = false; } // Step one back if previous Run holds the offset (the // above loops scan past what we're looking for since it's // faster). fp_Run* pPrevRun = pRun->getPrevRun(); if (pPrevRun && pPrevRun->getBlockOffset() + pPrevRun->getLength() > iRelOffset) { pRun = pPrevRun; bCoordOfPrevRun = false; } // Since the requested offset may be a page break (or similar // Runs) which cannot contain the point, now work backwards // while looking for a Run which can contain the point. if(pRun && !pRun->canContainPoint()) { fp_Run * pOldRun = pRun; while (pRun && !pRun->canContainPoint()) { pRun = pRun->getPrevRun(); bCoordOfPrevRun = false; } if(!pRun) { //look the other way pRun = pOldRun; while (pRun && !pRun->canContainPoint()) { pRun = pRun->getNextRun(); bCoordOfPrevRun = false; } } } // Assert if there have been no Runs which can hold the point // between the beginning of the block and the requested // offset. UT_ASSERT(NULL != pRun); if (!pRun){ x = x2 = y = y2 = height = 0; return NULL; } // This covers a special case (I) when bEOL. Consider this // line (| is the right margin, E end of document): // // 1: abcdefgh| // 2: iE // // When EOL position for display line 1 is requested, it's // done with either the offset of h or i (fall through to code // below first exit point). EOL position for display line 2 is // requested with offset of E (matches third sub-expresion). // (This check is rather non-intuitive - step through the code // for the different permutations to see why it's correct). if (bEOL && pRun->getBlockOffset() < iRelOffset && pRun->getBlockOffset() + pRun->getLength() >= iRelOffset) { bCoordOfPrevRun = false; bUseFirstExit = true; } // If not bEOL, we're done: either we have actually found the // Run containing the offset, or we have found the first // suitable Run before the requested offset. // // This is the exit point most calls will use (being the first // exit point, we should be OK performance wise). if (bUseFirstExit || !bEOL) { if (bCoordOfPrevRun && pRun->letPointPass()) { // This looks a little weird. What it does is first try to // go one Run back only, if allowed. If that fails, use // the original Run. pPrevRun = pRun->getPrevRun(); if (!pPrevRun || !pPrevRun->letPointPass() || !pPrevRun->canContainPoint()) { pPrevRun = pRun; } else { // If the code gets one Run back, keep going back // until finding a Run that is valid for point // coordinate calculations. while (pPrevRun && !pPrevRun->letPointPass() || !pPrevRun->canContainPoint()) { pPrevRun = pPrevRun->getPrevRun(); } // If this fails, go with the original Run. if (!pPrevRun) { pPrevRun = pRun; } } // One final check: only allow the point to move to a // different line if bEOL. if (!bEOL && pRun->getLine() != pPrevRun->getLine()) { pPrevRun = pRun; } if(getFirstRun()->getLine()) { pPrevRun->findPointCoords(iRelOffset, x, y, x2, y2, height, bDirection); } else { height = 0; } } else { if(getFirstRun()->getLine()) { pRun->findPointCoords(iRelOffset, x, y, x2, y2, height, bDirection); } else { height = 0; } } return pRun; } // Runs with layout information (page/column break) are not // visible (or rather, cannot contain the point). They look // like this (P is a page break, E is end of document): // // 1: abcdefghP // // 2: E // // When we have to find EOL position for display line 2, the // arguments (the offset) is the same as in case (I) above // (i.e., the argument is the offset of P). Thus this special // check. pPrevRun = pRun->getPrevRun(); if (!pPrevRun || !pPrevRun->letPointPass()) { if(getFirstRun()->getLine()) { pRun->findPointCoords(iRelOffset, x, y, x2, y2, height, bDirection); } else { height = 0; } return pRun; } // Now search for the Run at the end of the line. For a // soft-broken block (soft-break due to margin constraints), // this may be on the previous display line. It may also be // pRun (if the offset was past the last Run of this display // line). Consider this line (| is the right margin, N the // line break or paragraph end): // // 1: abcdefgh| // 2: ijklN // // For normal cursor movement (bEOL=false), IP (*) will move // from *h to *i (or vice versa), skipping the h* position. // When bEOL=true (user presses End key, or selects EOL with // mouse) IP on display line 1 will be at h*, even though the // requested offset is actually that of i. // while (pPrevRun && !pPrevRun->canContainPoint()) { pPrevRun = pPrevRun->getPrevRun(); } // If we went past the head of the list, it means that the // originally found Run is the only one on this display line. if (!pPrevRun) { if(getFirstRun()->getLine()) { pRun->findPointCoords(iRelOffset, x, y, x2, y2, height, bDirection); } else { height = 0; } return pRun; } // If the Runs are on the same line, assume pRun to be farther // right than pPrevRun. if (pPrevRun->getLine() == pRun->getLine()) { if(getFirstContainer()) { pRun->findPointCoords(iRelOffset, x, y, x2, y2, height, bDirection); } else { height = 0; } return pRun; } // Only case left is that of a soft-broken line. // Always return position _and_ Run of the previous line. Old // implementation returned pRun, but this will cause the // cursor to wander if End is pressed multiple times. if(getFirstRun()->getLine()) { pPrevRun->findPointCoords(iRelOffset, x, y, x2, y2, height, bDirection); } else { height = 0; } return pPrevRun; } fp_Line* fl_BlockLayout::findPrevLineInDocument(fp_Line* pLine) { if (pLine->getPrev()) { return static_cast(pLine->getPrev()); } else { if (getPrev()) { return static_cast(getPrev()->getLastContainer()); } else { fl_SectionLayout* pSL = static_cast(m_pSectionLayout->getPrev()); if (!pSL) { // at EOD, so just bail return NULL; } // is this cast safe? Could not some other layout class be returned? // if this assert fails, then this code needs to be fixed up. Tomas UT_ASSERT_HARMLESS( pSL->getLastLayout() && pSL->getLastLayout()->getContainerType() == FL_CONTAINER_BLOCK ); fl_BlockLayout* pBlock = static_cast(pSL->getLastLayout()); UT_ASSERT(pBlock); return static_cast(pBlock->getLastContainer()); } } UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return NULL; } fp_Line* fl_BlockLayout::findNextLineInDocument(fp_Line* pLine) { if (pLine->getNext()) { return static_cast(pLine->getNext()); } if (getNext()) { // grab the first line from the next block return static_cast(getNext()->getFirstContainer()); } else { // there is no next line in this section, try the next fl_SectionLayout* pSL = static_cast(m_pSectionLayout->getNext()); if (!pSL) { // at EOD, so just bail return NULL; } // is this cast safe? Could not some other layout class be returned? // if this assert fails, then this code needs to be fixed up. Tomas UT_ASSERT_HARMLESS( pSL->getLastLayout() && pSL->getLastLayout()->getContainerType() == FL_CONTAINER_BLOCK ); fl_BlockLayout* pBlock = static_cast(pSL->getFirstLayout()); UT_ASSERT(pBlock); return static_cast(pBlock->getFirstContainer()); } UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return NULL; } /*****************************************************************/ /*****************************************************************/ fl_PartOfBlock::fl_PartOfBlock(void) { m_iOffset = 0; m_iLength = 0; m_bIsIgnored = false; m_bIsInvisible = false; } fl_PartOfBlock::fl_PartOfBlock(UT_sint32 iOffset, UT_sint32 iLength, bool bIsIgnored /* = false */) { m_iOffset = iOffset; m_iLength = iLength; m_bIsIgnored = bIsIgnored; m_bIsInvisible = false; } void fl_PartOfBlock::setGrammarMessage(UT_UTF8String & sMsg) { m_sGrammarMessage = sMsg; } void fl_PartOfBlock::getGrammarMessage(UT_UTF8String & sMsg) { sMsg = m_sGrammarMessage; } /*! Does POB touch region \param iOffset Offset of region \param iLength Length of region \return True if the region touches the POB */ bool fl_PartOfBlock::doesTouch(UT_sint32 iOffset, UT_sint32 iLength) const { UT_sint32 start1, end1, start2, end2; xxx_UT_DEBUGMSG(("fl_PartOfBlock::doesTouch(%d, %d)\n", iOffset, iLength)); start1 = m_iOffset; end1 = m_iOffset + m_iLength; start2 = iOffset; end2 = iOffset + iLength; if (end1 == start2) { return true; } if (end2 == start1) { return true; } /* they overlap */ if ((start1 <= start2) && (start2 <= end1)) { return true; } if ((start2 <= start1) && (start1 <= end2)) { return true; } return false; } /*! Recalculate boundries for pending word \param iOffset Offset of change \param chg Size of change, negative is removal, zero is for recalculating the pending word. On entry, the block is already changed and any pending word is junk. On exit, there's either a single unchecked pending word, or nothing. */ void fl_BlockLayout::_recalcPendingWord(UT_uint32 iOffset, UT_sint32 chg) { xxx_UT_DEBUGMSG(("fl_BlockLayout::_recalcPendingWord(%d, %d)\n", iOffset, chg)); UT_GrowBuf pgb(1024); bool bRes = getBlockBuf(&pgb); UT_ASSERT(bRes); const UT_UCSChar* pBlockText = reinterpret_cast(pgb.getPointer(0)); if (pBlockText == NULL) { return; } UT_uint32 iFirst = iOffset; if (iFirst > pgb.getLength() - 1) iFirst = pgb.getLength() - 1; UT_uint32 iAbs = static_cast((chg >= 0) ? chg : -chg); UT_sint32 iLen = ((chg > 0) ? iAbs : 0); // We expand this region outward until we get a word delimiter on // each side. // First, look towards the start of the buffer while ((iFirst > 1) && !UT_isWordDelimiter(pBlockText[iFirst-1], pBlockText[iFirst] ,pBlockText[iFirst-2])) { iFirst--; } if(iFirst == 1 && !UT_isWordDelimiter(pBlockText[0], pBlockText[1], UCS_UNKPUNK)) { iFirst--; } UT_ASSERT(iOffset>=iFirst); iLen += (iOffset-iFirst); // Then look towards the end of the buffer UT_uint32 iBlockSize = pgb.getLength(); while ((iFirst + iLen < iBlockSize)) { UT_UCSChar followChar, prevChar; followChar = ((iFirst + iLen + 1) < iBlockSize) ? pBlockText[iFirst + iLen + 1] : UCS_UNKPUNK; prevChar = (iFirst == 0) ? UCS_UNKPUNK : pBlockText[iFirst + iLen - 1]; if (UT_isWordDelimiter(pBlockText[iFirst + iLen], followChar, prevChar)) break; iLen++; } // Now we figure out what to do with this expanded span if (chg > 0) { // Insertion - look for any completed words by finding the // first word delimiter from the end. UT_uint32 iLast = iOffset + chg; UT_UCSChar followChar = UCS_UNKPUNK, currentChar, prevChar = iLast > 2 ? pBlockText[iLast - 2] : UCS_UNKPUNK; while (iLast > iFirst) { currentChar = pBlockText[--iLast]; prevChar = iLast > 0 ? pBlockText[iLast - 1] : UCS_UNKPUNK; if (UT_isWordDelimiter(currentChar, followChar,prevChar)) break; followChar = currentChar; } if (iLast > (iFirst + 1)) { // Delimiter was found in the block - that means // there is one or more words between iFirst // and iLast we want to check. _checkMultiWord(iFirst, iLast, false); } // We still have the word at the end pending though. iLen -= (iLast - iFirst); iFirst = iLast; } else { // Deletion or update - everything's already set up, so just // fall through UT_ASSERT(chg <= 0); } // Skip any word delimiters; handling the case where a word // is split by space - without this check, the space would // become part of the pending word. UT_uint32 eor = pgb.getLength(); while (iLen > 0 && iFirst < eor) { UT_UCSChar currentChar = pBlockText[iFirst]; UT_UCSChar followChar = (((iFirst + 1) < eor) ? pBlockText[iFirst + + 1] : UCS_UNKPUNK); UT_UCSChar prevChar = iFirst > 0 ? pBlockText[iFirst - 1] : UCS_UNKPUNK; if (!UT_isWordDelimiter(currentChar, followChar, prevChar)) break; iFirst++; iLen--; } // Is there a pending word left? If so, record the details. if (iLen) { fl_PartOfBlock* pPending = NULL; bool bNew = false; if (m_pLayout->isPendingWordForSpell()) { pPending = m_pLayout->getPendingWordForSpell(); UT_ASSERT(pPending); } if (!pPending) { bNew = true; pPending = new fl_PartOfBlock(); UT_ASSERT(pPending); } if (pPending) { pPending->setOffset(iFirst); pPending->setLength(iLen); m_pLayout->setPendingWordForSpell(this, pPending); } } else { // No pending word any more m_pLayout->setPendingWordForSpell(NULL, NULL); } } /*****************************************************************/ /*****************************************************************/ /*! Check spelling of entire block Destructively recheck the entire block. Called from timer context, so we need to toggle IP. TODO - the IP toggling does not work very well, particularly just after a document was loaded. Long paragraphs do a slow blink of the IP and short paragraphs fast one virtually freezing the IP. The overall effect is rather erratic. I do not see, though, a good way of fixing this, particularly concering short blocks. */ bool fl_BlockLayout::checkSpelling(void) { xxx_UT_DEBUGMSG(("fl_BlockLayout::checkSpelling: this 0x%08x isOnScreen(): %d\n", this,static_cast(isOnScreen()))); // Don't spell check non-formatted blocks! if(m_pFirstRun == NULL) return false; if(m_pFirstRun->getLine() == NULL) return false; // we only want to do the cursor magic if the cursor is in this block bool bIsCursorInBlock = false; FV_View* pView = getView(); fp_Run* pLastRun = m_pFirstRun; while(pLastRun && pLastRun->getNextRun()) pLastRun = pLastRun->getNextRun(); if(pView && pLastRun) { UT_uint32 iBlPosStart = static_cast(getPosition()); UT_uint32 iBlPosEnd = iBlPosStart + pLastRun->getBlockOffset() + pLastRun->getLength(); UT_uint32 iPos = static_cast(pView->getPoint()); bIsCursorInBlock = ((iPos >= iBlPosStart) && (iPos <= iBlPosEnd)); } // Remove any existing squiggles from the screen... bool bUpdateScreen = m_pSpellSquiggles->deleteAll(); // Now start checking bUpdateScreen |= _checkMultiWord(0, -1, bIsCursorInBlock); if( bUpdateScreen && pView) { markAllRunsDirty(); setNeedsRedraw(); } return true; } /*! Spell-check region of block with potentially multiple words \param pBlockText Text of block \param iStart Start of region to check \param eor End of region to check (or -1 to check to the end) \param bToggleIP Toggle IP if true */ bool fl_BlockLayout::_checkMultiWord(UT_sint32 iStart, UT_sint32 eor, bool bToggleIP) { xxx_UT_DEBUGMSG(("fl_BlockLayout::_checkMultiWord\n")); bool bScreenUpdated = false; fl_BlockSpellIterator wordIterator(this, iStart); const UT_UCSChar* pWord; UT_sint32 iLength, iBlockPos; while (wordIterator.nextWordForSpellChecking(pWord, iLength, iBlockPos)) { // When past the provided end position, break out if (eor > 0 && iBlockPos > eor) break; fl_PartOfBlock* pPOB = new fl_PartOfBlock(iBlockPos, iLength); UT_ASSERT(pPOB); #if 0 // TODO: turn this code on someday FV_View* pView = getView(); XAP_App * pApp = XAP_App::getApp(); XAP_Prefs *pPrefs = pApp->getPrefs(); UT_ASSERT(pPrefs); bool b; // possibly auto-replace the squiggled word with a suggestion if (pPrefs->getPrefsValueBool(static_cast(AP_PREF_KEY_SpellAutoReplace), &b)) { if (b && !bIsIgnored) { // todo: better cursor movement pView->cmdContextSuggest(1, this, pPOB); pView->moveInsPtTo(FV_DOCPOS_EOW_MOVE); DELETEP(pPOB); } } #endif if (pPOB) { bool bwrong = false; bwrong = _doCheckWord(pPOB, pWord, true, bToggleIP); #if 0 if(bwrong) { UT_DEBUGMSG(("Found misspelt word in block %x \n",this)); } #endif bScreenUpdated |= bwrong; } } return bScreenUpdated; } /*! Validate a word and spell-check it \param pPOB Block region to squiggle if appropriate \param pBlockText Pointer to block's text \param bAddSquiggle True if pPOB should be added to squiggle list \return True if display was updated, otherwise false If the word bounded by pPOB is not squiggled, the pPOB is deleted. */ bool fl_BlockLayout::_doCheckWord(fl_PartOfBlock* pPOB, const UT_UCSChar* pWord, bool bAddSquiggle /* = true */, bool bClearScreen /* = true */) { UT_sint32 iLength = pPOB->getLength(); UT_sint32 iBlockPos = pPOB->getOffset(); do { // Spell check the word, return if correct if (_spellCheckWord(pWord, iLength, iBlockPos)) break; // Find out if the word is in the document's list of ignored // words pPOB->setIsIgnored(_getSpellChecker(iBlockPos)->isIgnored(pWord, iLength)); // Word not correct or recognized, so squiggle it if (bAddSquiggle) { m_pSpellSquiggles->add(pPOB); } if(bClearScreen) { m_pSpellSquiggles->clear(pPOB); } // Display was updated return true; } while (0); // Delete the POB which is not longer needed delete pPOB; return false; } /*! Spell-check word in the block region \param pPOB Block region bounding the word \return True if display was updated, false otherwise Consume word in pPOB -- either squiggle or delete it FIXME:jsk Make callers use fl_BlockSpellIterator so we don't have to check validity? Should just be provided the starting offset... */ bool fl_BlockLayout::checkWord(fl_PartOfBlock* pPOB) { xxx_UT_DEBUGMSG(("fl_BlockLayout::checkWord\n")); UT_ASSERT(pPOB); if (!pPOB) return false; // Just use the initial offset from the provided pPOB - the word's // exact location/length is not known (since what we're provided // is just the editing limits). fl_BlockSpellIterator wordIterator(this, pPOB->getOffset()); const UT_UCSChar* pWord; UT_sint32 iLength, iBlockPos; // The word iterator may be unable to find a word within the // editing limits provided by the pPOB - so check that before // continuing. if (wordIterator.nextWordForSpellChecking(pWord, iLength, iBlockPos) && (iBlockPos+iLength <= pPOB->getOffset()+pPOB->getLength())) { delete pPOB; fl_PartOfBlock* pNewPOB = new fl_PartOfBlock(iBlockPos, iLength); UT_ASSERT(pNewPOB); return _doCheckWord(pNewPOB, pWord ); } // Delete the POB which is not longer needed delete pPOB; return false; } /*****************************************************************/ /*****************************************************************/ bool fl_BlockLayout::doclistener_populateSpan(const PX_ChangeRecord_Span * pcrs, PT_BlockOffset blockOffset, UT_uint32 len) { _assertRunListIntegrity(); PT_BufIndex bi = pcrs->getBufIndex(); if(getPrev()!= NULL && getPrev()->getLastContainer()==NULL) { xxx_UT_DEBUGMSG(("In fl_BlockLayout::doclistener_populateSpan no LastLine \n")); xxx_UT_DEBUGMSG(("getPrev = %d this = %d \n",getPrev(),this)); // UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } const UT_UCSChar* pChars = m_pDoc->getPointer(bi); xxx_UT_DEBUGMSG(("fl_BlockLayout:: populateSpan BlockOffset %d NO chars %d \n",blockOffset,len)); /* walk through the characters provided and find any control characters. Then, each control character gets handled specially. Normal characters get grouped into runs as usual. */ UT_uint32 iNormalBase = 0; bool bNormal = false; UT_uint32 i; for (i=0; i(pChars[i]))); switch (pChars[i]) { // see similar control characters in fl_DocLayout.cpp case UCS_FF: // form feed, forced page break case UCS_VTAB: // vertical tab, forced column break case UCS_LF: // newline case UCS_FIELDSTART: // zero length line to mark field start case UCS_FIELDEND: // zero length line to mark field end case UCS_BOOKMARKSTART: case UCS_BOOKMARKEND: case UCS_TAB: // tab case UCS_LRO: // explicit direction overrides case UCS_RLO: case UCS_PDF: case UCS_LRE: case UCS_RLE: case UCS_LRM: case UCS_RLM: if (bNormal) { _doInsertTextSpan(iNormalBase + blockOffset, i - iNormalBase); bNormal = false; } /* Now, depending upon the kind of control char we found, we add a control run which corresponds to it. */ switch (pChars[i]) { case UCS_FF: _doInsertForcedPageBreakRun(i + blockOffset); break; case UCS_VTAB: _doInsertForcedColumnBreakRun(i + blockOffset); break; case UCS_LF: _doInsertForcedLineBreakRun(i + blockOffset); break; case UCS_FIELDSTART: _doInsertFieldStartRun(i + blockOffset); break; case UCS_FIELDEND: _doInsertFieldEndRun(i + blockOffset); break; case UCS_BOOKMARKSTART: case UCS_BOOKMARKEND: _doInsertBookmarkRun(i + blockOffset); break; case UCS_TAB: _doInsertTabRun(i + blockOffset); break; case UCS_LRO: case UCS_RLO: case UCS_LRE: case UCS_RLE: case UCS_PDF: // these should have been removed by // pd_Document::append/insert functions UT_ASSERT( UT_SHOULD_NOT_HAPPEN ); break; case UCS_LRM: case UCS_RLM: _doInsertDirectionMarkerRun(i + blockOffset,pChars[i]); break; default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); break; } break; default: if (!bNormal) { bNormal = true; iNormalBase = i; } break; } } UT_ASSERT(i == len); if (bNormal && (iNormalBase < i)) { _doInsertTextSpan(iNormalBase + blockOffset, i - iNormalBase); } _assertRunListIntegrity(); // This is needed because fl_BlockLayout::format() can be triggered by a timer // half-way through populating a block. If that happens the format clears the flag // and any runs that get inserted in subsequent populate calls are not correctly // positioned. It might be desirable to have a mechanism to ignore format() calls // while populating (for example storing sdh in a static member on populateStrux) // but calling setNeedsReformat() costs us little and will do OK for now. // Tomas, Apr 23, 2004 setNeedsReformat(blockOffset); updateEnclosingBlockIfNeeded(); if(isHidden() == FP_HIDDEN_FOLDED) { collapse(); } return true; } bool fl_BlockLayout::_doInsertTextSpan(PT_BlockOffset blockOffset, UT_uint32 len) { xxx_UT_DEBUGMSG(("_doInsertTextSpan: Initial offset %d, len %d\n", blockOffset, len)); UT_return_val_if_fail( m_pLayout, false ); PD_StruxIterator text(getStruxDocHandle(), blockOffset + fl_BLOCK_STRUX_OFFSET, blockOffset + fl_BLOCK_STRUX_OFFSET + len - 1); GR_Itemization I; I.setDirOverride(m_iDirOverride); I.setEmbedingLevel(m_iDomDirection); bool bShowControls = false; FV_View* pView = getView(); if(pView && pView->getShowPara()) bShowControls = true; I.setShowControlChars(bShowControls); const PP_AttrProp * pSpanAP = NULL; const PP_AttrProp * pBlockAP = NULL; getSpanAP(blockOffset, false, pSpanAP); getAP(pBlockAP); const char * szLang = static_cast(PP_evalProperty("lang",pSpanAP,pBlockAP,NULL,m_pDoc,true)); I.setLang(szLang); m_pLayout->getGraphics()->itemize(text, I); for(UT_uint32 i = 0; i < I.getItemCount() - 1; ++i) { UT_uint32 iRunOffset = I.getNthOffset(i); UT_uint32 iRunLength = I.getNthLength(i); // because of bug 8542 we do not allow runs longer than 32000 chars, so if it is // longer, just split it (we do not care where we split it, this is a contingency // measure only) while(iRunLength) { UT_uint32 iRunSegment = UT_MIN(iRunLength, 32000); fp_TextRun* pNewRun = new fp_TextRun(this, blockOffset + iRunOffset, iRunSegment); iRunOffset += iRunSegment; iRunLength -= iRunSegment; UT_return_val_if_fail(pNewRun && pNewRun->getType() == FPRUN_TEXT, false); pNewRun->setDirOverride(m_iDirOverride); GR_Item * pItem = I.getNthItem(i)->makeCopy(); UT_ASSERT( pItem ); pNewRun->setItem(pItem); if(!_doInsertRun(pNewRun)) return false; } } return true; } bool fl_BlockLayout::_doInsertForcedLineBreakRun(PT_BlockOffset blockOffset) { fp_Run* pNewRun = NULL; if(isContainedByTOC()) { pNewRun = new fp_DummyRun(this,blockOffset); } else { pNewRun = new fp_ForcedLineBreakRun(this, blockOffset, 1); } UT_ASSERT(pNewRun); // TODO check for outofmem bool bResult = _doInsertRun(pNewRun); if (bResult && !isContainedByTOC()) _breakLineAfterRun(pNewRun); return bResult; } bool fl_BlockLayout::_doInsertDirectionMarkerRun(PT_BlockOffset blockOffset, UT_UCS4Char iM) { xxx_UT_DEBUGMSG(("fl_BlockLayout::_doInsertDirectionMarkerRun: offset %d, marker 0x%04x\n", blockOffset, iM)); fp_Run * pNewRun = new fp_DirectionMarkerRun(this, blockOffset, iM); UT_ASSERT( pNewRun ); bool bResult = _doInsertRun(pNewRun); #if 0 if (bResult) _breakLineAfterRun(pNewRun); #endif return bResult; } #if 0 bool fl_BlockLayout::_deleteBookmarkRun(PT_BlockOffset blockOffset) { UT_DEBUGMSG(("fl_BlockLayout::_deleteBookmarkRun: blockOffset %d\n",blockOffset)); _assertRunListIntegrity(); fp_BookmarkRun *pB1; fp_Run* pRun = m_pFirstRun; /* we have to deal with FmtMarks, which are special case since they have width 0 and so can share block offset with our book mark */ while (pRun->getNextRun() && (pRun->getBlockOffset() != blockOffset || pRun->getType() == FPRUN_FMTMARK)) { pRun = pRun->getNextRun(); } UT_ASSERT(pRun && pRun->getType() == FPRUN_BOOKMARK); if(!pRun || pRun->getType() != FPRUN_BOOKMARK) return false; pB1 = static_cast(pRun); // Remove Run from line fp_Line* pLine = pB1->getLine(); UT_ASSERT(pLine); if(pLine) { pLine->removeRun(pB1, true); } // Unlink Run and delete it if (m_pFirstRun == pB1) { m_pFirstRun = pB1->getNextRun(); } pRun = pB1->getNextRun(); pB1->unlinkFromRunList(); delete pB1; fp_Run * pLastRun = static_cast(getLastContainer())->getLastRun(); while(pRun ) { pRun->setBlockOffset(pRun->getBlockOffset() - 1); if(pRun == pLastRun) break; pRun = pRun->getNextRun(); } xxx_UT_DEBUGMSG(("fl_BlockLayout::_deleteBookmarkRun: assert integrity (1)\n")); _assertRunListIntegrity(); return true; } #endif bool fl_BlockLayout::_doInsertBookmarkRun(PT_BlockOffset blockOffset) { fp_Run * pNewRun; if(!isContainedByTOC()) { pNewRun = new fp_BookmarkRun(this, blockOffset, 1); } else { pNewRun = new fp_DummyRun(this,blockOffset); } UT_ASSERT(pNewRun); bool bResult = _doInsertRun(pNewRun); #if 0 if (bResult) { _breakLineAfterRun(pNewRun); } #endif return bResult; } bool fl_BlockLayout::_doInsertHyperlinkRun(PT_BlockOffset blockOffset) { bool bResult = false; if(!isContainedByTOC()) { fp_HyperlinkRun * pNewRun = new fp_HyperlinkRun(this, blockOffset, 1); UT_ASSERT(pNewRun); bResult = _doInsertRun(pNewRun); if (bResult) { // if this is the start of the hyperlink, we need to mark all the runs // till the end of it // if this is because of an insert operation, the end run is already // in place, because we insert them in that order; if it is because of // append, ther is no end run, but then this is the last run; the other // runs will get marked as they get appended (inside fp_Run::insertRun...) // any hyperlink run will not get its m_pHyperlink set, so that // runs that follow it would not be marked if(pNewRun->isStartOfHyperlink()) { fp_Run * pRun = pNewRun->getNextRun(); UT_ASSERT(pRun); // when loading a document the opening hyperlink run is initially followed // by ENDOFPARAGRAPH run; we do not want to set this one while(pRun && pRun->getType() != FPRUN_HYPERLINK && pRun->getType() != FPRUN_ENDOFPARAGRAPH) { pRun->setHyperlink(pNewRun); pRun = pRun->getNextRun(); } } else { // // clear out any hyperlinks // fp_Run * pRun = pNewRun->getNextRun(); while(pRun && (pRun->getType() != FPRUN_HYPERLINK && pRun->getType() != FPRUN_ENDOFPARAGRAPH)) { pRun->setHyperlink(NULL); pRun = pRun->getNextRun(); } } //_breakLineAfterRun(pNewRun); } } else { fp_Run * pNewRun = new fp_DummyRun(this,blockOffset); UT_ASSERT(pNewRun); bResult = _doInsertRun(pNewRun); } return bResult; } bool fl_BlockLayout::_doInsertFieldStartRun(PT_BlockOffset blockOffset) { fp_Run* pNewRun = new fp_FieldStartRun(this,blockOffset, 1); UT_ASSERT(pNewRun); // TODO check for outofmem bool bResult = _doInsertRun(pNewRun); if (bResult) _breakLineAfterRun(pNewRun); return bResult; } bool fl_BlockLayout::_doInsertFieldEndRun(PT_BlockOffset blockOffset) { fp_Run* pNewRun = new fp_FieldEndRun(this, blockOffset, 1); UT_ASSERT(pNewRun); // TODO check for outofmem bool bResult = _doInsertRun(pNewRun); if (bResult) _breakLineAfterRun(pNewRun); return bResult; } bool fl_BlockLayout::_doInsertForcedPageBreakRun(PT_BlockOffset blockOffset) { fp_Run* pNewRun = NULL; if(isContainedByTOC()) { pNewRun = new fp_DummyRun(this,blockOffset); } else { pNewRun = new fp_ForcedPageBreakRun(this,blockOffset, 1); } UT_ASSERT(pNewRun); // TODO check for outofmem if(getPrev()!= NULL && getPrev()->getLastContainer()==NULL) { UT_DEBUGMSG(("In fl_BlockLayout::_doInsertForcedPageBreakRun no LastLine \n")); UT_DEBUGMSG(("getPrev = %d this = %d \n",getPrev(),this)); //UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } bool bResult = _doInsertRun(pNewRun); if (bResult) _breakLineAfterRun(pNewRun); return bResult; } bool fl_BlockLayout::_doInsertForcedColumnBreakRun(PT_BlockOffset blockOffset) { fp_Run* pNewRun = NULL; if(isContainedByTOC()) { pNewRun = new fp_DummyRun(this,blockOffset); } else { pNewRun = new fp_ForcedColumnBreakRun(this,blockOffset, 1); } UT_ASSERT(pNewRun); // TODO check for outofmem bool bResult = _doInsertRun(pNewRun); if (bResult) _breakLineAfterRun(pNewRun); return bResult; } bool fl_BlockLayout::_doInsertTabRun(PT_BlockOffset blockOffset) { fp_Run * pNewRun = NULL; if(!isContainedByTOC() || !m_bPrevListLabel) { pNewRun = new fp_TabRun(this,blockOffset, 1); } else { xxx_UT_DEBUGMSG(("Insert dummy in place of TAB at %d \n",blockOffset)); pNewRun = new fp_DummyRun(this,blockOffset); } UT_ASSERT(pNewRun); // TODO check for outofmem return _doInsertRun(pNewRun); } bool fl_BlockLayout::_doInsertMathRun(PT_BlockOffset blockOffset,PT_AttrPropIndex indexAP, PL_ObjectHandle oh) { fp_Run * pNewRun = NULL; pNewRun = new fp_MathRun(this,blockOffset,indexAP,oh); UT_ASSERT(pNewRun); // TODO check for outofmem return _doInsertRun(pNewRun); } bool fl_BlockLayout::_doInsertTOCTabRun(PT_BlockOffset blockOffset) { fp_Run* pNewRun = new fp_TabRun(this,blockOffset, 1); UT_ASSERT(pNewRun); // TODO check for outofmem static_cast(pNewRun)->setTOCTab(); return _doInsertRun(pNewRun); } /*! * Special TAB that follows a TOCListLabel. It has zero length since it's * not in the document. */ bool fl_BlockLayout::_doInsertTOCListTabRun(PT_BlockOffset blockOffset) { fp_Run* pNewRun = new fp_TabRun(this,blockOffset, 0); UT_ASSERT(pNewRun); // TODO check for outofmem static_cast(pNewRun)->setTOCTabListLabel(); fp_Run * pRun = m_pFirstRun; pRun->insertIntoRunListBeforeThis(*pNewRun); m_pFirstRun = pNewRun; pNewRun->markWidthDirty(); if(pRun->getLine()) { pRun->getLine()->insertRunBefore(pNewRun, pRun); } return true; } bool fl_BlockLayout::_doInsertImageRun(PT_BlockOffset blockOffset, FG_Graphic* pFG) { if(isContainedByTOC()) { fp_Run * pDumRun = new fp_DummyRun(this,blockOffset); xxx_UT_DEBUGMSG(("Inserting a dummy run instead of iamge at %d \n",blockOffset)); return _doInsertRun(pDumRun); } fp_ImageRun* pNewRun = new fp_ImageRun(this, blockOffset, 1, pFG); UT_ASSERT(pNewRun); // TODO check for outofmem return _doInsertRun(pNewRun); } bool fl_BlockLayout::_doInsertFieldRun(PT_BlockOffset blockOffset, const PX_ChangeRecord_Object * pcro) { // Get the field type. const PP_AttrProp * pSpanAP = NULL; #if 0 // this is unnecessarily involved, just use the index from the pcro getSpanAttrProp(blockOffset, false, &pSpanAP); UT_ASSERT(pSpanAP); #else UT_return_val_if_fail(pcro, false); PT_AttrPropIndex iAP = pcro->getIndexAP(); m_pLayout->getDocument()->getAttrProp(iAP, &pSpanAP); #endif const XML_Char* pszType = NULL; pSpanAP->getAttribute("type", pszType); // Create the field run. fp_FieldRun* pNewRun; if (!pszType) { UT_ASSERT (pszType); pNewRun = new fp_FieldRun(this, blockOffset,1); } else if(UT_strcmp(pszType, "list_label") == 0) { if(!isContainedByTOC()) { pNewRun = new fp_FieldListLabelRun(this, blockOffset, 1); } else { fp_Run * pDumRun = new fp_DummyRun(this,blockOffset); xxx_UT_DEBUGMSG(("Inserting a dummy run instead of listlabel at %d \n",blockOffset)); _doInsertRun(pDumRun); recalculateFields(0); m_bPrevListLabel = true; // // Might have to put in code here to detect if there is already // a tab run ahead of the list label. If so we replace it // with a dummyrun // fp_Run * pNextRun = pDumRun->getNextRun(); return true; } } else if(UT_strcmp(pszType, "footnote_ref") == 0) { if(isContainedByTOC()) { fp_Run * pDumRun = new fp_DummyRun(this,blockOffset); xxx_UT_DEBUGMSG(("Inserting a dummy run instead of footnote_ref at %d \n",blockOffset)); return _doInsertRun(pDumRun); } xxx_UT_DEBUGMSG(("Footnoet ref run created at %d \n",blockOffset)); pNewRun = new fp_FieldFootnoteRefRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "footnote_anchor") == 0) { if(isContainedByTOC()) { fp_Run * pDumRun = new fp_DummyRun(this,blockOffset); xxx_UT_DEBUGMSG(("Inserting a dummy run instead of endnote_ref at %d \n",blockOffset)); return _doInsertRun(pDumRun); } pNewRun = new fp_FieldFootnoteAnchorRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "endnote_ref") == 0) { if(isContainedByTOC()) { fp_Run * pDumRun = new fp_DummyRun(this,blockOffset); xxx_UT_DEBUGMSG(("Inserting a dummy run instead of endnote_ref at %d \n",blockOffset)); return _doInsertRun(pDumRun); } xxx_UT_DEBUGMSG(("Endnote ref run created at %d \n",blockOffset)); pNewRun = new fp_FieldEndnoteRefRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "endnote_anchor") == 0) { if(isContainedByTOC()) { fp_Run * pDumRun = new fp_DummyRun(this,blockOffset); xxx_UT_DEBUGMSG(("Inserting a dummy run instead of endnote_ref at %d \n",blockOffset)); return _doInsertRun(pDumRun); } pNewRun = new fp_FieldEndnoteAnchorRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "time") == 0) { pNewRun = new fp_FieldTimeRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "page_number") == 0) { pNewRun = new fp_FieldPageNumberRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "page_ref") == 0) { pNewRun = new fp_FieldPageReferenceRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "page_count") == 0) { pNewRun = new fp_FieldPageCountRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date") == 0) { pNewRun = new fp_FieldDateRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_mmddyy") == 0) { pNewRun = new fp_FieldMMDDYYRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_ddmmyy") == 0) { pNewRun = new fp_FieldDDMMYYRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_mdy") == 0) { pNewRun = new fp_FieldMonthDayYearRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_mthdy") == 0) { pNewRun = new fp_FieldMthDayYearRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_dfl") == 0) { pNewRun = new fp_FieldDefaultDateRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_ntdfl") == 0) { pNewRun = new fp_FieldDefaultDateNoTimeRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_wkday") == 0) { pNewRun = new fp_FieldWkdayRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "date_doy") == 0) { pNewRun = new fp_FieldDOYRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "time_miltime") == 0) { pNewRun = new fp_FieldMilTimeRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "time_ampm") == 0) { pNewRun = new fp_FieldAMPMRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "time_zone") == 0) { pNewRun = new fp_FieldTimeZoneRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "time_epoch") == 0) { pNewRun = new fp_FieldTimeEpochRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "datetime_custom") == 0) { pNewRun = new fp_FieldDateTimeCustomRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "word_count") == 0) { pNewRun = new fp_FieldWordCountRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "char_count") == 0) { pNewRun = new fp_FieldCharCountRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "line_count") == 0) { pNewRun = new fp_FieldLineCountRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "para_count") == 0) { pNewRun = new fp_FieldParaCountRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "nbsp_count") == 0) { pNewRun = new fp_FieldNonBlankCharCountRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "file_name") == 0) { pNewRun = new fp_FieldFileNameRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "app_ver") == 0) { pNewRun = new fp_FieldBuildVersionRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "app_id") == 0) { pNewRun = new fp_FieldBuildIdRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "app_options") == 0) { pNewRun = new fp_FieldBuildOptionsRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "app_target") == 0) { pNewRun = new fp_FieldBuildTargetRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "app_compiledate") == 0) { pNewRun = new fp_FieldBuildCompileDateRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "app_compiletime") == 0) { pNewRun = new fp_FieldBuildCompileTimeRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "mail_merge") == 0) { pNewRun = new fp_FieldMailMergeRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_title") == 0) { pNewRun = new fp_FieldMetaTitleRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_creator") == 0) { pNewRun = new fp_FieldMetaCreatorRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_subject") == 0) { pNewRun = new fp_FieldMetaSubjectRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_publisher") == 0) { pNewRun = new fp_FieldMetaPublisherRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_contributor") == 0) { pNewRun = new fp_FieldMetaContributorRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_date") == 0) { pNewRun = new fp_FieldMetaDateRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_type") == 0) { pNewRun = new fp_FieldMetaTypeRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_language") == 0) { pNewRun = new fp_FieldMetaLanguageRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_coverage") == 0) { pNewRun = new fp_FieldMetaCoverageRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_rights") == 0) { pNewRun = new fp_FieldMetaRightsRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_keywords") == 0) { pNewRun = new fp_FieldMetaKeywordsRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "meta_description") == 0) { pNewRun = new fp_FieldMetaDescriptionRun(this, blockOffset, 1); } else if(UT_strcmp(pszType, "sum_rows") == 0) { pNewRun = new fp_FieldTableSumRows(this, blockOffset, 1); } else if(UT_strcmp(pszType, "sum_cols") == 0) { pNewRun = new fp_FieldTableSumCols(this, blockOffset, 1); } else { UT_ASSERT_NOT_REACHED (); // // New Piece Table Field Leave it for that code.. // pNewRun = new fp_FieldRun(this, blockOffset, 1); } UT_ASSERT(pNewRun); // TODO check for outofmem // TODO -- is this really needed ??? // should not be, since we called lookupProperties in the // constructor - Tomas // pNewRun->lookupProperties(); pNewRun->calculateValue(); _doInsertRun(pNewRun); // recalculateFields(0); MES Do this in the format following return true; } bool fl_BlockLayout::_doInsertFieldTOCRun(PT_BlockOffset blockOffset) { fp_FieldRun* pNewRun; pNewRun = new fp_FieldTOCNumRun(this, blockOffset, 1); _doInsertRun(pNewRun); return true; } /*! * TOC List label run. It has zero length since it's not in the document. */ bool fl_BlockLayout::_doInsertTOCListLabelRun(PT_BlockOffset blockOffset) { fp_FieldRun* pNewRun; pNewRun = new fp_FieldTOCListLabelRun(this, blockOffset, 0); fp_Run * pRun = m_pFirstRun; pRun->insertIntoRunListBeforeThis(*pNewRun); m_pFirstRun = pNewRun; pNewRun->markWidthDirty(); if(pRun->getLine()) { pRun->getLine()->insertRunBefore(pNewRun, pRun); } return true; } bool fl_BlockLayout::_doInsertTOCHeadingRun(PT_BlockOffset blockOffset) { fp_FieldRun* pNewRun; pNewRun = new fp_FieldTOCHeadingRun(this, blockOffset, 1); fp_Run * pRun = m_pFirstRun; pRun->insertIntoRunListBeforeThis(*pNewRun); m_pFirstRun = pNewRun; pNewRun->markWidthDirty(); if(pRun->getLine()) { pRun->getLine()->insertRunBefore(pNewRun, pRun); } return true; } bool fl_BlockLayout::_doInsertRun(fp_Run* pNewRun) { PT_BlockOffset blockOffset = pNewRun->getBlockOffset(); UT_uint32 len = pNewRun->getLength(); xxx_UT_DEBUGMSG(("_doInsertRun: New run has offset %d Length %d \n",blockOffset,len)); _assertRunListIntegrity(); bool bInserted = false; fp_Run* pRun = m_pFirstRun; while (pRun) { UT_uint32 iRunBlockOffset = pRun->getBlockOffset(); UT_uint32 iRunLength = pRun->getLength(); xxx_UT_DEBUGMSG(("_doInsertRun: Target offset %d CurRun Offset %d Length %d Type %d \n",blockOffset,iRunBlockOffset,iRunLength,pRun->getType())); if ( (iRunBlockOffset + iRunLength) <= blockOffset ) { // nothing to do. the insert occurred AFTER this run } else if ((iRunBlockOffset > blockOffset) && bInserted) { // the insert is occuring BEFORE this run, so we just move the run offset pRun->setBlockOffset(iRunBlockOffset + len); } else if((iRunBlockOffset > blockOffset) && !bInserted) // // Run should be inserted before this run // { pRun->setBlockOffset(iRunBlockOffset + len); pRun->insertIntoRunListBeforeThis(*pNewRun); if(m_pFirstRun == pRun) { m_pFirstRun = pNewRun; } bInserted = true; if(pRun->getLine()) { pRun->getLine()->insertRunBefore(pNewRun, pRun); #if DEBUG pRun->getLine()->assertLineListIntegrity(); #endif } } else if (iRunBlockOffset == blockOffset) { UT_ASSERT(!bInserted); bInserted = true; // the insert is right before this run. pRun->setBlockOffset(iRunBlockOffset + len); pRun->insertIntoRunListBeforeThis(*pNewRun); if (m_pFirstRun == pRun) { m_pFirstRun = pNewRun; } if(pRun->getLine()) { pRun->getLine()->insertRunBefore(pNewRun, pRun); #if DEBUG pRun->getLine()->assertLineListIntegrity(); #endif } } // // Here if the run run starts before the target offset and finishes after it. // We need to split this run. // else { UT_ASSERT(!bInserted); UT_ASSERT((blockOffset >= pRun->getBlockOffset()) && (blockOffset < (pRun->getBlockOffset() + pRun->getLength()))); UT_ASSERT(pRun->getType() == FPRUN_TEXT); // only textual runs can be split anyway fp_TextRun* pTextRun = static_cast(pRun); pTextRun->split(blockOffset); UT_ASSERT(pRun->getNextRun()); UT_ASSERT(pRun->getNextRun()->getBlockOffset() == blockOffset); UT_ASSERT(pTextRun->getNextRun()); UT_ASSERT(pTextRun->getNextRun()->getType() == FPRUN_TEXT); // sterwill -- is the call to getNextRun() needed? pOtherHalfOfSplitRun // is not used. // fp_TextRun* pOtherHalfOfSplitRun = static_cast(pTextRun->getNextRun()); // pTextRun->recalcWidth(); bInserted = true; pRun = pRun->getNextRun(); iRunBlockOffset = pRun->getBlockOffset(); iRunLength = pRun->getLength(); UT_ASSERT(iRunBlockOffset == blockOffset); // the insert is right before this run. pRun->setBlockOffset(iRunBlockOffset + len); pRun->insertIntoRunListBeforeThis(*pNewRun); if(pRun->getLine()) { pRun->getLine()->insertRunBefore(pNewRun, pRun); #if DEBUG pRun->getLine()->assertLineListIntegrity(); #endif } // pOtherHalfOfSplitRun->recalcWidth(); } pRun = pRun->getNextRun(); } if (!bInserted) { pRun = m_pFirstRun; fp_Run * pLastRun = NULL; UT_uint32 offset = 0; while (pRun) { pLastRun = pRun; offset += pRun->getLength(); pRun = pRun->getNextRun(); } if (pLastRun) { if((pNewRun->getType() !=FPRUN_ENDOFPARAGRAPH) && (pLastRun->getType()== FPRUN_ENDOFPARAGRAPH)) { pLastRun->insertIntoRunListBeforeThis(*pNewRun); pLastRun->setBlockOffset(pNewRun->getBlockOffset()+pNewRun->getLength()); if(pLastRun->getLine()) { pLastRun->getLine()->insertRunBefore(pNewRun, pLastRun); #if DEBUG pLastRun->getLine()->assertLineListIntegrity(); #endif } } else { pLastRun->insertIntoRunListAfterThis(*pNewRun); if (getLastContainer()) { static_cast(getLastContainer())->addRun(pNewRun); #if DEBUG static_cast(getLastContainer())->assertLineListIntegrity(); #endif } } } else { m_pFirstRun = pNewRun; if (getLastContainer()) { static_cast(getLastContainer())->addRun(pNewRun); #if DEBUG static_cast(getLastContainer())->assertLineListIntegrity(); #endif } } } /* if we inserted a text run, and its direction is strong, then we might need to do some more work. Since a strong run can change the visual direction of adjucent weak characters, we need to ensure that any weak characters on either side are in runs of their own. */ UT_BidiCharType iDirection = pNewRun->getDirection(); if(FRIBIDI_IS_STRONG(iDirection) && pNewRun->getType() == FPRUN_TEXT) { static_cast(pNewRun)->breakNeighborsAtDirBoundaries(); } pNewRun->markWidthDirty(); _assertRunListIntegrity(); #if 0 // now that the run is in place and the context has been set, we // calculate character widths // actually, we are not in position to calculate widths at this // point, because the insertion of this run invalidated the draw // buffers of un unspecified number of runs on either side, and in // order for the width calculation to be correct, the widths of // the runs that precede it would need to be recalculated first, // otherwise we get wrong results with ligatures (when our run // starts with a ligature placeholder, its with gets set to 1/2 of // the width of the previous glyph; this assumes that the previous // glyph is already the ligature glyph which it is not) // There seems to be no reason why we would need to calculate the widths // here, so we will leave it for now, and when the widths are needed, // i.e., when we attempt to draw, we will have all the right // values in place if (pNewRun->getType() == FPRUN_TEXT) { fp_TextRun* pNewTextRun = static_cast(pNewRun); pNewTextRun->recalcWidth(); } #endif return true; } /*! * This method will append the text in the block to the UTF8 string supplied */ void fl_BlockLayout::appendUTF8String(UT_UTF8String & sText) { UT_GrowBuf buf; appendTextToBuf(buf); const UT_UCS4Char * pBuff = reinterpret_cast(buf.getPointer(0)); if((buf.getLength() > 0) && (pBuff != NULL)) { sText.appendUCS4(pBuff,buf.getLength()); } } /*! * This method extracts all the text from the current block and appends it * to the supplied growbuf. */ void fl_BlockLayout::appendTextToBuf(UT_GrowBuf & buf) { fp_Run * pRun = m_pFirstRun; while(pRun) { if(pRun->getType() == FPRUN_TEXT) { fp_TextRun * pTRun = static_cast(pRun); pTRun->appendTextToBuf(buf); } pRun = pRun->getNextRun(); } } bool fl_BlockLayout::doclistener_insertSpan(const PX_ChangeRecord_Span * pcrs) { UT_return_val_if_fail( m_pLayout, false ); _assertRunListIntegrity(); UT_ASSERT(pcrs->getType()==PX_ChangeRecord::PXT_InsertSpan); //UT_ASSERT(pcrs->getPosition() >= getPosition()); /* valid assert, but very expensive */ PT_BlockOffset blockOffset = pcrs->getBlockOffset(); UT_uint32 len = pcrs->getLength(); UT_ASSERT(len>0); PT_BufIndex bi = pcrs->getBufIndex(); const UT_UCSChar* pChars = m_pDoc->getPointer(bi); /* walk through the characters provided and find any control characters. Then, each control character gets handled specially. Normal characters get grouped into runs as usual. */ UT_uint32 iNormalBase = 0; bool bNormal = false; UT_uint32 i; UT_uint32 _sqlist[100], *sqlist = _sqlist; UT_uint32 sqcount = 0; // // Need this to find where to break section in the document. // fl_ContainerLayout * pPrevCL = getPrev(); fp_Page * pPrevP = NULL; if(pPrevCL) { fp_Container * pPrevCon = pPrevCL->getFirstContainer(); if(pPrevCon) { pPrevP = pPrevCon->getPage(); } } if (sizeof(_sqlist) / sizeof(_sqlist[0]) < len) { sqlist = new UT_uint32[len]; } xxx_UT_DEBUGMSG(("fl_BlockLayout::doclistener_insertSpan(), len=%d, pos %d \n", len, getPosition()+blockOffset)); for (i=0; i(pChars[i]))); switch (pChars[i]) { case UCS_FF: // form feed, forced page break case UCS_VTAB: // vertical tab, forced column break case UCS_LF: // newline case UCS_FIELDSTART: // zero length line to mark field start case UCS_FIELDEND: // zero length line to mark field end case UCS_BOOKMARKSTART: case UCS_BOOKMARKEND: case UCS_TAB: // tab case UCS_LRO: // explicit direction overrides case UCS_RLO: case UCS_LRE: case UCS_RLE: case UCS_PDF: case UCS_LRM: case UCS_RLM: if (bNormal) { _doInsertTextSpan(blockOffset + iNormalBase, i - iNormalBase); bNormal = false; } /* Now, depending upon the kind of control char we found, we add a control run which corresponds to it. */ switch (pChars[i]) { case UCS_FF: getDocSectionLayout()->setNeedsSectionBreak(true,pPrevP); _doInsertForcedPageBreakRun(i + blockOffset); break; case UCS_VTAB: getDocSectionLayout()->setNeedsSectionBreak(true,pPrevP); _doInsertForcedColumnBreakRun(i + blockOffset); break; case UCS_LF: getDocSectionLayout()->setNeedsSectionBreak(true,pPrevP); _doInsertForcedLineBreakRun(i + blockOffset); break; case UCS_FIELDSTART: _doInsertFieldStartRun(i + blockOffset); break; case UCS_FIELDEND: _doInsertFieldEndRun(i + blockOffset); break; case UCS_BOOKMARKSTART: case UCS_BOOKMARKEND: _doInsertBookmarkRun(i + blockOffset); break; case UCS_TAB: _doInsertTabRun(i + blockOffset); break; case UCS_LRO: case UCS_RLO: case UCS_LRE: case UCS_RLE: case UCS_PDF: // these should have been removed by // pd_Document::append/insert functions UT_ASSERT( UT_SHOULD_NOT_HAPPEN ); break; case UCS_LRM: case UCS_RLM: _doInsertDirectionMarkerRun(i + blockOffset,pChars[i]); break; default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); break; } break; default: if ((i != len-1) && UT_isSmartQuotableCharacter(pChars[i])) { // accumulate smart quote candidates and deal with them // as a bunch below after the final text insertion has // been dealt with sqlist[sqcount++] = blockOffset + i; } if (!bNormal) { bNormal = true; iNormalBase = i; } break; } } UT_ASSERT(i == len); if (bNormal && (iNormalBase < i)) { xxx_UT_DEBUGMSG(("insertSpan: BlockOffset %d iNormalBase %d i %d \n",blockOffset,iNormalBase,i)); _doInsertTextSpan(blockOffset + iNormalBase, i - iNormalBase); } m_iNeedsReformat = blockOffset; format(); updateEnclosingBlockIfNeeded(); m_pSpellSquiggles->textInserted(blockOffset, len); m_pGrammarSquiggles->textInserted(blockOffset, len); xxx_UT_DEBUGMSG(("Set pending block for grammar - insertSpan \n")); m_pLayout->setPendingBlockForGrammar(this); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pcrs->getPosition() + len); // if(!isHdrFtr()) // pView->notifyListeners(AV_CHG_FMTCHAR); // TODO verify that this is necessary. } else if(pView && pView->getPoint() > pcrs->getPosition()) pView->_setPoint(pView->getPoint() + len); if (m_pLayout->hasBackgroundCheckReason(FL_DocLayout::bgcrSmartQuotes)) { fl_BlockLayout *sq_bl = m_pLayout->getPendingBlockForSmartQuote(); UT_uint32 sq_of = m_pLayout->getOffsetForSmartQuote(); m_pLayout->setPendingSmartQuote(NULL, 0); if (sq_bl) { m_pLayout->considerSmartQuoteCandidateAt(sq_bl, sq_of); } if (sqcount) { m_pDoc->beginUserAtomicGlob(); for (UT_uint32 sdex=0; sdexconsiderSmartQuoteCandidateAt(this, sqlist[sdex]); } m_pDoc->endUserAtomicGlob(); } if (UT_isSmartQuotableCharacter(pChars[len - 1])) { m_pLayout->setPendingSmartQuote(this, blockOffset + len - 1); } } if (sqlist != _sqlist) delete[] sqlist; _assertRunListIntegrity(); #if 0 #if DEBUG fp_Run * ppRun = getFirstRun(); while(ppRun) { if(ppRun->getType() == FPRUN_TEXT) { fp_TextRun * pTRun = static_cast(ppRun); pTRun->printText(); } ppRun = ppRun->getNextRun(); } #endif #endif // // OK Now do the insertSpan for any TOC's that shadow this block. // if(!isNotTOCable() && !m_bIsTOC && m_bStyleInTOC) { UT_GenericVector vecBlocksInTOCs; if(m_pLayout->getMatchingBlocksFromTOCs(this, &vecBlocksInTOCs)) { UT_sint32 i = 0; for(i=0; i(vecBlocksInTOCs.getItemCount());i++) { fl_BlockLayout * pBL = vecBlocksInTOCs.getNthItem(i); pBL->doclistener_insertSpan(pcrs); } } else { m_bStyleInTOC = false; } } return true; } #ifndef NDEBUG /*! Assert integrity of the Run list Assert the following properties: - Offsets are correct - No adjacent FmtMark Runs - Only FmtMark Runs have length zero - List ends in an EOP Run */ void fl_BlockLayout::_assertRunListIntegrityImpl(void) { UT_return_if_fail( m_pLayout ); fp_Run* pRun = m_pFirstRun; UT_uint32 iOffset = 0; bool bPastFirst = false; if(m_pFirstRun) { // // Dummy Runs are allowed at the first positions // of a TOC // if(m_pFirstRun->getPrevRun()) { UT_ASSERT(m_pFirstRun->getPrevRun()->getType() == FPRUN_DUMMY); } } #if 0 // // This can legitmately be non zero while deleting a block with an // embedded footnote // UT_ASSERT(m_pFirstRun->getBlockOffset() == 0); // Verify that offset of this block is correct. #endif while (pRun) { #if 0 // // FIXME: Invent a clever way to account for embedded hidden stuff // in blocks (like footnotes). // Maybe detect this sort of anomaly can verify it matches // what is in the piecetable UT_ASSERT( iOffset == pRun->getBlockOffset() ); #endif iOffset += pRun->getLength(); // Verify that we don't have two adjacent FmtMarks. // UT_ASSERT( ((pRun->getType() != FPRUN_FMTMARK) // || !pRun->getNextRun() // || (pRun->getNextRun()->getType() != FPRUN_FMTMARK)) ); // Verify that the Run has a non-zero length (or is a FmtMark) UT_ASSERT( (FPRUN_FMTMARK == pRun->getType()) || ((FPRUN_TAB == pRun->getType()) || (FPRUN_FIELD == pRun->getType()) && isContainedByTOC()) || (pRun->getLength() > 0) ); // Verify that if there is no next Run, this Run is the EOP Run. // Or we're in the middle of loading a document. // // FIXME; Take this code out when things work. // if(pRun->getNextRun() || (FPRUN_ENDOFPARAGRAPH == pRun->getType()) ) { } else { m_pLayout->getDocument()->miniDump(getStruxDocHandle(),8); } UT_ASSERT( pRun->getNextRun() || (FPRUN_ENDOFPARAGRAPH == pRun->getType()) ); pRun = pRun->getNextRun(); bPastFirst = true; } } #endif /* !NDEBUG */ inline void fl_BlockLayout::_assertRunListIntegrity(void) { #ifndef NDEBUG _assertRunListIntegrityImpl(); #endif } bool fl_BlockLayout::_delete(PT_BlockOffset blockOffset, UT_uint32 len) { _assertRunListIntegrity(); xxx_UT_DEBUGMSG(("_delete fl_BlockLayout offset %d len %d \n",blockOffset,len)); // runs to do with bidi post-processing fp_TextRun * pTR_del1 = NULL; fp_TextRun * pTR_del2 = NULL; fp_TextRun * pTR_next = NULL; fp_TextRun * pTR_prev = NULL; fp_Run* pRun = m_pFirstRun; while (pRun) { UT_uint32 iRunBlockOffset = pRun->getBlockOffset(); UT_uint32 iRunLength = pRun->getLength(); xxx_UT_DEBUGMSG(("_delete run %x type %d offset %d len %d \n",pRun,pRun->getType(),iRunBlockOffset,iRunLength)); fp_Run* pNextRun = pRun->getNextRun(); // remember where we're going, since this run may get axed if ( (iRunBlockOffset + iRunLength) <= blockOffset ) { // nothing to do. the delete occurred AFTER this run } else if (iRunBlockOffset >= (blockOffset + len)) { // the delete occurred entirely before this run. xxx_UT_DEBUGMSG(("_delete Run %x New Offset offset %d len %d \n",pRun,iRunBlockOffset - len,iRunLength)); pRun->setBlockOffset(iRunBlockOffset - len); } else { // // Force a whole page redraw if we delete a page or column break // if(pRun->getType() == FPRUN_FORCEDCOLUMNBREAK || pRun->getType() == FPRUN_FORCEDPAGEBREAK) { fp_Container * pCon = static_cast(pRun->getLine()); fp_Page * pPage = pCon->getPage(); if(pPage) { pPage->markAllDirty(); } } if (blockOffset >= iRunBlockOffset) { if ((blockOffset + len) < (iRunBlockOffset + iRunLength)) { // the deleted section is entirely within this run if(pRun->getType()== FPRUN_DIRECTIONMARKER) { if(pRun->getNextRun() && pRun->getNextRun()->getType()== FPRUN_TEXT) { pTR_next = static_cast(pRun->getNextRun()); } if(pRun->getPrevRun() && pRun->getPrevRun()->getType()== FPRUN_TEXT) { pTR_prev = static_cast(pRun->getPrevRun()); } } else if(pRun->getType()== FPRUN_TEXT) { // there should always be something left of // this run pTR_del1 = static_cast(pRun); if(pRun->getNextRun() && pRun->getNextRun()->getType()== FPRUN_TEXT) { pTR_next = static_cast(pRun->getNextRun()); } if(pRun->getPrevRun() && pRun->getPrevRun()->getType()== FPRUN_TEXT) { pTR_prev = static_cast(pRun->getPrevRun()); } } //pRun->setLength(iRunLength - len); pRun->updateOnDelete(blockOffset - iRunBlockOffset, len); UT_ASSERT((pRun->getLength() == 0) || (pRun->getType() == FPRUN_TEXT)); // only textual runs could have a partial deletion } else { // the deleted section crosses over the end of the // run, but not the start; it can however, lead to // deletion of an entire run int iDeleted = iRunBlockOffset + iRunLength - blockOffset; UT_ASSERT(iDeleted > 0); if(pRun->getType()== FPRUN_DIRECTIONMARKER) { if(pRun->getNextRun() && pRun->getNextRun()->getType()== FPRUN_TEXT) { pTR_next = static_cast(pRun->getNextRun()); } if(pRun->getPrevRun() && pRun->getPrevRun()->getType()== FPRUN_TEXT) { pTR_prev = static_cast(pRun->getPrevRun()); } } else if(pRun->getType()== FPRUN_TEXT) { // if the block offset is same as the run // offset and deleted length is greater or // equal to the run length, this whole run is // going and we must do no further processing // on it ... if(!((iRunBlockOffset == blockOffset) && (iRunLength <= len))) pTR_del1 = static_cast(pRun); if(pRun->getNextRun() && pRun->getNextRun()->getType()== FPRUN_TEXT) { pTR_next = static_cast(pRun->getNextRun()); } if(pRun->getPrevRun() && pRun->getPrevRun()->getType()== FPRUN_TEXT) { pTR_prev = static_cast(pRun->getPrevRun()); } } //pRun->setLength(iRunLength - iDeleted); pRun->updateOnDelete(blockOffset - iRunBlockOffset, len); UT_ASSERT((pRun->getLength() == 0) || (pRun->getType() == FPRUN_TEXT)); // only textual runs could have a partial deletion } } else { // the deleted section crosses over the start of the // run and possibly also the end; unless this is the // first run in the block, then we have already deleted // something if(pRun->getType()== FPRUN_DIRECTIONMARKER) { if(pRun->getNextRun() && pRun->getNextRun()->getType()== FPRUN_TEXT) { pTR_next = static_cast(pRun->getNextRun()); } if(pRun->getPrevRun() && pRun->getPrevRun()->getType()== FPRUN_TEXT) { pTR_prev = static_cast(pRun->getPrevRun()); } } else if(pRun->getType()== FPRUN_TEXT) { if(!pTR_del1 && pRun->getPrevRun() && pRun->getPrevRun()->getType()== FPRUN_TEXT) { pTR_prev = static_cast(pRun->getPrevRun()); } if(pRun->getNextRun() && pRun->getNextRun()->getType()== FPRUN_TEXT) { pTR_next = static_cast(pRun->getNextRun()); } } if ((blockOffset + len) < (iRunBlockOffset + iRunLength)) { if(pTR_del1) { pTR_del2 = static_cast(pRun); } else { pTR_del1 = static_cast(pRun); } int iDeleted = blockOffset + len - iRunBlockOffset; UT_ASSERT(iDeleted > 0); pRun->setBlockOffset(iRunBlockOffset - (len - iDeleted)); //pRun->setLength(iRunLength - iDeleted); pRun->updateOnDelete(0, iDeleted); UT_ASSERT((pRun->getLength() == 0) || (pRun->getType() == FPRUN_TEXT)); // only textual runs could have a partial deletion } else { /* the deletion spans the entire run. time to delete it */ //pRun->setLength(0); pRun->updateOnDelete(0, iRunLength); } } if ((pRun->getLength() == 0) && (pRun->getType() != FPRUN_FMTMARK)) { // Remove Run from line // first, however, make sure that if this is our // pTR_next run, we get the run after it in its place if(pTR_next == pRun) { if(pRun->getNextRun() && pRun->getNextRun()->getType() == FPRUN_TEXT) { pTR_next = static_cast(pRun->getNextRun()); } else { pTR_next = NULL; } } fp_Line* pLine = pRun->getLine(); if(pLine) { pLine->removeRun(pRun, true); } // Unlink Run and delete it if (m_pFirstRun == pRun) { m_pFirstRun = pRun->getNextRun(); } pRun->unlinkFromRunList(); // make sure that we do not do any bidi // post-processing on the delete run ... if(pTR_del1 == pRun) pTR_del1 = NULL; if(pTR_del2 == pRun) pTR_del2 = NULL; if(pTR_prev == pRun) pTR_prev = NULL; DELETEP(pRun); if (!m_pFirstRun) { // When deleting content in a block, the EOP Run // should always remain. UT_ASSERT(UT_SHOULD_NOT_HAPPEN); _insertEndOfParagraphRun(); } } } pRun = pNextRun; } // now that we have done the deleting, we have to do some bidi // post processing, since the deletion might have seriously // impacted the visual order of the line; we have to break all // the text runs affected by this, plus the run before and after, // so that the bidi algorithm can be properly applied if(pTR_del1) pTR_del1->breakMeAtDirBoundaries(UT_BIDI_IGNORE); if(pTR_del2) pTR_del2->breakMeAtDirBoundaries(UT_BIDI_IGNORE); if(pTR_prev) pTR_prev->breakMeAtDirBoundaries(UT_BIDI_IGNORE); if(pTR_next) pTR_next->breakMeAtDirBoundaries(UT_BIDI_IGNORE); _assertRunListIntegrity(); return true; } bool fl_BlockLayout::doclistener_deleteSpan(const PX_ChangeRecord_Span * pcrs) { UT_return_val_if_fail( m_pLayout, false ); _assertRunListIntegrity(); UT_ASSERT(pcrs->getType()==PX_ChangeRecord::PXT_DeleteSpan); PT_BlockOffset blockOffset = pcrs->getBlockOffset(); UT_uint32 len = pcrs->getLength(); UT_ASSERT(len>0); xxx_UT_DEBUGMSG(("fl_BlockLayout:: deleteSpan offset %d len %d \n",blockOffset,len)); _delete(blockOffset, len); m_pSpellSquiggles->textDeleted(blockOffset, len); m_pGrammarSquiggles->textDeleted(blockOffset, len); xxx_UT_DEBUGMSG(("Set pending block for grammar - deleteSpan \n")); m_pLayout->setPendingBlockForGrammar(this); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_resetSelection(); pView->_setPoint(pcrs->getPosition()); } else if(pView && pView->getPoint() > pcrs->getPosition()) { if(pView->getPoint() <= pcrs->getPosition() + len) pView->_setPoint(pcrs->getPosition()); else pView->_setPoint(pView->getPoint() - len); } _assertRunListIntegrity(); m_iNeedsReformat = blockOffset; format(); updateEnclosingBlockIfNeeded(); // // OK Now do the deleteSpan for any TOC's that shadow this block. // if(!isNotTOCable() && !m_bIsTOC && m_bStyleInTOC) { UT_GenericVector vecBlocksInTOCs; if( m_pLayout->getMatchingBlocksFromTOCs(this, &vecBlocksInTOCs)) { UT_sint32 i = 0; for(i=0; i(vecBlocksInTOCs.getItemCount());i++) { fl_BlockLayout * pBL = vecBlocksInTOCs.getNthItem(i); pBL->doclistener_deleteSpan(pcrs); } } else { m_bStyleInTOC = false; } } return true; } /*! Change runs in the given span \param pcrsc Specifies the span This function makes all fp_Run objects within the given span lookup new properties and recalculate their width. Runs at the ends of the span that extend over the span border will be split, so runs fall entirely inside or outside of the span. */ bool fl_BlockLayout::doclistener_changeSpan(const PX_ChangeRecord_SpanChange * pcrsc) { _assertRunListIntegrity(); UT_ASSERT(pcrsc->getType()==PX_ChangeRecord::PXT_ChangeSpan); PT_BlockOffset blockOffset = pcrsc->getBlockOffset(); UT_uint32 len = pcrsc->getLength(); UT_ASSERT(len > 0); UT_GenericVector vecLines; vecLines.clear(); // First look for the first run inside the span fp_Run* pRun = m_pFirstRun; fp_Run* pPrevRun = NULL; while (pRun && pRun->getBlockOffset() < blockOffset) { pPrevRun = pRun; pRun = pRun->getNextRun(); } // If pRun is now at the blockOffset, the span falls on an // existing separation between runs. If not, we have to split the // run (pPrevRun) which is extending over the border. if (!pRun || (pRun->getBlockOffset() != blockOffset)) { // Need to split previous Run. // Note: That should be a fp_TextRun. If not, we'll keep going // using the first run fully inside the span - but keep the // assertion for alert in debug builds. UT_ASSERT(pPrevRun); UT_ASSERT(FPRUN_TEXT == pPrevRun->getType()); if (FPRUN_TEXT == pPrevRun->getType()) { fp_TextRun* pTextRun = static_cast(pPrevRun); pTextRun->split(blockOffset); } pRun = pPrevRun->getNextRun(); } // When we get here, we have a clean separation on the left // between what's outside and what's inside of the span. pRun is // the first run inside the span. UT_ASSERT(!pRun || (blockOffset == pRun->getBlockOffset())); // Now start forcing the runs to update while (pRun) { // If the run is on the right of the span, we're done if ((pRun->getBlockOffset() >= (blockOffset + len))) break; // If the run extends beyond the span, split it. if ((pRun->getBlockOffset() + pRun->getLength()) > (blockOffset + len)) { // Note: That should be a fp_TextRun. If not, we'll just // have to update the entire run - but keep the assertion // for alert in debug builds. UT_ASSERT(FPRUN_TEXT == pRun->getType()); if (FPRUN_TEXT == pRun->getType()) { fp_TextRun* pTextRun = static_cast(pRun); pTextRun->split(blockOffset+len); } } // FIXME:jskov Here we want to call a changeSpanMember // function in the Run which decides how to behave. That way // we don't forget new Run types as they get added, and // show-paragraphs mode can be handled properly. // Make the run update its properties and recalculate width as // necessary. if (pRun->getType() == FPRUN_TEXT) { fp_TextRun* pTextRun = static_cast(pRun); pTextRun->lookupProperties(); // I moved markWidthDirty() inside fp_TextRun::_lookupProperties(), // since there we are in proper position to determine if the // width needs redoing. Tomas, Nov 28, 2003 // pTextRun->markWidthDirty(); } else if (pRun->getType() == FPRUN_TAB) { pRun->lookupProperties(); } else if (pRun->getType() == FPRUN_IMAGE) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } // TODO: do we need to call lookupProperties for other run types. fp_Line * pLine = pRun->getLine(); if((pLine!= NULL) && (vecLines.findItem(pLine) < 0)) { vecLines.addItem(pLine); } pRun = pRun->getNextRun(); } // // maybe able to remove this once the rest of bug 5240 is fixed. // UT_uint32 i =0; for(i=0; i< vecLines.getItemCount(); i++) { fp_Line * pLine = vecLines.getNthItem(i); pLine->clearScreen(); } m_iNeedsReformat = blockOffset; format(); updateEnclosingBlockIfNeeded(); _assertRunListIntegrity(); return true; } /*! Delete strux Run \param pcrx Change record for the operation \return true if succeeded, false if not This function will merge the content of this strux to the previous strux. */ bool fl_BlockLayout::doclistener_deleteStrux(const PX_ChangeRecord_Strux* pcrx) { UT_DEBUGMSG(("doclistener_deleteStrux\n")); _assertRunListIntegrity(); UT_ASSERT(pcrx->getType()==PX_ChangeRecord::PXT_DeleteStrux); UT_ASSERT(pcrx->getStruxType()==PTX_Block); // First see if the block in a list. If so remove it! if(m_pAutoNum != NULL) { if( m_pAutoNum->isItem(getStruxDocHandle()) == true) { // This nifty method handles all the details m_pAutoNum->removeItem(getStruxDocHandle()); } } // // Do this before all the required info is deleted. // updateEnclosingBlockIfNeeded(); fp_Container * pCon = getFirstContainer(); if(pCon) { fp_Page * pPage = pCon->getPage(); getDocSectionLayout()->setNeedsSectionBreak(true,pPage); } else { getDocSectionLayout()->setNeedsSectionBreak(true,NULL); } setNeedsReformat(); // Erase the old version. Or this what I added when adding the // EOP stuff. Only, I don't remember why I did it, and it's wrong: // the strux is deleted only after its content has been deleted - // so the call might try to clear empty lines. jskov 2001.04.23 // Sevior put this back 2001.6.3. I don't understand why there was ever any // question about it's neccessity. clearScreen(m_pLayout->getGraphics()); // If there is a previous strux, we merge the Runs from this strux // into it - including the EOP Run, so delete that in the previous // strux. // If there is no previous strux (this being the first strux in // the document) this will be empty - but the EOP Run needs to be // deleted. // // This is not exactly the case; for example the first block in the footnote section // has not previous, yet it is not empty -- it contains at least the footnote reference. fp_Line* pLastLine = NULL; fl_BlockLayout * pPrevBL = NULL; fl_ContainerLayout *pCL = getPrev(); while(pCL && pCL->getContainerType() != FL_CONTAINER_BLOCK) { // // Attach to the block before the other container type , // because this block has to get merged with it // pCL = pCL->getPrev(); } // this is safe cast because we either have block or NULL pPrevBL = static_cast(pCL); // // Deal with embedded containers if any in this block. // shuffleEmbeddedIfNeeded(pPrevBL, 0); // // The idea here is to append the runs of the deleted block, if // any, at the end of the previous block. We must make sure to take // of embedded footnotes/endnotes. We need to calculate the offset // before we deletes the EOP run. The offset may not be contiguous // because of embedded footnotes/endnotes // UT_uint32 offset = 0; if (pPrevBL) { // Find the EOP Run. pLastLine = static_cast(pPrevBL->getLastContainer()); fp_Run* pNukeRun = pPrevBL->m_pFirstRun; fp_Run * pPrevRun = pPrevBL->m_pFirstRun; while(pNukeRun->getNextRun() != NULL) { pPrevRun = pNukeRun; UT_ASSERT(FPRUN_ENDOFPARAGRAPH != pPrevRun->getType()); pNukeRun = pPrevRun->getNextRun(); } UT_ASSERT(FPRUN_ENDOFPARAGRAPH == pNukeRun->getType()); // // The idea here is to append the runs of the deleted block, if // any, at the end of the previous block. We must make sure to take // account of embedded footnotes/endnotes. // We need to calculate the offset // before we delete the EOP run. // if(FPRUN_ENDOFPARAGRAPH == pNukeRun->getType()) { offset = pNukeRun->getBlockOffset(); } else { offset = pNukeRun->getBlockOffset() + pNukeRun->getLength(); } // Detach from the line fp_Line* pLine = pNukeRun->getLine(); UT_ASSERT(pLine && pLine == pLastLine); if(pLine) { pLine->removeRun(pNukeRun); } // Unlink and delete it if (pPrevRun && (pPrevRun != pNukeRun)) { pPrevRun->setNextRun(NULL); } else { pPrevBL->m_pFirstRun = NULL; } delete pNukeRun; } else { // Delete end-of-paragraph Run in this strux UT_ASSERT(m_pFirstRun && (FPRUN_ENDOFPARAGRAPH == m_pFirstRun->getType())); fp_Run* pNukeRun = m_pFirstRun; // Detach from the line fp_Line* pLine = pNukeRun->getLine(); UT_ASSERT(pLine); if(pLine) { pLine->removeRun(pNukeRun); } // Unlink and delete it m_pFirstRun = NULL; delete pNukeRun; } // We use the offset we calculated earlier. if (m_pFirstRun) { // Figure out where the merge point is fp_Run * pRun = pPrevBL->m_pFirstRun; fp_Run * pLastRun = NULL; while (pRun) { pLastRun = pRun; pRun = pRun->getNextRun(); } // Link them together if (pLastRun) { pLastRun->setNextRun(m_pFirstRun); if(m_pFirstRun) { m_pFirstRun->setPrevRun(pLastRun); } } else { pPrevBL->m_pFirstRun = m_pFirstRun; } UT_DEBUGMSG(("deleteStrux: offset = %d \n",offset)); // Tell all the new runs where they live pRun = m_pFirstRun; while (pRun) { pRun->setBlockOffset(pRun->getBlockOffset() + offset); pRun->setBlock(pPrevBL); // Detach from their line fp_Line* pLine = pRun->getLine(); UT_ASSERT(pLine); if(pLine) { pLine->removeRun(pRun); } if(pLastLine) { pLastLine->addRun(pRun); } pRun = pRun->getNextRun(); } // Runs are no longer attached to this block m_pFirstRun = NULL; } // // Transfer any frames from this block to the previous block in the // the document. // fl_BlockLayout * pPrevForFrames = pPrevBL; if(pPrevForFrames == NULL) { pPrevForFrames = getPrevBlockInDocument(); } if(pPrevForFrames) { if(getNumFrames() > 0) { fl_FrameLayout * pFrame = NULL; UT_sint32 i = 0; UT_sint32 count = getNumFrames(); for(i= 0; i < count; i++) { pFrame = getNthFrameLayout(i); pPrevForFrames->addFrame(pFrame); } for( i=count-1; i>=0; i--) { pFrame = getNthFrameLayout(i); removeFrame(pFrame); } } } // Get rid of everything else about the block purgeLayout(); // // Update it's TOC entry // if(m_pLayout->isBlockInTOC(this)) { m_pLayout->removeBlockFromTOC(this); } // Unlink this block if(getNext() && getNext()->getNext() && getNext()->getNext()->getContainerType() == FL_CONTAINER_TOC) { xxx_UT_DEBUGMSG(("Next container is TOC \n")); } // // Use actual getPrev() to preserve the structure of the document. // if (getPrev()) { getPrev()->setNext(getNext()); } if (getNext()) { getNext()->setPrev(getPrev()); } fl_SectionLayout* pSL = static_cast(myContainingLayout()); UT_ASSERT(pSL); if(pSL) { pSL->remove(this); } if (pPrevBL) { // // Now fix up the previous block. Calling this format fixes bug 2702 // fp_Run * pPrevBLRun =pPrevBL->getFirstRun(); while(pPrevBLRun) { pPrevBLRun->lookupProperties(); pPrevBLRun = pPrevBLRun->getNextRun(); } pPrevBL->format(); // This call will dequeue the block from background checking // if necessary m_pSpellSquiggles->join(offset, pPrevBL); m_pGrammarSquiggles->join(offset, pPrevBL); pPrevBL->setNeedsReformat(); // // Update if it's TOC entry by removing then restoring // if(m_pLayout->isBlockInTOC(pPrevBL)) { m_pLayout->removeBlockFromTOC(pPrevBL); m_pLayout->addOrRemoveBlockFromTOC(pPrevBL); } } else { // In case we've never checked this one m_pLayout->dequeueBlockForBackgroundCheck(this); } if(pSL) { FV_View* pView = pSL->getDocLayout()->getView(); if (pView->isHdrFtrEdit() && (!pView->getEditShadow() || !pView->getEditShadow()->getLastLayout())) pView->clearHdrFtrEdit(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pcrx->getPosition()); } else if(pView && pView->getPoint() > pcrx->getPosition()) { pView->_setPoint(pView->getPoint() - 1); } _assertRunListIntegrity(); } delete this; // FIXME: whoa! this construct is VERY dangerous. return true; } bool fl_BlockLayout::doclistener_changeStrux(const PX_ChangeRecord_StruxChange * pcrxc) { _assertRunListIntegrity(); UT_ASSERT(pcrxc->getType()==PX_ChangeRecord::PXT_ChangeStrux); // erase the old version if(!isHdrFtr()) { clearScreen(m_pLayout->getGraphics()); } collapse(); setAttrPropIndex(pcrxc->getIndexAP()); xxx_UT_DEBUGMSG(("SEVIOR: In changeStrux in fl_BlockLayout %x \n",this)); // // Not sure if we'll ever need this. We don't need this now I'll comment it out. // const XML_Char * szOldStyle = m_szStyle; UT_sint32 iOldDomDirection = m_iDomDirection; lookupProperties(); xxx_UT_DEBUGMSG(("SEVIOR: Old Style = %s new style = %s \n",szOldStyle,m_szStyle)); // // Not sure why we need this IF - Sevior // if ((szOldStyle != m_szStyle) && // (!szOldStyle || !m_szStyle || !!(UT_XML_strcmp(szOldStyle, m_szStyle)))) { /* A block-level style change means that we also need to update all the run-level properties. */ fp_Run* pRun = m_pFirstRun; xxx_UT_DEBUGMSG(("SEVIOR: Doing a style change \n")); while (pRun) { pRun->lookupProperties(); pRun->recalcWidth(); pRun = pRun->getNextRun(); } } fp_Line* pLine = static_cast(getFirstContainer()); while (pLine) { pLine->recalcHeight(); // line-height pLine->recalcMaxWidth(); if(m_iDomDirection != iOldDomDirection) { xxx_UT_DEBUGMSG(("block listener: change of direction\n")); pLine->setMapOfRunsDirty(); } pLine = static_cast(pLine->getNext()); } format(); #if 0 // This was... if(m_pDoc->isDoingPaste()) { format(); } // if we were on screen we need to reformat immediately, since the ruler will be // calling the findPointCoords() chain and if we are collapsed (as // we are now) and contain the point, it will fail if(bWasOnScreen) format(); else setNeedsReformat(); #endif updateEnclosingBlockIfNeeded(); // // Need this to find where to break section in the document. // fl_ContainerLayout * pPrevCL = getPrevBlockInDocument(); fp_Page * pPrevP = NULL; if(pPrevCL) { fp_Container * pPrevCon = pPrevCL->getFirstContainer(); if(pPrevCon) { pPrevP = pPrevCon->getPage(); } } getDocSectionLayout()->setNeedsSectionBreak(true,pPrevP); _assertRunListIntegrity(); return true; } bool fl_BlockLayout::doclistener_insertFirstBlock(const PX_ChangeRecord_Strux * pcrx, PL_StruxDocHandle sdh, PL_ListenerId lid, void (* pfnBindHandles)(PL_StruxDocHandle sdhNew, PL_ListenerId lid, PL_StruxFmtHandle sfhNew)) { // Exchange handles with the piece table PL_StruxFmtHandle sfhNew = static_cast(this); // // Don't bind to shadows! // if(pfnBindHandles) { pfnBindHandles(sdh,lid,sfhNew); } setNeedsReformat(); updateEnclosingBlockIfNeeded(); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) pView->_setPoint(pcrx->getPosition()); else if (pView && ((pView->getPoint() == 0) || pView->getPoint() > pcrx->getPosition()) ) pView->_setPoint(pView->getPoint() + fl_BLOCK_STRUX_OFFSET); // Run list should be valid now. _assertRunListIntegrity(); return true; } bool fl_BlockLayout::doclistener_insertBlock(const PX_ChangeRecord_Strux * pcrx, PL_StruxDocHandle sdh, PL_ListenerId lid, void (* pfnBindHandles)(PL_StruxDocHandle sdhNew, PL_ListenerId lid, PL_StruxFmtHandle sfhNew)) { _assertRunListIntegrity(); UT_ASSERT(pcrx->getType()==PX_ChangeRecord::PXT_InsertStrux); UT_ASSERT(pcrx->getStruxType()==PTX_Block); fl_SectionLayout* pSL = static_cast(myContainingLayout()); UT_ASSERT(pSL); fl_BlockLayout* pNewBL = static_cast(pSL->insert(sdh, this, pcrx->getIndexAP(),FL_CONTAINER_BLOCK)); if(isHdrFtr()) pNewBL->setHdrFtr(); if (!pNewBL) { UT_DEBUGMSG(("no memory for BlockLayout\n")); return false; } xxx_UT_DEBUGMSG(("Inserting block %x it's sectionLayout type is %d \n",pNewBL,pNewBL->getSectionLayout()->getContainerType())); //xxx_UT_DEBUGMSG(("Inserting block at pos %d \n",getPosition(true))); //xxx_UT_DEBUGMSG(("shd of strux block = %x of new block is %x \n",getStruxDocHandle(),pNewBL->getStruxDocHandle())); // The newly returned block will contain a line and EOP. Delete those // since the code below expects an empty block pNewBL->_purgeEndOfParagraphRun(); // Must call the bind function to complete the exchange // of handles with the document (piece table) *** before *** // anything tries to call down into the document (like all // of the view listeners). PL_StruxFmtHandle sfhNew = static_cast(pNewBL); // // Don't Bind to shadows // if(pfnBindHandles) { pfnBindHandles(sdh,lid,sfhNew); } /* The idea here is to divide the runs of the existing block into two equivalence classes. This may involve splitting an existing run. All runs and lines remaining in the existing block are fine, although the last run should be redrawn. All runs in the new block need their offsets fixed, and that entire block needs to be formatted from scratch. TODO is the above commentary still correct ?? */ // figure out where the breakpoint is PT_BlockOffset blockOffset = (pcrx->getPosition() - getPosition()); // // OK Now we have to deal with any embedded containerlayout associated with // this block. // If they are before the insert point they must be moved to be immediately // after this block (and hence before the new block) // shuffleEmbeddedIfNeeded(this,blockOffset); fp_Run* pFirstNewRun = NULL; fp_Run* pLastRun = NULL; fp_Run* pRun; xxx_UT_DEBUGMSG(("BlockOffset %d \n",blockOffset)); for (pRun=m_pFirstRun; (pRun && !pFirstNewRun); pLastRun=pRun, pRun=pRun->getNextRun()) { // We have passed the point. Why didn't previous Run claim to // hold the offset? Make the best of it in non-debug // builds. But keep the assert to get us information... xxx_UT_DEBUGMSG(("pRun %x pRun->next %x pRun->blockOffset %d pRun->getLength %d \n",pRun,pRun->getNextRun(),pRun->getBlockOffset(),pRun->getLength())); if (pRun->getBlockOffset() > blockOffset) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); pFirstNewRun = pRun; break; } // FIXME: Room for optimization improvement here - always scan // FIXME: past the point by only comparing getBlockOffset. if (pRun->getBlockOffset() <= blockOffset && (pRun->getBlockOffset() + pRun->getLength()) > blockOffset) { // We found the Run. Now handle splitting. if (pRun->getBlockOffset() == blockOffset) { // Split between this Run and the previous one pFirstNewRun = pRun; } else { // Need to split current Run UT_ASSERT(pRun->getType() == FPRUN_TEXT); // split here fp_TextRun* pTextRun = static_cast(pRun); pTextRun->split(blockOffset); pFirstNewRun = pRun->getNextRun(); } break; } } while(pFirstNewRun && (pFirstNewRun->getType() == FPRUN_FMTMARK)) { // Since a FmtMark has length zero, both it and the next run // have the same blockOffset. We always want to be to the // right of the FmtMark, so we take the next one. pFirstNewRun = pFirstNewRun->getNextRun(); } UT_sint32 iEOPOffset = -1; if (pFirstNewRun) { if(pFirstNewRun->getBlockOffset() == blockOffset) { iEOPOffset = pFirstNewRun->getBlockOffset(); } if (pFirstNewRun->getPrevRun()) { // Break doubly-linked list of runs into two distinct lists. // But remember the last Run in this block. pLastRun = pFirstNewRun->getPrevRun(); pFirstNewRun->getPrevRun()->setNextRun(NULL); pFirstNewRun->setPrevRun(NULL); } else pLastRun = NULL; } // else the old value of pLastRun is what we want. // pFirstNewRun can be NULL at this point. It means that the // entire set of runs in this block must remain with this block -- // and the newly created block will be empty. // // Also, note if pFirstNewRun == m_pFirstRun then we will be moving // the entire set of runs to the newly created block -- and leave // the current block empty. // Move remaining runs to new block pNewBL->m_pFirstRun = pFirstNewRun; // And update their positions for (pRun=pFirstNewRun; (pRun); pRun=pRun->getNextRun()) { pRun->setBlockOffset(pRun->getBlockOffset() - blockOffset); pRun->setBlock(pNewBL); // TODO [2] the following 2 steps seem expensive considering // TODO we already knew width information before divided the // TODO char widths data between the two clocks. see [1]. pRun->recalcWidth(); } // // Now transfer the frames of this block to the newly created one // if(getNumFrames() > 0) { fl_FrameLayout * pFrame = NULL; UT_sint32 i = 0; UT_sint32 count = getNumFrames(); for(i= 0; i < count; i++) { pFrame = getNthFrameLayout(i); pNewBL->addFrame(pFrame); } for( i=0; igetNthFrameLayout(i); removeFrame(pFrame); } } // Explicitly truncate rest of this block's layout _truncateLayout(pFirstNewRun); // Now make sure this block still has an EOP Run. if (m_pFirstRun) { UT_ASSERT(pLastRun); // Create a new end-of-paragraph run and add it to the block. fp_EndOfParagraphRun* pNewRun = new fp_EndOfParagraphRun(this, 0, 0); pLastRun->setNextRun(pNewRun); pNewRun->setPrevRun(pLastRun); if(iEOPOffset < 0) { pNewRun->setBlockOffset(pLastRun->getBlockOffset() + pLastRun->getLength()); } else { pNewRun->setBlockOffset(iEOPOffset); } if(pLastRun->getLine()) pLastRun->getLine()->addRun(pNewRun); coalesceRuns(); } else { _insertEndOfParagraphRun(); } setNeedsReformat(); pNewBL->collapse(); // remove all previous lines // Throw all the runs onto one jumbo line in the new block pNewBL->_stuffAllRunsOnALine(); if (pNewBL->m_pFirstRun) pNewBL->coalesceRuns(); else pNewBL->_insertEndOfParagraphRun(); pNewBL->setNeedsReformat(); updateEnclosingBlockIfNeeded(); // Split squiggles between this and the new block m_pSpellSquiggles->split(blockOffset, pNewBL); m_pGrammarSquiggles->split(blockOffset, pNewBL); m_pLayout->setPendingBlockForGrammar(pNewBL); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) pView->_setPoint(pcrx->getPosition() + fl_BLOCK_STRUX_OFFSET); else if(pView && pView->getPoint() > pcrx->getPosition()) pView->_setPoint(pView->getPoint() + fl_BLOCK_STRUX_OFFSET); _assertRunListIntegrity(); xxx_UT_DEBUGMSG(("Prev Block = %x type %d Next block = %x type %d \n",pNewBL->getPrev(),pNewBL->getContainerType(),pNewBL->getNext(),pNewBL->getContainerType())); return true; } /*! * This method shuffles any emebedded containers in the block to be placed * after the supplied block. * * If they are before the insert point they must be moved to be immediately * after this block (and hence before the new block) */ void fl_BlockLayout::shuffleEmbeddedIfNeeded(fl_BlockLayout * pBlock, UT_uint32 blockOffset) { if(pBlock == NULL) { return; } UT_sint32 iEmbed = 0; bool bStop = false; fl_ContainerLayout * pEmbedCL = NULL; while(!bStop) { iEmbed = pBlock->getEmbeddedOffset(iEmbed, pEmbedCL); if(iEmbed < 0) { bStop = true; break; } if(pEmbedCL == NULL) { bStop = true; break; } if((blockOffset > 0) && (iEmbed < static_cast(blockOffset))) { iEmbed++; continue; } // // Move pEmbedCL to be just after this block. // // Outer pointers // fl_ContainerLayout * pBLNext = pBlock->getNext(); if(pEmbedCL->getPrev() && (pEmbedCL->getPrev() != pBlock)) { pEmbedCL->getPrev()->setNext(pEmbedCL->getNext()); } if(pEmbedCL->getNext() && pBLNext != pEmbedCL) { pEmbedCL->getNext()->setPrev(pEmbedCL->getPrev()); } // // New pointers for EmbedCL pEmbedCL->setPrev(static_cast(pBlock)); if(pBLNext != pEmbedCL) { pEmbedCL->setNext(pBlock->getNext()); } // // New pointer here if(pBlock->getNext() && (pBlock->getNext() != pEmbedCL)) { pBlock->getNext()->setPrev(pEmbedCL); } pBlock->setNext(pEmbedCL); // // Now add in the length of the container // PL_StruxDocHandle sdhStart = pEmbedCL->getStruxDocHandle(); PL_StruxDocHandle sdhEnd = NULL; if(pEmbedCL->getContainerType() == FL_CONTAINER_FOOTNOTE) { getDocument()->getNextStruxOfType(sdhStart,PTX_EndFootnote, &sdhEnd); } else if(pEmbedCL->getContainerType() == FL_CONTAINER_ENDNOTE) { getDocument()->getNextStruxOfType(sdhStart,PTX_EndEndnote, &sdhEnd); } else if( pEmbedCL->getContainerType() == FL_CONTAINER_TOC) { getDocument()->getNextStruxOfType(sdhStart,PTX_EndTOC, &sdhEnd); } UT_return_if_fail(sdhEnd != NULL); PT_DocPosition posStart = getDocument()->getStruxPosition(sdhStart); PT_DocPosition posEnd = getDocument()->getStruxPosition(sdhEnd); UT_uint32 iSize = posEnd - posStart + 1; iEmbed += iSize; getDocSectionLayout()->setNeedsSectionBreak(true,NULL); } } bool fl_BlockLayout::doclistener_insertSection(const PX_ChangeRecord_Strux * pcrx, SectionType iType, PL_StruxDocHandle sdh, PL_ListenerId lid, void (* pfnBindHandles)(PL_StruxDocHandle sdhNew, PL_ListenerId lid, PL_StruxFmtHandle sfhNew)) { UT_ASSERT(iType == FL_SECTION_DOC || iType == FL_SECTION_HDRFTR || iType == FL_SECTION_TOC || iType == FL_SECTION_FOOTNOTE || iType == FL_SECTION_ENDNOTE); _assertRunListIntegrity(); // Insert a section at the location given in the change record. // Everything from this point forward (to the next section) needs // to be re-parented to this new section. We also need to verify // that this insertion point is at the end of the block (and that // another block follows). This is because a section cannot // contain content. UT_ASSERT(pcrx); UT_ASSERT(pcrx->getType() == PX_ChangeRecord::PXT_InsertStrux); UT_ASSERT(iType != FL_SECTION_DOC || pcrx->getStruxType() == PTX_Section); UT_ASSERT(iType != FL_SECTION_HDRFTR || pcrx->getStruxType() == PTX_SectionHdrFtr); UT_ASSERT(iType != FL_SECTION_FOOTNOTE || pcrx->getStruxType() == PTX_SectionFootnote); getDocSectionLayout()->setNeedsSectionBreak(true,NULL); // // Not true always. eg Undo on a delete header/footer. We should detect this // and deal with it. // PT_DocPosition pos1; // // This is to clean the fragments // m_pDoc->getBounds(true,pos1); fl_DocSectionLayout* pDSL = NULL; if(m_pSectionLayout->getType() == FL_SECTION_DOC) pDSL = static_cast(m_pSectionLayout); xxx_UT_DEBUGMSG(("SectionLayout for block is %x block is %x \n",m_pSectionLayout,this)); fl_SectionLayout* pSL = NULL; const XML_Char* pszNewID = NULL; UT_DEBUGMSG(("Insert section at pos %d sdh of section =%x sdh of block =%x \n",getPosition(true),sdh,getStruxDocHandle())); switch (iType) { case FL_SECTION_DOC: pSL = new fl_DocSectionLayout (m_pLayout, sdh, pcrx->getIndexAP(), FL_SECTION_DOC); if (!pSL) { UT_DEBUGMSG(("no memory for SectionLayout")); return false; } m_pLayout->insertSectionAfter(pDSL, static_cast(pSL)); break; case FL_SECTION_HDRFTR: { pSL = new fl_HdrFtrSectionLayout(FL_HDRFTR_NONE,m_pLayout,NULL, sdh, pcrx->getIndexAP()); if (!pSL) { UT_DEBUGMSG(("no memory for SectionLayout")); return false; } fl_HdrFtrSectionLayout * pHFSL = static_cast(pSL); m_pLayout->addHdrFtrSection(pHFSL); // // Need to find the DocSectionLayout associated with this. // const PP_AttrProp* pHFAP = NULL; PT_AttrPropIndex indexAP = pcrx->getIndexAP(); bool bres = (m_pDoc->getAttrProp(indexAP, &pHFAP) && pHFAP); UT_ASSERT(bres); pHFAP->getAttribute("id", pszNewID); // // pszHFID may not be defined yet. If not we can't do this stuff. If it is defined // this step is essential // if(pszNewID) { // plam mystery code // plam, MES here, I need this code for inserting headers/footers. UT_DEBUGMSG(("new id: tell plam if you see this message\n")); // UT_ASSERT(0); fl_DocSectionLayout* pDocSL = m_pLayout->findSectionForHdrFtr(static_cast(pszNewID)); UT_ASSERT(pDocSL); // // Determine if this is a header or a footer. // const XML_Char* pszSectionType = NULL; pHFAP->getAttribute("type", pszSectionType); HdrFtrType hfType = FL_HDRFTR_NONE; if (pszSectionType && *pszSectionType) { if(UT_strcmp(pszSectionType,"header") == 0) hfType = FL_HDRFTR_HEADER; else if (UT_strcmp(pszSectionType,"header-even") == 0) hfType = FL_HDRFTR_HEADER_EVEN; else if (UT_strcmp(pszSectionType,"header-first") == 0) hfType = FL_HDRFTR_HEADER_FIRST; else if (UT_strcmp(pszSectionType,"header-last") == 0) hfType = FL_HDRFTR_HEADER_LAST; else if (UT_strcmp(pszSectionType,"footer") == 0) hfType = FL_HDRFTR_FOOTER; else if (UT_strcmp(pszSectionType,"footer-even") == 0) hfType = FL_HDRFTR_FOOTER_EVEN; else if (UT_strcmp(pszSectionType,"footer-first") == 0) hfType = FL_HDRFTR_FOOTER_FIRST; else if (UT_strcmp(pszSectionType,"footer-last") == 0) hfType = FL_HDRFTR_FOOTER_LAST; if(hfType != FL_HDRFTR_NONE) { pHFSL->setDocSectionLayout(pDocSL); pHFSL->setHdrFtr(hfType); // // Set the pointers to this header/footer // pDocSL->setHdrFtr(hfType, pHFSL); } } } else { UT_DEBUGMSG(("NO ID found with insertSection HdrFtr \n")); } break; } case FL_SECTION_ENDNOTE: case FL_SECTION_FOOTNOTE: { // Most of the time, we would insert a new section // after the previous section. // But, here we insert our FootnoteLayout after this(?) // BlockLayout. -PL PT_AttrPropIndex indexAP = pcrx->getIndexAP(); if(iType == FL_SECTION_FOOTNOTE) { pSL = static_cast(static_cast(getSectionLayout())->insert(sdh,this,indexAP, FL_CONTAINER_FOOTNOTE)); } else { pSL = static_cast(static_cast(getSectionLayout())->insert(sdh,this,indexAP, FL_CONTAINER_ENDNOTE)); } // // Need to find the DocSectionLayout associated with this. // const PP_AttrProp* pAP = NULL; bool bres = (m_pDoc->getAttrProp(indexAP, &pAP) && pAP); UT_ASSERT(bres); pAP->getAttribute("id", pszNewID); break; } case FL_SECTION_TOC: { // Most of the time, we would insert a new section // after the previous section. // But, here we insert our TOCLayout after this(?) PT_AttrPropIndex indexAP = pcrx->getIndexAP(); pSL = static_cast(static_cast(getSectionLayout())->insert(sdh,this,indexAP, FL_CONTAINER_TOC)); // Must call the bind function to complete the exchange of handles // with the document (piece table) *** before *** anything tries // to call down into the document (like all of the view // listeners). PL_StruxFmtHandle sfhNew = static_cast(pSL); // // Don't bind to shadows // if(pfnBindHandles) { pfnBindHandles(sdh,lid,sfhNew); } // // That's all we need to do except update the view pointers I guess.. // FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pcrx->getPosition() + fl_BLOCK_STRUX_OFFSET); } else if(pView && pView->getPoint() > pcrx->getPosition()) { // // For EndTOC // pView->_setPoint(pView->getPoint() + fl_BLOCK_STRUX_OFFSET + fl_BLOCK_STRUX_OFFSET); } return true; } default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); break; } PT_DocPosition posSL = m_pDoc->getStruxPosition(pSL->getStruxDocHandle()); PT_DocPosition posThis = m_pDoc->getStruxPosition(getStruxDocHandle()); // Must call the bind function to complete the exchange of handles // with the document (piece table) *** before *** anything tries // to call down into the document (like all of the view // listeners). PL_StruxFmtHandle sfhNew = static_cast(pSL); // // Don't bind to shadows // if(pfnBindHandles) { pfnBindHandles(sdh,lid,sfhNew); } fl_SectionLayout* pOldSL = m_pSectionLayout; if ((iType == FL_SECTION_FOOTNOTE) || (iType == FL_SECTION_ENDNOTE)) { // // Now update the position pointer in the view // FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pcrx->getPosition() + fl_BLOCK_STRUX_OFFSET); } else if(pView && pView->getPoint() > pcrx->getPosition()) { pView->_setPoint(pView->getPoint() + fl_BLOCK_STRUX_OFFSET + fl_BLOCK_STRUX_OFFSET); } return true; } // // Now move all the blocks following into the new section // fl_ContainerLayout* pCL = NULL; if(posSL < posThis) { pCL = this; } else { pCL = getNext(); } // // BUT!!! Don't move the immediate Footnotes or Endnotes // fl_ContainerLayout * pLastCL = NULL; if(pCL) { pLastCL = pCL->getPrev(); } while(pCL && ((pCL->getContainerType() == FL_CONTAINER_FOOTNOTE) || (pCL->getContainerType() == FL_CONTAINER_ENDNOTE))) { pLastCL = pCL; pCL = pCL->getNext(); } fl_BlockLayout * pBL = NULL; while (pCL) { // // When inserting a HEADER/FOOTER dont move footnotes/endnotes into // Header/Footer // if((iType== FL_SECTION_HDRFTR) && (pCL->getContainerType() == FL_CONTAINER_FOOTNOTE || pCL->getContainerType() == FL_CONTAINER_ENDNOTE || pCL->getContainerType() == FL_CONTAINER_TOC || pCL->getContainerType() == FL_CONTAINER_FRAME)) { pCL = pCL->getNext(); continue; } fl_ContainerLayout* pNext = pCL->getNext(); pBL = NULL; pCL->collapse(); if(pCL->getContainerType()==FL_CONTAINER_BLOCK) { pBL = static_cast(pCL); } if(pBL && pBL->isHdrFtr()) { fl_HdrFtrSectionLayout * pHF = static_cast(pBL->getSectionLayout()); pHF->collapseBlock(pBL); } pOldSL->remove(pCL); pSL->add(pCL); if(pBL) { pBL->setSectionLayout( pSL); pBL->m_iNeedsReformat = 0; } if(pSL->getType() == FL_SECTION_DOC) { fl_DocSectionLayout * pDDSL = static_cast(pSL); if(pCL->getContainerType() == FL_CONTAINER_FOOTNOTE) { static_cast(pCL)-> setDocSectionLayout(pDDSL); } if(pCL->getContainerType() == FL_CONTAINER_ENDNOTE) { static_cast(pCL)-> setDocSectionLayout(pDDSL); } } pCL = pNext; } // // Terminate blocklist here. This Block is the last in this section. // if (pLastCL) { pLastCL->setNext(NULL); pOldSL->setLastLayout(pLastCL); } // // OK we have to redo all the containers now. // if(pSL->getType() == FL_SECTION_DOC) { fl_DocSectionLayout * pFirstDSL = static_cast(pOldSL); pDSL = pFirstDSL; while(pDSL != NULL) { pDSL->collapse(); pDSL = pDSL->getNextDocSection(); } pDSL = pFirstDSL; while(pDSL != NULL) { pDSL->updateDocSection(); pDSL = pDSL->getNextDocSection(); } } // // In the case of Header/Footer sections we must now format this stuff to create // the shadows. // if(iType == FL_SECTION_HDRFTR || iType == FL_SECTION_FOOTNOTE) { if(pszNewID) { pSL->format(); pSL->redrawUpdate(); } else return true; } updateEnclosingBlockIfNeeded(); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pcrx->getPosition() + fl_BLOCK_STRUX_OFFSET + fl_BLOCK_STRUX_OFFSET); } else if(pView && pView->getPoint() > pcrx->getPosition()) { pView->_setPoint(pView->getPoint() + fl_BLOCK_STRUX_OFFSET + fl_BLOCK_STRUX_OFFSET); } _assertRunListIntegrity(); #if DEBUG if(getFirstContainer()) { UT_ASSERT(getFirstContainer()->getPrev() == NULL); } #endif return true; } /*! * Insert a table into the list of blocks */ fl_SectionLayout * fl_BlockLayout::doclistener_insertTable(const PX_ChangeRecord_Strux * pcrx, SectionType iType, PL_StruxDocHandle sdh, PL_ListenerId lid, void (* pfnBindHandles)(PL_StruxDocHandle sdhNew, PL_ListenerId lid, PL_StruxFmtHandle sfhNew)) { UT_ASSERT(iType == FL_SECTION_TABLE); _assertRunListIntegrity(); // Insert a section at the location given in the change record. // Everything from this point forward (to the next section) needs // to be re-parented to this new section. We also need to verify // that this insertion point is at the end of the block (and that // another block follows). This is because a section cannot // contain content. UT_ASSERT(pcrx); UT_ASSERT(pcrx->getType() == PX_ChangeRecord::PXT_InsertStrux); // // Not true always. eg Undo on a delete header/footer. We should detect this // and deal with it. // PT_DocPosition pos1; // // This is to clean the fragments // m_pDoc->getBounds(true,pos1); fl_SectionLayout* pSL = NULL; pSL = static_cast(static_cast(getSectionLayout())->insert(sdh,this,pcrx->getIndexAP(), FL_CONTAINER_TABLE)); // Must call the bind function to complete the exchange of handles // with the document (piece table) *** before *** anything tries // to call down into the document (like all of the view // listeners). PL_StruxFmtHandle sfhNew = static_cast(pSL); // // Don't bind to shadows // if(pfnBindHandles) { pfnBindHandles(sdh,lid,sfhNew); } // // increment the insertion point in the view. // FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pcrx->getPosition() + fl_BLOCK_STRUX_OFFSET); } else if(pView && pView->getPoint() > pcrx->getPosition()) { pView->_setPoint(pView->getPoint() + fl_BLOCK_STRUX_OFFSET); } // // OK that's it! // updateEnclosingBlockIfNeeded(); return pSL; } /*! * Insert a Frame after this block. */ fl_SectionLayout * fl_BlockLayout::doclistener_insertFrame(const PX_ChangeRecord_Strux * pcrx, SectionType iType, PL_StruxDocHandle sdh, PL_ListenerId lid, void (* pfnBindHandles)(PL_StruxDocHandle sdhNew, PL_ListenerId lid, PL_StruxFmtHandle sfhNew)) { UT_ASSERT(iType == FL_SECTION_FRAME); _assertRunListIntegrity(); // Insert a section at the location given in the change record. // Everything from this point forward (to the next section) needs // to be re-parented to this new section. We also need to verify // that this insertion point is at the end of the block (and that // another block follows). This is because a section cannot // contain content. UT_ASSERT(pcrx); UT_ASSERT(pcrx->getType() == PX_ChangeRecord::PXT_InsertStrux); // // Not true always. eg Undo on a delete header/footer. We should detect this // and deal with it. // PT_DocPosition pos1; // // This is to clean the fragments // m_pDoc->getBounds(true,pos1); fl_SectionLayout* pSL = NULL; pSL = static_cast(static_cast(getSectionLayout())->insert(sdh,this,pcrx->getIndexAP(), FL_CONTAINER_FRAME)); // Must call the bind function to complete the exchange of handles // with the document (piece table) *** before *** anything tries // to call down into the document (like all of the view // listeners). PL_StruxFmtHandle sfhNew = static_cast(pSL); // // Don't bind to shadows // if(pfnBindHandles) { pfnBindHandles(sdh,lid,sfhNew); } fl_ContainerLayout * pPrevCL = getPrev(); fp_Page * pPrevP = NULL; if(pPrevCL) { fp_Container * pPrevCon = pPrevCL->getFirstContainer(); if(pPrevCon) { pPrevP = pPrevCon->getPage(); } } // Create a Physical Container for this frame static_cast(pSL)->format(); getDocSectionLayout()->completeBreakSection(); // // increment the insertion point in the view. // FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pcrx->getPosition() + fl_BLOCK_STRUX_OFFSET); } else if(pView && pView->getPoint() > pcrx->getPosition()) { pView->_setPoint(pView->getPoint() + fl_BLOCK_STRUX_OFFSET); } // // OK that's it! // updateEnclosingBlockIfNeeded(); return pSL; } /*! Draw squiggles intersecting with Run \param pRun Run For all misspelled words in this run, call the run->drawSquiggle() method. */ void fl_BlockLayout::findSpellSquigglesForRun(fp_Run* pRun) { xxx_UT_DEBUGMSG(("fl_BlockLayout::findSpellSquigglesForRun\n")); UT_ASSERT(pRun->getType() == FPRUN_TEXT); fp_TextRun* pTextRun = (static_cast(pRun)); UT_sint32 runBlockOffset = pRun->getBlockOffset(); UT_sint32 runBlockEnd = runBlockOffset + pRun->getLength(); UT_sint32 iFirst, iLast; if (m_pSpellSquiggles->findRange(runBlockOffset, runBlockEnd, iFirst, iLast)) { UT_sint32 iStart = 0, iEnd; fl_PartOfBlock* pPOB; UT_sint32 i = iFirst; // The first POB may only be partially within the region. Clip // it if necessary. pPOB = m_pSpellSquiggles->getNth(i++); if (!pPOB->getIsIgnored()) { iStart = pPOB->getOffset(); iEnd = iStart + pPOB->getLength(); if (iStart < runBlockOffset) iStart = runBlockOffset; // Only draw if there's more than one POB. If there's only // one POB, it may also need clipping at the end (let the // code below handle it). if (iFirst != iLast) { pTextRun->drawSquiggle(iStart, iEnd - iStart,FL_SQUIGGLE_SPELL); } } // The ones in the middle don't need clipping. for (; i < iLast; i++) { pPOB = m_pSpellSquiggles->getNth(i); if (pPOB->getIsIgnored()) continue; iStart = pPOB->getOffset(); iEnd = iStart + pPOB->getLength(); pTextRun->drawSquiggle(iStart, iEnd - iStart,FL_SQUIGGLE_SPELL); } // The last POB may only be partially within the region. Clip // it if necessary. Note the load with iLast instead of i. pPOB = m_pSpellSquiggles->getNth(iLast); if (!pPOB->getIsIgnored()) { // Only load start if this POB is different from the first // one. if (iFirst != iLast) iStart = pPOB->getOffset(); iEnd = pPOB->getOffset() + pPOB->getLength(); if (iEnd > runBlockEnd) iEnd = runBlockEnd; pTextRun->drawSquiggle(iStart, iEnd - iStart,FL_SQUIGGLE_SPELL); } } } /*! * Draw all the grammar squiggles in the Block. */ void fl_BlockLayout::drawGrammarSquiggles(void) { fp_Run * pRun = getFirstRun(); while(pRun) { if(pRun->getType() == FPRUN_TEXT) { findGrammarSquigglesForRun(pRun); } pRun = pRun->getNextRun(); } } /*! Draw grammar squiggles intersecting with Run \param pRun Run For all incorrect grammar in this run, call the run->drawSquiggle() method. */ void fl_BlockLayout::findGrammarSquigglesForRun(fp_Run* pRun) { xxx_UT_DEBUGMSG(("fl_BlockLayout::findSpellSquigglesForRun\n")); UT_ASSERT(pRun->getType() == FPRUN_TEXT); fp_TextRun* pTextRun = (static_cast(pRun)); UT_sint32 runBlockOffset = pRun->getBlockOffset(); UT_sint32 runBlockEnd = runBlockOffset + pRun->getLength(); UT_sint32 iFirst, iLast; if (m_pGrammarSquiggles->findRange(runBlockOffset, runBlockEnd, iFirst, iLast,true)) { UT_sint32 iStart = 0, iEnd; fl_PartOfBlock* pPOB; UT_sint32 i = iFirst; // The first POB may only be partially within the region. Clip // it if necessary. pPOB = m_pGrammarSquiggles->getNth(i++); if (!pPOB->getIsIgnored() && !pPOB->isInvisible()) { iStart = pPOB->getOffset(); iEnd = iStart + pPOB->getLength(); if (iStart < runBlockOffset) iStart = runBlockOffset; // Only draw if there's more than one POB. If there's only // one POB, it may also need clipping at the end (let the // code below handle it). // if (iFirst != iLast) { pTextRun->drawSquiggle(iStart, iEnd - iStart,FL_SQUIGGLE_GRAMMAR); } } // The ones in the middle don't need clipping. for (; i < iLast; i++) { pPOB = m_pGrammarSquiggles->getNth(i); if (pPOB->getIsIgnored() || pPOB->isInvisible()) continue; iStart = pPOB->getOffset(); iEnd = iStart + pPOB->getLength(); pTextRun->drawSquiggle(iStart, iEnd - iStart,FL_SQUIGGLE_GRAMMAR); } // The last POB may only be partially within the region. Clip // it if necessary. Note the load with iLast instead of i. pPOB = m_pGrammarSquiggles->getNth(iLast); if (!pPOB->getIsIgnored() && !pPOB->isInvisible()) { // Only load start if this POB is different from the first // one. if (iFirst != iLast) iStart = pPOB->getOffset(); if(iStart < pTextRun->getBlockOffset()) iStart = pTextRun->getBlockOffset(); iEnd = pPOB->getOffset() + pPOB->getLength(); if (iEnd > runBlockEnd) iEnd = runBlockEnd; pTextRun->drawSquiggle(iStart, iEnd - iStart,FL_SQUIGGLE_GRAMMAR); } } } ////////////////////////////////////////////////////////////////// // Object-related stuff ////////////////////////////////////////////////////////////////// bool fl_BlockLayout::doclistener_populateObject(PT_BlockOffset blockOffset, const PX_ChangeRecord_Object * pcro) { _assertRunListIntegrity(); switch (pcro->getObjectType()) { case PTO_Image: { FG_Graphic* pFG = FG_Graphic::createFromChangeRecord(this, pcro); if (pFG == NULL) return false; UT_DEBUGMSG(("Populate:InsertObject:Image:\n")); _doInsertImageRun(blockOffset, pFG); return true; } case PTO_Field: UT_DEBUGMSG(("!!!Populate:InsertObject:Field: BlockOffset %d \n",blockOffset)); _doInsertFieldRun(blockOffset, pcro); return true; case PTO_Bookmark: UT_DEBUGMSG(("Populate:InsertBookmark:\n")); _doInsertBookmarkRun(blockOffset); return true; case PTO_Hyperlink: UT_DEBUGMSG(("Populate:InsertHyperlink:\n")); _doInsertHyperlinkRun(blockOffset); return true; case PTO_Math: UT_DEBUGMSG(("Populate:InsertMathML:\n")); _doInsertMathRun(blockOffset,pcro->getIndexAP(),pcro->getObjectHandle()); return true; default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; } _assertRunListIntegrity(); updateEnclosingBlockIfNeeded(); if(isHidden() == FP_HIDDEN_FOLDED) { collapse(); } } bool fl_BlockLayout::doclistener_insertObject(const PX_ChangeRecord_Object * pcro) { _assertRunListIntegrity(); PT_BlockOffset blockOffset = 0; switch (pcro->getObjectType()) { case PTO_Image: { UT_DEBUGMSG(("Edit:InsertObject:Image:\n")); blockOffset = pcro->getBlockOffset(); FG_Graphic* pFG = FG_Graphic::createFromChangeRecord(this, pcro); if (pFG == NULL) return false; _doInsertImageRun(blockOffset, pFG); break; } case PTO_Field: { UT_DEBUGMSG(("Edit:InsertObject:Field:\n")); blockOffset = pcro->getBlockOffset(); _doInsertFieldRun(blockOffset, pcro); break; } case PTO_Bookmark: { UT_DEBUGMSG(("Edit:InsertObject:Bookmark:\n")); blockOffset = pcro->getBlockOffset(); _doInsertBookmarkRun(blockOffset); break; } case PTO_Hyperlink: { UT_DEBUGMSG(("Edit:InsertObject:Hyperlink:\n")); blockOffset = pcro->getBlockOffset(); _doInsertHyperlinkRun(blockOffset); break; } case PTO_Math: { UT_DEBUGMSG(("Edit:InsertObject:Math:\n")); blockOffset = pcro->getBlockOffset(); _doInsertMathRun(blockOffset,pcro->getIndexAP(),pcro->getObjectHandle()); break; } default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; } m_iNeedsReformat = blockOffset; // // Update the offsets before the format (Where stuff gets calculated) // updateEnclosingBlockIfNeeded(); format(); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) pView->_setPoint(pcro->getPosition() + 1); else if(pView && pView->getPoint() > pcro->getPosition()) pView->_setPoint(pView->getPoint() + 1); // TODO: are objects always one wide? m_pSpellSquiggles->textInserted(blockOffset, 1); m_pGrammarSquiggles->textInserted(blockOffset, 1); _assertRunListIntegrity(); // // OK Now do the insertSpan for any TOC's that shadow this block. // if(!isNotTOCable() && !m_bIsTOC && m_bStyleInTOC) { UT_GenericVector vecBlocksInTOCs; if(m_pLayout->getMatchingBlocksFromTOCs(this, &vecBlocksInTOCs)) { UT_sint32 i = 0; for(i=0; i(vecBlocksInTOCs.getItemCount());i++) { fl_BlockLayout * pBL = vecBlocksInTOCs.getNthItem(i); pBL->doclistener_insertObject(pcro); } } else { m_bStyleInTOC = false; } } return true; } bool fl_BlockLayout::doclistener_deleteObject(const PX_ChangeRecord_Object * pcro) { _assertRunListIntegrity(); PT_BlockOffset blockOffset = 0; switch (pcro->getObjectType()) { case PTO_Image: { UT_DEBUGMSG(("Edit:DeleteObject:Image:\n")); blockOffset = pcro->getBlockOffset(); _delete(blockOffset, 1); break; } case PTO_Math: { UT_DEBUGMSG(("Edit:DeleteObject:Math:\n")); blockOffset = pcro->getBlockOffset(); _delete(blockOffset, 1); break; } case PTO_Field: { xxx_UT_DEBUGMSG(("Edit:DeleteObject:Field:\n")); blockOffset = pcro->getBlockOffset(); _delete(blockOffset, 1); if(m_pAutoNum) { m_pAutoNum->markAsDirty(); } break; } case PTO_Bookmark: { UT_DEBUGMSG(("Edit:DeleteObject:Bookmark:\n")); blockOffset = pcro->getBlockOffset(); _delete(blockOffset,1); break; } case PTO_Hyperlink: { UT_DEBUGMSG(("Edit:DeleteObject:Hyperlink:\n")); blockOffset = pcro->getBlockOffset(); _delete(blockOffset,1); break; } default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; } updateEnclosingBlockIfNeeded(); m_iNeedsReformat = blockOffset; format(); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { pView->_resetSelection(); pView->_setPoint(pcro->getPosition()); } else if(pView && pView->getPoint() > pcro->getPosition()) pView->_setPoint(pView->getPoint() - 1); // TODO: are objects always one wide? if(m_pSpellSquiggles) m_pSpellSquiggles->textDeleted(blockOffset, 1); if(m_pGrammarSquiggles) m_pGrammarSquiggles->textDeleted(blockOffset, 1); _assertRunListIntegrity(); // // OK Now do the deleteObject for any TOC's that shadow this block. // if(!isNotTOCable() && !m_bIsTOC && m_bStyleInTOC && m_pLayout) { UT_GenericVector vecBlocksInTOCs; if( m_pLayout->getMatchingBlocksFromTOCs(this, &vecBlocksInTOCs)) { UT_sint32 i = 0; for(i=0; i(vecBlocksInTOCs.getItemCount());i++) { fl_BlockLayout * pBL = vecBlocksInTOCs.getNthItem(i); pBL->doclistener_deleteObject(pcro); } } else { m_bStyleInTOC = false; } } return true; } bool fl_BlockLayout::doclistener_changeObject(const PX_ChangeRecord_ObjectChange * pcroc) { _assertRunListIntegrity(); PT_BlockOffset blockOffset = 0; switch (pcroc->getObjectType()) { case PTO_Bookmark: case PTO_Hyperlink: return true; case PTO_Image: { xxx_UT_DEBUGMSG(("Edit:ChangeObject:Image:\n")); blockOffset = pcroc->getBlockOffset(); fp_Run* pRun = m_pFirstRun; while (pRun) { if (pRun->getBlockOffset() == blockOffset) { if(pRun->getType()!= FPRUN_IMAGE) { UT_DEBUGMSG(("!!! run type NOT OBJECT, instead = %d !!!! \n",pRun->getType())); while(pRun && pRun->getType() == FPRUN_FMTMARK) { pRun = pRun->getNextRun(); } } if(!pRun || pRun->getType() != FPRUN_IMAGE) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; } fp_ImageRun* pImageRun = static_cast(pRun); if(!isHdrFtr()) { pImageRun->clearScreen(); } pImageRun->lookupProperties(); goto done; } pRun = pRun->getNextRun(); } return false; } case PTO_Field: { xxx_UT_DEBUGMSG(("Edit:ChangeObject:Field:\n")); blockOffset = pcroc->getBlockOffset(); fp_Run* pRun = m_pFirstRun; while (pRun) { if (pRun->getBlockOffset() == blockOffset && (pRun->getType()!= FPRUN_FMTMARK)) { if(pRun->getType()!= FPRUN_FIELD) { UT_DEBUGMSG(("!!! run type NOT OBJECT, instead = %d !!!! \n",pRun->getType())); while(pRun && pRun->getType() == FPRUN_FMTMARK) { pRun = pRun->getNextRun(); } } if(!pRun || pRun->getType() != FPRUN_FIELD) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; } fp_FieldRun* pFieldRun = static_cast(pRun); if(!isHdrFtr()) { pFieldRun->clearScreen(); } pFieldRun->lookupProperties(); goto done; } pRun = pRun->getNextRun(); } return false; } case PTO_Math: { UT_DEBUGMSG(("Edit:ChangeObject:Math:\n")); blockOffset = pcroc->getBlockOffset(); fp_Run* pRun = m_pFirstRun; while (pRun) { if (pRun->getBlockOffset() == blockOffset && (pRun->getType()!= FPRUN_FMTMARK)) { if(pRun->getType()!= FPRUN_MATH) { UT_DEBUGMSG(("!!! run type NOT OBJECT, instead = %d !!!! \n",pRun->getType())); while(pRun && pRun->getType() == FPRUN_FMTMARK) { pRun = pRun->getNextRun(); } } if(!pRun || pRun->getType() != FPRUN_MATH) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; } fp_MathRun* pMathRun = static_cast(pRun); if(!isHdrFtr()) { pMathRun->clearScreen(); } pMathRun->lookupProperties(); goto done; } pRun = pRun->getNextRun(); } return false; } default: UT_ASSERT(0); return false; } done: m_iNeedsReformat = blockOffset; format(); _assertRunListIntegrity(); return true; } bool fl_BlockLayout::recalculateFields(UT_uint32 iUpdateCount) { _assertRunListIntegrity(); bool bResult = false; fp_Run* pRun = m_pFirstRun; while (pRun) { if (pRun->getType() == FPRUN_FIELD) { fp_FieldRun* pFieldRun = static_cast(pRun); /* TODO: Write list (fl_autonum, I think) code adding a member * bool indicating if the list structure has changed since the last field recalc and * setting it to true whenever such a change occurs (ie, adding an item, deleting one, whatever). * Then here you can * if(pFieldRun->getFieldType() == FPFIELD_list_label) * get the list to which it belongs, and get that list's * m_bDirtyForFieldRecalc which is set true after any change * to the list structure. Thus only recalc if needed. Finally, after the loop, tell the list to reset that member to false. * However, the possible down side to this is that you need to recalc the entire list and not just the individual fields, because otherwise * you risk having an only partially recalced list being left alone because it's marked clean... in retrospect I think you may need to * move this sort of optimization up to the DocSectionLayout redraw code, rather than having it here at the block level where it might (not 100% sure but might) * be waaay overcalculated (having a 1-1 block-li situation. In fl_DocSectionLayout::redrawUpdate, you just make a special case for if any block encountered has * an autonum (as opposed to any old field), and if so you do this (recalc the whole list and mark it no longer dirty for recalc), and then subsequent blocks with * part of the same autonum will pass over recalculating it. You may still need (or want) a new method in BL to recalculateAutoNums, called separately from * recalculateFields (which ignores fields from autonums), to ease still recalculating non-autonum fields from the DSL code. */ xxx_UT_DEBUGMSG(("DOM: %d %d\n", pFieldRun==0, pFieldRun->needsFrequentUpdates())); if((!iUpdateCount || !pFieldRun->needsFrequentUpdates() || !(iUpdateCount % pFieldRun->needsFrequentUpdates()))) { const bool bSizeChanged = pFieldRun->calculateValue(); bResult |= bSizeChanged; } } // else if(pRun->isField() == true) // { // bResult = pRun->getField()->update(); //} pRun = pRun->getNextRun(); } _assertRunListIntegrity(); return bResult; } bool fl_BlockLayout::findNextTabStop( double iStartX, double iMaxX, double& iPosition, eTabType & iType, eTabLeader &iLeader ) { #ifdef DEBUG double iMinLeft = m_iLeftMargin; if(m_iTextIndent < 0) iMinLeft += m_iTextIndent; UT_ASSERT(iStartX >= iMinLeft); #endif UT_uint32 iCountTabs = m_vecTabs.getItemCount(); UT_uint32 i; if(isContainedByTOC()) { iCountTabs = 0; } iLeader = FL_LEADER_NONE; for (i=0; igetPosition() > iMaxX) { break; } if (pTab->getPosition() > iStartX) { if(m_iDomDirection == UT_BIDI_RTL) { if(m_iRightMargin > iStartX && m_iRightMargin < pTab->getPosition()) { iPosition = m_iRightMargin; iType = FL_TAB_RIGHT; iLeader = FL_LEADER_NONE; } else { iPosition = pTab->getPosition(); iType = pTab->getType(); iLeader = pTab->getLeader(); } } else { if(m_iLeftMargin > iStartX && m_iLeftMargin < pTab->getPosition()) { iPosition = m_iLeftMargin; iType = FL_TAB_LEFT; iLeader = FL_LEADER_NONE; } else { iPosition = pTab->getPosition(); iType = pTab->getType(); iLeader = pTab->getLeader(); } } return true; } } // now, handle the default tabs double iMin; if(m_iDomDirection == UT_BIDI_RTL) iMin = m_iRightMargin; else iMin = m_iLeftMargin; if (iMin > iStartX) { iPosition = iMin; if(m_iDomDirection == UT_BIDI_RTL) iType = FL_TAB_RIGHT; else iType = FL_TAB_LEFT; return true; } UT_ASSERT(m_iDefaultTabInterval > 0); // mathematical approach const double iPos = (iStartX / m_iDefaultTabInterval + 1) * m_iDefaultTabInterval; if(iPos > iMaxX) iPosition = iMaxX; else iPosition = iPos; if(m_iDomDirection == UT_BIDI_RTL) iType = FL_TAB_RIGHT; else iType = FL_TAB_LEFT; UT_ASSERT(iPos > iStartX); return true; } bool fl_BlockLayout::findPrevTabStop( double iStartX, double iMaxX, double& iPosition, eTabType & iType, eTabLeader &iLeader ) { #ifdef DEBUG double iMinLeft = m_iLeftMargin; if(m_iTextIndent < 0) iMinLeft += m_iTextIndent; UT_ASSERT(iStartX >= iMinLeft); #endif UT_uint32 iCountTabs = m_vecTabs.getItemCount(); UT_uint32 i; iLeader = FL_LEADER_NONE; for (i=0; i(m_vecTabs.getNthItem(i)); UT_ASSERT(pTab); if (pTab->getPosition() > iMaxX) { break; } if (pTab->getPosition() > iStartX) { pTab = static_cast(m_vecTabs.getNthItem(i>0?i-1:0)); UT_ASSERT(pTab); if(m_iDomDirection == UT_BIDI_RTL) { if(m_iRightMargin > pTab->getPosition() && m_iRightMargin < iStartX) { iPosition = m_iRightMargin; iType = FL_TAB_RIGHT; iLeader = FL_LEADER_NONE; } else { iPosition = pTab->getPosition(); iType = pTab->getType(); iLeader = pTab->getLeader(); } } else { if(m_iLeftMargin > pTab->getPosition() && m_iLeftMargin < iStartX) { iPosition = m_iLeftMargin; iType = FL_TAB_LEFT; iLeader = FL_LEADER_NONE; } else { iPosition = pTab->getPosition(); iType = pTab->getType(); iLeader = pTab->getLeader(); } } return true; } } // the special case where there is no tabstop after the tab // but there is something before it if(iCountTabs > 0 && i == iCountTabs) { xxx_UT_DEBUGMSG(("found tabstop indx=%d\n", iCountTabs - 1)); fl_TabStop* pTab = static_cast(m_vecTabs.getNthItem(iCountTabs - 1)); UT_ASSERT(pTab); iPosition = pTab->getPosition(); iType = pTab->getType(); iLeader = pTab->getLeader(); return true; } // now, handle the default tabs double iMin; if(m_iDomDirection == UT_BIDI_RTL) iMin = m_iRightMargin; else iMin = m_iLeftMargin; if (iMin >= iStartX) { iPosition = iMin; if(m_iDomDirection == UT_BIDI_RTL) iType = FL_TAB_RIGHT; else iType = FL_TAB_LEFT; return true; } UT_ASSERT(m_iDefaultTabInterval > 0); // mathematical approach // the -1 is to ensure we do not get iStartX const double iPos = ((iStartX - 1)/ m_iDefaultTabInterval) * m_iDefaultTabInterval; iPosition = iPos; if(m_iDomDirection == UT_BIDI_RTL) iType = FL_TAB_RIGHT; else iType = FL_TAB_LEFT; UT_ASSERT(iPos <= iStartX); return true; } bool fl_BlockLayout::s_EnumTabStops( void * myThis, UT_uint32 k, fl_TabStop *pTabInfo) { // a static function fl_BlockLayout * pBL = static_cast(myThis); UT_uint32 iCountTabs = pBL->m_vecTabs.getItemCount(); if (k >= iCountTabs) return false; fl_TabStop * pTab = static_cast(pBL->m_vecTabs.getNthItem(k)); *pTabInfo = *pTab; return true; } void fl_BlockLayout::setSectionLayout(fl_SectionLayout* pSectionLayout) { // If we are setting the new section layout, this block // shouldn't already have a section. If we are clearing // it, then it should already have a section. if (pSectionLayout == NULL) { UT_ASSERT(m_pSectionLayout != NULL); } m_pSectionLayout = pSectionLayout; if(pSectionLayout) m_bIsHdrFtr = (pSectionLayout->getType() == FL_SECTION_HDRFTR); } ////////////////////////////////////////////////////////////////// // FmtMark-related stuff ////////////////////////////////////////////////////////////////// bool fl_BlockLayout::doclistener_insertFmtMark(const PX_ChangeRecord_FmtMark* pcrfm) { _assertRunListIntegrity(); PT_BlockOffset blockOffset = pcrfm->getBlockOffset(); xxx_UT_DEBUGMSG(("Edit:InsertFmtMark [blockOffset %ld]\n",blockOffset)); fp_FmtMarkRun * pNewRun = new fp_FmtMarkRun(this, blockOffset); UT_ASSERT(pNewRun); _doInsertRun(pNewRun); // TODO is it necessary to force a reformat when inserting a // FmtMark -- no fmt mark has no width, so it cannot change layout FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) pView->_setPoint(pcrfm->getPosition()); if (pView) { pView->_resetSelection(); // if(!isHdrFtr()) // pView->notifyListeners(AV_CHG_FMTCHAR); } m_iNeedsReformat = blockOffset; format(); _assertRunListIntegrity(); return true; } bool fl_BlockLayout::doclistener_deleteFmtMark(const PX_ChangeRecord_FmtMark* pcrfm) { UT_return_val_if_fail( m_pLayout, false ); _assertRunListIntegrity(); PT_BlockOffset blockOffset = pcrfm->getBlockOffset(); xxx_UT_DEBUGMSG(("Edit:DeleteFmtMark: [blockOffset %ld]\n",blockOffset)); // we can't use the regular _delete() since we are of length zero _deleteFmtMark(blockOffset); // TODO is it necessary to force a reformat when deleting a FmtMark m_iNeedsReformat = blockOffset; format(); updateEnclosingBlockIfNeeded(); FV_View* pView = getView(); PT_DocPosition posEOD =0; m_pDoc->getBounds(true,posEOD); if (pView && (pView->isActive() || pView->isPreview())) { pView->_resetSelection(); if(posEOD >= pcrfm->getPosition()) { pView->_setPoint(pcrfm->getPosition()); // if(!isHdrFtr()) // pView->notifyListeners(AV_CHG_FMTCHAR); } else { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } } _assertRunListIntegrity(); return true; } /*! Delete FmtMarkRun \param blockOffset Offset of Run to delete \return True Deleting a FmtMarkRun is a special version of _delete() since a FmtMarkRun has a length of zero. \fixme FmtMarkRun should not have a length of zero - jskov */ bool fl_BlockLayout::_deleteFmtMark(PT_BlockOffset blockOffset) { fp_Run* pRun = m_pFirstRun; while (pRun) { UT_uint32 iRunBlockOffset = pRun->getBlockOffset(); // Remember where we're going, since this run may get axed fp_Run* pNextRun = pRun->getNextRun(); if ( (iRunBlockOffset == blockOffset) && (pRun->getType() == FPRUN_FMTMARK) ) { fp_Line* pLine = pRun->getLine(); UT_ASSERT(pLine); // Remove Run from line if(pLine) { pLine->removeRun(pRun); } // Unlink and delete it if (m_pFirstRun == pRun) { m_pFirstRun = pRun->getNextRun(); } pRun->unlinkFromRunList(); delete pRun; if (!m_pFirstRun) { // By the time we get to deleting anything from a block, // it should already have the necessary EOP in place. UT_ASSERT(UT_SHOULD_NOT_HAPPEN); _insertEndOfParagraphRun(); } // I don't believe that we need to keep looping at this point. // We should not ever have two adjacent FmtMarks.... UT_ASSERT(!pNextRun || pNextRun->getType() != FPRUN_FMTMARK); break; } pRun = pNextRun; } return true; } bool fl_BlockLayout::doclistener_changeFmtMark(const PX_ChangeRecord_FmtMarkChange * pcrfmc) { _assertRunListIntegrity(); PT_BlockOffset blockOffset = pcrfmc->getBlockOffset(); xxx_UT_DEBUGMSG(("Edit:ChangeFmtMark: [blockOffset %ld]\n",blockOffset)); fp_Run* pRun = m_pFirstRun; while (pRun) { if (pRun->getBlockOffset() == blockOffset) { UT_ASSERT(pRun->getType() == FPRUN_FMTMARK); pRun->lookupProperties(); if(!isHdrFtr()) { pRun->clearScreen(); } break; } pRun = pRun->getNextRun(); } // We need a reformat for blocks that only contain a format mark. // ie. no next just a carrige return. m_iNeedsReformat = blockOffset; format(); updateEnclosingBlockIfNeeded(); FV_View* pView = getView(); if (pView && (pView->isActive() || pView->isPreview())) { // if(!isHdrFtr()) // pView->notifyListeners(AV_CHG_FMTCHAR); } _assertRunListIntegrity(); return true; } /*! Recheck ignored words For all misspelled words in this run, call the run->drawSquiggle() method. */ void fl_BlockLayout::recheckIgnoredWords(void) { // buffer to hold text UT_GrowBuf pgb(1024); bool bRes = getBlockBuf(&pgb); UT_ASSERT(bRes); const UT_UCSChar* pBlockText = reinterpret_cast(pgb.getPointer(0)); bool bUpdate = m_pSpellSquiggles->recheckIgnoredWords(pBlockText); // Update screen if any words squiggled FV_View* pView = getView(); if (bUpdate && pView) { pView->updateScreen(); } } //////////////////////////////////////////////////////////////////////////// //List Item Stuff /////////////////////////////////////////////////////////////////////////// XML_Char* fl_BlockLayout::getListStyleString( FL_ListType iListType) { XML_Char* style; // These strings match piece table styles and should not be // internationalized UT_sint32 nlisttype = static_cast(iListType); if(nlisttype < 0 || nlisttype >= static_cast(NOT_A_LIST)) style = static_cast(NULL); else { fl_AutoLists al; style = const_cast(al.getXmlList(nlisttype)); } return style; } FL_ListType fl_BlockLayout::getListTypeFromStyle( const XML_Char* style) { FL_ListType lType = NOT_A_LIST; UT_uint32 j; fl_AutoLists al; UT_uint32 size_xml_lists = al.getXmlListsSize(); for(j=0; j < size_xml_lists; j++) { if( UT_XML_strcmp(style,al.getXmlList(j))==0) break; } if(j < size_xml_lists) lType = static_cast(j); return lType; } char * fl_BlockLayout::getFormatFromListType( FL_ListType iListType) { UT_sint32 nlisttype = static_cast(iListType); char * format = NULL; if(nlisttype < 0 || nlisttype >= static_cast(NOT_A_LIST)) return format; fl_AutoLists al; format = const_cast(al.getFmtList(nlisttype)); return format; } FL_ListType fl_BlockLayout::decodeListType(char * listformat) { FL_ListType iType = NOT_A_LIST; UT_uint32 j; fl_AutoLists al; UT_uint32 size_fmt_lists = al.getFmtListsSize(); for(j=0; j < size_fmt_lists; j++) { if( strstr(listformat,al.getFmtList(j))!=NULL) break; } if(j < size_fmt_lists) iType = static_cast(j); return iType; } FL_ListType fl_BlockLayout::getListType(void) { if(isListItem()==false) { return NOT_A_LIST; } else if(getAutoNum()) { return getAutoNum()->getType(); } else { return NOT_A_LIST; } } void fl_BlockLayout::remItemFromList(void) { XML_Char lid[15], buf[5]; UT_uint32 id; bool bRet; UT_GenericVector vp; if( m_bListLabelCreated == true) { m_bListLabelCreated = false; FV_View* pView = getView(); UT_ASSERT(pView); UT_uint32 currLevel = getLevel(); UT_ASSERT(currLevel > 0); currLevel =0; // was currLevel--; sprintf(buf, "%i", currLevel); setStopping(false); fl_BlockLayout * pNext = getNextBlockInDocument(); if (currLevel == 0) { id = 0; } else { id = getAutoNum()->getParent()->getID(); pNext = getPreviousList( id); } sprintf(lid, "%i", id); setStopping(false); format(); // // Set formatiing to match the next paragraph if it exists // const XML_Char ** props = NULL; if(pNext != NULL) { pNext->getListPropertyVector( &vp); UT_uint32 countp = vp.getItemCount() + 1; UT_uint32 i; props = static_cast(UT_calloc(countp, sizeof(XML_Char *))); for(i=0; i 0 && UT_XML_strcmp(props[i-1], "text-indent")==0) { props[i] = "0.0000in"; } else { props[i] = vp.getNthItem(i); } } props[i] = static_cast(NULL); } else { getListPropertyVector( &vp); UT_uint32 countp = vp.getItemCount() + 1; UT_uint32 i; props = static_cast(UT_calloc(countp, sizeof(XML_Char *))); for(i=0; i 0 && UT_XML_strcmp(props[i-1], "text-indent")==0) { props[i] = "0.0000in"; } else { props[i] = vp.getNthItem(i); } } props[i] = static_cast(NULL); } if (currLevel == 0) { #ifndef __MRC__ const XML_Char * attribs[] = { "listid", lid, "level", buf, NULL, NULL }; #else const XML_Char * attribs[] = { "listid", NULL, "level", NULL, NULL, NULL }; attribs [1] = lid; attribs [3] = buf; #endif bRet = m_pDoc->changeStruxFmt(PTC_AddFmt, getPosition(), getPosition(), attribs, props, PTX_Block); m_bListItem = false; } else { #ifndef __MRC__ const XML_Char * attribs[] = { "listid", lid, "level", buf,NULL,NULL }; #else const XML_Char * attribs[] = { "listid", NULL, "level", NULL, NULL, NULL }; attribs [1] = lid; attribs [3] = buf; #endif bRet = m_pDoc->changeStruxFmt(PTC_AddFmt,getPosition(), getPosition(), attribs, props, PTX_Block); m_pDoc->listUpdate(getStruxDocHandle()); } //format(); // pView->AV_View::notifyListeners(AV_CHG_FMTBLOCK); // pView->_fixInsertionPointCoords(); FREEP(props); } } /*! * Start a list with the paragraph definition container in the style defined by "style" \param const XML_CHar * style the name of the paragraph style for this block. */ void fl_BlockLayout::StartList( const XML_Char * style, PL_StruxDocHandle prevSDH) { // // Starts a new list at the current block with list style style all other // attributes and properties are the default values // FL_ListType lType; PD_Style* pStyle = 0; const XML_Char* szDelim = 0; const XML_Char* szDec = 0; const XML_Char* szStart = 0; const XML_Char* szAlign = 0; const XML_Char* szIndent = 0;; const XML_Char* szFont = 0; const XML_Char* szListStyle = 0;; UT_uint32 startv, level, currID; // TODO -- this mixture of float and double is a mess, we should // either use double throughout or float float fAlign, fIndent; m_pDoc->getStyle(static_cast(style), &pStyle); if (pStyle) { xxx_UT_DEBUGMSG(("SEVIOR: Found list of style %s \n",style)); // Use the props in the style pStyle->getProperty(static_cast("list-delim"), szDelim); pStyle->getProperty(static_cast("list-decimal"), szDec); pStyle->getProperty(static_cast("start-value"), szStart); if(m_iDomDirection == UT_BIDI_RTL) pStyle->getProperty(static_cast("margin-right"), szAlign); else pStyle->getProperty(static_cast("margin-left"), szAlign); pStyle->getProperty(static_cast("text-indent"), szIndent); pStyle->getProperty(static_cast("field-font"), szFont); pStyle->getProperty(static_cast("list-style"), szListStyle); if (szStart) startv = atoi(szStart); else startv = 1; if (szAlign) fAlign = static_cast(UT_convertToInches(szAlign)); else fAlign = static_cast(LIST_DEFAULT_INDENT); if (szIndent) fIndent = static_cast(UT_convertToInches(szIndent)); else fIndent = static_cast(-LIST_DEFAULT_INDENT_LABEL); if(!szFont) { szFont = "Times New Roman"; UT_ASSERT(0); } double dLeft; if(m_iDomDirection == UT_BIDI_LTR) dLeft = UT_convertToInches(getProperty("margin-left",true)); else dLeft = UT_convertToInches(getProperty("margin-right",true)); fAlign += static_cast(dLeft); } else { xxx_UT_DEBUGMSG(("SEVIOR: Could NOT find list of style %s \n",style)); szDelim = "%L"; startv = 1; szDec = "."; fAlign = static_cast(LIST_DEFAULT_INDENT); fIndent = static_cast(-LIST_DEFAULT_INDENT_LABEL); } UT_uint32 count = m_pDoc->getListsCount(); UT_uint32 j = 0; bool bFound = false; fl_AutoNum * pPrev = NULL; if(prevSDH) { for(j=0; j< count && !bFound; j++) { pPrev = m_pDoc->getNthList(j); if(pPrev->isItem(prevSDH)) { bFound = true; } } } if(prevSDH == NULL || !bFound) { if (m_pAutoNum) { level = m_pAutoNum->getLevel(); currID = m_pAutoNum->getID(); } else { level = 0; currID = 0; } level++; fAlign *= static_cast(level); } else { currID = pPrev->getID(); level = pPrev->getLevel(); level++; } lType = getListTypeFromStyle(szListStyle); StartList( lType, startv,szDelim, szDec, szFont, fAlign, fIndent, currID,level); } void fl_BlockLayout::getListAttributesVector(UT_GenericVector * va) { // // This function fills the vector va with list attributes // UT_uint32 count=0,level; const XML_Char * style = NULL; const XML_Char * lid = NULL; static XML_Char buf[5]; const PP_AttrProp * pBlockAP = NULL; getAP(pBlockAP); pBlockAP->getAttribute(PT_STYLE_ATTRIBUTE_NAME,style); pBlockAP->getAttribute(static_cast(PT_LISTID_ATTRIBUTE_NAME),lid); if(getAutoNum()) { level = getAutoNum()->getLevel(); } else { level = 0; } sprintf(buf,"%i",level); // pBlockAP->getAttribute("level",buf); if(lid != NULL) { va->addItem("listid"); va->addItem(lid); count++; } if(buf != NULL) { va->addItem("level"); va->addItem(buf); count++; } if(style != NULL) { va->addItem(PT_STYLE_ATTRIBUTE_NAME); va->addItem(style); count++; } if(count == 0) { va->addItem( NULL); } } void fl_BlockLayout::getListPropertyVector(UT_GenericVector* vp) { // // This function fills the vector vp with list properties. All vector // quantities are const XML_Char * // UT_uint32 count=0; const XML_Char * pszStart = getProperty("start-value",true); const XML_Char * lDelim = getProperty("list-delim",true); const XML_Char * lDecimal = getProperty("list-decimal",true); const XML_Char * pszAlign; if(m_iDomDirection == UT_BIDI_RTL) pszAlign = getProperty("margin-right",true); else pszAlign = getProperty("margin-left",true); const XML_Char * pszIndent = getProperty("text-indent",true); const XML_Char * fFont = getProperty("field-font",true); const XML_Char * pszListStyle = getProperty("list-style",true); if(pszStart != NULL) { vp->addItem("start-value"); vp->addItem(pszStart); } if(pszAlign != NULL) { if(m_iDomDirection == UT_BIDI_RTL) vp->addItem("margin-right"); else vp->addItem("margin-left"); vp->addItem(pszAlign); count++; } if(pszIndent != NULL) { vp->addItem("text-indent"); vp->addItem(pszIndent); count++; } if(lDelim != NULL) { vp->addItem("list-delim"); vp->addItem(lDelim); count++; } if(lDecimal != NULL) { vp->addItem("list-decimal"); vp->addItem(lDecimal); count++; } if(fFont != NULL) { vp->addItem("field-font"); vp->addItem(fFont); count++; } if(pszListStyle != NULL) { vp->addItem("list-style"); vp->addItem(pszListStyle); count++; } if(count == 0) { vp->addItem( NULL); } } void fl_BlockLayout::StartList( FL_ListType lType, UT_uint32 start,const XML_Char * lDelim, const XML_Char * lDecimal, const XML_Char * fFont, float Align, float indent, UT_uint32 iParentID, UT_uint32 curlevel ) { // // Starts a new list at the current block with all the options // XML_Char lid[15], pszAlign[20], pszIndent[20],buf[20],pid[20],pszStart[20]; XML_Char * style = getListStyleString(lType); bool bRet; UT_uint32 id=0; UT_GenericVector vp,va; fl_AutoNum * pAutoNum; const PP_AttrProp * pBlockAP = NULL; const XML_Char * szLid=NULL; getAP(pBlockAP); bool bGetPrevAuto = true; if (!pBlockAP || !pBlockAP->getAttribute(PT_LISTID_ATTRIBUTE_NAME, szLid)) szLid = NULL; if (szLid) id = atoi(szLid); else { id = 0; bGetPrevAuto = false; } FV_View* pView = getView(); UT_ASSERT(pView); if(bGetPrevAuto) { pAutoNum = m_pDoc->getListByID(id); UT_DEBUGMSG(("SEVIOR: found autonum %x from id %d \n",pAutoNum,id)); if(pAutoNum != NULL) { m_pAutoNum = pAutoNum; m_bListItem = true; UT_DEBUGMSG(("Found list of id %d \n",id)); listUpdate(); } } UT_return_if_fail(m_pDoc); id = m_pDoc->getUID(UT_UniqueId::List); sprintf(lid, "%i", id); sprintf(pid, "%i", iParentID); sprintf(buf, "%i", curlevel); sprintf(pszStart,"%i",start); UT_XML_strncpy( pszAlign, sizeof(pszAlign), UT_convertInchesToDimensionString(DIM_IN, Align, 0)); UT_XML_strncpy( pszIndent, sizeof(pszIndent), UT_convertInchesToDimensionString(DIM_IN, indent, 0)); va.addItem("listid"); va.addItem(lid); va.addItem("parentid"); va.addItem(pid); va.addItem("level"); va.addItem(buf); vp.addItem("start-value"); vp.addItem(pszStart); if(m_iDomDirection == UT_BIDI_RTL) vp.addItem("margin-right"); else vp.addItem("margin-left"); vp.addItem(pszAlign); vp.addItem("text-indent"); vp.addItem(pszIndent); vp.addItem("field-font"); vp.addItem(fFont); vp.addItem("list-style"); vp.addItem(style); xxx_UT_DEBUGMSG(("SEVIOR: Starting List with font %s \n",fFont)); pAutoNum = new fl_AutoNum(id, iParentID, lType, start, lDelim, lDecimal, m_pDoc, getView()); if (!pAutoNum) { // TODO Out of Mem. } m_pDoc->addList(pAutoNum); pAutoNum->fixHierarchy(); UT_uint32 counta = va.getItemCount() + 1; UT_uint32 countp = vp.getItemCount() + 1; UT_uint32 i; const XML_Char ** attribs = static_cast(UT_calloc(counta, sizeof(XML_Char *))); for(i=0; i(NULL); const XML_Char ** props = static_cast(UT_calloc(countp, sizeof(XML_Char *))); for(i=0; i(NULL); setStarting( false); bRet = m_pDoc->changeStruxFmt(PTC_AddFmt, getPosition(), getPosition(), attribs, props, PTX_Block); m_pDoc->listUpdate(getStruxDocHandle()); FREEP(attribs); FREEP(props); } void fl_BlockLayout::StopListInBlock(void) { // // Stops the list in the current block // static XML_Char lid[15],pszlevel[5]; bool bRet; UT_uint32 id, level; UT_GenericVector vp; FV_View* pView = getView(); UT_ASSERT(pView); bool bHasStopped = m_pDoc->hasListStopped(); if(getAutoNum()== NULL || bHasStopped) { return; // this block has already been processed } m_pDoc->setHasListStopped(true); PT_DocPosition offset = pView->getPoint() - getPosition(); fl_BlockLayout * pPrev, * pNext; if (getAutoNum()->getParent()) { id = getAutoNum()->getParent()->getID(); level = getAutoNum()->getParent()->getLevel(); } else { id = 0; level = 0; } sprintf(lid, "%i", id); setStopping(false); // // Set formatting to match the next paragraph if it exists // const XML_Char ** props = NULL; const XML_Char * szAlign, * szIndent; pPrev = getPrevBlockInDocument(); pNext = getNextBlockInDocument(); if (id != 0) { UT_ASSERT(pPrev); // TMN: Is an assert appropriate here? //First, look for block in list bool bmatch = false; bmatch = static_cast(pPrev->isListItem() && pPrev->getLevel() == level && pPrev->getAutoNum()->getID() == id); while (pPrev && !bmatch) { pPrev = pPrev->getPrevBlockInDocument(); if (pPrev && pPrev->isListItem()) bmatch = static_cast(pPrev->getLevel() == level && pPrev->getAutoNum()->getID() == id); } while (pNext && !bmatch) { pNext = pNext->getNextBlockInDocument(); if (pNext && pNext->isListItem()) bmatch = static_cast(pNext->getLevel() == level && pNext->getAutoNum()->getID() == id); } if (pPrev) pPrev->getListPropertyVector( &vp); else if (pNext) pNext->getListPropertyVector( &vp); else { // We have a problem FL_ListType newType; PD_Style * pStyle; float fAlign, fIndent; XML_Char align[30], indent[30]; newType = getAutoNum()->getParent()->getType(); m_pDoc->getStyle(static_cast(getListStyleString(newType)), &pStyle); if (pStyle) { if(m_iDomDirection == UT_BIDI_RTL) pStyle->getProperty(static_cast("margin-right"), szAlign); else pStyle->getProperty(static_cast("margin-left"), szAlign); pStyle->getProperty(static_cast("text-indent"), szIndent); fAlign = static_cast(UT_convertToInches(szAlign)); fAlign *= level; UT_XML_strncpy( align, sizeof(align), UT_convertInchesToDimensionString(DIM_IN, fAlign, 0)); sprintf(indent, "%s", szIndent); } else { fAlign = static_cast(LIST_DEFAULT_INDENT) * level; fIndent = static_cast(-LIST_DEFAULT_INDENT_LABEL); UT_XML_strncpy( align, sizeof(align), UT_convertInchesToDimensionString(DIM_IN, fAlign, 0)); UT_XML_strncpy( indent, sizeof(indent), UT_convertInchesToDimensionString(DIM_IN, fIndent, 0)); } if(m_iDomDirection == UT_BIDI_RTL) vp.addItem("margin-right"); else vp.addItem("margin-left"); vp.addItem(align); vp.addItem("text-indent"); vp.addItem(indent); } } else { // Find the last non-list item and set alignment + indent while (pPrev && pPrev->isListItem()) pPrev = pPrev->getPrevBlockInDocument(); while (pNext && pNext->isListItem()) pNext = pNext->getNextBlockInDocument(); if (pPrev) { if(m_iDomDirection == UT_BIDI_RTL) szAlign = pPrev->getProperty("margin-right", true); else szAlign = pPrev->getProperty("margin-left", true); szIndent = pPrev->getProperty("text-indent", true); } else if (pNext) { if(m_iDomDirection == UT_BIDI_RTL) szAlign = pNext->getProperty("margin-right", true); else szAlign = pNext->getProperty("margin-left", true); szIndent = pNext->getProperty("text-indent", true); } else { szAlign = "0.0000in"; szIndent = "0.0000in"; } if(m_iDomDirection == UT_BIDI_RTL) vp.addItem("margin-right"); else vp.addItem("margin-left"); vp.addItem(szAlign); vp.addItem("text-indent"); vp.addItem(szIndent); } UT_uint32 countp = vp.getItemCount() + 1; UT_uint32 i; props = static_cast(UT_calloc(countp, sizeof(XML_Char *))); for (i = 0; i < vp.getItemCount(); i++) { props[i] = vp.getNthItem(i); } props[i] = NULL; sprintf(pszlevel, "%i", level); if (id == 0) { const XML_Char * pListAttrs[10]; pListAttrs[0] = "listid"; pListAttrs[1] = NULL; pListAttrs[2] = "parentid"; pListAttrs[3] = NULL; pListAttrs[4] = "level"; pListAttrs[5] = NULL; pListAttrs[6] = "type"; pListAttrs[7] = NULL; pListAttrs[8] = NULL; pListAttrs[9] = NULL; // we also need to explicitely clear the list formating // properties, since their values are not necessarily part // of the style definition, so that cloneWithEliminationIfEqual // which we call later will not get rid off them const XML_Char * pListProps[20]; pListProps[0] = "start-value"; pListProps[1] = NULL; pListProps[2] = "list-style"; pListProps[3] = NULL; if(m_iDomDirection == UT_BIDI_RTL) pListProps[4] = "margin-right"; else pListProps[4] = "margin-left"; pListProps[5] = NULL; pListProps[6] = "text-indent"; pListProps[7] = NULL; pListProps[8] = "field-color"; pListProps[9] = NULL; pListProps[10]= "list-delim"; pListProps[11] = NULL; pListProps[12]= "field-font"; pListProps[13] = NULL; pListProps[14]= "list-decimal"; pListProps[15] = NULL; pListProps[16] = "list-tag"; pListProps[17] = NULL; pListProps[18] = NULL; pListProps[19] = NULL; // // Remove all the list related properties // bRet = m_pDoc->changeStruxFmt(PTC_RemoveFmt, getPosition(), getPosition(), pListAttrs, pListProps, PTX_Block); fp_Run * pRun = getFirstRun(); while(pRun->getNextRun()) { pRun = pRun->getNextRun(); } PT_DocPosition lastPos = getPosition(false) + pRun->getBlockOffset(); bRet = m_pDoc->changeSpanFmt(PTC_RemoveFmt, getPosition(false), lastPos, pListAttrs, pListProps); // // Set the indents to match. // bRet = m_pDoc->changeStruxFmt(PTC_AddFmt, getPosition(), getPosition(), NULL, props, PTX_Block); m_bListItem = false; } else { const XML_Char * attribs[] = { "listid", NULL,"level",NULL, NULL,NULL }; attribs [1] = lid; attribs [3] = pszlevel; bRet = m_pDoc->changeStruxFmt(PTC_AddFmt,getPosition(), getPosition(), attribs, props, PTX_Block); m_pDoc->listUpdate(getStruxDocHandle()); } // format(); if (pView && (pView->isActive() || pView->isPreview())) { if(offset > 0 ) pView->_setPoint(pView->getPoint()+offset-2); } FREEP(props); } /*! * Find the most recent block with the list ID given. \param UT_uint32 id the identifier of the list \returns fl_BlockLayout * */ fl_BlockLayout * fl_BlockLayout::getPreviousList(UT_uint32 id) { // // Find the most recent list item that matches the id given // UT_ASSERT(m_pAutoNum); fl_BlockLayout * pPrev = getPrevBlockInDocument(); bool bmatchid = false; fl_AutoNum * pAutoNum = NULL; if (pPrev != NULL && (pPrev->getAutoNum() != NULL) && pPrev->isListItem()) { bmatchid = static_cast(id == pPrev->getAutoNum()->getID()); if (pPrev->isFirstInList() && !bmatchid) { pAutoNum = pPrev->getAutoNum()->getParent(); while (pAutoNum && !bmatchid) { bmatchid = static_cast(id == pAutoNum->getID() && pAutoNum->isItem(pPrev->getStruxDocHandle())); pAutoNum = pAutoNum->getParent(); } } } while (pPrev != NULL && bmatchid == false) { pPrev = pPrev->getPrevBlockInDocument(); if (pPrev && (pPrev->getAutoNum() != NULL) && pPrev->isListItem()) { bmatchid = static_cast(id == pPrev->getAutoNum()->getID()); if (pPrev->isFirstInList() && !bmatchid) { pAutoNum = pPrev->getAutoNum()->getParent(); while (pAutoNum && !bmatchid) { bmatchid = (bool) (id == pAutoNum->getID() && pAutoNum->isItem(pPrev->getStruxDocHandle())); pAutoNum = pAutoNum->getParent(); } } } } return pPrev; } fl_BlockLayout * fl_BlockLayout::getNextList(UT_uint32 id) { // // Find the next list item that matches the id given // fl_BlockLayout * pNext = getNextBlockInDocument(); bool bmatchLevel = false; if( pNext != NULL && pNext->isListItem()&& (pNext->getAutoNum() != NULL)) { bmatchLevel = static_cast(id == pNext->getAutoNum()->getID()); } while(pNext != NULL && bmatchLevel == false) { pNext = pNext->getNextBlockInDocument(); if( pNext != NULL && pNext->isListItem() && (pNext->getAutoNum() != NULL)) { bmatchLevel = static_cast(id == pNext->getAutoNum()->getID()); } } return pNext; } /*! * Find the most recent block with a list item. \returns fl_BlockLayout * */ fl_BlockLayout * fl_BlockLayout::getPreviousList( void) { // // Find the most recent block with a list // fl_BlockLayout * pPrev = getPrevBlockInDocument(); while(pPrev != NULL && !pPrev->isListItem()) { pPrev = pPrev->getPrevBlockInDocument(); } return pPrev; } /*! * Returns the most recent Block containing a list item of the closest match * of left-margin to this one. \returns fl_BlockLayout * */ fl_BlockLayout * fl_BlockLayout::getPreviousListOfSameMargin(void) { const char * szAlign; if(m_iDomDirection == UT_BIDI_RTL) szAlign = getProperty("margin-right",true); else szAlign = getProperty("margin-left",true); double dAlignMe = UT_convertToDimension(szAlign,DIM_IN); // // Find the most recent block with a list // fl_BlockLayout * pClosest = NULL; float dClosest = 100000.; bool bFound = false; fl_BlockLayout * pPrev = getPrevBlockInDocument(); while(pPrev != NULL && !bFound) { if(pPrev->isListItem()) { if(m_iDomDirection == UT_BIDI_RTL) szAlign = pPrev->getProperty("margin-right",true); else szAlign = pPrev->getProperty("margin-left",true); double dAlignThis = UT_convertToDimension(szAlign,DIM_IN); float diff = static_cast(fabs( static_cast(dAlignThis)-dAlignMe)); if(diff < 0.01) { pClosest = pPrev; bFound = true; } else { if(diff < dClosest) { pClosest = pPrev; dClosest = diff; } pPrev = pPrev->getPrevBlockInDocument(); } } else { pPrev = pPrev->getPrevBlockInDocument(); } } return pClosest; } inline fl_BlockLayout * fl_BlockLayout::getParentItem(void) { // TODO Again, more firendly. UT_ASSERT(m_pAutoNum); fl_AutoNum * pParent = m_pAutoNum->getActiveParent(); if (pParent) return getPreviousList(pParent->getID()); else return NULL; } void fl_BlockLayout::prependList( fl_BlockLayout * nextList) { // // Make the current block an element of the list before in the block nextList // UT_ASSERT(nextList); UT_GenericVector va,vp; nextList->getListPropertyVector( &vp); nextList->getListAttributesVector( &va); UT_uint32 counta = va.getItemCount() + 1; UT_uint32 countp = vp.getItemCount() + 1; UT_uint32 i; const XML_Char ** attribs = static_cast(UT_calloc(counta, sizeof(XML_Char *))); for(i=0; i(NULL); const XML_Char ** props = static_cast(UT_calloc(countp, sizeof(XML_Char *))); for(i=0; i(NULL); m_bStartList = false; m_bStopList = false; FV_View* pView = getView(); UT_ASSERT(pView); m_bListLabelCreated = false; m_pDoc->changeStruxFmt(PTC_AddFmt, getPosition(), getPosition(), attribs, props, PTX_Block); m_bListItem = true; m_pDoc->listUpdate(getStruxDocHandle()); FREEP(attribs); FREEP(props); } void fl_BlockLayout::resumeList( fl_BlockLayout * prevList) { // // Make the current block the next element of the list in the block prevList // UT_ASSERT(prevList); UT_GenericVector va,vp; // // Defensive code. This should not happen // UT_ASSERT(prevList->getAutoNum()); if(prevList->getAutoNum() == NULL) return; prevList->getListPropertyVector( &vp); prevList->getListAttributesVector( &va); UT_uint32 counta = va.getItemCount() + 1; UT_uint32 countp = vp.getItemCount() + 1; UT_uint32 i; const XML_Char ** attribs = static_cast(UT_calloc(counta, sizeof(XML_Char *))); for(i=0; i(NULL); const XML_Char ** props = static_cast(UT_calloc(countp, sizeof(XML_Char *))); for(i=0; i(NULL); m_bStartList = false; m_bStopList = false; FV_View* pView = getView(); UT_ASSERT(pView); m_bListLabelCreated = false; m_pDoc->changeStruxFmt(PTC_AddFmt, getPosition(), getPosition(), attribs, props, PTX_Block); m_bListItem = true; m_pDoc->listUpdate(getStruxDocHandle()); FREEP(attribs); FREEP(props); } void fl_BlockLayout::listUpdate(void) { // // Update the list on the screen to reflect changes made. // if(getSectionLayout() && (getSectionLayout()->getType()== FL_SECTION_HDRFTR)) { m_pAutoNum = NULL; return; } if (m_pAutoNum == NULL) return; if (m_bStartList == true) m_pAutoNum->update(1); if ((m_bListLabelCreated == false) && (m_bStopList == false)) _createListLabel(); format(); } void fl_BlockLayout::transferListFlags(void) { // // Transfer list flags from a block to the following list blocks // UT_ASSERT(getNext()); if(getNext() == NULL) { return; } if(getNext()->getContainerType() != FL_CONTAINER_BLOCK) { return; } if (getNextBlockInDocument()->isListItem()) // this is wrong. It should be next in the list. { UT_uint32 nId = getNext()->getAutoNum()->getID(); UT_uint32 cId=0, pId=0; fl_BlockLayout * pPrev = getPreviousList(); if(pPrev && pPrev->getAutoNum() == NULL) { return; } if(pPrev != NULL) pId = pPrev->getAutoNum()->getID(); if(isListItem()) cId = getAutoNum()->getID(); if(cId == nId) { if (!getNextBlockInDocument()->m_bStartList) getNextBlockInDocument()->m_bStartList = m_bStartList; if (!getNextBlockInDocument()->m_bStopList) getNextBlockInDocument()->m_bStopList = m_bStopList; } else if ( pId == nId) { if (!getNextBlockInDocument()->m_bStartList) getNextBlockInDocument()->m_bStartList = pPrev->m_bStartList; if (!getNextBlockInDocument()->m_bStopList) getNextBlockInDocument()->m_bStopList = pPrev->m_bStopList; } } } bool fl_BlockLayout::isListLabelInBlock( void) { fp_Run * pRun = m_pFirstRun; bool bListLabel = false; while( (pRun!= NULL) && (bListLabel == false)) { if(pRun->getType() == FPRUN_FIELD) { fp_FieldRun* pFRun = static_cast(pRun); if(pFRun->getFieldType() == FPFIELD_list_label) bListLabel = true; } pRun = pRun->getNextRun(); } return bListLabel; } bool fl_BlockLayout::isFirstInList(void) { PL_StruxDocHandle sdh = fl_Layout::getStruxDocHandle(); if (!m_pAutoNum) return false; else return static_cast(sdh == m_pAutoNum->getFirstItem()); } void fl_BlockLayout::_createListLabel(void) { // // Put the current list label into this block. // if(!m_pFirstRun) return; if (isListLabelInBlock() == true || m_bListLabelCreated == true) { m_bListLabelCreated = true; return; } UT_ASSERT(m_pAutoNum); xxx_UT_DEBUGMSG(("Doing create list label \n")); FV_View* pView = getView(); PT_DocPosition offset =0; if(pView) { offset = pView->getPoint() - getPosition(); } #if 1 const XML_Char ** blockatt; bool bHaveBlockAtt = pView->getCharFormat(&blockatt,true,getPosition()); #endif #if 1 XML_Char * tagatt[3] = {"list-tag",NULL,NULL}; XML_Char tagID[12]; UT_return_if_fail(m_pDoc); UT_uint32 itag = m_pDoc->getUID(UT_UniqueId::List); sprintf(tagID,"%d",itag); tagatt[1] = static_cast(&tagID[0]); m_pDoc->changeSpanFmt(PTC_AddFmt,getPosition(),getPosition(),NULL,const_cast(tagatt)); #endif const XML_Char* attributes[] = { "type","list_label", NULL, NULL }; bool bResult = m_pDoc->insertObject(getPosition(), PTO_Field, attributes, NULL); PT_DocPosition diff = 1; if(m_pDoc->isDoingPaste() == false) { UT_UCSChar c = UCS_TAB; bResult = m_pDoc->insertSpan(getPosition()+1,&c,1); diff = 2; } // // I don't think we need this. // We definately need this to preserve attributes on new list lines // // // UT_uint32 i =0; // while(blockatt[i] != NULL) // { // UT_DEBUGMSG(("SEVIOR: Applying blockatt[i] %s at %d %d \n",blockatt[i],getPosition(),getPosition()+diff)); // i++; // } // FV_View::getCharFmt() can sometimes return a static temporary if(bHaveBlockAtt) { m_pDoc->changeSpanFmt(PTC_AddFmt,getPosition(),getPosition()+diff,NULL,static_cast(blockatt)); FREEP(blockatt); } if (pView && (pView->isActive() || pView->isPreview())) { pView->_setPoint(pView->getPoint()+offset); } m_bListLabelCreated = true; } void fl_BlockLayout::deleteListLabel(void) { _deleteListLabel(); } void fl_BlockLayout::_deleteListLabel(void) { // // Remove the current list label from the block. This code does not assume the // label is at the first position in the block // PD_Document * pDoc = m_pLayout->getDocument(); UT_uint32 posBlock = getPosition(); // Find List Label fp_Run * pRun = getFirstRun(); bool bStop = false; m_bListLabelCreated = false; // // Search within the block for the list label // while(bStop == false && pRun != NULL) { if(pRun->getType() == FPRUN_FIELD) { fp_FieldRun * pFRun = static_cast(pRun); if(pFRun->getFieldType() == FPFIELD_list_label) { bStop = true; break; } } pRun = pRun->getNextRun(); if(pRun == NULL) { bStop = true; } } if(pRun != NULL) { UT_uint32 ioffset = pRun->getBlockOffset(); UT_uint32 npos = 1; fp_Run * tRun = pRun->getNextRun(); if(tRun != NULL && tRun->getType()==FPRUN_TAB) { npos = 2; } UT_uint32 iRealDeleteCount; pDoc->deleteSpan(posBlock+ioffset, posBlock+ioffset + npos,NULL,iRealDeleteCount); } } UT_UCSChar * fl_BlockLayout::getListLabel(void) { // UT_ASSERT(m_pAutoNum); // // Return the calculated list label for the block // if(m_pAutoNum != NULL) return const_cast(m_pAutoNum->getLabel(getStruxDocHandle())); else return NULL; } inline void fl_BlockLayout::_addBlockToPrevList( fl_BlockLayout * prevBlockInList, UT_uint32 level) { // // Insert the current block to the list at the point after prevBlockInList // fl_AutoNum * pAutoNum; bool bMatchList = false; UT_ASSERT(prevBlockInList); pAutoNum = prevBlockInList->getAutoNum(); while(pAutoNum && !bMatchList) { if (pAutoNum->getLevel() == level) { bMatchList = true; UT_DEBUGMSG(("Matched List. Returning.\n")); } else { pAutoNum = pAutoNum->getParent(); UT_DEBUGMSG(("Didn't match list. Going Up.\n")); } } UT_DEBUGMSG(("Found List with Id: %d\n", pAutoNum->getID())); m_pAutoNum = pAutoNum; m_pAutoNum->insertItem(getStruxDocHandle(), prevBlockInList->getStruxDocHandle()); } inline void fl_BlockLayout::_prependBlockToPrevList( fl_BlockLayout * nextBlockInList) { // // Insert the current block to the list at the point before nextBlockInList // UT_ASSERT(nextBlockInList); m_pAutoNum = nextBlockInList->getAutoNum(); m_pAutoNum->prependItem(getStruxDocHandle(), nextBlockInList->getStruxDocHandle()); } UT_uint32 fl_BlockLayout::getLevel(void) { if (!m_pAutoNum) return 0; else return m_pAutoNum->getLevel(); } void fl_BlockLayout::setStarting( bool bValue ) { m_bStartList = bValue; } void fl_BlockLayout::setStopping( bool bValue) { m_bStopList = bValue; } /*! * This Method searches for the next piece of of the block that could * be used for texttotable conversions. \returns true if a valid piece of text was found and there is more, false otherwise \param buf reference to a growbug containing the text in the block \param startPos - start search from this position \param begPos - first character of the word \param endPos - Last character of the word \param sWord - UTF8 string containing the word \param If true do not use a space as a delimiter. */ bool fl_BlockLayout::getNextTableElement(UT_GrowBuf * buf, PT_DocPosition startPos, PT_DocPosition & begPos, PT_DocPosition & endPos, UT_UTF8String & sWord, bool bIgnoreSpace) { UT_uint32 offset = startPos - getPosition(false); UT_uint32 i = 0; UT_UCS4Char curChar = 0; if(offset >= buf->getLength()) { begPos = 0; endPos = 0; return false; } UT_uint32 iMax = buf->getLength() - offset; // // skip initial punctuation marks for(i= 0; i < iMax; i++) { curChar = static_cast(*buf->getPointer(offset+i)); if(!UT_isWordDelimiter(curChar,UCS_UNKPUNK,UCS_UNKPUNK)) { break; } } if( i == iMax) { begPos = 0; endPos = 0; return false; } begPos = getPosition(false) + offset + i; bool bFoundFootnote = false; for(; i< iMax; i++) { curChar = static_cast(*buf->getPointer(offset+i)); xxx_UT_DEBUGMSG(("CurChar %d pos %d \n",curChar,offset+i+begPos)); if(curChar == 0) { PT_DocPosition pos = offset+i+begPos; if(m_pDoc->isFootnoteAtPos(pos)) { bFoundFootnote = true; continue; } if(m_pDoc->isEndFootnoteAtPos(pos)) { bFoundFootnote = false; continue; } } if(bFoundFootnote) { continue; } sWord += curChar; if(curChar == 7) { continue; // don't split on fields } if(UT_isWordDelimiter(curChar,UCS_UNKPUNK,UCS_UNKPUNK)) { if( bIgnoreSpace && (curChar == UCS_SPACE)) { continue; } break; } } if(i< iMax) { endPos = getPosition(false) + offset + i; } else { endPos = getPosition(false) + offset + i; } return true; } void fl_BlockLayout::setDominantDirection(UT_BidiCharType iDirection) { m_iDomDirection = iDirection; XML_Char * prop[] = {NULL, NULL, 0}; XML_Char ddir[] = "dom-dir"; XML_Char rtl[] = "rtl"; XML_Char ltr[] = "ltr"; prop[0] = static_cast(&ddir[0]); if(m_iDomDirection == UT_BIDI_RTL) { prop[1] = static_cast(&rtl[0]); } else { prop[1] = static_cast(<r[0]); } PT_DocPosition offset = getPosition(); PT_DocPosition offset2 = offset; //NB The casts in the following call are really necessary, it refuses to compile without them. #TF getDocument()->changeStruxFmt(static_cast(PTC_AddFmt), offset, offset2, static_cast(NULL), const_cast(prop), static_cast(PTX_Block)); UT_DEBUGMSG(("Block::setDominantDirection: offset=%d\n", offset)); } /*! Squiggle block being checked (for debugging) Trivial background checker which puts on and takes off squiggles from the entire block that's being checked. This sort of messes up the spelling squiggles, but it's just a debug thing anyhow. Enable it by setting a preference DebugFlash="1" */ void fl_BlockLayout::debugFlashing(void) { #if 0 xxx_UT_DEBUGMSG(("fl_BlockLayout::debugFlashing() was called\n")); UT_GrowBuf pgb(1024); bool bRes = getBlockBuf(&pgb); UT_ASSERT(bRes); UT_uint32 eor = pgb.getLength(); // end of region FV_View* pView = getView(); fl_PartOfBlock* pPOB = new fl_PartOfBlock(0, eor); UT_ASSERT(pPOB); if (pPOB) { m_pSpellSquiggles->add(pPOB); m_pSpellSquiggles->clear(pPOB); pView->updateScreen(); UT_usleep(250000); //_deleteSquiggles(0, eor); pView->updateScreen(); } pView->updateScreen(); #endif } fp_Run* fl_BlockLayout::findRunAtOffset(UT_uint32 offset) const { fp_Run * pRun = getFirstRun(); UT_return_val_if_fail(pRun, NULL); fp_Run * pRunResult = NULL; while (pRun) { if( pRun->getBlockOffset() <= offset && (pRun->getBlockOffset() + pRun->getLength()) > offset ) { pRunResult = pRun; break; } pRun = pRun->getNextRun(); } return pRunResult; } /*! Constructor for iterator Use the iterator to find words for spell-checking in the block. \param pBL BlockLayout this iterator should work on \param iPos Position the iterator should start from */ fl_BlockSpellIterator::fl_BlockSpellIterator(fl_BlockLayout* pBL, UT_sint32 iPos) : m_pBL(pBL), m_iWordOffset(iPos), m_iStartIndex(iPos), m_iPrevStartIndex(iPos), m_pMutatedString(NULL), m_iSentenceStart(0), m_iSentenceEnd(0) { m_pgb = new UT_GrowBuf(1024); bool bRes = pBL->getBlockBuf(m_pgb); UT_ASSERT(bRes); m_pText = reinterpret_cast(m_pgb->getPointer(0)); m_iLength = m_pgb->getLength(); } /*! Destructor for iterator */ fl_BlockSpellIterator::~fl_BlockSpellIterator() { DELETEP(m_pgb); FREEP(m_pMutatedString); } /*! Get length of the block text \return Length of the block */ UT_sint32 fl_BlockSpellIterator::getBlockLength(void) { return m_iLength; } /*! Update block information for this iterator This method must be called whenever the block this iterator is associated with changes. */ void fl_BlockSpellIterator::updateBlock(void) { m_pgb->truncate(0); bool bRes = m_pBL->getBlockBuf(m_pgb); UT_ASSERT(bRes); m_pText = reinterpret_cast(m_pgb->getPointer(0)); UT_sint32 iNewLen = m_pgb->getLength(); if (iNewLen <= m_iStartIndex) { m_iStartIndex = iNewLen; m_iPrevStartIndex = iNewLen; } m_iLength = iNewLen; m_iWordOffset = 0; m_iWordLength = 0; } /*! Returns next word for spell checking in block The method finds the next word in the block for spell checking. It takes care of ignoring words as per user configuration and speller limitations. It also makes necessary tweaks to the word (such as right-quote to ASCII-quote conversion). If the block is changed between calls to this method, the updateBlock method must be called. \result pWord Pointer to word. \result iLength Length of word. \result iBlockPost The word's position in the block \return True if word was found, false otherwise. */ bool fl_BlockSpellIterator::nextWordForSpellChecking(const UT_UCSChar*& pWord, UT_sint32& iLength, UT_sint32& iBlockPos) { // For empty blocks, there will be no buffer if (NULL == m_pText) return false; for (;;) { bool bFound = false; bool bWordStartFound = false; m_iWordOffset = m_iStartIndex; // Special case for first character in block - checked // seperately to avoid in loop below if (0 == m_iWordOffset) { UT_UCSChar followChar = (((m_iWordOffset + 1) < m_iLength) ? m_pText[m_iWordOffset+1] : UCS_UNKPUNK); if (!UT_isWordDelimiter( m_pText[m_iWordOffset], followChar, UCS_UNKPUNK)) { bWordStartFound = true; } else { m_iWordOffset++; } } // If start of word not found, keep looking (until the last // character but one - avoids boundary checks for the // followChar argument) if (!bWordStartFound) { while (m_iWordOffset < (m_iLength-1)) { if (!UT_isWordDelimiter( m_pText[m_iWordOffset], m_pText[m_iWordOffset+1], m_pText[m_iWordOffset-1])) { bWordStartFound = true; break; } m_iWordOffset++; } } // No word start has been found. We still have to check the // last character in the block, but even if it is a word // character, we don't spell-check one-character words, so // there's no reason to make the effort. Just exit... if (!bWordStartFound) { return false; } // Now we have the starting position of the word in // m_iWordOffset. // Ignore some initial characters if (_ignoreFirstWordCharacter(m_pText[m_iWordOffset])) { m_iWordOffset++; } // If we're at the end of the block after ignoring characters, // nothing more to do if (m_iWordOffset == m_iLength) { return false; } // We're at the start of a word. Find end of word while // keeping track of numerics and case of letters. Again, only // check until the last but one character to avoid followChar // boundary checks... bool bAllUpperCase = true; bool bHasNumeric = false; UT_sint32 iWordEnd = m_iWordOffset; if (0 == iWordEnd) { // Special check for first letter in the block - which can // never be a word delimiter (we skipped those in the // first loop of this method, remember?) - so juct collect // the property data bAllUpperCase &= UT_UCS4_isupper(m_pText[iWordEnd]); bHasNumeric |= UT_UCS4_isdigit(m_pText[iWordEnd]); iWordEnd++; } while (!bFound && (iWordEnd < (m_iLength-1))) { if (UT_isWordDelimiter( m_pText[iWordEnd], m_pText[iWordEnd+1], m_pText[iWordEnd-1])) { bFound = true; } else { if (bAllUpperCase) { // Only check as long as all seen characters have // been upper case. Most words will cause // bAllUpperCase to go false pretty early, so we // can save the lookup... bAllUpperCase &= UT_UCS4_isupper(m_pText[iWordEnd]); } // It's not worth making this lookup conditional: // majority of words do not contain digits, so the // if-statement will just become an overhead... bHasNumeric |= UT_UCS4_isdigit(m_pText[iWordEnd]); iWordEnd++; } } // Check last character in block if necessary if (!bFound && iWordEnd != m_iLength) { UT_ASSERT(iWordEnd == (m_iLength-1)); if (UT_isWordDelimiter(m_pText[iWordEnd], UCS_UNKPUNK, m_pText[iWordEnd-1])) { bFound = true; } else { if (bAllUpperCase) bAllUpperCase &= UT_UCS4_isupper(m_pText[iWordEnd]); bHasNumeric |= UT_UCS4_isdigit(m_pText[iWordEnd]); iWordEnd++; } } UT_ASSERT(bFound || iWordEnd == m_iLength); // This is where we want to start from at next call. m_iPrevStartIndex = m_iStartIndex; m_iStartIndex = iWordEnd; // Find length of word UT_sint32 iWordLength = static_cast(iWordEnd) - static_cast(m_iWordOffset); // ignore some terminal characters UT_sint32 tempIDX = static_cast(m_iWordOffset) + iWordLength - 1; UT_ASSERT(tempIDX >= 0); if (_ignoreLastWordCharacter(m_pText[tempIDX])) { iWordLength--; } // Ignore words where first character is a digit if (UT_UCS4_isdigit(m_pText[m_iWordOffset])) { continue; } // Don't check all-UPPERCASE words unless so configured if (bAllUpperCase && !m_pBL->getView()->getLayout()->getSpellCheckCaps()) { continue; } // Don't check words with numbers unless so configured if (bHasNumeric && !m_pBL->getView()->getLayout()->getSpellCheckNumbers()) { continue; } // TODO i18n the CJK stuff here is a hack if (!XAP_EncodingManager::get_instance()->noncjk_letters(m_pText+m_iWordOffset, iWordLength)) { continue; } // These are the current word details UT_uint32 iNewLength = iWordLength; pWord = &m_pText[m_iWordOffset]; // Now make any necessary mutations to the word before it is // returned. Normal case is that no changes are necessary, so // do this in two loops, only executing the second if any // mutation is necessary. This means the normal case will not // require the allocation+copy of the word. FREEP(m_pMutatedString); bool bNeedsMutation = false; for (UT_uint32 i=0; i < static_cast(iWordLength); i++) { UT_UCSChar currentChar = m_pText[m_iWordOffset + i]; if (currentChar == UCS_ABI_OBJECT || currentChar == UCS_RQUOTE) { bNeedsMutation = true; break; } } if (bNeedsMutation) { // Generate the mutated word in a new buffer pointed to by m_pMutatedString m_pMutatedString = static_cast(UT_calloc(iWordLength, sizeof(UT_UCSChar))); UT_ASSERT(m_pMutatedString); pWord = m_pMutatedString; iNewLength = 0; for (UT_uint32 i=0; i < static_cast(iWordLength); i++) { UT_UCSChar currentChar = m_pText[m_iWordOffset + i]; // Remove UCS_ABI_OBJECT from the word if (currentChar == UCS_ABI_OBJECT) continue; // Convert smart quote apostrophe to ASCII single quote to // be compatible with ispell if (currentChar == UCS_RQUOTE) currentChar = '\''; m_pMutatedString[iNewLength++] = currentChar; } } // Ignore one-character words. // Note: if this is ever changed to be 2+, the scan for word // delimiters at the top must also be changed to check for a word // in the last character of the block. if (iNewLength <= 1) { continue; } // Don't blow ispell's little mind... if (INPUTWORDLEN < iNewLength) { continue; } // OK, we found the word. Feed the length/pos details to the // caller... iLength = iNewLength; iBlockPos = m_iWordOffset; // Also remember length of m_pWord m_iWordLength = iNewLength; // Return success! return true; } } // TODO This function finds the beginning and end of a sentence enclosing // TODO the current misspelled word. Right now, it starts from the word // TODO and works forward/backward until finding [.!?] or EOB // TODO This needs to be improved badly. However, I can't think of a // TODO algorithm to do so -- especially not one which could work with // TODO other languages very well... // TODO Anyone have something better? // TODO Hipi: ICU includes an international sentence iterator // TODO Hipi: Arabic / Hebrew reverse ? should count, Spanish upside-down // TODO Hipi: ? should not count. CJK scripts have their own equivalents // TODO Hipi: to [.!?]. Indic languages can use a "danda" or "double danda". // TODO Hipi: Unicode chartype functions may be useable /*! Update sentence baoundaries around current word Find sentence the current word is in. */ void fl_BlockSpellIterator::updateSentenceBoundaries(void) { UT_sint32 iBlockLength = m_pgb->getLength(); // If the block is small, don't bother looking for // boundaries. Just display the full block. if (iBlockLength < 30) { m_iSentenceStart = 0; m_iSentenceEnd = iBlockLength - 1; return; } // Go back from the current word start until a period is found m_iSentenceStart = m_iWordOffset; while (m_iSentenceStart > 0) { if (UT_UCS4_isSentenceSeparator(m_pText[m_iSentenceStart])) break; m_iSentenceStart--; } // Go forward past any whitespace if sentence start is not at the // start of the block if (m_iSentenceStart > 0) { // Seeing as we're not at the start of the block, and the word // must contain at least one character, we don't have to make // conditional boundary checking (and UCS_UNKPUNK // substitution). UT_ASSERT(m_iSentenceStart > 0); UT_ASSERT(m_iWordLength > 1); while (++m_iSentenceStart < m_iWordOffset && UT_isWordDelimiter(m_pText[m_iSentenceStart], m_pText[m_iSentenceStart+1], m_pText[m_iSentenceStart-1])) { // Nothing to do... just iterating... }; } // Find end of sentence. Go forward until a period is found. If // getting to within 10 characters of the end of the block, stop // and go with that as the end.... m_iSentenceEnd = m_iWordOffset + m_iWordLength; while (m_iSentenceEnd < (iBlockLength - 10)) { if (UT_UCS4_isSentenceSeparator(m_pText[m_iSentenceEnd++])) break; } if (m_iSentenceEnd == (iBlockLength-10)) m_iSentenceEnd = iBlockLength-1; } /*! Get current word \result iLength Length of string. \return Pointer to word. */ const UT_UCSChar* fl_BlockSpellIterator::getCurrentWord(UT_sint32& iLength) { iLength = m_iWordLength; if (NULL != m_pMutatedString) { return m_pMutatedString; } else { return &m_pText[m_iWordOffset]; } } /*! Get part of sentence before current word \result iLength Length of string. If 0, NULL will be returned. \return Pointer to sentence prior to current word, or NULL */ const UT_UCSChar* fl_BlockSpellIterator::getPreWord(UT_sint32& iLength) { iLength = m_iWordOffset - m_iSentenceStart; // If it ever becomes necessary to mutate the pre-word, allocate // space to m_pMutatedString and return it. Caller will consume // that buffer before calling any other function. if (0 >= iLength) return NULL; return reinterpret_cast(m_pgb->getPointer(m_iSentenceStart)); } /*! Get part of sentence after current word \result iLength Length of string. If 0, NULL will be returned. \return Pointer to sentence following current word, or NULL */ const UT_UCSChar* fl_BlockSpellIterator::getPostWord(UT_sint32& iLength) { iLength = m_iSentenceEnd - m_iStartIndex; // If it ever becomes necessary to mutate the pre-word, allocate // space to m_pMutatedString and return it. Caller will consume // that buffer before calling any other function. if (0 >= iLength) return NULL; return reinterpret_cast(m_pgb->getPointer(m_iStartIndex)); } /*! Move iterator back to the previous word. This method can only be called once per iteration. */ void fl_BlockSpellIterator::revertToPreviousWord() { m_iStartIndex = m_iPrevStartIndex; } bool fl_BlockSpellIterator::_ignoreFirstWordCharacter(const UT_UCSChar c) const { switch (c) { case '\'': case '"': case UCS_LDBLQUOTE: // smart quoute, open double case UCS_LQUOTE: // smart quoute, open return true; default: return false; } } bool fl_BlockSpellIterator::_ignoreLastWordCharacter(const UT_UCSChar c) const { switch (c) { case '\'': case '"': case UCS_RDBLQUOTE: // smart quote, close double case UCS_RQUOTE: // smart quote, close return true; default: return false; } }