diff options
author | Michael Stahl <mstahl@redhat.com> | 2013-06-15 21:25:27 +0200 |
---|---|---|
committer | Michael Stahl <mstahl@redhat.com> | 2013-06-20 00:34:38 +0200 |
commit | 6db39dbd7378351f6476f6db25eb7110c9cfb291 (patch) | |
tree | 0f9321d40740e87e80d8ed05a7c7f474d5310afd | |
parent | e012f326c1c32c053304998a6826cb322f2c7728 (diff) |
fdo#52028: sw: let text formatting ignore RSID in automatic styles
A suprising regression from 062eaeffe7cb986255063bb9b0a5f3fb3fc8e34c:
The RSID text attributes that are inserted for every user-inserted text
cause the text formatting (SwAttrIter) to create a lot more text portions,
and the portion breaks make font kerning impossible.
This is the only way i can think of to fix this problem; alternatives that
don't work are splitting the RSID out of the AUTOFMT hint into a separate
one and combining them in the sw UNO wrappers (fails because
SwXAutoStylesEnumerator actually does need to enumerate every AUTOFMT
including RSID), trying to detect and ignore them just in the text
formatting (the SwAttrIter cannot easily/cheaply detect when it's allowed
to skip), and having an internal subdivision inside the AUTOFMT hint (one
subsection for every RSID change) (which does not work because it cannot
ignore RSID-only AUTOFMTs completely).
Solve the problem with 2 additional flags on AUTOFMT and CHARFMT
attributes: FormatIgnoreStart and FormatIgnoreEnd, which indicate to
SwAttrIter::GetNextAttr() that the start or end of the hint should be
ignored, so that effectively it is merged with the preceding/subsequent
hint. Of course the UNO API does not respect the flags so we can store
the RSIDs in automatic styles.
The flags are maintained in SwpHints::MergePortions, which detects both
RSID-only AUTOFMT hints (which can be ignored completely), and the
situation of N CHARFMT hints + AUTOFMT hint vs. N CHARFMT hints +
AUTOFMT hint where the AUTOFMT hints differ only in their RSID attribute.
This means that MergePortions needs to be called more often now, in cases
where the ignore flags may have been invalidated, such as:
- insertion of text with possible DontExpand flag set on hints
- deletion of hints
- SETATTR_NOHINTADJUST mode
Change-Id: I1fb95a87c654c67d114f6f7f2c43b847c50b0ffa
-rw-r--r-- | sw/inc/ndhints.hxx | 2 | ||||
-rw-r--r-- | sw/inc/swtypes.hxx | 2 | ||||
-rw-r--r-- | sw/inc/txatbase.hxx | 6 | ||||
-rw-r--r-- | sw/source/core/inc/rolbck.hxx | 2 | ||||
-rw-r--r-- | sw/source/core/layout/frmtool.cxx | 3 | ||||
-rw-r--r-- | sw/source/core/layout/laycache.cxx | 5 | ||||
-rw-r--r-- | sw/source/core/text/itratr.cxx | 24 | ||||
-rw-r--r-- | sw/source/core/txtnode/ndhints.cxx | 88 | ||||
-rw-r--r-- | sw/source/core/txtnode/ndtxt.cxx | 44 | ||||
-rw-r--r-- | sw/source/core/txtnode/thints.cxx | 213 | ||||
-rw-r--r-- | sw/source/core/txtnode/txatbase.cxx | 2 | ||||
-rw-r--r-- | sw/source/core/txtnode/txtedt.cxx | 4 | ||||
-rw-r--r-- | sw/source/core/undo/rolbck.cxx | 14 |
13 files changed, 371 insertions, 38 deletions
diff --git a/sw/inc/ndhints.hxx b/sw/inc/ndhints.hxx index 0d7a158ee889..d8b611535cbc 100644 --- a/sw/inc/ndhints.hxx +++ b/sw/inc/ndhints.hxx @@ -116,7 +116,7 @@ public: inline sal_uInt16 Count() const { return m_HintStarts.size(); } #ifdef DBG_UTIL - bool Check() const; + bool Check(bool) const; #endif }; diff --git a/sw/inc/swtypes.hxx b/sw/inc/swtypes.hxx index 187e070e8da8..f5ba633310ab 100644 --- a/sw/inc/swtypes.hxx +++ b/sw/inc/swtypes.hxx @@ -192,6 +192,8 @@ namespace nsSetAttrMode const SetAttrMode SETATTR_DONTREPLACE = 0x0002; // Don't replace another text attribute. const SetAttrMode SETATTR_NOTXTATRCHR = 0x0004; // Don't insert 0xFF at attributes with no end. + /// attention: NOHINTADJUST prevents MergePortions! + /// when using this need to pay attention to ignore start/end flags of hint const SetAttrMode SETATTR_NOHINTADJUST = 0x0008; // No merging of ranges. const SetAttrMode SETATTR_NOFORMATATTR = 0x0010; // Do not change into format attribute. const SetAttrMode SETATTR_DONTCHGNUMRULE = 0x0020; // Do not change NumRule. diff --git a/sw/inc/txatbase.hxx b/sw/inc/txatbase.hxx index 3a250f068885..e69b08871fd3 100644 --- a/sw/inc/txatbase.hxx +++ b/sw/inc/txatbase.hxx @@ -55,6 +55,8 @@ private: bool m_bDontExpandStart : 1; // don't expand start at paragraph start (ruby) bool m_bNesting : 1; // SwTxtAttrNesting bool m_bHasDummyChar : 1; // without end + meta + bool m_bFormatIgnoreStart : 1; ///< text formatting should ignore start + bool m_bFormatIgnoreEnd : 1; ///< text formatting should ignore end protected: SwTxtAttr( SfxPoolItem& rAttr, xub_StrLen nStart ); @@ -94,6 +96,10 @@ public: bool IsDontExpandStartAttr() const { return m_bDontExpandStart; } bool IsNesting() const { return m_bNesting; } bool HasDummyChar() const { return m_bHasDummyChar; } + bool IsFormatIgnoreStart() const { return m_bFormatIgnoreStart; } + bool IsFormatIgnoreEnd () const { return m_bFormatIgnoreEnd ; } + void SetFormatIgnoreStart(bool bFlag) { m_bFormatIgnoreStart = bFlag; } + void SetFormatIgnoreEnd (bool bFlag) { m_bFormatIgnoreEnd = bFlag; } inline const SfxPoolItem& GetAttr() const; inline SfxPoolItem& GetAttr(); diff --git a/sw/source/core/inc/rolbck.hxx b/sw/source/core/inc/rolbck.hxx index 62b01844f43c..ddff2a21e50f 100644 --- a/sw/source/core/inc/rolbck.hxx +++ b/sw/source/core/inc/rolbck.hxx @@ -122,6 +122,8 @@ class SwHistorySetTxt : public SwHistoryHint const sal_uLong m_nNodeIndex; const xub_StrLen m_nStart; const xub_StrLen m_nEnd; + bool m_bFormatIgnoreStart : 1; + bool m_bFormatIgnoreEnd : 1; public: SwHistorySetTxt( SwTxtAttr* pTxtHt, sal_uLong nNode ); diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx index f9337b13e268..91239873e537 100644 --- a/sw/source/core/layout/frmtool.cxx +++ b/sw/source/core/layout/frmtool.cxx @@ -1277,8 +1277,7 @@ void _InsertCnt( SwLayoutFrm *pLay, SwDoc *pDoc, if ( pNd->IsCntntNode() ) { SwCntntNode* pNode = (SwCntntNode*)pNd; - pFrm = pNode->IsTxtNode() ? new SwTxtFrm( (SwTxtNode*)pNode, pLay ) : - pNode->MakeFrm( pLay ); + pFrm = pNode->MakeFrm(pLay); if( pPageMaker ) pPageMaker->CheckInsert( nIndex ); diff --git a/sw/source/core/layout/laycache.cxx b/sw/source/core/layout/laycache.cxx index 1753d8544e50..ebd9e722aa2e 100644 --- a/sw/source/core/layout/laycache.cxx +++ b/sw/source/core/layout/laycache.cxx @@ -843,8 +843,9 @@ bool SwLayHelper::CheckInsert( sal_uLong nNodeIndex ) } else { - SwTxtFrm *pNew = new SwTxtFrm( ((SwTxtFrm*)rpFrm)-> - GetTxtNode(), rpFrm ); + SwTxtFrm *const pNew = static_cast<SwTxtFrm*>( + static_cast<SwTxtFrm*>(rpFrm) + ->GetTxtNode()->MakeFrm(rpFrm)); pNew->ManipOfst( nOfst ); pNew->SetFollow( ((SwTxtFrm*)rpFrm)->GetFollow() ); ((SwTxtFrm*)rpFrm)->SetFollow( pNew ); diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index d95ee5500f49..fba4898bbf84 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -311,12 +311,26 @@ xub_StrLen SwAttrIter::GetNextAttr( ) const xub_StrLen nNext = STRING_LEN; if( pHints ) { - if (pHints->GetStartCount() > nStartIndex) // Gibt es noch Starts? - nNext = (*pHints->GetStart(nStartIndex)->GetStart()); - if (pHints->GetEndCount() > nEndIndex) // Gibt es noch Enden? + // are there attribute starts left? + for (sal_uInt16 i = nStartIndex; i < pHints->GetStartCount(); ++i) { - xub_StrLen nNextEnd = (*pHints->GetEnd(nEndIndex)->GetAnyEnd()); - if ( nNextEnd<nNext ) nNext = nNextEnd; // Wer ist naeher? + SwTxtAttr *const pAttr(pHints->GetStart(i)); + if (!pAttr->IsFormatIgnoreStart()) + { + nNext = *pAttr->GetStart(); + break; + } + } + // are there attribute ends left? + for (sal_uInt16 i = nEndIndex; i < pHints->GetEndCount(); ++i) + { + SwTxtAttr *const pAttr(pHints->GetEnd(i)); + if (!pAttr->IsFormatIgnoreEnd()) + { + xub_StrLen const nNextEnd = *pAttr->GetAnyEnd(); + nNext = std::min(nNext, nNextEnd); // pick nearest one + break; + } } } if (m_pTxtNode!=NULL) { diff --git a/sw/source/core/txtnode/ndhints.cxx b/sw/source/core/txtnode/ndhints.cxx index 02ea3443a884..4d070e1fb57a 100644 --- a/sw/source/core/txtnode/ndhints.cxx +++ b/sw/source/core/txtnode/ndhints.cxx @@ -24,6 +24,8 @@ #ifdef DBG_UTIL #include <pam.hxx> +#include <fmtautofmt.hxx> +#include <set> #endif @@ -171,7 +173,7 @@ sal_uInt16 SwpHintsArray::GetPos( const SwTxtAttr *pHt ) const return false; \ } -bool SwpHintsArray::Check() const +bool SwpHintsArray::Check(bool bPortionsMerged) const { // 1) gleiche Anzahl in beiden Arrays CHECK_ERR( m_HintStarts.size() == m_HintEnds.size(), @@ -181,6 +183,23 @@ bool SwpHintsArray::Check() const const SwTxtAttr *pLastStart = 0; const SwTxtAttr *pLastEnd = 0; + std::set<SwTxtAttr const*> RsidOnlyAutoFmts; + if (bPortionsMerged) + { + for (sal_uInt16 i = 0; i < Count(); ++i) + { + SwTxtAttr const*const pHint(m_HintStarts[i]); + if (RES_TXTATR_AUTOFMT == pHint->Which()) + { + boost::shared_ptr<SfxItemSet> const pSet( + pHint->GetAutoFmt().GetStyleHandle()); + if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false)) + { + RsidOnlyAutoFmts.insert(pHint); + } + } + } + } for( sal_uInt16 i = 0; i < Count(); ++i ) { @@ -270,6 +289,73 @@ bool SwpHintsArray::Check() const "HintsCheck: Portion inconsistency. " "This can be temporarily ok during undo operations" ); + // 8 1/2) format ignore start/end flag check + // (problems because MergePortions buggy or not called) + if (bPortionsMerged) + { + if (RES_TXTATR_AUTOFMT == pHt->Which() || + RES_TXTATR_CHARFMT == pHt->Which()) + { + // mostly ignore the annoying no-length hints + // BuildPortions inserts these in the middle of an exsiting one + bool const bNoLength(*pHt->GetStart() == *pHt->GetEnd()); + bool bNeedContinuation(!bNoLength && pHt->IsFormatIgnoreEnd()); + bool bForbidContinuation(!bNoLength && !bNeedContinuation); + if (RES_TXTATR_AUTOFMT == pHt->Which()) + { + if (RsidOnlyAutoFmts.find(pHt) != RsidOnlyAutoFmts.end()) + { + assert(pHt->IsFormatIgnoreStart()); + bNeedContinuation = false; + // don't forbid continuation - may be other hint here! + } + } + if (bNeedContinuation || bForbidContinuation) + { + bool bFound(false); + for (sal_uInt16 j = i + 1; j < Count(); ++j) + { + SwTxtAttr *const pOther(m_HintStarts[j]); + if (*pOther->GetStart() > *pHt->GetEnd()) + { + break; // done + } + else if (*pOther->GetStart() == *pOther->GetAnyEnd()) + { + continue; // empty hint: ignore + } + else if (*pOther->GetStart() == *pHt->GetEnd()) + { + if (RES_TXTATR_AUTOFMT == pOther->Which() || + RES_TXTATR_CHARFMT == pOther->Which()) + { // multiple charfmt on same range must all match + if (bNeedContinuation) + { + assert(pOther->IsFormatIgnoreStart()); + bFound = true; + } + else if (bForbidContinuation && + (RsidOnlyAutoFmts.find(pOther) == + RsidOnlyAutoFmts.end())) + { + assert(!pOther->IsFormatIgnoreStart()); + } + } + } + } + if (bNeedContinuation) + { + assert(bFound); // ? can this happen temp. during undo? + } + } + } + else + { + assert(!pHt->IsFormatIgnoreStart()); + assert(!pHt->IsFormatIgnoreEnd()); + } + } + // 9) nesting portion check if (pHtThis->IsNesting()) { diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx index 8d4cdaf2f831..7b3b98dcc3a9 100644 --- a/sw/source/core/txtnode/ndtxt.cxx +++ b/sw/source/core/txtnode/ndtxt.cxx @@ -95,7 +95,7 @@ TYPEINIT1( SwTxtNode, SwCntntNode ) #ifdef DBG_UTIL #define CHECK_SWPHINTS(pNd) { if( pNd->GetpSwpHints() && \ !pNd->GetDoc()->IsInReading() ) \ - pNd->GetpSwpHints()->Check(); } + pNd->GetpSwpHints()->Check(true); } #else #define CHECK_SWPHINTS(pNd) #endif @@ -263,6 +263,12 @@ SwTxtNode::~SwTxtNode() SwCntntFrm *SwTxtNode::MakeFrm( SwFrm* pSib ) { + // fdo#52028: ODF file import does not result in MergePortions being called + // for every attribute, since that would be inefficient. So call it here. + if (m_pSwpHints) + { + m_pSwpHints->MergePortions(*this); + } SwCntntFrm *pFrm = new SwTxtFrm( this, pSib ); return pFrm; } @@ -883,6 +889,7 @@ void SwTxtNode::Update( SwIndex const & rPos, const xub_StrLen nChangeLen, { bool bNoExp = false; bool bResort = false; + bool bMergePortionsNeeded = false; const sal_uInt16 coArrSz = static_cast<sal_uInt16>(RES_TXTATR_WITHEND_END) - static_cast<sal_uInt16>(RES_CHRATR_BEGIN); @@ -929,6 +936,11 @@ void SwTxtNode::Update( SwIndex const & rPos, const xub_StrLen nChangeLen, { pHint->SetDontExpand( false ); bResort = true; + // could have a continuation with IgnoreStart()... + if (pHint->IsFormatIgnoreEnd()) + { + bMergePortionsNeeded = true; + } if ( pHint->IsCharFmtAttr() ) { bNoExp = true; @@ -969,7 +981,11 @@ void SwTxtNode::Update( SwIndex const & rPos, const xub_StrLen nChangeLen, } } } - if ( bResort ) + if (bMergePortionsNeeded) + { + m_pSwpHints->MergePortions(*this); // does Resort too + } + else if (bResort) { m_pSwpHints->Resort(); } @@ -1745,6 +1761,7 @@ OUString SwTxtNode::InsertText( const XubString & rStr, const SwIndex & rIdx, if ( HasHints() ) { + bool bMergePortionsNeeded(false); for ( sal_uInt16 i = 0; i < m_pSwpHints->Count() && rIdx >= *(*m_pSwpHints)[i]->GetStart(); ++i ) { @@ -1764,6 +1781,14 @@ OUString SwTxtNode::InsertText( const XubString & rStr, const SwIndex & rIdx, *pHt->GetStart() = *pHt->GetStart() - nLen; *pEndIdx = *pEndIdx - nLen; m_pSwpHints->DeleteAtPos(i); + // could be that pHt has IsFormatIgnoreEnd set, and it's + // not a RSID-only hint - now we have the inserted text + // between pHt and its continuation... which we don't know. + // punt the job to MergePortions below. + if (pHt->IsFormatIgnoreEnd()) + { + bMergePortionsNeeded = true; + } InsertHint( pHt, nsSetAttrMode::SETATTR_NOHINTADJUST ); } // empty hints at insert position? @@ -1792,9 +1817,14 @@ OUString SwTxtNode::InsertText( const XubString & rStr, const SwIndex & rIdx, // Kein Feld, am Absatzanfang, HintExpand m_pSwpHints->DeleteAtPos(i); *pHt->GetStart() = *pHt->GetStart() - nLen; + // no effect on format ignore flags here (para start) InsertHint( pHt, nsSetAttrMode::SETATTR_NOHINTADJUST ); } } + if (bMergePortionsNeeded) + { + m_pSwpHints->MergePortions(*this); + } TryDeleteSwpHints(); } @@ -2042,6 +2072,7 @@ void SwTxtNode::CutImpl( SwTxtNode * const pDest, const SwIndex & rDestStart, // 2. Attribute verschieben // durch das Attribute-Array, bis der Anfang des Geltungsbereiches // des Attributs hinter dem zu verschiebenden Bereich liegt + bool bMergePortionsNeeded(false); sal_uInt16 nAttrCnt = 0; while ( m_pSwpHints && (nAttrCnt < m_pSwpHints->Count()) ) { @@ -2085,6 +2116,10 @@ void SwTxtNode::CutImpl( SwTxtNode * const pDest, const SwIndex & rDestStart, // Attribut verschieben m_pSwpHints->Delete( pHt ); // die Start/End Indicies neu setzen + if (pHt->IsFormatIgnoreStart() || pHt->IsFormatIgnoreEnd()) + { + bMergePortionsNeeded = true; + } *pHt->GetStart() = nDestStart + (nAttrStartIdx - nTxtStartIdx); if( pEndIdx ) @@ -2164,6 +2199,11 @@ void SwTxtNode::CutImpl( SwTxtNode * const pDest, const SwIndex & rDestStart, Update( rStart, nLen, sal_True, sal_True ); } + if (bMergePortionsNeeded) + { + m_pSwpHints->MergePortions(*this); + } + CHECK_SWPHINTS(this); } diff --git a/sw/source/core/txtnode/thints.cxx b/sw/source/core/txtnode/thints.cxx index 71da72eb18c2..9bcfcf426ea8 100644 --- a/sw/source/core/txtnode/thints.cxx +++ b/sw/source/core/txtnode/thints.cxx @@ -72,9 +72,11 @@ #include <map> #ifdef DBG_UTIL -#define CHECK Check(); +#define CHECK Check(true); +#define CHECK_NOTMERGED Check(false); #else #define CHECK +#define CHECK_NOTMERGED #endif using namespace ::com::sun::star::i18n; @@ -649,7 +651,7 @@ void SwpHints::BuildPortions( SwTxtNode& rNode, SwTxtAttr& rNewHint, #ifdef DBG_UTIL if( !rNode.GetDoc()->IsInReading() ) - CHECK; + CHECK_NOTMERGED; // ignore flags not set properly yet, don't check them #endif // @@ -1563,6 +1565,8 @@ void SwTxtNode::DeleteAttributes( const sal_uInt16 nWhich, const SfxPoolItem* pHiddenItem = CharFmt::GetItem( *pTxtHt, RES_CHRATR_HIDDEN ); if ( pHiddenItem ) SetCalcHiddenCharFlags(); + // for auto styles DeleteAttributes is only called from Undo + // so it shouldn't need to care about ignore start/end flags } xub_StrLen const * const pEndIdx = pTxtHt->GetEnd(); @@ -2306,6 +2310,9 @@ SwTxtNode::impl_FmtToTxtAttr(const SfxItemSet& i_rAttrSet) aCurRange = aRange.second; } + // hints were directly inserted, so need to fix the Ignore flags now + m_pSwpHints->MergePortions(*this); + // 3. Clear items from the node std::vector<sal_uInt16> aClearedIds; lcl_FillWhichIds(i_rAttrSet, aClearedIds); @@ -2497,8 +2504,9 @@ bool SwpHints::MergePortions( SwTxtNode& rNode ) SwpHintsArray::Resort(); bool bRet = false; - typedef std::multimap< int, SwTxtAttr* > PortionMap; + typedef std::multimap< int, std::pair<SwTxtAttr*, bool> > PortionMap; PortionMap aPortionMap; + std::map<int, bool> RsidOnlyAutoFmtFlagMap; xub_StrLen nLastPorStart = STRING_LEN; sal_uInt16 i = 0; int nKey = 0; @@ -2513,14 +2521,45 @@ bool SwpHints::MergePortions( SwTxtNode& rNode ) //RES_TXTATR_INETFMT != pHt->Which() ) continue; + bool isRsidOnlyAutoFmt(false); + // check for RSID-only AUTOFMT + if (RES_TXTATR_AUTOFMT == pHt->Which()) + { + boost::shared_ptr<SfxItemSet> const pSet( + pHt->GetAutoFmt().GetStyleHandle()); + if ((pSet->Count() == 1) && pSet->GetItem(RES_CHRATR_RSID, false)) + { + // fdo#52028: this one has _only_ RSID => ignore it completely + if (!pHt->IsFormatIgnoreStart() || !pHt->IsFormatIgnoreEnd()) + { + NoteInHistory(pHt); + pHt->SetFormatIgnoreStart(true); + pHt->SetFormatIgnoreEnd (true); + NoteInHistory(pHt, true); + } + isRsidOnlyAutoFmt = true; + } + } + + if (*pHt->GetStart() == *pHt->GetEnd()) + { + // no-length hints are a disease. ignore them here. + // the SwAttrIter::SeekFwd will not call Rst/Chg for them. + continue; + } + const xub_StrLen nPorStart = *pHt->GetStart(); - if ( nPorStart != nLastPorStart && nLastPorStart != STRING_LEN ) + if (nPorStart != nLastPorStart) ++nKey; nLastPorStart = nPorStart; - aPortionMap.insert( std::pair< const int, SwTxtAttr* >( nKey, pHt ) ); + aPortionMap.insert(std::make_pair(nKey, + std::make_pair(pHt, isRsidOnlyAutoFmt))); + RsidOnlyAutoFmtFlagMap[nKey] = isRsidOnlyAutoFmt; } // check if portion i can be merged with portion i+1: + // note: need to include i=0 to set IgnoreStart and j=nKey+1 to reset + // IgnoreEnd at first / last portion i = 0; int j = i + 1; while ( i <= nKey ) @@ -2530,37 +2569,128 @@ bool SwpHints::MergePortions( SwTxtNode& rNode ) PortionMap::iterator aIter1 = aRange1.first; PortionMap::iterator aIter2 = aRange2.first; - bool bMerge = true; - const sal_uInt16 nAttributesInPor1 = static_cast<sal_uInt16>(std::distance( aRange1.first, aRange1.second )); - const sal_uInt16 nAttributesInPor2 = static_cast<sal_uInt16>(std::distance( aRange2.first, aRange2.second )); - - if ( nAttributesInPor1 == nAttributesInPor2 && nAttributesInPor1 != 0 ) + enum { MATCH, DIFFER_ONLY_RSID, DIFFER } eMerge(MATCH); + size_t const nAttributesInPor1 = std::distance(aRange1.first, aRange1.second); + size_t const nAttributesInPor2 = std::distance(aRange2.first, aRange2.second); + bool const isRsidOnlyAutoFmt1(RsidOnlyAutoFmtFlagMap[i]); + bool const isRsidOnlyAutoFmt2(RsidOnlyAutoFmtFlagMap[j]); + + // if both have one they could be equal, but not if only one has it + bool const bSkipRsidOnlyAutoFmt(nAttributesInPor1 != nAttributesInPor2); + + // this loop needs to handle the case where one has a CHARFMT and the + // other CHARFMT + RSID-only AUTOFMT, so... + // want to skip over RSID-only AUTOFMT here, hence the -1 + if ((nAttributesInPor1 - ((isRsidOnlyAutoFmt1) ? 1 : 0)) == + (nAttributesInPor2 - ((isRsidOnlyAutoFmt2) ? 1 : 0)) + && (nAttributesInPor1 != 0 || nAttributesInPor2 != 0)) { - while ( aIter1 != aRange1.second ) + // _if_ there is one element more either in aRange1 or aRange2 + // it _must_ be an RSID-only AUTOFMT, which can be ignored here... + // But if both have RSID-only AUTOFMT they could be equal, no skip! + while (aIter1 != aRange1.second || aIter2 != aRange2.second) { - const SwTxtAttr* p1 = (*aIter1).second; - const SwTxtAttr* p2 = (*aIter2).second; - if ( *p1->GetEnd() < *p2->GetStart() || p1->Which() != p2->Which() || !(*p1 == *p2) ) + // first of all test if there's no gap (before skipping stuff!) + if (aIter1 != aRange1.second && aIter2 != aRange2.second && + *aIter1->second.first->GetEnd() < *aIter2->second.first->GetStart()) { - bMerge = false; + eMerge = DIFFER; break; } + // skip it - cannot be equal if bSkipRsidOnlyAutoFmt is set + if (bSkipRsidOnlyAutoFmt + && aIter1 != aRange1.second && aIter1->second.second) + { + assert(DIFFER != eMerge); + eMerge = DIFFER_ONLY_RSID; + ++aIter1; + continue; + } + if (bSkipRsidOnlyAutoFmt + && aIter2 != aRange2.second && aIter2->second.second) + { + assert(DIFFER != eMerge); + eMerge = DIFFER_ONLY_RSID; + ++aIter2; + continue; + } + assert(aIter1 != aRange1.second && aIter2 != aRange2.second); + SwTxtAttr const*const p1 = aIter1->second.first; + SwTxtAttr const*const p2 = aIter2->second.first; + if (p1->Which() != p2->Which()) + { + eMerge = DIFFER; + break; + } + if (!(*p1 == *p2)) + { + // fdo#52028: for auto styles, check if they differ only + // in the RSID, which should have no effect on text layout + if (RES_TXTATR_AUTOFMT == p1->Which()) + { + SfxItemSet set1(*p1->GetAutoFmt().GetStyleHandle()); + SfxItemSet set2(*p2->GetAutoFmt().GetStyleHandle()); + + set1.ClearItem(RES_CHRATR_RSID); + set2.ClearItem(RES_CHRATR_RSID); + + // sadly SfxItemSet::operator== does not seem to work? + SfxItemIter iter1(set1); + SfxItemIter iter2(set2); + if (set1.Count() == set2.Count()) + { + for (SfxPoolItem const* pItem1 = iter1.FirstItem(), + * pItem2 = iter2.FirstItem(); + pItem1 && pItem2; + pItem1 = iter1.NextItem(), + pItem2 = iter2.NextItem()) + { + if (pItem1 != pItem2 || + pItem1->Which() != pItem2->Which() || + *pItem1 != *pItem2) + { + eMerge = DIFFER; + break; + } + if (iter1.IsAtEnd()) + { + assert(iter2.IsAtEnd()); + eMerge = DIFFER_ONLY_RSID; + } + } + if (DIFFER == eMerge) + break; // outer loop too + } + else + { + eMerge = DIFFER; + break; + } + } + else + { + eMerge = DIFFER; + break; + } + } ++aIter1; ++aIter2; } } else { - bMerge = false; + eMerge = DIFFER; } - if ( bMerge ) + if (MATCH == eMerge) { + // important: delete second range so any IgnoreStart on the first + // range is still valid // erase all elements with key i + 1 xub_StrLen nNewPortionEnd = 0; for ( aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2 ) { - SwTxtAttr* p2 = (*aIter2).second; + SwTxtAttr *const p2 = aIter2->second.first; nNewPortionEnd = *p2->GetEnd(); const sal_uInt16 nCountBeforeDelete = Count(); @@ -2577,7 +2707,7 @@ bool SwpHints::MergePortions( SwTxtNode& rNode ) aRange1 = aPortionMap.equal_range( i ); for ( aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1 ) { - SwTxtAttr* p1 = (*aIter1).second; + SwTxtAttr *const p1 = aIter1->second.first; NoteInHistory( p1 ); *p1->GetEnd() = nNewPortionEnd; NoteInHistory( p1, true ); @@ -2586,8 +2716,37 @@ bool SwpHints::MergePortions( SwTxtNode& rNode ) } else { - ++i; - j = i + 1; + // when not merging the ignore flags need to be either set or reset + // (reset too in case one of the autofmts was recently changed) + bool const bSetIgnoreFlag(DIFFER_ONLY_RSID == eMerge); + for (aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1) + { + if (!aIter1->second.second) // already set above, don't change + { + SwTxtAttr *const pCurrent(aIter1->second.first); + if (pCurrent->IsFormatIgnoreEnd() != bSetIgnoreFlag) + { + NoteInHistory(pCurrent); + pCurrent->SetFormatIgnoreEnd(bSetIgnoreFlag); + NoteInHistory(pCurrent, true); + } + } + } + for (aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2) + { + if (!aIter2->second.second) // already set above, don't change + { + SwTxtAttr *const pCurrent(aIter2->second.first); + if (pCurrent->IsFormatIgnoreStart() != bSetIgnoreFlag) + { + NoteInHistory(pCurrent); + pCurrent->SetFormatIgnoreStart(bSetIgnoreFlag); + NoteInHistory(pCurrent, true); + } + } + } + i = j; // ++i not enough: i + 1 may have been deleted (MATCH)! + ++j; } } @@ -2676,6 +2835,16 @@ bool SwpHints::TryInsertHint( SwTxtAttr* const pHint, SwTxtNode &rNode, // #i75430# Recalc hidden flags if necessary case RES_TXTATR_AUTOFMT: { + if (*pHint->GetStart() == *pHint->GetEnd()) + { + boost::shared_ptr<SfxItemSet> const pSet( + pHint->GetAutoFmt().GetStyleHandle()); + if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false)) + { // empty range RSID-only hints could cause trouble, there's no + rNode.DestroyAttr(pHint); // need for them so don't insert + return false; + } + } // Check if auto style contains hidden attribute: const SfxPoolItem* pHiddenItem = CharFmt::GetItem( *pHint, RES_CHRATR_HIDDEN ); if ( pHiddenItem ) @@ -2974,7 +3143,7 @@ void SwpHints::DeleteAtPos( const sal_uInt16 nPos ) } CalcFlags(); - CHECK; + CHECK_NOTMERGED; // called from BuildPortions } // Ist der Hint schon bekannt, dann suche die Position und loesche ihn. diff --git a/sw/source/core/txtnode/txatbase.cxx b/sw/source/core/txtnode/txatbase.cxx index e0acb29d72f2..e59e969d78b6 100644 --- a/sw/source/core/txtnode/txatbase.cxx +++ b/sw/source/core/txtnode/txatbase.cxx @@ -34,6 +34,8 @@ SwTxtAttr::SwTxtAttr( SfxPoolItem& rAttr, xub_StrLen nStart ) , m_bDontExpandStart( false ) , m_bNesting( false ) , m_bHasDummyChar( false ) + , m_bFormatIgnoreStart(false) + , m_bFormatIgnoreEnd(false) { } diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx index 691da301329c..016d56a5db02 100644 --- a/sw/source/core/txtnode/txtedt.cxx +++ b/sw/source/core/txtnode/txtedt.cxx @@ -584,8 +584,8 @@ void SwTxtNode::RstAttr(const SwIndex &rIdx, xub_StrLen nLen, sal_uInt16 nWhich, if (bChanged) { if ( HasHints() ) - { - m_pSwpHints->Resort(); + { // possibly sometimes Resort would be sufficient, but... + m_pSwpHints->MergePortions(*this); } //TxtFrm's reagieren auf aHint, andere auf aNew SwUpdateAttr aHint( nMin, nMax, 0 ); diff --git a/sw/source/core/undo/rolbck.cxx b/sw/source/core/undo/rolbck.cxx index ced76e507c80..59cfec65def5 100644 --- a/sw/source/core/undo/rolbck.cxx +++ b/sw/source/core/undo/rolbck.cxx @@ -195,6 +195,8 @@ SwHistorySetTxt::SwHistorySetTxt( SwTxtAttr* pTxtHt, sal_uLong nNodePos ) , m_nNodeIndex( nNodePos ) , m_nStart( *pTxtHt->GetStart() ) , m_nEnd( *pTxtHt->GetAnyEnd() ) + , m_bFormatIgnoreStart(pTxtHt->IsFormatIgnoreStart()) + , m_bFormatIgnoreEnd (pTxtHt->IsFormatIgnoreEnd ()) { // Caution: the following attributes generate no format attributes: // - NoLineBreak, NoHypen, Inserted, Deleted @@ -234,9 +236,19 @@ void SwHistorySetTxt::SetInDoc( SwDoc* pDoc, bool ) if ( pTxtNd ) { - pTxtNd->InsertItem( *m_pAttr, m_nStart, m_nEnd, + SwTxtAttr *const pAttr = pTxtNd->InsertItem(*m_pAttr, m_nStart, m_nEnd, nsSetAttrMode::SETATTR_NOTXTATRCHR | nsSetAttrMode::SETATTR_NOHINTADJUST ); + // shouldn't be possible to hit any error/merging path from here + assert(pAttr); + if (m_bFormatIgnoreStart) + { + pAttr->SetFormatIgnoreStart(true); + } + if (m_bFormatIgnoreEnd) + { + pAttr->SetFormatIgnoreEnd(true); + } } } |