/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include "paralist.hxx" #include #include #include "outlundo.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::advance; // Outliner void Outliner::ImplCheckDepth( sal_Int16& rnDepth ) const { if( rnDepth < gnMinDepth ) rnDepth = gnMinDepth; else if( rnDepth > nMaxDepth ) rnDepth = nMaxDepth; } Paragraph* Outliner::Insert(const OUString& rText, sal_Int32 nAbsPos, sal_Int16 nDepth) { DBG_ASSERT(pParaList->GetParagraphCount(),"Insert:No Paras"); Paragraph* pPara; ImplCheckDepth( nDepth ); sal_Int32 nParagraphCount = pParaList->GetParagraphCount(); if( nAbsPos > nParagraphCount ) nAbsPos = nParagraphCount; if( bFirstParaIsEmpty ) { pPara = pParaList->GetParagraph( 0 ); if( pPara->GetDepth() != nDepth ) { nDepthChangedHdlPrevDepth = pPara->GetDepth(); ParaFlag nPrevFlags = pPara->nFlags; pPara->SetDepth( nDepth ); DepthChangedHdl(pPara, nPrevFlags); } pPara->nFlags |= ParaFlag::HOLDDEPTH; SetText( rText, pPara ); } else { bool bUpdate = pEditEngine->SetUpdateLayout( false ); ImplBlockInsertionCallbacks( true ); pPara = new Paragraph( nDepth ); pParaList->Insert( std::unique_ptr(pPara), nAbsPos ); pEditEngine->InsertParagraph( nAbsPos, OUString() ); DBG_ASSERT(pPara==pParaList->GetParagraph(nAbsPos),"Insert:Failed"); ImplInitDepth( nAbsPos, nDepth, false ); ParagraphInsertedHdl(pPara); pPara->nFlags |= ParaFlag::HOLDDEPTH; SetText( rText, pPara ); ImplBlockInsertionCallbacks( false ); pEditEngine->SetUpdateLayout( bUpdate ); } bFirstParaIsEmpty = false; DBG_ASSERT(pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(),"SetText failed"); return pPara; } void Outliner::ParagraphInserted( sal_Int32 nPara ) { if ( nBlockInsCallback ) return; if( bPasting || pEditEngine->IsInUndo() ) { Paragraph* pPara = new Paragraph( -1 ); pParaList->Insert( std::unique_ptr(pPara), nPara ); if( pEditEngine->IsInUndo() ) { pPara->bVisible = true; const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); pPara->SetDepth( rLevel.GetValue() ); } } else { sal_Int16 nDepth = -1; Paragraph* pParaBefore = pParaList->GetParagraph( nPara-1 ); if ( pParaBefore ) nDepth = pParaBefore->GetDepth(); Paragraph* pPara = new Paragraph( nDepth ); pParaList->Insert( std::unique_ptr(pPara), nPara ); if( !pEditEngine->IsInUndo() ) { ImplCalcBulletText( nPara, true, false ); ParagraphInsertedHdl(pPara); } } } void Outliner::ParagraphDeleted( sal_Int32 nPara ) { if ( nBlockInsCallback || ( nPara == EE_PARA_ALL ) ) return; Paragraph* pPara = pParaList->GetParagraph( nPara ); if (!pPara) return; sal_Int16 nDepth = pPara->GetDepth(); if( !pEditEngine->IsInUndo() ) { aParaRemovingHdl.Call( { this, pPara } ); } pParaList->Remove( nPara ); if( pEditEngine->IsInUndo() || bPasting ) return; pPara = pParaList->GetParagraph( nPara ); if ( pPara && ( pPara->GetDepth() > nDepth ) ) { ImplCalcBulletText( nPara, true, false ); // Search for next on the this level ... while ( pPara && pPara->GetDepth() > nDepth ) pPara = pParaList->GetParagraph( ++nPara ); } if ( pPara && ( pPara->GetDepth() == nDepth ) ) ImplCalcBulletText( nPara, true, false ); } void Outliner::Init( OutlinerMode nMode ) { nOutlinerMode = nMode; Clear(); EEControlBits nCtrl = pEditEngine->GetControlWord(); nCtrl &= ~EEControlBits(EEControlBits::OUTLINER|EEControlBits::OUTLINER2); SetMaxDepth( 9 ); switch ( GetOutlinerMode() ) { case OutlinerMode::TextObject: case OutlinerMode::TitleObject: break; case OutlinerMode::OutlineObject: nCtrl |= EEControlBits::OUTLINER2; break; case OutlinerMode::OutlineView: nCtrl |= EEControlBits::OUTLINER; break; default: OSL_FAIL( "Outliner::Init - Invalid Mode!" ); } pEditEngine->SetControlWord( nCtrl ); const bool bWasUndoEnabled(IsUndoEnabled()); EnableUndo(false); ImplInitDepth( 0, -1, false ); GetUndoManager().Clear(); EnableUndo(bWasUndoEnabled); } void Outliner::SetMaxDepth( sal_Int16 nDepth ) { if( nMaxDepth != nDepth ) { nMaxDepth = std::min( nDepth, sal_Int16(SVX_MAX_NUM-1) ); } } sal_Int16 Outliner::GetDepth( sal_Int32 nPara ) const { Paragraph* pPara = pParaList->GetParagraph( nPara ); DBG_ASSERT( pPara, "Outliner::GetDepth - Paragraph not found!" ); return pPara ? pPara->GetDepth() : -1; } void Outliner::SetDepth( Paragraph* pPara, sal_Int16 nNewDepth ) { ImplCheckDepth( nNewDepth ); if ( nNewDepth == pPara->GetDepth() ) return; nDepthChangedHdlPrevDepth = pPara->GetDepth(); ParaFlag nPrevFlags = pPara->nFlags; sal_Int32 nPara = GetAbsPos( pPara ); ImplInitDepth( nPara, nNewDepth, true ); ImplCalcBulletText( nPara, false, false ); if ( GetOutlinerMode() == OutlinerMode::OutlineObject ) ImplSetLevelDependentStyleSheet( nPara ); DepthChangedHdl(pPara, nPrevFlags); } sal_Int16 Outliner::GetNumberingStartValue( sal_Int32 nPara ) const { Paragraph* pPara = pParaList->GetParagraph( nPara ); DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" ); return pPara ? pPara->GetNumberingStartValue() : -1; } void Outliner::SetNumberingStartValue( sal_Int32 nPara, sal_Int16 nNumberingStartValue ) { Paragraph* pPara = pParaList->GetParagraph( nPara ); DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" ); if( pPara && pPara->GetNumberingStartValue() != nNumberingStartValue ) { if( IsUndoEnabled() && !IsInUndo() ) InsertUndo( std::make_unique( this, nPara, pPara->GetNumberingStartValue(), nNumberingStartValue, pPara->IsParaIsNumberingRestart(), pPara->IsParaIsNumberingRestart() ) ); pPara->SetNumberingStartValue( nNumberingStartValue ); ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); pEditEngine->SetModified(); } } bool Outliner::IsParaIsNumberingRestart( sal_Int32 nPara ) const { Paragraph* pPara = pParaList->GetParagraph( nPara ); DBG_ASSERT( pPara, "Outliner::IsParaIsNumberingRestart - Paragraph not found!" ); return pPara && pPara->IsParaIsNumberingRestart(); } void Outliner::SetParaIsNumberingRestart( sal_Int32 nPara, bool bParaIsNumberingRestart ) { Paragraph* pPara = pParaList->GetParagraph( nPara ); DBG_ASSERT( pPara, "Outliner::SetParaIsNumberingRestart - Paragraph not found!" ); if( pPara && (pPara->IsParaIsNumberingRestart() != bParaIsNumberingRestart) ) { if( IsUndoEnabled() && !IsInUndo() ) InsertUndo( std::make_unique( this, nPara, pPara->GetNumberingStartValue(), pPara->GetNumberingStartValue(), pPara->IsParaIsNumberingRestart(), bParaIsNumberingRestart ) ); pPara->SetParaIsNumberingRestart( bParaIsNumberingRestart ); ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); pEditEngine->SetModified(); } } sal_Int32 Outliner::GetBulletsNumberingStatus( const sal_Int32 nParaStart, const sal_Int32 nParaEnd ) const { if ( nParaStart > nParaEnd || nParaEnd >= pParaList->GetParagraphCount() ) { SAL_WARN("editeng", " - unexpected parameter values" ); return 2; } sal_Int32 nBulletsCount = 0; sal_Int32 nNumberingCount = 0; for (sal_Int32 nPara = nParaStart; nPara <= nParaEnd; ++nPara) { if ( !pParaList->GetParagraph(nPara) ) { break; } const SvxNumberFormat* pFmt = GetNumberFormat(nPara); if (!pFmt) { // At least, exists one paragraph that has no Bullets/Numbering. break; } else if ((pFmt->GetNumberingType() == SVX_NUM_BITMAP) || (pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL)) { // Having Bullets in this paragraph. nBulletsCount++; } else { // Having Numbering in this paragraph. nNumberingCount++; } } const sal_Int32 nParaCount = nParaEnd - nParaStart + 1; if ( nBulletsCount == nParaCount ) { return 0; } else if ( nNumberingCount == nParaCount ) { return 1; } return 2; } sal_Int32 Outliner::GetBulletsNumberingStatus() const { return pParaList->GetParagraphCount() > 0 ? GetBulletsNumberingStatus( 0, pParaList->GetParagraphCount()-1 ) : 2; } std::optional Outliner::CreateParaObject( sal_Int32 nStartPara, sal_Int32 nCount ) const { if ( static_cast(nStartPara) + nCount > o3tl::make_unsigned(pParaList->GetParagraphCount()) ) nCount = pParaList->GetParagraphCount() - nStartPara; // When a new OutlinerParaObject is created because a paragraph is just being deleted, // it can happen that the ParaList is not updated yet... if ( ( nStartPara + nCount ) > pEditEngine->GetParagraphCount() ) nCount = pEditEngine->GetParagraphCount() - nStartPara; if (nCount <= 0) return std::nullopt; std::unique_ptr xText = pEditEngine->CreateTextObject( nStartPara, nCount ); const bool bIsEditDoc(OutlinerMode::TextObject == GetOutlinerMode()); ParagraphDataVector aParagraphDataVector(nCount); const sal_Int32 nLastPara(nStartPara + nCount - 1); for(sal_Int32 nPara(nStartPara); nPara <= nLastPara; nPara++) { aParagraphDataVector[nPara-nStartPara] = *GetParagraph(nPara); } xText->ClearPortionInfo(); // tdf#147166 the PortionInfo is unwanted here OutlinerParaObject aPObj(std::move(xText), std::move(aParagraphDataVector), bIsEditDoc); aPObj.SetOutlinerMode(GetOutlinerMode()); return aPObj; } void Outliner::SetToEmptyText() { SetText(GetEmptyParaObject()); } void Outliner::SetText( const OUString& rText, Paragraph* pPara ) { DBG_ASSERT(pPara,"SetText:No Para"); const sal_Int32 nPara = pParaList->GetAbsPos( pPara ); if (pEditEngine->GetText( nPara ) == rText) { // short-circuit logic to improve performance bFirstParaIsEmpty = false; return; } const bool bUpdate = pEditEngine->SetUpdateLayout( false ); ImplBlockInsertionCallbacks( true ); if (rText.isEmpty()) { pEditEngine->SetText( nPara, rText ); ImplInitDepth( nPara, pPara->GetDepth(), false ); } else { const OUString aText(convertLineEnd(rText, LINEEND_LF)); sal_Int32 nPos = 0; sal_Int32 nInsPos = nPara+1; sal_Int32 nIdx {0}; // Loop over all tokens, but ignore the last one if empty // (i.e. if strings ends with the delimiter, detected by // checking nIdx against string length). This check also // handle empty strings. while( nIdx>=0 && nIdxGetDepth(); // In the outliner mode, filter the tabs and set the indentation // about a LRSpaceItem. In EditEngine mode intend over old tabs if( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) || ( GetOutlinerMode() == OutlinerMode::OutlineView ) ) { // Extract Tabs size_t nTabs = 0; while ( ( nTabs < aStr.size() ) && ( aStr[nTabs] == '\t' ) ) nTabs++; if ( nTabs ) aStr = aStr.substr(nTabs); // Keep depth? (see Outliner::Insert) if( !(pPara->nFlags & ParaFlag::HOLDDEPTH) ) { nCurDepth = nTabs-1; //TODO: sal_Int32 -> sal_Int16! ImplCheckDepth( nCurDepth ); pPara->SetDepth( nCurDepth ); } } if( nPos ) // not with the first paragraph { pParaList->Insert( std::unique_ptr(pPara), nInsPos ); pEditEngine->InsertParagraph( nInsPos, OUString(aStr) ); ParagraphInsertedHdl(pPara); } else { nInsPos--; pEditEngine->SetText( nInsPos, OUString(aStr) ); } ImplInitDepth( nInsPos, nCurDepth, false ); nInsPos++; nPos++; } } DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"SetText failed!"); bFirstParaIsEmpty = false; ImplBlockInsertionCallbacks( false ); // Restore the update mode. pEditEngine->SetUpdateLayout(bUpdate, /*bRestoring=*/true); } // pView == 0 -> Ignore tabs bool Outliner::ImpConvertEdtToOut( sal_Int32 nPara ) { bool bConverted = false; sal_Int32 nTabs = 0; ESelection aDelSel; OUString aName; OUString aStr( pEditEngine->GetText( nPara ) ); const sal_Unicode* pPtr = aStr.getStr(); sal_Int32 nHeadingNumberStart = 0; sal_Int32 nNumberingNumberStart = 0; SfxStyleSheet* pStyle= pEditEngine->GetStyleSheet( nPara ); if( pStyle ) { OUString aHeading_US( "heading" ); OUString aNumber_US( "Numbering" ); aName = pStyle->GetName(); sal_Int32 nSearch; if ( ( nSearch = aName.indexOf( aHeading_US ) ) != -1 ) nHeadingNumberStart = nSearch + aHeading_US.getLength(); else if ( ( nSearch = aName.indexOf( aNumber_US ) ) != -1 ) nNumberingNumberStart = nSearch + aNumber_US.getLength(); } if ( nHeadingNumberStart || nNumberingNumberStart ) { // PowerPoint import ? if( nHeadingNumberStart && ( aStr.getLength() >= 2 ) && ( pPtr[0] != '\t' ) && ( pPtr[1] == '\t' ) ) { // Extract Bullet and Tab aDelSel = ESelection( nPara, 0, nPara, 2 ); } sal_Int32 nPos = nHeadingNumberStart ? nHeadingNumberStart : nNumberingNumberStart; std::u16string_view aLevel = comphelper::string::stripStart(aName.subView(nPos), ' '); nTabs = o3tl::toInt32(aLevel); if( nTabs ) nTabs--; // Level 0 = "heading 1" bConverted = true; } else { // filter leading tabs while( *pPtr == '\t' ) { pPtr++; nTabs++; } // Remove tabs from the text if( nTabs ) aDelSel = ESelection( nPara, 0, nPara, nTabs ); } if ( aDelSel.HasRange() ) { pEditEngine->QuickDelete( aDelSel ); } const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); sal_Int16 nOutlLevel = rLevel.GetValue(); ImplCheckDepth( nOutlLevel ); ImplInitDepth( nPara, nOutlLevel, false ); return bConverted; } void Outliner::SetText( const OutlinerParaObject& rPObj ) { bool bUpdate = pEditEngine->SetUpdateLayout( false ); bool bUndo = pEditEngine->IsUndoEnabled(); EnableUndo( false ); Init( rPObj.GetOutlinerMode() ); ImplBlockInsertionCallbacks( true ); pEditEngine->SetText(rPObj.GetTextObject()); bFirstParaIsEmpty = false; pParaList->Clear(); for( sal_Int32 nCurPara = 0; nCurPara < rPObj.Count(); nCurPara++ ) { std::unique_ptr pPara(new Paragraph( rPObj.GetParagraphData(nCurPara))); ImplCheckDepth( pPara->nDepth ); pParaList->Append(std::move(pPara)); ImplCheckNumBulletItem( nCurPara ); } ImplCheckParagraphs( 0, pParaList->GetParagraphCount() ); EnableUndo( bUndo ); ImplBlockInsertionCallbacks( false ); pEditEngine->SetUpdateLayout( bUpdate ); DBG_ASSERT( pParaList->GetParagraphCount()==rPObj.Count(),"SetText failed"); DBG_ASSERT( pEditEngine->GetParagraphCount()==rPObj.Count(),"SetText failed"); } void Outliner::AddText( const OutlinerParaObject& rPObj, bool bAppend ) { bool bUpdate = pEditEngine->SetUpdateLayout( false ); ImplBlockInsertionCallbacks( true ); sal_Int32 nPara; if( bFirstParaIsEmpty ) { pParaList->Clear(); pEditEngine->SetText(rPObj.GetTextObject()); nPara = 0; bAppend = false; } else { nPara = pParaList->GetParagraphCount(); pEditEngine->InsertParagraph( EE_PARA_APPEND, rPObj.GetTextObject(), bAppend ); } bFirstParaIsEmpty = false; for( sal_Int32 n = 0; n < rPObj.Count(); n++ ) { if ( n == 0 && bAppend ) { // This first "paragraph" was just appended to an existing (incomplete) paragraph. // Since no new paragraph will be added, the assumed increase-by-1 also won't happen. --nPara; continue; } Paragraph* pPara = new Paragraph( rPObj.GetParagraphData(n) ); pParaList->Append(std::unique_ptr(pPara)); sal_Int32 nP = nPara+n; DBG_ASSERT(pParaList->GetAbsPos(pPara)==nP,"AddText:Out of sync"); ImplInitDepth( nP, pPara->GetDepth(), false ); } DBG_ASSERT( pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(), "SetText: OutOfSync" ); ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); ImplBlockInsertionCallbacks( false ); pEditEngine->SetUpdateLayout( bUpdate ); } OUString Outliner::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional& rpTxtColor, std::optional& rpFldColor, std::optional& rpFldLineStyle ) { if ( !aCalcFieldValueHdl.IsSet() ) return OUString( ' ' ); EditFieldInfo aFldInfo( this, rField, nPara, nPos ); // The FldColor is preset with COL_LIGHTGRAY. if ( rpFldColor ) aFldInfo.SetFieldColor( *rpFldColor ); aCalcFieldValueHdl.Call( &aFldInfo ); if ( aFldInfo.GetTextColor() ) { rpTxtColor = *aFldInfo.GetTextColor(); } if ( aFldInfo.GetFontLineStyle() ) { rpFldLineStyle = *aFldInfo.GetFontLineStyle(); } if (aFldInfo.GetFieldColor()) rpFldColor = *aFldInfo.GetFieldColor(); else rpFldColor.reset(); return aFldInfo.GetRepresentation(); } void Outliner::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ) { Paragraph* pPara = pParaList->GetParagraph( nPara ); if (pPara) { pEditEngine->SetStyleSheet( nPara, pStyle ); ImplCheckNumBulletItem( nPara ); } } void Outliner::ImplCheckNumBulletItem( sal_Int32 nPara ) { Paragraph* pPara = pParaList->GetParagraph( nPara ); if (pPara) pPara->aBulSize.setWidth( -1 ); } void Outliner::ImplSetLevelDependentStyleSheet( sal_Int32 nPara ) { DBG_ASSERT( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) || ( GetOutlinerMode() == OutlinerMode::OutlineView ), "SetLevelDependentStyleSheet: Wrong Mode!" ); SfxStyleSheet* pStyle = GetStyleSheet( nPara ); if ( !pStyle ) return; sal_Int16 nDepth = GetDepth( nPara ); if( nDepth < 0 ) nDepth = 0; OUString aNewStyleSheetName( pStyle->GetName() ); aNewStyleSheetName = aNewStyleSheetName.subView( 0, aNewStyleSheetName.getLength()-1 ) + OUString::number( nDepth+1 ); SfxStyleSheet* pNewStyle = static_cast(GetStyleSheetPool()->Find( aNewStyleSheetName, pStyle->GetFamily() )); DBG_ASSERT( pNewStyle, "AutoStyleSheetName - Style not found!" ); if ( pNewStyle && ( pNewStyle != GetStyleSheet( nPara ) ) ) { SfxItemSet aOldAttrs( GetParaAttribs( nPara ) ); SetStyleSheet( nPara, pNewStyle ); if ( aOldAttrs.GetItemState( EE_PARA_NUMBULLET ) == SfxItemState::SET ) { SfxItemSet aAttrs( GetParaAttribs( nPara ) ); aAttrs.Put( aOldAttrs.Get( EE_PARA_NUMBULLET ) ); SetParaAttribs( nPara, aAttrs ); } } } void Outliner::ImplInitDepth( sal_Int32 nPara, sal_Int16 nDepth, bool bCreateUndo ) { DBG_ASSERT( ( nDepth >= gnMinDepth ) && ( nDepth <= nMaxDepth ), "ImplInitDepth - Depth is invalid!" ); Paragraph* pPara = pParaList->GetParagraph( nPara ); if (!pPara) return; sal_Int16 nOldDepth = pPara->GetDepth(); pPara->SetDepth( nDepth ); // For IsInUndo attributes and style do not have to be set, there // the old values are restored by the EditEngine. if( IsInUndo() ) return; bool bUpdate = pEditEngine->SetUpdateLayout( false ); bool bUndo = bCreateUndo && IsUndoEnabled(); SfxItemSet aAttrs( pEditEngine->GetParaAttribs( nPara ) ); aAttrs.Put( SfxInt16Item( EE_PARA_OUTLLEVEL, nDepth ) ); pEditEngine->SetParaAttribs( nPara, aAttrs ); ImplCheckNumBulletItem( nPara ); ImplCalcBulletText( nPara, false, false ); if ( bUndo ) { InsertUndo( std::make_unique( this, nPara, nOldDepth, nDepth ) ); } pEditEngine->SetUpdateLayout( bUpdate ); } void Outliner::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) { pEditEngine->SetParaAttribs( nPara, rSet ); } void Outliner::SetCharAttribs(sal_Int32 nPara, const SfxItemSet& rSet) { pEditEngine->SetCharAttribs(nPara, rSet); } bool Outliner::Expand( Paragraph const * pPara ) { if ( !pParaList->HasHiddenChildren( pPara ) ) return false; std::unique_ptr pUndo; bool bUndo = IsUndoEnabled() && !IsInUndo(); if( bUndo ) { UndoActionStart( OLUNDO_EXPAND ); pUndo.reset( new OLUndoExpand( this, OLUNDO_EXPAND ) ); pUndo->nCount = pParaList->GetAbsPos( pPara ); } pParaList->Expand( pPara ); InvalidateBullet(pParaList->GetAbsPos(pPara)); if( bUndo ) { InsertUndo( std::move(pUndo) ); UndoActionEnd(); } return true; } bool Outliner::Collapse( Paragraph const * pPara ) { if ( !pParaList->HasVisibleChildren( pPara ) ) // collapsed return false; std::unique_ptr pUndo; bool bUndo = false; if( !IsInUndo() && IsUndoEnabled() ) bUndo = true; if( bUndo ) { UndoActionStart( OLUNDO_COLLAPSE ); pUndo.reset( new OLUndoExpand( this, OLUNDO_COLLAPSE ) ); pUndo->nCount = pParaList->GetAbsPos( pPara ); } pParaList->Collapse( pPara ); InvalidateBullet(pParaList->GetAbsPos(pPara)); if( bUndo ) { InsertUndo( std::move(pUndo) ); UndoActionEnd(); } return true; } vcl::Font Outliner::ImpCalcBulletFont( sal_Int32 nPara ) const { const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); DBG_ASSERT( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ), "ImpCalcBulletFont: Missing or BitmapBullet!" ); vcl::Font aStdFont; if ( !pEditEngine->IsFlatMode() ) { ESelection aSel( nPara, 0, nPara, 0 ); aStdFont = EditEngine::CreateFontFromItemSet( pEditEngine->GetAttribs( aSel ), pEditEngine->GetScriptType( aSel ) ); } else { aStdFont = pEditEngine->GetStandardFont( nPara ); } vcl::Font aBulletFont; std::optional pSourceFont; if ( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL ) { pSourceFont = pFmt->GetBulletFont(); } if (pSourceFont) { aBulletFont = *pSourceFont; } else { aBulletFont = aStdFont; aBulletFont.SetUnderline( LINESTYLE_NONE ); aBulletFont.SetOverline( LINESTYLE_NONE ); aBulletFont.SetStrikeout( STRIKEOUT_NONE ); aBulletFont.SetEmphasisMark( FontEmphasisMark::NONE ); aBulletFont.SetRelief( FontRelief::NONE ); } // Use original scale... double nStretchY = 100.0; getGlobalScale(o3tl::temporary(double()), nStretchY, o3tl::temporary(double()), o3tl::temporary(double())); double fScale = pFmt->GetBulletRelSize() * nStretchY / 100.0; double fScaledLineHeight = aStdFont.GetFontSize().Height(); fScaledLineHeight *= fScale * 10; fScaledLineHeight /= 1000.0; aBulletFont.SetAlignment( ALIGN_BOTTOM ); aBulletFont.SetFontSize(Size(0, basegfx::fround(fScaledLineHeight))); bool bVertical = IsVertical(); aBulletFont.SetVertical( bVertical ); aBulletFont.SetOrientation( Degree10(bVertical ? (IsTopToBottom() ? 2700 : 900) : 0) ); Color aColor( COL_AUTO ); if( !pEditEngine->IsFlatMode() && !( pEditEngine->GetControlWord() & EEControlBits::NOCOLORS ) ) { aColor = pFmt->GetBulletColor(); } if ( ( aColor == COL_AUTO ) || ( IsForceAutoColor() ) ) aColor = pEditEngine->GetAutoColor(); aBulletFont.SetColor( aColor ); return aBulletFont; } void Outliner::PaintBullet(sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev) { bool bDrawBullet = false; if (pEditEngine) { const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ); bDrawBullet = rBulletState.GetValue(); } if (!(bDrawBullet && ImplHasNumberFormat(nPara))) return; bool bVertical = IsVertical(); bool bTopToBottom = IsTopToBottom(); bool bRightToLeftPara = pEditEngine->IsRightToLeft( nPara ); tools::Rectangle aBulletArea( ImpCalcBulletArea( nPara, true, false ) ); double nStretchX = 100.0; getGlobalScale(o3tl::temporary(double()), o3tl::temporary(double()), nStretchX, o3tl::temporary(double())); tools::Long nStretchBulletX = basegfx::fround(double(aBulletArea.Left()) * nStretchX / 100.0); tools::Long nStretchBulletWidth = basegfx::fround(double(aBulletArea.GetWidth()) * nStretchX / 100.0); aBulletArea = tools::Rectangle(Point(nStretchBulletX, aBulletArea.Top()), Size(nStretchBulletWidth, aBulletArea.GetHeight()) ); Paragraph* pPara = pParaList->GetParagraph( nPara ); const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); if ( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) ) { if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) { vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); // Use baseline bool bSymbol = pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL; aBulletFont.SetAlignment( bSymbol ? ALIGN_BOTTOM : ALIGN_BASELINE ); vcl::Font aOldFont = rOutDev.GetFont(); rOutDev.SetFont( aBulletFont ); ParagraphInfos aParaInfos = pEditEngine->GetParagraphInfos( nPara ); Point aTextPos; if ( !bVertical ) { // aTextPos.Y() = rStartPos.Y() + aBulletArea.Bottom(); aTextPos.setY( rStartPos.Y() + ( bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent ) ); if ( !bRightToLeftPara ) aTextPos.setX( rStartPos.X() + aBulletArea.Left() ); else aTextPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() ); } else { if (bTopToBottom) { // aTextPos.X() = rStartPos.X() - aBulletArea.Bottom(); aTextPos.setX( rStartPos.X() - (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) ); aTextPos.setY( rStartPos.Y() + aBulletArea.Left() ); } else { aTextPos.setX( rStartPos.X() + (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) ); aTextPos.setY( rStartPos.Y() + aBulletArea.Left() ); } } if ( nOrientation ) { // Both TopLeft and bottom left is not quite correct, // since in EditEngine baseline ... double nRealOrientation = toRadians(nOrientation); double nCos = cos( nRealOrientation ); double nSin = sin( nRealOrientation ); Point aRotatedPos; // Translation... aTextPos -= rOrigin; // Rotation... aRotatedPos.setX(static_cast(nCos*aTextPos.X() + nSin*aTextPos.Y()) ); aRotatedPos.setY(static_cast(- (nSin*aTextPos.X() - nCos*aTextPos.Y())) ); aTextPos = aRotatedPos; // Translation... aTextPos += rOrigin; vcl::Font aRotatedFont( aBulletFont ); aRotatedFont.SetOrientation( nOrientation ); rOutDev.SetFont( aRotatedFont ); } // VCL will take care of brackets and so on... vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode(); nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::BiDiStrong); if ( bRightToLeftPara ) nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft | vcl::text::ComplexTextLayoutFlags::BiDiStrong; rOutDev.SetLayoutMode( nLayoutMode ); if(bStrippingPortions) { const vcl::Font& aSvxFont(rOutDev.GetFont()); KernArray aBuf; rOutDev.GetTextArray( pPara->GetText(), &aBuf ); if(bSymbol) { // aTextPos is Bottom, go to Baseline FontMetric aMetric(rOutDev.GetFontMetric()); aTextPos.AdjustY( -(aMetric.GetDescent()) ); } assert(aBuf.get_factor() == 1); DrawingText(aTextPos, pPara->GetText(), 0, pPara->GetText().getLength(), aBuf.get_subunit_array(), {}, aSvxFont, nPara, bRightToLeftPara ? 1 : 0, nullptr, nullptr, false, false, true, nullptr, Color(), Color()); } else { rOutDev.DrawText( aTextPos, pPara->GetText() ); } rOutDev.SetFont( aOldFont ); } else { if ( pFmt->GetBrush()->GetGraphicObject() ) { Point aBulletPos; if ( !bVertical ) { aBulletPos.setY( rStartPos.Y() + aBulletArea.Top() ); if ( !bRightToLeftPara ) aBulletPos.setX( rStartPos.X() + aBulletArea.Left() ); else aBulletPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() ); } else { if (bTopToBottom) { aBulletPos.setX( rStartPos.X() - aBulletArea.Bottom() ); aBulletPos.setY( rStartPos.Y() + aBulletArea.Left() ); } else { aBulletPos.setX( rStartPos.X() + aBulletArea.Top() ); aBulletPos.setY( rStartPos.Y() - aBulletArea.Right() ); } } if(bStrippingPortions) { if(aDrawBulletHdl.IsSet()) { // call something analog to aDrawPortionHdl (if set) and feed it something // analog to DrawPortionInfo... // created aDrawBulletHdl, Set/GetDrawBulletHdl. // created DrawBulletInfo and added handling to sdrtextdecomposition.cxx DrawBulletInfo aDrawBulletInfo( *pFmt->GetBrush()->GetGraphicObject(), aBulletPos, pPara->aBulSize); aDrawBulletHdl.Call(&aDrawBulletInfo); } } else { pFmt->GetBrush()->GetGraphicObject()->Draw(rOutDev, aBulletPos, pPara->aBulSize); } } } } // In case of collapsed subparagraphs paint a line before the text. if( !pParaList->HasChildren(pPara) || pParaList->HasVisibleChildren(pPara) || bStrippingPortions || nOrientation ) return; tools::Long nWidth = rOutDev.PixelToLogic( Size( 10, 0 ) ).Width(); Point aStartPos, aEndPos; if ( !bVertical ) { aStartPos.setY( rStartPos.Y() + aBulletArea.Bottom() ); if ( !bRightToLeftPara ) aStartPos.setX( rStartPos.X() + aBulletArea.Right() ); else aStartPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Left() ); aEndPos = aStartPos; aEndPos.AdjustX(nWidth ); } else { aStartPos.setX( rStartPos.X() - aBulletArea.Bottom() ); aStartPos.setY( rStartPos.Y() + aBulletArea.Right() ); aEndPos = aStartPos; aEndPos.AdjustY(nWidth ); } const Color& rOldLineColor = rOutDev.GetLineColor(); rOutDev.SetLineColor( COL_BLACK ); rOutDev.DrawLine( aStartPos, aEndPos ); rOutDev.SetLineColor( rOldLineColor ); } void Outliner::InvalidateBullet(sal_Int32 nPara) { tools::Long nLineHeight = static_cast(pEditEngine->GetLineHeight(nPara )); for (OutlinerView* pView : aViewList) { Point aPos( pView->pEditView->GetWindowPosTopLeft(nPara ) ); tools::Rectangle aRect( pView->GetOutputArea() ); aRect.SetRight( aPos.X() ); aRect.SetTop( aPos.Y() ); aRect.SetBottom( aPos.Y() ); aRect.AdjustBottom(nLineHeight ); pView->pEditView->InvalidateWindow(aRect); } } ErrCode Outliner::Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) { bool bOldUndo = pEditEngine->IsUndoEnabled(); EnableUndo( false ); bool bUpdate = pEditEngine->SetUpdateLayout( false ); Clear(); ImplBlockInsertionCallbacks( true ); ErrCode nRet = pEditEngine->Read( rInput, rBaseURL, eFormat, pHTTPHeaderAttrs ); bFirstParaIsEmpty = false; sal_Int32 nParas = pEditEngine->GetParagraphCount(); pParaList->Clear(); for ( sal_Int32 n = 0; n < nParas; n++ ) { std::unique_ptr pPara(new Paragraph( 0 )); pParaList->Append(std::move(pPara)); } ImpFilterIndents( 0, nParas-1 ); ImplBlockInsertionCallbacks( false ); pEditEngine->SetUpdateLayout( bUpdate ); EnableUndo( bOldUndo ); return nRet; } void Outliner::ImpFilterIndents( sal_Int32 nFirstPara, sal_Int32 nLastPara ) { bool bUpdate = pEditEngine->SetUpdateLayout( false ); Paragraph* pLastConverted = nullptr; for( sal_Int32 nPara = nFirstPara; nPara <= nLastPara; nPara++ ) { Paragraph* pPara = pParaList->GetParagraph( nPara ); if (pPara) { if( ImpConvertEdtToOut( nPara ) ) { pLastConverted = pPara; } else if ( pLastConverted ) { // Arrange normal paragraphs below the heading ... pPara->SetDepth( pLastConverted->GetDepth() ); } ImplInitDepth( nPara, pPara->GetDepth(), false ); } } pEditEngine->SetUpdateLayout( bUpdate ); } SfxUndoManager& Outliner::GetUndoManager() { return pEditEngine->GetUndoManager(); } SfxUndoManager* Outliner::SetUndoManager(SfxUndoManager* pNew) { return pEditEngine->SetUndoManager(pNew); } void Outliner::ImpTextPasted( sal_Int32 nStartPara, sal_Int32 nCount ) { bool bUpdate = pEditEngine->SetUpdateLayout( false ); const sal_Int32 nStart = nStartPara; Paragraph* pPara = pParaList->GetParagraph( nStartPara ); while( nCount && pPara ) { if( GetOutlinerMode() != OutlinerMode::TextObject ) { nDepthChangedHdlPrevDepth = pPara->GetDepth(); ParaFlag nPrevFlags = pPara->nFlags; ImpConvertEdtToOut( nStartPara ); if( nStartPara == nStart ) { // the existing paragraph has changed depth or flags if( (pPara->GetDepth() != nDepthChangedHdlPrevDepth) || (pPara->nFlags != nPrevFlags) ) DepthChangedHdl(pPara, nPrevFlags); } } else // EditEngine mode { sal_Int16 nDepth = -1; const SfxItemSet& rAttrs = pEditEngine->GetParaAttribs( nStartPara ); if ( rAttrs.GetItemState( EE_PARA_OUTLLEVEL ) == SfxItemState::SET ) { const SfxInt16Item& rLevel = rAttrs.Get( EE_PARA_OUTLLEVEL ); nDepth = rLevel.GetValue(); } if ( nDepth != GetDepth( nStartPara ) ) ImplInitDepth( nStartPara, nDepth, false ); } nCount--; nStartPara++; pPara = pParaList->GetParagraph( nStartPara ); } pEditEngine->SetUpdateLayout( bUpdate ); DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"ImpTextPasted failed"); } bool Outliner::IndentingPagesHdl( OutlinerView* pView ) { if( !aIndentingPagesHdl.IsSet() ) return true; return aIndentingPagesHdl.Call( pView ); } bool Outliner::ImpCanIndentSelectedPages( OutlinerView* pCurView ) { // The selected pages must already be set in advance through // ImpCalcSelectedPages // If the first paragraph is on level 0 it can not indented in any case, // possible there might be indentations in the following on the 0 level. if ( ( mnFirstSelPage == 0 ) && ( GetOutlinerMode() != OutlinerMode::TextObject ) ) { if ( nDepthChangedHdlPrevDepth == 1 ) // is the only page return false; else (void)pCurView->ImpCalcSelectedPages( false ); // without the first } return IndentingPagesHdl( pCurView ); } bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView ) { // The selected pages must already be set in advance through // ImpCalcSelectedPages return RemovingPagesHdl( pCurView ); } Outliner::Outliner(SfxItemPool* pPool, OutlinerMode nMode) : mnFirstSelPage(0) , nDepthChangedHdlPrevDepth(0) , nMaxDepth(9) , bFirstParaIsEmpty(true) , nBlockInsCallback(0) , bStrippingPortions(false) , bPasting(false) { pParaList.reset( new ParagraphList ); pParaList->SetVisibleStateChangedHdl( LINK( this, Outliner, ParaVisibleStateChangedHdl ) ); std::unique_ptr pPara(new Paragraph( 0 )); pParaList->Append(std::move(pPara)); pEditEngine.reset( new OutlinerEditEng( this, pPool ) ); pEditEngine->SetBeginMovingParagraphsHdl( LINK( this, Outliner, BeginMovingParagraphsHdl ) ); pEditEngine->SetEndMovingParagraphsHdl( LINK( this, Outliner, EndMovingParagraphsHdl ) ); pEditEngine->SetBeginPasteOrDropHdl( LINK( this, Outliner, BeginPasteOrDropHdl ) ); pEditEngine->SetEndPasteOrDropHdl( LINK( this, Outliner, EndPasteOrDropHdl ) ); Init( nMode ); } Outliner::~Outliner() { pParaList->Clear(); pParaList.reset(); pEditEngine.reset(); } size_t Outliner::InsertView( OutlinerView* pView, size_t nIndex ) { size_t ActualIndex; if ( nIndex >= aViewList.size() ) { aViewList.push_back( pView ); ActualIndex = aViewList.size() - 1; } else { ViewList::iterator it = aViewList.begin(); advance( it, nIndex ); ActualIndex = nIndex; } pEditEngine->InsertView( pView->pEditView.get(), nIndex ); return ActualIndex; } void Outliner::RemoveView( OutlinerView const * pView ) { ViewList::iterator it = std::find(aViewList.begin(), aViewList.end(), pView); if (it != aViewList.end()) { pView->pEditView->HideCursor(); // HACK pEditEngine->RemoveView( pView->pEditView.get() ); aViewList.erase( it ); } } void Outliner::RemoveView( size_t nIndex ) { EditView* pEditView = pEditEngine->GetView( nIndex ); pEditView->HideCursor(); // HACK pEditEngine->RemoveView( nIndex ); { ViewList::iterator it = aViewList.begin(); advance( it, nIndex ); aViewList.erase( it ); } } OutlinerView* Outliner::GetView( size_t nIndex ) const { return ( nIndex >= aViewList.size() ) ? nullptr : aViewList[ nIndex ]; } size_t Outliner::GetViewCount() const { return aViewList.size(); } void Outliner::ParagraphInsertedHdl(Paragraph* pPara) { if( !IsInUndo() ) aParaInsertedHdl.Call( { this, pPara } ); } void Outliner::DepthChangedHdl(Paragraph* pPara, ParaFlag nPrevFlags) { if( !IsInUndo() ) aDepthChangedHdl.Call( { this, pPara, nPrevFlags } ); } sal_Int32 Outliner::GetAbsPos( Paragraph const * pPara ) const { DBG_ASSERT(pPara,"GetAbsPos:No Para"); return pParaList->GetAbsPos( pPara ); } sal_Int32 Outliner::GetParagraphCount() const { return pParaList->GetParagraphCount(); } Paragraph* Outliner::GetParagraph( sal_Int32 nAbsPos ) const { return pParaList->GetParagraph( nAbsPos ); } bool Outliner::HasChildren( Paragraph const * pParagraph ) const { return pParaList->HasChildren( pParagraph ); } bool Outliner::ImplHasNumberFormat( sal_Int32 nPara ) const { return GetNumberFormat(nPara) != nullptr; } const SvxNumberFormat* Outliner::GetNumberFormat( sal_Int32 nPara ) const { const SvxNumberFormat* pFmt = nullptr; Paragraph* pPara = pParaList->GetParagraph( nPara ); if (!pPara) return nullptr; sal_Int16 nDepth = pPara->GetDepth(); if( nDepth >= 0 ) { const SvxNumBulletItem& rNumBullet = pEditEngine->GetParaAttrib( nPara, EE_PARA_NUMBULLET ); if ( rNumBullet.GetNumRule().GetLevelCount() > nDepth ) pFmt = rNumBullet.GetNumRule().Get( nDepth ); } return pFmt; } Size Outliner::ImplGetBulletSize( sal_Int32 nPara ) { Paragraph* pPara = pParaList->GetParagraph( nPara ); if (!pPara) return Size(); if( pPara->aBulSize.Width() == -1 ) { const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); DBG_ASSERT( pFmt, "ImplGetBulletSize - no Bullet!" ); if ( pFmt->GetNumberingType() == SVX_NUM_NUMBER_NONE ) { pPara->aBulSize = Size( 0, 0 ); } else if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) { OUString aBulletText = ImplGetBulletText( nPara ); OutputDevice* pRefDev = pEditEngine->GetRefDevice(); vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); vcl::Font aRefFont( pRefDev->GetFont()); pRefDev->SetFont( aBulletFont ); pPara->aBulSize.setWidth( pRefDev->GetTextWidth( aBulletText ) ); pPara->aBulSize.setHeight( pRefDev->GetTextHeight() ); pRefDev->SetFont( aRefFont ); } else { pPara->aBulSize = OutputDevice::LogicToLogic(pFmt->GetGraphicSize(), MapMode(MapUnit::Map100thMM), pEditEngine->GetRefDevice()->GetMapMode()); } } return pPara->aBulSize; } void Outliner::ImplCheckParagraphs( sal_Int32 nStart, sal_Int32 nEnd ) { for ( sal_Int32 n = nStart; n < nEnd; n++ ) { Paragraph* pPara = pParaList->GetParagraph( n ); if (pPara) { pPara->Invalidate(); ImplCalcBulletText( n, false, false ); } } } void Outliner::SetRefDevice( OutputDevice* pRefDev ) { pEditEngine->SetRefDevice( pRefDev ); for ( sal_Int32 n = pParaList->GetParagraphCount(); n; ) { Paragraph* pPara = pParaList->GetParagraph( --n ); pPara->Invalidate(); } } void Outliner::ParaAttribsChanged( sal_Int32 nPara ) { // The Outliner does not have an undo of its own, when paragraphs are // separated/merged. When ParagraphInserted the attribute EE_PARA_OUTLLEVEL // may not be set, this is however needed when the depth of the paragraph // is to be determined. if (!pEditEngine->IsInUndo()) return; if (pParaList->GetParagraphCount() != pEditEngine->GetParagraphCount()) return; Paragraph* pPara = pParaList->GetParagraph(nPara); if (!pPara) return; // tdf#100734: force update of bullet pPara->Invalidate(); const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); if (pPara->GetDepth() == rLevel.GetValue()) return; pPara->SetDepth(rLevel.GetValue()); ImplCalcBulletText(nPara, true, true); } void Outliner::StyleSheetChanged( SfxStyleSheet const * pStyle ) { // The EditEngine calls StyleSheetChanged also for derived styles. // Here all the paragraphs, which had the said template, used to be // hunted by an ImpRecalcParaAttribs, why? // => only the Bullet-representation can really change... sal_Int32 nParas = pParaList->GetParagraphCount(); for( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) { if ( pEditEngine->GetStyleSheet( nPara ) == pStyle ) { ImplCheckNumBulletItem( nPara ); ImplCalcBulletText( nPara, false, false ); // EditEngine formats changed paragraphs before calling this method, // so they are not reformatted now and use wrong bullet indent pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); } } } tools::Rectangle Outliner::ImpCalcBulletArea( sal_Int32 nPara, bool bAdjust, bool bReturnPaperPos ) { // Bullet area within the paragraph ... tools::Rectangle aBulletArea; const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); if ( pFmt ) { Point aTopLeft; Size aBulletSize( ImplGetBulletSize( nPara ) ); bool bOutlineMode = bool( pEditEngine->GetControlWord() & EEControlBits::OUTLINER ); // the ODF attribute text:space-before which holds the spacing to add to the left of the label const auto nSpaceBefore = pFmt->GetAbsLSpace() + pFmt->GetFirstLineOffset(); const SvxLRSpaceItem& rLR = pEditEngine->GetParaAttrib( nPara, bOutlineMode ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); aTopLeft.setX( rLR.GetTextLeft() + rLR.GetTextFirstLineOffset() + nSpaceBefore ); tools::Long nBulletWidth = std::max( static_cast(-rLR.GetTextFirstLineOffset()), static_cast((-pFmt->GetFirstLineOffset()) + pFmt->GetCharTextDistance()) ); if ( nBulletWidth < aBulletSize.Width() ) // The Bullet creates its space nBulletWidth = aBulletSize.Width(); if ( bAdjust && !bOutlineMode ) { // Adjust when centered or align right const SvxAdjustItem& rItem = pEditEngine->GetParaAttrib( nPara, EE_PARA_JUST ); if ( ( !pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Left ) ) || ( pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Right ) ) ) { aTopLeft.setX( pEditEngine->GetFirstLineStartX( nPara ) - nBulletWidth ); } } // Vertical: ParagraphInfos aInfos = pEditEngine->GetParagraphInfos( nPara ); if ( aInfos.bValid ) { aTopLeft.setY( /* aInfos.nFirstLineOffset + */ // nFirstLineOffset is already added to the StartPos (PaintBullet) from the EditEngine aInfos.nFirstLineHeight - aInfos.nFirstLineTextHeight + aInfos.nFirstLineTextHeight / 2 - aBulletSize.Height() / 2 ); // may prefer to print out on the baseline ... if( ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) ) { vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); if ( aBulletFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ) { OutputDevice* pRefDev = pEditEngine->GetRefDevice(); vcl::Font aOldFont = pRefDev->GetFont(); pRefDev->SetFont( aBulletFont ); FontMetric aMetric( pRefDev->GetFontMetric() ); // Leading on the first line ... aTopLeft.setY( /* aInfos.nFirstLineOffset + */ aInfos.nFirstLineMaxAscent ); aTopLeft.AdjustY( -(aMetric.GetAscent()) ); pRefDev->SetFont( aOldFont ); } } } // Horizontal: if( pFmt->GetNumAdjust() == SvxAdjust::Right ) { aTopLeft.AdjustX(nBulletWidth - aBulletSize.Width() ); } else if( pFmt->GetNumAdjust() == SvxAdjust::Center ) { aTopLeft.AdjustX(( nBulletWidth - aBulletSize.Width() ) / 2 ); } if ( aTopLeft.X() < 0 ) // then push aTopLeft.setX( 0 ); aBulletArea = tools::Rectangle( aTopLeft, aBulletSize ); } if ( bReturnPaperPos ) { Size aBulletSize( aBulletArea.GetSize() ); Point aBulletDocPos( aBulletArea.TopLeft() ); aBulletDocPos.AdjustY(pEditEngine->GetDocPosTopLeft( nPara ).Y() ); Point aBulletPos( aBulletDocPos ); if ( IsVertical() ) { aBulletPos.setY( aBulletDocPos.X() ); aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.Y() ); // Rotate: aBulletPos.AdjustX( -(aBulletSize.Height()) ); Size aSz( aBulletSize ); aBulletSize.setWidth( aSz.Height() ); aBulletSize.setHeight( aSz.Width() ); } else if ( pEditEngine->IsRightToLeft( nPara ) ) { aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.X() - aBulletSize.Width() ); } aBulletArea = tools::Rectangle( aBulletPos, aBulletSize ); } return aBulletArea; } EBulletInfo Outliner::GetBulletInfo( sal_Int32 nPara ) { EBulletInfo aInfo; aInfo.nParagraph = nPara; aInfo.bVisible = ImplHasNumberFormat( nPara ); const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); aInfo.nType = pFmt ? pFmt->GetNumberingType() : 0; if( pFmt ) { if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) { aInfo.aText = ImplGetBulletText( nPara ); if( pFmt->GetBulletFont() ) aInfo.aFont = *pFmt->GetBulletFont(); } } if ( aInfo.bVisible ) { aInfo.aBounds = ImpCalcBulletArea( nPara, true, true ); } return aInfo; } OUString Outliner::GetText( Paragraph const * pParagraph, sal_Int32 nCount ) const { OUStringBuffer aText(128); sal_Int32 nStartPara = pParaList->GetAbsPos( pParagraph ); for ( sal_Int32 n = 0; n < nCount; n++ ) { aText.append(pEditEngine->GetText( nStartPara + n )); if ( (n+1) < nCount ) aText.append("\n"); } return aText.makeStringAndClear(); } void Outliner::Remove( Paragraph const * pPara, sal_Int32 nParaCount ) { sal_Int32 nPos = pParaList->GetAbsPos( pPara ); if( !nPos && ( nParaCount >= pParaList->GetParagraphCount() ) ) { Clear(); } else { for( sal_Int32 n = 0; n < nParaCount; n++ ) pEditEngine->RemoveParagraph( nPos ); } } void Outliner::StripPortions() { bStrippingPortions = true; pEditEngine->StripPortions(); bStrippingPortions = false; } void Outliner::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart, sal_Int32 nTextLen, o3tl::span pDXArray, o3tl::span pKashidaArray, const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft, const EEngineData::WrongSpellVector* pWrongSpellVector, const SvxFieldData* pFieldData, bool bEndOfLine, bool bEndOfParagraph, bool bEndOfBullet, const css::lang::Locale* pLocale, const Color& rOverlineColor, const Color& rTextLineColor) { if(aDrawPortionHdl.IsSet()) { DrawPortionInfo aInfo( rStartPos, rText, nTextStart, nTextLen, rFont, nPara, pDXArray, pKashidaArray, pWrongSpellVector, pFieldData, pLocale, rOverlineColor, rTextLineColor, nRightToLeft, false, 0, bEndOfLine, bEndOfParagraph, bEndOfBullet); aDrawPortionHdl.Call( &aInfo ); } } void Outliner::DrawingTab( const Point& rStartPos, tools::Long nWidth, const OUString& rChar, const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft, bool bEndOfLine, bool bEndOfParagraph, const Color& rOverlineColor, const Color& rTextLineColor) { if(aDrawPortionHdl.IsSet()) { DrawPortionInfo aInfo( rStartPos, rChar, 0, rChar.getLength(), rFont, nPara, {}, {}, nullptr, nullptr, nullptr, rOverlineColor, rTextLineColor, nRightToLeft, true, nWidth, bEndOfLine, bEndOfParagraph, false); aDrawPortionHdl.Call( &aInfo ); } } bool Outliner::RemovingPagesHdl( OutlinerView* pView ) { return !aRemovingPagesHdl.IsSet() || aRemovingPagesHdl.Call( pView ); } bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView, sal_Int32 _nFirstPage, sal_Int32 nPages ) { nDepthChangedHdlPrevDepth = nPages; mnFirstSelPage = _nFirstPage; return RemovingPagesHdl( pCurView ); } SfxItemSet const & Outliner::GetParaAttribs( sal_Int32 nPara ) const { return pEditEngine->GetParaAttribs( nPara ); } IMPL_LINK( Outliner, ParaVisibleStateChangedHdl, Paragraph&, rPara, void ) { sal_Int32 nPara = pParaList->GetAbsPos( &rPara ); pEditEngine->ShowParagraph( nPara, rPara.IsVisible() ); } IMPL_LINK_NOARG(Outliner, BeginMovingParagraphsHdl, MoveParagraphsInfo&, void) { if( !IsInUndo() ) aBeginMovingHdl.Call( this ); } IMPL_LINK( Outliner, BeginPasteOrDropHdl, PasteOrDropInfos&, rInfos, void ) { UndoActionStart( EDITUNDO_DRAGANDDROP ); maBeginPasteOrDropHdl.Call(&rInfos); } IMPL_LINK( Outliner, EndPasteOrDropHdl, PasteOrDropInfos&, rInfos, void ) { bPasting = false; ImpTextPasted( rInfos.nStartPara, rInfos.nEndPara - rInfos.nStartPara + 1 ); maEndPasteOrDropHdl.Call( &rInfos ); UndoActionEnd(); } IMPL_LINK( Outliner, EndMovingParagraphsHdl, MoveParagraphsInfo&, rInfos, void ) { pParaList->MoveParagraphs( rInfos.nStartPara, rInfos.nDestPara, rInfos.nEndPara - rInfos.nStartPara + 1 ); sal_Int32 nChangesStart = std::min( rInfos.nStartPara, rInfos.nDestPara ); sal_Int32 nParas = pParaList->GetParagraphCount(); for ( sal_Int32 n = nChangesStart; n < nParas; n++ ) ImplCalcBulletText( n, false, false ); if( !IsInUndo() ) aEndMovingHdl.Call( this ); } static bool isSameNumbering( const SvxNumberFormat& rN1, const SvxNumberFormat& rN2 ) { if( rN1.GetNumberingType() != rN2.GetNumberingType() ) return false; if( rN1.GetNumStr(1) != rN2.GetNumStr(1) ) return false; if( (rN1.GetPrefix() != rN2.GetPrefix()) || (rN1.GetSuffix() != rN2.GetSuffix()) ) return false; return true; } sal_uInt16 Outliner::ImplGetNumbering( sal_Int32 nPara, const SvxNumberFormat* pParaFmt ) { sal_uInt16 nNumber = pParaFmt->GetStart() - 1; Paragraph* pPara = pParaList->GetParagraph( nPara ); const sal_Int16 nParaDepth = pPara->GetDepth(); do { pPara = pParaList->GetParagraph( nPara ); const sal_Int16 nDepth = pPara->GetDepth(); // ignore paragraphs that are below our paragraph or have no numbering if( (nDepth > nParaDepth) || (nDepth == -1) ) continue; // stop on paragraphs that are above our paragraph if( nDepth < nParaDepth ) break; const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); if( pFmt == nullptr ) continue; // ignore paragraphs without bullets // check if numbering less than or equal to pParaFmt if( !isSameNumbering( *pFmt, *pParaFmt ) || ( pFmt->GetStart() < pParaFmt->GetStart() ) ) break; if ( pFmt->GetStart() > pParaFmt->GetStart() ) { nNumber += pFmt->GetStart() - pParaFmt->GetStart(); pParaFmt = pFmt; } const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ); if( rBulletState.GetValue() ) nNumber += 1; // same depth, same number format, check for restart const sal_Int16 nNumberingStartValue = pPara->GetNumberingStartValue(); if( (nNumberingStartValue != -1) || pPara->IsParaIsNumberingRestart() ) { if( nNumberingStartValue != -1 ) nNumber += nNumberingStartValue - 1; break; } } while( nPara-- ); return nNumber; } void Outliner::ImplCalcBulletText( sal_Int32 nPara, bool bRecalcLevel, bool bRecalcChildren ) { Paragraph* pPara = pParaList->GetParagraph( nPara ); while ( pPara ) { OUString aBulletText; const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); if( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) ) { aBulletText += pFmt->GetPrefix(); if( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL ) { sal_UCS4 cChar = pFmt->GetBulletChar(); aBulletText += OUString(&cChar, 1); } else if( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) { aBulletText += pFmt->GetNumStr( ImplGetNumbering( nPara, pFmt ) ); } aBulletText += pFmt->GetSuffix(); } if (pPara->GetText() != aBulletText) pPara->SetText( aBulletText ); if ( bRecalcLevel ) { sal_Int16 nDepth = pPara->GetDepth(); pPara = pParaList->GetParagraph( ++nPara ); if ( !bRecalcChildren ) { while ( pPara && ( pPara->GetDepth() > nDepth ) ) pPara = pParaList->GetParagraph( ++nPara ); } if ( pPara && ( pPara->GetDepth() < nDepth ) ) pPara = nullptr; } else { pPara = nullptr; } } } void Outliner::Clear() { if( !bFirstParaIsEmpty ) { ImplBlockInsertionCallbacks( true ); pEditEngine->Clear(); pParaList->Clear(); pParaList->Append( std::unique_ptr(new Paragraph( gnMinDepth ))); bFirstParaIsEmpty = true; ImplBlockInsertionCallbacks( false ); } else { Paragraph* pPara = pParaList->GetParagraph( 0 ); if(pPara) pPara->SetDepth( gnMinDepth ); } } void Outliner::SetFlatMode( bool bFlat ) { if( bFlat != pEditEngine->IsFlatMode() ) { for ( sal_Int32 nPara = pParaList->GetParagraphCount(); nPara; ) pParaList->GetParagraph( --nPara )->aBulSize.setWidth( -1 ); pEditEngine->SetFlatMode( bFlat ); } } OUString Outliner::ImplGetBulletText( sal_Int32 nPara ) { OUString aRes; Paragraph* pPara = pParaList->GetParagraph( nPara ); if (pPara) { ImplCalcBulletText( nPara, false, false ); aRes = pPara->GetText(); } return aRes; } // this is needed for StarOffice Api void Outliner::SetLevelDependentStyleSheet( sal_Int32 nPara ) { SfxItemSet aOldAttrs( pEditEngine->GetParaAttribs( nPara ) ); ImplSetLevelDependentStyleSheet( nPara ); pEditEngine->SetParaAttribs( nPara, aOldAttrs ); } void Outliner::ImplBlockInsertionCallbacks( bool b ) { if ( b ) { nBlockInsCallback++; } else { DBG_ASSERT( nBlockInsCallback, "ImplBlockInsertionCallbacks ?!" ); nBlockInsCallback--; if ( !nBlockInsCallback ) { // Call blocked notify events... while(!pEditEngine->aNotifyCache.empty()) { EENotify aNotify(pEditEngine->aNotifyCache.front()); // Remove from list before calling, maybe we enter LeaveBlockNotifications while calling the handler... pEditEngine->aNotifyCache.erase(pEditEngine->aNotifyCache.begin()); pEditEngine->aOutlinerNotifyHdl.Call( aNotify ); } } } } IMPL_LINK( Outliner, EditEngineNotifyHdl, EENotify&, rNotify, void ) { if ( !nBlockInsCallback ) pEditEngine->aOutlinerNotifyHdl.Call( rNotify ); else pEditEngine->aNotifyCache.push_back(rNotify); } /** sets a link that is called at the beginning of a drag operation at an edit view */ void Outliner::SetBeginDropHdl( const Link& rLink ) { pEditEngine->SetBeginDropHdl( rLink ); } /** sets a link that is called at the end of a drag operation at an edit view */ void Outliner::SetEndDropHdl( const Link& rLink ) { pEditEngine->SetEndDropHdl( rLink ); } /** sets a link that is called before a drop or paste operation. */ void Outliner::SetBeginPasteOrDropHdl( const Link& rLink ) { maBeginPasteOrDropHdl = rLink; } /** sets a link that is called after a drop or paste operation. */ void Outliner::SetEndPasteOrDropHdl( const Link& rLink ) { maEndPasteOrDropHdl = rLink; } void Outliner::SetParaFlag( Paragraph* pPara, ParaFlag nFlag ) { if( pPara && !pPara->HasFlag( nFlag ) ) { if( IsUndoEnabled() && !IsInUndo() ) InsertUndo( std::make_unique( this, GetAbsPos( pPara ), pPara->nFlags, pPara->nFlags|nFlag ) ); pPara->SetFlag( nFlag ); } } bool Outliner::HasParaFlag( const Paragraph* pPara, ParaFlag nFlag ) { return pPara && pPara->HasFlag( nFlag ); } bool Outliner::IsPageOverflow() { return pEditEngine->IsPageOverflow(); } std::optional Outliner::GetNonOverflowingText() const { /* XXX: * nCount should be the number of paragraphs of the non overflowing text * nStart should be the starting paragraph of the non overflowing text (XXX: Always 0?) */ if ( GetParagraphCount() < 1 ) return {}; // last non-overflowing paragraph is before the first overflowing one sal_Int32 nCount = pEditEngine->GetOverflowingParaNum(); sal_Int32 nOverflowLine = pEditEngine->GetOverflowingLineNum(); // XXX: Unused for now // Defensive check: overflowing para index beyond actual # of paragraphs? if ( nCount > GetParagraphCount()-1) { SAL_INFO("editeng.chaining", "[Overflowing] Ops, trying to retrieve para " << nCount << " when max index is " << GetParagraphCount()-1 ); return {}; } if (nCount < 0) { SAL_INFO("editeng.chaining", "[Overflowing] No Overflowing text but GetNonOverflowinText called?!"); return {}; } // NOTE: We want the selection of the overflowing text from here // At the same time we may want to consider the beginning of such text // in a more fine grained way (i.e. as GetNonOverflowingText did) /* sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum(); sal_uInt32 nParaCount = GetParagraphCount(); sal_uInt32 nLen = 0; for ( sal_Int32 nLine = 0; nLine < pEditEngine->GetOverflowingLineNum(); nLine++) { nLen += GetLineLen(nHeadPara, nLine); } sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum(); ESelection aOverflowingTextSel; sal_Int32 nLastPara = nParaCount-1; sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength(); aOverflowingTextSel = ESelection(nOverflowingPara, nLen, nLastPara, nLastParaLen); bool bLastParaInterrupted = pEditEngine->GetOverflowingLineNum() > 0; return new NonOverflowingText(aOverflowingTextSel, bLastParaInterrupted); **/ // Only overflowing text, i.e. 1st line of 1st paragraph overflowing bool bItAllOverflew = nCount == 0 && nOverflowLine == 0; if ( bItAllOverflew ) { ESelection aEmptySel(0,0,0,0); //EditTextObject *pTObj = pEditEngine->CreateTextObject(aEmptySel); bool const bLastParaInterrupted = true; // Last Para was interrupted since everything overflew return NonOverflowingText(aEmptySel, bLastParaInterrupted); } else { // Get the lines that of the overflowing para fit in the box sal_Int32 nOverflowingPara = nCount; sal_uInt32 nLen = 0; for ( sal_Int32 nLine = 0; nLine < pEditEngine->GetOverflowingLineNum(); nLine++) { nLen += GetLineLen(nOverflowingPara, nLine); } //sal_Int32 nStartPara = 0; //sal_Int32 nStartPos = 0; ESelection aOverflowingTextSelection; const sal_Int32 nEndPara = GetParagraphCount()-1; const sal_Int32 nEndPos = pEditEngine->GetTextLen(nEndPara); if (nLen == 0) { // XXX: What happens inside this case might be dependent on the joining paragraph or not-thingy // Overflowing paragraph is empty or first line overflowing: it's not "Non-Overflowing" text then sal_Int32 nParaLen = GetText(GetParagraph(nOverflowingPara-1)).getLength(); aOverflowingTextSelection = ESelection(nOverflowingPara-1, nParaLen, nEndPara, nEndPos); } else { // We take until we have to from the overflowing paragraph aOverflowingTextSelection = ESelection(nOverflowingPara, nLen, nEndPara, nEndPos); } //EditTextObject *pTObj = pEditEngine->CreateTextObject(aNonOverflowingTextSelection); //sal_Int32 nLastLine = GetLineCount(nOverflowingPara)-1; bool bLastParaInterrupted = pEditEngine->GetOverflowingLineNum() > 0; return NonOverflowingText(aOverflowingTextSelection, bLastParaInterrupted); } } OutlinerParaObject Outliner::GetEmptyParaObject() const { std::unique_ptr pEmptyText = pEditEngine->GetEmptyTextObject(); OutlinerParaObject aPObj( std::move(pEmptyText) ); aPObj.SetOutlinerMode(GetOutlinerMode()); return aPObj; } std::optional Outliner::GetOverflowingText() const { if ( pEditEngine->GetOverflowingParaNum() < 0) return {}; // Defensive check: overflowing para index beyond actual # of paragraphs? if ( pEditEngine->GetOverflowingParaNum() > GetParagraphCount()-1) { SAL_INFO("editeng.chaining", "[Overflowing] Ops, trying to retrieve para " << pEditEngine->GetOverflowingParaNum() << " when max index is " << GetParagraphCount()-1 ); return {}; } sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum(); sal_uInt32 nParaCount = GetParagraphCount(); sal_uInt32 nLen = 0; for ( sal_Int32 nLine = 0; nLine < pEditEngine->GetOverflowingLineNum(); nLine++) { nLen += GetLineLen(nHeadPara, nLine); } sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum(); ESelection aOverflowingTextSel; sal_Int32 nLastPara = nParaCount-1; sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength(); aOverflowingTextSel = ESelection(nOverflowingPara, nLen, nLastPara, nLastParaLen); return OverflowingText(pEditEngine->CreateTransferable(aOverflowingTextSel)); } void Outliner::ClearOverflowingParaNum() { pEditEngine->ClearOverflowingParaNum(); } void Outliner::dumpAsXml(xmlTextWriterPtr pWriter) const { bool bOwns = false; if (!pWriter) { pWriter = xmlNewTextWriterFilename("outliner.xml", 0); xmlTextWriterSetIndent(pWriter,1); (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); bOwns = true; } (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Outliner")); pParaList->dumpAsXml(pWriter); (void)xmlTextWriterEndElement(pWriter); if (bOwns) { (void)xmlTextWriterEndDocument(pWriter); xmlFreeTextWriter(pWriter); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */