/* -*- 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 <memory>
#include <i18nutil/searchopt.hxx>
#include <o3tl/deleter.hxx>
#include <vcl/textview.hxx>
#include <vcl/texteng.hxx>
#include <vcl/settings.hxx>
#include "textdoc.hxx"
#include <vcl/textdata.hxx>
#include <vcl/xtextedt.hxx>
#include "textdat2.hxx"
#include <svl/undo.hxx>
#include <vcl/cursor.hxx>
#include <vcl/weld.hxx>
#include <vcl/window.hxx>
#include <vcl/svapp.hxx>
#include <tools/stream.hxx>
#include <sal/log.hxx>
#include <sot/formats.hxx>
#include <svl/urlbmk.hxx>
#include <cppuhelper/weak.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <vcl/unohelp.hxx>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/util/SearchOptions.hpp>
#include <com/sun/star/util/SearchFlags.hpp>
#include <vcl/edit.hxx>
#include <sot/exchange.hxx>
#include <algorithm>
#include <cstddef>
#include <o3tl/make_unique.hxx>
TETextDataObject::TETextDataObject( const OUString& rText ) : maText( rText )
{
}
// css::uno::XInterface
css::uno::Any TETextDataObject::queryInterface( const css::uno::Type & rType )
{
css::uno::Any aRet = ::cppu::queryInterface( rType, static_cast< css::datatransfer::XTransferable* >(this) );
return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
}
// css::datatransfer::XTransferable
css::uno::Any TETextDataObject::getTransferData( const css::datatransfer::DataFlavor& rFlavor )
{
css::uno::Any aAny;
SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
if ( nT == SotClipboardFormatId::STRING )
{
aAny <<= GetText();
}
else if ( nT == SotClipboardFormatId::HTML )
{
GetHTMLStream().Seek( STREAM_SEEK_TO_END );
sal_uLong nLen = GetHTMLStream().Tell();
GetHTMLStream().Seek(0);
css::uno::Sequence< sal_Int8 > aSeq( nLen );
memcpy( aSeq.getArray(), GetHTMLStream().GetData(), nLen );
aAny <<= aSeq;
}
else
{
throw css::datatransfer::UnsupportedFlavorException();
}
return aAny;
}
css::uno::Sequence< css::datatransfer::DataFlavor > TETextDataObject::getTransferDataFlavors( )
{
GetHTMLStream().Seek( STREAM_SEEK_TO_END );
bool bHTML = GetHTMLStream().Tell() > 0;
css::uno::Sequence< css::datatransfer::DataFlavor > aDataFlavors( bHTML ? 2 : 1 );
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] );
if ( bHTML )
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::HTML, aDataFlavors.getArray()[1] );
return aDataFlavors;
}
sal_Bool TETextDataObject::isDataFlavorSupported( const css::datatransfer::DataFlavor& rFlavor )
{
SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
return ( nT == SotClipboardFormatId::STRING );
}
struct ImpTextView
{
ExtTextEngine* mpTextEngine;
VclPtr<vcl::Window> mpWindow;
TextSelection maSelection;
Point maStartDocPos;
std::unique_ptr<vcl::Cursor, o3tl::default_delete<vcl::Cursor>> mpCursor;
std::unique_ptr<TextDDInfo, o3tl::default_delete<TextDDInfo>> mpDDInfo;
std::unique_ptr<SelectionEngine> mpSelEngine;
std::unique_ptr<TextSelFunctionSet> mpSelFuncSet;
css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mxDnDListener;
sal_uInt16 mnTravelXPos;
bool mbAutoScroll : 1;
bool mbInsertMode : 1;
bool mbReadOnly : 1;
bool mbPaintSelection : 1;
bool mbAutoIndent : 1;
bool mbHighlightSelection : 1;
bool mbCursorEnabled : 1;
bool mbClickedInSelection : 1;
bool mbSupportProtectAttribute : 1;
bool mbCursorAtEndOfLine;
};
TextView::TextView( ExtTextEngine* pEng, vcl::Window* pWindow ) :
mpImpl(new ImpTextView)
{
pWindow->EnableRTL( false );
mpImpl->mpWindow = pWindow;
mpImpl->mpTextEngine = pEng;
mpImpl->mbPaintSelection = true;
mpImpl->mbAutoScroll = true;
mpImpl->mbInsertMode = true;
mpImpl->mbReadOnly = false;
mpImpl->mbHighlightSelection = false;
mpImpl->mbAutoIndent = false;
mpImpl->mbCursorEnabled = true;
mpImpl->mbClickedInSelection = false;
mpImpl->mbSupportProtectAttribute = false;
mpImpl->mbCursorAtEndOfLine = false;
// mbInSelection = false;
mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
mpImpl->mpSelFuncSet = o3tl::make_unique<TextSelFunctionSet>( this );
mpImpl->mpSelEngine = o3tl::make_unique<SelectionEngine>( mpImpl->mpWindow, mpImpl->mpSelFuncSet.get() );
mpImpl->mpSelEngine->SetSelectionMode( SelectionMode::Range );
mpImpl->mpSelEngine->EnableDrag( true );
mpImpl->mpCursor.reset(new vcl::Cursor);
mpImpl->mpCursor->Show();
pWindow->SetCursor( mpImpl->mpCursor.get() );
pWindow->SetInputContext( InputContext( pEng->GetFont(), InputContextFlags::Text|InputContextFlags::ExtText ) );
if ( pWindow->GetSettings().GetStyleSettings().GetSelectionOptions() & SelectionOptions::Invert )
mpImpl->mbHighlightSelection = true;
pWindow->SetLineColor();
if ( pWindow->GetDragGestureRecognizer().is() )
{
vcl::unohelper::DragAndDropWrapper* pDnDWrapper = new vcl::unohelper::DragAndDropWrapper( this );
mpImpl->mxDnDListener = pDnDWrapper;
css::uno::Reference< css::datatransfer::dnd::XDragGestureListener> xDGL( mpImpl->mxDnDListener, css::uno::UNO_QUERY );
pWindow->GetDragGestureRecognizer()->addDragGestureListener( xDGL );
css::uno::Reference< css::datatransfer::dnd::XDropTargetListener> xDTL( xDGL, css::uno::UNO_QUERY );
pWindow->GetDropTarget()->addDropTargetListener( xDTL );
pWindow->GetDropTarget()->setActive( true );
pWindow->GetDropTarget()->setDefaultActions( css::datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
}
}
TextView::~TextView()
{
mpImpl->mpSelEngine.reset();
mpImpl->mpSelFuncSet.reset();
if ( mpImpl->mpWindow->GetCursor() == mpImpl->mpCursor.get() )
mpImpl->mpWindow->SetCursor( nullptr );
mpImpl->mpCursor.reset();
mpImpl->mpDDInfo.reset();
}
void TextView::Invalidate()
{
mpImpl->mpWindow->Invalidate();
}
void TextView::SetSelection( const TextSelection& rTextSel, bool bGotoCursor )
{
// if someone left an empty attribute and then the Outliner manipulated the selection
if ( !mpImpl->maSelection.HasRange() )
mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
// if the selection is manipulated after a KeyInput
mpImpl->mpTextEngine->CheckIdleFormatter();
HideSelection();
TextSelection aNewSel( rTextSel );
mpImpl->mpTextEngine->ValidateSelection( aNewSel );
ImpSetSelection( aNewSel );
ShowSelection();
ShowCursor( bGotoCursor );
}
void TextView::SetSelection( const TextSelection& rTextSel )
{
SetSelection( rTextSel, mpImpl->mbAutoScroll );
}
const TextSelection& TextView::GetSelection() const
{
return mpImpl->maSelection;
}
TextSelection& TextView::GetSelection()
{
return mpImpl->maSelection;
}
void TextView::DeleteSelected()
{
// HideSelection();
mpImpl->mpTextEngine->UndoActionStart();
TextPaM aPaM = mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
mpImpl->mpTextEngine->UndoActionEnd();
ImpSetSelection( aPaM );
mpImpl->mpTextEngine->FormatAndUpdate( this );
ShowCursor();
}
void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection)
{
if (!mpImpl->mbPaintSelection)
{
pSelection = nullptr;
}
else
{
// set correct background color;
// unfortunately we cannot detect if it has changed
vcl::Font aFont = mpImpl->mpTextEngine->GetFont();
Color aColor = rRenderContext.GetBackground().GetColor();
aColor.SetTransparency(0);
if (aColor != aFont.GetFillColor())
{
if (aFont.IsTransparent())
aColor = COL_TRANSPARENT;
aFont.SetFillColor(aColor);
mpImpl->mpTextEngine->maFont = aFont;
}
}
mpImpl->mpTextEngine->ImpPaint(&rRenderContext, rStartPos, pPaintArea, pSelection);
}
void TextView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
ImpPaint(rRenderContext, rRect);
}
void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
if ( !mpImpl->mpTextEngine->GetUpdateMode() || mpImpl->mpTextEngine->IsInUndo() )
return;
TextSelection *pDrawSelection = nullptr;
if (!mpImpl->mbHighlightSelection && mpImpl->maSelection.HasRange())
pDrawSelection = &mpImpl->maSelection;
Point aStartPos = ImpGetOutputStartPos(mpImpl->maStartDocPos);
ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
if (mpImpl->mbHighlightSelection)
ImpHighlight(mpImpl->maSelection);
}
void TextView::ImpHighlight( const TextSelection& rSel )
{
TextSelection aSel( rSel );
aSel.Justify();
if ( aSel.HasRange() && !mpImpl->mpTextEngine->IsInUndo() && mpImpl->mpTextEngine->GetUpdateMode() )
{
mpImpl->mpCursor->Hide();
SAL_WARN_IF( mpImpl->mpTextEngine->mpIdleFormatter->IsActive(), "vcl", "ImpHighlight: Not formatted!" );
tools::Rectangle aVisArea( mpImpl->maStartDocPos, mpImpl->mpWindow->GetOutputSizePixel() );
long nY = 0;
const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
const sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
for ( sal_uInt32 nPara = 0; nPara <= nEndPara; ++nPara )
{
const long nParaHeight = mpImpl->mpTextEngine->CalcParaHeight( nPara );
if ( ( nPara >= nStartPara ) && ( ( nY + nParaHeight ) > aVisArea.Top() ) )
{
TEParaPortion* pTEParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( nPara );
std::vector<TextLine>::size_type nStartLine = 0;
std::vector<TextLine>::size_type nEndLine = pTEParaPortion->GetLines().size() -1;
if ( nPara == nStartPara )
nStartLine = pTEParaPortion->GetLineNumber( aSel.GetStart().GetIndex(), false );
if ( nPara == nEndPara )
nEndLine = pTEParaPortion->GetLineNumber( aSel.GetEnd().GetIndex(), true );
// iterate over all lines
for ( std::vector<TextLine>::size_type nLine = nStartLine; nLine <= nEndLine; nLine++ )
{
TextLine& rLine = pTEParaPortion->GetLines()[ nLine ];
sal_Int32 nStartIndex = rLine.GetStart();
sal_Int32 nEndIndex = rLine.GetEnd();
if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) )
nStartIndex = aSel.GetStart().GetIndex();
if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) )
nEndIndex = aSel.GetEnd().GetIndex();
// possible if at the beginning of a wrapped line
if ( nEndIndex < nStartIndex )
nEndIndex = nStartIndex;
tools::Rectangle aTmpRect( mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nStartIndex ), false ) );
aTmpRect.AdjustTop(nY );
aTmpRect.AdjustBottom(nY );
Point aTopLeft( aTmpRect.TopLeft() );
aTmpRect = mpImpl->mpTextEngine->GetEditCursor( TextPaM( nPara, nEndIndex ), true );
aTmpRect.AdjustTop(nY );
aTmpRect.AdjustBottom(nY );
Point aBottomRight( aTmpRect.BottomRight() );
aBottomRight.AdjustX( -1 );
// only paint if in the visible region
if ( ( aTopLeft.X() < aBottomRight.X() ) && ( aBottomRight.Y() >= aVisArea.Top() ) )
{
Point aPnt1( GetWindowPos( aTopLeft ) );
Point aPnt2( GetWindowPos( aBottomRight ) );
tools::Rectangle aRect( aPnt1, aPnt2 );
mpImpl->mpWindow->Invert( aRect );
}
}
}
nY += nParaHeight;
if ( nY >= aVisArea.Bottom() )
break;
}
}
}
void TextView::ImpSetSelection( const TextSelection& rSelection )
{
if (rSelection != mpImpl->maSelection)
{
bool bCaret = false, bSelection = false;
const TextPaM &rEnd = rSelection.GetEnd();
const TextPaM &rOldEnd = mpImpl->maSelection.GetEnd();
bool bGap = rSelection.HasRange(), bOldGap = mpImpl->maSelection.HasRange();
if (rEnd != rOldEnd)
bCaret = true;
if (bGap || bOldGap)
bSelection = true;
mpImpl->maSelection = rSelection;
if (bSelection)
mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged));
if (bCaret)
mpImpl->mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewCaretChanged));
}
}
void TextView::ShowSelection()
{
ImpShowHideSelection();
}
void TextView::HideSelection()
{
ImpShowHideSelection();
}
void TextView::ShowSelection( const TextSelection& rRange )
{
ImpShowHideSelection( &rRange );
}
void TextView::ImpShowHideSelection(const TextSelection* pRange)
{
const TextSelection* pRangeOrSelection = pRange ? pRange : &mpImpl->maSelection;
if ( pRangeOrSelection->HasRange() )
{
if ( mpImpl->mbHighlightSelection )
{
ImpHighlight( *pRangeOrSelection );
}
else
{
if( mpImpl->mpWindow->IsPaintTransparent() )
mpImpl->mpWindow->Invalidate();
else
{
TextSelection aRange( *pRangeOrSelection );
aRange.Justify();
bool bVisCursor = mpImpl->mpCursor->IsVisible();
mpImpl->mpCursor->Hide();
Invalidate();
if (bVisCursor)
mpImpl->mpCursor->Show();
}
}
}
}
bool TextView::KeyInput( const KeyEvent& rKeyEvent )
{
bool bDone = true;
bool bModified = false;
bool bMoved = false;
bool bEndKey = false; // special CursorPosition
bool bAllowIdle = true;
// check mModified;
// the local bModified is not set e.g. by Cut/Paste, as here
// the update happens somewhere else
bool bWasModified = mpImpl->mpTextEngine->IsModified();
mpImpl->mpTextEngine->SetModified( false );
TextSelection aCurSel( mpImpl->maSelection );
TextSelection aOldSel( aCurSel );
sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode();
KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
if ( eFunc != KeyFuncType::DONTKNOW )
{
switch ( eFunc )
{
case KeyFuncType::CUT:
{
if ( !mpImpl->mbReadOnly )
Cut();
}
break;
case KeyFuncType::COPY:
{
Copy();
}
break;
case KeyFuncType::PASTE:
{
if ( !mpImpl->mbReadOnly )
Paste();
}
break;
case KeyFuncType::UNDO:
{
if ( !mpImpl->mbReadOnly )
Undo();
}
break;
case KeyFuncType::REDO:
{
if ( !mpImpl->mbReadOnly )
Redo();
}
break;
default: // might get processed below
eFunc = KeyFuncType::DONTKNOW;
}
}
if ( eFunc == KeyFuncType::DONTKNOW )
{
switch ( nCode )
{
case KEY_UP:
case KEY_DOWN:
case KEY_LEFT:
case KEY_RIGHT:
case KEY_HOME:
case KEY_END:
case KEY_PAGEUP:
case KEY_PAGEDOWN:
case css::awt::Key::MOVE_WORD_FORWARD:
case css::awt::Key::SELECT_WORD_FORWARD:
case css::awt::Key::MOVE_WORD_BACKWARD:
case css::awt::Key::SELECT_WORD_BACKWARD:
case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
case css::awt::Key::MOVE_TO_END_OF_LINE:
case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
case css::awt::Key::SELECT_TO_END_OF_LINE:
case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
{
if ( ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) )
&& !( rKeyEvent.GetKeyCode().IsMod1() && ( nCode == KEY_PAGEUP || nCode == KEY_PAGEDOWN ) ) )
{
aCurSel = ImpMoveCursor( rKeyEvent );
if ( aCurSel.HasRange() ) {
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
Copy( aSelection );
}
bMoved = true;
if ( nCode == KEY_END )
bEndKey = true;
}
else
bDone = false;
}
break;
case KEY_BACKSPACE:
case KEY_DELETE:
case css::awt::Key::DELETE_WORD_BACKWARD:
case css::awt::Key::DELETE_WORD_FORWARD:
case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
case css::awt::Key::DELETE_TO_END_OF_LINE:
{
if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod2() )
{
sal_uInt8 nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT;
sal_uInt8 nMode = rKeyEvent.GetKeyCode().IsMod1() ? DELMODE_RESTOFWORD : DELMODE_SIMPLE;
if ( ( nMode == DELMODE_RESTOFWORD ) && rKeyEvent.GetKeyCode().IsShift() )
nMode = DELMODE_RESTOFCONTENT;
switch( nCode )
{
case css::awt::Key::DELETE_WORD_BACKWARD:
nDel = DEL_LEFT;
nMode = DELMODE_RESTOFWORD;
break;
case css::awt::Key::DELETE_WORD_FORWARD:
nDel = DEL_RIGHT;
nMode = DELMODE_RESTOFWORD;
break;
case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
nDel = DEL_LEFT;
nMode = DELMODE_RESTOFCONTENT;
break;
case css::awt::Key::DELETE_TO_END_OF_LINE:
nDel = DEL_RIGHT;
nMode = DELMODE_RESTOFCONTENT;
break;
default: break;
}
mpImpl->mpTextEngine->UndoActionStart();
if(mpImpl->mbSupportProtectAttribute)
{
//expand selection to include all protected content - if there is any
const TextCharAttrib* pStartAttr = mpImpl->mpTextEngine->FindCharAttrib(
TextPaM(mpImpl->maSelection.GetStart().GetPara(),
mpImpl->maSelection.GetStart().GetIndex()),
TEXTATTR_PROTECTED );
const TextCharAttrib* pEndAttr = mpImpl->mpTextEngine->FindCharAttrib(
TextPaM(mpImpl->maSelection.GetEnd().GetPara(),
mpImpl->maSelection.GetEnd().GetIndex()),
TEXTATTR_PROTECTED );
if(pStartAttr && pStartAttr->GetStart() < mpImpl->maSelection.GetStart().GetIndex())
{
mpImpl->maSelection.GetStart().GetIndex() = pStartAttr->GetStart();
aOldSel = mpImpl->maSelection; // update to deleted!
}
if(pEndAttr && pEndAttr->GetEnd() > mpImpl->maSelection.GetEnd().GetIndex())
{
mpImpl->maSelection.GetEnd().GetIndex() = pEndAttr->GetEnd();
aOldSel = mpImpl->maSelection; // update to deleted!
}
}
aCurSel = ImpDelete( nDel, nMode );
mpImpl->mpTextEngine->UndoActionEnd();
bModified = true;
bAllowIdle = false;
}
else
bDone = false;
}
break;
case KEY_TAB:
{
if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() &&
!rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() &&
ImplCheckTextLen( OUString('x') ) )
{
aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, '\t', !IsInsertMode() );
bModified = true;
}
else
bDone = false;
}
break;
case KEY_RETURN:
{
// do not swallow Shift-RETURN, as this would disable multi-line entries
// in dialogs & property editors
if ( !mpImpl->mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() &&
!rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( OUString('x') ) )
{
mpImpl->mpTextEngine->UndoActionStart();
aCurSel = mpImpl->mpTextEngine->ImpInsertParaBreak( aCurSel );
if ( mpImpl->mbAutoIndent )
{
TextNode* pPrev = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aCurSel.GetEnd().GetPara() - 1 ].get();
sal_Int32 n = 0;
while ( ( n < pPrev->GetText().getLength() ) && (
( pPrev->GetText()[ n ] == ' ' ) ||
( pPrev->GetText()[ n ] == '\t' ) ) )
{
n++;
}
if ( n )
aCurSel = mpImpl->mpTextEngine->ImpInsertText( aCurSel, pPrev->GetText().copy( 0, n ) );
}
mpImpl->mpTextEngine->UndoActionEnd();
bModified = true;
}
else
bDone = false;
}
break;
case KEY_INSERT:
{
if ( !mpImpl->mbReadOnly )
SetInsertMode( !IsInsertMode() );
}
break;
default:
{
if ( TextEngine::IsSimpleCharInput( rKeyEvent ) )
{
sal_Unicode nCharCode = rKeyEvent.GetCharCode();
if ( !mpImpl->mbReadOnly && ImplCheckTextLen( OUString(nCharCode) ) ) // otherwise swallow the character anyway
{
aCurSel = mpImpl->mpTextEngine->ImpInsertText( nCharCode, aCurSel, !IsInsertMode(), true );
bModified = true;
}
}
else
bDone = false;
}
}
}
if ( aCurSel != aOldSel ) // Check if changed, maybe other method already changed mpImpl->maSelection, don't overwrite that!
ImpSetSelection( aCurSel );
if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )
mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
if ( bModified )
{
// Idle-Formatter only if AnyInput
if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) )
mpImpl->mpTextEngine->IdleFormatAndUpdate( this );
else
mpImpl->mpTextEngine->FormatAndUpdate( this);
}
else if ( bMoved )
{
// selection is painted now in ImpMoveCursor
ImpShowCursor( mpImpl->mbAutoScroll, true, bEndKey );
}
if ( mpImpl->mpTextEngine->IsModified() )
mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
else if ( bWasModified )
mpImpl->mpTextEngine->SetModified( true );
return bDone;
}
void TextView::MouseButtonUp( const MouseEvent& rMouseEvent )
{
mpImpl->mbClickedInSelection = false;
mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
mpImpl->mpSelEngine->SelMouseButtonUp( rMouseEvent );
if ( rMouseEvent.IsMiddle() && !IsReadOnly() &&
( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
{
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
Paste( aSelection );
if ( mpImpl->mpTextEngine->IsModified() )
mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
}
else if ( rMouseEvent.IsLeft() && GetSelection().HasRange() )
{
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetWindow()->GetPrimarySelection());
Copy( aSelection );
}
}
void TextView::MouseButtonDown( const MouseEvent& rMouseEvent )
{
mpImpl->mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
mpImpl->mpTextEngine->SetActiveView( this );
mpImpl->mpSelEngine->SelMouseButtonDown( rMouseEvent );
// mbu 20.01.2005 - SelMouseButtonDown() possibly triggers a 'selection changed'
// notification. The appropriate handler could change the current selection,
// which is the case in the MailMerge address block control. To enable select'n'drag
// we need to reevaluate the selection after the notification has been fired.
mpImpl->mbClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
// special cases
if ( !rMouseEvent.IsShift() && ( rMouseEvent.GetClicks() >= 2 ) )
{
if ( rMouseEvent.IsMod2() )
{
HideSelection();
ImpSetSelection( mpImpl->maSelection.GetEnd() );
SetCursorAtPoint( rMouseEvent.GetPosPixel() ); // not set by SelectionEngine for MOD2
}
if ( rMouseEvent.GetClicks() == 2 )
{
// select word
if ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) )
{
HideSelection();
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ mpImpl->maSelection.GetEnd().GetPara() ].get();
css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
TextSelection aNewSel( mpImpl->maSelection );
aNewSel.GetStart().GetIndex() = aBoundary.startPos;
aNewSel.GetEnd().GetIndex() = aBoundary.endPos;
if(mpImpl->mbSupportProtectAttribute)
{
//expand selection to include all protected content - if there is any
const TextCharAttrib* pStartAttr = mpImpl->mpTextEngine->FindCharAttrib(
TextPaM(aNewSel.GetStart().GetPara(), aBoundary.startPos),
TEXTATTR_PROTECTED );
const TextCharAttrib* pEndAttr = mpImpl->mpTextEngine->FindCharAttrib(
TextPaM(aNewSel.GetEnd().GetPara(), aBoundary.endPos),
TEXTATTR_PROTECTED );
if(pStartAttr && pStartAttr->GetStart() < aNewSel.GetStart().GetIndex())
{
aNewSel.GetStart().GetIndex() = pStartAttr->GetStart();
}
if(pEndAttr && pEndAttr->GetEnd() > aNewSel.GetEnd().GetIndex())
{
aNewSel.GetEnd().GetIndex() = pEndAttr->GetEnd();
}
}
ImpSetSelection( aNewSel );
ShowSelection();
ShowCursor();
}
}
else if ( rMouseEvent.GetClicks() == 3 )
{
// select paragraph
if ( mpImpl->maSelection.GetStart().GetIndex() || ( mpImpl->maSelection.GetEnd().GetIndex() < mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection.GetEnd().GetPara() ) ) )
{
HideSelection();
TextSelection aNewSel( mpImpl->maSelection );
aNewSel.GetStart().GetIndex() = 0;
aNewSel.GetEnd().GetIndex() = mpImpl->mpTextEngine->mpDoc->GetNodes()[ mpImpl->maSelection.GetEnd().GetPara() ]->GetText().getLength();
ImpSetSelection( aNewSel );
ShowSelection();
ShowCursor();
}
}
}
}
void TextView::MouseMove( const MouseEvent& rMouseEvent )
{
mpImpl->mnTravelXPos = TRAVEL_X_DONTKNOW;
mpImpl->mpSelEngine->SelMouseMove( rMouseEvent );
}
void TextView::Command( const CommandEvent& rCEvt )
{
mpImpl->mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
mpImpl->mpTextEngine->SetActiveView( this );
if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
{
DeleteSelected();
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ GetSelection().GetEnd().GetPara() ].get();
mpImpl->mpTextEngine->mpIMEInfos = o3tl::make_unique<TEIMEInfos>( GetSelection().GetEnd(), pNode->GetText().copy( GetSelection().GetEnd().GetIndex() ) );
mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
}
else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
{
SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::EndExtTextInput => No Start ?" );
if( mpImpl->mpTextEngine->mpIMEInfos )
{
TEParaPortion* pPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
pPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
bool bInsertMode = !mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite;
mpImpl->mpTextEngine->mpIMEInfos.reset();
mpImpl->mpTextEngine->TextModified();
mpImpl->mpTextEngine->FormatAndUpdate( this );
SetInsertMode( bInsertMode );
if ( mpImpl->mpTextEngine->IsModified() )
mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
}
}
else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
{
SAL_WARN_IF( !mpImpl->mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?" );
if( mpImpl->mpTextEngine->mpIMEInfos )
{
const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
if ( !pData->IsOnlyCursorChanged() )
{
TextSelection aSelect( mpImpl->mpTextEngine->mpIMEInfos->aPos );
aSelect.GetEnd().GetIndex() += mpImpl->mpTextEngine->mpIMEInfos->nLen;
aSelect = mpImpl->mpTextEngine->ImpDeleteText( aSelect );
aSelect = mpImpl->mpTextEngine->ImpInsertText( aSelect, pData->GetText() );
if ( mpImpl->mpTextEngine->mpIMEInfos->bWasCursorOverwrite )
{
const sal_Int32 nOldIMETextLen = mpImpl->mpTextEngine->mpIMEInfos->nLen;
const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
if ( ( nOldIMETextLen > nNewIMETextLen ) &&
( nNewIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
{
// restore old characters
sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
aPaM.GetIndex() += nNewIMETextLen;
mpImpl->mpTextEngine->ImpInsertText( aPaM, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
}
else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
( nOldIMETextLen < mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
{
// overwrite
const sal_Int32 nOverwrite = std::min( nNewIMETextLen, mpImpl->mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) - nOldIMETextLen;
SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" );
TextPaM aPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos );
aPaM.GetIndex() += nNewIMETextLen;
TextSelection aSel( aPaM );
aSel.GetEnd().GetIndex() += nOverwrite;
mpImpl->mpTextEngine->ImpDeleteText( aSel );
}
}
if ( pData->GetTextAttr() )
{
mpImpl->mpTextEngine->mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
}
else
{
mpImpl->mpTextEngine->mpIMEInfos->DestroyAttribs();
}
TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara() );
pPPortion->MarkSelectionInvalid( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() );
mpImpl->mpTextEngine->FormatAndUpdate( this );
}
TextSelection aNewSel = TextPaM( mpImpl->mpTextEngine->mpIMEInfos->aPos.GetPara(), mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() );
SetSelection( aNewSel );
SetInsertMode( !pData->IsCursorOverwrite() );
if ( pData->IsCursorVisible() )
ShowCursor();
else
HideCursor();
}
}
else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
{
if ( mpImpl->mpTextEngine->mpIMEInfos && mpImpl->mpTextEngine->mpIMEInfos->nLen )
{
TextPaM aPaM( GetSelection().GetEnd() );
tools::Rectangle aR1 = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM );
sal_Int32 nInputEnd = mpImpl->mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpImpl->mpTextEngine->mpIMEInfos->nLen;
if ( !mpImpl->mpTextEngine->IsFormatted() )
mpImpl->mpTextEngine->FormatDoc();
TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
std::vector<TextLine>::size_type nLine = pParaPortion->GetLineNumber( aPaM.GetIndex(), true );
TextLine& rLine = pParaPortion->GetLines()[ nLine ];
if ( nInputEnd > rLine.GetEnd() )
nInputEnd = rLine.GetEnd();
tools::Rectangle aR2 = mpImpl->mpTextEngine->PaMtoEditCursor( TextPaM( aPaM.GetPara(), nInputEnd ) );
long nWidth = aR2.Left()-aR1.Right();
aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() );
GetWindow()->SetCursorRect( &aR1, nWidth );
}
else
{
GetWindow()->SetCursorRect();
}
}
else
{
mpImpl->mpSelEngine->Command( rCEvt );
}
}
void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
{
// this setting has more weight
if ( !mpImpl->mbAutoScroll )
bGotoCursor = false;
ImpShowCursor( bGotoCursor, bForceVisCursor, false );
}
void TextView::HideCursor()
{
mpImpl->mpCursor->Hide();
}
void TextView::Scroll( long ndX, long ndY )
{
SAL_WARN_IF( !mpImpl->mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!" );
if ( !ndX && !ndY )
return;
Point aNewStartPos( mpImpl->maStartDocPos );
// Vertical:
aNewStartPos.AdjustY( -ndY );
if ( aNewStartPos.Y() < 0 )
aNewStartPos.setY( 0 );
// Horizontal:
aNewStartPos.AdjustX( -ndX );
if ( aNewStartPos.X() < 0 )
aNewStartPos.setX( 0 );
long nDiffX = mpImpl->maStartDocPos.X() - aNewStartPos.X();
long nDiffY = mpImpl->maStartDocPos.Y() - aNewStartPos.Y();
if ( nDiffX || nDiffY )
{
bool bVisCursor = mpImpl->mpCursor->IsVisible();
mpImpl->mpCursor->Hide();
mpImpl->mpWindow->Update();
mpImpl->maStartDocPos = aNewStartPos;
if ( mpImpl->mpTextEngine->IsRightToLeft() )
nDiffX = -nDiffX;
mpImpl->mpWindow->Scroll( nDiffX, nDiffY );
mpImpl->mpWindow->Update();
mpImpl->mpCursor->SetPos( mpImpl->mpCursor->GetPos() + Point( nDiffX, nDiffY ) );
if ( bVisCursor && !mpImpl->mbReadOnly )
mpImpl->mpCursor->Show();
}
mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextViewScrolled ) );
}
void TextView::Undo()
{
mpImpl->mpTextEngine->SetActiveView( this );
mpImpl->mpTextEngine->GetUndoManager().Undo();
}
void TextView::Redo()
{
mpImpl->mpTextEngine->SetActiveView( this );
mpImpl->mpTextEngine->GetUndoManager().Redo();
}
void TextView::Cut()
{
mpImpl->mpTextEngine->UndoActionStart();
Copy();
DeleteSelected();
mpImpl->mpTextEngine->UndoActionEnd();
}
void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
{
if ( rxClipboard.is() )
{
TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
SolarMutexReleaser aReleaser;
try
{
rxClipboard->setContents( pDataObj, nullptr );
css::uno::Reference< css::datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, css::uno::UNO_QUERY );
if( xFlushableClipboard.is() )
xFlushableClipboard->flushClipboard();
}
catch( const css::uno::Exception& )
{
}
}
}
void TextView::Copy()
{
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
Copy( aClipboard );
}
void TextView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
{
if ( rxClipboard.is() )
{
css::uno::Reference< css::datatransfer::XTransferable > xDataObj;
try
{
SolarMutexReleaser aReleaser;
xDataObj = rxClipboard->getContents();
}
catch( const css::uno::Exception& )
{
}
if ( xDataObj.is() )
{
css::datatransfer::DataFlavor aFlavor;
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
if ( xDataObj->isDataFlavorSupported( aFlavor ) )
{
try
{
css::uno::Any aData = xDataObj->getTransferData( aFlavor );
OUString aText;
aData >>= aText;
bool bWasTruncated = false;
if( mpImpl->mpTextEngine->GetMaxTextLen() != 0 )
bWasTruncated = ImplTruncateNewText( aText );
InsertText( aText );
mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
if( bWasTruncated )
Edit::ShowTruncationWarning(mpImpl->mpWindow->GetFrameWeld());
}
catch( const css::datatransfer::UnsupportedFlavorException& )
{
}
}
}
}
}
void TextView::Paste()
{
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
Paste( aClipboard );
}
OUString TextView::GetSelected()
{
return GetSelected( GetSystemLineEnd() );
}
OUString TextView::GetSelected( LineEnd aSeparator )
{
return mpImpl->mpTextEngine->GetText( mpImpl->maSelection, aSeparator );
}
void TextView::SetInsertMode( bool bInsert )
{
if ( mpImpl->mbInsertMode != bInsert )
{
mpImpl->mbInsertMode = bInsert;
ShowCursor( mpImpl->mbAutoScroll, false );
}
}
void TextView::SetReadOnly( bool bReadOnly )
{
if ( mpImpl->mbReadOnly != bReadOnly )
{
mpImpl->mbReadOnly = bReadOnly;
if ( !mpImpl->mbReadOnly )
ShowCursor( mpImpl->mbAutoScroll, false );
else
HideCursor();
GetWindow()->SetInputContext( InputContext( mpImpl->mpTextEngine->GetFont(), bReadOnly ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
}
}
TextSelection const & TextView::ImpMoveCursor( const KeyEvent& rKeyEvent )
{
// normally only needed for Up/Down; but who cares
mpImpl->mpTextEngine->CheckIdleFormatter();
TextPaM aPaM( mpImpl->maSelection.GetEnd() );
TextPaM aOldEnd( aPaM );
TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
if ( mpImpl->mpTextEngine->IsRightToLeft() )
eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
bool bSelect = aTranslatedKeyEvent.GetKeyCode().IsShift();
switch ( nCode )
{
case KEY_UP: aPaM = CursorUp( aPaM );
break;
case KEY_DOWN: aPaM = CursorDown( aPaM );
break;
case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
break;
case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
break;
case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM );
break;
case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM );
break;
case KEY_LEFT: aPaM = bCtrl ? CursorWordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
break;
case KEY_RIGHT: aPaM = bCtrl ? CursorWordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
break;
case css::awt::Key::SELECT_WORD_FORWARD:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_WORD_FORWARD:
aPaM = CursorWordRight( aPaM );
break;
case css::awt::Key::SELECT_WORD_BACKWARD:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_WORD_BACKWARD:
aPaM = CursorWordLeft( aPaM );
break;
case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
aPaM = CursorStartOfLine( aPaM );
break;
case css::awt::Key::SELECT_TO_END_OF_LINE:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_TO_END_OF_LINE:
aPaM = CursorEndOfLine( aPaM );
break;
case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
aPaM = CursorStartOfParagraph( aPaM );
break;
case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
aPaM = CursorEndOfParagraph( aPaM );
break;
case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
aPaM = CursorStartOfDoc();
break;
case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
bSelect = true;
SAL_FALLTHROUGH;
case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
aPaM = CursorEndOfDoc();
break;
}
// might cause a CreateAnchor or Deselection all
mpImpl->mpSelEngine->CursorPosChanging( bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
if ( aOldEnd != aPaM )
{
mpImpl->mpTextEngine->CursorMoved( aOldEnd.GetPara() );
TextSelection aNewSelection( mpImpl->maSelection );
aNewSelection.GetEnd() = aPaM;
if ( bSelect )
{
// extend the selection
ImpSetSelection( aNewSelection );
ShowSelection( TextSelection( aOldEnd, aPaM ) );
}
else
{
aNewSelection.GetStart() = aPaM;
ImpSetSelection( aNewSelection );
}
}
return mpImpl->maSelection;
}
void TextView::InsertText( const OUString& rStr )
{
mpImpl->mpTextEngine->UndoActionStart();
TextSelection aNewSel = mpImpl->mpTextEngine->ImpInsertText( mpImpl->maSelection, rStr );
ImpSetSelection( aNewSel );
mpImpl->mpTextEngine->UndoActionEnd();
mpImpl->mpTextEngine->FormatAndUpdate( this );
}
TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
{
TextPaM aPaM( rPaM );
if ( aPaM.GetIndex() )
{
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
sal_Int32 nCount = 1;
aPaM.GetIndex() = xBI->previousCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
}
else if ( aPaM.GetPara() )
{
aPaM.GetPara()--;
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
aPaM.GetIndex() = pNode->GetText().getLength();
}
return aPaM;
}
TextPaM TextView::CursorRight( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
{
TextPaM aPaM( rPaM );
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
if ( aPaM.GetIndex() < pNode->GetText().getLength() )
{
css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
sal_Int32 nCount = 1;
aPaM.GetIndex() = xBI->nextCharacters( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), nCharacterIteratorMode, nCount, nCount );
}
else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
{
aPaM.GetPara()++;
aPaM.GetIndex() = 0;
}
return aPaM;
}
TextPaM TextView::CursorWordLeft( const TextPaM& rPaM )
{
TextPaM aPaM( rPaM );
if ( aPaM.GetIndex() )
{
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
if ( aBoundary.startPos >= rPaM.GetIndex() )
aBoundary = xBI->previousWord( pNode->GetText(), rPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
aPaM.GetIndex() = ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0;
}
else if ( aPaM.GetPara() )
{
aPaM.GetPara()--;
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
aPaM.GetIndex() = pNode->GetText().getLength();
}
return aPaM;
}
TextPaM TextView::CursorWordRight( const TextPaM& rPaM )
{
TextPaM aPaM( rPaM );
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
if ( aPaM.GetIndex() < pNode->GetText().getLength() )
{
css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
css::i18n::Boundary aBoundary = xBI->nextWord( pNode->GetText(), aPaM.GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
aPaM.GetIndex() = aBoundary.startPos;
}
else if ( aPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size()-1) )
{
aPaM.GetPara()++;
aPaM.GetIndex() = 0;
}
return aPaM;
}
TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode )
{
if ( mpImpl->maSelection.HasRange() ) // only delete selection
return mpImpl->mpTextEngine->ImpDeleteText( mpImpl->maSelection );
TextPaM aStartPaM = mpImpl->maSelection.GetStart();
TextPaM aEndPaM = aStartPaM;
if ( nMode == DEL_LEFT )
{
if ( nDelMode == DELMODE_SIMPLE )
{
aEndPaM = CursorLeft( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) );
}
else if ( nDelMode == DELMODE_RESTOFWORD )
{
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
css::i18n::Boundary aBoundary = xBI->getWordBoundary( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
if ( aBoundary.startPos == mpImpl->maSelection.GetEnd().GetIndex() )
aBoundary = xBI->previousWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
// #i63506# startPos is -1 when the paragraph starts with a tab
aEndPaM.GetIndex() = std::max<sal_Int32>(aBoundary.startPos, 0);
}
else // DELMODE_RESTOFCONTENT
{
if ( aEndPaM.GetIndex() != 0 )
aEndPaM.GetIndex() = 0;
else if ( aEndPaM.GetPara() )
{
// previous paragraph
aEndPaM.GetPara()--;
aEndPaM.GetIndex() = 0;
}
}
}
else
{
if ( nDelMode == DELMODE_SIMPLE )
{
aEndPaM = CursorRight( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
}
else if ( nDelMode == DELMODE_RESTOFWORD )
{
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
css::uno::Reference < css::i18n::XBreakIterator > xBI = mpImpl->mpTextEngine->GetBreakIterator();
css::i18n::Boundary aBoundary = xBI->nextWord( pNode->GetText(), mpImpl->maSelection.GetEnd().GetIndex(), mpImpl->mpTextEngine->GetLocale(), css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
aEndPaM.GetIndex() = aBoundary.startPos;
}
else // DELMODE_RESTOFCONTENT
{
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
if ( aEndPaM.GetIndex() < pNode->GetText().getLength() )
aEndPaM.GetIndex() = pNode->GetText().getLength();
else if ( aEndPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) )
{
// next paragraph
aEndPaM.GetPara()++;
TextNode* pNextNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aEndPaM.GetPara() ].get();
aEndPaM.GetIndex() = pNextNode->GetText().getLength();
}
}
}
return mpImpl->mpTextEngine->ImpDeleteText( TextSelection( aStartPaM, aEndPaM ) );
}
TextPaM TextView::CursorUp( const TextPaM& rPaM )
{
TextPaM aPaM( rPaM );
long nX;
if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
{
nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
}
else
nX = mpImpl->mnTravelXPos;
TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
if ( nLine ) // same paragraph
{
aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine-1, nX );
// If we need to go to the end of a line that was wrapped automatically,
// the cursor ends up at the beginning of the 2nd line
// Problem: Last character of an automatically wrapped line = Cursor
TextLine& rLine = pPPortion->GetLines()[ nLine - 1 ];
if ( aPaM.GetIndex() && ( aPaM.GetIndex() == rLine.GetEnd() ) )
--aPaM.GetIndex();
}
else if ( rPaM.GetPara() ) // previous paragraph
{
aPaM.GetPara()--;
pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1;
aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), nL, nX+1 );
}
return aPaM;
}
TextPaM TextView::CursorDown( const TextPaM& rPaM )
{
TextPaM aPaM( rPaM );
long nX;
if ( mpImpl->mnTravelXPos == TRAVEL_X_DONTKNOW )
{
nX = mpImpl->mpTextEngine->GetEditCursor( rPaM, false ).Left();
mpImpl->mnTravelXPos = static_cast<sal_uInt16>(nX)+1;
}
else
nX = mpImpl->mnTravelXPos;
TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
if ( nLine < ( pPPortion->GetLines().size() - 1 ) )
{
aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( rPaM.GetPara(), nLine+1, nX );
// special case CursorUp
TextLine& rLine = pPPortion->GetLines()[ nLine + 1 ];
if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && aPaM.GetIndex() < pPPortion->GetNode()->GetText().getLength() )
--aPaM.GetIndex();
}
else if ( rPaM.GetPara() < ( mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1 ) ) // next paragraph
{
aPaM.GetPara()++;
pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
aPaM.GetIndex() = mpImpl->mpTextEngine->GetCharPos( aPaM.GetPara(), 0, nX+1 );
TextLine& rLine = pPPortion->GetLines().front();
if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && ( pPPortion->GetLines().size() > 1 ) )
--aPaM.GetIndex();
}
return aPaM;
}
TextPaM TextView::CursorStartOfLine( const TextPaM& rPaM )
{
TextPaM aPaM( rPaM );
TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
TextLine& rLine = pPPortion->GetLines()[ nLine ];
aPaM.GetIndex() = rLine.GetStart();
return aPaM;
}
TextPaM TextView::CursorEndOfLine( const TextPaM& rPaM )
{
TextPaM aPaM( rPaM );
TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( rPaM.GetPara() );
std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
TextLine& rLine = pPPortion->GetLines()[ nLine ];
aPaM.GetIndex() = rLine.GetEnd();
if ( rLine.GetEnd() > rLine.GetStart() ) // empty line
{
sal_Unicode cLastChar = pPPortion->GetNode()->GetText()[ aPaM.GetIndex()-1 ];
if ( ( cLastChar == ' ' ) && ( aPaM.GetIndex() != pPPortion->GetNode()->GetText().getLength() ) )
{
// for a blank in an automatically-wrapped line it is better to stand before it,
// as the user will intend to stand behind the prior word.
// If there is a change, special case for Pos1 after End!
--aPaM.GetIndex();
}
}
return aPaM;
}
TextPaM TextView::CursorStartOfParagraph( const TextPaM& rPaM )
{
TextPaM aPaM( rPaM );
aPaM.GetIndex() = 0;
return aPaM;
}
TextPaM TextView::CursorEndOfParagraph( const TextPaM& rPaM )
{
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ rPaM.GetPara() ].get();
TextPaM aPaM( rPaM );
aPaM.GetIndex() = pNode->GetText().getLength();
return aPaM;
}
TextPaM TextView::CursorStartOfDoc()
{
TextPaM aPaM( 0, 0 );
return aPaM;
}
TextPaM TextView::CursorEndOfDoc()
{
const sal_uInt32 nNode = static_cast<sal_uInt32>(mpImpl->mpTextEngine->mpDoc->GetNodes().size() - 1);
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ nNode ].get();
TextPaM aPaM( nNode, pNode->GetText().getLength() );
return aPaM;
}
TextPaM TextView::PageUp( const TextPaM& rPaM )
{
tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
Point aTopLeft = aRect.TopLeft();
aTopLeft.AdjustY( -(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10) );
aTopLeft.AdjustX(1 );
if ( aTopLeft.Y() < 0 )
aTopLeft.setY( 0 );
TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aTopLeft );
return aPaM;
}
TextPaM TextView::PageDown( const TextPaM& rPaM )
{
tools::Rectangle aRect = mpImpl->mpTextEngine->PaMtoEditCursor( rPaM );
Point aBottomRight = aRect.BottomRight();
aBottomRight.AdjustY(mpImpl->mpWindow->GetOutputSizePixel().Height() * 9/10 );
aBottomRight.AdjustX(1 );
long nHeight = mpImpl->mpTextEngine->GetTextHeight();
if ( aBottomRight.Y() > nHeight )
aBottomRight.setY( nHeight-1 );
TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aBottomRight );
return aPaM;
}
void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial )
{
if ( mpImpl->mpTextEngine->IsFormatting() )
return;
if ( !mpImpl->mpTextEngine->GetUpdateMode() )
return;
if ( mpImpl->mpTextEngine->IsInUndo() )
return;
mpImpl->mpTextEngine->CheckIdleFormatter();
if ( !mpImpl->mpTextEngine->IsFormatted() )
mpImpl->mpTextEngine->FormatAndUpdate( this );
TextPaM aPaM( mpImpl->maSelection.GetEnd() );
tools::Rectangle aEditCursor = mpImpl->mpTextEngine->PaMtoEditCursor( aPaM, bSpecial );
// Remember that we placed the cursor behind the last character of a line
mpImpl->mbCursorAtEndOfLine = false;
if( bSpecial )
{
TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
mpImpl->mbCursorAtEndOfLine =
pParaPortion->GetLineNumber( aPaM.GetIndex(), true ) != pParaPortion->GetLineNumber( aPaM.GetIndex(), false );
}
if ( !IsInsertMode() && !mpImpl->maSelection.HasRange() )
{
TextNode* pNode = mpImpl->mpTextEngine->mpDoc->GetNodes()[ aPaM.GetPara() ].get();
if ( !pNode->GetText().isEmpty() && ( aPaM.GetIndex() < pNode->GetText().getLength() ) )
{
// If we are behind a portion, and the next portion has other direction, we must change position...
aEditCursor.SetLeft( mpImpl->mpTextEngine->GetEditCursor( aPaM, false, true ).Left() );
aEditCursor.SetRight( aEditCursor.Left() );
TEParaPortion* pParaPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
sal_Int32 nTextPortionStart = 0;
std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
TETextPortion* pTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
if ( pTextPortion->GetKind() == PORTIONKIND_TAB )
{
aEditCursor.AdjustRight(pTextPortion->GetWidth() );
}
else
{
TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
aEditCursor.SetRight( mpImpl->mpTextEngine->GetEditCursor( aNext, true ).Left() );
}
}
}
Size aOutSz = mpImpl->mpWindow->GetOutputSizePixel();
if ( aEditCursor.GetHeight() > aOutSz.Height() )
aEditCursor.SetBottom( aEditCursor.Top() + aOutSz.Height() - 1 );
aEditCursor.AdjustLeft( -1 );
if ( bGotoCursor
// #i81283# protect maStartDocPos against initialization problems
&& aOutSz.Width() && aOutSz.Height()
)
{
long nVisStartY = mpImpl->maStartDocPos.Y();
long nVisEndY = mpImpl->maStartDocPos.Y() + aOutSz.Height();
long nVisStartX = mpImpl->maStartDocPos.X();
long nVisEndX = mpImpl->maStartDocPos.X() + aOutSz.Width();
long nMoreX = aOutSz.Width() / 4;
Point aNewStartPos( mpImpl->maStartDocPos );
if ( aEditCursor.Bottom() > nVisEndY )
{
aNewStartPos.AdjustY( aEditCursor.Bottom() - nVisEndY);
}
else if ( aEditCursor.Top() < nVisStartY )
{
aNewStartPos.AdjustY( -( nVisStartY - aEditCursor.Top() ) );
}
if ( aEditCursor.Right() >= nVisEndX )
{
aNewStartPos.AdjustX( aEditCursor.Right() - nVisEndX );
// do you want some more?
aNewStartPos.AdjustX(nMoreX );
}
else if ( aEditCursor.Left() <= nVisStartX )
{
aNewStartPos.AdjustX( -( nVisStartX - aEditCursor.Left() ) );
// do you want some more?
aNewStartPos.AdjustX( -nMoreX );
}
// X can be wrong for the 'some more' above:
// sal_uLong nMaxTextWidth = mpImpl->mpTextEngine->GetMaxTextWidth();
// if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) )
// nMaxTextWidth = 0x7FFFFFFF;
// long nMaxX = (long)nMaxTextWidth - aOutSz.Width();
long nMaxX = mpImpl->mpTextEngine->CalcTextWidth() - aOutSz.Width();
if ( nMaxX < 0 )
nMaxX = 0;
if ( aNewStartPos.X() < 0 )
aNewStartPos.setX( 0 );
else if ( aNewStartPos.X() > nMaxX )
aNewStartPos.setX( nMaxX );
// Y should not be further down than needed
long nYMax = mpImpl->mpTextEngine->GetTextHeight() - aOutSz.Height();
if ( nYMax < 0 )
nYMax = 0;
if ( aNewStartPos.Y() > nYMax )
aNewStartPos.setY( nYMax );
if ( aNewStartPos != mpImpl->maStartDocPos )
Scroll( -(aNewStartPos.X() - mpImpl->maStartDocPos.X()), -(aNewStartPos.Y() - mpImpl->maStartDocPos.Y()) );
}
if ( aEditCursor.Right() < aEditCursor.Left() )
{
long n = aEditCursor.Left();
aEditCursor.SetLeft( aEditCursor.Right() );
aEditCursor.SetRight( n );
}
Point aPoint( GetWindowPos( !mpImpl->mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft() : aEditCursor.TopRight() ) );
mpImpl->mpCursor->SetPos( aPoint );
mpImpl->mpCursor->SetSize( aEditCursor.GetSize() );
if ( bForceVisCursor && mpImpl->mbCursorEnabled )
mpImpl->mpCursor->Show();
}
void TextView::SetCursorAtPoint( const Point& rPosPixel )
{
mpImpl->mpTextEngine->CheckIdleFormatter();
Point aDocPos = GetDocPos( rPosPixel );
TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
// aTmpNewSel: Diff between old and new; not the new selection
TextSelection aTmpNewSel( mpImpl->maSelection.GetEnd(), aPaM );
TextSelection aNewSel( mpImpl->maSelection );
aNewSel.GetEnd() = aPaM;
if ( !mpImpl->mpSelEngine->HasAnchor() )
{
if ( mpImpl->maSelection.GetStart() != aPaM )
mpImpl->mpTextEngine->CursorMoved( mpImpl->maSelection.GetStart().GetPara() );
aNewSel.GetStart() = aPaM;
ImpSetSelection( aNewSel );
}
else
{
ImpSetSelection( aNewSel );
ShowSelection( aTmpNewSel );
}
bool bForceCursor = !mpImpl->mpDDInfo; // && !mbInSelection
ImpShowCursor( mpImpl->mbAutoScroll, bForceCursor, false );
}
bool TextView::IsSelectionAtPoint( const Point& rPosPixel )
{
Point aDocPos = GetDocPos( rPosPixel );
TextPaM aPaM = mpImpl->mpTextEngine->GetPaM( aDocPos );
// BeginDrag is only called, however, if IsSelectionAtPoint()
// Problem: IsSelectionAtPoint is not called by Command()
// if before MBDown returned false.
return IsInSelection( aPaM );
}
bool TextView::IsInSelection( const TextPaM& rPaM )
{
TextSelection aSel = mpImpl->maSelection;
aSel.Justify();
const sal_uInt32 nStartNode = aSel.GetStart().GetPara();
const sal_uInt32 nEndNode = aSel.GetEnd().GetPara();
const sal_uInt32 nCurNode = rPaM.GetPara();
if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
return true;
if ( nStartNode == nEndNode )
{
if ( nCurNode == nStartNode )
if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
return true;
}
else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) )
return true;
else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
return true;
return false;
}
void TextView::ImpHideDDCursor()
{
if ( mpImpl->mpDDInfo && mpImpl->mpDDInfo->mbVisCursor )
{
mpImpl->mpDDInfo->maCursor.Hide();
mpImpl->mpDDInfo->mbVisCursor = false;
}
}
void TextView::ImpShowDDCursor()
{
if ( !mpImpl->mpDDInfo->mbVisCursor )
{
tools::Rectangle aCursor = mpImpl->mpTextEngine->PaMtoEditCursor( mpImpl->mpDDInfo->maDropPos, true );
aCursor.AdjustRight( 1 );
aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );
mpImpl->mpDDInfo->maCursor.SetWindow( mpImpl->mpWindow );
mpImpl->mpDDInfo->maCursor.SetPos( aCursor.TopLeft() );
mpImpl->mpDDInfo->maCursor.SetSize( aCursor.GetSize() );
mpImpl->mpDDInfo->maCursor.Show();
mpImpl->mpDDInfo->mbVisCursor = true;
}
}
void TextView::SetPaintSelection( bool bPaint )
{
if ( bPaint != mpImpl->mbPaintSelection )
{
mpImpl->mbPaintSelection = bPaint;
ShowSelection( mpImpl->maSelection );
}
}
void TextView::Read( SvStream& rInput )
{
mpImpl->mpTextEngine->Read( rInput, &mpImpl->maSelection );
ShowCursor();
}
bool TextView::ImplTruncateNewText( OUString& rNewText ) const
{
bool bTruncated = false;
const sal_Int32 nMaxLen = mpImpl->mpTextEngine->GetMaxTextLen();
// 0 means unlimited
if( nMaxLen != 0 )
{
const sal_Int32 nCurLen = mpImpl->mpTextEngine->GetTextLen();
const sal_Int32 nNewLen = rNewText.getLength();
if ( nCurLen + nNewLen > nMaxLen )
{
// see how much text will be replaced
const sal_Int32 nSelLen = mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
if ( nCurLen + nNewLen - nSelLen > nMaxLen )
{
const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen);
rNewText = rNewText.copy( 0, nTruncatedLen );
bTruncated = true;
}
}
}
return bTruncated;
}
bool TextView::ImplCheckTextLen( const OUString& rNewText )
{
bool bOK = true;
if ( mpImpl->mpTextEngine->GetMaxTextLen() )
{
sal_Int32 n = mpImpl->mpTextEngine->GetTextLen() + rNewText.getLength();
if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
{
// calculate how much text is being deleted
n -= mpImpl->mpTextEngine->GetTextLen( mpImpl->maSelection );
if ( n > mpImpl->mpTextEngine->GetMaxTextLen() )
bOK = false;
}
}
return bOK;
}
void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
{
if ( mpImpl->mbClickedInSelection )
{
SolarMutexGuard aVclGuard;
SAL_WARN_IF( !mpImpl->maSelection.HasRange(), "vcl", "TextView::dragGestureRecognized: mpImpl->mbClickedInSelection, but no selection?" );
mpImpl->mpDDInfo.reset(new TextDDInfo);
mpImpl->mpDDInfo->mbStarterOfDD = true;
TETextDataObject* pDataObj = new TETextDataObject( GetSelected() );
mpImpl->mpCursor->Hide();
sal_Int8 nActions = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
if ( !IsReadOnly() )
nActions |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mpImpl->mxDnDListener );
}
}
void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& )
{
ImpHideDDCursor();
mpImpl->mpDDInfo.reset();
}
void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
{
SolarMutexGuard aVclGuard;
if ( !mpImpl->mbReadOnly && mpImpl->mpDDInfo )
{
ImpHideDDCursor();
// Data for deleting after DROP_MOVE:
TextSelection aPrevSel( mpImpl->maSelection );
aPrevSel.Justify();
const sal_uInt32 nPrevParaCount = mpImpl->mpTextEngine->GetParagraphCount();
const sal_Int32 nPrevStartParaLen = mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() );
bool bStarterOfDD = false;
for ( sal_uInt16 nView = mpImpl->mpTextEngine->GetViewCount(); nView && !bStarterOfDD; )
bStarterOfDD = mpImpl->mpTextEngine->GetView( --nView )->mpImpl->mpDDInfo && mpImpl->mpTextEngine->GetView( nView )->mpImpl->mpDDInfo->mbStarterOfDD;
HideSelection();
ImpSetSelection( mpImpl->mpDDInfo->maDropPos );
mpImpl->mpTextEngine->UndoActionStart();
OUString aText;
css::uno::Reference< css::datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
if ( xDataObj.is() )
{
css::datatransfer::DataFlavor aFlavor;
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
if ( xDataObj->isDataFlavorSupported( aFlavor ) )
{
css::uno::Any aData = xDataObj->getTransferData( aFlavor );
OUString aOUString;
aData >>= aOUString;
aText = convertLineEnd(aOUString, LINEEND_LF);
}
}
if ( !aText.isEmpty() && ( aText[ aText.getLength()-1 ] == LINE_SEP ) )
aText = aText.copy(0, aText.getLength()-1);
TextPaM aTempStart = mpImpl->maSelection.GetStart();
if ( ImplCheckTextLen( aText ) )
ImpSetSelection( mpImpl->mpTextEngine->ImpInsertText( mpImpl->mpDDInfo->maDropPos, aText ) );
if(mpImpl->mbSupportProtectAttribute)
{
mpImpl->mpTextEngine->SetAttrib( TextAttribProtect(),
aTempStart.GetPara(),
aTempStart.GetIndex(),
mpImpl->maSelection.GetEnd().GetIndex(), false );
}
if ( aPrevSel.HasRange() &&
!mpImpl->mbSupportProtectAttribute && // don't remove currently selected element
(( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) )
{
// adjust selection if necessary
if ( ( mpImpl->mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara() ) ||
( ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
&& ( mpImpl->mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex() ) ) )
{
const sal_uInt32 nNewParasBeforeSelection =
mpImpl->mpTextEngine->GetParagraphCount() - nPrevParaCount;
aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection;
aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection;
if ( mpImpl->mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara() )
{
const sal_Int32 nNewChars =
mpImpl->mpTextEngine->GetTextLen( aPrevSel.GetStart().GetPara() ) - nPrevStartParaLen;
aPrevSel.GetStart().GetIndex() += nNewChars;
if ( aPrevSel.GetStart().GetPara() == aPrevSel.GetEnd().GetPara() )
aPrevSel.GetEnd().GetIndex() += nNewChars;
}
}
else
{
// adjust current selection
TextPaM aPaM = mpImpl->maSelection.GetStart();
aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() );
if ( aPrevSel.GetEnd().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
{
aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex();
if ( aPrevSel.GetStart().GetPara() == mpImpl->mpDDInfo->maDropPos.GetPara() )
aPaM.GetIndex() += aPrevSel.GetStart().GetIndex();
}
ImpSetSelection( aPaM );
}
mpImpl->mpTextEngine->ImpDeleteText( aPrevSel );
}
mpImpl->mpTextEngine->UndoActionEnd();
mpImpl->mpDDInfo.reset();
mpImpl->mpTextEngine->FormatAndUpdate( this );
mpImpl->mpTextEngine->Broadcast( TextHint( SfxHintId::TextModified ) );
}
rDTDE.Context->dropComplete( false/*bChanges*/ );
}
void TextView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& )
{
}
void TextView::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
{
SolarMutexGuard aVclGuard;
ImpHideDDCursor();
}
void TextView::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
{
SolarMutexGuard aVclGuard;
if (!mpImpl->mpDDInfo)
mpImpl->mpDDInfo.reset(new TextDDInfo);
TextPaM aPrevDropPos = mpImpl->mpDDInfo->maDropPos;
Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
Point aDocPos = GetDocPos( aMousePos );
mpImpl->mpDDInfo->maDropPos = mpImpl->mpTextEngine->GetPaM( aDocPos );
bool bProtected = false;
if(mpImpl->mbSupportProtectAttribute)
{
const TextCharAttrib* pStartAttr = mpImpl->mpTextEngine->FindCharAttrib(
mpImpl->mpDDInfo->maDropPos,
TEXTATTR_PROTECTED );
bProtected = pStartAttr != nullptr &&
pStartAttr->GetStart() != mpImpl->mpDDInfo->maDropPos.GetIndex() &&
pStartAttr->GetEnd() != mpImpl->mpDDInfo->maDropPos.GetIndex();
}
// Don't drop in selection or in read only engine
if ( IsReadOnly() || IsInSelection( mpImpl->mpDDInfo->maDropPos ) || bProtected)
{
ImpHideDDCursor();
rDTDE.Context->rejectDrag();
}
else
{
// delete old Cursor
if ( !mpImpl->mpDDInfo->mbVisCursor || ( aPrevDropPos != mpImpl->mpDDInfo->maDropPos ) )
{
ImpHideDDCursor();
ImpShowDDCursor();
}
rDTDE.Context->acceptDrag( rDTDE.DropAction );
}
}
Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const
{
Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() );
if ( mpImpl->mpTextEngine->IsRightToLeft() )
{
Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
aStartPos.setX( rStartDocPos.X() + aSz.Width() - 1 ); // -1: Start is 0
}
return aStartPos;
}
Point TextView::GetDocPos( const Point& rWindowPos ) const
{
// Window Position => Document Position
Point aPoint;
aPoint.setY( rWindowPos.Y() + mpImpl->maStartDocPos.Y() );
if ( !mpImpl->mpTextEngine->IsRightToLeft() )
{
aPoint.setX( rWindowPos.X() + mpImpl->maStartDocPos.X() );
}
else
{
Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
aPoint.setX( ( aSz.Width() - 1 ) - rWindowPos.X() + mpImpl->maStartDocPos.X() );
}
return aPoint;
}
Point TextView::GetWindowPos( const Point& rDocPos ) const
{
// Document Position => Window Position
Point aPoint;
aPoint.setY( rDocPos.Y() - mpImpl->maStartDocPos.Y() );
if ( !mpImpl->mpTextEngine->IsRightToLeft() )
{
aPoint.setX( rDocPos.X() - mpImpl->maStartDocPos.X() );
}
else
{
Size aSz = mpImpl->mpWindow->GetOutputSizePixel();
aPoint.setX( ( aSz.Width() - 1 ) - ( rDocPos.X() - mpImpl->maStartDocPos.X() ) );
}
return aPoint;
}
sal_Int32 TextView::GetLineNumberOfCursorInSelection() const
{
// PROGRESS
sal_Int32 nLineNo = -1;
if( mpImpl->mbCursorEnabled )
{
TextPaM aPaM = GetSelection().GetEnd();
TEParaPortion* pPPortion = mpImpl->mpTextEngine->mpTEParaPortions->GetObject( aPaM.GetPara() );
nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
//TODO: std::vector<TextLine>::size_type -> sal_Int32!
if( mpImpl->mbCursorAtEndOfLine )
--nLineNo;
}
return nLineNo;
}
// (+) class TextSelFunctionSet
TextSelFunctionSet::TextSelFunctionSet( TextView* pView )
{
mpView = pView;
}
void TextSelFunctionSet::BeginDrag()
{
}
void TextSelFunctionSet::CreateAnchor()
{
// TextSelection aSel( mpView->GetSelection() );
// aSel.GetStart() = aSel.GetEnd();
// mpView->SetSelection( aSel );
// may not be followed by ShowCursor
mpView->HideSelection();
mpView->ImpSetSelection( mpView->mpImpl->maSelection.GetEnd() );
}
void TextSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool )
{
mpView->SetCursorAtPoint( rPointPixel );
}
bool TextSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
{
return mpView->IsSelectionAtPoint( rPointPixel );
}
void TextSelFunctionSet::DeselectAll()
{
CreateAnchor();
}
void TextSelFunctionSet::DeselectAtPoint( const Point& )
{
// only for multiple selection
}
void TextSelFunctionSet::DestroyAnchor()
{
// only for multiple selection
}
TextEngine* TextView::GetTextEngine() const
{ return mpImpl->mpTextEngine; }
vcl::Window* TextView::GetWindow() const
{ return mpImpl->mpWindow; }
void TextView::EnableCursor( bool bEnable )
{ mpImpl->mbCursorEnabled = bEnable; }
bool TextView::IsCursorEnabled() const
{ return mpImpl->mbCursorEnabled; }
void TextView::SetStartDocPos( const Point& rPos )
{ mpImpl->maStartDocPos = rPos; }
const Point& TextView::GetStartDocPos() const
{ return mpImpl->maStartDocPos; }
void TextView::SetAutoIndentMode( bool bAutoIndent )
{ mpImpl->mbAutoIndent = bAutoIndent; }
bool TextView::IsReadOnly() const
{ return mpImpl->mbReadOnly; }
void TextView::SetAutoScroll( bool bAutoScroll )
{ mpImpl->mbAutoScroll = bAutoScroll; }
bool TextView::IsAutoScroll() const
{ return mpImpl->mbAutoScroll; }
bool TextView::HasSelection() const
{ return mpImpl->maSelection.HasRange(); }
bool TextView::IsInsertMode() const
{ return mpImpl->mbInsertMode; }
void TextView::SupportProtectAttribute(bool bSupport)
{ mpImpl->mbSupportProtectAttribute = bSupport;}
void TextView::MatchGroup()
{
TextSelection aTmpSel( GetSelection() );
aTmpSel.Justify();
if ( ( aTmpSel.GetStart().GetPara() != aTmpSel.GetEnd().GetPara() ) ||
( ( aTmpSel.GetEnd().GetIndex() - aTmpSel.GetStart().GetIndex() ) > 1 ) )
{
return;
}
TextSelection aMatchSel = static_cast<ExtTextEngine*>(GetTextEngine())->MatchGroup( aTmpSel.GetStart() );
if ( aMatchSel.HasRange() )
SetSelection( aMatchSel );
}
bool TextView::Search( const i18nutil::SearchOptions& rSearchOptions, bool bForward )
{
bool bFound = false;
TextSelection aSel( GetSelection() );
if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) )
{
bFound = true;
// First add the beginning of the word to the selection,
// so that the whole word is in the visible region.
SetSelection( aSel.GetStart() );
ShowCursor( true, false );
}
else
{
aSel = GetSelection().GetEnd();
}
SetSelection( aSel );
ShowCursor();
return bFound;
}
sal_uInt16 TextView::Replace( const i18nutil::SearchOptions& rSearchOptions, bool bAll, bool bForward )
{
sal_uInt16 nFound = 0;
if ( !bAll )
{
if ( GetSelection().HasRange() )
{
InsertText( rSearchOptions.replaceString );
nFound = 1;
Search( rSearchOptions, bForward ); // right away to the next
}
else
{
if( Search( rSearchOptions, bForward ) )
nFound = 1;
}
}
else
{
// the writer replaces all, from beginning to end
ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine());
// HideSelection();
TextSelection aSel;
bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) );
if ( bSearchInSelection )
{
aSel = GetSelection();
aSel.Justify();
}
TextSelection aSearchSel( aSel );
bool bFound = pTextEngine->Search( aSel, rSearchOptions );
if ( bFound )
pTextEngine->UndoActionStart();
while ( bFound )
{
nFound++;
TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString );
aSel = aSearchSel;
aSel.GetStart() = aNewStart;
bFound = pTextEngine->Search( aSel, rSearchOptions );
}
if ( nFound )
{
SetSelection( aSel.GetStart() );
pTextEngine->FormatAndUpdate( this );
pTextEngine->UndoActionEnd();
}
}
return nFound;
}
bool TextView::ImpIndentBlock( bool bRight )
{
bool bDone = false;
TextSelection aSel = GetSelection();
aSel.Justify();
HideSelection();
GetTextEngine()->UndoActionStart();
const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() )
{
nEndPara--; // do not indent
}
for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara )
{
if ( bRight )
{
// add tabs
GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' );
bDone = true;
}
else
{
// remove Tabs/Blanks
OUString aText = GetTextEngine()->GetText( nPara );
if ( !aText.isEmpty() && (
( aText[ 0 ] == '\t' ) ||
( aText[ 0 ] == ' ' ) ) )
{
GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) );
bDone = true;
}
}
}
GetTextEngine()->UndoActionEnd();
bool bRange = aSel.HasRange();
if ( bRight )
{
++aSel.GetStart().GetIndex();
if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) )
++aSel.GetEnd().GetIndex();
}
else
{
if ( aSel.GetStart().GetIndex() )
--aSel.GetStart().GetIndex();
if ( bRange && aSel.GetEnd().GetIndex() )
--aSel.GetEnd().GetIndex();
}
ImpSetSelection( aSel );
GetTextEngine()->FormatAndUpdate( this );
return bDone;
}
bool TextView::IndentBlock()
{
return ImpIndentBlock( true );
}
bool TextView::UnindentBlock()
{
return ImpIndentBlock( false );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V524 It is odd that the body of 'HideSelection' function is fully equivalent to the body of 'ShowSelection' function.