/* -*- 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 "outleeng.hxx" #include "paralist.hxx" #include "outlundo.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; OutlinerView::OutlinerView( Outliner* pOut, vcl::Window* pWin ) { pOwner = pOut; pEditView.reset( new EditView( pOut->pEditEngine.get(), pWin ) ); } OutlinerView::~OutlinerView() { } void OutlinerView::Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) { // For the first Paint/KeyInput/Drop an empty Outliner is turned into // an Outliner with exactly one paragraph. if( pOwner->bFirstParaIsEmpty ) pOwner->Insert( OUString() ); pEditView->Paint( rRect, pTargetDevice ); } bool OutlinerView::PostKeyEvent( const KeyEvent& rKEvt, vcl::Window const * pFrameWin ) { // For the first Paint/KeyInput/Drop an empty Outliner is turned into // an Outliner with exactly one paragraph. if( pOwner->bFirstParaIsEmpty ) pOwner->Insert( OUString() ); bool bKeyProcessed = false; ESelection aSel( pEditView->GetSelection() ); bool bSelection = aSel.HasRange(); vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); KeyFuncType eFunc = aKeyCode.GetFunction(); sal_uInt16 nCode = aKeyCode.GetCode(); bool bReadOnly = IsReadOnly(); if( bSelection && ( nCode != KEY_TAB ) && EditEngine::DoesKeyChangeText( rKEvt ) ) { if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) return true; } if ( eFunc != KeyFuncType::DONTKNOW ) { switch ( eFunc ) { case KeyFuncType::CUT: { if ( !bReadOnly ) { Cut(); bKeyProcessed = true; } } break; case KeyFuncType::COPY: { Copy(); bKeyProcessed = true; } break; case KeyFuncType::PASTE: { if ( !bReadOnly ) { PasteSpecial(); bKeyProcessed = true; } } break; case KeyFuncType::DELETE: { if( !bReadOnly && !bSelection && ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject ) ) { if( aSel.nEndPos == pOwner->pEditEngine->GetTextLen( aSel.nEndPara ) ) { Paragraph* pNext = pOwner->pParaList->GetParagraph( aSel.nEndPara+1 ); if( pNext && pNext->HasFlag(ParaFlag::ISPAGE) ) { if( !pOwner->ImpCanDeleteSelectedPages( this, aSel.nEndPara, 1 ) ) return false; } } } } break; default: // is then possibly edited below. eFunc = KeyFuncType::DONTKNOW; } } if ( eFunc == KeyFuncType::DONTKNOW ) { switch ( nCode ) { case KEY_TAB: { if ( !bReadOnly && !aKeyCode.IsMod1() && !aKeyCode.IsMod2() ) { if ( ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject ) && ( pOwner->GetOutlinerMode() != OutlinerMode::TitleObject ) && ( bSelection || !aSel.nStartPos ) ) { Indent( aKeyCode.IsShift() ? -1 : +1 ); bKeyProcessed = true; } else if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) && !bSelection && !aSel.nEndPos && pOwner->ImplHasNumberFormat( aSel.nEndPara ) ) { Indent( aKeyCode.IsShift() ? -1 : +1 ); bKeyProcessed = true; } } } break; case KEY_BACKSPACE: { if( !bReadOnly && !bSelection && aSel.nEndPara && !aSel.nEndPos ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( aSel.nEndPara ); Paragraph* pPrev = pOwner->pParaList->GetParagraph( aSel.nEndPara-1 ); if( !pPrev->IsVisible() ) return true; if( !pPara->GetDepth() ) { if(!pOwner->ImpCanDeleteSelectedPages(this, aSel.nEndPara , 1 ) ) return true; } } } break; case KEY_RETURN: { if ( !bReadOnly ) { // Special treatment: hard return at the end of a paragraph, // which has collapsed subparagraphs. Paragraph* pPara = pOwner->pParaList->GetParagraph( aSel.nEndPara ); if( !aKeyCode.IsShift() ) { // ImpGetCursor again??? if( !bSelection && aSel.nEndPos == pOwner->pEditEngine->GetTextLen( aSel.nEndPara ) ) { sal_Int32 nChildren = pOwner->pParaList->GetChildCount(pPara); if( nChildren && !pOwner->pParaList->HasVisibleChildren(pPara)) { pOwner->UndoActionStart( OLUNDO_INSERT ); sal_Int32 nTemp = aSel.nEndPara; nTemp += nChildren; nTemp++; // insert above next Non-Child SAL_WARN_IF( nTemp < 0, "editeng", "OutlinerView::PostKeyEvent - overflow"); if (nTemp >= 0) { pOwner->Insert( OUString(),nTemp,pPara->GetDepth()); // Position the cursor ESelection aTmpSel(nTemp,0,nTemp,0); pEditView->SetSelection( aTmpSel ); } pEditView->ShowCursor(); pOwner->UndoActionEnd(); bKeyProcessed = true; } } } if( !bKeyProcessed && !bSelection && !aKeyCode.IsShift() && aKeyCode.IsMod1() && ( aSel.nEndPos == pOwner->pEditEngine->GetTextLen(aSel.nEndPara) ) ) { pOwner->UndoActionStart( OLUNDO_INSERT ); sal_Int32 nTemp = aSel.nEndPara; nTemp++; pOwner->Insert( OUString(), nTemp, pPara->GetDepth()+1 ); // Position the cursor ESelection aTmpSel(nTemp,0,nTemp,0); pEditView->SetSelection( aTmpSel ); pEditView->ShowCursor(); pOwner->UndoActionEnd(); bKeyProcessed = true; } } } break; } } return bKeyProcessed || pEditView->PostKeyEvent( rKEvt, pFrameWin ); } sal_Int32 OutlinerView::ImpCheckMousePos(const Point& rPosPix, MouseTarget& reTarget) { sal_Int32 nPara = EE_PARA_NOT_FOUND; Point aMousePosWin = pEditView->GetOutputDevice().PixelToLogic( rPosPix ); if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) { reTarget = MouseTarget::Outside; } else { reTarget = MouseTarget::Text; Point aPaperPos( aMousePosWin ); tools::Rectangle aOutArea = pEditView->GetOutputArea(); tools::Rectangle aVisArea = pEditView->GetVisArea(); aPaperPos.AdjustX( -(aOutArea.Left()) ); aPaperPos.AdjustX(aVisArea.Left() ); aPaperPos.AdjustY( -(aOutArea.Top()) ); aPaperPos.AdjustY(aVisArea.Top() ); bool bBullet; if ( pOwner->IsTextPos( aPaperPos, 0, &bBullet ) ) { Point aDocPos = pOwner->GetDocPos( aPaperPos ); nPara = pOwner->pEditEngine->FindParagraph( aDocPos.Y() ); if ( bBullet ) { reTarget = MouseTarget::Bullet; } else { // Check for hyperlink const SvxFieldItem* pFieldItem = pEditView->GetField( aMousePosWin ); if ( pFieldItem && pFieldItem->GetField() && dynamic_cast< const SvxURLField* >(pFieldItem->GetField()) != nullptr ) reTarget = MouseTarget::Hypertext; } } } return nPara; } bool OutlinerView::MouseMove( const MouseEvent& rMEvt ) { if( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode()) return pEditView->MouseMove( rMEvt ); Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) return false; PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); pEditView->GetWindow()->SetPointer( aPointer ); return pEditView->MouseMove( rMEvt ); } bool OutlinerView::MouseButtonDown( const MouseEvent& rMEvt ) { if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode() ) return pEditView->MouseButtonDown( rMEvt ); Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) return false; PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); pEditView->GetWindow()->SetPointer( aPointer ); MouseTarget eTarget; sal_Int32 nPara = ImpCheckMousePos( rMEvt.GetPosPixel(), eTarget ); if ( eTarget == MouseTarget::Bullet ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); bool bHasChildren = (pPara && pOwner->pParaList->HasChildren(pPara)); if( rMEvt.GetClicks() == 1 ) { sal_Int32 nEndPara = nPara; if ( bHasChildren && pOwner->pParaList->HasVisibleChildren(pPara) ) nEndPara += pOwner->pParaList->GetChildCount( pPara ); // The selection is inverted, so that EditEngine does not scroll ESelection aSel(nEndPara, EE_TEXTPOS_ALL, nPara, 0 ); pEditView->SetSelection( aSel ); } else if( rMEvt.GetClicks() == 2 && bHasChildren ) ImpToggleExpand( pPara ); return true; } // special case for outliner view in impress, check if double click hits the page icon for toggle if( (nPara == EE_PARA_NOT_FOUND) && (pOwner->GetOutlinerMode() == OutlinerMode::OutlineView) && (eTarget == MouseTarget::Text) && (rMEvt.GetClicks() == 2) ) { ESelection aSel( pEditView->GetSelection() ); nPara = aSel.nStartPara; Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); if( (pPara && pOwner->pParaList->HasChildren(pPara)) && pPara->HasFlag(ParaFlag::ISPAGE) ) { ImpToggleExpand( pPara ); } } return pEditView->MouseButtonDown( rMEvt ); } bool OutlinerView::MouseButtonUp( const MouseEvent& rMEvt ) { if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode() ) return pEditView->MouseButtonUp( rMEvt ); Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) return false; PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); pEditView->GetWindow()->SetPointer( aPointer ); return pEditView->MouseButtonUp( rMEvt ); } void OutlinerView::ReleaseMouse() { pEditView->ReleaseMouse(); } void OutlinerView::ImpToggleExpand( Paragraph const * pPara ) { sal_Int32 nPara = pOwner->pParaList->GetAbsPos( pPara ); pEditView->SetSelection( ESelection( nPara, 0, nPara, 0 ) ); ImplExpandOrCollaps( nPara, nPara, !pOwner->pParaList->HasVisibleChildren( pPara ) ); pEditView->ShowCursor(); } void OutlinerView::Select( Paragraph const * pParagraph, bool bSelect ) { sal_Int32 nPara = pOwner->pParaList->GetAbsPos( pParagraph ); sal_Int32 nEnd = 0; if ( bSelect ) nEnd = SAL_MAX_INT32; ESelection aSel( nPara, 0, nPara, nEnd ); pEditView->SetSelection( aSel ); } void OutlinerView::SetAttribs( const SfxItemSet& rAttrs ) { bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) pOwner->UndoActionStart( OLUNDO_ATTR ); ParaRange aSel = ImpGetSelectedParagraphs( false ); pEditView->SetAttribs( rAttrs ); // Update Bullet text for( sal_Int32 nPara= aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) { pOwner->ImplCheckNumBulletItem( nPara ); pOwner->ImplCalcBulletText( nPara, false, false ); if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) pOwner->InsertUndo( std::make_unique( pOwner, nPara ) ); } if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) pOwner->UndoActionEnd(); pEditView->SetEditEngineUpdateLayout( bUpdate ); } ParaRange OutlinerView::ImpGetSelectedParagraphs( bool bIncludeHiddenChildren ) { ESelection aSel = pEditView->GetSelection(); ParaRange aParas( aSel.nStartPara, aSel.nEndPara ); aParas.Adjust(); // Record the invisible Children of the last Parents in the selection if ( bIncludeHiddenChildren ) { Paragraph* pLast = pOwner->pParaList->GetParagraph( aParas.nEndPara ); if ( pOwner->pParaList->HasHiddenChildren( pLast ) ) aParas.nEndPara = aParas.nEndPara + pOwner->pParaList->GetChildCount( pLast ); } return aParas; } // TODO: Name should be changed! void OutlinerView::AdjustDepth( short nDX ) { Indent( nDX ); } void OutlinerView::Indent( short nDiff ) { if( !nDiff || ( ( nDiff > 0 ) && ImpCalcSelectedPages( true ) && !pOwner->ImpCanIndentSelectedPages( this ) ) ) return; const bool bOutlinerView = bool(pOwner->pEditEngine->GetControlWord() & EEControlBits::OUTLINER); bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); bool bUndo = !pOwner->IsInUndo() && pOwner->IsUndoEnabled(); if( bUndo ) pOwner->UndoActionStart( OLUNDO_DEPTH ); sal_Int16 nMinDepth = -1; // Optimization: avoid recalculate too many paragraphs if not really needed. ParaRange aSel = ImpGetSelectedParagraphs( true ); for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); sal_Int16 nOldDepth = pPara->GetDepth(); sal_Int16 nNewDepth = nOldDepth + nDiff; if( bOutlinerView && nPara ) { const bool bPage = pPara->HasFlag(ParaFlag::ISPAGE); if( (bPage && (nDiff == +1)) || (!bPage && (nDiff == -1) && (nOldDepth <= 0)) ) { // Notify App pOwner->nDepthChangedHdlPrevDepth = nOldDepth; ParaFlag nPrevFlags = pPara->nFlags; if( bPage ) pPara->RemoveFlag( ParaFlag::ISPAGE ); else pPara->SetFlag( ParaFlag::ISPAGE ); pOwner->DepthChangedHdl(pPara, nPrevFlags); pOwner->pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); if( bUndo ) pOwner->InsertUndo( std::make_unique( pOwner, nPara, nPrevFlags, pPara->nFlags ) ); continue; } } // do not switch off numeration with tab if( (nOldDepth == 0) && (nNewDepth == -1) ) continue; // do not indent if there is no numeration enabled if( nOldDepth == -1 ) continue; if ( nNewDepth < Outliner::gnMinDepth ) nNewDepth = Outliner::gnMinDepth; if ( nNewDepth > pOwner->nMaxDepth ) nNewDepth = pOwner->nMaxDepth; if( nOldDepth < nMinDepth ) nMinDepth = nOldDepth; if( nNewDepth < nMinDepth ) nMinDepth = nNewDepth; if( nOldDepth != nNewDepth ) { if ( ( nPara == aSel.nStartPara ) && aSel.nStartPara && ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject )) { // Special case: the predecessor of an indented paragraph is // invisible and is now on the same level as the visible // paragraph. In this case, the next visible paragraph is // searched for and fluffed. #ifdef DBG_UTIL Paragraph* _pPara = pOwner->pParaList->GetParagraph( aSel.nStartPara ); DBG_ASSERT(_pPara->IsVisible(),"Selected Paragraph invisible ?!"); #endif Paragraph* pPrev= pOwner->pParaList->GetParagraph( aSel.nStartPara-1 ); if( !pPrev->IsVisible() && ( pPrev->GetDepth() == nNewDepth ) ) { // Predecessor is collapsed and is on the same level // => find next visible paragraph and expand it pPrev = pOwner->pParaList->GetParent( pPrev ); while( !pPrev->IsVisible() ) pPrev = pOwner->pParaList->GetParent( pPrev ); pOwner->Expand( pPrev ); pOwner->InvalidateBullet(pOwner->pParaList->GetAbsPos(pPrev)); } } pOwner->nDepthChangedHdlPrevDepth = nOldDepth; ParaFlag nPrevFlags = pPara->nFlags; pOwner->ImplInitDepth( nPara, nNewDepth, true ); pOwner->ImplCalcBulletText( nPara, false, false ); if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) pOwner->ImplSetLevelDependentStyleSheet( nPara ); // Notify App pOwner->DepthChangedHdl(pPara, nPrevFlags); } else { // Needs at least a repaint... pOwner->pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); } } sal_Int32 nParas = pOwner->pParaList->GetParagraphCount(); for ( sal_Int32 n = aSel.nEndPara+1; n < nParas; n++ ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( n ); if ( pPara->GetDepth() < nMinDepth ) break; pOwner->ImplCalcBulletText( n, false, false ); } if ( bUpdate ) { pEditView->SetEditEngineUpdateLayout( true ); pEditView->ShowCursor(); } if( bUndo ) pOwner->UndoActionEnd(); } void OutlinerView::AdjustHeight( tools::Long nDY ) { pEditView->MoveParagraphs( nDY ); } tools::Rectangle OutlinerView::GetVisArea() const { return pEditView->GetVisArea(); } void OutlinerView::Expand() { ParaRange aParas = ImpGetSelectedParagraphs( false ); ImplExpandOrCollaps( aParas.nStartPara, aParas.nEndPara, true ); } void OutlinerView::Collapse() { ParaRange aParas = ImpGetSelectedParagraphs( false ); ImplExpandOrCollaps( aParas.nStartPara, aParas.nEndPara, false ); } void OutlinerView::ExpandAll() { ImplExpandOrCollaps( 0, pOwner->pParaList->GetParagraphCount()-1, true ); } void OutlinerView::CollapseAll() { ImplExpandOrCollaps( 0, pOwner->pParaList->GetParagraphCount()-1, false ); } void OutlinerView::ImplExpandOrCollaps( sal_Int32 nStartPara, sal_Int32 nEndPara, bool bExpand ) { bool bUpdate = pOwner->SetUpdateLayout( false ); bool bUndo = !pOwner->IsInUndo() && pOwner->IsUndoEnabled(); if( bUndo ) pOwner->UndoActionStart( bExpand ? OLUNDO_EXPAND : OLUNDO_COLLAPSE ); for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); bool bDone = bExpand ? pOwner->Expand( pPara ) : pOwner->Collapse( pPara ); if( bDone ) { // The line under the paragraph should disappear ... pOwner->pEditEngine->QuickMarkToBeRepainted( nPara ); } } if( bUndo ) pOwner->UndoActionEnd(); if ( bUpdate ) { pOwner->SetUpdateLayout( true ); pEditView->ShowCursor(); } } void OutlinerView::InsertText( const OutlinerParaObject& rParaObj ) { // Like Paste, only EditView::Insert, instead of EditView::Paste. // Actually not quite true that possible indentations must be corrected, // but that comes later by a universal import. The indentation level is // then determined right in the Inserted method. // Possible structure: // pImportInfo with DestPara, DestPos, nFormat, pParaObj... // Possibly problematic: // EditEngine, RTF => Splitting the area, later join together. if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) return; pOwner->UndoActionStart( OLUNDO_INSERT ); const bool bPrevUpdateLayout = pOwner->pEditEngine->SetUpdateLayout( false ); sal_Int32 nStart, nParaCount; nParaCount = pOwner->pEditEngine->GetParagraphCount(); sal_uInt16 nSize = ImpInitPaste( nStart ); pEditView->InsertText( rParaObj.GetTextObject() ); ImpPasted( nStart, nParaCount, nSize); pEditView->SetEditEngineUpdateLayout( bPrevUpdateLayout ); pOwner->UndoActionEnd(); pEditView->ShowCursor(); } void OutlinerView::Cut() { if ( !ImpCalcSelectedPages( false ) || pOwner->ImpCanDeleteSelectedPages( this ) ) { pEditView->Cut(); // Chaining handling aEndCutPasteLink.Call(nullptr); } } void OutlinerView::PasteSpecial() { Paste( true ); } void OutlinerView::Paste( bool bUseSpecial ) { if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) return; pOwner->UndoActionStart( OLUNDO_INSERT ); const bool bPrevUpdateLayout = pOwner->pEditEngine->SetUpdateLayout( false ); pOwner->bPasting = true; if ( bUseSpecial ) pEditView->PasteSpecial(); else pEditView->Paste(); if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) { const sal_Int32 nParaCount = pOwner->pEditEngine->GetParagraphCount(); for( sal_Int32 nPara = 0; nPara < nParaCount; nPara++ ) pOwner->ImplSetLevelDependentStyleSheet( nPara ); } pEditView->SetEditEngineUpdateLayout( bPrevUpdateLayout ); pOwner->UndoActionEnd(); pEditView->ShowCursor(); // Chaining handling // NOTE: We need to do this last because it pEditView may be deleted if a switch of box occurs aEndCutPasteLink.Call(nullptr); } void OutlinerView::CreateSelectionList (std::vector &aSelList) { ParaRange aParas = ImpGetSelectedParagraphs( true ); for ( sal_Int32 nPara = aParas.nStartPara; nPara <= aParas.nEndPara; nPara++ ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); aSelList.push_back(pPara); } } const SfxStyleSheet* OutlinerView::GetStyleSheet() const { return pEditView->GetStyleSheet(); } SfxStyleSheet* OutlinerView::GetStyleSheet() { return pEditView->GetStyleSheet(); } PointerStyle OutlinerView::GetPointer( const Point& rPosPixel ) { MouseTarget eTarget; ImpCheckMousePos( rPosPixel, eTarget ); PointerStyle ePointerStyle = PointerStyle::Arrow; if ( eTarget == MouseTarget::Text ) { ePointerStyle = GetOutliner()->IsVertical() ? PointerStyle::TextVertical : PointerStyle::Text; } else if ( eTarget == MouseTarget::Hypertext ) { ePointerStyle = PointerStyle::RefHand; } else if ( eTarget == MouseTarget::Bullet ) { ePointerStyle = PointerStyle::Move; } return ePointerStyle; } sal_Int32 OutlinerView::ImpInitPaste( sal_Int32& rStart ) { pOwner->bPasting = true; ESelection aSelection( pEditView->GetSelection() ); aSelection.Adjust(); rStart = aSelection.nStartPara; sal_Int32 nSize = aSelection.nEndPara - aSelection.nStartPara + 1; return nSize; } void OutlinerView::ImpPasted( sal_Int32 nStart, sal_Int32 nPrevParaCount, sal_Int32 nSize) { pOwner->bPasting = false; sal_Int32 nCurParaCount = pOwner->pEditEngine->GetParagraphCount(); if( nCurParaCount < nPrevParaCount ) nSize = nSize - ( nPrevParaCount - nCurParaCount ); else nSize = nSize + ( nCurParaCount - nPrevParaCount ); pOwner->ImpTextPasted( nStart, nSize ); } bool OutlinerView::Command(const CommandEvent& rCEvt) { return pEditView->Command(rCEvt); } void OutlinerView::SelectRange( sal_Int32 nFirst, sal_Int32 nCount ) { sal_Int32 nLast = nFirst+nCount; nCount = pOwner->pParaList->GetParagraphCount(); if( nLast <= nCount ) nLast = nCount - 1; ESelection aSel( nFirst, 0, nLast, EE_TEXTPOS_ALL ); pEditView->SetSelection( aSel ); } sal_Int32 OutlinerView::ImpCalcSelectedPages( bool bIncludeFirstSelected ) { ESelection aSel( pEditView->GetSelection() ); aSel.Adjust(); sal_Int32 nPages = 0; sal_Int32 nFirstPage = EE_PARA_MAX_COUNT; sal_Int32 nStartPara = aSel.nStartPara; if ( !bIncludeFirstSelected ) nStartPara++; // All paragraphs after StartPara will be deleted for ( sal_Int32 nPara = nStartPara; nPara <= aSel.nEndPara; nPara++ ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); DBG_ASSERT(pPara, "ImpCalcSelectedPages: invalid Selection? "); if( pPara->HasFlag(ParaFlag::ISPAGE) ) { nPages++; if( nFirstPage == EE_PARA_MAX_COUNT ) nFirstPage = nPara; } } if( nPages ) { pOwner->nDepthChangedHdlPrevDepth = nPages; pOwner->mnFirstSelPage = nFirstPage; } return nPages; } void OutlinerView::ToggleBullets() { pOwner->UndoActionStart( OLUNDO_DEPTH ); ESelection aSel( pEditView->GetSelection() ); aSel.Adjust(); const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); sal_Int16 nNewDepth = -2; const SvxNumRule* pDefaultBulletNumRule = nullptr; for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); DBG_ASSERT(pPara, "OutlinerView::ToggleBullets(), illegal selection?"); if( pPara ) { if( nNewDepth == -2 ) { nNewDepth = (pOwner->GetDepth(nPara) == -1) ? 0 : -1; if ( nNewDepth == 0 ) { // determine default numbering rule for bullets const ESelection aSelection(nPara, 0); const SfxItemSet aTmpSet(pOwner->pEditEngine->GetAttribs(aSelection)); const SfxPoolItem& rPoolItem = aTmpSet.GetPool()->GetDefaultItem( EE_PARA_NUMBULLET ); const SvxNumBulletItem* pNumBulletItem = dynamic_cast< const SvxNumBulletItem* >(&rPoolItem); pDefaultBulletNumRule = pNumBulletItem ? &pNumBulletItem->GetNumRule() : nullptr; } } pOwner->SetDepth( pPara, nNewDepth ); if( nNewDepth == -1 ) { const SfxItemSet& rAttrs = pOwner->GetParaAttribs( nPara ); if ( rAttrs.GetItemState( EE_PARA_BULLETSTATE ) == SfxItemState::SET ) { SfxItemSet aAttrs(rAttrs); aAttrs.ClearItem( EE_PARA_BULLETSTATE ); pOwner->SetParaAttribs( nPara, aAttrs ); } } else { if ( pDefaultBulletNumRule ) { const SvxNumberFormat* pFmt = pOwner ->GetNumberFormat( nPara ); if ( !pFmt || ( pFmt->GetNumberingType() != SVX_NUM_BITMAP && pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) ) { SfxItemSet aAttrs( pOwner->GetParaAttribs( nPara ) ); SvxNumRule aNewNumRule( *pDefaultBulletNumRule ); aAttrs.Put( SvxNumBulletItem( std::move(aNewNumRule), EE_PARA_NUMBULLET ) ); pOwner->SetParaAttribs( nPara, aAttrs ); } } } } } const sal_Int32 nParaCount = pOwner->pParaList->GetParagraphCount(); pOwner->ImplCheckParagraphs( aSel.nStartPara, nParaCount ); sal_Int32 nEndPara = (nParaCount > 0) ? nParaCount-1 : nParaCount; pOwner->pEditEngine->QuickMarkInvalid( ESelection( aSel.nStartPara, 0, nEndPara, 0 ) ); pOwner->pEditEngine->SetUpdateLayout( bUpdate ); pOwner->UndoActionEnd(); } void OutlinerView::ToggleBulletsNumbering( const bool bToggle, const bool bHandleBullets, const SvxNumRule* pNumRule ) { ESelection aSel( pEditView->GetSelection() ); aSel.Adjust(); bool bToggleOn = true; if ( bToggle ) { bToggleOn = false; const sal_Int16 nBulletNumberingStatus( pOwner->GetBulletsNumberingStatus( aSel.nStartPara, aSel.nEndPara ) ); if ( nBulletNumberingStatus != 0 && bHandleBullets ) { // not all paragraphs have bullets and method called to toggle bullets --> bullets on bToggleOn = true; } else if ( nBulletNumberingStatus != 1 && !bHandleBullets ) { // not all paragraphs have numbering and method called to toggle numberings --> numberings on bToggleOn = true; } } if ( bToggleOn ) { // apply bullets/numbering for selected paragraphs ApplyBulletsNumbering( bHandleBullets, pNumRule, bToggle, true ); } else { // switch off bullets/numbering for selected paragraphs SwitchOffBulletsNumbering( true ); } } void OutlinerView::EnsureNumberingIsOn() { pOwner->UndoActionStart(OLUNDO_DEPTH); ESelection aSel(pEditView->GetSelection()); aSel.Adjust(); const bool bUpdate = pOwner->pEditEngine->IsUpdateLayout(); pOwner->pEditEngine->SetUpdateLayout(false); for (sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++) { Paragraph* pPara = pOwner->pParaList->GetParagraph(nPara); DBG_ASSERT(pPara, "OutlinerView::EnableBullets(), illegal selection?"); if (pPara && pOwner->GetDepth(nPara) == -1) pOwner->SetDepth(pPara, 0); } sal_Int32 nParaCount = pOwner->pParaList->GetParagraphCount(); pOwner->ImplCheckParagraphs(aSel.nStartPara, nParaCount); const sal_Int32 nEndPara = (nParaCount > 0) ? nParaCount-1 : nParaCount; pOwner->pEditEngine->QuickMarkInvalid(ESelection(aSel.nStartPara, 0, nEndPara, 0)); pOwner->pEditEngine->SetUpdateLayout(bUpdate); pOwner->UndoActionEnd(); } void OutlinerView::ApplyBulletsNumbering( const bool bHandleBullets, const SvxNumRule* pNewNumRule, const bool bCheckCurrentNumRuleBeforeApplyingNewNumRule, const bool bAtSelection ) { if (!pOwner || !pOwner->pEditEngine || !pOwner->pParaList) { return; } pOwner->UndoActionStart(OLUNDO_DEPTH); const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout(false); sal_Int32 nStartPara = 0; sal_Int32 nEndPara = 0; if ( bAtSelection ) { ESelection aSel( pEditView->GetSelection() ); aSel.Adjust(); nStartPara = aSel.nStartPara; nEndPara = aSel.nEndPara; } else { nStartPara = 0; nEndPara = pOwner->pParaList->GetParagraphCount() - 1; } for (sal_Int32 nPara = nStartPara; nPara <= nEndPara; ++nPara) { Paragraph* pPara = pOwner->pParaList->GetParagraph(nPara); DBG_ASSERT(pPara, "OutlinerView::ApplyBulletsNumbering(..), illegal selection?"); if (pPara) { const sal_Int16 nDepth = pOwner->GetDepth(nPara); if ( nDepth == -1 ) { pOwner->SetDepth( pPara, 0 ); } const SfxItemSet& rAttrs = pOwner->GetParaAttribs(nPara); SfxItemSet aAttrs(rAttrs); aAttrs.Put(SfxBoolItem(EE_PARA_BULLETSTATE, true)); // apply new numbering rule if ( pNewNumRule ) { bool bApplyNumRule = false; if ( !bCheckCurrentNumRuleBeforeApplyingNewNumRule ) { bApplyNumRule = true; } else { const SvxNumberFormat* pFmt = pOwner ->GetNumberFormat(nPara); if (!pFmt) { bApplyNumRule = true; } else { sal_Int16 nNumType = pFmt->GetNumberingType(); if ( bHandleBullets && nNumType != SVX_NUM_BITMAP && nNumType != SVX_NUM_CHAR_SPECIAL) { // Set to Normal bullet, old bullet type is Numbering bullet. bApplyNumRule = true; } else if ( !bHandleBullets && (nNumType == SVX_NUM_BITMAP || nNumType == SVX_NUM_CHAR_SPECIAL)) { // Set to Numbering bullet, old bullet type is Normal bullet. bApplyNumRule = true; } } } if ( bApplyNumRule ) { SvxNumRule aNewRule(*pNewNumRule); // Get old bullet space. { const SvxNumBulletItem* pNumBulletItem = rAttrs.GetItemIfSet(EE_PARA_NUMBULLET, false); if (pNumBulletItem) { // Use default value when has not contain bullet item. ESelection aSelection(nPara, 0); SfxItemSet aTmpSet(pOwner->pEditEngine->GetAttribs(aSelection)); pNumBulletItem = aTmpSet.GetItem(EE_PARA_NUMBULLET); } if (pNumBulletItem) { const sal_uInt16 nLevelCnt = std::min(pNumBulletItem->GetNumRule().GetLevelCount(), aNewRule.GetLevelCount()); for ( sal_uInt16 nLevel = 0; nLevel < nLevelCnt; ++nLevel ) { const SvxNumberFormat* pOldFmt = pNumBulletItem->GetNumRule().Get(nLevel); const SvxNumberFormat* pNewFmt = aNewRule.Get(nLevel); if (pOldFmt && pNewFmt && (pOldFmt->GetFirstLineOffset() != pNewFmt->GetFirstLineOffset() || pOldFmt->GetAbsLSpace() != pNewFmt->GetAbsLSpace())) { SvxNumberFormat aNewFmtClone(*pNewFmt); aNewFmtClone.SetFirstLineOffset(pOldFmt->GetFirstLineOffset()); aNewFmtClone.SetAbsLSpace(pOldFmt->GetAbsLSpace()); aNewRule.SetLevel(nLevel, &aNewFmtClone); } } } } aAttrs.Put(SvxNumBulletItem(std::move(aNewRule), EE_PARA_NUMBULLET)); } } pOwner->SetParaAttribs(nPara, aAttrs); } } const sal_uInt16 nParaCount = static_cast(pOwner->pParaList->GetParagraphCount()); pOwner->ImplCheckParagraphs( nStartPara, nParaCount ); pOwner->pEditEngine->QuickMarkInvalid( ESelection( nStartPara, 0, nParaCount, 0 ) ); pOwner->pEditEngine->SetUpdateLayout( bUpdate ); pOwner->UndoActionEnd(); } void OutlinerView::SwitchOffBulletsNumbering( const bool bAtSelection ) { sal_Int32 nStartPara = 0; sal_Int32 nEndPara = 0; if ( bAtSelection ) { ESelection aSel( pEditView->GetSelection() ); aSel.Adjust(); nStartPara = aSel.nStartPara; nEndPara = aSel.nEndPara; } else { nStartPara = 0; nEndPara = pOwner->pParaList->GetParagraphCount() - 1; } pOwner->UndoActionStart( OLUNDO_DEPTH ); const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; ++nPara ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); DBG_ASSERT(pPara, "OutlinerView::SwitchOffBulletsNumbering(...), illegal paragraph index?"); if( pPara ) { pOwner->SetDepth( pPara, -1 ); const SfxItemSet& rAttrs = pOwner->GetParaAttribs( nPara ); if (rAttrs.GetItemState( EE_PARA_BULLETSTATE ) == SfxItemState::SET) { SfxItemSet aAttrs(rAttrs); aAttrs.ClearItem( EE_PARA_BULLETSTATE ); pOwner->SetParaAttribs( nPara, aAttrs ); } } } const sal_uInt16 nParaCount = static_cast(pOwner->pParaList->GetParagraphCount()); pOwner->ImplCheckParagraphs( nStartPara, nParaCount ); pOwner->pEditEngine->QuickMarkInvalid( ESelection( nStartPara, 0, nParaCount, 0 ) ); pOwner->pEditEngine->SetUpdateLayout( bUpdate ); pOwner->UndoActionEnd(); } void OutlinerView::RemoveAttribsKeepLanguages( bool bRemoveParaAttribs ) { RemoveAttribs( bRemoveParaAttribs, true /*keep language attribs*/ ); } void OutlinerView::RemoveAttribs( bool bRemoveParaAttribs, bool bKeepLanguages ) { bool bUpdate = pOwner->SetUpdateLayout( false ); pOwner->UndoActionStart( OLUNDO_ATTR ); if (bKeepLanguages) pEditView->RemoveAttribsKeepLanguages( bRemoveParaAttribs ); else pEditView->RemoveAttribs( bRemoveParaAttribs ); if ( bRemoveParaAttribs ) { // Loop through all paragraphs and set indentation and level ESelection aSel = pEditView->GetSelection(); aSel.Adjust(); for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) { Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); pOwner->ImplInitDepth( nPara, pPara->GetDepth(), false ); } } pOwner->UndoActionEnd(); pOwner->SetUpdateLayout( bUpdate ); } // ====================== Simple pass-through ======================= void OutlinerView::InsertText( const OUString& rNew, bool bSelect ) { if( pOwner->bFirstParaIsEmpty ) pOwner->Insert( OUString() ); pEditView->InsertText( rNew, bSelect ); } void OutlinerView::SetVisArea( const tools::Rectangle& rRect ) { pEditView->SetVisArea( rRect ); } void OutlinerView::SetSelection( const ESelection& rSel ) { pEditView->SetSelection( rSel ); } void OutlinerView::GetSelectionRectangles(std::vector& rLogicRects) const { pEditView->GetSelectionRectangles(rLogicRects); } void OutlinerView::SetReadOnly( bool bReadOnly ) { pEditView->SetReadOnly( bReadOnly ); } bool OutlinerView::IsReadOnly() const { return pEditView->IsReadOnly(); } bool OutlinerView::HasSelection() const { return pEditView->HasSelection(); } void OutlinerView::ShowCursor( bool bGotoCursor, bool bActivate ) { pEditView->ShowCursor( bGotoCursor, /*bForceVisCursor=*/true, bActivate ); } void OutlinerView::HideCursor(bool bDeactivate) { pEditView->HideCursor(bDeactivate); } void OutlinerView::SetWindow( vcl::Window* pWin ) { pEditView->SetWindow( pWin ); } vcl::Window* OutlinerView::GetWindow() const { return pEditView->GetWindow(); } void OutlinerView::SetOutputArea( const tools::Rectangle& rRect ) { pEditView->SetOutputArea( rRect ); } tools::Rectangle const & OutlinerView::GetOutputArea() const { return pEditView->GetOutputArea(); } OUString OutlinerView::GetSelected() const { return pEditView->GetSelected(); } void OutlinerView::StartSpeller(weld::Widget* pDialogParent) { pEditView->StartSpeller(pDialogParent); } EESpellState OutlinerView::StartThesaurus(weld::Widget* pDialogParent) { return pEditView->StartThesaurus(pDialogParent); } void OutlinerView::StartTextConversion(weld::Widget* pDialogParent, LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc ) { if ( (LANGUAGE_KOREAN == nSrcLang && LANGUAGE_KOREAN == nDestLang) || (LANGUAGE_CHINESE_SIMPLIFIED == nSrcLang && LANGUAGE_CHINESE_TRADITIONAL == nDestLang) || (LANGUAGE_CHINESE_TRADITIONAL == nSrcLang && LANGUAGE_CHINESE_SIMPLIFIED == nDestLang) ) { pEditView->StartTextConversion(pDialogParent, nSrcLang, nDestLang, pDestFont, nOptions, bIsInteractive, bMultipleDoc); } else { OSL_FAIL( "unexpected language" ); } } sal_Int32 OutlinerView::StartSearchAndReplace( const SvxSearchItem& rSearchItem ) { return pEditView->StartSearchAndReplace( rSearchItem ); } void OutlinerView::TransliterateText( TransliterationFlags nTransliterationMode ) { pEditView->TransliterateText( nTransliterationMode ); } ESelection OutlinerView::GetSelection() const { return pEditView->GetSelection(); } void OutlinerView::Scroll( tools::Long nHorzScroll, tools::Long nVertScroll ) { pEditView->Scroll( nHorzScroll, nVertScroll ); } void OutlinerView::SetControlWord( EVControlBits nWord ) { pEditView->SetControlWord( nWord ); } EVControlBits OutlinerView::GetControlWord() const { return pEditView->GetControlWord(); } void OutlinerView::SetAnchorMode( EEAnchorMode eMode ) { pEditView->SetAnchorMode( eMode ); } EEAnchorMode OutlinerView::GetAnchorMode() const { return pEditView->GetAnchorMode(); } void OutlinerView::Copy() { pEditView->Copy(); } void OutlinerView::InsertField( const SvxFieldItem& rFld ) { pEditView->InsertField( rFld ); } const SvxFieldItem* OutlinerView::GetFieldUnderMousePointer() const { return pEditView->GetFieldUnderMousePointer(); } const SvxFieldItem* OutlinerView::GetFieldAtSelection() const { return pEditView->GetFieldAtSelection(); } const SvxFieldData* OutlinerView::GetFieldAtCursor() const { return pEditView->GetFieldAtCursor(); } void OutlinerView::SelectFieldAtCursor() { pEditView->SelectFieldAtCursor(); } void OutlinerView::SetInvalidateMore( sal_uInt16 nPixel ) { pEditView->SetInvalidateMore( nPixel ); } sal_uInt16 OutlinerView::GetInvalidateMore() const { return pEditView->GetInvalidateMore(); } bool OutlinerView::IsCursorAtWrongSpelledWord() { return pEditView->IsCursorAtWrongSpelledWord(); } bool OutlinerView::IsWrongSpelledWordAtPos( const Point& rPosPixel ) { return pEditView->IsWrongSpelledWordAtPos( rPosPixel, /*bMarkIfWrong*/false ); } void OutlinerView::ExecuteSpellPopup(const Point& rPosPixel, const Link& rStartDlg) { pEditView->ExecuteSpellPopup(rPosPixel, rStartDlg); } void OutlinerView::Read( SvStream& rInput, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) { sal_Int32 nOldParaCount = pEditView->GetEditEngine()->GetParagraphCount(); ESelection aOldSel = pEditView->GetSelection(); aOldSel.Adjust(); pEditView->Read( rInput, eFormat, pHTTPHeaderAttrs ); tools::Long nParaDiff = pEditView->GetEditEngine()->GetParagraphCount() - nOldParaCount; sal_Int32 nChangesStart = aOldSel.nStartPara; sal_Int32 nChangesEnd = nChangesStart + nParaDiff + (aOldSel.nEndPara-aOldSel.nStartPara); for ( sal_Int32 n = nChangesStart; n <= nChangesEnd; n++ ) { if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) pOwner->ImplSetLevelDependentStyleSheet( n ); } pOwner->ImpFilterIndents( nChangesStart, nChangesEnd ); } void OutlinerView::SetBackgroundColor( const Color& rColor ) { pEditView->SetBackgroundColor( rColor ); } void OutlinerView::RegisterViewShell(OutlinerViewShell* pViewShell) { pEditView->RegisterViewShell(pViewShell); } Color const & OutlinerView::GetBackgroundColor() const { return pEditView->GetBackgroundColor(); } SfxItemSet OutlinerView::GetAttribs() { return pEditView->GetAttribs(); } SvtScriptType OutlinerView::GetSelectedScriptType() const { return pEditView->GetSelectedScriptType(); } OUString OutlinerView::GetSurroundingText() const { return pEditView->GetSurroundingText(); } Selection OutlinerView::GetSurroundingTextSelection() const { return pEditView->GetSurroundingTextSelection(); } bool OutlinerView::DeleteSurroundingText(const Selection& rSelection) { return pEditView->DeleteSurroundingText(rSelection); } // ===== some code for thesaurus sub menu within context menu namespace { bool isSingleScriptType( SvtScriptType nScriptType ) { sal_uInt8 nScriptCount = 0; if (nScriptType & SvtScriptType::LATIN) ++nScriptCount; if (nScriptType & SvtScriptType::ASIAN) ++nScriptCount; if (nScriptType & SvtScriptType::COMPLEX) ++nScriptCount; return nScriptCount == 1; } } // returns: true if a word for thesaurus look-up was found at the current cursor position. // The status string will be word + iso language string (e.g. "light#en-US") bool GetStatusValueForThesaurusFromContext( OUString &rStatusVal, LanguageType &rLang, const EditView &rEditView ) { // get text and locale for thesaurus look up OUString aText; EditEngine *pEditEngine = rEditView.GetEditEngine(); ESelection aTextSel( rEditView.GetSelection() ); if (!aTextSel.HasRange()) aTextSel = pEditEngine->GetWord( aTextSel, i18n::WordType::DICTIONARY_WORD ); aText = pEditEngine->GetText( aTextSel ); aTextSel.Adjust(); if (!isSingleScriptType(pEditEngine->GetScriptType(aTextSel))) return false; LanguageType nLang = pEditEngine->GetLanguage( aTextSel.nStartPara, aTextSel.nStartPos ).nLang; OUString aLangText( LanguageTag::convertToBcp47( nLang ) ); // set word and locale to look up as status value rStatusVal = aText + "#" + aLangText; rLang = nLang; return aText.getLength() > 0; } void ReplaceTextWithSynonym( EditView &rEditView, const OUString &rSynonmText ) { // get selection to use ESelection aCurSel( rEditView.GetSelection() ); if (!rEditView.HasSelection()) { // select the same word that was used in GetStatusValueForThesaurusFromContext by calling GetWord. // (In the end both functions will call ImpEditEngine::SelectWord) rEditView.SelectCurrentWord( i18n::WordType::DICTIONARY_WORD ); aCurSel = rEditView.GetSelection(); } // replace word ... rEditView.InsertText( rSynonmText ); rEditView.ShowCursor( true, false ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */