2420 lines
81 KiB
C++
2420 lines
81 KiB
C++
/* -*- 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/string_view.hxx>
|
|
#include <utility>
|
|
#include <vcl/textview.hxx>
|
|
#include <vcl/texteng.hxx>
|
|
#include <vcl/settings.hxx>
|
|
#include "textdoc.hxx"
|
|
#include <vcl/textdata.hxx>
|
|
#include <vcl/transfer.hxx>
|
|
#include <vcl/xtextedt.hxx>
|
|
#include "textdat2.hxx"
|
|
#include <vcl/commandevent.hxx>
|
|
#include <vcl/inputctx.hxx>
|
|
|
|
#include <svl/undo.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 <cppuhelper/weak.hxx>
|
|
#include <cppuhelper/queryinterface.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/util/SearchFlags.hpp>
|
|
|
|
#include <vcl/toolkit/edit.hxx>
|
|
|
|
#include <sot/exchange.hxx>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
|
|
TETextDataObject::TETextDataObject( OUString aText ) : maText(std::move( aText ))
|
|
{
|
|
}
|
|
|
|
// 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 <<= maText;
|
|
}
|
|
else if ( nT == SotClipboardFormatId::HTML )
|
|
{
|
|
sal_uInt64 nLen = GetHTMLStream().TellEnd();
|
|
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 );
|
|
}
|
|
|
|
TextView::TextView(ExtTextEngine* pEng, vcl::Window* pWindow)
|
|
{
|
|
pWindow->EnableRTL( false );
|
|
|
|
mpWindow = pWindow;
|
|
mpTextEngine = pEng;
|
|
|
|
mbPaintSelection = true;
|
|
mbAutoScroll = true;
|
|
mbInsertMode = true;
|
|
mbReadOnly = false;
|
|
mbAutoIndent = false;
|
|
mbCursorEnabled = true;
|
|
mbClickedInSelection = false;
|
|
mbCursorAtEndOfLine = false;
|
|
// mbInSelection = false;
|
|
|
|
mnTravelXPos = TRAVEL_X_DONTKNOW;
|
|
|
|
mpSelFuncSet = std::make_unique<TextSelFunctionSet>(this);
|
|
mpSelEngine = std::make_unique<SelectionEngine>(mpWindow, mpSelFuncSet.get());
|
|
mpSelEngine->SetSelectionMode(SelectionMode::Range);
|
|
mpSelEngine->EnableDrag(true);
|
|
|
|
mpCursor.reset(new vcl::Cursor);
|
|
mpCursor->Show();
|
|
pWindow->SetCursor(mpCursor.get());
|
|
pWindow->SetInputContext( InputContext( pEng->GetFont(), InputContextFlags::Text|InputContextFlags::ExtText ) );
|
|
|
|
pWindow->GetOutDev()->SetLineColor();
|
|
|
|
if ( pWindow->GetDragGestureRecognizer().is() )
|
|
{
|
|
mxDnDListener = new vcl::unohelper::DragAndDropWrapper(this);
|
|
|
|
pWindow->GetDragGestureRecognizer()->addDragGestureListener(mxDnDListener);
|
|
pWindow->GetDropTarget()->addDropTargetListener(mxDnDListener);
|
|
pWindow->GetDropTarget()->setActive( true );
|
|
pWindow->GetDropTarget()->setDefaultActions( css::datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
|
|
}
|
|
}
|
|
|
|
TextView::~TextView()
|
|
{
|
|
mpSelEngine.reset();
|
|
mpSelFuncSet.reset();
|
|
|
|
if (mpWindow->GetCursor() == mpCursor.get())
|
|
mpWindow->SetCursor(nullptr);
|
|
|
|
mpCursor.reset();
|
|
mpDDInfo.reset();
|
|
}
|
|
|
|
void TextView::Invalidate()
|
|
{
|
|
mpWindow->Invalidate();
|
|
}
|
|
|
|
void TextView::SetSelection( const TextSelection& rTextSel, bool bGotoCursor )
|
|
{
|
|
// if someone left an empty attribute and then the Outliner manipulated the selection
|
|
if (!maSelection.HasRange())
|
|
mpTextEngine->CursorMoved(maSelection.GetStart().GetPara());
|
|
|
|
// if the selection is manipulated after a KeyInput
|
|
mpTextEngine->CheckIdleFormatter();
|
|
|
|
HideSelection();
|
|
TextSelection aNewSel( rTextSel );
|
|
mpTextEngine->ValidateSelection(aNewSel);
|
|
ImpSetSelection( aNewSel );
|
|
ShowSelection();
|
|
ShowCursor( bGotoCursor );
|
|
}
|
|
|
|
void TextView::SetSelection( const TextSelection& rTextSel )
|
|
{
|
|
SetSelection(rTextSel, mbAutoScroll);
|
|
}
|
|
|
|
const TextSelection& TextView::GetSelection() const
|
|
{
|
|
return maSelection;
|
|
}
|
|
|
|
TextSelection& TextView::GetSelection()
|
|
{
|
|
return maSelection;
|
|
}
|
|
|
|
void TextView::DeleteSelected()
|
|
{
|
|
// HideSelection();
|
|
|
|
mpTextEngine->UndoActionStart();
|
|
TextPaM aPaM = mpTextEngine->ImpDeleteText(maSelection);
|
|
mpTextEngine->UndoActionEnd();
|
|
|
|
ImpSetSelection( aPaM );
|
|
mpTextEngine->FormatAndUpdate(this);
|
|
ShowCursor();
|
|
}
|
|
|
|
void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection)
|
|
{
|
|
if (!mbPaintSelection)
|
|
{
|
|
pSelection = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// set correct background color;
|
|
// unfortunately we cannot detect if it has changed
|
|
vcl::Font aFont = mpTextEngine->GetFont();
|
|
Color aColor = rRenderContext.GetBackground().GetColor();
|
|
aColor.SetAlpha(255);
|
|
if (aColor != aFont.GetFillColor())
|
|
{
|
|
if (aFont.IsTransparent())
|
|
aColor = COL_TRANSPARENT;
|
|
aFont.SetFillColor(aColor);
|
|
mpTextEngine->maFont = std::move(aFont);
|
|
}
|
|
}
|
|
|
|
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 (!mpTextEngine->GetUpdateMode() || mpTextEngine->IsInUndo())
|
|
return;
|
|
|
|
TextSelection *pDrawSelection = nullptr;
|
|
if (maSelection.HasRange())
|
|
pDrawSelection = &maSelection;
|
|
|
|
Point aStartPos = ImpGetOutputStartPos(maStartDocPos);
|
|
ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
|
|
}
|
|
|
|
void TextView::ImpSetSelection( const TextSelection& rSelection )
|
|
{
|
|
if (rSelection == maSelection)
|
|
return;
|
|
|
|
bool bCaret = false, bSelection = false;
|
|
const TextPaM &rEnd = rSelection.GetEnd();
|
|
const TextPaM& rOldEnd = maSelection.GetEnd();
|
|
bool bGap = rSelection.HasRange(), bOldGap = maSelection.HasRange();
|
|
if (rEnd != rOldEnd)
|
|
bCaret = true;
|
|
if (bGap || bOldGap)
|
|
bSelection = true;
|
|
|
|
maSelection = rSelection;
|
|
|
|
if (bSelection)
|
|
mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged));
|
|
|
|
if (bCaret)
|
|
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 : &maSelection;
|
|
|
|
if ( !pRangeOrSelection->HasRange() )
|
|
return;
|
|
|
|
if (mpWindow->IsPaintTransparent())
|
|
mpWindow->Invalidate();
|
|
else
|
|
{
|
|
TextSelection aRange( *pRangeOrSelection );
|
|
aRange.Justify();
|
|
bool bVisCursor = mpCursor->IsVisible();
|
|
mpCursor->Hide();
|
|
Invalidate();
|
|
if (bVisCursor)
|
|
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 = mpTextEngine->IsModified();
|
|
mpTextEngine->SetModified(false);
|
|
|
|
TextSelection aCurSel(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 (!mbReadOnly)
|
|
Cut();
|
|
}
|
|
break;
|
|
case KeyFuncType::COPY:
|
|
{
|
|
Copy();
|
|
}
|
|
break;
|
|
case KeyFuncType::PASTE:
|
|
{
|
|
if (!mbReadOnly)
|
|
Paste();
|
|
}
|
|
break;
|
|
case KeyFuncType::UNDO:
|
|
{
|
|
if (!mbReadOnly)
|
|
Undo();
|
|
}
|
|
break;
|
|
case KeyFuncType::REDO:
|
|
{
|
|
if (!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(GetSystemPrimarySelection());
|
|
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 (!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;
|
|
}
|
|
|
|
mpTextEngine->UndoActionStart();
|
|
aCurSel = ImpDelete( nDel, nMode );
|
|
mpTextEngine->UndoActionEnd();
|
|
bModified = true;
|
|
bAllowIdle = false;
|
|
}
|
|
else
|
|
bDone = false;
|
|
}
|
|
break;
|
|
case KEY_TAB:
|
|
{
|
|
if ( !mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() &&
|
|
!rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() &&
|
|
ImplCheckTextLen( u"x" ) )
|
|
{
|
|
aCurSel = 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 ( !mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() &&
|
|
!rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( u"x" ) )
|
|
{
|
|
mpTextEngine->UndoActionStart();
|
|
aCurSel = mpTextEngine->ImpInsertParaBreak(aCurSel);
|
|
if (mbAutoIndent)
|
|
{
|
|
TextNode* pPrev = 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
|
|
= mpTextEngine->ImpInsertText(aCurSel, pPrev->GetText().copy(0, n));
|
|
}
|
|
mpTextEngine->UndoActionEnd();
|
|
bModified = true;
|
|
}
|
|
else
|
|
bDone = false;
|
|
}
|
|
break;
|
|
case KEY_INSERT:
|
|
{
|
|
if (!mbReadOnly)
|
|
SetInsertMode( !IsInsertMode() );
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
if ( TextEngine::IsSimpleCharInput( rKeyEvent ) )
|
|
{
|
|
sal_Unicode nCharCode = rKeyEvent.GetCharCode();
|
|
if (!mbReadOnly && ImplCheckTextLen(OUStringChar(nCharCode))) // otherwise swallow the character anyway
|
|
{
|
|
aCurSel = mpTextEngine->ImpInsertText(nCharCode, aCurSel, !IsInsertMode(), true);
|
|
bModified = true;
|
|
}
|
|
}
|
|
else
|
|
bDone = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( aCurSel != aOldSel ) // Check if changed, maybe other method already changed maSelection, don't overwrite that!
|
|
ImpSetSelection( aCurSel );
|
|
|
|
if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )
|
|
mnTravelXPos = TRAVEL_X_DONTKNOW;
|
|
|
|
if ( bModified )
|
|
{
|
|
// Idle-Formatter only if AnyInput
|
|
if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) )
|
|
mpTextEngine->IdleFormatAndUpdate(this);
|
|
else
|
|
mpTextEngine->FormatAndUpdate(this);
|
|
}
|
|
else if ( bMoved )
|
|
{
|
|
// selection is painted now in ImpMoveCursor
|
|
ImpShowCursor(mbAutoScroll, true, bEndKey);
|
|
}
|
|
|
|
if (mpTextEngine->IsModified())
|
|
mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
|
|
else if ( bWasModified )
|
|
mpTextEngine->SetModified(true);
|
|
|
|
return bDone;
|
|
}
|
|
|
|
void TextView::MouseButtonUp( const MouseEvent& rMouseEvent )
|
|
{
|
|
mbClickedInSelection = false;
|
|
mnTravelXPos = TRAVEL_X_DONTKNOW;
|
|
mpSelEngine->SelMouseButtonUp(rMouseEvent);
|
|
if ( rMouseEvent.IsMiddle() && !IsReadOnly() &&
|
|
( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
|
|
{
|
|
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
|
|
Paste( aSelection );
|
|
if (mpTextEngine->IsModified())
|
|
mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
|
|
}
|
|
else if ( rMouseEvent.IsLeft() && GetSelection().HasRange() )
|
|
{
|
|
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
|
|
Copy( aSelection );
|
|
}
|
|
}
|
|
|
|
void TextView::MouseButtonDown( const MouseEvent& rMouseEvent )
|
|
{
|
|
mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
|
|
mnTravelXPos = TRAVEL_X_DONTKNOW;
|
|
mbClickedInSelection = IsSelectionAtPoint(rMouseEvent.GetPosPixel());
|
|
|
|
mpTextEngine->SetActiveView(this);
|
|
|
|
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.
|
|
mbClickedInSelection = IsSelectionAtPoint(rMouseEvent.GetPosPixel());
|
|
|
|
// special cases
|
|
if ( rMouseEvent.IsShift() || ( rMouseEvent.GetClicks() < 2 ))
|
|
return;
|
|
|
|
if ( rMouseEvent.IsMod2() )
|
|
{
|
|
HideSelection();
|
|
ImpSetSelection(maSelection.GetEnd());
|
|
SetCursorAtPoint( rMouseEvent.GetPosPixel() ); // not set by SelectionEngine for MOD2
|
|
}
|
|
|
|
if ( rMouseEvent.GetClicks() == 2 )
|
|
{
|
|
// select word
|
|
if (maSelection.GetEnd().GetIndex() < mpTextEngine->GetTextLen( maSelection.GetEnd().GetPara()))
|
|
{
|
|
HideSelection();
|
|
// tdf#57879 - expand selection to include connector punctuations
|
|
TextSelection aNewSel;
|
|
mpTextEngine->GetWord(maSelection.GetEnd(), &aNewSel.GetStart(), &aNewSel.GetEnd());
|
|
ImpSetSelection( aNewSel );
|
|
ShowSelection();
|
|
ShowCursor();
|
|
}
|
|
}
|
|
else if ( rMouseEvent.GetClicks() == 3 )
|
|
{
|
|
// select paragraph
|
|
if (maSelection.GetStart().GetIndex() || (maSelection.GetEnd().GetIndex() < mpTextEngine->GetTextLen(maSelection.GetEnd().GetPara())))
|
|
{
|
|
HideSelection();
|
|
TextSelection aNewSel(maSelection);
|
|
aNewSel.GetStart().GetIndex() = 0;
|
|
aNewSel.GetEnd().GetIndex() = mpTextEngine->mpDoc->GetNodes()[maSelection.GetEnd().GetPara()]->GetText().getLength();
|
|
ImpSetSelection( aNewSel );
|
|
ShowSelection();
|
|
ShowCursor();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextView::MouseMove( const MouseEvent& rMouseEvent )
|
|
{
|
|
mnTravelXPos = TRAVEL_X_DONTKNOW;
|
|
mpSelEngine->SelMouseMove(rMouseEvent);
|
|
}
|
|
|
|
void TextView::Command( const CommandEvent& rCEvt )
|
|
{
|
|
mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
|
|
mpTextEngine->SetActiveView(this);
|
|
|
|
if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
|
|
{
|
|
DeleteSelected();
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[GetSelection().GetEnd().GetPara()].get();
|
|
mpTextEngine->mpIMEInfos = std::make_unique<TEIMEInfos>(
|
|
GetSelection().GetEnd(), pNode->GetText().copy(GetSelection().GetEnd().GetIndex()));
|
|
mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
|
|
{
|
|
SAL_WARN_IF(!mpTextEngine->mpIMEInfos, "vcl",
|
|
"CommandEventId::EndExtTextInput => No Start ?");
|
|
if (mpTextEngine->mpIMEInfos)
|
|
{
|
|
TEParaPortion* pPortion = mpTextEngine->mpTEParaPortions->GetObject(
|
|
mpTextEngine->mpIMEInfos->aPos.GetPara());
|
|
pPortion->MarkSelectionInvalid(mpTextEngine->mpIMEInfos->aPos.GetIndex());
|
|
|
|
bool bInsertMode = !mpTextEngine->mpIMEInfos->bWasCursorOverwrite;
|
|
|
|
mpTextEngine->mpIMEInfos.reset();
|
|
|
|
mpTextEngine->TextModified();
|
|
mpTextEngine->FormatAndUpdate(this);
|
|
|
|
SetInsertMode( bInsertMode );
|
|
|
|
if (mpTextEngine->IsModified())
|
|
mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
|
|
}
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
|
|
{
|
|
SAL_WARN_IF(!mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?");
|
|
if (mpTextEngine->mpIMEInfos)
|
|
{
|
|
const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
|
|
|
|
if ( !pData->IsOnlyCursorChanged() )
|
|
{
|
|
TextSelection aSelect(mpTextEngine->mpIMEInfos->aPos);
|
|
aSelect.GetEnd().GetIndex() += mpTextEngine->mpIMEInfos->nLen;
|
|
aSelect = mpTextEngine->ImpDeleteText(aSelect);
|
|
aSelect = mpTextEngine->ImpInsertText(aSelect, pData->GetText());
|
|
|
|
if (mpTextEngine->mpIMEInfos->bWasCursorOverwrite)
|
|
{
|
|
const sal_Int32 nOldIMETextLen = mpTextEngine->mpIMEInfos->nLen;
|
|
const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
|
|
|
|
if ((nOldIMETextLen > nNewIMETextLen) &&
|
|
( nNewIMETextLen < mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
|
|
{
|
|
// restore old characters
|
|
sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
|
|
TextPaM aPaM(mpTextEngine->mpIMEInfos->aPos);
|
|
aPaM.GetIndex() += nNewIMETextLen;
|
|
mpTextEngine->ImpInsertText(aPaM, mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy(nNewIMETextLen, nRestore));
|
|
}
|
|
else if ((nOldIMETextLen < nNewIMETextLen) &&
|
|
( nOldIMETextLen < mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
|
|
{
|
|
// overwrite
|
|
const sal_Int32 nOverwrite = std::min(nNewIMETextLen, mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength()) - nOldIMETextLen;
|
|
SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" );
|
|
TextPaM aPaM(mpTextEngine->mpIMEInfos->aPos);
|
|
aPaM.GetIndex() += nNewIMETextLen;
|
|
TextSelection aSel( aPaM );
|
|
aSel.GetEnd().GetIndex() += nOverwrite;
|
|
mpTextEngine->ImpDeleteText(aSel);
|
|
}
|
|
}
|
|
|
|
if ( pData->GetTextAttr() )
|
|
{
|
|
mpTextEngine->mpIMEInfos->CopyAttribs(pData->GetTextAttr(), pData->GetText().getLength());
|
|
}
|
|
else
|
|
{
|
|
mpTextEngine->mpIMEInfos->DestroyAttribs();
|
|
}
|
|
|
|
TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(
|
|
mpTextEngine->mpIMEInfos->aPos.GetPara());
|
|
pPPortion->MarkSelectionInvalid(mpTextEngine->mpIMEInfos->aPos.GetIndex());
|
|
mpTextEngine->FormatAndUpdate(this);
|
|
}
|
|
|
|
TextSelection aNewSel
|
|
= TextPaM(mpTextEngine->mpIMEInfos->aPos.GetPara(),
|
|
mpTextEngine->mpIMEInfos->aPos.GetIndex() + pData->GetCursorPos());
|
|
SetSelection( aNewSel );
|
|
SetInsertMode( !pData->IsCursorOverwrite() );
|
|
|
|
if ( pData->IsCursorVisible() )
|
|
ShowCursor();
|
|
else
|
|
HideCursor();
|
|
}
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
|
|
{
|
|
if (mpTextEngine->mpIMEInfos && mpTextEngine->mpIMEInfos->nLen)
|
|
{
|
|
TextPaM aPaM( GetSelection().GetEnd() );
|
|
tools::Rectangle aR1 = mpTextEngine->PaMtoEditCursor(aPaM);
|
|
|
|
sal_Int32 nInputEnd
|
|
= mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpTextEngine->mpIMEInfos->nLen;
|
|
|
|
if (!mpTextEngine->IsFormatted())
|
|
mpTextEngine->FormatDoc();
|
|
|
|
TEParaPortion* pParaPortion = 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
|
|
= mpTextEngine->PaMtoEditCursor(TextPaM(aPaM.GetPara(), nInputEnd));
|
|
|
|
tools::Long nWidth = aR2.Left()-aR1.Right();
|
|
aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() );
|
|
GetWindow()->SetCursorRect( &aR1, nWidth );
|
|
}
|
|
else
|
|
{
|
|
GetWindow()->SetCursorRect();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mpSelEngine->Command(rCEvt);
|
|
}
|
|
}
|
|
|
|
void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
|
|
{
|
|
// this setting has more weight
|
|
if (!mbAutoScroll)
|
|
bGotoCursor = false;
|
|
ImpShowCursor( bGotoCursor, bForceVisCursor, false );
|
|
}
|
|
|
|
void TextView::HideCursor()
|
|
{
|
|
mpCursor->Hide();
|
|
}
|
|
|
|
void TextView::Scroll( tools::Long ndX, tools::Long ndY )
|
|
{
|
|
SAL_WARN_IF(!mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!");
|
|
|
|
if ( !ndX && !ndY )
|
|
return;
|
|
|
|
Point aNewStartPos(maStartDocPos);
|
|
|
|
// Vertical:
|
|
aNewStartPos.AdjustY( -ndY );
|
|
if ( aNewStartPos.Y() < 0 )
|
|
aNewStartPos.setY( 0 );
|
|
|
|
// Horizontal:
|
|
aNewStartPos.AdjustX( -ndX );
|
|
if ( aNewStartPos.X() < 0 )
|
|
aNewStartPos.setX( 0 );
|
|
|
|
tools::Long nDiffX = maStartDocPos.X() - aNewStartPos.X();
|
|
tools::Long nDiffY = maStartDocPos.Y() - aNewStartPos.Y();
|
|
|
|
if ( nDiffX || nDiffY )
|
|
{
|
|
bool bVisCursor = mpCursor->IsVisible();
|
|
mpCursor->Hide();
|
|
mpWindow->PaintImmediately();
|
|
maStartDocPos = aNewStartPos;
|
|
|
|
if (mpTextEngine->IsRightToLeft())
|
|
nDiffX = -nDiffX;
|
|
mpWindow->Scroll(nDiffX, nDiffY);
|
|
mpWindow->PaintImmediately();
|
|
mpCursor->SetPos(mpCursor->GetPos() + Point(nDiffX, nDiffY));
|
|
if (bVisCursor && !mbReadOnly)
|
|
mpCursor->Show();
|
|
}
|
|
|
|
mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewScrolled));
|
|
}
|
|
|
|
void TextView::Undo()
|
|
{
|
|
mpTextEngine->SetActiveView(this);
|
|
mpTextEngine->GetUndoManager().Undo();
|
|
}
|
|
|
|
void TextView::Redo()
|
|
{
|
|
mpTextEngine->SetActiveView(this);
|
|
mpTextEngine->GetUndoManager().Redo();
|
|
}
|
|
|
|
void TextView::Cut()
|
|
{
|
|
mpTextEngine->UndoActionStart();
|
|
Copy();
|
|
DeleteSelected();
|
|
mpTextEngine->UndoActionEnd();
|
|
}
|
|
|
|
void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
|
|
{
|
|
if ( !rxClipboard.is() )
|
|
return;
|
|
|
|
rtl::Reference<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() )
|
|
return;
|
|
|
|
css::uno::Reference< css::datatransfer::XTransferable > xDataObj;
|
|
|
|
try
|
|
{
|
|
SolarMutexReleaser aReleaser;
|
|
xDataObj = rxClipboard->getContents();
|
|
}
|
|
catch( const css::uno::Exception& )
|
|
{
|
|
}
|
|
|
|
if ( !xDataObj.is() )
|
|
return;
|
|
|
|
css::datatransfer::DataFlavor aFlavor;
|
|
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
|
|
if ( !xDataObj->isDataFlavorSupported( aFlavor ) )
|
|
return;
|
|
|
|
try
|
|
{
|
|
css::uno::Any aData = xDataObj->getTransferData( aFlavor );
|
|
OUString aText;
|
|
aData >>= aText;
|
|
bool bWasTruncated = false;
|
|
if (mpTextEngine->GetMaxTextLen() != 0)
|
|
bWasTruncated = ImplTruncateNewText( aText );
|
|
InsertText( aText );
|
|
mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
|
|
|
|
if( bWasTruncated )
|
|
Edit::ShowTruncationWarning(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() const
|
|
{
|
|
return GetSelected( GetSystemLineEnd() );
|
|
}
|
|
|
|
OUString TextView::GetSelected( LineEnd aSeparator ) const
|
|
{
|
|
return mpTextEngine->GetText(maSelection, aSeparator);
|
|
}
|
|
|
|
void TextView::SetInsertMode( bool bInsert )
|
|
{
|
|
if (mbInsertMode != bInsert)
|
|
{
|
|
mbInsertMode = bInsert;
|
|
ShowCursor(mbAutoScroll, false);
|
|
}
|
|
}
|
|
|
|
void TextView::SetReadOnly( bool bReadOnly )
|
|
{
|
|
if (mbReadOnly != bReadOnly)
|
|
{
|
|
mbReadOnly = bReadOnly;
|
|
if (!mbReadOnly)
|
|
ShowCursor(mbAutoScroll, false);
|
|
else
|
|
HideCursor();
|
|
|
|
GetWindow()->SetInputContext(
|
|
InputContext(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
|
|
mpTextEngine->CheckIdleFormatter();
|
|
|
|
TextPaM aPaM(maSelection.GetEnd());
|
|
TextPaM aOldEnd( aPaM );
|
|
|
|
TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
|
|
if (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:
|
|
if (bCtrl)
|
|
{
|
|
aPaM = CursorStartOfDoc();
|
|
}
|
|
else
|
|
{
|
|
// tdf#145764 - move cursor to the beginning or the first non-space character in the same line
|
|
const TextPaM aFirstWordPaM = CursorFirstWord(aPaM);
|
|
aPaM = aPaM.GetIndex() == aFirstWordPaM.GetIndex() ? CursorStartOfLine(aPaM) : aFirstWordPaM;
|
|
}
|
|
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;
|
|
[[fallthrough]];
|
|
case css::awt::Key::MOVE_WORD_FORWARD:
|
|
aPaM = CursorWordRight( aPaM );
|
|
break;
|
|
case css::awt::Key::SELECT_WORD_BACKWARD:
|
|
bSelect = true;
|
|
[[fallthrough]];
|
|
case css::awt::Key::MOVE_WORD_BACKWARD:
|
|
aPaM = CursorWordLeft( aPaM );
|
|
break;
|
|
case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
|
|
bSelect = true;
|
|
[[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;
|
|
[[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;
|
|
[[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;
|
|
[[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;
|
|
[[fallthrough]];
|
|
case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
|
|
aPaM = CursorStartOfDoc();
|
|
break;
|
|
case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
|
|
bSelect = true;
|
|
[[fallthrough]];
|
|
case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
|
|
aPaM = CursorEndOfDoc();
|
|
break;
|
|
}
|
|
|
|
// might cause a CreateAnchor or Deselection all
|
|
mpSelEngine->CursorPosChanging(bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1());
|
|
|
|
if ( aOldEnd != aPaM )
|
|
{
|
|
mpTextEngine->CursorMoved(aOldEnd.GetPara());
|
|
|
|
TextSelection aNewSelection(maSelection);
|
|
aNewSelection.GetEnd() = aPaM;
|
|
if ( bSelect )
|
|
{
|
|
// extend the selection
|
|
ImpSetSelection( aNewSelection );
|
|
ShowSelection( TextSelection( aOldEnd, aPaM ) );
|
|
}
|
|
else
|
|
{
|
|
aNewSelection.GetStart() = aPaM;
|
|
ImpSetSelection( aNewSelection );
|
|
}
|
|
}
|
|
|
|
return maSelection;
|
|
}
|
|
|
|
void TextView::InsertText( const OUString& rStr )
|
|
{
|
|
mpTextEngine->UndoActionStart();
|
|
|
|
TextSelection aNewSel = mpTextEngine->ImpInsertText(maSelection, rStr);
|
|
|
|
ImpSetSelection( aNewSel );
|
|
|
|
mpTextEngine->UndoActionEnd();
|
|
|
|
mpTextEngine->FormatAndUpdate(this);
|
|
}
|
|
|
|
TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
|
|
{
|
|
TextPaM aPaM( rPaM );
|
|
|
|
if ( aPaM.GetIndex() )
|
|
{
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
|
|
css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
|
|
sal_Int32 nCount = 1;
|
|
aPaM.GetIndex()
|
|
= xBI->previousCharacters(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
|
|
nCharacterIteratorMode, nCount, nCount);
|
|
}
|
|
else if ( aPaM.GetPara() )
|
|
{
|
|
aPaM.GetPara()--;
|
|
TextNode* pNode = 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 = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
|
|
if ( aPaM.GetIndex() < pNode->GetText().getLength() )
|
|
{
|
|
css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
|
|
sal_Int32 nCount = 1;
|
|
aPaM.GetIndex()
|
|
= xBI->nextCharacters(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
|
|
nCharacterIteratorMode, nCount, nCount);
|
|
}
|
|
else if (aPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1))
|
|
{
|
|
aPaM.GetPara()++;
|
|
aPaM.GetIndex() = 0;
|
|
}
|
|
|
|
return aPaM;
|
|
}
|
|
|
|
TextPaM TextView::CursorFirstWord( const TextPaM& rPaM )
|
|
{
|
|
TextPaM aPaM(rPaM);
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
|
|
|
|
css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
|
|
aPaM.GetIndex() = xBI->beginOfSentence(pNode->GetText(), 0, mpTextEngine->GetLocale());
|
|
|
|
return aPaM;
|
|
}
|
|
|
|
TextPaM TextView::CursorWordLeft( const TextPaM& rPaM )
|
|
{
|
|
TextPaM aPaM( rPaM );
|
|
|
|
if ( aPaM.GetIndex() )
|
|
{
|
|
// tdf#57879 - expand selection to the left to include connector punctuations
|
|
mpTextEngine->GetWord(rPaM, &aPaM);
|
|
if ( aPaM.GetIndex() >= rPaM.GetIndex() )
|
|
{
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
|
|
css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
|
|
aPaM.GetIndex()
|
|
= xBI->previousWord(pNode->GetText(), rPaM.GetIndex(), mpTextEngine->GetLocale(),
|
|
css::i18n::WordType::ANYWORD_IGNOREWHITESPACES).startPos;
|
|
if ( aPaM.GetIndex() > 0 )
|
|
mpTextEngine->GetWord(aPaM, &aPaM);
|
|
else
|
|
aPaM.GetIndex() = 0;
|
|
}
|
|
}
|
|
else if ( aPaM.GetPara() )
|
|
{
|
|
aPaM.GetPara()--;
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
|
|
aPaM.GetIndex() = pNode->GetText().getLength();
|
|
}
|
|
return aPaM;
|
|
}
|
|
|
|
TextPaM TextView::CursorWordRight( const TextPaM& rPaM )
|
|
{
|
|
TextPaM aPaM( rPaM );
|
|
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
|
|
if ( aPaM.GetIndex() < pNode->GetText().getLength() )
|
|
{
|
|
css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
|
|
// tdf#160202 - NextWord unexpectedly skips two words at the start of any word
|
|
const auto aWordBoundary
|
|
= xBI->getWordBoundary(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
|
|
css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
|
|
|
|
// Check if the current index is inside the word boundary
|
|
if (aWordBoundary.startPos <= aPaM.GetIndex() && aPaM.GetIndex() < aWordBoundary.endPos)
|
|
aPaM.GetIndex() = aWordBoundary.startPos;
|
|
else
|
|
aPaM.GetIndex()
|
|
= xBI->nextWord(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
|
|
css::i18n::WordType::ANYWORD_IGNOREWHITESPACES)
|
|
.endPos;
|
|
mpTextEngine->GetWord(aPaM, nullptr, &aPaM);
|
|
}
|
|
else if (aPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1))
|
|
{
|
|
aPaM.GetPara()++;
|
|
aPaM.GetIndex() = 0;
|
|
}
|
|
|
|
return aPaM;
|
|
}
|
|
|
|
TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode )
|
|
{
|
|
if (maSelection.HasRange()) // only delete selection
|
|
return mpTextEngine->ImpDeleteText(maSelection);
|
|
|
|
TextPaM aStartPaM = 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 = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
|
|
css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
|
|
css::i18n::Boundary aBoundary = xBI->getWordBoundary(
|
|
pNode->GetText(), maSelection.GetEnd().GetIndex(), mpTextEngine->GetLocale(),
|
|
css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
|
|
if (aBoundary.startPos == maSelection.GetEnd().GetIndex())
|
|
aBoundary = xBI->previousWord(pNode->GetText(), maSelection.GetEnd().GetIndex(),
|
|
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 = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
|
|
css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
|
|
css::i18n::Boundary aBoundary = xBI->nextWord(
|
|
pNode->GetText(), maSelection.GetEnd().GetIndex(), mpTextEngine->GetLocale(),
|
|
css::i18n::WordType::ANYWORD_IGNOREWHITESPACES);
|
|
aEndPaM.GetIndex() = aBoundary.startPos;
|
|
}
|
|
else // DELMODE_RESTOFCONTENT
|
|
{
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
|
|
if ( aEndPaM.GetIndex() < pNode->GetText().getLength() )
|
|
aEndPaM.GetIndex() = pNode->GetText().getLength();
|
|
else if (aEndPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1))
|
|
{
|
|
// next paragraph
|
|
aEndPaM.GetPara()++;
|
|
TextNode* pNextNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
|
|
aEndPaM.GetIndex() = pNextNode->GetText().getLength();
|
|
}
|
|
}
|
|
}
|
|
|
|
return mpTextEngine->ImpDeleteText(TextSelection(aStartPaM, aEndPaM));
|
|
}
|
|
|
|
TextPaM TextView::CursorUp( const TextPaM& rPaM )
|
|
{
|
|
TextPaM aPaM( rPaM );
|
|
|
|
tools::Long nX;
|
|
if (mnTravelXPos == TRAVEL_X_DONTKNOW)
|
|
{
|
|
nX = mpTextEngine->GetEditCursor(rPaM, false).Left();
|
|
mnTravelXPos = static_cast<sal_uInt16>(nX) + 1;
|
|
}
|
|
else
|
|
nX = mnTravelXPos;
|
|
|
|
TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara());
|
|
std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
|
|
if ( nLine ) // same paragraph
|
|
{
|
|
aPaM.GetIndex() = 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 = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
|
|
std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1;
|
|
aPaM.GetIndex() = mpTextEngine->GetCharPos(aPaM.GetPara(), nL, nX + 1);
|
|
}
|
|
|
|
return aPaM;
|
|
}
|
|
|
|
TextPaM TextView::CursorDown( const TextPaM& rPaM )
|
|
{
|
|
TextPaM aPaM( rPaM );
|
|
|
|
tools::Long nX;
|
|
if (mnTravelXPos == TRAVEL_X_DONTKNOW)
|
|
{
|
|
nX = mpTextEngine->GetEditCursor(rPaM, false).Left();
|
|
mnTravelXPos = static_cast<sal_uInt16>(nX) + 1;
|
|
}
|
|
else
|
|
nX = mnTravelXPos;
|
|
|
|
TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara());
|
|
std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
|
|
if ( nLine < ( pPPortion->GetLines().size() - 1 ) )
|
|
{
|
|
aPaM.GetIndex() = 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() < (mpTextEngine->mpDoc->GetNodes().size() - 1)) // next paragraph
|
|
{
|
|
aPaM.GetPara()++;
|
|
pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
|
|
aPaM.GetIndex() = 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 = 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 = 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 = 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>(mpTextEngine->mpDoc->GetNodes().size() - 1);
|
|
TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[nNode].get();
|
|
TextPaM aPaM( nNode, pNode->GetText().getLength() );
|
|
return aPaM;
|
|
}
|
|
|
|
TextPaM TextView::PageUp( const TextPaM& rPaM )
|
|
{
|
|
tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM);
|
|
Point aTopLeft = aRect.TopLeft();
|
|
aTopLeft.AdjustY(-(mpWindow->GetOutputSizePixel().Height() * 9 / 10));
|
|
aTopLeft.AdjustX(1 );
|
|
if ( aTopLeft.Y() < 0 )
|
|
aTopLeft.setY( 0 );
|
|
|
|
TextPaM aPaM = mpTextEngine->GetPaM(aTopLeft);
|
|
return aPaM;
|
|
}
|
|
|
|
TextPaM TextView::PageDown( const TextPaM& rPaM )
|
|
{
|
|
tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM);
|
|
Point aBottomRight = aRect.BottomRight();
|
|
aBottomRight.AdjustY(mpWindow->GetOutputSizePixel().Height() * 9 / 10);
|
|
aBottomRight.AdjustX(1 );
|
|
tools::Long nHeight = mpTextEngine->GetTextHeight();
|
|
if ( aBottomRight.Y() > nHeight )
|
|
aBottomRight.setY( nHeight-1 );
|
|
|
|
TextPaM aPaM = mpTextEngine->GetPaM(aBottomRight);
|
|
return aPaM;
|
|
}
|
|
|
|
void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial )
|
|
{
|
|
if (mpTextEngine->IsFormatting())
|
|
return;
|
|
if (!mpTextEngine->GetUpdateMode())
|
|
return;
|
|
if (mpTextEngine->IsInUndo())
|
|
return;
|
|
|
|
mpTextEngine->CheckIdleFormatter();
|
|
if (!mpTextEngine->IsFormatted())
|
|
mpTextEngine->FormatAndUpdate(this);
|
|
|
|
TextPaM aPaM(maSelection.GetEnd());
|
|
tools::Rectangle aEditCursor = mpTextEngine->PaMtoEditCursor(aPaM, bSpecial);
|
|
|
|
// Remember that we placed the cursor behind the last character of a line
|
|
mbCursorAtEndOfLine = false;
|
|
if( bSpecial )
|
|
{
|
|
TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
|
|
mbCursorAtEndOfLine = pParaPortion->GetLineNumber(aPaM.GetIndex(), true)
|
|
!= pParaPortion->GetLineNumber(aPaM.GetIndex(), false);
|
|
}
|
|
|
|
if (!IsInsertMode() && !maSelection.HasRange())
|
|
{
|
|
TextNode* pNode = 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(mpTextEngine->GetEditCursor(aPaM, false, true).Left());
|
|
aEditCursor.SetRight( aEditCursor.Left() );
|
|
|
|
TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
|
|
|
|
sal_Int32 nTextPortionStart = 0;
|
|
std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
|
|
TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
|
|
if ( rTextPortion.GetKind() == PORTIONKIND_TAB )
|
|
{
|
|
aEditCursor.AdjustRight(rTextPortion.GetWidth() );
|
|
}
|
|
else
|
|
{
|
|
TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
|
|
aEditCursor.SetRight(mpTextEngine->GetEditCursor(aNext, true).Left());
|
|
}
|
|
}
|
|
}
|
|
|
|
Size aOutSz = 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()
|
|
)
|
|
{
|
|
tools::Long nVisStartY = maStartDocPos.Y();
|
|
tools::Long nVisEndY = maStartDocPos.Y() + aOutSz.Height();
|
|
tools::Long nVisStartX = maStartDocPos.X();
|
|
tools::Long nVisEndX = maStartDocPos.X() + aOutSz.Width();
|
|
tools::Long nMoreX = aOutSz.Width() / 4;
|
|
|
|
Point aNewStartPos(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 = mpTextEngine->GetMaxTextWidth();
|
|
// if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) )
|
|
// nMaxTextWidth = 0x7FFFFFFF;
|
|
// long nMaxX = (long)nMaxTextWidth - aOutSz.Width();
|
|
tools::Long nMaxX = 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
|
|
tools::Long nYMax = mpTextEngine->GetTextHeight() - aOutSz.Height();
|
|
if ( nYMax < 0 )
|
|
nYMax = 0;
|
|
if ( aNewStartPos.Y() > nYMax )
|
|
aNewStartPos.setY( nYMax );
|
|
|
|
if (aNewStartPos != maStartDocPos)
|
|
Scroll(-(aNewStartPos.X() - maStartDocPos.X()), -(aNewStartPos.Y() - maStartDocPos.Y()));
|
|
}
|
|
|
|
if ( aEditCursor.Right() < aEditCursor.Left() )
|
|
{
|
|
tools::Long n = aEditCursor.Left();
|
|
aEditCursor.SetLeft( aEditCursor.Right() );
|
|
aEditCursor.SetRight( n );
|
|
}
|
|
|
|
Point aPoint(GetWindowPos(!mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft()
|
|
: aEditCursor.TopRight()));
|
|
mpCursor->SetPos(aPoint);
|
|
mpCursor->SetSize(aEditCursor.GetSize());
|
|
if (bForceVisCursor && mbCursorEnabled)
|
|
mpCursor->Show();
|
|
}
|
|
|
|
void TextView::SetCursorAtPoint( const Point& rPosPixel )
|
|
{
|
|
mpTextEngine->CheckIdleFormatter();
|
|
|
|
Point aDocPos = GetDocPos( rPosPixel );
|
|
|
|
TextPaM aPaM = mpTextEngine->GetPaM(aDocPos);
|
|
|
|
// aTmpNewSel: Diff between old and new; not the new selection
|
|
TextSelection aTmpNewSel(maSelection.GetEnd(), aPaM);
|
|
TextSelection aNewSel(maSelection);
|
|
aNewSel.GetEnd() = aPaM;
|
|
|
|
if (!mpSelEngine->HasAnchor())
|
|
{
|
|
if (maSelection.GetStart() != aPaM)
|
|
mpTextEngine->CursorMoved(maSelection.GetStart().GetPara());
|
|
aNewSel.GetStart() = aPaM;
|
|
ImpSetSelection( aNewSel );
|
|
}
|
|
else
|
|
{
|
|
ImpSetSelection( aNewSel );
|
|
ShowSelection( aTmpNewSel );
|
|
}
|
|
|
|
bool bForceCursor = !mpDDInfo; // && !mbInSelection
|
|
ImpShowCursor(mbAutoScroll, bForceCursor, false);
|
|
}
|
|
|
|
bool TextView::IsSelectionAtPoint( const Point& rPosPixel )
|
|
{
|
|
Point aDocPos = GetDocPos( rPosPixel );
|
|
TextPaM aPaM = 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 ) const
|
|
{
|
|
TextSelection aSel = 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 (mpDDInfo && mpDDInfo->mbVisCursor)
|
|
{
|
|
mpDDInfo->maCursor.Hide();
|
|
mpDDInfo->mbVisCursor = false;
|
|
}
|
|
}
|
|
|
|
void TextView::ImpShowDDCursor()
|
|
{
|
|
if (!mpDDInfo->mbVisCursor)
|
|
{
|
|
tools::Rectangle aCursor = mpTextEngine->PaMtoEditCursor(mpDDInfo->maDropPos, true);
|
|
aCursor.AdjustRight( 1 );
|
|
aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );
|
|
|
|
mpDDInfo->maCursor.SetWindow(mpWindow);
|
|
mpDDInfo->maCursor.SetPos(aCursor.TopLeft());
|
|
mpDDInfo->maCursor.SetSize(aCursor.GetSize());
|
|
mpDDInfo->maCursor.Show();
|
|
mpDDInfo->mbVisCursor = true;
|
|
}
|
|
}
|
|
|
|
void TextView::SetPaintSelection( bool bPaint )
|
|
{
|
|
if (bPaint != mbPaintSelection)
|
|
{
|
|
mbPaintSelection = bPaint;
|
|
ShowSelection(maSelection);
|
|
}
|
|
}
|
|
|
|
void TextView::Read( SvStream& rInput )
|
|
{
|
|
mpTextEngine->Read(rInput, &maSelection);
|
|
ShowCursor();
|
|
}
|
|
|
|
bool TextView::ImplTruncateNewText( OUString& rNewText ) const
|
|
{
|
|
bool bTruncated = false;
|
|
|
|
const sal_Int32 nMaxLen = mpTextEngine->GetMaxTextLen();
|
|
// 0 means unlimited
|
|
if( nMaxLen != 0 )
|
|
{
|
|
const sal_Int32 nCurLen = mpTextEngine->GetTextLen();
|
|
|
|
const sal_Int32 nNewLen = rNewText.getLength();
|
|
if ( nCurLen + nNewLen > nMaxLen )
|
|
{
|
|
// see how much text will be replaced
|
|
const sal_Int32 nSelLen = mpTextEngine->GetTextLen(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( std::u16string_view rNewText ) const
|
|
{
|
|
bool bOK = true;
|
|
if (mpTextEngine->GetMaxTextLen())
|
|
{
|
|
sal_Int32 n = mpTextEngine->GetTextLen() + rNewText.size();
|
|
if (n > mpTextEngine->GetMaxTextLen())
|
|
{
|
|
// calculate how much text is being deleted
|
|
n -= mpTextEngine->GetTextLen(maSelection);
|
|
if (n > mpTextEngine->GetMaxTextLen())
|
|
bOK = false;
|
|
}
|
|
}
|
|
return bOK;
|
|
}
|
|
|
|
void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
|
|
{
|
|
if (!mbClickedInSelection)
|
|
return;
|
|
|
|
SolarMutexGuard aVclGuard;
|
|
|
|
SAL_WARN_IF(!maSelection.HasRange(), "vcl",
|
|
"TextView::dragGestureRecognized: mbClickedInSelection, but no selection?");
|
|
|
|
mpDDInfo.reset(new TextDDInfo);
|
|
mpDDInfo->mbStarterOfDD = true;
|
|
|
|
rtl::Reference<TETextDataObject> pDataObj = new TETextDataObject( GetSelected() );
|
|
|
|
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, mxDnDListener);
|
|
}
|
|
|
|
void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& )
|
|
{
|
|
ImpHideDDCursor();
|
|
mpDDInfo.reset();
|
|
}
|
|
|
|
void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
|
|
{
|
|
SolarMutexGuard aVclGuard;
|
|
|
|
if (!mbReadOnly && mpDDInfo)
|
|
{
|
|
ImpHideDDCursor();
|
|
|
|
// Data for deleting after DROP_MOVE:
|
|
TextSelection aPrevSel(maSelection);
|
|
aPrevSel.Justify();
|
|
const sal_uInt32 nPrevParaCount = mpTextEngine->GetParagraphCount();
|
|
const sal_Int32 nPrevStartParaLen = mpTextEngine->GetTextLen(aPrevSel.GetStart().GetPara());
|
|
|
|
bool bStarterOfDD = false;
|
|
for (sal_uInt16 nView = mpTextEngine->GetViewCount(); nView && !bStarterOfDD; )
|
|
bStarterOfDD = mpTextEngine->GetView( --nView )->mpDDInfo && mpTextEngine->GetView( nView )->mpDDInfo->mbStarterOfDD;
|
|
|
|
HideSelection();
|
|
ImpSetSelection(mpDDInfo->maDropPos);
|
|
|
|
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);
|
|
|
|
if ( ImplCheckTextLen( aText ) )
|
|
ImpSetSelection(mpTextEngine->ImpInsertText(mpDDInfo->maDropPos, aText));
|
|
|
|
if ( aPrevSel.HasRange() &&
|
|
(( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) )
|
|
{
|
|
// adjust selection if necessary
|
|
if ((mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara()) ||
|
|
((mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara())
|
|
&& (mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex())))
|
|
{
|
|
const sal_uInt32 nNewParasBeforeSelection
|
|
= mpTextEngine->GetParagraphCount() - nPrevParaCount;
|
|
|
|
aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection;
|
|
aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection;
|
|
|
|
if (mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara())
|
|
{
|
|
const sal_Int32 nNewChars
|
|
= 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 = maSelection.GetStart();
|
|
aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() );
|
|
if (aPrevSel.GetEnd().GetPara() == mpDDInfo->maDropPos.GetPara())
|
|
{
|
|
aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex();
|
|
if (aPrevSel.GetStart().GetPara() == mpDDInfo->maDropPos.GetPara())
|
|
aPaM.GetIndex() += aPrevSel.GetStart().GetIndex();
|
|
}
|
|
ImpSetSelection( aPaM );
|
|
|
|
}
|
|
mpTextEngine->ImpDeleteText(aPrevSel);
|
|
}
|
|
|
|
mpTextEngine->UndoActionEnd();
|
|
|
|
mpDDInfo.reset();
|
|
|
|
mpTextEngine->FormatAndUpdate(this);
|
|
|
|
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 (!mpDDInfo)
|
|
mpDDInfo.reset(new TextDDInfo);
|
|
|
|
TextPaM aPrevDropPos = mpDDInfo->maDropPos;
|
|
Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
|
|
Point aDocPos = GetDocPos( aMousePos );
|
|
mpDDInfo->maDropPos = mpTextEngine->GetPaM(aDocPos);
|
|
|
|
// Don't drop in selection or in read only engine
|
|
if (IsReadOnly() || IsInSelection(mpDDInfo->maDropPos))
|
|
{
|
|
ImpHideDDCursor();
|
|
rDTDE.Context->rejectDrag();
|
|
}
|
|
else
|
|
{
|
|
// delete old Cursor
|
|
if (!mpDDInfo->mbVisCursor || (aPrevDropPos != mpDDInfo->maDropPos))
|
|
{
|
|
ImpHideDDCursor();
|
|
ImpShowDDCursor();
|
|
}
|
|
rDTDE.Context->acceptDrag( rDTDE.DropAction );
|
|
}
|
|
}
|
|
|
|
Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const
|
|
{
|
|
Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() );
|
|
if (mpTextEngine->IsRightToLeft())
|
|
{
|
|
Size aSz = 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() + maStartDocPos.Y());
|
|
|
|
if (!mpTextEngine->IsRightToLeft())
|
|
{
|
|
aPoint.setX(rWindowPos.X() + maStartDocPos.X());
|
|
}
|
|
else
|
|
{
|
|
Size aSz = mpWindow->GetOutputSizePixel();
|
|
aPoint.setX((aSz.Width() - 1) - rWindowPos.X() + maStartDocPos.X());
|
|
}
|
|
|
|
return aPoint;
|
|
}
|
|
|
|
Point TextView::GetWindowPos( const Point& rDocPos ) const
|
|
{
|
|
// Document Position => Window Position
|
|
|
|
Point aPoint;
|
|
|
|
aPoint.setY(rDocPos.Y() - maStartDocPos.Y());
|
|
|
|
if (!mpTextEngine->IsRightToLeft())
|
|
{
|
|
aPoint.setX(rDocPos.X() - maStartDocPos.X());
|
|
}
|
|
else
|
|
{
|
|
Size aSz = mpWindow->GetOutputSizePixel();
|
|
aPoint.setX((aSz.Width() - 1) - (rDocPos.X() - maStartDocPos.X()));
|
|
}
|
|
|
|
return aPoint;
|
|
}
|
|
|
|
sal_Int32 TextView::GetLineNumberOfCursorInSelection() const
|
|
{
|
|
// PROGRESS
|
|
sal_Int32 nLineNo = -1;
|
|
if (mbCursorEnabled)
|
|
{
|
|
TextPaM aPaM = GetSelection().GetEnd();
|
|
TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
|
|
nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
|
|
//TODO: std::vector<TextLine>::size_type -> sal_Int32!
|
|
if (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->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 mpTextEngine; }
|
|
|
|
vcl::Window* TextView::GetWindow() const { return mpWindow; }
|
|
|
|
void TextView::EnableCursor(bool bEnable) { mbCursorEnabled = bEnable; }
|
|
|
|
bool TextView::IsCursorEnabled() const { return mbCursorEnabled; }
|
|
|
|
void TextView::SetStartDocPos(const Point& rPos) { maStartDocPos = rPos; }
|
|
|
|
const Point& TextView::GetStartDocPos() const { return maStartDocPos; }
|
|
|
|
void TextView::SetAutoIndentMode(bool bAutoIndent) { mbAutoIndent = bAutoIndent; }
|
|
|
|
bool TextView::IsReadOnly() const { return mbReadOnly; }
|
|
|
|
void TextView::SetAutoScroll(bool bAutoScroll) { mbAutoScroll = bAutoScroll; }
|
|
|
|
bool TextView::IsAutoScroll() const { return mbAutoScroll; }
|
|
|
|
bool TextView::HasSelection() const { return maSelection.HasRange(); }
|
|
|
|
bool TextView::IsInsertMode() const { return mbInsertMode; }
|
|
|
|
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 );
|
|
}
|
|
|
|
void TextView::CenterPaM( const TextPaM& rPaM )
|
|
{
|
|
// Get textview size and the corresponding y-coordinates
|
|
Size aOutSz = mpWindow->GetOutputSizePixel();
|
|
tools::Long nVisStartY = maStartDocPos.Y();
|
|
tools::Long nVisEndY = maStartDocPos.Y() + aOutSz.Height();
|
|
|
|
// Retrieve the coordinates of the PaM
|
|
tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM);
|
|
|
|
// Recalculate the offset of the center y-coordinates and scroll
|
|
Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY());
|
|
}
|
|
|
|
bool TextView::Search( const i18nutil::SearchOptions2& 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 );
|
|
// tdf#49482: Move the start of the selection to the center of the textview
|
|
if (bFound)
|
|
{
|
|
CenterPaM( aSel.GetStart() );
|
|
}
|
|
ShowCursor();
|
|
|
|
return bFound;
|
|
}
|
|
|
|
sal_uInt16 TextView::Replace( const i18nutil::SearchOptions2& 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 );
|
|
// tdf#64690 - extend selection to include inserted text portions
|
|
if ( aSel.GetEnd().GetPara() == aSearchSel.GetEnd().GetPara() )
|
|
{
|
|
aSearchSel.GetEnd().GetIndex() += rSearchOptions.replaceString.getLength() - 1;
|
|
}
|
|
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 );
|
|
}
|
|
|
|
void TextView::ToggleComment()
|
|
{
|
|
/* To determines whether to add or remove comment markers, the rule is:
|
|
* - If any of the lines in the selection does not start with a comment character "'"
|
|
* or "REM" then the selection is commented
|
|
* - Otherwise, the selection is uncommented (i.e. if all of the lines start with a
|
|
* comment marker "'" or "REM")
|
|
* - Empty lines, or lines with only blank spaces or tabs are ignored
|
|
*/
|
|
|
|
TextEngine* pEngine = GetTextEngine();
|
|
TextSelection aSel = GetSelection();
|
|
sal_uInt32 nStartPara = aSel.GetStart().GetPara();
|
|
sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
|
|
|
|
// True = Comment character will be added; False = Comment marker will be removed
|
|
bool bAddCommentChar = false;
|
|
|
|
// Indicates whether any change has been made
|
|
bool bChanged = false;
|
|
|
|
// Indicates whether the selection is downwards (normal) or upwards (reversed)
|
|
bool bSelReversed = false;
|
|
|
|
if (nEndPara < nStartPara)
|
|
{
|
|
std::swap(nStartPara, nEndPara);
|
|
bSelReversed = true;
|
|
}
|
|
|
|
for (sal_uInt32 n = nStartPara; n <= nEndPara; n++)
|
|
{
|
|
OUString sText = pEngine->GetText(n).trim();
|
|
|
|
// Empty lines or lines with only blank spaces and tabs are ignored
|
|
if (sText.isEmpty())
|
|
continue;
|
|
|
|
if (!sText.startsWith("'") && !sText.startsWithIgnoreAsciiCase("REM"))
|
|
{
|
|
bAddCommentChar = true;
|
|
break;
|
|
}
|
|
|
|
// Notice that a REM comment is only actually a comment if:
|
|
// a) There is no subsequent character or
|
|
// b) The subsequent character is a blank space or a tab
|
|
std::u16string_view sRest;
|
|
if (sText.startsWithIgnoreAsciiCase("REM", &sRest))
|
|
{
|
|
if (sRest.size() > 0 && !o3tl::starts_with(sRest, u" ") && !o3tl::starts_with(sRest, u"\t"))
|
|
{
|
|
bAddCommentChar = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAddCommentChar)
|
|
{
|
|
// For each line, determine the first position where there is a character that is not
|
|
// a blank space or a tab; the comment marker will be the smallest such position
|
|
size_t nCommentPos = std::string::npos;
|
|
|
|
for (sal_uInt32 n = nStartPara; n <= nEndPara; n++)
|
|
{
|
|
OUString sText = pEngine->GetText(n);
|
|
std::u16string_view sLine(sText);
|
|
sal_uInt32 nCharPos = sLine.find_first_not_of(u" \t");
|
|
|
|
// Update the position where to place the comment marker
|
|
if (nCharPos < nCommentPos)
|
|
nCommentPos = nCharPos;
|
|
|
|
// If the comment position is zero, then there's no more need to keep searching
|
|
if (nCommentPos == 0)
|
|
break;
|
|
}
|
|
|
|
// Insert the comment marker in all lines (except empty lines)
|
|
for (sal_uInt32 n = nStartPara; n <= nEndPara; n++)
|
|
{
|
|
OUString sText = pEngine->GetText(n);
|
|
std::u16string_view sLine(sText);
|
|
if (o3tl::trim(sLine).length() > 0)
|
|
{
|
|
pEngine->ImpInsertText(TextPaM(n, nCommentPos), u"' "_ustr);
|
|
bChanged = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For each line, find the first comment marker and remove it
|
|
for (sal_uInt32 nPara = nStartPara; nPara <= nEndPara; nPara++)
|
|
{
|
|
OUString sText = pEngine->GetText(nPara);
|
|
if (!sText.isEmpty())
|
|
{
|
|
// Determine the position of the comment marker and check whether it's
|
|
// a single quote "'" or a "REM" comment
|
|
sal_Int32 nQuotePos = sText.indexOf("'");
|
|
sal_Int32 nRemPos = sText.toAsciiUpperCase().indexOf("REM");
|
|
|
|
// An empty line or a line with only blank spaces or tabs needs to be skipped
|
|
if (nQuotePos == -1 && nRemPos == -1)
|
|
continue;
|
|
|
|
// nRemPos only refers to a comment if the subsequent character is a blank space or tab
|
|
const sal_Int32 nRemSub = nRemPos + 3;
|
|
if (nRemPos != -1 && nRemPos < sText.getLength() - 3 &&
|
|
sText.indexOf(" ", nRemSub) != nRemSub &&
|
|
sText.indexOf("\t", nRemSub) != nRemSub)
|
|
{
|
|
nRemPos = -1;
|
|
}
|
|
|
|
// True = comment uses single quote; False = comment uses REM
|
|
bool bQuoteComment = true;
|
|
|
|
// Start and end positions to be removed
|
|
sal_Int32 nStartPos = nQuotePos;
|
|
sal_Int32 nEndPos = nStartPos + 1;
|
|
|
|
if (nQuotePos == -1)
|
|
bQuoteComment = false;
|
|
else if (nRemPos != -1 && nRemPos < nQuotePos)
|
|
bQuoteComment = false;
|
|
|
|
if (!bQuoteComment)
|
|
{
|
|
nStartPos = nRemPos;
|
|
nEndPos = nStartPos + 3;
|
|
}
|
|
|
|
// Check if the next character is a blank space or a tab
|
|
if (sText.indexOf(" ", nEndPos) == nEndPos || sText.indexOf("\t", nEndPos) == nEndPos)
|
|
nEndPos++;
|
|
|
|
// Remove the comment marker
|
|
pEngine->ImpDeleteText(TextSelection(TextPaM(nPara, nStartPos), TextPaM(nPara, nEndPos)));
|
|
bChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update selection if there was a selection in the first place
|
|
if (bChanged)
|
|
{
|
|
TextPaM aNewStart;
|
|
if (!bSelReversed)
|
|
aNewStart = TextPaM(nStartPara, std::min(aSel.GetStart().GetIndex(),
|
|
pEngine->GetText(nStartPara).getLength()));
|
|
else
|
|
aNewStart = TextPaM(nStartPara, std::min(aSel.GetEnd().GetIndex(),
|
|
pEngine->GetText(nEndPara).getLength()));
|
|
|
|
if (HasSelection())
|
|
{
|
|
TextPaM aNewEnd;
|
|
if (!bSelReversed)
|
|
aNewEnd = TextPaM(nEndPara, pEngine->GetText(nEndPara).getLength());
|
|
else
|
|
aNewEnd = TextPaM(nEndPara, pEngine->GetText(nStartPara).getLength());
|
|
|
|
TextSelection aNewSel(aNewStart, aNewEnd);
|
|
ImpSetSelection(aNewSel);
|
|
}
|
|
else
|
|
{
|
|
TextSelection aNewSel(aNewStart, aNewStart);
|
|
ImpSetSelection(aNewSel);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|